Add custom nodes, Civitai loras (LFS), and vast.ai setup script
Some checks failed
Python Linting / Run Ruff (push) Has been cancelled
Python Linting / Run Pylint (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.10, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.11, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.12, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-unix-nightly (12.1, , linux, 3.11, [self-hosted Linux], nightly) (push) Has been cancelled
Execution Tests / test (macos-latest) (push) Has been cancelled
Execution Tests / test (ubuntu-latest) (push) Has been cancelled
Execution Tests / test (windows-latest) (push) Has been cancelled
Test server launches without errors / test (push) Has been cancelled
Unit Tests / test (macos-latest) (push) Has been cancelled
Unit Tests / test (ubuntu-latest) (push) Has been cancelled
Unit Tests / test (windows-2022) (push) Has been cancelled

Includes 30 custom nodes committed directly, 7 Civitai-exclusive
loras stored via Git LFS, and a setup script that installs all
dependencies and downloads HuggingFace-hosted models on vast.ai.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-09 00:55:26 +00:00
parent 2b70ab9ad0
commit f09734b0ee
2274 changed files with 748556 additions and 3 deletions

View File

@@ -0,0 +1,59 @@
from typing import Any, Callable, Mapping
DEFAULT_BOOL = ("BOOLEAN", {"default": False})
BOOL_UNARY_OPERATIONS: Mapping[str, Callable[[bool], bool]] = {
"Not": lambda a: not a,
}
BOOL_BINARY_OPERATIONS: Mapping[str, Callable[[bool, bool], bool]] = {
"Nor": lambda a, b: not (a or b),
"Xor": lambda a, b: a ^ b,
"Nand": lambda a, b: not (a and b),
"And": lambda a, b: a and b,
"Xnor": lambda a, b: not (a ^ b),
"Or": lambda a, b: a or b,
"Eq": lambda a, b: a == b,
"Neq": lambda a, b: a != b,
}
class BoolUnaryOperation:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {"op": (list(BOOL_UNARY_OPERATIONS.keys()),), "a": DEFAULT_BOOL}
}
RETURN_TYPES = ("BOOLEAN",)
FUNCTION = "op"
CATEGORY = "math/bool"
def op(self, op: str, a: bool) -> tuple[bool]:
return (BOOL_UNARY_OPERATIONS[op](a),)
class BoolBinaryOperation:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {
"op": (list(BOOL_BINARY_OPERATIONS.keys()),),
"a": DEFAULT_BOOL,
"b": DEFAULT_BOOL,
}
}
RETURN_TYPES = ("BOOLEAN",)
FUNCTION = "op"
CATEGORY = "math/bool"
def op(self, op: str, a: bool, b: bool) -> tuple[bool]:
return (BOOL_BINARY_OPERATIONS[op](a, b),)
NODE_CLASS_MAPPINGS = {
"CM_BoolUnaryOperation": BoolUnaryOperation,
"CM_BoolBinaryOperation": BoolBinaryOperation,
}

View File

@@ -0,0 +1,3 @@
from typing import Any, Mapping
NODE_CLASS_MAPPINGS: Mapping[str, Any] = {}

View File

