Some checks are pending
Publish to Comfy registry / Publish Custom Node to registry (push) Waiting to run
JS extension auto-creates LiteGraph links between consecutive passthrough nodes based on their order widget (1→2→3→4), hiding the wait_for input and drawing visual chain lines between nodes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
197 lines
6.0 KiB
Python
197 lines
6.0 KiB
Python
import os
|
|
|
|
|
|
class AnyType(str):
|
|
"""Universal connector type that matches any ComfyUI type."""
|
|
def __ne__(self, __value: object) -> bool:
|
|
return False
|
|
|
|
any_type = AnyType("*")
|
|
|
|
|
|
class JDL_PathJoin:
|
|
"""Joins 1-6 path segments using os.path.join."""
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {
|
|
"required": {
|
|
"segment_1": ("STRING", {"default": "", "multiline": False}),
|
|
},
|
|
"optional": {
|
|
"segment_2": ("STRING", {"default": "", "multiline": False}),
|
|
"segment_3": ("STRING", {"default": "", "multiline": False}),
|
|
"segment_4": ("STRING", {"default": "", "multiline": False}),
|
|
"segment_5": ("STRING", {"default": "", "multiline": False}),
|
|
"segment_6": ("STRING", {"default": "", "multiline": False}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("STRING",)
|
|
RETURN_NAMES = ("path",)
|
|
FUNCTION = "join_path"
|
|
CATEGORY = "utils/path"
|
|
|
|
def join_path(self, segment_1, segment_2="", segment_3="", segment_4="",
|
|
segment_5="", segment_6=""):
|
|
segments = [s for s in [segment_1, segment_2, segment_3, segment_4,
|
|
segment_5, segment_6] if s]
|
|
if not segments:
|
|
return ("",)
|
|
return (os.path.normpath(os.path.join(*segments)),)
|
|
|
|
|
|
class JDL_StringFormat:
|
|
"""Python format-string templating with up to 8 inputs."""
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
optional = {}
|
|
for i in range(8):
|
|
optional[f"v{i}"] = (any_type, {"default": ""})
|
|
return {
|
|
"required": {
|
|
"template": ("STRING", {"default": "{0}/{1}", "multiline": False}),
|
|
},
|
|
"optional": optional,
|
|
}
|
|
|
|
RETURN_TYPES = ("STRING",)
|
|
RETURN_NAMES = ("string",)
|
|
FUNCTION = "format_string"
|
|
CATEGORY = "utils/string"
|
|
|
|
def format_string(self, template, **kwargs):
|
|
values = []
|
|
for i in range(8):
|
|
values.append(kwargs.get(f"v{i}", ""))
|
|
try:
|
|
result = template.format(*values)
|
|
except (IndexError, KeyError, ValueError) as e:
|
|
result = f"[format error: {e}]"
|
|
return (result,)
|
|
|
|
|
|
class JDL_StringExtract:
|
|
"""Extracts substrings via split, between-delimiters, or filename decomposition."""
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {
|
|
"required": {
|
|
"text": ("STRING", {"default": "", "multiline": False}),
|
|
"mode": (["split_take", "between", "filename_parts"],),
|
|
},
|
|
"optional": {
|
|
"delimiter": ("STRING", {"default": "/", "multiline": False}),
|
|
"index": ("INT", {"default": -1, "min": -999, "max": 999}),
|
|
"delimiter_2": ("STRING", {"default": "", "multiline": False}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING")
|
|
RETURN_NAMES = ("result", "dirname", "basename", "extension")
|
|
FUNCTION = "extract"
|
|
CATEGORY = "utils/string"
|
|
|
|
def extract(self, text, mode, delimiter="/", index=-1, delimiter_2=""):
|
|
dirname = os.path.dirname(text)
|
|
full_basename = os.path.basename(text)
|
|
name, ext = os.path.splitext(full_basename)
|
|
extension = ext.lstrip(".")
|
|
|
|
if mode == "split_take":
|
|
parts = text.split(delimiter)
|
|
try:
|
|
result = parts[index]
|
|
except IndexError:
|
|
result = ""
|
|
elif mode == "between":
|
|
result = ""
|
|
start = text.find(delimiter)
|
|
if start != -1:
|
|
start += len(delimiter)
|
|
if delimiter_2:
|
|
end = text.find(delimiter_2, start)
|
|
if end != -1:
|
|
result = text[start:end]
|
|
else:
|
|
result = text[start:]
|
|
else:
|
|
result = text[start:]
|
|
else: # filename_parts
|
|
result = name
|
|
|
|
return (result, dirname, name, extension)
|
|
|
|
|
|
class JDL_StringSwitch:
|
|
"""Boolean-based string selection with built-in defaults."""
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {
|
|
"required": {
|
|
"condition": ("BOOLEAN", {"default": True}),
|
|
},
|
|
"optional": {
|
|
"on_true": (any_type,),
|
|
"on_false": (any_type,),
|
|
"default_true": ("STRING", {"default": "", "multiline": False}),
|
|
"default_false": ("STRING", {"default": "", "multiline": False}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = (any_type,)
|
|
RETURN_NAMES = ("result",)
|
|
FUNCTION = "switch"
|
|
CATEGORY = "utils/string"
|
|
|
|
def switch(self, condition, on_true=None, on_false=None,
|
|
default_true="", default_false=""):
|
|
if condition:
|
|
return (on_true if on_true is not None else default_true,)
|
|
else:
|
|
return (on_false if on_false is not None else default_false,)
|
|
|
|
|
|
class JDL_DependencyPassthrough:
|
|
"""Passes data through unchanged. Set 'order' in the JS widget to auto-wire
|
|
execution order between passthrough nodes (1 → 2 → 3 → 4)."""
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {
|
|
"required": {
|
|
"data": (any_type,),
|
|
},
|
|
"optional": {
|
|
"wait_for": (any_type,),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = (any_type,)
|
|
RETURN_NAMES = ("data",)
|
|
FUNCTION = "passthrough"
|
|
CATEGORY = "utils/flow"
|
|
|
|
def passthrough(self, data, wait_for=None):
|
|
return (data,)
|
|
|
|
|
|
NODE_CLASS_MAPPINGS = {
|
|
"JDL_PathJoin": JDL_PathJoin,
|
|
"JDL_StringFormat": JDL_StringFormat,
|
|
"JDL_StringExtract": JDL_StringExtract,
|
|
"JDL_StringSwitch": JDL_StringSwitch,
|
|
"JDL_DependencyPassthrough": JDL_DependencyPassthrough,
|
|
}
|
|
|
|
NODE_DISPLAY_NAME_MAPPINGS = {
|
|
"JDL_PathJoin": "Path Join",
|
|
"JDL_StringFormat": "String Format",
|
|
"JDL_StringExtract": "String Extract",
|
|
"JDL_StringSwitch": "String Switch",
|
|
"JDL_DependencyPassthrough": "Ordered Passthrough",
|
|
}
|