feat(node-editor, blueprint): add group box and math/logic nodes (#438)

* feat(node-editor, blueprint): add group box and math/logic nodes

node-editor:
- Add visual group box for organizing nodes
- Dynamic bounds calculation based on node pin counts
- Groups auto-resize to wrap contained nodes
- Dragging group header moves all nodes together

blueprint:
- Add comprehensive math nodes (modulo, power, sqrt, trig, etc.)
- Add logic nodes (comparison, boolean, select)

docs:
- Update nodes.md with new math and logic nodes
- Add group feature documentation to editor-guide.md

* chore: remove unused debug and test scripts

Remove FBX animation debug scripts that are no longer needed:
- analyze-fbx, debug-*, test-*, verify-*, check-*, compare-*, trace-*, simple-fbx-test

Remove unused kill-dev-server.js from editor-app
This commit is contained in:
YHH
2026-01-05 11:23:42 +08:00
committed by GitHub
parent 45de62e453
commit 0d33cf0097
39 changed files with 1770 additions and 6487 deletions

View File

@@ -7,6 +7,7 @@
* - ecs: ECS 操作Entity, Component, Flow
* - variables: 变量读写
* - math: 数学运算
* - logic: 比较和逻辑运算
* - time: 时间工具
* - debug: 调试工具
*
@@ -15,6 +16,7 @@
* - ecs: ECS operations (Entity, Component, Flow)
* - variables: Variable get/set
* - math: Math operations
* - logic: Comparison and logical operations
* - time: Time utilities
* - debug: Debug utilities
*/
@@ -31,6 +33,9 @@ export * from './variables';
// Math operations | 数学运算
export * from './math';
// Logic operations | 逻辑运算
export * from './logic';
// Time utilities | 时间工具
export * from './time';

View File

@@ -0,0 +1,435 @@
/**
* @zh 比较运算节点
* @en Comparison Operation Nodes
*/
import { BlueprintNodeTemplate, BlueprintNode } from '../../types/nodes';
import { ExecutionContext, ExecutionResult } from '../../runtime/ExecutionContext';
import { INodeExecutor, RegisterNode } from '../../runtime/NodeRegistry';
// ============================================================================
// Equal Node (等于节点)
// ============================================================================
export const EqualTemplate: BlueprintNodeTemplate = {
type: 'Equal',
title: 'Equal',
category: 'logic',
color: '#9C27B0',
description: 'Returns true if A equals B (如果 A 等于 B 则返回 true)',
keywords: ['equal', '==', 'same', 'compare', 'logic'],
isPure: true,
inputs: [
{ name: 'a', type: 'any', displayName: 'A' },
{ name: 'b', type: 'any', displayName: 'B' }
],
outputs: [
{ name: 'result', type: 'bool', displayName: 'Result' }
]
};
@RegisterNode(EqualTemplate)
export class EqualExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
const a = context.evaluateInput(node.id, 'a', null);
const b = context.evaluateInput(node.id, 'b', null);
return { outputs: { result: a === b } };
}
}
// ============================================================================
// Not Equal Node (不等于节点)
// ============================================================================
export const NotEqualTemplate: BlueprintNodeTemplate = {
type: 'NotEqual',
title: 'Not Equal',
category: 'logic',
color: '#9C27B0',
description: 'Returns true if A does not equal B (如果 A 不等于 B 则返回 true)',
keywords: ['not', 'equal', '!=', 'different', 'compare', 'logic'],
isPure: true,
inputs: [
{ name: 'a', type: 'any', displayName: 'A' },
{ name: 'b', type: 'any', displayName: 'B' }
],
outputs: [
{ name: 'result', type: 'bool', displayName: 'Result' }
]
};
@RegisterNode(NotEqualTemplate)
export class NotEqualExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
const a = context.evaluateInput(node.id, 'a', null);
const b = context.evaluateInput(node.id, 'b', null);
return { outputs: { result: a !== b } };
}
}
// ============================================================================
// Greater Than Node (大于节点)
// ============================================================================
export const GreaterThanTemplate: BlueprintNodeTemplate = {
type: 'GreaterThan',
title: 'Greater Than',
category: 'logic',
color: '#9C27B0',
description: 'Returns true if A is greater than B (如果 A 大于 B 则返回 true)',
keywords: ['greater', 'than', '>', 'compare', 'logic'],
isPure: true,
inputs: [
{ name: 'a', type: 'float', displayName: 'A', defaultValue: 0 },
{ name: 'b', type: 'float', displayName: 'B', defaultValue: 0 }
],
outputs: [
{ name: 'result', type: 'bool', displayName: 'Result' }
]
};
@RegisterNode(GreaterThanTemplate)
export class GreaterThanExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
const a = Number(context.evaluateInput(node.id, 'a', 0));
const b = Number(context.evaluateInput(node.id, 'b', 0));
return { outputs: { result: a > b } };
}
}
// ============================================================================
// Greater Than Or Equal Node (大于等于节点)
// ============================================================================
export const GreaterThanOrEqualTemplate: BlueprintNodeTemplate = {
type: 'GreaterThanOrEqual',
title: 'Greater Or Equal',
category: 'logic',
color: '#9C27B0',
description: 'Returns true if A is greater than or equal to B (如果 A 大于等于 B 则返回 true)',
keywords: ['greater', 'equal', '>=', 'compare', 'logic'],
isPure: true,
inputs: [
{ name: 'a', type: 'float', displayName: 'A', defaultValue: 0 },
{ name: 'b', type: 'float', displayName: 'B', defaultValue: 0 }
],
outputs: [
{ name: 'result', type: 'bool', displayName: 'Result' }
]
};
@RegisterNode(GreaterThanOrEqualTemplate)
export class GreaterThanOrEqualExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
const a = Number(context.evaluateInput(node.id, 'a', 0));
const b = Number(context.evaluateInput(node.id, 'b', 0));
return { outputs: { result: a >= b } };
}
}
// ============================================================================
// Less Than Node (小于节点)
// ============================================================================
export const LessThanTemplate: BlueprintNodeTemplate = {
type: 'LessThan',
title: 'Less Than',
category: 'logic',
color: '#9C27B0',
description: 'Returns true if A is less than B (如果 A 小于 B 则返回 true)',
keywords: ['less', 'than', '<', 'compare', 'logic'],
isPure: true,
inputs: [
{ name: 'a', type: 'float', displayName: 'A', defaultValue: 0 },
{ name: 'b', type: 'float', displayName: 'B', defaultValue: 0 }
],
outputs: [
{ name: 'result', type: 'bool', displayName: 'Result' }
]
};
@RegisterNode(LessThanTemplate)
export class LessThanExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
const a = Number(context.evaluateInput(node.id, 'a', 0));
const b = Number(context.evaluateInput(node.id, 'b', 0));
return { outputs: { result: a < b } };
}
}
// ============================================================================
// Less Than Or Equal Node (小于等于节点)
// ============================================================================
export const LessThanOrEqualTemplate: BlueprintNodeTemplate = {
type: 'LessThanOrEqual',
title: 'Less Or Equal',
category: 'logic',
color: '#9C27B0',
description: 'Returns true if A is less than or equal to B (如果 A 小于等于 B 则返回 true)',
keywords: ['less', 'equal', '<=', 'compare', 'logic'],
isPure: true,
inputs: [
{ name: 'a', type: 'float', displayName: 'A', defaultValue: 0 },
{ name: 'b', type: 'float', displayName: 'B', defaultValue: 0 }
],
outputs: [
{ name: 'result', type: 'bool', displayName: 'Result' }
]
};
@RegisterNode(LessThanOrEqualTemplate)
export class LessThanOrEqualExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
const a = Number(context.evaluateInput(node.id, 'a', 0));
const b = Number(context.evaluateInput(node.id, 'b', 0));
return { outputs: { result: a <= b } };
}
}
// ============================================================================
// And Node (逻辑与节点)
// ============================================================================
export const AndTemplate: BlueprintNodeTemplate = {
type: 'And',
title: 'AND',
category: 'logic',
color: '#9C27B0',
description: 'Returns true if both A and B are true (如果 A 和 B 都为 true 则返回 true)',
keywords: ['and', '&&', 'both', 'logic'],
isPure: true,
inputs: [
{ name: 'a', type: 'bool', displayName: 'A', defaultValue: false },
{ name: 'b', type: 'bool', displayName: 'B', defaultValue: false }
],
outputs: [
{ name: 'result', type: 'bool', displayName: 'Result' }
]
};
@RegisterNode(AndTemplate)
export class AndExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
const a = Boolean(context.evaluateInput(node.id, 'a', false));
const b = Boolean(context.evaluateInput(node.id, 'b', false));
return { outputs: { result: a && b } };
}
}
// ============================================================================
// Or Node (逻辑或节点)
// ============================================================================
export const OrTemplate: BlueprintNodeTemplate = {
type: 'Or',
title: 'OR',
category: 'logic',
color: '#9C27B0',
description: 'Returns true if either A or B is true (如果 A 或 B 为 true 则返回 true)',
keywords: ['or', '||', 'either', 'logic'],
isPure: true,
inputs: [
{ name: 'a', type: 'bool', displayName: 'A', defaultValue: false },
{ name: 'b', type: 'bool', displayName: 'B', defaultValue: false }
],
outputs: [
{ name: 'result', type: 'bool', displayName: 'Result' }
]
};
@RegisterNode(OrTemplate)
export class OrExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
const a = Boolean(context.evaluateInput(node.id, 'a', false));
const b = Boolean(context.evaluateInput(node.id, 'b', false));
return { outputs: { result: a || b } };
}
}
// ============================================================================
// Not Node (逻辑非节点)
// ============================================================================
export const NotTemplate: BlueprintNodeTemplate = {
type: 'Not',
title: 'NOT',
category: 'logic',
color: '#9C27B0',
description: 'Returns the opposite boolean value (返回相反的布尔值)',
keywords: ['not', '!', 'negate', 'invert', 'logic'],
isPure: true,
inputs: [
{ name: 'value', type: 'bool', displayName: 'Value', defaultValue: false }
],
outputs: [
{ name: 'result', type: 'bool', displayName: 'Result' }
]
};
@RegisterNode(NotTemplate)
export class NotExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
const value = Boolean(context.evaluateInput(node.id, 'value', false));
return { outputs: { result: !value } };
}
}
// ============================================================================
// XOR Node (异或节点)
// ============================================================================
export const XorTemplate: BlueprintNodeTemplate = {
type: 'Xor',
title: 'XOR',
category: 'logic',
color: '#9C27B0',
description: 'Returns true if exactly one of A or B is true (如果 A 和 B 中恰好有一个为 true 则返回 true)',
keywords: ['xor', 'exclusive', 'or', 'logic'],
isPure: true,
inputs: [
{ name: 'a', type: 'bool', displayName: 'A', defaultValue: false },
{ name: 'b', type: 'bool', displayName: 'B', defaultValue: false }
],
outputs: [
{ name: 'result', type: 'bool', displayName: 'Result' }
]
};
@RegisterNode(XorTemplate)
export class XorExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
const a = Boolean(context.evaluateInput(node.id, 'a', false));
const b = Boolean(context.evaluateInput(node.id, 'b', false));
return { outputs: { result: (a || b) && !(a && b) } };
}
}
// ============================================================================
// NAND Node (与非节点)
// ============================================================================
export const NandTemplate: BlueprintNodeTemplate = {
type: 'Nand',
title: 'NAND',
category: 'logic',
color: '#9C27B0',
description: 'Returns true if not both A and B are true (如果 A 和 B 不都为 true 则返回 true)',
keywords: ['nand', 'not', 'and', 'logic'],
isPure: true,
inputs: [
{ name: 'a', type: 'bool', displayName: 'A', defaultValue: false },
{ name: 'b', type: 'bool', displayName: 'B', defaultValue: false }
],
outputs: [
{ name: 'result', type: 'bool', displayName: 'Result' }
]
};
@RegisterNode(NandTemplate)
export class NandExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
const a = Boolean(context.evaluateInput(node.id, 'a', false));
const b = Boolean(context.evaluateInput(node.id, 'b', false));
return { outputs: { result: !(a && b) } };
}
}
// ============================================================================
// In Range Node (范围检查节点)
// ============================================================================
export const InRangeTemplate: BlueprintNodeTemplate = {
type: 'InRange',
title: 'In Range',
category: 'logic',
color: '#9C27B0',
description: 'Returns true if value is between min and max (如果值在 min 和 max 之间则返回 true)',
keywords: ['range', 'between', 'check', 'logic'],
isPure: true,
inputs: [
{ name: 'value', type: 'float', displayName: 'Value', defaultValue: 0 },
{ name: 'min', type: 'float', displayName: 'Min', defaultValue: 0 },
{ name: 'max', type: 'float', displayName: 'Max', defaultValue: 1 },
{ name: 'inclusive', type: 'bool', displayName: 'Inclusive', defaultValue: true }
],
outputs: [
{ name: 'result', type: 'bool', displayName: 'Result' }
]
};
@RegisterNode(InRangeTemplate)
export class InRangeExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
const value = Number(context.evaluateInput(node.id, 'value', 0));
const min = Number(context.evaluateInput(node.id, 'min', 0));
const max = Number(context.evaluateInput(node.id, 'max', 1));
const inclusive = Boolean(context.evaluateInput(node.id, 'inclusive', true));
const result = inclusive
? value >= min && value <= max
: value > min && value < max;
return { outputs: { result } };
}
}
// ============================================================================
// Is Null Node (空值检查节点)
// ============================================================================
export const IsNullTemplate: BlueprintNodeTemplate = {
type: 'IsNull',
title: 'Is Null',
category: 'logic',
color: '#9C27B0',
description: 'Returns true if the value is null or undefined (如果值为 null 或 undefined 则返回 true)',
keywords: ['null', 'undefined', 'empty', 'check', 'logic'],
isPure: true,
inputs: [
{ name: 'value', type: 'any', displayName: 'Value' }
],
outputs: [
{ name: 'result', type: 'bool', displayName: 'Is Null' }
]
};
@RegisterNode(IsNullTemplate)
export class IsNullExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
const value = context.evaluateInput(node.id, 'value', null);
return { outputs: { result: value == null } };
}
}
// ============================================================================
// Select Node (选择节点)
// ============================================================================
export const SelectTemplate: BlueprintNodeTemplate = {
type: 'Select',
title: 'Select',
category: 'logic',
color: '#9C27B0',
description: 'Returns A if condition is true, otherwise returns B (如果条件为 true 返回 A否则返回 B)',
keywords: ['select', 'choose', 'ternary', '?:', 'logic'],
isPure: true,
inputs: [
{ name: 'condition', type: 'bool', displayName: 'Condition', defaultValue: false },
{ name: 'a', type: 'any', displayName: 'A (True)' },
{ name: 'b', type: 'any', displayName: 'B (False)' }
],
outputs: [
{ name: 'result', type: 'any', displayName: 'Result' }
]
};
@RegisterNode(SelectTemplate)
export class SelectExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
const condition = Boolean(context.evaluateInput(node.id, 'condition', false));
const a = context.evaluateInput(node.id, 'a', null);
const b = context.evaluateInput(node.id, 'b', null);
return { outputs: { result: condition ? a : b } };
}
}