@@ -0,0 +1,273 @@
from typing import Any, Mapping
from .vec import VEC2_ZERO, VEC3_ZERO, VEC4_ZERO
from .types import Number, Vec2, Vec3, Vec4
class BoolToInt:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {"required": {"a": ("BOOLEAN", {"default": False})}}
RETURN_TYPES = ("INT",)
FUNCTION = "op"
CATEGORY = "math/conversion"
def op(self, a: bool) -> tuple[int]:
return (int(a),)
class IntToBool:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {"required": {"a": ("INT", {"default": 0})}}
RETURN_TYPES = ("BOOLEAN",)
FUNCTION = "op"
CATEGORY = "math/conversion"
def op(self, a: int) -> tuple[bool]:
return (a != 0,)
class FloatToInt:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {"required": {"a": ("FLOAT", {"default": 0.0, "round": False})}}
RETURN_TYPES = ("INT",)
FUNCTION = "op"
CATEGORY = "math/conversion"
def op(self, a: float) -> tuple[int]:
return (int(a),)
class IntToFloat:
@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) -> tuple[float]:
return (float(a),)
class IntToNumber:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {"required": {"a": ("INT", {"default": 0})}}
RETURN_TYPES = ("NUMBER",)
FUNCTION = "op"
CATEGORY = "math/conversion"
def op(self, a: int) -> tuple[Number]:
return (a,)
class NumberToInt:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {"required": {"a": ("NUMBER", {"default": 0.0})}}
RETURN_TYPES = ("INT",)
FUNCTION = "op"
CATEGORY = "math/conversion"
def op(self, a: Number) -> tuple[int]:
return (int(a),)
class FloatToNumber:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {"required": {"a": ("FLOAT", {"default": 0.0, "round": False})}}
RETURN_TYPES = ("NUMBER",)
FUNCTION = "op"
CATEGORY = "math/conversion"
def op(self, a: float) -> tuple[Number]:
return (a,)
class NumberToFloat:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {"required": {"a": ("NUMBER", {"default": 0.0})}}
RETURN_TYPES = ("FLOAT",)
FUNCTION = "op"
CATEGORY = "math/conversion"
def op(self, a: Number) -> tuple[float]:
return (float(a),)
class ComposeVec2:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {
"x": ("FLOAT", {"default": 0.0, "round": False}),
"y": ("FLOAT", {"default": 0.0, "round": False}),
}
}
RETURN_TYPES = ("VEC2",)
FUNCTION = "op"
CATEGORY = "math/conversion"
def op(self, x: float, y: float) -> tuple[Vec2]:
return ((x, y),)
class FillVec2:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {
"a": ("FLOAT", {"default": 0.0, "round": False}),
}
}
RETURN_TYPES = ("VEC2",)
FUNCTION = "op"
CATEGORY = "math/conversion"
def op(self, a: float) -> tuple[Vec2]:
return ((a, a),)
class BreakoutVec2:
@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:
@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) -> tuple[Vec3]:
return ((x, y, z),)
class FillVec3:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {
"a": ("FLOAT", {"default": 0.0}),
}
}
RETURN_TYPES = ("VEC3",)
FUNCTION = "op"
CATEGORY = "math/conversion"
def op(self, a: float) -> tuple[Vec3]:
return ((a, a, a),)
class BreakoutVec3:
@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:
@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) -> tuple[Vec4]:
return ((x, y, z, w),)
class FillVec4:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {
"a": ("FLOAT", {"default": 0.0}),
}
}
RETURN_TYPES = ("VEC4",)
FUNCTION = "op"
CATEGORY = "math/conversion"
def op(self, a: float) -> tuple[Vec4]:
return ((a, a, a, a),)
class BreakoutVec4:
@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 = {
"CM_BoolToInt": BoolToInt,
"CM_IntToBool": IntToBool,
"CM_FloatToInt": FloatToInt,
"CM_IntToFloat": IntToFloat,
"CM_IntToNumber": IntToNumber,
"CM_NumberToInt": NumberToInt,
"CM_FloatToNumber": FloatToNumber,
"CM_NumberToFloat": NumberToFloat,
"CM_ComposeVec2": ComposeVec2,
"CM_ComposeVec3": ComposeVec3,
"CM_ComposeVec4": ComposeVec4,
"CM_BreakoutVec2": BreakoutVec2,
"CM_BreakoutVec3": BreakoutVec3,
"CM_BreakoutVec4": BreakoutVec4,
}

View File

