import logging
from typing import Literal, NamedTuple, TypeVar
from procfunc import types as pt
from procfunc.nodes import types as nt
from procfunc.nodes.util.bindings_util import (
ContextualNode,
RuntimeResolveDataType,
raise_io_error,
)
from procfunc.nodes.util.bpy_node_info import NodeDataType
logger = logging.getLogger(__name__)
[docs]
def align_euler_to_vector(
factor: nt.SocketOrVal[float],
vector: nt.SocketOrVal[pt.Vector],
rotation: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
axis: Literal["X", "Y", "Z"] = "X",
pivot_axis: Literal["AUTO", "X", "Y", "Z"] = "AUTO",
) -> nt.ProcNode[pt.Vector]:
"""
Uses a AlignEulerToVector Function Node.
See: http://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/utilities/rotation/align_euler_to_vector.html
"""
return nt.ProcNode.from_nodetype(
node_type="FunctionNodeAlignEulerToVector",
inputs={"Rotation": rotation, "Factor": factor, "Vector": vector},
attrs={"axis": axis, "pivot_axis": pivot_axis},
)
[docs]
def align_rotation_to_vector(
factor: nt.SocketOrVal[float],
vector: nt.SocketOrVal[pt.Vector],
rotation: nt.SocketOrVal[pt.Euler] = (0, 0, 0),
axis: Literal["X", "Y", "Z"] = "Z",
pivot_axis: Literal["AUTO", "X", "Y", "Z"] = "AUTO",
) -> nt.ProcNode[pt.Vector]:
"""
Uses a AlignRotationToVector Function Node.
See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/utilities/rotation/align_rotation_to_vector.html
"""
return nt.ProcNode.from_nodetype(
node_type="FunctionNodeAlignRotationToVector",
inputs={"Rotation": rotation, "Factor": factor, "Vector": vector},
attrs={"axis": axis, "pivot_axis": pivot_axis},
)
[docs]
def axes_to_rotation(
primary_axis_vector: nt.SocketOrVal[pt.Vector],
secondary_axis_vector: nt.SocketOrVal[pt.Vector],
primary_axis: Literal["X", "Y", "Z"] = "X",
secondary_axis: Literal["X", "Y", "Z"] = "Y",
) -> nt.ProcNode[pt.Vector]:
"""
Uses a AxesToRotation Function Node.
See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/utilities/rotation/axis_to_rotation.html
"""
return nt.ProcNode.from_nodetype(
node_type="FunctionNodeAxesToRotation",
inputs={
"Primary Axis": primary_axis_vector,
"Secondary Axis": secondary_axis_vector,
},
attrs={"primary_axis": primary_axis, "secondary_axis": secondary_axis},
)
[docs]
def axis_angle_to_rotation(
axis: nt.SocketOrVal[pt.Vector] = (0, 0, 1),
angle: nt.SocketOrVal[float] = 0.0,
) -> nt.ProcNode[pt.Vector]:
"""
Uses a AxisAngleToRotation Function Node.
See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/utilities/rotation/axis_angle_to_rotation.html
"""
return nt.ProcNode.from_nodetype(
node_type="FunctionNodeAxisAngleToRotation",
inputs={"Axis": axis, "Angle": angle},
attrs={},
)
[docs]
def boolean_or(
a: nt.SocketOrVal[bool],
b: nt.SocketOrVal[bool],
) -> nt.ProcNode[bool]:
return nt.ProcNode.from_nodetype(
node_type="FunctionNodeBooleanMath",
inputs={("Boolean", 0): a, ("Boolean", 1): b},
attrs={"operation": "OR"},
)
[docs]
def boolean_and(
a: nt.SocketOrVal[bool],
b: nt.SocketOrVal[bool],
) -> nt.ProcNode[bool]:
return nt.ProcNode.from_nodetype(
node_type="FunctionNodeBooleanMath",
inputs={("Boolean", 0): a, ("Boolean", 1): b},
attrs={"operation": "AND"},
)
[docs]
def boolean_xor(
a: nt.SocketOrVal[bool],
b: nt.SocketOrVal[bool],
) -> nt.ProcNode[bool]:
return nt.ProcNode.from_nodetype(
node_type="FunctionNodeBooleanMath",
inputs={("Boolean", 0): a, ("Boolean", 1): b},
attrs={"operation": "XOR"},
)
[docs]
def boolean_not(
a: nt.SocketOrVal[bool],
) -> nt.ProcNode[bool]:
"""
Uses a BooleanNot Function Node.
See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/utilities/math/boolean_math.html
"""
return nt.ProcNode.from_nodetype(
node_type="FunctionNodeBooleanMath",
inputs={("Boolean", 0): a},
attrs={"operation": "NOT"},
)
[docs]
def combine_matrix(
column_1_row_1: nt.SocketOrVal[float] = 1.0,
column_1_row_2: nt.SocketOrVal[float] = 0.0,
column_1_row_3: nt.SocketOrVal[float] = 0.0,
column_1_row_4: nt.SocketOrVal[float] = 0.0,
column_2_row_1: nt.SocketOrVal[float] = 0.0,
column_2_row_2: nt.SocketOrVal[float] = 1.0,
column_2_row_3: nt.SocketOrVal[float] = 0.0,
column_2_row_4: nt.SocketOrVal[float] = 0.0,
column_3_row_1: nt.SocketOrVal[float] = 0.0,
column_3_row_2: nt.SocketOrVal[float] = 0.0,
column_3_row_3: nt.SocketOrVal[float] = 1.0,
column_3_row_4: nt.SocketOrVal[float] = 0.0,
column_4_row_1: nt.SocketOrVal[float] = 0.0,
column_4_row_2: nt.SocketOrVal[float] = 0.0,
column_4_row_3: nt.SocketOrVal[float] = 0.0,
column_4_row_4: nt.SocketOrVal[float] = 1.0,
) -> nt.ProcNode[pt.Matrix]:
"""
Uses a CombineMatrix Function Node.
See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/utilities/matrix/combine_matrix.html
"""
return nt.ProcNode.from_nodetype(
node_type="FunctionNodeCombineMatrix",
inputs={
"Column 1 Row 1": column_1_row_1,
"Column 1 Row 2": column_1_row_2,
"Column 1 Row 3": column_1_row_3,
"Column 1 Row 4": column_1_row_4,
"Column 2 Row 1": column_2_row_1,
"Column 2 Row 2": column_2_row_2,
"Column 2 Row 3": column_2_row_3,
"Column 2 Row 4": column_2_row_4,
"Column 3 Row 1": column_3_row_1,
"Column 3 Row 2": column_3_row_2,
"Column 3 Row 3": column_3_row_3,
"Column 3 Row 4": column_3_row_4,
"Column 4 Row 1": column_4_row_1,
"Column 4 Row 2": column_4_row_2,
"Column 4 Row 3": column_4_row_3,
"Column 4 Row 4": column_4_row_4,
},
attrs={},
)
TCompare = TypeVar(
"TCompare",
nt.SocketOrVal[int],
nt.SocketOrVal[pt.Color],
nt.SocketOrVal[str],
nt.SocketOrVal[float],
nt.SocketOrVal[pt.Vector],
)
TCompareOperation = Literal[
"LESS_THAN", "LESS_EQUAL", "GREATER_THAN", "GREATER_EQUAL", "EQUAL", "NOT_EQUAL"
]
# RGBA-only Compare operations, valid only when data_type is RGBA
TCompareColorOperation = Literal["EQUAL", "NOT_EQUAL", "BRIGHTER", "DARKER"]
# matches Blender's FunctionNodeCompare Epsilon socket default
COMPARE_EPSILON_DEFAULT = 0.001
def _compare(
a: TCompare,
b: TCompare,
epsilon: nt.SocketOrVal[float] = COMPARE_EPSILON_DEFAULT,
operation: TCompareOperation | TCompareColorOperation = "EQUAL",
data_type: NodeDataType | RuntimeResolveDataType | None = None,
) -> nt.ProcNode[bool]:
"""
Compares two values, context-dispatching to FunctionNodeCompare in geometry
trees (full data_type support incl. INT) and Math nodes elsewhere. Outside
geometry, LESS_THAN / GREATER_THAN map to a single Math node, while
EQUAL / NOT_EQUAL / LESS_EQUAL / GREATER_EQUAL lower to a small Math
composition (see construct_operator._lower_compare_outside_geometry). The `data_type` option and
non-float operands remain geometry-only. `epsilon` defaults to Blender's own
Compare node default (0.001) and only applies to EQUAL / NOT_EQUAL.
See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/utilities/math/compare.html
"""
if data_type is None:
# FLOAT_VECTOR before RGBA: ambiguous tuple operands resolve to the
# element-wise vector compare (matching the convention in geo.py)
data_type = RuntimeResolveDataType(
[
NodeDataType.INT,
NodeDataType.FLOAT,
NodeDataType.FLOAT_VECTOR,
NodeDataType.RGBA,
NodeDataType.STRING,
],
["A", "B"],
)
# tuple keys let inline operator dispatch (`<`, `>`) bind positional args.
inputs: dict[tuple[str, int], nt.SocketOrVal] = {
("A", 0): a,
("B", 0): b,
("Epsilon", 0): epsilon,
}
return nt.ProcNode.from_nodetype(
node_type=ContextualNode.COMPARE.value,
inputs=inputs,
attrs={
"operation": operation,
"data_type": data_type,
},
)
TCompareNumeric = TypeVar("TCompareNumeric", nt.SocketOrVal[int], nt.SocketOrVal[float])
[docs]
def less_than(
a: TCompareNumeric,
b: TCompareNumeric,
) -> nt.ProcNode[bool]:
return _compare(a, b, operation="LESS_THAN")
[docs]
def less_equal(
a: TCompareNumeric,
b: TCompareNumeric,
) -> nt.ProcNode[bool]:
return _compare(a, b, operation="LESS_EQUAL")
[docs]
def greater_than(
a: TCompareNumeric,
b: TCompareNumeric,
) -> nt.ProcNode[bool]:
return _compare(a, b, operation="GREATER_THAN")
[docs]
def greater_equal(
a: TCompareNumeric,
b: TCompareNumeric,
) -> nt.ProcNode[bool]:
return _compare(a, b, operation="GREATER_EQUAL")
TCompareEqual = TypeVar(
"TCompareEqual",
nt.SocketOrVal[int],
nt.SocketOrVal[float],
)
[docs]
def equal(
a: TCompareEqual,
b: TCompareEqual,
epsilon: nt.SocketOrVal[float] = COMPARE_EPSILON_DEFAULT,
) -> nt.ProcNode[bool]:
return _compare(a, b, epsilon, operation="EQUAL")
[docs]
def not_equal(
a: TCompareEqual,
b: TCompareEqual,
epsilon: nt.SocketOrVal[float] = COMPARE_EPSILON_DEFAULT,
) -> nt.ProcNode[bool]:
return _compare(a, b, epsilon, operation="NOT_EQUAL")
# FunctionNodeCompare's ELEMENT-mode VECTOR and RGBA variants gate the Epsilon
# socket on the operation (enabled only for EQUAL/NOT_EQUAL), so each operation
# is a separate binding that routes through _compare; the helper omits Epsilon
# unless it differs from the Blender default, keeping disabled sockets unwired.
[docs]
def vector_elementwise_less_than(
a: nt.SocketOrVal[pt.Vector],
b: nt.SocketOrVal[pt.Vector],
) -> nt.ProcNode[bool]:
return _compare(a, b, operation="LESS_THAN", data_type=NodeDataType.FLOAT_VECTOR)
[docs]
def vector_elementwise_less_equal(
a: nt.SocketOrVal[pt.Vector],
b: nt.SocketOrVal[pt.Vector],
) -> nt.ProcNode[bool]:
return _compare(a, b, operation="LESS_EQUAL", data_type=NodeDataType.FLOAT_VECTOR)
[docs]
def vector_elementwise_greater_than(
a: nt.SocketOrVal[pt.Vector],
b: nt.SocketOrVal[pt.Vector],
) -> nt.ProcNode[bool]:
return _compare(a, b, operation="GREATER_THAN", data_type=NodeDataType.FLOAT_VECTOR)
[docs]
def vector_elementwise_greater_equal(
a: nt.SocketOrVal[pt.Vector],
b: nt.SocketOrVal[pt.Vector],
) -> nt.ProcNode[bool]:
return _compare(
a, b, operation="GREATER_EQUAL", data_type=NodeDataType.FLOAT_VECTOR
)
[docs]
def vector_elementwise_equal(
a: nt.SocketOrVal[pt.Vector],
b: nt.SocketOrVal[pt.Vector],
epsilon: nt.SocketOrVal[float] = COMPARE_EPSILON_DEFAULT,
) -> nt.ProcNode[bool]:
return _compare(
a, b, epsilon, operation="EQUAL", data_type=NodeDataType.FLOAT_VECTOR
)
[docs]
def vector_elementwise_not_equal(
a: nt.SocketOrVal[pt.Vector],
b: nt.SocketOrVal[pt.Vector],
epsilon: nt.SocketOrVal[float] = COMPARE_EPSILON_DEFAULT,
) -> nt.ProcNode[bool]:
return _compare(
a, b, epsilon, operation="NOT_EQUAL", data_type=NodeDataType.FLOAT_VECTOR
)
[docs]
def color_equal(
a: nt.SocketOrVal[pt.Color],
b: nt.SocketOrVal[pt.Color],
epsilon: nt.SocketOrVal[float] = COMPARE_EPSILON_DEFAULT,
) -> nt.ProcNode[bool]:
return _compare(a, b, epsilon, operation="EQUAL", data_type=NodeDataType.RGBA)
[docs]
def color_not_equal(
a: nt.SocketOrVal[pt.Color],
b: nt.SocketOrVal[pt.Color],
epsilon: nt.SocketOrVal[float] = COMPARE_EPSILON_DEFAULT,
) -> nt.ProcNode[bool]:
return _compare(a, b, epsilon, operation="NOT_EQUAL", data_type=NodeDataType.RGBA)
[docs]
def color_brighter(
a: nt.SocketOrVal[pt.Color],
b: nt.SocketOrVal[pt.Color],
) -> nt.ProcNode[bool]:
return _compare(a, b, operation="BRIGHTER", data_type=NodeDataType.RGBA)
[docs]
def color_darker(
a: nt.SocketOrVal[pt.Color],
b: nt.SocketOrVal[pt.Color],
) -> nt.ProcNode[bool]:
return _compare(a, b, operation="DARKER", data_type=NodeDataType.RGBA)
[docs]
def euler_to_rotation(
euler: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
) -> nt.ProcNode[pt.Vector]:
"""
Uses a EulerToRotation Function Node.
See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/utilities/rotation/euler_to_rotation.html
"""
return nt.ProcNode.from_nodetype(
node_type="FunctionNodeEulerToRotation",
inputs={"Euler": euler},
attrs={},
)
[docs]
def float_to_int(
float: nt.SocketOrVal[float],
rounding_mode: Literal["ROUND", "FLOOR", "CEILING", "TRUNCATE"] = "ROUND",
) -> nt.ProcNode[int]:
"""
Uses a FloatToInt Function Node.
See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/utilities/math/float_to_integer.html
"""
return nt.ProcNode.from_nodetype(
node_type="FunctionNodeFloatToInt",
inputs={"Float": float},
attrs={"rounding_mode": rounding_mode},
)
'''
def input_bool(boolean: bool = False) -> t.ProcNode[bool]:
"""
Uses a InputBool Function Node.
See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/input/constant/boolean.html
"""
raise_io_error("input_bool", logger=logger)
return t.ProcNode.from_nodetype(
node_type="FunctionNodeInputBool",
inputs={},
attrs={"boolean": boolean},
)
def input_color(value: tuple = (0.5, 0.5, 0.5, 1.0)) -> t.ProcNode[pt.Color]:
"""
Uses a InputColor Function Node.
See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/input/constant/color.html
"""
raise_io_error("input_color", logger=logger)
return t.ProcNode.from_nodetype(
node_type="FunctionNodeInputColor",
inputs={},
attrs={"value": value},
)
def input_int(integer: int = 0) -> t.ProcNode[int]:
"""
Uses a InputInt Function Node.
See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/input/constant/integer.html
"""
raise_io_error("input_int", logger=logger)
return t.ProcNode.from_nodetype(
node_type="FunctionNodeInputInt",
inputs={},
attrs={"integer": integer},
)
def input_rotation(rotation_euler: tuple = (0.0, 0.0, 0.0)) -> t.ProcNode[pt.Vector]:
"""
Uses a InputRotation Function Node.
See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/input/constant/rotation.html
"""
raise_io_error("input_rotation", logger=logger)
return t.ProcNode.from_nodetype(
node_type="FunctionNodeInputRotation",
inputs={},
attrs={"rotation_euler": rotation_euler},
)
'''
[docs]
class InvertMatrixResult(NamedTuple):
matrix: nt.ProcNode[pt.Matrix]
invertible: nt.ProcNode[bool]
[docs]
def invert_matrix(matrix: nt.SocketOrVal[pt.Matrix]) -> InvertMatrixResult:
"""
Uses a InvertMatrix Function Node.
See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/utilities/matrix/invert_matrix.html
"""
node = nt.ProcNode.from_nodetype(
node_type="FunctionNodeInvertMatrix",
inputs={"Matrix": matrix},
attrs={},
)
return InvertMatrixResult(
node._output_socket("matrix"), node._output_socket("invertible")
)
[docs]
def invert_rotation(
rotation: nt.SocketOrVal[pt.Euler] = (0, 0, 0),
) -> nt.ProcNode[pt.Vector]:
"""
Uses a InvertRotation Function Node.
See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/utilities/rotation/invert_rotation.html
"""
return nt.ProcNode.from_nodetype(
node_type="FunctionNodeInvertRotation",
inputs={"Rotation": rotation},
attrs={},
)
[docs]
def matrix_multiply(
a: nt.SocketOrVal[pt.Matrix],
b: nt.SocketOrVal[pt.Matrix],
) -> nt.ProcNode[pt.Matrix]:
"""
Uses a MatrixMultiply Function Node.
See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/utilities/matrix/multiply_matrices.html
"""
return nt.ProcNode.from_nodetype(
node_type="FunctionNodeMatrixMultiply",
inputs={("Matrix", 0): a, ("Matrix", 1): b},
attrs={},
)
[docs]
def project_point(
vector: nt.SocketOrVal[pt.Vector],
transform: nt.SocketOrVal[pt.Matrix],
) -> nt.ProcNode[pt.Vector]:
"""
Uses a ProjectPoint Function Node.
See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/utilities/matrix/project_point.html
"""
return nt.ProcNode.from_nodetype(
node_type="FunctionNodeProjectPoint",
inputs={"Vector": vector, "Transform": transform},
attrs={},
)
[docs]
def quaternion_to_rotation(
w: nt.SocketOrVal[float] = 1.0,
x: nt.SocketOrVal[float] = 0.0,
y: nt.SocketOrVal[float] = 0.0,
z: nt.SocketOrVal[float] = 0.0,
) -> nt.ProcNode[pt.Vector]:
"""
Uses a QuaternionToRotation Function Node.
See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/utilities/rotation/quaternion_to_rotation.html
"""
return nt.ProcNode.from_nodetype(
node_type="FunctionNodeQuaternionToRotation",
inputs={"W": w, "X": x, "Y": y, "Z": z},
attrs={},
)
TRandomValue = TypeVar("TRandomValue", int, float, pt.Vector, pt.Color)
[docs]
def random_value(
min: nt.SocketOrVal[TRandomValue] = 0.0,
max: nt.SocketOrVal[TRandomValue] = 1.0,
id: nt.SocketOrVal[int] = 0,
seed: nt.SocketOrVal[int] = 0,
data_type: NodeDataType | RuntimeResolveDataType | None = None,
) -> nt.ProcNode[TRandomValue]:
"""
Uses a RandomValue Function Node.
See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/utilities/random_value.html
"""
if data_type is None:
# bl4.2 FunctionNodeRandomValue only supports FLOAT/INT/FLOAT_VECTOR
# (and BOOLEAN via random_boolean) — it has no color/RGBA data type.
data_type = RuntimeResolveDataType(
[
NodeDataType.INT,
NodeDataType.FLOAT,
NodeDataType.FLOAT_VECTOR,
],
["Min", "Max"],
)
return nt.ProcNode.from_nodetype(
node_type="FunctionNodeRandomValue",
inputs={
"ID": id,
"Max": max,
"Min": min,
"Seed": seed,
},
attrs={"data_type": data_type},
)
[docs]
def random_boolean(
probability: nt.SocketOrVal[float] = 0.5,
id: nt.SocketOrVal[int] = 0,
seed: nt.SocketOrVal[int] = 0,
) -> nt.ProcNode[bool]:
"""
Uses a RandomBoolean Function Node.
"""
return nt.ProcNode.from_nodetype(
node_type="FunctionNodeRandomValue",
inputs={"ID": id, "Probability": probability, "Seed": seed},
attrs={"data_type": NodeDataType.BOOLEAN},
)
[docs]
def replace_string(
string: nt.SocketOrVal[str],
find: nt.SocketOrVal[str],
replace: nt.SocketOrVal[str],
) -> nt.ProcNode[str]:
"""
Uses a ReplaceString Function Node.
See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/utilities/text/replace_string.html
"""
return nt.ProcNode.from_nodetype(
node_type="FunctionNodeReplaceString",
inputs={"String": string, "Find": find, "Replace": replace},
attrs={},
)
[docs]
def rotate_euler(
rotation: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
rotate_by: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
rotation_type: Literal["EULER", "AXIS_ANGLE"] = "EULER",
space: Literal["OBJECT", "LOCAL"] = "OBJECT",
) -> nt.ProcNode[pt.Vector]:
"""
Uses a RotateEuler Function Node.
See: http://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/utilities/rotation/rotate_euler.html
"""
return nt.ProcNode.from_nodetype(
node_type="FunctionNodeRotateEuler",
inputs={"Rotation": rotation, "Rotate By": rotate_by},
attrs={"rotation_type": rotation_type, "space": space},
)
[docs]
def rotate_rotation(
rotation: nt.SocketOrVal[pt.Euler] = (0, 0, 0),
rotate_by: nt.SocketOrVal[pt.Euler] = (0, 0, 0),
rotation_space: Literal["GLOBAL", "LOCAL"] = "GLOBAL",
) -> nt.ProcNode[pt.Vector]:
"""
Uses a RotateRotation Function Node.
See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/utilities/rotation/rotate_rotation.html
"""
return nt.ProcNode.from_nodetype(
node_type="FunctionNodeRotateRotation",
inputs={"Rotation": rotation, "Rotate By": rotate_by},
attrs={"rotation_space": rotation_space},
)
[docs]
def rotate_vector(
vector: nt.SocketOrVal[pt.Vector],
rotation: nt.SocketOrVal[pt.Euler] = (0, 0, 0),
) -> nt.ProcNode[pt.Vector]:
"""
Uses a RotateVector Function Node.
See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/utilities/rotation/rotate_vector.html
"""
return nt.ProcNode.from_nodetype(
node_type="FunctionNodeRotateVector",
inputs={"Vector": vector, "Rotation": rotation},
attrs={},
)
[docs]
class RotationToAxisAngleResult(NamedTuple):
axis: nt.ProcNode[pt.Vector]
angle: nt.ProcNode[float]
[docs]
def rotation_to_axis_angle(
rotation: nt.SocketOrVal[pt.Euler] = (0, 0, 0),
) -> RotationToAxisAngleResult:
"""
Uses a RotationToAxisAngle Function Node.
See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/utilities/rotation/axis_angle_to_rotation.html
"""
node = nt.ProcNode.from_nodetype(
node_type="FunctionNodeRotationToAxisAngle",
inputs={"Rotation": rotation},
attrs={},
)
return RotationToAxisAngleResult(
node._output_socket("axis"), node._output_socket("angle")
)
[docs]
def rotation_to_euler(
rotation: nt.SocketOrVal[pt.Euler] = (0, 0, 0),
) -> nt.ProcNode[pt.Vector]:
"""
Uses a RotationToEuler Function Node.
See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/utilities/rotation/rotation_to_euler.html
"""
return nt.ProcNode.from_nodetype(
node_type="FunctionNodeRotationToEuler",
inputs={"Rotation": rotation},
attrs={},
)
[docs]
class RotationToQuaternionResult(NamedTuple):
w: nt.ProcNode[float]
x: nt.ProcNode[float]
y: nt.ProcNode[float]
z: nt.ProcNode[float]
[docs]
def rotation_to_quaternion(
rotation: nt.SocketOrVal[pt.Euler] = (0, 0, 0),
) -> RotationToQuaternionResult:
"""
Uses a RotationToQuaternion Function Node.
See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/utilities/rotation/rotation_to_quaternion.html
"""
node = nt.ProcNode.from_nodetype(
node_type="FunctionNodeRotationToQuaternion",
inputs={"Rotation": rotation},
attrs={},
)
return RotationToQuaternionResult(
node._output_socket("w"),
node._output_socket("x"),
node._output_socket("y"),
node._output_socket("z"),
)
[docs]
class SeparateMatrixResult(NamedTuple):
column_1_row_1: nt.ProcNode[float]
column_1_row_2: nt.ProcNode[float]
column_1_row_3: nt.ProcNode[float]
column_1_row_4: nt.ProcNode[float]
column_2_row_1: nt.ProcNode[float]
column_2_row_2: nt.ProcNode[float]
column_2_row_3: nt.ProcNode[float]
column_2_row_4: nt.ProcNode[float]
column_3_row_1: nt.ProcNode[float]
column_3_row_2: nt.ProcNode[float]
column_3_row_3: nt.ProcNode[float]
column_3_row_4: nt.ProcNode[float]
column_4_row_1: nt.ProcNode[float]
column_4_row_2: nt.ProcNode[float]
column_4_row_3: nt.ProcNode[float]
column_4_row_4: nt.ProcNode[float]
[docs]
def separate_matrix(
matrix: nt.SocketOrVal[pt.Matrix],
) -> SeparateMatrixResult:
"""
Uses a SeparateMatrix Function Node.
See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/utilities/matrix/separate_matrix.html
"""
node = nt.ProcNode.from_nodetype(
node_type="FunctionNodeSeparateMatrix",
inputs={"Matrix": matrix},
attrs={},
)
return SeparateMatrixResult(
node._output_socket("column_1_row_1"),
node._output_socket("column_1_row_2"),
node._output_socket("column_1_row_3"),
node._output_socket("column_1_row_4"),
node._output_socket("column_2_row_1"),
node._output_socket("column_2_row_2"),
node._output_socket("column_2_row_3"),
node._output_socket("column_2_row_4"),
node._output_socket("column_3_row_1"),
node._output_socket("column_3_row_2"),
node._output_socket("column_3_row_3"),
node._output_socket("column_3_row_4"),
node._output_socket("column_4_row_1"),
node._output_socket("column_4_row_2"),
node._output_socket("column_4_row_3"),
node._output_socket("column_4_row_4"),
)
[docs]
def slice_string(
string: nt.SocketOrVal[str],
position: nt.SocketOrVal[int] = 0,
length: nt.SocketOrVal[int] = 10,
) -> nt.ProcNode[str]:
"""
Uses a SliceString Function Node.
See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/utilities/text/slice_string.html
"""
return nt.ProcNode.from_nodetype(
node_type="FunctionNodeSliceString",
inputs={"String": string, "Position": position, "Length": length},
attrs={},
)
[docs]
def string_length(string: nt.SocketOrVal[str]) -> nt.ProcNode[int]:
"""
Uses a StringLength Function Node.
See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/utilities/text/string_length.html
"""
return nt.ProcNode.from_nodetype(
node_type="FunctionNodeStringLength",
inputs={"String": string},
attrs={},
)
[docs]
def transpose_matrix(
matrix: nt.SocketOrVal[pt.Matrix],
) -> nt.ProcNode[pt.Matrix]:
"""
Uses a TransposeMatrix Function Node.
See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/utilities/matrix/transpose_matrix.html
"""
return nt.ProcNode.from_nodetype(
node_type="FunctionNodeTransposeMatrix",
inputs={"Matrix": matrix},
attrs={},
)
[docs]
def value_to_string(
value: nt.SocketOrVal[float], decimals: nt.SocketOrVal[int] = 0
) -> nt.ProcNode[str]:
"""
Uses a ValueToString Function Node.
See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/utilities/text/value_to_string.html
"""
return nt.ProcNode.from_nodetype(
node_type="FunctionNodeValueToString",
inputs={"Value": value, "Decimals": decimals},
attrs={},
)
TIndexSwitch = TypeVar(
"TIndexSwitch",
nt.SocketOrVal[bool],
nt.SocketOrVal[int],
nt.SocketOrVal[pt.Color],
nt.SocketOrVal[str],
nt.SocketOrVal[float],
nt.SocketOrVal[nt.pt.Vector],
)
[docs]
def index_switch(
a: TIndexSwitch = 0,
b: TIndexSwitch = 0,
index: nt.SocketOrVal[int] = 0,
data_type: NodeDataType | RuntimeResolveDataType | None = None,
) -> nt.ProcNode:
"""
Uses a IndexSwitch Geometry Node.
See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/utilities/index_switch.html
"""
if data_type is None:
data_type = RuntimeResolveDataType(
[
NodeDataType.BOOLEAN,
NodeDataType.INT,
NodeDataType.FLOAT,
NodeDataType.FLOAT_VECTOR,
NodeDataType.ROTATION,
NodeDataType.FLOAT_MATRIX,
NodeDataType.STRING,
NodeDataType.RGBA,
NodeDataType.OBJECT,
NodeDataType.IMAGE,
NodeDataType.GEOMETRY,
NodeDataType.COLLECTION,
NodeDataType.MATERIAL,
],
["0", "1"],
)
return nt.ProcNode.from_nodetype(
node_type="GeometryNodeIndexSwitch",
inputs={"0": a, "1": b, "Index": index},
attrs={
"data_type": data_type,
},
)
TSwitchArgType = TypeVar(
"TAnyDataVal",
int,
float,
bool,
str,
pt.Vector,
pt.Color,
pt.Matrix,
pt.Quaternion,
nt.Geometry,
)
_SWITCH_DATA_TYPES = [
NodeDataType.BOOLEAN,
NodeDataType.INT,
NodeDataType.FLOAT,
NodeDataType.FLOAT_VECTOR,
NodeDataType.ROTATION,
NodeDataType.FLOAT_MATRIX,
NodeDataType.STRING,
NodeDataType.RGBA,
NodeDataType.OBJECT,
NodeDataType.IMAGE,
NodeDataType.GEOMETRY,
NodeDataType.COLLECTION,
NodeDataType.MATERIAL,
]
[docs]
def switch(
switch: nt.SocketOrVal[bool] = False,
a: nt.SocketOrVal[TSwitchArgType] | None = None,
b: nt.SocketOrVal[TSwitchArgType] | None = None,
data_type: NodeDataType | RuntimeResolveDataType | None = None,
) -> nt.ProcNode[TSwitchArgType]:
"""
Uses a Switch Geometry Node.
See: https://docs.blender.org/manual/en/4.2/modeling/geometry_nodes/utilities/switch.html
"""
if data_type is None:
data_type = RuntimeResolveDataType(
_SWITCH_DATA_TYPES,
["False", "True"],
)
return nt.ProcNode.from_nodetype(
node_type="GeometryNodeSwitch",
inputs={"Switch": switch, "False": a, "True": b},
attrs={"input_type": data_type},
)