View File

@@ -0,0 +1,6 @@
/**
* @zh 逻辑节点 - 比较和逻辑运算节点
* @en Logic Nodes - Comparison and logical operation nodes
*/
export * from './ComparisonNodes';

View File

@@ -120,3 +120,444 @@ export class DivideExecutor implements INodeExecutor {
return { outputs: { result: a / b } };
}
}
// ============================================================================
// Modulo Node (取模节点)
// ============================================================================
export const ModuloTemplate: BlueprintNodeTemplate = {
type: 'Modulo',
title: 'Modulo',
category: 'math',
color: '#4CAF50',
description: 'Returns the remainder of A divided by B (返回 A 除以 B 的余数)',
keywords: ['modulo', 'mod', 'remainder', '%', 'math'],
isPure: true,
inputs: [
{ name: 'a', type: 'float', displayName: 'A', defaultValue: 0 },
{ name: 'b', type: 'float', displayName: 'B', defaultValue: 1 }
],
outputs: [
{ name: 'result', type: 'float', displayName: 'Result' }
]
};
@RegisterNode(ModuloTemplate)
export class ModuloExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
const a = Number(context.evaluateInput(node.id, 'a', 0));
const b = Number(context.evaluateInput(node.id, 'b', 1));
if (b === 0) return { outputs: { result: 0 } };
return { outputs: { result: a % b } };
}
}
// ============================================================================
// Absolute Value Node (绝对值节点)
// ============================================================================
export const AbsTemplate: BlueprintNodeTemplate = {
type: 'Abs',
title: 'Absolute',
category: 'math',
color: '#4CAF50',
description: 'Returns the absolute value (返回绝对值)',
keywords: ['abs', 'absolute', 'math'],
isPure: true,
inputs: [
{ name: 'value', type: 'float', displayName: 'Value', defaultValue: 0 }
],
outputs: [
{ name: 'result', type: 'float', displayName: 'Result' }
]
};
@RegisterNode(AbsTemplate)
export class AbsExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
const value = Number(context.evaluateInput(node.id, 'value', 0));
return { outputs: { result: Math.abs(value) } };
}
}
// ============================================================================
// Min Node (最小值节点)
// ============================================================================
export const MinTemplate: BlueprintNodeTemplate = {
type: 'Min',
title: 'Min',
category: 'math',
color: '#4CAF50',
description: 'Returns the smaller of two values (返回两个值中较小的一个)',
keywords: ['min', 'minimum', 'smaller', 'math'],
isPure: true,
inputs: [
{ name: 'a', type: 'float', displayName: 'A', defaultValue: 0 },
{ name: 'b', type: 'float', displayName: 'B', defaultValue: 0 }
],
outputs: [
{ name: 'result', type: 'float', displayName: 'Result' }
]
};
@RegisterNode(MinTemplate)
export class MinExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
const a = Number(context.evaluateInput(node.id, 'a', 0));
const b = Number(context.evaluateInput(node.id, 'b', 0));
return { outputs: { result: Math.min(a, b) } };
}
}
// ============================================================================
// Max Node (最大值节点)
// ============================================================================
export const MaxTemplate: BlueprintNodeTemplate = {
type: 'Max',
title: 'Max',
category: 'math',
color: '#4CAF50',
description: 'Returns the larger of two values (返回两个值中较大的一个)',
keywords: ['max', 'maximum', 'larger', 'math'],
isPure: true,
inputs: [
{ name: 'a', type: 'float', displayName: 'A', defaultValue: 0 },
{ name: 'b', type: 'float', displayName: 'B', defaultValue: 0 }
],
outputs: [
{ name: 'result', type: 'float', displayName: 'Result' }
]
};
@RegisterNode(MaxTemplate)
export class MaxExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
const a = Number(context.evaluateInput(node.id, 'a', 0));
const b = Number(context.evaluateInput(node.id, 'b', 0));
return { outputs: { result: Math.max(a, b) } };
}
}
// ============================================================================
// Clamp Node (限制范围节点)
// ============================================================================
export const ClampTemplate: BlueprintNodeTemplate = {
type: 'Clamp',
title: 'Clamp',
category: 'math',
color: '#4CAF50',
description: 'Clamps a value between min and max (将值限制在最小和最大之间)',
keywords: ['clamp', 'limit', 'range', 'bound', 'math'],
isPure: true,
inputs: [
{ name: 'value', type: 'float', displayName: 'Value', defaultValue: 0 },
{ name: 'min', type: 'float', displayName: 'Min', defaultValue: 0 },
{ name: 'max', type: 'float', displayName: 'Max', defaultValue: 1 }
],
outputs: [
{ name: 'result', type: 'float', displayName: 'Result' }
]
};
@RegisterNode(ClampTemplate)
export class ClampExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
const value = Number(context.evaluateInput(node.id, 'value', 0));
const min = Number(context.evaluateInput(node.id, 'min', 0));
const max = Number(context.evaluateInput(node.id, 'max', 1));
return { outputs: { result: Math.max(min, Math.min(max, value)) } };
}
}
// ============================================================================
// Lerp Node (线性插值节点)
// ============================================================================
export const LerpTemplate: BlueprintNodeTemplate = {
type: 'Lerp',
title: 'Lerp',
category: 'math',
color: '#4CAF50',
description: 'Linear interpolation between A and B (A 和 B 之间的线性插值)',
keywords: ['lerp', 'interpolate', 'blend', 'mix', 'math'],
isPure: true,
inputs: [
{ name: 'a', type: 'float', displayName: 'A', defaultValue: 0 },
{ name: 'b', type: 'float', displayName: 'B', defaultValue: 1 },
{ name: 't', type: 'float', displayName: 'Alpha', defaultValue: 0.5 }
],
outputs: [
{ name: 'result', type: 'float', displayName: 'Result' }
]
};
@RegisterNode(LerpTemplate)
export class LerpExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
const a = Number(context.evaluateInput(node.id, 'a', 0));
const b = Number(context.evaluateInput(node.id, 'b', 1));
const t = Number(context.evaluateInput(node.id, 't', 0.5));
return { outputs: { result: a + (b - a) * t } };
}
}
// ============================================================================
// Random Range Node (随机范围节点)
// ============================================================================
export const RandomRangeTemplate: BlueprintNodeTemplate = {
type: 'RandomRange',
title: 'Random Range',
category: 'math',
color: '#4CAF50',
description: 'Returns a random number between min and max (返回 min 和 max 之间的随机数)',
keywords: ['random', 'range', 'rand', 'math'],
isPure: true,
inputs: [
{ name: 'min', type: 'float', displayName: 'Min', defaultValue: 0 },
{ name: 'max', type: 'float', displayName: 'Max', defaultValue: 1 }
],
outputs: [
{ name: 'result', type: 'float', displayName: 'Result' }
]
};
@RegisterNode(RandomRangeTemplate)
export class RandomRangeExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
const min = Number(context.evaluateInput(node.id, 'min', 0));
const max = Number(context.evaluateInput(node.id, 'max', 1));
return { outputs: { result: min + Math.random() * (max - min) } };
}
}
// ============================================================================
// Random Integer Node (随机整数节点)
// ============================================================================
export const RandomIntTemplate: BlueprintNodeTemplate = {
type: 'RandomInt',
title: 'Random Integer',
category: 'math',
color: '#4CAF50',
description: 'Returns a random integer between min and max inclusive (返回 min 和 max 之间的随机整数,包含边界)',
keywords: ['random', 'int', 'integer', 'rand', 'math'],
isPure: true,
inputs: [
{ name: 'min', type: 'int', displayName: 'Min', defaultValue: 0 },
{ name: 'max', type: 'int', displayName: 'Max', defaultValue: 10 }
],
outputs: [
{ name: 'result', type: 'int', displayName: 'Result' }
]
};
@RegisterNode(RandomIntTemplate)
export class RandomIntExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
const min = Math.floor(Number(context.evaluateInput(node.id, 'min', 0)));
const max = Math.floor(Number(context.evaluateInput(node.id, 'max', 10)));
return { outputs: { result: Math.floor(min + Math.random() * (max - min + 1)) } };
}
}
// ============================================================================
// Power Node (幂运算节点)
// ============================================================================
export const PowerTemplate: BlueprintNodeTemplate = {
type: 'Power',
title: 'Power',
category: 'math',
color: '#4CAF50',
description: 'Returns base raised to the power of exponent (返回底数的指数次幂)',
keywords: ['power', 'pow', 'exponent', '^', 'math'],
isPure: true,
inputs: [
{ name: 'base', type: 'float', displayName: 'Base', defaultValue: 2 },
{ name: 'exponent', type: 'float', displayName: 'Exponent', defaultValue: 2 }
],
outputs: [
{ name: 'result', type: 'float', displayName: 'Result' }
]
};
@RegisterNode(PowerTemplate)
export class PowerExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
const base = Number(context.evaluateInput(node.id, 'base', 2));
const exponent = Number(context.evaluateInput(node.id, 'exponent', 2));
return { outputs: { result: Math.pow(base, exponent) } };
}
}
// ============================================================================
// Square Root Node (平方根节点)
// ============================================================================
export const SqrtTemplate: BlueprintNodeTemplate = {
type: 'Sqrt',
title: 'Square Root',
category: 'math',
color: '#4CAF50',
description: 'Returns the square root (返回平方根)',
keywords: ['sqrt', 'square', 'root', 'math'],
isPure: true,
inputs: [
{ name: 'value', type: 'float', displayName: 'Value', defaultValue: 4 }
],
outputs: [
{ name: 'result', type: 'float', displayName: 'Result' }
]
};
@RegisterNode(SqrtTemplate)
export class SqrtExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
const value = Number(context.evaluateInput(node.id, 'value', 4));
return { outputs: { result: Math.sqrt(Math.abs(value)) } };
}
}
// ============================================================================
// Floor Node (向下取整节点)
// ============================================================================
export const FloorTemplate: BlueprintNodeTemplate = {
type: 'Floor',
title: 'Floor',
category: 'math',
color: '#4CAF50',
description: 'Rounds down to the nearest integer (向下取整)',
keywords: ['floor', 'round', 'down', 'int', 'math'],
isPure: true,
inputs: [
{ name: 'value', type: 'float', displayName: 'Value', defaultValue: 0 }
],
outputs: [
{ name: 'result', type: 'int', displayName: 'Result' }
]
};
@RegisterNode(FloorTemplate)
export class FloorExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
const value = Number(context.evaluateInput(node.id, 'value', 0));
return { outputs: { result: Math.floor(value) } };
}
}
// ============================================================================
// Ceil Node (向上取整节点)
// ============================================================================
export const CeilTemplate: BlueprintNodeTemplate = {
type: 'Ceil',
title: 'Ceil',
category: 'math',
color: '#4CAF50',
description: 'Rounds up to the nearest integer (向上取整)',
keywords: ['ceil', 'ceiling', 'round', 'up', 'int', 'math'],
isPure: true,
inputs: [
{ name: 'value', type: 'float', displayName: 'Value', defaultValue: 0 }
],
outputs: [
{ name: 'result', type: 'int', displayName: 'Result' }
]
};
@RegisterNode(CeilTemplate)
export class CeilExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
const value = Number(context.evaluateInput(node.id, 'value', 0));
return { outputs: { result: Math.ceil(value) } };
}
}
// ============================================================================
// Round Node (四舍五入节点)
// ============================================================================
export const RoundTemplate: BlueprintNodeTemplate = {
type: 'Round',
title: 'Round',
category: 'math',
color: '#4CAF50',
description: 'Rounds to the nearest integer (四舍五入到最近的整数)',
keywords: ['round', 'int', 'math'],
isPure: true,
inputs: [
{ name: 'value', type: 'float', displayName: 'Value', defaultValue: 0 }
],
outputs: [
{ name: 'result', type: 'int', displayName: 'Result' }
]
};
@RegisterNode(RoundTemplate)
export class RoundExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
const value = Number(context.evaluateInput(node.id, 'value', 0));
return { outputs: { result: Math.round(value) } };
}
}
// ============================================================================
// Negate Node (取反节点)
// ============================================================================
export const NegateTemplate: BlueprintNodeTemplate = {
type: 'Negate',
title: 'Negate',
category: 'math',
color: '#4CAF50',
description: 'Returns the negative of a value (返回值的负数)',
keywords: ['negate', 'negative', 'minus', '-', 'math'],
isPure: true,
inputs: [
{ name: 'value', type: 'float', displayName: 'Value', defaultValue: 0 }
],
outputs: [
{ name: 'result', type: 'float', displayName: 'Result' }
]
};
@RegisterNode(NegateTemplate)
export class NegateExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
const value = Number(context.evaluateInput(node.id, 'value', 0));
return { outputs: { result: -value } };
}
}
// ============================================================================
// Sign Node (符号节点)
// ============================================================================
export const SignTemplate: BlueprintNodeTemplate = {
type: 'Sign',
title: 'Sign',
category: 'math',
color: '#4CAF50',
description: 'Returns -1, 0, or 1 based on the sign of the value (根据值的符号返回 -1、0 或 1)',
keywords: ['sign', 'positive', 'negative', 'math'],
isPure: true,
inputs: [
{ name: 'value', type: 'float', displayName: 'Value', defaultValue: 0 }
],
outputs: [
{ name: 'result', type: 'int', displayName: 'Result' }
]
};
@RegisterNode(SignTemplate)
export class SignExecutor implements INodeExecutor {
execute(node: BlueprintNode, context: ExecutionContext): ExecutionResult {
const value = Number(context.evaluateInput(node.id, 'value', 0));
return { outputs: { result: Math.sign(value) } };
}
}