@@ -0,0 +1,159 @@
import math
from typing import Any, Callable, Mapping
DEFAULT_FLOAT = ("FLOAT", {"default": 0.0, "step": 0.001, "round": False})
FLOAT_UNARY_OPERATIONS: Mapping[str, Callable[[float], float]] = {
"Neg": lambda a: -a,
"Inc": lambda a: a + 1,
"Dec": lambda a: a - 1,
"Abs": lambda a: abs(a),
"Sqr": lambda a: a * a,
"Cube": lambda a: a * a * a,
"Sqrt": lambda a: math.sqrt(a),
"Exp": lambda a: math.exp(a),
"Ln": lambda a: math.log(a),
"Log10": lambda a: math.log10(a),
"Log2": lambda a: math.log2(a),
"Sin": lambda a: math.sin(a),
"Cos": lambda a: math.cos(a),
"Tan": lambda a: math.tan(a),
"Asin": lambda a: math.asin(a),
"Acos": lambda a: math.acos(a),
"Atan": lambda a: math.atan(a),
"Sinh": lambda a: math.sinh(a),
"Cosh": lambda a: math.cosh(a),
"Tanh": lambda a: math.tanh(a),
"Asinh": lambda a: math.asinh(a),
"Acosh": lambda a: math.acosh(a),
"Atanh": lambda a: math.atanh(a),
"Round": lambda a: round(a),
"Floor": lambda a: math.floor(a),
"Ceil": lambda a: math.ceil(a),
"Trunc": lambda a: math.trunc(a),
"Erf": lambda a: math.erf(a),
"Erfc": lambda a: math.erfc(a),
"Gamma": lambda a: math.gamma(a),
"Radians": lambda a: math.radians(a),
"Degrees": lambda a: math.degrees(a),
}
FLOAT_UNARY_CONDITIONS: Mapping[str, Callable[[float], bool]] = {
"IsZero": lambda a: a == 0.0,
"IsPositive": lambda a: a > 0.0,
"IsNegative": lambda a: a < 0.0,
"IsNonZero": lambda a: a != 0.0,
"IsPositiveInfinity": lambda a: math.isinf(a) and a > 0.0,
"IsNegativeInfinity": lambda a: math.isinf(a) and a < 0.0,
"IsNaN": lambda a: math.isnan(a),
"IsFinite": lambda a: math.isfinite(a),
"IsInfinite": lambda a: math.isinf(a),
"IsEven": lambda a: a % 2 == 0.0,
"IsOdd": lambda a: a % 2 != 0.0,
}
FLOAT_BINARY_OPERATIONS: Mapping[str, Callable[[float, float], float]] = {
"Add": lambda a, b: a + b,
"Sub": lambda a, b: a - b,
"Mul": lambda a, b: a * b,
"Div": lambda a, b: a / b,
"Mod": lambda a, b: a % b,
"Pow": lambda a, b: a**b,
"FloorDiv": lambda a, b: a // b,
"Max": lambda a, b: max(a, b),
"Min": lambda a, b: min(a, b),
"Log": lambda a, b: math.log(a, b),
"Atan2": lambda a, b: math.atan2(a, b),
}
FLOAT_BINARY_CONDITIONS: Mapping[str, Callable[[float, float], bool]] = {
"Eq": lambda a, b: a == b,
"Neq": lambda a, b: a != b,
"Gt": lambda a, b: a > b,
"Gte": lambda a, b: a >= b,
"Lt": lambda a, b: a < b,
"Lte": lambda a, b: a <= b,
}
class FloatUnaryOperation:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {
"op": (list(FLOAT_UNARY_OPERATIONS.keys()),),
"a": DEFAULT_FLOAT,
}
}
RETURN_TYPES = ("FLOAT",)
FUNCTION = "op"
CATEGORY = "math/float"
def op(self, op: str, a: float) -> tuple[float]:
return (FLOAT_UNARY_OPERATIONS[op](a),)
class FloatUnaryCondition:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {
"op": (list(FLOAT_UNARY_CONDITIONS.keys()),),
"a": DEFAULT_FLOAT,
}
}
RETURN_TYPES = ("BOOLEAN",)
FUNCTION = "op"
CATEGORY = "math/float"
def op(self, op: str, a: float) -> tuple[bool]:
return (FLOAT_UNARY_CONDITIONS[op](a),)
class FloatBinaryOperation:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {
"op": (list(FLOAT_BINARY_OPERATIONS.keys()),),
"a": DEFAULT_FLOAT,
"b": DEFAULT_FLOAT,
}
}
RETURN_TYPES = ("FLOAT",)
FUNCTION = "op"
CATEGORY = "math/float"
def op(self, op: str, a: float, b: float) -> tuple[float]:
return (FLOAT_BINARY_OPERATIONS[op](a, b),)
class FloatBinaryCondition:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {
"op": (list(FLOAT_BINARY_CONDITIONS.keys()),),
"a": DEFAULT_FLOAT,
"b": DEFAULT_FLOAT,
}
}
RETURN_TYPES = ("BOOLEAN",)
FUNCTION = "op"
CATEGORY = "math/float"
def op(self, op: str, a: float, b: float) -> tuple[bool]:
return (FLOAT_BINARY_CONDITIONS[op](a, b),)
NODE_CLASS_MAPPINGS = {
"CM_FloatUnaryOperation": FloatUnaryOperation,
"CM_FloatUnaryCondition": FloatUnaryCondition,
"CM_FloatBinaryOperation": FloatBinaryOperation,
"CM_FloatBinaryCondition": FloatBinaryCondition,
}

View File

