mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2025-12-23 04:50:49 +08:00
Add nesting of inputs on DynamicCombo during execution
This commit is contained in:
parent
159e2d02c9
commit
75cc2194ff
@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import inspect
|
||||
import re
|
||||
from abc import ABC, abstractmethod
|
||||
from collections import Counter
|
||||
from collections.abc import Iterable
|
||||
@ -881,6 +882,14 @@ class AutogrowDynamic(ComfyTypeI):
|
||||
curr_count += 1
|
||||
return new_inputs
|
||||
|
||||
def add_dynamic_id_mapping(d: dict[str], inputs: list[Input], curr_prefix: str, self: DynamicInput=None):
|
||||
dynamic = d.setdefault("dynamic_data", {})
|
||||
if self is not None:
|
||||
dynamic[self.id] = f"{curr_prefix}{self.id}"
|
||||
for i in inputs:
|
||||
if not isinstance(i, DynamicInput):
|
||||
dynamic[i.id] = f"{curr_prefix}{i.id}"
|
||||
|
||||
@comfytype(io_type="COMFY_DYNAMICCOMBO_V3")
|
||||
class DynamicCombo(ComfyTypeI):
|
||||
class Option:
|
||||
@ -900,8 +909,9 @@ class DynamicCombo(ComfyTypeI):
|
||||
super().__init__(id, display_name, optional, tooltip, lazy, extra_dict)
|
||||
self.options = options
|
||||
|
||||
def add_to_dict_live_inputs(self, d: dict[str], live_inputs: dict[str]):
|
||||
def add_to_dict_live_inputs(self, d: dict[str], live_inputs: dict[str], curr_prefix=''):
|
||||
# check if dynamic input's id is in live_inputs
|
||||
curr_prefix = f"{curr_prefix}{self.id}."
|
||||
if self.id in live_inputs:
|
||||
key = live_inputs[self.id]
|
||||
selected_option = None
|
||||
@ -910,8 +920,8 @@ class DynamicCombo(ComfyTypeI):
|
||||
selected_option = option
|
||||
break
|
||||
if selected_option is not None:
|
||||
add_to_input_dict_v1(d, selected_option.inputs, live_inputs)
|
||||
add_dynamic_to_dict_v1(d, self, selected_option.inputs)
|
||||
add_to_input_dict_v1(d, selected_option.inputs, live_inputs, curr_prefix)
|
||||
add_dynamic_id_mapping(d, selected_option.inputs, curr_prefix, self)
|
||||
|
||||
def get_dynamic(self) -> list[Input]:
|
||||
return [input for option in self.options for input in option.inputs]
|
||||
@ -1259,12 +1269,12 @@ def create_input_dict_v1(inputs: list[Input], live_inputs: dict[str]=None) -> di
|
||||
add_to_input_dict_v1(input, inputs, live_inputs)
|
||||
return input
|
||||
|
||||
def add_to_input_dict_v1(d: dict[str], inputs: list[Input], live_inputs: dict[str]=None):
|
||||
def add_to_input_dict_v1(d: dict[str], inputs: list[Input], live_inputs: dict[str]=None, curr_prefix=''):
|
||||
for i in inputs:
|
||||
if isinstance(i, DynamicInput):
|
||||
add_to_dict_v1(i, d)
|
||||
if live_inputs is not None:
|
||||
i.add_to_dict_live_inputs(d, live_inputs)
|
||||
i.add_to_dict_live_inputs(d, live_inputs, curr_prefix)
|
||||
else:
|
||||
add_to_dict_v1(i, d)
|
||||
|
||||
@ -1279,14 +1289,59 @@ def add_to_dict_v1(i: Input, d: dict, dynamic_dict: dict=None):
|
||||
value = (i.get_io_type(), as_dict, dynamic_dict)
|
||||
d.setdefault(key, {})[i.id] = value
|
||||
|
||||
def add_dynamic_to_dict_v1(d: dict[str], parent: DynamicInput, inputs: list[Input]):
|
||||
dynamic = d.setdefault("dynamic_data", {})
|
||||
ids = [i.id for i in inputs]
|
||||
dynamic[parent.id] = {"ids": ids}
|
||||
|
||||
def add_to_dict_v3(io: Input | Output, d: dict):
|
||||
d[io.id] = (io.get_io_type(), io.as_dict())
|
||||
|
||||
def build_nested_inputs(values: dict[str], paths: dict[str]):
|
||||
# NOTE: This was initially AI generated
|
||||
# Tries to account for arrays as well, will likely be changed once that's in
|
||||
values = values.copy()
|
||||
result = {}
|
||||
|
||||
index_pattern = re.compile(r"^(?P<key>[A-Za-z0-9_]+)\[(?P<index>\d+)\]$")
|
||||
|
||||
for key, path in paths.items():
|
||||
parts = path.split(".")
|
||||
current = result
|
||||
|
||||
for i, p in enumerate(parts):
|
||||
is_last = (i == len(parts) - 1)
|
||||
|
||||
match = index_pattern.match(p)
|
||||
if match:
|
||||
list_key = match.group("key")
|
||||
index = int(match.group("index"))
|
||||
|
||||
# Ensure list exists
|
||||
if list_key not in current or not isinstance(current[list_key], list):
|
||||
current[list_key] = []
|
||||
|
||||
lst = current[list_key]
|
||||
|
||||
# Expand list to the needed index
|
||||
while len(lst) <= index:
|
||||
lst.append(None)
|
||||
|
||||
# Last element → assign the value directly
|
||||
if is_last:
|
||||
lst[index] = values.pop(key)
|
||||
break
|
||||
|
||||
# Non-last element → ensure dict
|
||||
if lst[index] is None or not isinstance(lst[index], dict):
|
||||
lst[index] = {}
|
||||
|
||||
current = lst[index]
|
||||
continue
|
||||
|
||||
# Normal dict key
|
||||
if is_last:
|
||||
current[p] = values.pop(key)
|
||||
else:
|
||||
current = current.setdefault(p, {})
|
||||
|
||||
values.update(result)
|
||||
return values
|
||||
|
||||
|
||||
class _ComfyNodeBaseInternal(_ComfyNodeInternal):
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
from typing import TypedDict
|
||||
from typing_extensions import override
|
||||
from comfy_api.latest import ComfyExtension, io
|
||||
|
||||
@ -35,6 +36,12 @@ class SwitchNode(io.ComfyNode):
|
||||
|
||||
|
||||
class DCTestNode(io.ComfyNode):
|
||||
class DCValues(TypedDict):
|
||||
combo: str
|
||||
string: str
|
||||
integer: int
|
||||
image: io.Image.Type
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls):
|
||||
return io.Schema(
|
||||
@ -52,15 +59,16 @@ class DCTestNode(io.ComfyNode):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, combo, **kwargs) -> io.NodeOutput:
|
||||
if combo == "option1":
|
||||
return io.NodeOutput(kwargs["string"])
|
||||
elif combo == "option2":
|
||||
return io.NodeOutput(kwargs["integer"])
|
||||
elif combo == "option3":
|
||||
return io.NodeOutput(kwargs["image"])
|
||||
def execute(cls, combo: DCValues) -> io.NodeOutput:
|
||||
combo_val = combo["combo"]
|
||||
if combo_val == "option1":
|
||||
return io.NodeOutput(combo["string"])
|
||||
elif combo_val == "option2":
|
||||
return io.NodeOutput(combo["integer"])
|
||||
elif combo_val == "option3":
|
||||
return io.NodeOutput(combo["image"])
|
||||
else:
|
||||
raise ValueError(f"Invalid combo: {combo}")
|
||||
raise ValueError(f"Invalid combo: {combo_val}")
|
||||
|
||||
|
||||
class LogicExtension(ComfyExtension):
|
||||
|
||||
@ -34,7 +34,7 @@ from comfy_execution.validation import validate_node_input
|
||||
from comfy_execution.progress import get_progress_state, reset_progress_state, add_progress_handler, WebUIProgressHandler
|
||||
from comfy_execution.utils import CurrentNodeContext
|
||||
from comfy_api.internal import _ComfyNodeInternal, _NodeOutputInternal, first_real_override, is_class, make_locked_method_func
|
||||
from comfy_api.latest import io
|
||||
from comfy_api.latest import io, _io
|
||||
|
||||
|
||||
class ExecutionResult(Enum):
|
||||
@ -268,6 +268,9 @@ async def _async_map_node_over_list(prompt_id, unique_id, obj, input_data_all, f
|
||||
type_obj.VALIDATE_CLASS()
|
||||
class_clone = type_obj.PREPARE_CLASS_CLONE(v3_data)
|
||||
f = make_locked_method_func(type_obj, func, class_clone)
|
||||
# in case of dynamic inputs, restructure inputs to expected nested dict
|
||||
if v3_data is not None and v3_data["dynamic_data"] is not None:
|
||||
inputs = _io.build_nested_inputs(inputs, v3_data["dynamic_data"])
|
||||
# V1
|
||||
else:
|
||||
f = getattr(obj, func)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user