"""
Math and Vector Math Node bindings for Blender
"""
from typing import Literal
from procfunc import types as pt
from procfunc.nodes import types as nt
[docs]
def clamp(
value: nt.SocketOrVal[float] = 1.0,
min: nt.SocketOrVal[float] = 0.0,
max: nt.SocketOrVal[float] = 1.0,
clamp_type: Literal["MINMAX", "RANGE"] = "MINMAX",
) -> nt.ProcNode[float]:
"""
Uses a Clamp Shader Node.
See: https://docs.blender.org/manual/en/4.2/render/shader_nodes/converter/clamp.html
"""
return nt.ProcNode.from_nodetype(
node_type="ShaderNodeClamp",
inputs={"Value": value, "Min": min, "Max": max},
attrs={"clamp_type": clamp_type},
)
# Math Nodes
def _math(
a: nt.SocketOrVal[float] = None,
b: nt.SocketOrVal[float] = None,
value_2: nt.SocketOrVal[float] = None,
operation: str = "ADD",
) -> nt.ProcNode[float]:
"""
Uses a Math Shader Node.
Procfunc does NOT support the inline clamp option - use pf.nodes.math.clamp() on the output instead.
See: https://docs.blender.org/manual/en/4.2/render/shader_nodes/converter/math.html
"""
return nt.ProcNode.from_nodetype(
node_type="ShaderNodeMath",
inputs={("Value", 0): a, ("Value", 1): b, ("Value", 2): value_2},
attrs={
"operation": operation,
"use_clamp": False, # not supported by procfunc
},
)
# Basic Math Operations
[docs]
def add(
a: nt.SocketOrVal[float] = 0.5, b: nt.SocketOrVal[float] = 0.5
) -> nt.ProcNode[float]:
return _math(a, b, operation="ADD")
[docs]
def subtract(
a: nt.SocketOrVal[float] = 0.5, b: nt.SocketOrVal[float] = 0.5
) -> nt.ProcNode[float]:
return _math(a, b, operation="SUBTRACT")
[docs]
def multiply(
a: nt.SocketOrVal[float] = 0.5, b: nt.SocketOrVal[float] = 0.5
) -> nt.ProcNode[float]:
return _math(a, b, operation="MULTIPLY")
[docs]
def multiply_add(
a: nt.SocketOrVal[float] = 0.5,
b: nt.SocketOrVal[float] = 0.5,
addend: nt.SocketOrVal[float] = 0.0,
) -> nt.ProcNode[float]:
return _math(a, b, addend, operation="MULTIPLY_ADD")
[docs]
def divide(
numerator: nt.SocketOrVal[float] = 0.5, denominator: nt.SocketOrVal[float] = 0.5
) -> nt.ProcNode[float]:
return _math(numerator, denominator, operation="DIVIDE")
[docs]
def power(
base: nt.SocketOrVal[float] = 0.5, exponent: nt.SocketOrVal[float] = 0.5
) -> nt.ProcNode[float]:
return _math(base, exponent, operation="POWER")
[docs]
def logarithm(
value: nt.SocketOrVal[float] = 0.5, base: nt.SocketOrVal[float] = 0.5
) -> nt.ProcNode[float]:
return _math(value, base, operation="LOGARITHM")
[docs]
def sqrt(value: nt.SocketOrVal[float] = 0.5) -> nt.ProcNode[float]:
return _math(value, operation="SQRT")
[docs]
def inverse_sqrt(value: nt.SocketOrVal[float] = 0.5) -> nt.ProcNode[float]:
return _math(value, operation="INVERSE_SQRT")
[docs]
def absolute(value: nt.SocketOrVal[float] = 0.5) -> nt.ProcNode[float]:
return _math(value, operation="ABSOLUTE")
[docs]
def exponent(value: nt.SocketOrVal[float] = 0.5) -> nt.ProcNode[float]:
return _math(value, operation="EXPONENT")
# Comparison Operations
[docs]
def minimum(
a: nt.SocketOrVal[float] = 0.5, b: nt.SocketOrVal[float] = 0.5
) -> nt.ProcNode[float]:
return _math(a, b, operation="MINIMUM")
[docs]
def maximum(
a: nt.SocketOrVal[float] = 0.5, b: nt.SocketOrVal[float] = 0.5
) -> nt.ProcNode[float]:
return _math(a, b, operation="MAXIMUM")
[docs]
def less_than(
a: nt.SocketOrVal[float] = 0.5, b: nt.SocketOrVal[float] = 0.5
) -> nt.ProcNode[float]:
return _math(a, b, operation="LESS_THAN")
[docs]
def greater_than(
a: nt.SocketOrVal[float] = 0.5, b: nt.SocketOrVal[float] = 0.5
) -> nt.ProcNode[float]:
return _math(a, b, operation="GREATER_THAN")
[docs]
def sign(value: nt.SocketOrVal[float] = 0.5) -> nt.ProcNode[float]:
return _math(value, operation="SIGN")
[docs]
def compare(
a: nt.SocketOrVal[float] = 0.5,
b: nt.SocketOrVal[float] = 0.5,
epsilon: nt.SocketOrVal[float] = 0.001,
) -> nt.ProcNode[float]:
return _math(a, b, epsilon, operation="COMPARE")
[docs]
def smooth_minimum(
a: nt.SocketOrVal[float] = 0.5,
b: nt.SocketOrVal[float] = 0.5,
distance: nt.SocketOrVal[float] = 0.0,
) -> nt.ProcNode[float]:
return _math(a, b, distance, operation="SMOOTH_MIN")
[docs]
def smooth_maximum(
a: nt.SocketOrVal[float] = 0.5,
b: nt.SocketOrVal[float] = 0.5,
distance: nt.SocketOrVal[float] = 0.0,
) -> nt.ProcNode[float]:
return _math(a, b, distance, operation="SMOOTH_MAX")
[docs]
def round(value: nt.SocketOrVal[float] = 0.5) -> nt.ProcNode[float]:
return _math(value, operation="ROUND")
[docs]
def floor(value: nt.SocketOrVal[float] = 0.5) -> nt.ProcNode[float]:
return _math(value, operation="FLOOR")
[docs]
def ceil(value: nt.SocketOrVal[float] = 0.5) -> nt.ProcNode[float]:
return _math(value, operation="CEIL")
[docs]
def truncate(value: nt.SocketOrVal[float] = 0.5) -> nt.ProcNode[float]:
return _math(value, operation="TRUNC")
[docs]
def fraction(value: nt.SocketOrVal[float] = 0.5) -> nt.ProcNode[float]:
return _math(value, operation="FRACT")
[docs]
def modulo(
a: nt.SocketOrVal[float] = 0.5, b: nt.SocketOrVal[float] = 0.5
) -> nt.ProcNode[float]:
return _math(a, b, operation="MODULO")
[docs]
def floor_mod(
a: nt.SocketOrVal[float] = 0.5, b: nt.SocketOrVal[float] = 0.5
) -> nt.ProcNode[float]:
return _math(a, b, operation="FLOORED_MODULO")
[docs]
def wrap(
value: nt.SocketOrVal[float] = 0.5,
max_val: nt.SocketOrVal[float] = 1.0,
min_val: nt.SocketOrVal[float] = 0.0,
) -> nt.ProcNode[float]:
return _math(value, max_val, min_val, operation="WRAP")
[docs]
def snap(
value: nt.SocketOrVal[float] = 0.5, increment: nt.SocketOrVal[float] = 1.0
) -> nt.ProcNode[float]:
return _math(value, increment, operation="SNAP")
[docs]
def pingpong(
value: nt.SocketOrVal[float] = 0.5, scale: nt.SocketOrVal[float] = 1.0
) -> nt.ProcNode[float]:
return _math(value, scale, operation="PINGPONG")
# Trigonometric Operations
[docs]
def sin(value: nt.SocketOrVal[float] = 0.5) -> nt.ProcNode[float]:
return _math(value, operation="SINE")
[docs]
def cos(value: nt.SocketOrVal[float] = 0.5) -> nt.ProcNode[float]:
return _math(value, operation="COSINE")
[docs]
def tan(value: nt.SocketOrVal[float] = 0.5) -> nt.ProcNode[float]:
return _math(value, operation="TANGENT")
[docs]
def asin(value: nt.SocketOrVal[float] = 0.5) -> nt.ProcNode[float]:
return _math(value, operation="ARCSINE")
[docs]
def acos(value: nt.SocketOrVal[float] = 0.5) -> nt.ProcNode[float]:
return _math(value, operation="ARCCOSINE")
[docs]
def atan(value: nt.SocketOrVal[float] = 0.5) -> nt.ProcNode[float]:
return _math(value, operation="ARCTANGENT")
[docs]
def atan2(
y: nt.SocketOrVal[float] = 0.5, x: nt.SocketOrVal[float] = 0.5
) -> nt.ProcNode[float]:
return _math(y, x, operation="ARCTAN2")
[docs]
def sinh(value: nt.SocketOrVal[float] = 0.5) -> nt.ProcNode[float]:
return _math(value, operation="SINH")
[docs]
def cosh(value: nt.SocketOrVal[float] = 0.5) -> nt.ProcNode[float]:
return _math(value, operation="COSH")
[docs]
def tanh(value: nt.SocketOrVal[float] = 0.5) -> nt.ProcNode[float]:
return _math(value, operation="TANH")
# Conversion Operations
[docs]
def deg_to_rad(value: nt.SocketOrVal[float] = 0.5) -> nt.ProcNode[float]:
return _math(value, operation="RADIANS")
[docs]
def rad_to_deg(value: nt.SocketOrVal[float] = 0.5) -> nt.ProcNode[float]:
return _math(value, operation="DEGREES")
# Vector Math Operations
[docs]
def vector_add(
a: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
b: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
) -> nt.ProcNode[pt.Vector]:
"""Add two vectors."""
return nt.ProcNode.from_nodetype(
node_type="ShaderNodeVectorMath",
inputs={("Vector", 0): a, ("Vector", 1): b},
attrs={"operation": "ADD"},
)
[docs]
def vector_subtract(
a: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
b: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
) -> nt.ProcNode[pt.Vector]:
return nt.ProcNode.from_nodetype(
node_type="ShaderNodeVectorMath",
inputs={("Vector", 0): a, ("Vector", 1): b},
attrs={"operation": "SUBTRACT"},
)
[docs]
def vector_multiply(
a: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
b: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
) -> nt.ProcNode[pt.Vector]:
return nt.ProcNode.from_nodetype(
node_type="ShaderNodeVectorMath",
inputs={("Vector", 0): a, ("Vector", 1): b},
attrs={"operation": "MULTIPLY"},
)
[docs]
def vector_multiply_add(
a: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
b: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
addend: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
) -> nt.ProcNode[pt.Vector]:
return nt.ProcNode.from_nodetype(
node_type="ShaderNodeVectorMath",
attrs={"operation": "MULTIPLY_ADD"},
inputs={("Vector", 0): a, ("Vector", 1): b, ("Vector", 2): addend},
)
[docs]
def vector_divide(
a: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
b: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
) -> nt.ProcNode[pt.Vector]:
return nt.ProcNode.from_nodetype(
node_type="ShaderNodeVectorMath",
inputs={("Vector", 0): a, ("Vector", 1): b},
attrs={"operation": "DIVIDE"},
)
[docs]
def vector_cross_product(
a: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
b: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
) -> nt.ProcNode[pt.Vector]:
return nt.ProcNode.from_nodetype(
node_type="ShaderNodeVectorMath",
inputs={("Vector", 0): a, ("Vector", 1): b},
attrs={"operation": "CROSS_PRODUCT"},
)
[docs]
def vector_project(
vector: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
onto: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
) -> nt.ProcNode[float]:
return nt.ProcNode.from_nodetype(
node_type="ShaderNodeVectorMath",
inputs={("Vector", 0): vector, ("Vector", 1): onto},
attrs={"operation": "PROJECT"},
)
[docs]
def vector_reflect(
a: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
normal: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
) -> nt.ProcNode[pt.Vector]:
return nt.ProcNode.from_nodetype(
node_type="ShaderNodeVectorMath",
inputs={("Vector", 0): a, ("Vector", 1): normal},
attrs={"operation": "REFLECT"},
)
[docs]
def vector_refract(
incident: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
normal: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
ior: nt.SocketOrVal[float] = 1.0,
) -> nt.ProcNode[pt.Vector]:
return nt.ProcNode.from_nodetype(
node_type="ShaderNodeVectorMath",
inputs={("Vector", 0): incident, ("Vector", 1): normal, ("Scale", 2): ior},
attrs={"operation": "REFRACT"},
)
[docs]
def vector_faceforward(
vector: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
surface: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
normal: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
) -> nt.ProcNode[pt.Vector]:
return nt.ProcNode.from_nodetype(
node_type="ShaderNodeVectorMath",
inputs={
("Vector", 0): vector,
("Vector", 1): surface,
("Vector", 2): normal,
},
attrs={"operation": "FACEFORWARD"},
)
[docs]
def vector_dot_product(
a: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
b: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
) -> nt.ProcNode[pt.Vector]:
return nt.ProcNode.from_nodetype(
node_type="ShaderNodeVectorMath",
inputs={("Vector", 0): a, ("Vector", 1): b},
attrs={"operation": "DOT_PRODUCT"},
)
[docs]
def vector_distance(
a: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
b: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
) -> nt.ProcNode[pt.Vector]:
return nt.ProcNode.from_nodetype(
node_type="ShaderNodeVectorMath",
inputs={("Vector", 0): a, ("Vector", 1): b},
attrs={"operation": "DISTANCE"},
)
[docs]
def vector_length(vector: nt.SocketOrVal[pt.Vector] = (0, 0, 0)) -> nt.ProcNode[float]:
return nt.ProcNode.from_nodetype(
node_type="ShaderNodeVectorMath",
inputs={("Vector", 0): vector},
attrs={"operation": "LENGTH"},
)
[docs]
def vector_scale(
vector: nt.SocketOrVal[pt.Vector] = (0, 0, 0), scale: nt.SocketOrVal[float] = 1.0
) -> nt.ProcNode[pt.Vector]:
return nt.ProcNode.from_nodetype(
node_type="ShaderNodeVectorMath",
inputs={("Vector", 0): vector, ("Scale", 0): scale},
attrs={"operation": "SCALE"},
)
[docs]
def vector_normalize(
vector: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
) -> nt.ProcNode[pt.Vector]:
return nt.ProcNode.from_nodetype(
node_type="ShaderNodeVectorMath",
inputs={("Vector", 0): vector},
attrs={"operation": "NORMALIZE"},
)
[docs]
def vector_wrap(
vector: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
max_val: nt.SocketOrVal[pt.Vector] = (1, 1, 1),
min_val: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
) -> nt.ProcNode[pt.Vector]:
return nt.ProcNode.from_nodetype(
node_type="ShaderNodeVectorMath",
inputs={("Vector", 0): vector, ("Vector", 1): max_val, ("Vector", 2): min_val},
attrs={"operation": "WRAP"},
)
[docs]
def vector_snap(
a: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
b: nt.SocketOrVal[pt.Vector] = (1, 1, 1),
) -> nt.ProcNode[pt.Vector]:
return nt.ProcNode.from_nodetype(
node_type="ShaderNodeVectorMath",
inputs={("Vector", 0): a, ("Vector", 1): b},
attrs={"operation": "SNAP"},
)
[docs]
def vector_floor(
vector: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
) -> nt.ProcNode[pt.Vector]:
return nt.ProcNode.from_nodetype(
node_type="ShaderNodeVectorMath",
inputs={("Vector", 0): vector},
attrs={"operation": "FLOOR"},
)
[docs]
def vector_ceil(
vector: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
) -> nt.ProcNode[pt.Vector]:
return nt.ProcNode.from_nodetype(
node_type="ShaderNodeVectorMath",
inputs={("Vector", 0): vector},
attrs={"operation": "CEIL"},
)
[docs]
def vector_modulo(
a: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
b: nt.SocketOrVal[pt.Vector] = (1, 1, 1),
) -> nt.ProcNode[pt.Vector]:
return nt.ProcNode.from_nodetype(
node_type="ShaderNodeVectorMath",
inputs={("Vector", 0): a, ("Vector", 1): b},
attrs={"operation": "MODULO"},
)
[docs]
def vector_fraction(
vector: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
) -> nt.ProcNode[pt.Vector]:
return nt.ProcNode.from_nodetype(
node_type="ShaderNodeVectorMath",
inputs={("Vector", 0): vector},
attrs={"operation": "FRACTION"},
)
[docs]
def vector_round(
vector: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
) -> nt.ProcNode[pt.Vector]:
return nt.ProcNode.from_nodetype(
node_type="ShaderNodeVectorMath",
inputs={("Vector", 0): vector},
attrs={"operation": "ROUND"},
)
[docs]
def vector_truncate(
vector: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
) -> nt.ProcNode[pt.Vector]:
return nt.ProcNode.from_nodetype(
node_type="ShaderNodeVectorMath",
inputs={("Vector", 0): vector},
attrs={"operation": "TRUNC"},
)
[docs]
def vector_absolute(
vector: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
) -> nt.ProcNode[pt.Vector]:
return nt.ProcNode.from_nodetype(
node_type="ShaderNodeVectorMath",
inputs={("Vector", 0): vector},
attrs={"operation": "ABSOLUTE"},
)
[docs]
def vector_minimum(
a: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
b: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
) -> nt.ProcNode[pt.Vector]:
return nt.ProcNode.from_nodetype(
node_type="ShaderNodeVectorMath",
inputs={("Vector", 0): a, ("Vector", 1): b},
attrs={"operation": "MINIMUM"},
)
[docs]
def vector_maximum(
a: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
b: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
) -> nt.ProcNode[pt.Vector]:
return nt.ProcNode.from_nodetype(
node_type="ShaderNodeVectorMath",
inputs={("Vector", 0): a, ("Vector", 1): b},
attrs={"operation": "MAXIMUM"},
)
[docs]
def vector_sine(
vector: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
) -> nt.ProcNode[pt.Vector]:
return nt.ProcNode.from_nodetype(
node_type="ShaderNodeVectorMath",
inputs={("Vector", 0): vector},
attrs={"operation": "SINE"},
)
[docs]
def vector_cosine(
vector: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
) -> nt.ProcNode[pt.Vector]:
return nt.ProcNode.from_nodetype(
node_type="ShaderNodeVectorMath",
inputs={("Vector", 0): vector},
attrs={"operation": "COSINE"},
)
[docs]
def vector_tangent(
vector: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
) -> nt.ProcNode[pt.Vector]:
return nt.ProcNode.from_nodetype(
node_type="ShaderNodeVectorMath",
inputs={("Vector", 0): vector},
attrs={"operation": "TANGENT"},
)
[docs]
def vector_rotate_axis_angle(
vector: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
center: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
axis: nt.SocketOrVal[pt.Vector] = (0, 0, 1),
angle: nt.SocketOrVal[float] = 0.0,
invert: bool = False,
) -> nt.ProcNode[pt.Vector]:
"""
Uses a VectorRotate Shader Node.
See: https://docs.blender.org/manual/en/4.2/render/shader_nodes/vector/vector_rotate.html
"""
return nt.ProcNode.from_nodetype(
node_type="ShaderNodeVectorRotate",
inputs={"Vector": vector, "Center": center, "Axis": axis, "Angle": angle},
attrs={"invert": invert, "rotation_type": "AXIS_ANGLE"},
)
[docs]
def vector_rotate_euler(
vector: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
center: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
rotation: nt.SocketOrVal[pt.Vector] = (0, 0, 0),
invert: bool = False,
) -> nt.ProcNode[pt.Vector]:
return nt.ProcNode.from_nodetype(
node_type="ShaderNodeVectorRotate",
inputs={"Vector": vector, "Center": center, "Rotation": rotation},
attrs={"invert": invert, "rotation_type": "EULER_XYZ"},
)
# NOTE: mode XYZ have been dropped. transpiler specialcases will map these back to vector_rotate_euler calls.