Merge pull request #11 from StableLlama/dynamic_nodes3
Dynamic nodes part 3
This commit is contained in:
+3
-2
@@ -1,4 +1,5 @@
|
||||
"""Top-level package for basic_data_handling."""
|
||||
import os, sys; sys.path.append(os.path.dirname(os.path.realpath(__file__)))
|
||||
|
||||
__all__ = [
|
||||
"NODE_CLASS_MAPPINGS",
|
||||
@@ -6,7 +7,7 @@ __all__ = [
|
||||
"WEB_DIRECTORY",
|
||||
]
|
||||
|
||||
from .src.basic_data_handling import NODE_CLASS_MAPPINGS
|
||||
from .src.basic_data_handling import NODE_DISPLAY_NAME_MAPPINGS
|
||||
from src.basic_data_handling import NODE_CLASS_MAPPINGS
|
||||
from src.basic_data_handling import NODE_DISPLAY_NAME_MAPPINGS
|
||||
|
||||
WEB_DIRECTORY = "./web"
|
||||
|
||||
+4
-3
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "basic_data_handling"
|
||||
version = "0.3.2"
|
||||
version = "0.3.3"
|
||||
description = """NOTE: Still in development! Expect breaking changes!
|
||||
Basic Python functions for manipulating data that every programmer is used to.
|
||||
Currently supported ComfyUI data types: BOOLEAN, FLOAT, INT, STRING and data lists.
|
||||
@@ -46,14 +46,15 @@ Icon = ""
|
||||
minversion = "8.0"
|
||||
pythonpath = [
|
||||
"src",
|
||||
"../..", # Path to parent directory containing comfy module
|
||||
#"../..", # Path to parent directory containing comfy module
|
||||
"."
|
||||
]
|
||||
testpaths = [
|
||||
"tests",
|
||||
]
|
||||
#python_files = ["test_*.py"]
|
||||
python_files = ["conftest.py", "test_boolean_nodes.py"]
|
||||
#python_files = ["conftest.py", "test_boolean_nodes.py"]
|
||||
python_files = ["test_boolean_nodes.py"]
|
||||
|
||||
[tool.mypy]
|
||||
files = "."
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
class ContainsDynamicDict(dict):
|
||||
"""
|
||||
A custom dictionary that dynamically returns values for keys based on a pattern.
|
||||
- If a key in the passed dictionary has a value with `{"_dynamic": "number"}` in the tuple's second position,
|
||||
then any other key starting with the same string and ending with a number will return that value.
|
||||
- For other keys, normal dictionary lookup behavior applies.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# Store prefixes associated with `_dynamic` values for efficient lookup
|
||||
self._dynamic_prefixes = {
|
||||
key.rstrip("0123456789"): value
|
||||
for key, value in self.items()
|
||||
if isinstance(value, tuple) and len(value) > 1 and value[1].get("_dynamic") == "number"
|
||||
}
|
||||
|
||||
def __contains__(self, key):
|
||||
# Check if key matches a dynamically handled prefix or exists normally
|
||||
return (
|
||||
any(key.startswith(prefix) and key[len(prefix):].isdigit() for prefix in self._dynamic_prefixes)
|
||||
or super().__contains__(key)
|
||||
)
|
||||
|
||||
def __getitem__(self, key):
|
||||
# Dynamically return the value for keys matching a `prefix<number>` pattern
|
||||
print(f'_ dynamic prefixes: {self._dynamic_prefixes}; get key: {key}')
|
||||
for prefix, value in self._dynamic_prefixes.items():
|
||||
if key.startswith(prefix) and key[len(prefix):].isdigit():
|
||||
return value
|
||||
# Fallback to normal dictionary behavior for other keys
|
||||
return super().__getitem__(key)
|
||||
@@ -1,5 +1,16 @@
|
||||
from inspect import cleandoc
|
||||
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
|
||||
|
||||
try:
|
||||
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
|
||||
except:
|
||||
class IO:
|
||||
BOOLEAN = "BOOLEAN"
|
||||
INT = "INT"
|
||||
FLOAT = "FLOAT"
|
||||
STRING = "STRING"
|
||||
NUMBER = "FLOAT,INT"
|
||||
ANY = "*"
|
||||
ComfyNodeABC = object
|
||||
|
||||
class BooleanAnd(ComfyNodeABC):
|
||||
"""
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
from typing import Any
|
||||
from inspect import cleandoc
|
||||
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
|
||||
|
||||
try:
|
||||
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
|
||||
except:
|
||||
class IO:
|
||||
BOOLEAN = "BOOLEAN"
|
||||
INT = "INT"
|
||||
FLOAT = "FLOAT"
|
||||
STRING = "STRING"
|
||||
NUMBER = "FLOAT,INT"
|
||||
ANY = "*"
|
||||
ComfyNodeABC = object
|
||||
|
||||
class CastToBoolean(ComfyNodeABC):
|
||||
"""
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
from typing import Any
|
||||
from inspect import cleandoc
|
||||
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
|
||||
|
||||
try:
|
||||
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
|
||||
except:
|
||||
class IO:
|
||||
BOOLEAN = "BOOLEAN"
|
||||
INT = "INT"
|
||||
FLOAT = "FLOAT"
|
||||
STRING = "STRING"
|
||||
NUMBER = "FLOAT,INT"
|
||||
ANY = "*"
|
||||
ComfyNodeABC = object
|
||||
|
||||
class Equal(ComfyNodeABC):
|
||||
"""
|
||||
|
||||
@@ -1,7 +1,21 @@
|
||||
from typing import Any
|
||||
from inspect import cleandoc
|
||||
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
|
||||
from comfy_execution.graph import ExecutionBlocker
|
||||
|
||||
try:
|
||||
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
|
||||
from comfy_execution.graph import ExecutionBlocker
|
||||
except:
|
||||
class IO:
|
||||
BOOLEAN = "BOOLEAN"
|
||||
INT = "INT"
|
||||
FLOAT = "FLOAT"
|
||||
STRING = "STRING"
|
||||
NUMBER = "FLOAT,INT"
|
||||
ANY = "*"
|
||||
ComfyNodeABC = object
|
||||
ExecutionBlocker = lambda x: x
|
||||
|
||||
from ._dynamic_input import ContainsDynamicDict
|
||||
|
||||
|
||||
class IfElse(ComfyNodeABC):
|
||||
@@ -58,11 +72,11 @@ class IfElifElse(ComfyNodeABC):
|
||||
"if": (IO.BOOLEAN, {"forceInput": True}),
|
||||
"then": (IO.ANY, {"lazy": True}),
|
||||
},
|
||||
"optional": {
|
||||
"optional": ContainsDynamicDict({
|
||||
"elif_0": (IO.BOOLEAN, {"forceInput": True, "lazy": True, "_dynamic": "number", "_dynamicGroup": 0}),
|
||||
"then_0": (IO.ANY, {"lazy": True, "_dynamic": "number", "_dynamicGroup": 0}),
|
||||
"else": (IO.ANY, {"lazy": True}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.ANY,)
|
||||
@@ -140,10 +154,10 @@ class SwitchCase(ComfyNodeABC):
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"required": ContainsDynamicDict({
|
||||
"selector": (IO.INT, {"default": 0, "min": 0}),
|
||||
"case_0": (IO.ANY, {"lazy": True, "_dynamic": "number"}),
|
||||
},
|
||||
}),
|
||||
"optional": {
|
||||
"default": (IO.ANY, {"lazy": True}),
|
||||
}
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
from typing import Any
|
||||
from inspect import cleandoc
|
||||
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
|
||||
|
||||
try:
|
||||
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
|
||||
except:
|
||||
class IO:
|
||||
BOOLEAN = "BOOLEAN"
|
||||
INT = "INT"
|
||||
FLOAT = "FLOAT"
|
||||
STRING = "STRING"
|
||||
NUMBER = "FLOAT,INT"
|
||||
ANY = "*"
|
||||
ComfyNodeABC = object
|
||||
|
||||
from ._dynamic_input import ContainsDynamicDict
|
||||
|
||||
INT_MAX = 2**15-1 # the computer can do more but be nice to the eyes
|
||||
|
||||
@@ -15,9 +28,9 @@ class DataListCreate(ComfyNodeABC):
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"optional": {
|
||||
"optional": ContainsDynamicDict({
|
||||
"item_0": (IO.ANY, {"_dynamic": "number"}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.ANY,)
|
||||
@@ -42,9 +55,9 @@ class DataListCreateFromBoolean(ComfyNodeABC):
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"optional": {
|
||||
"optional": ContainsDynamicDict({
|
||||
"item_0": (IO.BOOLEAN, {"_dynamic": "number"}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.BOOLEAN,)
|
||||
@@ -69,9 +82,9 @@ class DataListCreateFromFloat(ComfyNodeABC):
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"optional": {
|
||||
"optional": ContainsDynamicDict({
|
||||
"item_0": (IO.FLOAT, {"_dynamic": "number"}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.FLOAT,)
|
||||
@@ -96,9 +109,9 @@ class DataListCreateFromInt(ComfyNodeABC):
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"optional": {
|
||||
"optional": ContainsDynamicDict({
|
||||
"item_0": (IO.INT, {"_dynamic": "number"}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.INT,)
|
||||
@@ -123,9 +136,9 @@ class DataListCreateFromString(ComfyNodeABC):
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"optional": {
|
||||
"optional": ContainsDynamicDict({
|
||||
"item_0": (IO.STRING, {"_dynamic": "number"}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.STRING,)
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
from typing import Any
|
||||
from inspect import cleandoc
|
||||
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
|
||||
|
||||
try:
|
||||
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
|
||||
except:
|
||||
class IO:
|
||||
BOOLEAN = "BOOLEAN"
|
||||
INT = "INT"
|
||||
FLOAT = "FLOAT"
|
||||
STRING = "STRING"
|
||||
NUMBER = "FLOAT,INT"
|
||||
ANY = "*"
|
||||
ComfyNodeABC = object
|
||||
|
||||
from ._dynamic_input import ContainsDynamicDict
|
||||
|
||||
|
||||
class DictCreate(ComfyNodeABC):
|
||||
@@ -13,10 +26,10 @@ class DictCreate(ComfyNodeABC):
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"optional": {
|
||||
"optional": ContainsDynamicDict({
|
||||
"key_0": (IO.STRING, {"_dynamic": "number", "_dynamicGroup": 0}),
|
||||
"value_0": (IO.ANY, {"_dynamic": "number", "_dynamicGroup": 0}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("DICT",)
|
||||
@@ -45,10 +58,10 @@ class DictCreateFromBoolean(ComfyNodeABC):
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"optional": {
|
||||
"optional": ContainsDynamicDict({
|
||||
"key_0": (IO.STRING, {"_dynamic": "number", "_dynamicGroup": 0}),
|
||||
"value_0": (IO.BOOLEAN, {"_dynamic": "number", "_dynamicGroup": 0}),
|
||||
}
|
||||
"value_0": (IO.BOOLEAN, {"_dynamic": "number", "_dynamicGroup": 0}),
|
||||
})
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("DICT",)
|
||||
@@ -77,10 +90,10 @@ class DictCreateFromFloat(ComfyNodeABC):
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"optional": {
|
||||
"optional": ContainsDynamicDict({
|
||||
"key_0": (IO.STRING, {"_dynamic": "number", "_dynamicGroup": 0}),
|
||||
"value_0": (IO.FLOAT, {"_dynamic": "number", "_dynamicGroup": 0}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("DICT",)
|
||||
@@ -109,10 +122,10 @@ class DictCreateFromInt(ComfyNodeABC):
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"optional": {
|
||||
"optional": ContainsDynamicDict({
|
||||
"key_0": (IO.STRING, {"_dynamic": "number", "_dynamicGroup": 0}),
|
||||
"value_0": (IO.INT, {"_dynamic": "number", "_dynamicGroup": 0}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("DICT",)
|
||||
@@ -141,10 +154,10 @@ class DictCreateFromString(ComfyNodeABC):
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"optional": {
|
||||
"optional": ContainsDynamicDict({
|
||||
"key_0": (IO.STRING, {"_dynamic": "number", "_dynamicGroup": 0}),
|
||||
"value_0": (IO.STRING, {"_dynamic": "number", "_dynamicGroup": 0}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("DICT",)
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
from inspect import cleandoc
|
||||
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
|
||||
|
||||
try:
|
||||
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
|
||||
except:
|
||||
class IO:
|
||||
BOOLEAN = "BOOLEAN"
|
||||
INT = "INT"
|
||||
FLOAT = "FLOAT"
|
||||
STRING = "STRING"
|
||||
NUMBER = "FLOAT,INT"
|
||||
ANY = "*"
|
||||
ComfyNodeABC = object
|
||||
|
||||
|
||||
class FloatCreate(ComfyNodeABC):
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
from inspect import cleandoc
|
||||
from typing import Literal
|
||||
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
|
||||
|
||||
try:
|
||||
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
|
||||
except:
|
||||
class IO:
|
||||
BOOLEAN = "BOOLEAN"
|
||||
INT = "INT"
|
||||
FLOAT = "FLOAT"
|
||||
STRING = "STRING"
|
||||
NUMBER = "FLOAT,INT"
|
||||
ANY = "*"
|
||||
ComfyNodeABC = object
|
||||
|
||||
|
||||
class IntCreate(ComfyNodeABC):
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
from typing import Any
|
||||
from inspect import cleandoc
|
||||
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
|
||||
|
||||
try:
|
||||
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
|
||||
except:
|
||||
class IO:
|
||||
BOOLEAN = "BOOLEAN"
|
||||
INT = "INT"
|
||||
FLOAT = "FLOAT"
|
||||
STRING = "STRING"
|
||||
NUMBER = "FLOAT,INT"
|
||||
ANY = "*"
|
||||
ComfyNodeABC = object
|
||||
|
||||
from ._dynamic_input import ContainsDynamicDict
|
||||
|
||||
INT_MAX = 2**15-1 # the computer can do more but be nice to the eyes
|
||||
|
||||
@@ -15,9 +28,9 @@ class ListCreate(ComfyNodeABC):
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"optional": {
|
||||
"optional": ContainsDynamicDict({
|
||||
"item_0": (IO.ANY, {"_dynamic": "number"}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LIST",)
|
||||
@@ -40,9 +53,9 @@ class ListCreateFromBoolean(ComfyNodeABC):
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"optional": {
|
||||
"optional": ContainsDynamicDict({
|
||||
"item_0": (IO.BOOLEAN, {"_dynamic": "number"}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LIST",)
|
||||
@@ -65,9 +78,9 @@ class ListCreateFromFloat(ComfyNodeABC):
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"optional": {
|
||||
"optional": ContainsDynamicDict({
|
||||
"item_0": (IO.FLOAT, {"_dynamic": "number"}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LIST",)
|
||||
@@ -90,9 +103,9 @@ class ListCreateFromInt(ComfyNodeABC):
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"optional": {
|
||||
"optional": ContainsDynamicDict({
|
||||
"item_0": (IO.INT, {"_dynamic": "number"}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LIST",)
|
||||
@@ -115,9 +128,9 @@ class ListCreateFromString(ComfyNodeABC):
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"optional": {
|
||||
"optional": ContainsDynamicDict({
|
||||
"item_0": (IO.STRING, {"_dynamic": "number"}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LIST",)
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
from inspect import cleandoc
|
||||
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
|
||||
|
||||
try:
|
||||
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
|
||||
except:
|
||||
class IO:
|
||||
BOOLEAN = "BOOLEAN"
|
||||
INT = "INT"
|
||||
FLOAT = "FLOAT"
|
||||
STRING = "STRING"
|
||||
NUMBER = "FLOAT,INT"
|
||||
ANY = "*"
|
||||
ComfyNodeABC = object
|
||||
|
||||
from ._dynamic_input import ContainsDynamicDict
|
||||
|
||||
class MathFormula(ComfyNodeABC):
|
||||
"""
|
||||
@@ -19,10 +32,10 @@ class MathFormula(ComfyNodeABC):
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"required": ContainsDynamicDict({
|
||||
"formula": (IO.STRING, {"default": "a + b"}),
|
||||
"a": (IO.NUMBER, {"default": 0.0, "_dynamic": "letter"}),
|
||||
},
|
||||
}),
|
||||
}
|
||||
|
||||
RETURN_TYPES = (IO.FLOAT,)
|
||||
|
||||
@@ -1,7 +1,18 @@
|
||||
import math
|
||||
from inspect import cleandoc
|
||||
from typing import Literal, Union
|
||||
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
|
||||
|
||||
try:
|
||||
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
|
||||
except:
|
||||
class IO:
|
||||
BOOLEAN = "BOOLEAN"
|
||||
INT = "INT"
|
||||
FLOAT = "FLOAT"
|
||||
STRING = "STRING"
|
||||
NUMBER = "FLOAT,INT"
|
||||
ANY = "*"
|
||||
ComfyNodeABC = object
|
||||
|
||||
class MathAbs(ComfyNodeABC):
|
||||
"""
|
||||
|
||||
@@ -1,7 +1,18 @@
|
||||
from inspect import cleandoc
|
||||
import os
|
||||
import glob
|
||||
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
|
||||
|
||||
try:
|
||||
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
|
||||
except:
|
||||
class IO:
|
||||
BOOLEAN = "BOOLEAN"
|
||||
INT = "INT"
|
||||
FLOAT = "FLOAT"
|
||||
STRING = "STRING"
|
||||
NUMBER = "FLOAT,INT"
|
||||
ANY = "*"
|
||||
ComfyNodeABC = object
|
||||
|
||||
|
||||
class PathAbspath(ComfyNodeABC):
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
from inspect import cleandoc
|
||||
import re
|
||||
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
|
||||
|
||||
try:
|
||||
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
|
||||
except:
|
||||
class IO:
|
||||
BOOLEAN = "BOOLEAN"
|
||||
INT = "INT"
|
||||
FLOAT = "FLOAT"
|
||||
STRING = "STRING"
|
||||
NUMBER = "FLOAT,INT"
|
||||
ANY = "*"
|
||||
ComfyNodeABC = object
|
||||
|
||||
class RegexFindallDataList(ComfyNodeABC):
|
||||
"""
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
from typing import Any
|
||||
from inspect import cleandoc
|
||||
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
|
||||
|
||||
try:
|
||||
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
|
||||
except:
|
||||
class IO:
|
||||
BOOLEAN = "BOOLEAN"
|
||||
INT = "INT"
|
||||
FLOAT = "FLOAT"
|
||||
STRING = "STRING"
|
||||
NUMBER = "FLOAT,INT"
|
||||
ANY = "*"
|
||||
ComfyNodeABC = object
|
||||
|
||||
from ._dynamic_input import ContainsDynamicDict
|
||||
|
||||
|
||||
class SetCreate(ComfyNodeABC):
|
||||
@@ -13,9 +26,9 @@ class SetCreate(ComfyNodeABC):
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"optional": {
|
||||
"optional": ContainsDynamicDict({
|
||||
"item_0": (IO.ANY, {"_dynamic": "number"}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("SET",)
|
||||
@@ -38,9 +51,9 @@ class SetCreateFromBoolean(ComfyNodeABC):
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"optional": {
|
||||
"optional": ContainsDynamicDict({
|
||||
"item_0": (IO.BOOLEAN, {"_dynamic": "number"}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("SET",)
|
||||
@@ -63,9 +76,9 @@ class SetCreateFromFloat(ComfyNodeABC):
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"optional": {
|
||||
"optional": ContainsDynamicDict({
|
||||
"item_0": (IO.FLOAT, {"_dynamic": "number"}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("SET",)
|
||||
@@ -88,9 +101,9 @@ class SetCreateFromInt(ComfyNodeABC):
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"optional": {
|
||||
"optional": ContainsDynamicDict({
|
||||
"item_0": (IO.INT, {"_dynamic": "number"}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("SET",)
|
||||
@@ -113,9 +126,9 @@ class SetCreateFromString(ComfyNodeABC):
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"optional": {
|
||||
"optional": ContainsDynamicDict({
|
||||
"item_0": (IO.STRING, {"_dynamic": "number"}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("SET",)
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
from inspect import cleandoc
|
||||
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
|
||||
|
||||
try:
|
||||
from comfy.comfy_types.node_typing import IO, ComfyNodeABC
|
||||
except:
|
||||
class IO:
|
||||
BOOLEAN = "BOOLEAN"
|
||||
INT = "INT"
|
||||
FLOAT = "FLOAT"
|
||||
STRING = "STRING"
|
||||
NUMBER = "FLOAT,INT"
|
||||
ANY = "*"
|
||||
ComfyNodeABC = object
|
||||
|
||||
class StringCapitalize(ComfyNodeABC):
|
||||
"""Converts the first character of the input string to uppercase and all other characters to lowercase."""
|
||||
|
||||
+36
-36
@@ -1,36 +1,36 @@
|
||||
import os
|
||||
import sys
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
# Add the project root directory to Python path
|
||||
# This allows the tests to import the project
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
|
||||
print("=======================================================================")
|
||||
print("Mocking 'comfy.comfy_types.node_typing' via conftest.py")
|
||||
print("=======================================================================")
|
||||
|
||||
# Create a dummy 'comfy.comfy_types.node_typing' module
|
||||
mock_comfy = MagicMock()
|
||||
mock_comfy.IO = MagicMock(
|
||||
BOOLEAN="BOOLEAN",
|
||||
INT="INT",
|
||||
FLOAT="FLOAT",
|
||||
STRING="STRING",
|
||||
NUMBER = "FLOAT,INT",
|
||||
ANY="*",
|
||||
)
|
||||
mock_comfy.ComfyNodeABC = object
|
||||
|
||||
# Inject the mocked module into sys.modules
|
||||
sys.modules["comfy"] = mock_comfy
|
||||
sys.modules["comfy.comfy_types"] = mock_comfy
|
||||
sys.modules["comfy.comfy_types.node_typing"] = mock_comfy
|
||||
|
||||
def mock_execution_blocker(_):
|
||||
return None
|
||||
|
||||
mock_comfy_execution = MagicMock()
|
||||
mock_comfy_execution.ExecutionBlocker = mock_execution_blocker
|
||||
sys.modules["comfy_execution"] = mock_comfy_execution
|
||||
sys.modules["comfy_execution.graph"] = mock_comfy_execution
|
||||
#import os
|
||||
#import sys
|
||||
# from unittest.mock import MagicMock
|
||||
#
|
||||
# # Add the project root directory to Python path
|
||||
# # This allows the tests to import the project
|
||||
#sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
#
|
||||
# print("=======================================================================")
|
||||
# print("Mocking 'comfy.comfy_types.node_typing' via conftest.py")
|
||||
# print("=======================================================================")
|
||||
#
|
||||
# # Create a dummy 'comfy.comfy_types.node_typing' module
|
||||
# mock_comfy = MagicMock()
|
||||
# mock_comfy.IO = MagicMock(
|
||||
# BOOLEAN="BOOLEAN",
|
||||
# INT="INT",
|
||||
# FLOAT="FLOAT",
|
||||
# STRING="STRING",
|
||||
# NUMBER = "FLOAT,INT",
|
||||
# ANY="*",
|
||||
# )
|
||||
# mock_comfy.ComfyNodeABC = object
|
||||
#
|
||||
# # Inject the mocked module into sys.modules
|
||||
# sys.modules["comfy"] = mock_comfy
|
||||
# sys.modules["comfy.comfy_types"] = mock_comfy
|
||||
# sys.modules["comfy.comfy_types.node_typing"] = mock_comfy
|
||||
#
|
||||
# def mock_execution_blocker(_):
|
||||
# return None
|
||||
#
|
||||
# mock_comfy_execution = MagicMock()
|
||||
# mock_comfy_execution.ExecutionBlocker = mock_execution_blocker
|
||||
# sys.modules["comfy_execution"] = mock_comfy_execution
|
||||
# sys.modules["comfy_execution.graph"] = mock_comfy_execution
|
||||
|
||||
+200
-100
@@ -44,11 +44,19 @@ app.registerExtension({
|
||||
const combinedInputData = {
|
||||
...nodeData?.input?.required ?? {},
|
||||
...nodeData?.input?.optional ?? {}
|
||||
};
|
||||
}
|
||||
const combinedInputDataOrder = [
|
||||
...nodeData?.input_order?.required ?? [],
|
||||
...nodeData?.input_order?.optional ?? []
|
||||
];
|
||||
|
||||
/** Array of (generic) dynamic inputs. */
|
||||
const dynamicInputs = [];
|
||||
for (const name in combinedInputData) {
|
||||
/** Array of groups of (generic) dynamic inputs. */
|
||||
const dynamicInputGroups = [];
|
||||
for (const name of combinedInputDataOrder) {
|
||||
const dynamic = combinedInputData[name][1]?._dynamic;
|
||||
const dynamicGroup = combinedInputData[name][1]?._dynamicGroup ?? 0;
|
||||
|
||||
if (dynamic) {
|
||||
let matcher;
|
||||
@@ -67,7 +75,13 @@ app.registerExtension({
|
||||
}
|
||||
const baseName = name.match(matcher)?.[1] ?? name;
|
||||
const dynamicType = combinedInputData[name][0];
|
||||
dynamicInputs.push({name, baseName, matcher, dynamic, dynamicType});
|
||||
if (dynamicInputGroups[dynamicGroup] === undefined) {
|
||||
dynamicInputGroups[dynamicGroup] = [];
|
||||
}
|
||||
dynamicInputGroups[dynamicGroup].push(
|
||||
dynamicInputs.length
|
||||
)
|
||||
dynamicInputs.push({name, baseName, matcher, dynamic, dynamicType, dynamicGroup});
|
||||
}
|
||||
}
|
||||
if (dynamicInputs.length === 0) {
|
||||
@@ -83,53 +97,39 @@ app.registerExtension({
|
||||
|
||||
/**
|
||||
* Utility: Update inputs' slot indices after reordering.
|
||||
* @param {Array} inputs - List of inputs for the node.
|
||||
* @param {Object} graph - Graph containing the node.
|
||||
* @param {ComfyNode} node - The node to update.
|
||||
*/
|
||||
const updateSlotIndices = (inputs, graph) => {
|
||||
inputs.forEach((input, index) => {
|
||||
const updateSlotIndices = (node) => {
|
||||
node.inputs.forEach((input, index) => {
|
||||
input.slot_index = index;
|
||||
if (input.link !== null) {
|
||||
const link = graph._links.get(input.link);
|
||||
if (link)
|
||||
if (input.isConnected) {
|
||||
const link = node.graph._links.get(input.link);
|
||||
if (link) {
|
||||
link.target_slot = index;
|
||||
else
|
||||
} else {
|
||||
console.error(`Input ${index} has an invalid link.`);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// Override onConfigure: Ensure dynamic inputs are continuously ordered
|
||||
const onConfigure = nodeType.prototype.onConfigure;
|
||||
nodeType.prototype.onConfigure = function() {
|
||||
const result = onConfigure?.apply(this, arguments);
|
||||
let lastDynamicIdx = -1
|
||||
for (let idx = 0; idx < this.inputs.length; idx++) {
|
||||
const input = this.inputs[idx];
|
||||
const isDynamic = isDynamicInput(input.name);
|
||||
if (isDynamic) {
|
||||
if (lastDynamicIdx < 0 || lastDynamicIdx === idx - 1) {
|
||||
lastDynamicIdx = idx;
|
||||
} else {
|
||||
// non-continuous dynamic inputs -> move up
|
||||
for (let i = idx-1; i > lastDynamicIdx; i--) {
|
||||
this.swapInputs(i, i + 1);
|
||||
}
|
||||
lastDynamicIdx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Add helper method to insert input at a specific position
|
||||
nodeType.prototype.addInputAtPosition = function (name, type, position) {
|
||||
console.warn("Adding input at position:", name, type, position);
|
||||
this.addInput(name, type); // Add new input
|
||||
nodeType.prototype.addInputAtPosition = function (name, type, position, isWidget, shape) {
|
||||
if (isWidget) {
|
||||
this.addWidget(type, name, '', ()=>{}, {});
|
||||
|
||||
const GET_CONFIG = Symbol();
|
||||
const input = this.addInput(name, type, {
|
||||
shape,
|
||||
widget: {name, [GET_CONFIG]: () =>{}}
|
||||
})
|
||||
} else {
|
||||
this.addInput(name, type, {shape}); // Add new input
|
||||
}
|
||||
const newInput = this.inputs.pop(); // Fetch the newly added input (last item)
|
||||
this.inputs.splice(position, 0, newInput); // Place it at the desired position
|
||||
updateSlotIndices(this.inputs, this.graph); // Update indices
|
||||
updateSlotIndices(this); // Update indices
|
||||
return newInput;
|
||||
};
|
||||
|
||||
@@ -142,80 +142,133 @@ app.registerExtension({
|
||||
nodeType.prototype.onConnectionsChange = function (type, slotIndex, isConnected, link, ioSlot) {
|
||||
const result = onConnectionsChange?.apply(this, arguments);
|
||||
|
||||
if (type !== TypeSlot.Input || isProcessingConnection) {
|
||||
if (type !== TypeSlot.Input || isProcessingConnection || !isDynamicInput(this.inputs[slotIndex].name)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
function getDynamicGroup(inputName) {
|
||||
// Find the dynamicGroup by matching the baseName with input name
|
||||
for (const di of dynamicInputs) {
|
||||
if (inputName.startsWith(di.baseName)) {
|
||||
return di.dynamicGroup;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
isProcessingConnection = true;
|
||||
|
||||
try {
|
||||
const baseName = dynamicInputs[0].baseName;
|
||||
//const dynamicType = nodeData.input.required[dynamicInputs[0].name][0];
|
||||
const dynamicType = dynamicInputs[0].dynamicType;
|
||||
|
||||
// Get dynamic input slots
|
||||
const dynamicSlots = this.inputs
|
||||
.map((input, idx) => ({
|
||||
index: idx,
|
||||
name: input.name,
|
||||
connected: input.link !== null,
|
||||
isDynamic: isDynamicInput(input.name),
|
||||
}))
|
||||
.filter((input) => input.isDynamic);
|
||||
const dynamicSlots = [];
|
||||
const dynamicGroupCount = [];
|
||||
const dynamicGroupConnected = [];
|
||||
for (const [index, input] of this.inputs.entries()) {
|
||||
const isDynamic = isDynamicInput(input.name);
|
||||
if (isDynamic) {
|
||||
const connected = input.isConnected;
|
||||
const dynamicGroup = getDynamicGroup(input.name);
|
||||
if (dynamicGroup in dynamicGroupCount) {
|
||||
if (input.name.startsWith(dynamicInputs[dynamicInputGroups[dynamicGroup][0]].baseName)) {
|
||||
dynamicGroupConnected[dynamicGroup][dynamicGroupCount[dynamicGroup]] ||= connected;
|
||||
dynamicGroupCount[dynamicGroup]++;
|
||||
}
|
||||
} else {
|
||||
dynamicGroupCount[dynamicGroup] = 1;
|
||||
dynamicGroupConnected[dynamicGroup] = [connected];
|
||||
}
|
||||
dynamicSlots.push({
|
||||
index,
|
||||
name: input.name,
|
||||
isWidget: input.widget !== undefined,
|
||||
shape: input.shape,
|
||||
connected,
|
||||
isDynamic,
|
||||
dynamicGroup,
|
||||
dynamicGroupCount: dynamicGroupCount[dynamicGroup]
|
||||
});
|
||||
|
||||
// sanity check to make sure every widget is in reality a widget. When loading a workflow this
|
||||
// isn't the case so we must fix it ourselves.
|
||||
if (this.widgets && !this.widgets.some((w) => w.name === input.name)) {
|
||||
this.addWidget(input.type, input.name, '', ()=>{}, {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle connection event
|
||||
if (isConnected === TypeSlotEvent.Connect) {
|
||||
const hasEmptyDynamic = dynamicSlots.some(di => !di.connected);
|
||||
const hasEmptyDynamic = dynamicGroupConnected[0].some(dgc => !dgc);
|
||||
|
||||
if (!hasEmptyDynamic) {
|
||||
// No empty slot - add a new one after the last dynamic input
|
||||
const lastDynamicIdx = Math.max(...dynamicSlots.map((slot) => slot.index), -1);
|
||||
const insertPosition = lastDynamicIdx + 1;
|
||||
let insertPosition = lastDynamicIdx + 1;
|
||||
let inputInRange = true;
|
||||
|
||||
let newName;
|
||||
if (dynamicInputs[0].dynamic === 'letter') {
|
||||
if (dynamicSlots.length > 26) {
|
||||
inputInRange = false;
|
||||
for (const groupMember of dynamicInputGroups[dynamicInputs[0].dynamicGroup]) {
|
||||
const baseName = dynamicInputs[groupMember].baseName;
|
||||
const dynamicType = dynamicInputs[groupMember].dynamicType;
|
||||
let newName;
|
||||
if (dynamicInputs[0].dynamic === 'letter') {
|
||||
if (dynamicSlots.length >= 26) {
|
||||
inputInRange = false;
|
||||
}
|
||||
// For letter type, use the next letter in sequence
|
||||
newName = String.fromCharCode(97 + dynamicSlots.length); // 97 is ASCII for 'a'
|
||||
} else {
|
||||
// For number type, use baseName + index as before
|
||||
newName = `${baseName}${dynamicSlots.length}`;
|
||||
}
|
||||
// For letter type, use the next letter in sequence
|
||||
newName = String.fromCharCode(97 + dynamicSlots.length); // 97 is ASCII for 'a'
|
||||
} else {
|
||||
// For number type, use baseName + index as before
|
||||
newName = `${baseName}${dynamicSlots.length}`;
|
||||
}
|
||||
|
||||
if (inputInRange) {
|
||||
// Insert the new empty input at the correct position
|
||||
this.addInputAtPosition(newName, dynamicType, insertPosition);
|
||||
|
||||
// Renumber inputs after addition
|
||||
this.renumberDynamicInputs(baseName, dynamicInputs, dynamicInputs[0].dynamic);
|
||||
if (inputInRange) {
|
||||
// Insert the new empty input at the correct position
|
||||
this.addInputAtPosition(newName, dynamicType, insertPosition++, dynamicSlots[groupMember].isWidget, dynamicSlots[groupMember].shape);
|
||||
// Renumber inputs after addition
|
||||
this.renumberDynamicInputs(baseName, dynamicInputs, dynamicInputs[0].dynamic);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (isConnected === TypeSlotEvent.Disconnect) {
|
||||
let hasEmptyDynamic = false;
|
||||
let foundEmptyIndex = -1;
|
||||
|
||||
for (let idx = 0; idx < this.inputs.length; idx++) {
|
||||
const input = this.inputs[idx];
|
||||
const isDynamic = isDynamicInput(input.name);
|
||||
if (hasEmptyDynamic && isDynamic) {
|
||||
if (input.link === null) {
|
||||
// last input is empty and this input is empty
|
||||
this.removeInput(idx);
|
||||
// continue with this ixd as it is now pointing to
|
||||
// a new input
|
||||
idx--;
|
||||
continue;
|
||||
}
|
||||
// this input is dynamic and connected
|
||||
this.swapInputs(idx, idx - 1);
|
||||
|
||||
if (!isDynamicInput(input.name)) {
|
||||
continue;
|
||||
}
|
||||
if (isDynamic && input.link === null) {
|
||||
hasEmptyDynamic = true;
|
||||
|
||||
if (!input.isConnected) { // Check if the input is empty
|
||||
// remove empty input - but only when it's not the last one
|
||||
const dynamicGroup = getDynamicGroup(input.name);
|
||||
let isLast = true;
|
||||
for (let i = idx + 1; i < this.inputs.length; i++) {
|
||||
isLast &&= !this.inputs[i].name.startsWith(dynamicInputs[dynamicInputGroups[dynamicGroup][0]].baseName);
|
||||
}
|
||||
if (isLast) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let i = idx + 1; i < this.inputs.length; i++) {
|
||||
this.swapInputs(i - 1, i);
|
||||
}
|
||||
const lastIdx = this.inputs.length - 1;
|
||||
if (this.inputs[lastIdx].widget !== undefined) {
|
||||
const widgetIdx = this.widgets.findIndex((w) => w.name === this.inputs[lastIdx].widget.name)
|
||||
this.widgets.splice(widgetIdx, 1);
|
||||
this.widgets_values?.splice(widgetIdx, 1);
|
||||
}
|
||||
this.removeInput(lastIdx);
|
||||
}
|
||||
}
|
||||
this.renumberDynamicInputs(baseName, dynamicInputs, dynamicInputs[0].dynamic);
|
||||
|
||||
// Renumber dynamic inputs to ensure proper ordering
|
||||
for (const groupMember of dynamicInputGroups[dynamicInputs[0].dynamicGroup]) {
|
||||
const baseName = dynamicInputs[groupMember].baseName;
|
||||
this.renumberDynamicInputs(baseName, dynamicInputs, dynamicInputs[0].dynamic);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this.setDirtyCanvas(true, true);
|
||||
@@ -230,6 +283,35 @@ app.registerExtension({
|
||||
return result;
|
||||
};
|
||||
|
||||
const onConnectInput = nodeType.prototype.onConnectInput;
|
||||
nodeType.prototype.onConnectInput = function(inputIndex, outputType, outputSlot, outputNode, outputIndex) {
|
||||
const result = onRemoved?.apply(this, arguments) ?? true;
|
||||
|
||||
if (this.inputs[inputIndex].isConnected) {
|
||||
const pre_isProcessingConnection = isProcessingConnection;
|
||||
isProcessingConnection = true;
|
||||
this.disconnectInput(inputIndex, true);
|
||||
isProcessingConnection = pre_isProcessingConnection;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const onRemoved = nodeType.prototype.onRemoved;
|
||||
nodeType.prototype.onRemoved = function () {
|
||||
const result = onRemoved?.apply(this, arguments);
|
||||
|
||||
// When this is called, the input links are already removed - but
|
||||
// due to the implementation of the remove() method it might not
|
||||
// have worked with the dynamic inputs. So we need to fix it here.
|
||||
for (let i = this.inputs.length-1; i >= 0; i--) {
|
||||
if ( this.inputs[i].isConnected) {
|
||||
this.disconnectInput(i, true);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Method to swap two inputs in the "this.inputs" array by their indices
|
||||
nodeType.prototype.swapInputs = function(indexA, indexB) {
|
||||
// Validate indices
|
||||
@@ -239,13 +321,30 @@ app.registerExtension({
|
||||
indexB >= this.inputs.length ||
|
||||
indexA === indexB
|
||||
) {
|
||||
console.warn("Invalid input indices for swapping:", indexA, indexB);
|
||||
console.error("Invalid input indices for swapping:", indexA, indexB);
|
||||
return;
|
||||
}
|
||||
|
||||
// reflect the swap with the widgets
|
||||
if (this.inputs[indexA].widget !== undefined) {
|
||||
if (this.inputs[indexB].widget === undefined) {
|
||||
console.error("Bad swap: input A is a widget but input B is not", indexA, indexB);
|
||||
}
|
||||
const widgetIdxA = this.widgets.findIndex((w) => w.name === this.inputs[indexA].widget.name);
|
||||
const widgetIdxB = this.widgets.findIndex((w) => w.name === this.inputs[indexB].widget.name);
|
||||
[this.widgets[widgetIdxA].y, this.widgets[widgetIdxB].y] = [this.widgets[widgetIdxB].y, this.widgets[widgetIdxA].y];
|
||||
[this.widgets[widgetIdxA].last_y, this.widgets[widgetIdxB].last_y] = [this.widgets[widgetIdxB].last_y, this.widgets[widgetIdxA].last_y];
|
||||
[this.widgets[widgetIdxA], this.widgets[widgetIdxB]] = [this.widgets[widgetIdxB], this.widgets[widgetIdxA]];
|
||||
if (this.widgets_values) {
|
||||
[this.widgets_values[widgetIdxA], this.widgets_values[widgetIdxB]] = [this.widgets_values[widgetIdxB], this.widgets_values[widgetIdxA]];
|
||||
}
|
||||
}
|
||||
|
||||
// Swap the inputs in the array
|
||||
[this.inputs[indexA].boundingRect, this.inputs[indexB].boundingRect] = [this.inputs[indexB].boundingRect, this.inputs[indexA].boundingRect];
|
||||
[this.inputs[indexA].pos, this.inputs[indexB].pos] = [this.inputs[indexB].pos, this.inputs[indexA].pos];
|
||||
[this.inputs[indexA], this.inputs[indexB]] = [this.inputs[indexB], this.inputs[indexA]];
|
||||
updateSlotIndices(this.inputs, this.graph); // Refresh indices
|
||||
updateSlotIndices(this); // Refresh indices
|
||||
|
||||
// Redraw the node to ensure the graph updates properly
|
||||
// -> not needed as the calling method must do it!
|
||||
@@ -261,31 +360,32 @@ app.registerExtension({
|
||||
const input = this.inputs[i];
|
||||
const isDynamic = isDynamicInput(input.name);
|
||||
|
||||
if (isDynamic) {
|
||||
if (isDynamic && input.name.startsWith(baseName)) {
|
||||
dynamicInputInfo.push({
|
||||
index: i,
|
||||
widgetIdx: input.widget !== undefined ? this.widgets.findIndex((w) => w.name === input.widget.name) : undefined,
|
||||
name: input.name,
|
||||
connected: input.link !== null
|
||||
connected: input.isConnected
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Sort connected first, then by index
|
||||
dynamicInputInfo.sort((a, b) => {
|
||||
if (a.connected !== b.connected) {
|
||||
return b.connected ? 1 : -1; // Connected first
|
||||
}
|
||||
return a.index - b.index; // Keep order for same connection status
|
||||
});
|
||||
|
||||
// Just rename the inputs in place - don't remove/add to keep connections intact
|
||||
for (let i = 0; i < dynamicInputInfo.length; i++) {
|
||||
const info = dynamicInputInfo[i];
|
||||
const input = this.inputs[info.index];
|
||||
const newName = dynamic === "number" ? `${baseName}${i}` : String.fromCharCode(97 + i); // 97 is ASCII for 'a'
|
||||
|
||||
if (this.inputs[info.index].name !== newName) {
|
||||
this.inputs[info.index].name = newName;
|
||||
this.inputs[info.index].localized_name = newName;
|
||||
if (input.widget !== undefined) {
|
||||
const widgetIdx = info.widgetIdx;
|
||||
input.widget.name = newName;
|
||||
this.widgets[widgetIdx].name = newName;
|
||||
this.widgets[widgetIdx].label = newName;
|
||||
}
|
||||
|
||||
if (input.name !== newName) {
|
||||
input.name = newName;
|
||||
input.localized_name = newName;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user