diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eeb8a6e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +**/__pycache__ diff --git a/README.md b/README.md new file mode 100644 index 0000000..54ec13d --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# ComfyMath + +Provides Math Nodes for [ComfyUI](https://github.com/comfyanonymous/ComfyUI) + +## Features + +Provides nodes for: +* Boolean Logic +* Integer Arithmetic +* Floating Point Arithmetic and Functions +* Vec2, Vec3, and Vec4 Arithmetic and Functions + +## Installation + +From the `custom_nodes` directory in your ComfyUI installation, run: + +```sh +git clone https://github.com/evanspearman/ComfyMath.git +``` diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..8b8aa7d --- /dev/null +++ b/__init__.py @@ -0,0 +1,20 @@ +from .src.comfymath.convert import NODE_CLASS_MAPPINGS as convert_NCM +from .src.comfymath.bool import NODE_CLASS_MAPPINGS as bool_NCM +from .src.comfymath.int import NODE_CLASS_MAPPINGS as int_NCM +from .src.comfymath.float import NODE_CLASS_MAPPINGS as float_NCM +from .src.comfymath.vec2 import NODE_CLASS_MAPPINGS as vec2_NCM +from .src.comfymath.vec3 import NODE_CLASS_MAPPINGS as vec3_NCM +from .src.comfymath.vec4 import NODE_CLASS_MAPPINGS as vec4_NCM + + +NODE_CLASS_MAPPINGS = { + **convert_NCM, + **bool_NCM, + **int_NCM, + **float_NCM, + **vec2_NCM, + **vec3_NCM, + **vec4_NCM, +} + +NODE_DISPLAY_NAME_MAPPINGS = {key: key for key in NODE_CLASS_MAPPINGS} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..41689f3 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,19 @@ +[tool.poetry] +name = "comfymath" +version = "0.1.0" +description = "Math nodes for ComfyUI" +authors = ["Evan Spearman "] +license = "Apache-2.0" +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.10" +numpy = "^1.25.1" + +[tool.poetry.group.dev.dependencies] +mypy = "^1.4.1" +black = "^23.7.0" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..24ce15a --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +numpy diff --git a/src/comfymath/__init__.py b/src/comfymath/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/comfymath/bool.py b/src/comfymath/bool.py new file mode 100644 index 0000000..a49e987 --- /dev/null +++ b/src/comfymath/bool.py @@ -0,0 +1,132 @@ +from abc import ABC, abstractmethod +from typing import Any, Mapping + + +class BoolPrimitive: + def __init__(sefl): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return {"required": {"a": (["true", "false"],)}} + + RETURN_TYPES = ("BOOL",) + FUNCTION = "op" + CATEGORY = "math/bool" + + def op(self, a: str) -> bool: + return True if a == "true" else False + + +class BoolUnaryOperator(ABC): + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return {"required": {"a": ("BOOL", {"default": False})}} + + RETURN_TYPES = ("BOOL",) + FUNCTION = "op" + + @abstractmethod + def op(self, a: bool) -> bool: + pass + + +class BoolBinaryOperator(ABC): + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return { + "required": { + "a": ("BOOL", {"default": False}), + "b": ("BOOL", {"default": False}), + } + } + + RETURN_TYPES = ("BOOL",) + FUNCTION = "op" + + @abstractmethod + def op(self, a: bool, b: bool) -> bool: + pass + + +class BoolAnd(BoolBinaryOperator): + def op(self, a: bool, b: bool) -> bool: + return a and b + + CATEGORY = "math/bool" + + +class BoolOr(BoolBinaryOperator): + def op(self, a: bool, b: bool) -> bool: + return a or b + + CATEGORY = "math/bool" + + +class BoolNot(BoolUnaryOperator): + def op(self, a: bool) -> bool: + return not a + + CATEGORY = "math/bool" + + +class BoolXor(BoolBinaryOperator): + def op(self, a: bool, b: bool) -> bool: + return a ^ b + + CATEGORY = "math/bool" + + +class BoolXnor(BoolBinaryOperator): + def op(self, a: bool, b: bool) -> bool: + return not (a ^ b) + + CATEGORY = "math/bool" + + +class BoolNand(BoolBinaryOperator): + def op(self, a: bool, b: bool) -> bool: + return not (a and b) + + CATEGORY = "math/bool" + + +class BoolNor(BoolBinaryOperator): + def op(self, a: bool, b: bool) -> bool: + return not (a or b) + + CATEGORY = "math/bool" + + +class BoolEq(BoolBinaryOperator): + def op(self, a: bool, b: bool) -> bool: + return a == b + + CATEGORY = "math/bool/compare" + + +class BoolNe(BoolBinaryOperator): + def op(self, a: bool, b: bool) -> bool: + return a != b + + CATEGORY = "math/bool/compare" + + +NODE_CLASS_MAPPINGS = { + "BoolPrimitive": BoolPrimitive, + "BoolAnd": BoolAnd, + "BoolOr": BoolOr, + "BoolXor": BoolXor, + "BoolNot": BoolNot, + "BoolXnor": BoolXnor, + "BoolNand": BoolNand, + "BoolNor": BoolNor, + "BoolEq": BoolEq, + "BoolNe": BoolNe, +} diff --git a/src/comfymath/convert.py b/src/comfymath/convert.py new file mode 100644 index 0000000..2f4e852 --- /dev/null +++ b/src/comfymath/convert.py @@ -0,0 +1,197 @@ +from typing import Any, Mapping + +from .vec2 import Vec2, VEC2_ZERO +from .vec3 import Vec3, VEC3_ZERO +from .vec4 import Vec4, VEC4_ZERO + + +class BoolToInt: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return {"required": {"a": ("BOOL", {"default": False})}} + + RETURN_TYPES = ("INT",) + FUNCTION = "op" + CATEGORY = "math/conversion" + + def op(self, a: bool) -> int: + return int(a) + + +class IntToBool: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return {"required": {"a": ("INT", {"default": 0})}} + + RETURN_TYPES = ("BOOL",) + FUNCTION = "op" + CATEGORY = "math/conversion" + + def op(self, a: int) -> bool: + return bool(a) + + +class FloatToInt: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return {"required": {"a": ("FLOAT", {"default": 0.0})}} + + RETURN_TYPES = ("INT",) + FUNCTION = "op" + CATEGORY = "math/conversion" + + def op(self, a: float) -> int: + return int(a) + + +class IntToFloat: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return {"required": {"a": ("INT", {"default": 0})}} + + RETURN_TYPES = ("FLOAT",) + FUNCTION = "op" + CATEGORY = "math/conversion" + + def op(self, a: int) -> float: + return float(a) + + +class ComposeVec2: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return { + "required": { + "x": ("FLOAT", {"default": 0.0}), + "y": ("FLOAT", {"default": 0.0}), + } + } + + RETURN_TYPES = ("VEC2",) + FUNCTION = "op" + CATEGORY = "math/conversion" + + def op(self, x: float, y: float) -> Vec2: + return (x, y) + + +class BreakoutVec2: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return {"required": {"a": ("VEC2", {"default": VEC2_ZERO})}} + + RETURN_TYPES = ("FLOAT", "FLOAT") + FUNCTION = "op" + CATEGORY = "math/conversion" + + def op(self, a: Vec2) -> tuple[float, float]: + return (a[0], a[1]) + + +class ComposeVec3: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return { + "required": { + "x": ("FLOAT", {"default": 0.0}), + "y": ("FLOAT", {"default": 0.0}), + "z": ("FLOAT", {"default": 0.0}), + } + } + + RETURN_TYPES = ("VEC3",) + FUNCTION = "op" + CATEGORY = "math/conversion" + + def op(self, x: float, y: float, z: float) -> Vec3: + return (x, y, z) + + +class BreakoutVec3: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return {"required": {"a": ("VEC3", {"default": VEC3_ZERO})}} + + RETURN_TYPES = ("FLOAT", "FLOAT", "FLOAT") + FUNCTION = "op" + CATEGORY = "math/conversion" + + def op(self, a: Vec3) -> tuple[float, float, float]: + return (a[0], a[1], a[2]) + + +class ComposeVec4: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return { + "required": { + "x": ("FLOAT", {"default": 0.0}), + "y": ("FLOAT", {"default": 0.0}), + "z": ("FLOAT", {"default": 0.0}), + "w": ("FLOAT", {"default": 0.0}), + } + } + + RETURN_TYPES = ("VEC4",) + FUNCTION = "op" + CATEGORY = "math/conversion" + + def op(self, x: float, y: float, z: float, w: float) -> Vec4: + return (x, y, z, w) + + +class BreakoutVec4: + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return {"required": {"a": ("VEC4", {"default": VEC4_ZERO})}} + + RETURN_TYPES = ("FLOAT", "FLOAT", "FLOAT", "FLOAT") + FUNCTION = "op" + CATEGORY = "math/conversion" + + def op(self, a: Vec4) -> tuple[float, float, float, float]: + return (a[0], a[1], a[2], a[3]) + + +NODE_CLASS_MAPPINGS = { + "BoolToInt": BoolToInt, + "IntToBool": IntToBool, + "FloatToInt": FloatToInt, + "IntToFloat": IntToFloat, + "ComposeVec2": ComposeVec2, + "ComposeVec3": ComposeVec3, + "ComposeVec4": ComposeVec4, + "BreakoutVec2": BreakoutVec2, + "BreakoutVec3": BreakoutVec3, + "BreakoutVec4": BreakoutVec4, +} diff --git a/src/comfymath/float.py b/src/comfymath/float.py new file mode 100644 index 0000000..3414618 --- /dev/null +++ b/src/comfymath/float.py @@ -0,0 +1,440 @@ +import math + +from abc import ABC, abstractmethod +from typing import Any, Mapping + + +class FloatUnaryOperator(ABC): + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return {"required": {"a": ("FLOAT", {"default": 0.0})}} + + RETURN_TYPES = ("FLOAT",) + FUNCTION = "op" + + @abstractmethod + def op(self, a: float) -> float: + pass + + +class FloatBinaryOperator(ABC): + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return { + "required": { + "a": ("FLOAT", {"default": 0.0}), + "b": ("FLOAT", {"default": 0.0}), + } + } + + RETURN_TYPES = ("FLOAT",) + FUNCTION = "op" + + @abstractmethod + def op(self, a: float, b: float) -> float: + pass + + +class FloatUnaryQuery(ABC): + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return {"required": {"a": ("FLOAT", {"default": 0.0})}} + + RETURN_TYPES = ("BOOL",) + FUNCTION = "op" + + @abstractmethod + def op(self, a: float) -> bool: + pass + + +class FloatBinaryQuery(ABC): + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return { + "required": { + "a": ("FLOAT", {"default": 0.0}), + "b": ("FLOAT", {"default": 0.0}), + } + } + + RETURN_TYPES = ("BOOL",) + FUNCTION = "op" + + @abstractmethod + def op(self, a: float, b: float) -> bool: + pass + + +class FloatAdd(FloatBinaryOperator): + def op(self, a: float, b: float) -> float: + return a + b + + CATEGORY = "math/float" + + +class FloatSub(FloatBinaryOperator): + def op(self, a: float, b: float) -> float: + return a - b + + CATEGORY = "math/float" + + +class FloatMul(FloatBinaryOperator): + def op(self, a: float, b: float) -> float: + return a * b + + CATEGORY = "math/float" + + +class FloatDiv(FloatBinaryOperator): + def op(self, a: float, b: float) -> float: + return a / b + + CATEGORY = "math/float" + + +class FloatLt(FloatBinaryQuery): + def op(self, a: float, b: float) -> bool: + return a < b + + CATEGORY = "math/float/compare" + + +class FloatGt(FloatBinaryQuery): + def op(self, a: float, b: float) -> bool: + return a > b + + CATEGORY = "math/float/compare" + + +class FloatLe(FloatBinaryQuery): + def op(self, a: float, b: float) -> bool: + return a <= b + + CATEGORY = "math/float/compare" + + +class FloatGe(FloatBinaryQuery): + def op(self, a: float, b: float) -> bool: + return a >= b + + CATEGORY = "math/float/compare" + + +class FloatEq(FloatBinaryQuery): + def op(self, a: float, b: float) -> bool: + return a == b + + CATEGORY = "math/float/compare" + + +class FloatNe(FloatBinaryQuery): + def op(self, a: float, b: float) -> bool: + return a != b + + CATEGORY = "math/float/compare" + + +class FloatNeg(FloatUnaryOperator): + def op(self, a: float) -> float: + return -a + + CATEGORY = "math/float" + + +class FloatInc(FloatUnaryOperator): + def op(self, a: float) -> float: + return a + 1 + + CATEGORY = "math/float" + + +class FloatDec(FloatUnaryOperator): + def op(self, a: float) -> float: + return a - 1 + + CATEGORY = "math/float" + + +class FloatAbs(FloatUnaryOperator): + def op(self, a: float) -> float: + return math.fabs(a) + + CATEGORY = "math/float" + + +class FloatSqrt(FloatUnaryOperator): + def op(self, a: float) -> float: + return a**0.5 + + CATEGORY = "math/float" + + +class FloatPow(FloatBinaryOperator): + def op(self, a: float, b: float) -> float: + return a**b + + CATEGORY = "math/float" + + +class FloatLog(FloatBinaryOperator): + def op(self, a: float, b: float) -> float: + return math.log(a, b) + + CATEGORY = "math/float" + + +class FloatCeil(FloatUnaryOperator): + def op(self, a: float) -> float: + return math.ceil(a) + + CATEGORY = "math/float" + + +class FloatFloor(FloatUnaryOperator): + def op(self, a: float) -> float: + return math.floor(a) + + CATEGORY = "math/float" + + +class FloatRound(FloatUnaryOperator): + def op(self, a: float) -> float: + return round(a) + + CATEGORY = "math/float" + + +class FloatTrunc(FloatUnaryOperator): + def op(self, a: float) -> float: + return math.trunc(a) + + CATEGORY = "math/float" + + +class FloatSin(FloatUnaryOperator): + def op(self, a: float) -> float: + return math.sin(a) + + CATEGORY = "math/float/trigonometry" + + +class FloatCos(FloatUnaryOperator): + def op(self, a: float) -> float: + return math.cos(a) + + CATEGORY = "math/float/trigonometry" + + +class FloatTan(FloatUnaryOperator): + def op(self, a: float) -> float: + return math.tan(a) + + CATEGORY = "math/float/trigonometry" + + +class FloatAsin(FloatUnaryOperator): + def op(self, a: float) -> float: + return math.asin(a) + + CATEGORY = "math/float/trigonometry" + + +class FloatAcos(FloatUnaryOperator): + def op(self, a: float) -> float: + return math.acos(a) + + CATEGORY = "math/float/trigonometry" + + +class FloatAtan(FloatUnaryOperator): + def op(self, a: float) -> float: + return math.atan(a) + + CATEGORY = "math/float/trigonometry" + + +class FloatAtan2(FloatBinaryOperator): + def op(self, a: float, b: float) -> float: + return math.atan2(a, b) + + CATEGORY = "math/float/trigonometry" + + +class FloatLn(FloatUnaryOperator): + def op(self, a: float) -> float: + return math.log(a) + + CATEGORY = "math/float" + + +class FloatLog10(FloatUnaryOperator): + def op(self, a: float) -> float: + return math.log10(a) + + CATEGORY = "math/float" + + +class FloatLog2(FloatUnaryOperator): + def op(self, a: float) -> float: + return math.log2(a) + + CATEGORY = "math/float" + + +class FloatSinh(FloatUnaryOperator): + def op(self, a: float) -> float: + return math.sinh(a) + + CATEGORY = "math/float/trigonometry" + + +class FloatCosh(FloatUnaryOperator): + def op(self, a: float) -> float: + return math.cosh(a) + + CATEGORY = "math/float/trigonometry" + + +class FloatTanh(FloatUnaryOperator): + def op(self, a: float) -> float: + return math.tanh(a) + + CATEGORY = "math/float/trigonometry" + + +class FloatAsinh(FloatUnaryOperator): + def op(self, a: float) -> float: + return math.asinh(a) + + CATEGORY = "math/float/trigonometry" + + +class FloatAcosh(FloatUnaryOperator): + def op(self, a: float) -> float: + return math.acosh(a) + + CATEGORY = "math/float/trigonometry" + + +class FloatAtanh(FloatUnaryOperator): + def op(self, a: float) -> float: + return math.atanh(a) + + CATEGORY = "math/float/trigonometry" + + +class FloatExp(FloatUnaryOperator): + def op(self, a: float) -> float: + return math.exp(a) + + CATEGORY = "math/float" + + +class FloatExpm1(FloatUnaryOperator): + def op(self, a: float) -> float: + return math.expm1(a) + + CATEGORY = "math/float/functions" + + +class FloatErf(FloatUnaryOperator): + def op(self, a: float) -> float: + return math.erf(a) + + CATEGORY = "math/float/functions" + + +class FloatErfc(FloatUnaryOperator): + def op(self, a: float) -> float: + return math.erfc(a) + + CATEGORY = "math/float/functions" + + +class FloatGamma(FloatUnaryOperator): + def op(self, a: float) -> float: + return math.gamma(a) + + CATEGORY = "math/float/functions" + + +class FloatRadians(FloatUnaryOperator): + def op(self, a: float) -> float: + return math.radians(a) + + CATEGORY = "math/float/trigonometry" + + +class FloatDegrees(FloatUnaryOperator): + def op(self, a: float) -> float: + return math.degrees(a) + + CATEGORY = "math/float/trigonometry" + + +NODE_CLASS_MAPPINGS = { + "FloatAdd": FloatAdd, + "FloatSub": FloatSub, + "FloatMul": FloatMul, + "FloatDiv": FloatDiv, + "FloatLt": FloatLt, + "FloatGt": FloatGt, + "FloatLe": FloatLe, + "FloatGe": FloatGe, + "FloatEq": FloatEq, + "FloatNe": FloatNe, + "FloatNeg": FloatNeg, + "FloatInc": FloatInc, + "FloatDec": FloatDec, + "FloatAbs": FloatAbs, + "FloatAbs": FloatAbs, + "FloatSqrt": FloatSqrt, + "FloatPow": FloatPow, + "FloatLog": FloatLog, + "FloatCeil": FloatCeil, + "FloatCeil": FloatCeil, + "FloatFloor": FloatFloor, + "FloatRound": FloatRound, + "FloatTrunc": FloatTrunc, + "FloatSin": FloatSin, + "FloatCos": FloatCos, + "FloatCos": FloatCos, + "FloatTan": FloatTan, + "FloatAsin": FloatAsin, + "FloatAcos": FloatAcos, + "FloatAtan": FloatAtan, + "FloatAtan2": FloatAtan2, + "FloatAtan2": FloatAtan2, + "FloatLn": FloatLn, + "FloatLog10": FloatLog10, + "FloatLog2": FloatLog2, + "FloatSinh": FloatSinh, + "FloatCosh": FloatCosh, + "FloatCosh": FloatCosh, + "FloatTanh": FloatTanh, + "FloatAsinh": FloatAsinh, + "FloatAcosh": FloatAcosh, + "FloatAtanh": FloatAtanh, + "FloatExp": FloatExp, + "FloatExpm1": FloatExpm1, + "FloatExpm1": FloatExpm1, + "FloatErf": FloatErf, + "FloatErfc": FloatErfc, + "FloatGamma": FloatGamma, + "FloatRadians": FloatRadians, + "FloatDegrees": FloatDegrees, +} diff --git a/src/comfymath/int.py b/src/comfymath/int.py new file mode 100644 index 0000000..a1e0fa8 --- /dev/null +++ b/src/comfymath/int.py @@ -0,0 +1,246 @@ +import math + +from abc import ABC, abstractmethod +from typing import Any, Mapping + + +class IntUnaryOperator(ABC): + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return {"required": {"a": ("INT", {"default": 0})}} + + RETURN_TYPES = ("INT",) + FUNCTION = "op" + + @abstractmethod + def op(self, a: int) -> int: + pass + + +class IntBinaryOperator(ABC): + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return { + "required": {"a": ("INT", {"default": 0}), "b": ("INT", {"default": 0})} + } + + RETURN_TYPES = ("INT",) + FUNCTION = "op" + + @abstractmethod + def op(self, a: int, b: int) -> int: + pass + + +class IntUnaryQuery(ABC): + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return {"required": {"a": ("INT", {"default": 0})}} + + RETURN_TYPES = ("BOOL",) + FUNCTION = "op" + + @abstractmethod + def op(self, a: int) -> bool: + pass + + +class IntBinaryQuery(ABC): + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return { + "required": { + "a": ("INT", {"default": 0}), + "b": ("INT", {"default": 0}), + } + } + + RETURN_TYPES = ("BOOL",) + FUNCTION = "op" + + @abstractmethod + def op(self, a: int, b: int) -> bool: + pass + + +class IntAdd(IntBinaryOperator): + def op(self, a: int, b: int) -> int: + return a + b + + CATEGORY = "math/int" + + +class IntSub(IntBinaryOperator): + def op(self, a: int, b: int) -> int: + return a - b + + CATEGORY = "math/int" + + +class IntMul(IntBinaryOperator): + def op(self, a: int, b: int) -> int: + return a * b + + CATEGORY = "math/int" + + +class IntDiv(IntBinaryOperator): + def op(self, a: int, b: int) -> int: + return a // b + + CATEGORY = "math/int" + + +class IntMod(IntBinaryOperator): + def op(self, a: int, b: int) -> int: + return a % b + + CATEGORY = "math/int" + + +class IntPow(IntBinaryOperator): + def op(self, a: int, b: int) -> int: + return a**b + + CATEGORY = "math/int" + + +class IntLt(IntBinaryQuery): + def op(self, a: int, b: int) -> bool: + return a < b + + CATEGORY = "math/int/compare" + + +class IntGt(IntBinaryQuery): + def op(self, a: int, b: int) -> bool: + return a > b + + CATEGORY = "math/int/compare" + + +class IntLe(IntBinaryQuery): + def op(self, a: int, b: int) -> bool: + return a <= b + + CATEGORY = "math/int/compare" + + +class IntGe(IntBinaryQuery): + def op(self, a: int, b: int) -> bool: + return a >= b + + CATEGORY = "math/int/compare" + + +class IntEq(IntBinaryQuery): + def op(self, a: int, b: int) -> bool: + return a == b + + CATEGORY = "math/int/compare" + + +class IntNe(IntBinaryQuery): + def op(self, a: int, b: int) -> bool: + return a != b + + CATEGORY = "math/int/compare" + + +class IntAnd(IntBinaryOperator): + def op(self, a: int, b: int) -> int: + return a & b + + CATEGORY = "math/int/bitwise" + + +class IntOr(IntBinaryOperator): + def op(self, a: int, b: int) -> int: + return a | b + + CATEGORY = "math/int/bitwise" + + +class IntXor(IntBinaryOperator): + def op(self, a: int, b: int) -> int: + return a ^ b + + CATEGORY = "math/int/bitwise" + + +class IntNot(IntUnaryOperator): + def op(self, a: int) -> int: + return ~a + + CATEGORY = "math/int/bitwise" + + +class IntNeg(IntUnaryOperator): + def op(self, a: int) -> int: + return -a + + CATEGORY = "math/int" + + +class IntInc(IntUnaryOperator): + def op(self, a: int) -> int: + return a + 1 + + CATEGORY = "math/int" + + +class IntDec(IntUnaryOperator): + def op(self, a: int) -> int: + return a - 1 + + CATEGORY = "math/int" + + +class IntAbs(IntUnaryOperator): + def op(self, a: int) -> int: + return abs(a) + + CATEGORY = "math/int" + + +class IntFactorial(IntUnaryOperator): + def op(self, a: int) -> int: + return math.factorial(a) + + CATEGORY = "math/int" + + +NODE_CLASS_MAPPINGS = { + "IntAdd": IntAdd, + "IntSub": IntSub, + "IntMul": IntMul, + "IntDiv": IntDiv, + "IntLt": IntLt, + "IntGt": IntGt, + "IntLe": IntLe, + "IntGe": IntGe, + "IntEq": IntEq, + "IntNe": IntNe, + "IntAnd": IntAnd, + "IntOr": IntOr, + "IntXor": IntXor, + "IntNot": IntNot, + "IntNeg": IntNeg, + "IntInc": IntInc, + "IntInc": IntInc, + "IntDec": IntDec, + "IntAbs": IntAbs, + "IntFactorial": IntFactorial, +} diff --git a/src/comfymath/py.typed b/src/comfymath/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/src/comfymath/vec2.py b/src/comfymath/vec2.py new file mode 100644 index 0000000..925b7ab --- /dev/null +++ b/src/comfymath/vec2.py @@ -0,0 +1,259 @@ +import numpy + +from abc import ABC, abstractmethod +from typing import Any, Mapping, TypeAlias + +Vec2: TypeAlias = tuple[float, float] +VEC2_ZERO = (0.0, 0.0) + + +def _vec2_from_numpy(a: numpy.ndarray) -> Vec2: + return ( + float(a[0]), + float(a[1]), + ) + + +class Vec2UnaryOperator(ABC): + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return {"required": {"a": ("VEC2", {"default": VEC2_ZERO})}} + + RETURN_TYPES = ("VEC2",) + FUNCTION = "op" + + def op(self, a: Vec2) -> Vec2: + return _vec2_from_numpy(self.op_numpy(numpy.array(a))) + + @abstractmethod + def op_numpy(self, a: numpy.ndarray) -> numpy.ndarray: + pass + + +class Vec2BinaryOperator(ABC): + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return { + "required": { + "a": ("VEC2", {"default": VEC2_ZERO}), + "b": ("VEC2", {"default": VEC2_ZERO}), + } + } + + RETURN_TYPES = ("VEC2",) + FUNCTION = "op" + + def op(self, a: Vec2, b: Vec2) -> Vec2: + return _vec2_from_numpy(self.op_numpy(numpy.array(a), numpy.array(b))) + + @abstractmethod + def op_numpy(self, a: numpy.ndarray, b: numpy.ndarray) -> numpy.ndarray: + pass + + +class Vec2UnaryQuery(ABC): + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return {"required": {"a": ("VEC2", {"default": VEC2_ZERO})}} + + RETURN_TYPES = ("BOOL",) + FUNCTION = "op" + + def op(self, a: Vec2) -> bool: + return self.op_numpy(numpy.array(a)) + + @abstractmethod + def op_numpy(self, a: numpy.ndarray) -> bool: + pass + + +class Vec2BinaryQuery(ABC): + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return { + "required": { + "a": ("VEC2", {"default": VEC2_ZERO}), + "b": ("VEC2", {"default": VEC2_ZERO}), + } + } + + RETURN_TYPES = ("BOOL",) + FUNCTION = "op" + + def op(self, a: Vec2, b: Vec2) -> bool: + return self.op_numpy(numpy.array(a), numpy.array(b)) + + @abstractmethod + def op_numpy(self, a: numpy.ndarray, b: numpy.ndarray) -> bool: + pass + + +class Vec2ToScalarUnary(ABC): + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return {"required": {"a": ("VEC2", {"default": VEC2_ZERO})}} + + RETURN_TYPES = ("FLOAT",) + FUNCTION = "op" + + def op(self, a: Vec2) -> float: + return self.op_numpy(numpy.array(a)) + + @abstractmethod + def op_numpy(self, a: numpy.ndarray) -> float: + pass + + +class Vec2ToScalarBinary(ABC): + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return { + "required": { + "a": ("VEC2", {"default": VEC2_ZERO}), + "b": ("VEC2", {"default": VEC2_ZERO}), + } + } + + RETURN_TYPES = ("FLOAT",) + FUNCTION = "op" + + def op(self, a: Vec2, b: Vec2) -> float: + return self.op_numpy(numpy.array(a), numpy.array(b)) + + @abstractmethod + def op_numpy(self, a: numpy.ndarray, b: numpy.ndarray) -> float: + pass + + +class Vec2ScalarOperation(ABC): + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return { + "required": { + "a": ("VEC2", {"default": VEC2_ZERO}), + "b": ("FLOAT", {"default": 0.0}), + } + } + + RETURN_TYPES = ("VEC2",) + FUNCTION = "op" + + def op(self, a: Vec2, b: float) -> Vec2: + return _vec2_from_numpy(self.op_numpy(numpy.array(a), b)) + + @abstractmethod + def op_numpy(self, a: numpy.ndarray, b: float) -> numpy.ndarray: + pass + + +class Vec2Add(Vec2BinaryOperator): + def op_numpy(self, a: numpy.ndarray, b: numpy.ndarray) -> numpy.ndarray: + return a + b + + CATEGORY = "math/vec2" + + +class Vec2Sub(Vec2BinaryOperator): + def op_numpy(self, a: numpy.ndarray, b: numpy.ndarray) -> numpy.ndarray: + return a - b + + CATEGORY = "math/vec2" + + +class Vec2Dot(Vec2ToScalarBinary): + def op_numpy(self, a: numpy.ndarray, b: numpy.ndarray) -> float: + return a.dot(b).astype(float) + + CATEGORY = "math/vec2" + + +class Vec2Cross(Vec2BinaryOperator): + def op_numpy(self, a: numpy.ndarray, b: numpy.ndarray) -> numpy.ndarray: + return numpy.cross(a, b) + + CATEGORY = "math/vec2" + + +class Vec2Eq(Vec2BinaryQuery): + def op_numpy(self, a: numpy.ndarray, b: numpy.ndarray) -> bool: + return a == b + + CATEGORY = "math/vec2" + + +class Vec2Ne(Vec2BinaryQuery): + def op_numpy(self, a: numpy.ndarray, b: numpy.ndarray) -> bool: + return a != b + + CATEGORY = "math/vec2" + + +class Vec2Neg(Vec2UnaryOperator): + def op_numpy(self, a: numpy.ndarray) -> numpy.ndarray: + return -a + + CATEGORY = "math/vec2" + + +class Vec2Norm(Vec2ToScalarUnary): + def op_numpy(self, a: numpy.ndarray) -> float: + return numpy.linalg.norm(a).astype(float) + + CATEGORY = "math/vec2" + + +class Vec2Normalize(Vec2UnaryOperator): + def op_numpy(self, a: numpy.ndarray) -> numpy.ndarray: + return a / numpy.linalg.norm(a) + + CATEGORY = "math/vec2" + + +class Vec2Distance(Vec2ToScalarBinary): + def op_numpy(self, a: numpy.ndarray, b: numpy.ndarray) -> float: + return numpy.linalg.norm(a - b).astype(float) + + CATEGORY = "math/vec2" + + +class Vec2ScalarMul(Vec2ScalarOperation): + def op_numpy(self, a: numpy.ndarray, b: float) -> numpy.ndarray: + return a * b + + CATEGORY = "math/vec2" + + +NODE_CLASS_MAPPINGS = { + "Vec2Add": Vec2Add, + "Vec2Sub": Vec2Sub, + "Vec2Dot": Vec2Dot, + "Vec2Cross": Vec2Cross, + "Vec2Eq": Vec2Eq, + "Vec2Ne": Vec2Ne, + "Vec2Neg": Vec2Neg, + "Vec2Norm": Vec2Norm, + "Vec2Normalize": Vec2Normalize, + "Vec2Distance": Vec2Distance, + "Vec2ScalarMul": Vec2ScalarMul, +} diff --git a/src/comfymath/vec3.py b/src/comfymath/vec3.py new file mode 100644 index 0000000..a88b57d --- /dev/null +++ b/src/comfymath/vec3.py @@ -0,0 +1,260 @@ +import numpy + +from abc import ABC, abstractmethod +from typing import Any, Mapping, TypeAlias + +Vec3: TypeAlias = tuple[float, float, float] +VEC3_ZERO = (0.0, 0.0, 0.0) + + +def _vec3_from_numpy(a: numpy.ndarray) -> Vec3: + return ( + float(a[0]), + float(a[1]), + float(a[2]), + ) + + +class Vec3UnaryOperator(ABC): + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return {"required": {"a": ("VEC3", {"default": VEC3_ZERO})}} + + RETURN_TYPES = ("VEC3",) + FUNCTION = "op" + + def op(self, a: Vec3) -> Vec3: + return _vec3_from_numpy(self.op_numpy(numpy.array(a))) + + @abstractmethod + def op_numpy(self, a: numpy.ndarray) -> numpy.ndarray: + pass + + +class Vec3BinaryOperator(ABC): + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return { + "required": { + "a": ("VEC3", {"default": VEC3_ZERO}), + "b": ("VEC3", {"default": VEC3_ZERO}), + } + } + + RETURN_TYPES = ("VEC3",) + FUNCTION = "op" + + def op(self, a: Vec3, b: Vec3) -> Vec3: + return _vec3_from_numpy(self.op_numpy(numpy.array(a), numpy.array(b))) + + @abstractmethod + def op_numpy(self, a: numpy.ndarray, b: numpy.ndarray) -> numpy.ndarray: + pass + + +class Vec3UnaryQuery(ABC): + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return {"required": {"a": ("VEC3", {"default": VEC3_ZERO})}} + + RETURN_TYPES = ("BOOL",) + FUNCTION = "op" + + def op(self, a: Vec3) -> bool: + return self.op_numpy(numpy.array(a)) + + @abstractmethod + def op_numpy(self, a: numpy.ndarray) -> bool: + pass + + +class Vec3BinaryQuery(ABC): + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return { + "required": { + "a": ("VEC3", {"default": VEC3_ZERO}), + "b": ("VEC3", {"default": VEC3_ZERO}), + } + } + + RETURN_TYPES = ("BOOL",) + FUNCTION = "op" + + def op(self, a: Vec3, b: Vec3) -> bool: + return self.op_numpy(numpy.array(a), numpy.array(b)) + + @abstractmethod + def op_numpy(self, a: numpy.ndarray, b: numpy.ndarray) -> bool: + pass + + +class Vec3ToScalarUnary(ABC): + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return {"required": {"a": ("VEC2", {"default": VEC3_ZERO})}} + + RETURN_TYPES = ("FLOAT",) + FUNCTION = "op" + + def op(self, a: Vec3) -> float: + return self.op_numpy(numpy.array(a)) + + @abstractmethod + def op_numpy(self, a: numpy.ndarray) -> float: + pass + + +class Vec3ToScalarBinary(ABC): + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return { + "required": { + "a": ("VEC2", {"default": VEC3_ZERO}), + "b": ("VEC2", {"default": VEC3_ZERO}), + } + } + + RETURN_TYPES = ("FLOAT",) + FUNCTION = "op" + + def op(self, a: Vec3, b: Vec3) -> float: + return self.op_numpy(numpy.array(a), numpy.array(b)) + + @abstractmethod + def op_numpy(self, a: numpy.ndarray, b: numpy.ndarray) -> float: + pass + + +class Vec3ScalarOperation(ABC): + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return { + "required": { + "a": ("VEC3", {"default": VEC3_ZERO}), + "b": ("FLOAT", {"default": 0.0}), + } + } + + RETURN_TYPES = ("VEC3",) + FUNCTION = "op" + + def op(self, a: Vec3, b: float) -> Vec3: + return _vec3_from_numpy(self.op_numpy(numpy.array(a), b)) + + @abstractmethod + def op_numpy(self, a: numpy.ndarray, b: float) -> numpy.ndarray: + pass + + +class Vec3Add(Vec3BinaryOperator): + def op_numpy(self, a: numpy.ndarray, b: numpy.ndarray) -> numpy.ndarray: + return a + b + + CATEGORY = "math/vec3" + + +class Vec3Sub(Vec3BinaryOperator): + def op_numpy(self, a: numpy.ndarray, b: numpy.ndarray) -> numpy.ndarray: + return a - b + + CATEGORY = "math/vec3" + + +class Vec3Dot(Vec3ToScalarBinary): + def op_numpy(self, a: numpy.ndarray, b: numpy.ndarray) -> float: + return a.dot(b).astype(float) + + CATEGORY = "math/vec3" + + +class Vec3Cross(Vec3BinaryOperator): + def op_numpy(self, a: numpy.ndarray, b: numpy.ndarray) -> numpy.ndarray: + return numpy.cross(a, b) + + CATEGORY = "math/vec3" + + +class Vec3Eq(Vec3BinaryQuery): + def op_numpy(self, a: numpy.ndarray, b: numpy.ndarray) -> bool: + return a == b + + CATEGORY = "math/vec3" + + +class Vec3Ne(Vec3BinaryQuery): + def op_numpy(self, a: numpy.ndarray, b: numpy.ndarray) -> bool: + return a != b + + CATEGORY = "math/vec3" + + +class Vec3Neg(Vec3UnaryOperator): + def op_numpy(self, a: numpy.ndarray) -> numpy.ndarray: + return -a + + CATEGORY = "math/vec3" + + +class Vec3Norm(Vec3ToScalarUnary): + def op_numpy(self, a: numpy.ndarray) -> float: + return numpy.linalg.norm(a).astype(float) + + CATEGORY = "math/vec3" + + +class Vec3Normalize(Vec3UnaryOperator): + def op_numpy(self, a: numpy.ndarray) -> numpy.ndarray: + return a / numpy.linalg.norm(a) + + CATEGORY = "math/vec3" + + +class Vec3Distance(Vec3ToScalarBinary): + def op_numpy(self, a: numpy.ndarray, b: numpy.ndarray) -> float: + return numpy.linalg.norm(a - b).astype(float) + + CATEGORY = "math/vec3" + + +class Vec3ScalarMul(Vec3ScalarOperation): + def op_numpy(self, a: numpy.ndarray, b: float) -> numpy.ndarray: + return a * b + + CATEGORY = "math/vec3" + + +NODE_CLASS_MAPPINGS = { + "Vec3Add": Vec3Add, + "Vec3Sub": Vec3Sub, + "Vec3Dot": Vec3Dot, + "Vec3Cross": Vec3Cross, + "Vec3Eq": Vec3Eq, + "Vec3Ne": Vec3Ne, + "Vec3Neg": Vec3Neg, + "Vec3Norm": Vec3Norm, + "Vec3Normalize": Vec3Normalize, + "Vec3Distance": Vec3Distance, + "Vec3ScalarMul": Vec3ScalarMul, +} diff --git a/src/comfymath/vec4.py b/src/comfymath/vec4.py new file mode 100644 index 0000000..f9612b7 --- /dev/null +++ b/src/comfymath/vec4.py @@ -0,0 +1,261 @@ +import numpy + +from abc import ABC, abstractmethod +from typing import Any, Mapping, TypeAlias + +Vec4: TypeAlias = tuple[float, float, float, float] +VEC4_ZERO = (0.0, 0.0, 0.0, 0.0) + + +def _vec4_from_numpy(a: numpy.ndarray) -> Vec4: + return ( + float(a[0]), + float(a[1]), + float(a[2]), + float(a[3]), + ) + + +class Vec4UnaryOperator(ABC): + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return {"required": {"a": ("VEC4", {"default": VEC4_ZERO})}} + + RETURN_TYPES = ("VEC4",) + FUNCTION = "op" + + def op(self, a: Vec4) -> Vec4: + return _vec4_from_numpy(self.op_numpy(numpy.array(a))) + + @abstractmethod + def op_numpy(self, a: numpy.ndarray) -> numpy.ndarray: + pass + + +class Vec4BinaryOperator(ABC): + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return { + "required": { + "a": ("VEC4", {"default": VEC4_ZERO}), + "b": ("VEC4", {"default": VEC4_ZERO}), + } + } + + RETURN_TYPES = ("VEC4",) + FUNCTION = "op" + + def op(self, a: Vec4, b: Vec4) -> Vec4: + return _vec4_from_numpy(self.op_numpy(numpy.array(a), numpy.array(b))) + + @abstractmethod + def op_numpy(self, a: numpy.ndarray, b: numpy.ndarray) -> numpy.ndarray: + pass + + +class Vec4UnaryQuery(ABC): + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return {"required": {"a": ("VEC4", {"default": VEC4_ZERO})}} + + RETURN_TYPES = ("BOOL",) + FUNCTION = "op" + + def op(self, a: Vec4) -> bool: + return self.op_numpy(numpy.array(a)) + + @abstractmethod + def op_numpy(self, a: numpy.ndarray) -> bool: + pass + + +class Vec4BinaryQuery(ABC): + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return { + "required": { + "a": ("VEC4", {"default": VEC4_ZERO}), + "b": ("VEC4", {"default": VEC4_ZERO}), + } + } + + RETURN_TYPES = ("BOOL",) + FUNCTION = "op" + + def op(self, a: Vec4, b: Vec4) -> bool: + return self.op_numpy(numpy.array(a), numpy.array(b)) + + @abstractmethod + def op_numpy(self, a: numpy.ndarray, b: numpy.ndarray) -> bool: + pass + + +class Vec4ToScalarUnary(ABC): + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return {"required": {"a": ("VEC4", {"default": VEC4_ZERO})}} + + RETURN_TYPES = ("FLOAT",) + FUNCTION = "op" + + def op(self, a: Vec4) -> float: + return self.op_numpy(numpy.array(a)) + + @abstractmethod + def op_numpy(self, a: numpy.ndarray) -> float: + pass + + +class Vec4ToScalarBinary(ABC): + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return { + "required": { + "a": ("VEC4", {"default": VEC4_ZERO}), + "b": ("VEC4", {"default": VEC4_ZERO}), + } + } + + RETURN_TYPES = ("FLOAT",) + FUNCTION = "op" + + def op(self, a: Vec4, b: Vec4) -> float: + return self.op_numpy(numpy.array(a), numpy.array(b)) + + @abstractmethod + def op_numpy(self, a: numpy.ndarray, b: numpy.ndarray) -> float: + pass + + +class Vec4ScalarOperation(ABC): + def __init__(self): + pass + + @classmethod + def INPUT_TYPES(cls) -> Mapping[str, Any]: + return { + "required": { + "a": ("VEC4", {"default": VEC4_ZERO}), + "b": ("FLOAT", {"default": 0.0}), + } + } + + RETURN_TYPES = ("VEC4",) + FUNCTION = "op" + + def op(self, a: Vec4, b: float) -> Vec4: + return _vec4_from_numpy(self.op_numpy(numpy.array(a), b)) + + @abstractmethod + def op_numpy(self, a: numpy.ndarray, b: float) -> numpy.ndarray: + pass + + +class Vec4Add(Vec4BinaryOperator): + def op_numpy(self, a: numpy.ndarray, b: numpy.ndarray) -> numpy.ndarray: + return a + b + + CATEGORY = "math/vec4" + + +class Vec4Sub(Vec4BinaryOperator): + def op_numpy(self, a: numpy.ndarray, b: numpy.ndarray) -> numpy.ndarray: + return a - b + + CATEGORY = "math/vec4" + + +class Vec4Dot(Vec4ToScalarBinary): + def op_numpy(self, a: numpy.ndarray, b: numpy.ndarray) -> float: + return a.dot(b).astype(float) + + CATEGORY = "math/vec4" + + +class Vec4Cross(Vec4BinaryOperator): + def op_numpy(self, a: numpy.ndarray, b: numpy.ndarray) -> numpy.ndarray: + return numpy.cross(a, b) + + CATEGORY = "math/vec4" + + +class Vec4Eq(Vec4BinaryQuery): + def op_numpy(self, a: numpy.ndarray, b: numpy.ndarray) -> bool: + return a == b + + CATEGORY = "math/vec4" + + +class Vec4Ne(Vec4BinaryQuery): + def op_numpy(self, a: numpy.ndarray, b: numpy.ndarray) -> bool: + return a != b + + CATEGORY = "math/vec4" + + +class Vec4Neg(Vec4UnaryOperator): + def op_numpy(self, a: numpy.ndarray) -> numpy.ndarray: + return -a + + CATEGORY = "math/vec4" + + +class Vec4Norm(Vec4ToScalarUnary): + def op_numpy(self, a: numpy.ndarray) -> float: + return numpy.linalg.norm(a).astype(float) + + CATEGORY = "math/vec4" + + +class Vec4Normalize(Vec4UnaryOperator): + def op_numpy(self, a: numpy.ndarray) -> numpy.ndarray: + return a / numpy.linalg.norm(a) + + CATEGORY = "math/vec4" + + +class Vec4Distance(Vec4ToScalarBinary): + def op_numpy(self, a: numpy.ndarray, b: numpy.ndarray) -> float: + return numpy.linalg.norm(a - b).astype(float) + + CATEGORY = "math/vec4" + + +class Vec4ScalarMul(Vec4ScalarOperation): + def op_numpy(self, a: numpy.ndarray, b: float) -> numpy.ndarray: + return a * b + + CATEGORY = "math/vec4" + + +NODE_CLASS_MAPPINGS = { + "Vec4Add": Vec4Add, + "Vec4Sub": Vec4Sub, + "Vec4Dot": Vec4Dot, + "Vec4Cross": Vec4Cross, + "Vec4Eq": Vec4Eq, + "Vec4Ne": Vec4Ne, + "Vec4Neg": Vec4Neg, + "Vec4Norm": Vec4Norm, + "Vec4Normalize": Vec4Normalize, + "Vec4Distance": Vec4Distance, + "Vec4ScalarMul": Vec4ScalarMul, +}