@@ -0,0 +1,153 @@
from abc import ABC, abstractmethod
from typing import Any, Mapping, Sequence, Tuple
SDXL_SUPPORTED_RESOLUTIONS = [
(1024, 1024, 1.0),
(1152, 896, 1.2857142857142858),
(896, 1152, 0.7777777777777778),
(1216, 832, 1.4615384615384615),
(832, 1216, 0.6842105263157895),
(1344, 768, 1.75),
(768, 1344, 0.5714285714285714),
(1536, 640, 2.4),
(640, 1536, 0.4166666666666667),
]
SDXL_EXTENDED_RESOLUTIONS = [
(512, 2048, 0.25),
(512, 1984, 0.26),
(512, 1920, 0.27),
(512, 1856, 0.28),
(576, 1792, 0.32),
(576, 1728, 0.33),
(576, 1664, 0.35),
(640, 1600, 0.4),
(640, 1536, 0.42),
(704, 1472, 0.48),
(704, 1408, 0.5),
(704, 1344, 0.52),
(768, 1344, 0.57),
(768, 1280, 0.6),
(832, 1216, 0.68),
(832, 1152, 0.72),
(896, 1152, 0.78),
(896, 1088, 0.82),
(960, 1088, 0.88),
(960, 1024, 0.94),
(1024, 1024, 1.0),
(1024, 960, 1.8),
(1088, 960, 1.14),
(1088, 896, 1.22),
(1152, 896, 1.30),
(1152, 832, 1.39),
(1216, 832, 1.47),
(1280, 768, 1.68),
(1344, 768, 1.76),
(1408, 704, 2.0),
(1472, 704, 2.10),
(1536, 640, 2.4),
(1600, 640, 2.5),
(1664, 576, 2.90),
(1728, 576, 3.0),
(1792, 576, 3.12),
(1856, 512, 3.63),
(1920, 512, 3.76),
(1984, 512, 3.89),
(2048, 512, 4.0),
]
class Resolution(ABC):
@classmethod
@abstractmethod
def resolutions(cls) -> Sequence[Tuple[int, int, float]]: ...
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {
"resolution": ([f"{res[0]}x{res[1]}" for res in cls.resolutions()],)
}
}
RETURN_TYPES = ("INT", "INT")
RETURN_NAMES = ("width", "height")
FUNCTION = "op"
CATEGORY = "math/graphics"
def op(self, resolution: str) -> tuple[int, int]:
width, height = resolution.split("x")
return (int(width), int(height))
class NearestResolution(ABC):
@classmethod
@abstractmethod
def resolutions(cls) -> Sequence[Tuple[int, int, float]]: ...
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {"required": {"image": ("IMAGE",)}}
RETURN_TYPES = ("INT", "INT")
RETURN_NAMES = ("width", "height")
FUNCTION = "op"
CATEGORY = "math/graphics"
def op(self, image) -> tuple[int, int]:
image_width = image.size()[2]
image_height = image.size()[1]
print(f"Input image resolution: {image_width}x{image_height}")
image_ratio = image_width / image_height
differences = [
(abs(image_ratio - resolution[2]), resolution)
for resolution in self.resolutions()
]
smallest = None
for difference in differences:
if smallest is None:
smallest = difference
else:
if difference[0] < smallest[0]:
smallest = difference
if smallest is not None:
width = smallest[1][0]
height = smallest[1][1]
else:
width = 1024
height = 1024
print(f"Selected resolution: {width}x{height}")
return (width, height)
class SDXLResolution(Resolution):
@classmethod
def resolutions(cls):
return SDXL_SUPPORTED_RESOLUTIONS
class SDXLExtendedResolution(Resolution):
@classmethod
def resolutions(cls):
return SDXL_EXTENDED_RESOLUTIONS
class NearestSDXLResolution(NearestResolution):
@classmethod
def resolutions(cls):
return SDXL_SUPPORTED_RESOLUTIONS
class NearestSDXLExtendedResolution(NearestResolution):
@classmethod
def resolutions(cls):
return SDXL_EXTENDED_RESOLUTIONS
NODE_CLASS_MAPPINGS = {
"CM_SDXLResolution": SDXLResolution,
"CM_NearestSDXLResolution": NearestSDXLResolution,
"CM_SDXLExtendedResolution": SDXLExtendedResolution,
"CM_NearestSDXLExtendedResolution": NearestSDXLExtendedResolution,
}

View File

@@ -0,0 +1,129 @@
import math
from typing import Any, Callable, Mapping
DEFAULT_INT = ("INT", {"default": 0})
INT_UNARY_OPERATIONS: Mapping[str, Callable[[int], int]] = {
"Abs": lambda a: abs(a),
"Neg": lambda a: -a,
"Inc": lambda a: a + 1,
"Dec": lambda a: a - 1,
"Sqr": lambda a: a * a,
"Cube": lambda a: a * a * a,
"Not": lambda a: ~a,
"Factorial": lambda a: math.factorial(a),
}
INT_UNARY_CONDITIONS: Mapping[str, Callable[[int], bool]] = {
"IsZero": lambda a: a == 0,
"IsNonZero": lambda a: a != 0,
"IsPositive": lambda a: a > 0,
"IsNegative": lambda a: a < 0,
"IsEven": lambda a: a % 2 == 0,
"IsOdd": lambda a: a % 2 == 1,
}
INT_BINARY_OPERATIONS: Mapping[str, Callable[[int, int], int]] = {
"Add": lambda a, b: a + b,
"Sub": lambda a, b: a - b,
"Mul": lambda a, b: a * b,
"Div": lambda a, b: a // b,
"Mod": lambda a, b: a % b,
"Pow": lambda a, b: a**b,
"And": lambda a, b: a & b,
"Nand": lambda a, b: ~a & b,
"Or": lambda a, b: a | b,
"Nor": lambda a, b: ~a & b,
"Xor": lambda a, b: a ^ b,
"Xnor": lambda a, b: ~a ^ b,
"Shl": lambda a, b: a << b,
"Shr": lambda a, b: a >> b,
"Max": lambda a, b: max(a, b),
"Min": lambda a, b: min(a, b),
}
INT_BINARY_CONDITIONS: Mapping[str, Callable[[int, int], bool]] = {
"Eq": lambda a, b: a == b,
"Neq": lambda a, b: a != b,
"Gt": lambda a, b: a > b,
"Lt": lambda a, b: a < b,
"Geq": lambda a, b: a >= b,
"Leq": lambda a, b: a <= b,
}
class IntUnaryOperation:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {"op": (list(INT_UNARY_OPERATIONS.keys()),), "a": DEFAULT_INT}
}
RETURN_TYPES = ("INT",)
FUNCTION = "op"
CATEGORY = "math/int"
def op(self, op: str, a: int) -> tuple[int]:
return (INT_UNARY_OPERATIONS[op](a),)
class IntUnaryCondition:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {"op": (list(INT_UNARY_CONDITIONS.keys()),), "a": DEFAULT_INT}
}
RETURN_TYPES = ("BOOL",)
FUNCTION = "op"
CATEGORY = "math/int"
def op(self, op: str, a: int) -> tuple[bool]:
return (INT_UNARY_CONDITIONS[op](a),)
class IntBinaryOperation:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {
"op": (list(INT_BINARY_OPERATIONS.keys()),),
"a": DEFAULT_INT,
"b": DEFAULT_INT,
}
}
RETURN_TYPES = ("INT",)
FUNCTION = "op"
CATEGORY = "math/int"
def op(self, op: str, a: int, b: int) -> tuple[int]:
return (INT_BINARY_OPERATIONS[op](a, b),)
class IntBinaryCondition:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {
"op": (list(INT_BINARY_CONDITIONS.keys()),),
"a": DEFAULT_INT,
"b": DEFAULT_INT,
}
}
RETURN_TYPES = ("BOOL",)
FUNCTION = "op"
CATEGORY = "math/int"
def op(self, op: str, a: int, b: int) -> tuple[bool]:
return (INT_BINARY_CONDITIONS[op](a, b),)
NODE_CLASS_MAPPINGS = {
"CM_IntUnaryOperation": IntUnaryOperation,
"CM_IntUnaryCondition": IntUnaryCondition,
"CM_IntBinaryOperation": IntBinaryOperation,
"CM_IntBinaryCondition": IntBinaryCondition,
}

