Customize Nodes
Create custom nodes with input and output ports for the Node Editor
Overview#
Nodes are visual programming blocks in the Node Editor. They process inputs and produce outputs, allowing users to create complex data flows without writing code.
Basic Node#
from witwin.node_editor import Node, input_port, output_port
class AddNode(Node):
node_type = "add"
display_name = "Add"
category = "Math"
# Input ports
a = input_port("float", default=0.0, description="First operand")
b = input_port("float", default=0.0, description="Second operand")
# Output port
result = output_port("float", description="Sum of a and b")
def execute(self, a: float, b: float) -> float:
return a + bNode Class Attributes#
| Attribute | Description |
|---|---|
node_type | Unique identifier (e.g., "math/add") |
display_name | Name shown in UI |
category | Category in node library |
Input Ports#
Input ports receive data from connected nodes or default values.
input_port(
port_type: str, # Data type
default=None, # Default value when unconnected
description="" # Tooltip description
)Port Types#
| Type | Description | Default |
|---|---|---|
float | Floating point number | 0.0 |
int | Integer number | 0 |
bool | Boolean | False |
string | Text string | "" |
vector2 | 2D vector [x, y] | [0, 0] |
vector3 | 3D vector [x, y, z] | [0, 0, 0] |
vector4 | 4D vector [x, y, z, w] | [0, 0, 0, 0] |
color | RGBA color | [1, 1, 1, 1] |
texture2D | 2D texture reference | None |
tensor | N-dimensional array | None |
matrix | Matrix data | None |
Examples#
class MathNode(Node):
# Numeric inputs
value = input_port("float", default=1.0, description="Input value")
count = input_port("int", default=10)
# Boolean input
enabled = input_port("bool", default=True)
# Vector inputs
position = input_port("vector3", default=[0, 0, 0])
uv = input_port("vector2", default=[0, 0])
# Color input
color = input_port("color", default=[1, 1, 1, 1])Output Ports#
Output ports produce data that can be connected to other nodes.
output_port(
port_type: str, # Data type
description="" # Tooltip description
)Examples#
class ProcessorNode(Node):
# Single output
result = output_port("float", description="Processed result")
# Multiple outputs
position = output_port("vector3", description="World position")
normal = output_port("vector3", description="Surface normal")
distance = output_port("float", description="Distance from origin")Execute Method#
The execute method processes inputs and returns outputs:
class MultiplyNode(Node):
node_type = "multiply"
display_name = "Multiply"
category = "Math"
a = input_port("float", default=1.0)
b = input_port("float", default=1.0)
result = output_port("float")
def execute(self, a: float, b: float) -> float:
return a * bMultiple Outputs#
Return a tuple matching the order of output port definitions:
class SplitVectorNode(Node):
node_type = "split_vector"
display_name = "Split Vector"
category = "Vector"
vector = input_port("vector3", default=[0, 0, 0])
x = output_port("float")
y = output_port("float")
z = output_port("float")
def execute(self, vector: list) -> tuple:
return vector[0], vector[1], vector[2]Nodes with Fields#
Add editable parameters using component fields:
from witwin.node_editor import Node, input_port, output_port
from witwin.components import float_field, string_field, int_field, bool_field
class NoiseNode(Node):
node_type = "noise_generator"
display_name = "Noise Generator"
category = "Generators"
# Output
noise = output_port("float", description="Generated noise value")
# Editable fields (shown in node body)
noise_type = string_field(
"perlin",
options=["perlin", "simplex", "worley", "value"],
description="Noise algorithm"
)
scale = float_field(1.0, min=0.1, max=10.0)
octaves = int_field(4, min=1, max=8)
seed = int_field(0, min=0, max=9999)
def execute(self) -> float:
# Use self.noise_type, self.scale, etc.
return self.generate_noise()Nodes with Buttons#
Add action buttons to nodes:
from witwin.components import button, image_field
class PreviewNode(Node):
node_type = "preview"
display_name = "Preview"
category = "Output"
data = input_port("tensor")
preview = image_field(description="Preview image")
@button(display_name="Render Preview")
def render(self):
if self.data is not None:
self.preview = self.data
return "Preview rendered"Port Colors#
Ports are color-coded by type in the Node Editor:
| Type | Color |
|---|---|
float | Gray |
int | Gray |
vector2 | Green |
vector3 | Yellow |
vector4 | Pink |
bool | Purple |
color | Gradient |
texture2D | Blue |
tensor | Orange |
Node Categories#
Organize nodes into categories for the library:
class AddNode(Node):
category = "Math" # Shows under Math category
class SineNode(Node):
category = "Math/Trigonometry" # SubcategoryCommon categories:
Math- Mathematical operationsVector- Vector operationsColor- Color manipulationGenerators- Value generatorsInput- Scene inputsOutput- Results and visualizationLogic- Boolean operations
Complete Examples#
Math Node#
from witwin.node_editor import Node, input_port, output_port
from witwin.components import string_field
class MathOperationNode(Node):
node_type = "math_operation"
display_name = "Math"
category = "Math"
a = input_port("float", default=0.0)
b = input_port("float", default=0.0)
result = output_port("float")
operation = string_field(
"add",
options=["add", "subtract", "multiply", "divide", "power"],
enum_toggle=True
)
def execute(self, a: float, b: float) -> float:
if self.operation == "add":
return a + b
elif self.operation == "subtract":
return a - b
elif self.operation == "multiply":
return a * b
elif self.operation == "divide":
return a / b if b != 0 else 0
elif self.operation == "power":
return a ** b
return 0Vector Node#
class CombineVectorNode(Node):
node_type = "combine_vector"
display_name = "Combine XYZ"
category = "Vector"
x = input_port("float", default=0.0, description="X component")
y = input_port("float", default=0.0, description="Y component")
z = input_port("float", default=0.0, description="Z component")
vector = output_port("vector3", description="Combined vector")
def execute(self, x: float, y: float, z: float) -> list:
return [x, y, z]Color Node#
class ColorMixNode(Node):
node_type = "color_mix"
display_name = "Mix Colors"
category = "Color"
color_a = input_port("color", default=[1, 0, 0, 1])
color_b = input_port("color", default=[0, 0, 1, 1])
factor = input_port("float", default=0.5)
result = output_port("color")
def execute(self, color_a: list, color_b: list, factor: float) -> list:
factor = max(0, min(1, factor))
return [
color_a[0] * (1 - factor) + color_b[0] * factor,
color_a[1] * (1 - factor) + color_b[1] * factor,
color_a[2] * (1 - factor) + color_b[2] * factor,
color_a[3] * (1 - factor) + color_b[3] * factor,
]Generator Node with Preview#
from witwin.components import button, image_field, float_field, int_field
import numpy as np
class GradientNode(Node):
node_type = "gradient_generator"
display_name = "Gradient"
category = "Generators"
texture = output_port("texture2D")
width = int_field(256, min=16, max=2048)
height = int_field(256, min=16, max=2048)
angle = float_field(0.0, min=0, max=360)
preview = image_field(hide_label=True)
@button(display_name="Generate")
def generate(self):
w, h = self.width, self.height
gradient = np.zeros((h, w, 3), dtype=np.uint8)
for x in range(w):
gradient[:, x] = [int(255 * x / w)] * 3
self.preview = gradient
return "Gradient generated"
def execute(self):
return self.previewDifferentiable Nodes#
For optimization workflows, nodes can be marked as differentiable. Connect nodes through differentiable edges (blue dashed lines) from the Hyperparameters panel to enable automatic differentiation.
class DifferentiableNode(Node):
node_type = "differentiable_op"
display_name = "Differentiable"
category = "Optimization"
# Inputs can receive gradients
param = input_port("float", default=1.0)
result = output_port("float")
def execute(self, param: float) -> float:
# This operation will be tracked for differentiation
return param ** 2