Files

179 lines
4.6 KiB
Python

#!/usr/bin/env python3
"""
Pipe node for ComfyUI meant to encapsulate all pipe-oriented needs.
"""
# Define all pipe fields in one place
PIPE_FIELDS = {
"required": {
"model": ("MODEL",),
"clip": ("CLIP",),
"vae": ("VAE",),
},
"optional": {
"positive": ("CONDITIONING",),
"negative": ("CONDITIONING",),
"seed": ("INT", {"forceInput": True}),
"image": ("IMAGE",),
"latent": ("LATENT",),
"meta": ("META", {"forceInput": True}),
}
}
def get_all_field_names():
"""Get list of all field names from PIPE_FIELDS."""
return (list(PIPE_FIELDS["required"].keys()) +
list(PIPE_FIELDS["optional"].keys()))
def get_all_field_types():
"""Get list of all field types in order."""
types = []
for field in get_all_field_names():
if field in PIPE_FIELDS["required"]:
types.append(PIPE_FIELDS["required"][field][0])
else:
types.append(PIPE_FIELDS["optional"][field][0])
return types
class FullPipePack:
"""Pack inputs into a dictionary pipe."""
@classmethod
def INPUT_TYPES(cls):
return PIPE_FIELDS.copy()
RETURN_TYPES = ("FULL_PIPE",)
RETURN_NAMES = ("full_pipe",)
FUNCTION = "pack"
CATEGORY = "mudknight/pipe"
def pack(self, **kwargs):
# Pack all provided kwargs into pipe dictionary
return ({k: v for k, v in kwargs.items()},)
class FullPipeOut:
"""Unpack a dictionary pipe into individual outputs."""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"full_pipe": ("FULL_PIPE",),
}
}
RETURN_TYPES = ("FULL_PIPE",) + tuple(get_all_field_types())
RETURN_NAMES = ("full_pipe",) + tuple(get_all_field_names())
FUNCTION = "unpack"
CATEGORY = "mudknight/pipe"
def unpack(self, full_pipe):
# Return pipe followed by all fields in order
return tuple([full_pipe] +
[full_pipe.get(name) for name in get_all_field_names()])
class FullPipeIn:
"""Update specific values in the pipe without unpacking everything."""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"full_pipe": ("FULL_PIPE",),
},
"optional": dict(list(PIPE_FIELDS["required"].items()) +
list(PIPE_FIELDS["optional"].items()))
}
RETURN_TYPES = ("FULL_PIPE",)
RETURN_NAMES = ("full_pipe",)
FUNCTION = "edit"
CATEGORY = "mudknight/pipe"
def edit(self, full_pipe, **kwargs):
new_pipe = full_pipe.copy()
for key, value in kwargs.items():
if value is not None:
if isinstance(value, str) and value == "":
continue
new_pipe[key] = value
return (new_pipe,)
class MetaEditNode:
"""Set a key-value pair on a meta dict."""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"key": ("STRING", {"default": ""}),
"value": ("STRING", {"default": ""}),
},
"optional": {
"meta": ("META", {"forceInput": True}),
},
}
RETURN_TYPES = ("META",)
RETURN_NAMES = ("meta",)
FUNCTION = "edit"
CATEGORY = "mudknight/pipe"
def edit(self, key, value, meta=None):
# Start from empty dict if no meta provided
result = dict(meta) if meta else {}
result[key] = value
return (result,)
class PipeMetaEditNode:
"""Set a key-value pair on the meta dict inside a full pipe."""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"full_pipe": ("FULL_PIPE",),
"key": ("STRING", {"default": ""}),
"value": ("STRING", {"default": ""}),
},
}
RETURN_TYPES = ("FULL_PIPE",)
RETURN_NAMES = ("full_pipe",)
FUNCTION = "edit"
CATEGORY = "mudknight/pipe"
def edit(self, full_pipe, key, value):
new_pipe = full_pipe.copy()
# Copy existing meta or start fresh
meta = dict(new_pipe.get("meta") or {})
meta[key] = value
new_pipe["meta"] = meta
return (new_pipe,)
NODE_CLASS_MAPPINGS = {
"FullPipePack": FullPipePack,
"FullPipeOut": FullPipeOut,
"FullPipeIn": FullPipeIn,
"MetaEditNode": MetaEditNode,
"PipeMetaEditNode": PipeMetaEditNode,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"FullPipePack": "Full Pipe Pack",
"FullPipeOut": "Full Pipe Out",
"FullPipeIn": "Full Pipe In",
"MetaEditNode": "Meta Edit",
"PipeMetaEditNode": "Pipe Meta Edit",
}