View File

@@ -0,0 +1,94 @@
from dataclasses import dataclass
from typing import Any, Callable, Mapping
from .float import (
FLOAT_UNARY_OPERATIONS,
FLOAT_UNARY_CONDITIONS,
FLOAT_BINARY_OPERATIONS,
FLOAT_BINARY_CONDITIONS,
)
from .types import Number
DEFAULT_NUMBER = ("NUMBER", {"default": 0.0})
class NumberUnaryOperation:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {
"op": (list(FLOAT_UNARY_OPERATIONS.keys()),),
"a": DEFAULT_NUMBER,
}
}
RETURN_TYPES = ("NUMBER",)
FUNCTION = "op"
CATEGORY = "math/number"
def op(self, op: str, a: Number) -> tuple[float]:
return (FLOAT_UNARY_OPERATIONS[op](float(a)),)
class NumberUnaryCondition:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {
"op": (list(FLOAT_UNARY_CONDITIONS.keys()),),
"a": DEFAULT_NUMBER,
}
}
RETURN_TYPES = ("BOOL",)
FUNCTION = "op"
CATEGORY = "math/Number"
def op(self, op: str, a: Number) -> tuple[bool]:
return (FLOAT_UNARY_CONDITIONS[op](float(a)),)
class NumberBinaryOperation:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {
"op": (list(FLOAT_BINARY_OPERATIONS.keys()),),
"a": DEFAULT_NUMBER,
"b": DEFAULT_NUMBER,
}
}
RETURN_TYPES = ("NUMBER",)
FUNCTION = "op"
CATEGORY = "math/number"
def op(self, op: str, a: Number, b: Number) -> tuple[float]:
return (FLOAT_BINARY_OPERATIONS[op](float(a), float(b)),)
class NumberBinaryCondition:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {
"op": (list(FLOAT_BINARY_CONDITIONS.keys()),),
"a": DEFAULT_NUMBER,
"b": DEFAULT_NUMBER,
}
}
RETURN_TYPES = ("BOOL",)
FUNCTION = "op"
CATEGORY = "math/float"
def op(self, op: str, a: Number, b: Number) -> tuple[bool]:
return (FLOAT_BINARY_CONDITIONS[op](float(a), float(b)),)
NODE_CLASS_MAPPINGS = {
"CM_NumberUnaryOperation": NumberUnaryOperation,
"CM_NumberUnaryCondition": NumberUnaryCondition,
"CM_NumberBinaryOperation": NumberBinaryOperation,
"CM_NumberBinaryCondition": NumberBinaryCondition,
}

View File

@@ -0,0 +1,16 @@
import sys
if sys.version_info[1] < 10:
from typing import Tuple, Union
Number = Union[int, float]
Vec2 = Tuple[float, float]
Vec3 = Tuple[float, float, float]
Vec4 = Tuple[float, float, float, float]
else:
from typing import TypeAlias
Number: TypeAlias = int | float
Vec2: TypeAlias = tuple[float, float]
Vec3: TypeAlias = tuple[float, float, float]
Vec4: TypeAlias = tuple[float, float, float, float]

View File

