Initial commit
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
name: Publish to Comfy registry
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "pyproject.toml"
|
||||
|
||||
jobs:
|
||||
publish-node:
|
||||
name: Publish Custom Node to registry
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
- name: Publish Custom Node
|
||||
uses: Comfy-Org/publish-node-action@main
|
||||
with:
|
||||
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }} ## Add your own personal access token to your Github Repository secrets and reference it here.
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
from .math_nodes import MATH_NODE_CLASS_MAPPINGS, MATH_NODE_DISPLAY_NAME_MAPPINGS
|
||||
|
||||
NODE_CLASS_MAPPINGS = {}
|
||||
NODE_CLASS_MAPPINGS.update(MATH_NODE_CLASS_MAPPINGS)
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {}
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(MATH_NODE_DISPLAY_NAME_MAPPINGS)
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
NODE_NAME = "Basic Math ➕" # Can be changed to any desired prefix
|
||||
|
||||
NODE_POSTFIX = "| Basic"
|
||||
|
||||
# Base class for all nodes
|
||||
class BaseNode:
|
||||
CATEGORY = NODE_NAME
|
||||
|
||||
@classmethod
|
||||
def get_category(cls):
|
||||
return cls.CATEGORY
|
||||
|
||||
class PrimitiveNode(BaseNode):
|
||||
CATEGORY = f"{NODE_NAME}/Primitives"
|
||||
|
||||
class ArithmeticNode(BaseNode):
|
||||
CATEGORY = f"{NODE_NAME}/Arithmetic"
|
||||
|
||||
class BooleanNode(BaseNode):
|
||||
CATEGORY = f"{NODE_NAME}/Boolean"
|
||||
|
||||
class ConversionNode(BaseNode):
|
||||
CATEGORY = f"{NODE_NAME}/Conversion"
|
||||
|
||||
class UtilityNode(BaseNode):
|
||||
CATEGORY = f"{NODE_NAME}/Utility"
|
||||
|
||||
class ConstantsNode(BaseNode):
|
||||
CATEGORY = f"{NODE_NAME}/Constants"
|
||||
|
||||
class DebugNode(BaseNode):
|
||||
CATEGORY = f"{NODE_NAME}/Debug"
|
||||
|
||||
|
||||
+611
@@ -0,0 +1,611 @@
|
||||
import math
|
||||
from .tools import VariantSupport, SmartType
|
||||
from .base_node import NODE_POSTFIX, ArithmeticNode, BooleanNode, ConversionNode, UtilityNode, ConstantsNode, PrimitiveNode
|
||||
|
||||
# Create a NUMBER type that accepts both INT and FLOAT
|
||||
NUMBER = SmartType("INT,FLOAT")
|
||||
|
||||
@VariantSupport()
|
||||
class IntegerInput(PrimitiveNode):
|
||||
"""
|
||||
Output an integer value.
|
||||
"""
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"value": ("INT", {"default": 0, "min": -0xffffffffffffffff, "max": 0xffffffffffffffff, "step": 1}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("INT",)
|
||||
RETURN_NAMES = ("value",)
|
||||
FUNCTION = "output"
|
||||
|
||||
def output(self, value):
|
||||
return (value,)
|
||||
|
||||
@VariantSupport()
|
||||
class FloatInput(PrimitiveNode):
|
||||
"""
|
||||
Output a float value.
|
||||
"""
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"value": ("FLOAT", {"default": 0.0, "min": -999999999999.0, "max": 999999999999.0, "step": 0.001}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("FLOAT",)
|
||||
RETURN_NAMES = ("value",)
|
||||
FUNCTION = "output"
|
||||
|
||||
def output(self, value):
|
||||
return (value,)
|
||||
|
||||
@VariantSupport()
|
||||
class PreciseFloatInput(PrimitiveNode):
|
||||
"""
|
||||
Output a precise float value.
|
||||
"""
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"value": ("FLOAT", {"default": 0.0, "min": -999999999999.0, "max": 999999999999.0, "step": 0.0000000001}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("FLOAT",)
|
||||
RETURN_NAMES = ("value",)
|
||||
FUNCTION = "output"
|
||||
|
||||
def output(self, value):
|
||||
return (value,)
|
||||
|
||||
@VariantSupport()
|
||||
class BooleanInput(PrimitiveNode):
|
||||
"""
|
||||
Output a boolean value.
|
||||
"""
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"value": ("BOOLEAN", {"default": False}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("BOOLEAN",)
|
||||
RETURN_NAMES = ("value",)
|
||||
FUNCTION = "output"
|
||||
|
||||
def output(self, value):
|
||||
return (value,)
|
||||
|
||||
@VariantSupport()
|
||||
class IntToType(ConversionNode):
|
||||
"""
|
||||
Convert an integer to various types.
|
||||
"""
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"value": ("INT", {"default": 0, "min": -0xffffffffffffffff, "max": 0xffffffffffffffff, "step": 1}),
|
||||
"output_type": (["INT", "FLOAT", "STRING", "BOOLEAN"],),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("*",)
|
||||
RETURN_NAMES = ("value",)
|
||||
FUNCTION = "convert"
|
||||
|
||||
def convert(self, value, output_type):
|
||||
if output_type == "INT":
|
||||
return (value,)
|
||||
elif output_type == "FLOAT":
|
||||
return (float(value),)
|
||||
elif output_type == "STRING":
|
||||
return (str(value),)
|
||||
elif output_type == "BOOLEAN":
|
||||
return (bool(value),)
|
||||
|
||||
@VariantSupport()
|
||||
class FloatToType(ConversionNode):
|
||||
"""
|
||||
Convert a float to various types.
|
||||
"""
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"value": ("FLOAT", {"default": 0.0, "min": -999999999999.0, "max": 999999999999.0, "step": 0.001}),
|
||||
"output_type": (["INT", "FLOAT", "STRING", "BOOLEAN"],),
|
||||
},
|
||||
"optional": {
|
||||
"round_method": (["round", "floor", "ceil", "trunc"], {"default": "round"}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("*",)
|
||||
RETURN_NAMES = ("value",)
|
||||
FUNCTION = "convert"
|
||||
|
||||
def convert(self, value, output_type, round_method="round"):
|
||||
if output_type == "INT":
|
||||
if round_method == "round":
|
||||
return (round(value),)
|
||||
elif round_method == "floor":
|
||||
return (math.floor(value),)
|
||||
elif round_method == "ceil":
|
||||
return (math.ceil(value),)
|
||||
elif round_method == "trunc":
|
||||
return (math.trunc(value),)
|
||||
elif output_type == "FLOAT":
|
||||
return (value,)
|
||||
elif output_type == "STRING":
|
||||
return (str(value),)
|
||||
elif output_type == "BOOLEAN":
|
||||
return (bool(value),)
|
||||
|
||||
@VariantSupport()
|
||||
class BasicMath(ArithmeticNode):
|
||||
"""
|
||||
Basic mathematical operations between two numbers.
|
||||
"""
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"a": (NUMBER, {"default": 0.0}),
|
||||
"b": (NUMBER, {"default": 0.0}),
|
||||
"operation": (["+", "-", "*", "/", "//", "%", "**", "min", "max"],),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("*",)
|
||||
RETURN_NAMES = ("result",)
|
||||
FUNCTION = "calculate"
|
||||
|
||||
def calculate(self, a, b, operation):
|
||||
# Determine if we should return int or float based on input types and operation
|
||||
is_int_operation = isinstance(a, int) and isinstance(b, int) and operation != "/"
|
||||
|
||||
try:
|
||||
if operation == "+":
|
||||
result = a + b
|
||||
elif operation == "-":
|
||||
result = a - b
|
||||
elif operation == "*":
|
||||
result = a * b
|
||||
elif operation == "/":
|
||||
if b == 0:
|
||||
return (float('inf') if a > 0 else float('-inf'),)
|
||||
result = a / b
|
||||
is_int_operation = False # Division always returns float
|
||||
elif operation == "//":
|
||||
if b == 0:
|
||||
return (float('inf') if a > 0 else float('-inf'),)
|
||||
result = a // b
|
||||
elif operation == "%":
|
||||
if b == 0:
|
||||
return (float('nan'),)
|
||||
result = a % b
|
||||
elif operation == "**":
|
||||
result = a ** b
|
||||
# Power can return float even with int inputs
|
||||
if not isinstance(result, int):
|
||||
is_int_operation = False
|
||||
elif operation == "min":
|
||||
result = min(a, b)
|
||||
elif operation == "max":
|
||||
result = max(a, b)
|
||||
|
||||
# Return as int if both inputs were int and operation preserves int type
|
||||
if is_int_operation and isinstance(result, (int, float)) and result == int(result):
|
||||
return (int(result),)
|
||||
else:
|
||||
return (float(result),)
|
||||
except:
|
||||
return (float('nan'),)
|
||||
|
||||
@VariantSupport()
|
||||
class IntMath(ArithmeticNode):
|
||||
"""
|
||||
Basic mathematical operations between two integers.
|
||||
"""
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"a": ("INT", {"default": 0, "min": -0xffffffffffffffff, "max": 0xffffffffffffffff, "step": 1}),
|
||||
"b": ("INT", {"default": 0, "min": -0xffffffffffffffff, "max": 0xffffffffffffffff, "step": 1}),
|
||||
"operation": (["+", "-", "*", "//", "%", "**", "min", "max", "&", "|", "^", "<<", ">>"],),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("INT",)
|
||||
RETURN_NAMES = ("result",)
|
||||
FUNCTION = "calculate"
|
||||
|
||||
def calculate(self, a, b, operation):
|
||||
try:
|
||||
if operation == "+":
|
||||
return (a + b,)
|
||||
elif operation == "-":
|
||||
return (a - b,)
|
||||
elif operation == "*":
|
||||
return (a * b,)
|
||||
elif operation == "//":
|
||||
if b == 0:
|
||||
return (0,)
|
||||
return (a // b,)
|
||||
elif operation == "%":
|
||||
if b == 0:
|
||||
return (0,)
|
||||
return (a % b,)
|
||||
elif operation == "**":
|
||||
return (a ** b,)
|
||||
elif operation == "min":
|
||||
return (min(a, b),)
|
||||
elif operation == "max":
|
||||
return (max(a, b),)
|
||||
elif operation == "&":
|
||||
return (a & b,)
|
||||
elif operation == "|":
|
||||
return (a | b,)
|
||||
elif operation == "^":
|
||||
return (a ^ b,)
|
||||
elif operation == "<<":
|
||||
return (a << b,)
|
||||
elif operation == ">>":
|
||||
return (a >> b,)
|
||||
except:
|
||||
return (0,)
|
||||
|
||||
@VariantSupport()
|
||||
class UnaryMath(ArithmeticNode):
|
||||
"""
|
||||
Unary mathematical operations on a single number.
|
||||
"""
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"value": (NUMBER, {"default": 0.0}),
|
||||
"operation": (["abs", "neg", "sqrt", "sin", "cos", "tan", "log", "log10", "exp", "floor", "ceil", "round"],),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("*",)
|
||||
RETURN_NAMES = ("result",)
|
||||
FUNCTION = "calculate"
|
||||
|
||||
def calculate(self, value, operation):
|
||||
# Determine return type based on operation and input
|
||||
preserve_int = isinstance(value, int) and operation in ["abs", "neg"]
|
||||
|
||||
try:
|
||||
if operation == "abs":
|
||||
result = abs(value)
|
||||
elif operation == "neg":
|
||||
result = -value
|
||||
elif operation == "sqrt":
|
||||
result = math.sqrt(abs(value))
|
||||
preserve_int = False
|
||||
elif operation == "sin":
|
||||
result = math.sin(value)
|
||||
preserve_int = False
|
||||
elif operation == "cos":
|
||||
result = math.cos(value)
|
||||
preserve_int = False
|
||||
elif operation == "tan":
|
||||
result = math.tan(value)
|
||||
preserve_int = False
|
||||
elif operation == "log":
|
||||
result = math.log(abs(value)) if value != 0 else float('-inf')
|
||||
preserve_int = False
|
||||
elif operation == "log10":
|
||||
result = math.log10(abs(value)) if value != 0 else float('-inf')
|
||||
preserve_int = False
|
||||
elif operation == "exp":
|
||||
result = math.exp(value)
|
||||
preserve_int = False
|
||||
elif operation == "floor":
|
||||
result = math.floor(value)
|
||||
preserve_int = isinstance(value, int)
|
||||
elif operation == "ceil":
|
||||
result = math.ceil(value)
|
||||
preserve_int = isinstance(value, int)
|
||||
elif operation == "round":
|
||||
result = round(value)
|
||||
preserve_int = isinstance(value, int)
|
||||
|
||||
if preserve_int:
|
||||
return (int(result),)
|
||||
else:
|
||||
return (float(result),)
|
||||
except:
|
||||
return (float('nan'),)
|
||||
|
||||
@VariantSupport()
|
||||
class MathConstants(ConstantsNode):
|
||||
"""
|
||||
Common mathematical constants.
|
||||
"""
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"constant": (["pi", "e", "tau", "inf", "nan"],),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("FLOAT",)
|
||||
RETURN_NAMES = ("value",)
|
||||
FUNCTION = "get_constant"
|
||||
|
||||
def get_constant(self, constant):
|
||||
if constant == "pi":
|
||||
return (math.pi,)
|
||||
elif constant == "e":
|
||||
return (math.e,)
|
||||
elif constant == "tau":
|
||||
return (math.tau,)
|
||||
elif constant == "inf":
|
||||
return (float('inf'),)
|
||||
elif constant == "nan":
|
||||
return (float('nan'),)
|
||||
|
||||
@VariantSupport()
|
||||
class NumberRound(UtilityNode):
|
||||
"""
|
||||
Round a number to specified decimal places.
|
||||
"""
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"value": (NUMBER, {"default": 0.0}),
|
||||
"decimals": ("INT", {"default": 2, "min": 0, "max": 10, "step": 1}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("*",)
|
||||
RETURN_NAMES = ("rounded",)
|
||||
FUNCTION = "round_number"
|
||||
|
||||
def round_number(self, value, decimals):
|
||||
result = round(value, decimals)
|
||||
# If input was int and decimals is 0, return int
|
||||
if isinstance(value, int) and decimals == 0:
|
||||
return (int(result),)
|
||||
else:
|
||||
return (float(result),)
|
||||
|
||||
@VariantSupport()
|
||||
class NumberClamp(UtilityNode):
|
||||
"""
|
||||
Clamp a number between minimum and maximum values.
|
||||
"""
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"value": (NUMBER, {"default": 0.0}),
|
||||
"min_value": (NUMBER, {"default": 0.0}),
|
||||
"max_value": (NUMBER, {"default": 1.0}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("*",)
|
||||
RETURN_NAMES = ("clamped",)
|
||||
FUNCTION = "clamp"
|
||||
|
||||
def clamp(self, value, min_value, max_value):
|
||||
result = max(min_value, min(max_value, value))
|
||||
# If all inputs are int, return int
|
||||
if all(isinstance(x, int) for x in [value, min_value, max_value]):
|
||||
return (int(result),)
|
||||
else:
|
||||
return (float(result),)
|
||||
|
||||
@VariantSupport()
|
||||
class NumberLerp(UtilityNode):
|
||||
"""
|
||||
Linear interpolation between two values.
|
||||
"""
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"a": (NUMBER, {"default": 0.0}),
|
||||
"b": (NUMBER, {"default": 1.0}),
|
||||
"t": (NUMBER, {"default": 0.5}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("*",)
|
||||
RETURN_NAMES = ("result",)
|
||||
FUNCTION = "lerp"
|
||||
|
||||
def lerp(self, a, b, t):
|
||||
result = a + t * (b - a)
|
||||
# Lerp usually returns float due to multiplication
|
||||
# Only return int if result is whole number and inputs were int
|
||||
if all(isinstance(x, int) for x in [a, b, t]) and result == int(result):
|
||||
return (int(result),)
|
||||
else:
|
||||
return (float(result),)
|
||||
|
||||
@VariantSupport()
|
||||
class NumberRange(UtilityNode):
|
||||
"""
|
||||
Check if a number is within a range.
|
||||
"""
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"value": (NUMBER, {"default": 0.0}),
|
||||
"min_value": (NUMBER, {"default": 0.0}),
|
||||
"max_value": (NUMBER, {"default": 1.0}),
|
||||
"inclusive": ("BOOLEAN", {"default": True}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("BOOLEAN",)
|
||||
RETURN_NAMES = ("in_range",)
|
||||
FUNCTION = "check_range"
|
||||
|
||||
def check_range(self, value, min_value, max_value, inclusive):
|
||||
if inclusive:
|
||||
return (min_value <= value <= max_value,)
|
||||
else:
|
||||
return (min_value < value < max_value,)
|
||||
|
||||
@VariantSupport()
|
||||
class BooleanLogic(BooleanNode):
|
||||
"""
|
||||
Boolean logic operations.
|
||||
"""
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"a": ("BOOLEAN",),
|
||||
"b": ("BOOLEAN",),
|
||||
"operation": (["AND", "OR", "XOR", "NAND", "NOR", "XNOR"],),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("BOOLEAN",)
|
||||
RETURN_NAMES = ("result",)
|
||||
FUNCTION = "logic_operation"
|
||||
|
||||
def logic_operation(self, a, b, operation):
|
||||
if operation == "AND":
|
||||
return (a and b,)
|
||||
elif operation == "OR":
|
||||
return (a or b,)
|
||||
elif operation == "XOR":
|
||||
return (a ^ b,)
|
||||
elif operation == "NAND":
|
||||
return (not (a and b),)
|
||||
elif operation == "NOR":
|
||||
return (not (a or b),)
|
||||
elif operation == "XNOR":
|
||||
return (not (a ^ b),)
|
||||
|
||||
@VariantSupport()
|
||||
class BooleanUnary(BooleanNode):
|
||||
"""
|
||||
Unary boolean operations.
|
||||
"""
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"value": ("BOOLEAN",),
|
||||
"operation": (["NOT", "IDENTITY"],),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("BOOLEAN",)
|
||||
RETURN_NAMES = ("result",)
|
||||
FUNCTION = "unary_operation"
|
||||
|
||||
def unary_operation(self, value, operation):
|
||||
if operation == "NOT":
|
||||
return (not value,)
|
||||
elif operation == "IDENTITY":
|
||||
return (value,)
|
||||
|
||||
MATH_NODE_CLASS_MAPPINGS = {
|
||||
"IntegerInput": IntegerInput,
|
||||
"FloatInput": FloatInput,
|
||||
"PreciseFloatInput": PreciseFloatInput,
|
||||
"BooleanInput": BooleanInput,
|
||||
"IntToType": IntToType,
|
||||
"FloatToType": FloatToType,
|
||||
"BasicMath": BasicMath,
|
||||
"IntMath": IntMath,
|
||||
"UnaryMath": UnaryMath,
|
||||
"MathConstants": MathConstants,
|
||||
"NumberRound": NumberRound,
|
||||
"NumberClamp": NumberClamp,
|
||||
"NumberLerp": NumberLerp,
|
||||
"NumberRange": NumberRange,
|
||||
"BooleanLogic": BooleanLogic,
|
||||
"BooleanUnary": BooleanUnary,
|
||||
}
|
||||
|
||||
MATH_NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"IntegerInput": f"Integer {NODE_POSTFIX}",
|
||||
"FloatInput": f"Float {NODE_POSTFIX}",
|
||||
"PreciseFloatInput": f"Precise Float {NODE_POSTFIX}",
|
||||
"BooleanInput": f"Boolean {NODE_POSTFIX}",
|
||||
"IntToType": f"Int to Type {NODE_POSTFIX}",
|
||||
"FloatToType": f"Float to Type {NODE_POSTFIX}",
|
||||
"BasicMath": f"Basic Math {NODE_POSTFIX}",
|
||||
"IntMath": f"Int Math {NODE_POSTFIX}",
|
||||
"UnaryMath": f"Unary Math {NODE_POSTFIX}",
|
||||
"MathConstants": f"Math Constants {NODE_POSTFIX}",
|
||||
"NumberRound": f"Number Round {NODE_POSTFIX}",
|
||||
"NumberClamp": f"Number Clamp {NODE_POSTFIX}",
|
||||
"NumberLerp": f"Number Lerp {NODE_POSTFIX}",
|
||||
"NumberRange": f"Number Range {NODE_POSTFIX}",
|
||||
"BooleanLogic": f"Boolean Logic {NODE_POSTFIX}",
|
||||
"BooleanUnary": f"Boolean Unary {NODE_POSTFIX}",
|
||||
}
|
||||
Executable
+13
@@ -0,0 +1,13 @@
|
||||
[project]
|
||||
name = "comfyui-basic-math"
|
||||
description = "Custom nodes for performing basic math operations"
|
||||
version = "0.0.1"
|
||||
license = { file = "LICENSE.txt" }
|
||||
|
||||
[project.urls]
|
||||
Repository = "https://github.com/akatz-ai/ComfyUI-Basic-Math"
|
||||
|
||||
[tool.comfy]
|
||||
PublisherId = "akatz"
|
||||
DisplayName = "ComfyUI-Basic-Math"
|
||||
Icon = "https://i.imgur.com/aiqQI7U.png"
|
||||
@@ -0,0 +1,65 @@
|
||||
|
||||
def MakeSmartType(t):
|
||||
if isinstance(t, str):
|
||||
return SmartType(t)
|
||||
return t
|
||||
|
||||
class SmartType(str):
|
||||
def __ne__(self, other):
|
||||
if self == "*" or other == "*":
|
||||
return False
|
||||
selfset = set(self.split(','))
|
||||
otherset = set(other.split(','))
|
||||
return not selfset.issubset(otherset)
|
||||
|
||||
def VariantSupport():
|
||||
def decorator(cls):
|
||||
if hasattr(cls, "INPUT_TYPES"):
|
||||
old_input_types = getattr(cls, "INPUT_TYPES")
|
||||
def new_input_types(*args, **kwargs):
|
||||
types = old_input_types(*args, **kwargs)
|
||||
for category in ["required", "optional"]:
|
||||
if category not in types:
|
||||
continue
|
||||
for key, value in types[category].items():
|
||||
if isinstance(value, tuple):
|
||||
types[category][key] = (MakeSmartType(value[0]),) + value[1:]
|
||||
return types
|
||||
setattr(cls, "INPUT_TYPES", new_input_types)
|
||||
if hasattr(cls, "RETURN_TYPES"):
|
||||
old_return_types = cls.RETURN_TYPES
|
||||
setattr(cls, "RETURN_TYPES", tuple(MakeSmartType(x) for x in old_return_types))
|
||||
if hasattr(cls, "VALIDATE_INPUTS"):
|
||||
# Reflection is used to determine what the function signature is, so we can't just change the function signature
|
||||
raise NotImplementedError("VariantSupport does not support VALIDATE_INPUTS yet")
|
||||
else:
|
||||
def validate_individual(input_types, inputs):
|
||||
for key, value in input_types.items():
|
||||
if isinstance(value, SmartType):
|
||||
continue
|
||||
if "required" in inputs and key in inputs["required"]:
|
||||
expected_type = inputs["required"][key][0]
|
||||
elif "optional" in inputs and key in inputs["optional"]:
|
||||
expected_type = inputs["optional"][key][0]
|
||||
else:
|
||||
expected_type = None
|
||||
if expected_type is not None and MakeSmartType(value) != expected_type:
|
||||
return f"Invalid type of {key}: {value} (expected {expected_type})"
|
||||
return True
|
||||
def validate_inputs(input_types):
|
||||
inputs = cls.INPUT_TYPES()
|
||||
|
||||
if not isinstance(input_types, list):
|
||||
return validate_individual(input_types, inputs)
|
||||
|
||||
for input_type in input_types:
|
||||
response = validate_individual(input_type, inputs)
|
||||
if isinstance(response, str):
|
||||
return response
|
||||
|
||||
return True
|
||||
|
||||
setattr(cls, "VALIDATE_INPUTS", validate_inputs)
|
||||
return cls
|
||||
return decorator
|
||||
|
||||
Reference in New Issue
Block a user