@@ -0,0 +1,500 @@
import numpy
from typing import Any, Callable, Mapping
from .types import Vec2, Vec3, Vec4
VEC2_ZERO = (0.0, 0.0)
DEFAULT_VEC2 = ("VEC2", {"default": VEC2_ZERO})
VEC3_ZERO = (0.0, 0.0, 0.0)
DEFAULT_VEC3 = ("VEC3", {"default": VEC3_ZERO})
VEC4_ZERO = (0.0, 0.0, 0.0, 0.0)
DEFAULT_VEC4 = ("VEC4", {"default": VEC4_ZERO})
VEC_UNARY_OPERATIONS: Mapping[str, Callable[[numpy.ndarray], numpy.ndarray]] = {
"Neg": lambda a: -a,
"Normalize": lambda a: a / numpy.linalg.norm(a),
}
VEC_TO_SCALAR_UNARY_OPERATION: Mapping[str, Callable[[numpy.ndarray], float]] = {
"Norm": lambda a: numpy.linalg.norm(a).astype(float),
}
VEC_UNARY_CONDITIONS: Mapping[str, Callable[[numpy.ndarray], bool]] = {
"IsZero": lambda a: not numpy.any(a).astype(bool),
"IsNotZero": lambda a: numpy.any(a).astype(bool),
"IsNormalized": lambda a: numpy.allclose(a, a / numpy.linalg.norm(a)),
"IsNotNormalized": lambda a: not numpy.allclose(a, a / numpy.linalg.norm(a)),
}
VEC_BINARY_OPERATIONS: Mapping[
str, Callable[[numpy.ndarray, numpy.ndarray], numpy.ndarray]
] = {
"Add": lambda a, b: a + b,
"Sub": lambda a, b: a - b,
"Cross": lambda a, b: numpy.cross(a, b),
}
VEC_TO_SCALAR_BINARY_OPERATION: Mapping[
str, Callable[[numpy.ndarray, numpy.ndarray], float]
] = {
"Dot": lambda a, b: numpy.dot(a, b),
"Distance": lambda a, b: numpy.linalg.norm(a - b).astype(float),
}
VEC_BINARY_CONDITIONS: Mapping[str, Callable[[numpy.ndarray, numpy.ndarray], bool]] = {
"Eq": lambda a, b: numpy.allclose(a, b),
"Neq": lambda a, b: not numpy.allclose(a, b),
}
VEC_SCALAR_OPERATION: Mapping[str, Callable[[numpy.ndarray, float], numpy.ndarray]] = {
"Mul": lambda a, b: a * b,
"Div": lambda a, b: a / b,
}
def _vec2_from_numpy(a: numpy.ndarray) -> Vec2:
return (
float(a[0]),
float(a[1]),
)
def _vec3_from_numpy(a: numpy.ndarray) -> Vec3:
return (
float(a[0]),
float(a[1]),
float(a[2]),
)
def _vec4_from_numpy(a: numpy.ndarray) -> Vec4:
return (
float(a[0]),
float(a[1]),
float(a[2]),
float(a[3]),
)
class Vec2UnaryOperation:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {
"op": (list(VEC_UNARY_OPERATIONS.keys()),),
"a": DEFAULT_VEC2,
}
}
RETURN_TYPES = ("VEC2",)
FUNCTION = "op"
CATEGORY = "math/vec2"
def op(self, op: str, a: Vec2) -> tuple[Vec2]:
return (_vec2_from_numpy(VEC_UNARY_OPERATIONS[op](numpy.array(a))),)
class Vec2ToScalarUnaryOperation:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {
"op": (list(VEC_TO_SCALAR_UNARY_OPERATION.keys()),),
"a": DEFAULT_VEC2,
}
}
RETURN_TYPES = ("FLOAT",)
FUNCTION = "op"
CATEGORY = "math/vec2"
def op(self, op: str, a: Vec2) -> tuple[float]:
return (VEC_TO_SCALAR_UNARY_OPERATION[op](numpy.array(a)),)
class Vec2UnaryCondition:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {
"op": (list(VEC_UNARY_CONDITIONS.keys()),),
"a": DEFAULT_VEC2,
}
}
RETURN_TYPES = ("BOOL",)
FUNCTION = "op"
CATEGORY = "math/vec2"
def op(self, op: str, a: Vec2) -> tuple[bool]:
return (VEC_UNARY_CONDITIONS[op](numpy.array(a)),)
class Vec2BinaryOperation:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {
"op": (list(VEC_BINARY_OPERATIONS.keys()),),
"a": DEFAULT_VEC2,
"b": DEFAULT_VEC2,
}
}
RETURN_TYPES = ("VEC2",)
FUNCTION = "op"
CATEGORY = "math/vec2"
def op(self, op: str, a: Vec2, b: Vec2) -> tuple[Vec2]:
return (
_vec2_from_numpy(VEC_BINARY_OPERATIONS[op](numpy.array(a), numpy.array(b))),
)
class Vec2ToScalarBinaryOperation:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {
"op": (list(VEC_TO_SCALAR_BINARY_OPERATION.keys()),),
"a": DEFAULT_VEC2,
"b": DEFAULT_VEC2,
}
}
RETURN_TYPES = ("FLOAT",)
FUNCTION = "op"
CATEGORY = "math/vec2"
def op(self, op: str, a: Vec2, b: Vec2) -> tuple[float]:
return (VEC_TO_SCALAR_BINARY_OPERATION[op](numpy.array(a), numpy.array(b)),)
class Vec2BinaryCondition:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {
"op": (list(VEC_BINARY_CONDITIONS.keys()),),
"a": DEFAULT_VEC2,
"b": DEFAULT_VEC2,
}
}
RETURN_TYPES = ("BOOL",)
FUNCTION = "op"
CATEGORY = "math/vec2"
def op(self, op: str, a: Vec2, b: Vec2) -> tuple[bool]:
return (VEC_BINARY_CONDITIONS[op](numpy.array(a), numpy.array(b)),)
class Vec2ScalarOperation:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {
"op": (list(VEC_SCALAR_OPERATION.keys()),),
"a": DEFAULT_VEC2,
"b": ("FLOAT",),
}
}
RETURN_TYPES = ("VEC2",)
FUNCTION = "op"
CATEGORY = "math/vec2"
def op(self, op: str, a: Vec2, b: float) -> tuple[Vec2]:
return (_vec2_from_numpy(VEC_SCALAR_OPERATION[op](numpy.array(a), b)),)
class Vec3UnaryOperation:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {
"op": (list(VEC_UNARY_OPERATIONS.keys()),),
"a": DEFAULT_VEC3,
}
}
RETURN_TYPES = ("VEC3",)
FUNCTION = "op"
CATEGORY = "math/vec3"
def op(self, op: str, a: Vec3) -> tuple[Vec3]:
return (_vec3_from_numpy(VEC_UNARY_OPERATIONS[op](numpy.array(a))),)
class Vec3ToScalarUnaryOperation:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {
"op": (list(VEC_TO_SCALAR_UNARY_OPERATION.keys()),),
"a": DEFAULT_VEC3,
}
}
RETURN_TYPES = ("FLOAT",)
FUNCTION = "op"
CATEGORY = "math/vec3"
def op(self, op: str, a: Vec3) -> tuple[float]:
return (VEC_TO_SCALAR_UNARY_OPERATION[op](numpy.array(a)),)
class Vec3UnaryCondition:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {
"op": (list(VEC_UNARY_CONDITIONS.keys()),),
"a": DEFAULT_VEC3,
}
}
RETURN_TYPES = ("BOOL",)
FUNCTION = "op"
CATEGORY = "math/vec3"
def op(self, op: str, a: Vec3) -> tuple[bool]:
return (VEC_UNARY_CONDITIONS[op](numpy.array(a)),)
class Vec3BinaryOperation:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {
"op": (list(VEC_BINARY_OPERATIONS.keys()),),
"a": DEFAULT_VEC3,
"b": DEFAULT_VEC3,
}
}
RETURN_TYPES = ("VEC3",)
FUNCTION = "op"
CATEGORY = "math/vec3"
def op(self, op: str, a: Vec3, b: Vec3) -> tuple[Vec3]:
return (
_vec3_from_numpy(VEC_BINARY_OPERATIONS[op](numpy.array(a), numpy.array(b))),
)
class Vec3ToScalarBinaryOperation:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {
"op": (list(VEC_TO_SCALAR_BINARY_OPERATION.keys()),),
"a": DEFAULT_VEC3,
"b": DEFAULT_VEC3,
}
}
RETURN_TYPES = ("FLOAT",)
FUNCTION = "op"
CATEGORY = "math/vec3"
def op(self, op: str, a: Vec3, b: Vec3) -> tuple[float]:
return (VEC_TO_SCALAR_BINARY_OPERATION[op](numpy.array(a), numpy.array(b)),)
class Vec3BinaryCondition:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {
"op": (list(VEC_BINARY_CONDITIONS.keys()),),
"a": DEFAULT_VEC3,
"b": DEFAULT_VEC3,
}
}
RETURN_TYPES = ("BOOL",)
FUNCTION = "op"
CATEGORY = "math/vec3"
def op(self, op: str, a: Vec3, b: Vec3) -> tuple[bool]:
return (VEC_BINARY_CONDITIONS[op](numpy.array(a), numpy.array(b)),)
class Vec3ScalarOperation:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {
"op": (list(VEC_SCALAR_OPERATION.keys()),),
"a": DEFAULT_VEC3,
"b": ("FLOAT",),
}
}
RETURN_TYPES = ("VEC3",)
FUNCTION = "op"
CATEGORY = "math/vec3"
def op(self, op: str, a: Vec3, b: float) -> tuple[Vec3]:
return (_vec3_from_numpy(VEC_SCALAR_OPERATION[op](numpy.array(a), b)),)
class Vec4UnaryOperation:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {
"op": (list(VEC_UNARY_OPERATIONS.keys()),),
"a": DEFAULT_VEC4,
}
}
RETURN_TYPES = ("VEC4",)
FUNCTION = "op"
CATEGORY = "math/vec4"
def op(self, op: str, a: Vec4) -> tuple[Vec4]:
return (_vec4_from_numpy(VEC_UNARY_OPERATIONS[op](numpy.array(a))),)
class Vec4ToScalarUnaryOperation:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {
"op": (list(VEC_TO_SCALAR_UNARY_OPERATION.keys()),),
"a": DEFAULT_VEC4,
}
}
RETURN_TYPES = ("FLOAT",)
FUNCTION = "op"
CATEGORY = "math/vec4"
def op(self, op: str, a: Vec4) -> tuple[float]:
return (VEC_TO_SCALAR_UNARY_OPERATION[op](numpy.array(a)),)
class Vec4UnaryCondition:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {
"op": (list(VEC_UNARY_CONDITIONS.keys()),),
"a": DEFAULT_VEC4,
}
}
RETURN_TYPES = ("BOOL",)
FUNCTION = "op"
CATEGORY = "math/vec4"
def op(self, op: str, a: Vec4) -> tuple[bool]:
return (VEC_UNARY_CONDITIONS[op](numpy.array(a)),)
class Vec4BinaryOperation:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {
"op": (list(VEC_BINARY_OPERATIONS.keys()),),
"a": DEFAULT_VEC4,
"b": DEFAULT_VEC4,
}
}
RETURN_TYPES = ("VEC4",)
FUNCTION = "op"
CATEGORY = "math/vec4"
def op(self, op: str, a: Vec4, b: Vec4) -> tuple[Vec4]:
return (
_vec4_from_numpy(VEC_BINARY_OPERATIONS[op](numpy.array(a), numpy.array(b))),
)
class Vec4ToScalarBinaryOperation:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {
"op": (list(VEC_TO_SCALAR_BINARY_OPERATION.keys()),),
"a": DEFAULT_VEC4,
"b": DEFAULT_VEC4,
}
}
RETURN_TYPES = ("FLOAT",)
FUNCTION = "op"
CATEGORY = "math/vec4"
def op(self, op: str, a: Vec4, b: Vec4) -> tuple[float]:
return (VEC_TO_SCALAR_BINARY_OPERATION[op](numpy.array(a), numpy.array(b)),)
class Vec4BinaryCondition:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {
"op": (list(VEC_BINARY_CONDITIONS.keys()),),
"a": DEFAULT_VEC4,
"b": DEFAULT_VEC4,
}
}
RETURN_TYPES = ("BOOL",)
FUNCTION = "op"
CATEGORY = "math/vec4"
def op(self, op: str, a: Vec4, b: Vec4) -> tuple[bool]:
return (VEC_BINARY_CONDITIONS[op](numpy.array(a), numpy.array(b)),)
class Vec4ScalarOperation:
@classmethod
def INPUT_TYPES(cls) -> Mapping[str, Any]:
return {
"required": {
"op": (list(VEC_SCALAR_OPERATION.keys()),),
"a": DEFAULT_VEC4,
"b": ("FLOAT",),
}
}
RETURN_TYPES = ("VEC4",)
FUNCTION = "op"
CATEGORY = "math/vec4"
def op(self, op: str, a: Vec4, b: float) -> tuple[Vec4]:
return (_vec4_from_numpy(VEC_SCALAR_OPERATION[op](numpy.array(a), b)),)
NODE_CLASS_MAPPINGS = {
"CM_Vec2UnaryOperation": Vec2UnaryOperation,
"CM_Vec2UnaryCondition": Vec2UnaryCondition,
"CM_Vec2ToScalarUnaryOperation": Vec2ToScalarUnaryOperation,
"CM_Vec2BinaryOperation": Vec2BinaryOperation,
"CM_Vec2BinaryCondition": Vec2BinaryCondition,
"CM_Vec2ToScalarBinaryOperation": Vec2ToScalarBinaryOperation,
"CM_Vec2ScalarOperation": Vec2ScalarOperation,
"CM_Vec3UnaryOperation": Vec3UnaryOperation,
"CM_Vec3UnaryCondition": Vec3UnaryCondition,
"CM_Vec3ToScalarUnaryOperation": Vec3ToScalarUnaryOperation,
"CM_Vec3BinaryOperation": Vec3BinaryOperation,
"CM_Vec3BinaryCondition": Vec3BinaryCondition,
"CM_Vec3ToScalarBinaryOperation": Vec3ToScalarBinaryOperation,
"CM_Vec3ScalarOperation": Vec3ScalarOperation,
"CM_Vec4UnaryOperation": Vec4UnaryOperation,
"CM_Vec4UnaryCondition": Vec4UnaryCondition,
"CM_Vec4ToScalarUnaryOperation": Vec4ToScalarUnaryOperation,
"CM_Vec4BinaryOperation": Vec4BinaryOperation,
"CM_Vec4BinaryCondition": Vec4BinaryCondition,
"CM_Vec4ToScalarBinaryOperation": Vec4ToScalarBinaryOperation,
"CM_Vec4ScalarOperation": Vec4ScalarOperation,
}