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 copy
|
||||||
import inspect
|
import inspect
|
||||||
|
import re
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
from collections.abc import Iterable
|
from collections.abc import Iterable
|
||||||
@ -881,6 +882,14 @@ class AutogrowDynamic(ComfyTypeI):
|
|||||||
curr_count += 1
|
curr_count += 1
|
||||||
return new_inputs
|
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")
|
@comfytype(io_type="COMFY_DYNAMICCOMBO_V3")
|
||||||
class DynamicCombo(ComfyTypeI):
|
class DynamicCombo(ComfyTypeI):
|
||||||
class Option:
|
class Option:
|
||||||
@ -900,8 +909,9 @@ class DynamicCombo(ComfyTypeI):
|
|||||||
super().__init__(id, display_name, optional, tooltip, lazy, extra_dict)
|
super().__init__(id, display_name, optional, tooltip, lazy, extra_dict)
|
||||||
self.options = options
|
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
|
# check if dynamic input's id is in live_inputs
|
||||||
|
curr_prefix = f"{curr_prefix}{self.id}."
|
||||||
if self.id in live_inputs:
|
if self.id in live_inputs:
|
||||||
key = live_inputs[self.id]
|
key = live_inputs[self.id]
|
||||||
selected_option = None
|
selected_option = None
|
||||||
@ -910,8 +920,8 @@ class DynamicCombo(ComfyTypeI):
|
|||||||
selected_option = option
|
selected_option = option
|
||||||
break
|
break
|
||||||
if selected_option is not None:
|
if selected_option is not None:
|
||||||
add_to_input_dict_v1(d, selected_option.inputs, live_inputs)
|
add_to_input_dict_v1(d, selected_option.inputs, live_inputs, curr_prefix)
|
||||||
add_dynamic_to_dict_v1(d, self, selected_option.inputs)
|
add_dynamic_id_mapping(d, selected_option.inputs, curr_prefix, self)
|
||||||
|
|
||||||
def get_dynamic(self) -> list[Input]:
|
def get_dynamic(self) -> list[Input]:
|
||||||
return [input for option in self.options for input in option.inputs]
|
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)
|
add_to_input_dict_v1(input, inputs, live_inputs)
|
||||||
return input
|
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:
|
for i in inputs:
|
||||||
if isinstance(i, DynamicInput):
|
if isinstance(i, DynamicInput):
|
||||||
add_to_dict_v1(i, d)
|
add_to_dict_v1(i, d)
|
||||||
if live_inputs is not None:
|
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:
|
else:
|
||||||
add_to_dict_v1(i, d)
|
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)
|
value = (i.get_io_type(), as_dict, dynamic_dict)
|
||||||
d.setdefault(key, {})[i.id] = value
|
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):
|
def add_to_dict_v3(io: Input | Output, d: dict):
|
||||||
d[io.id] = (io.get_io_type(), io.as_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):
|
class _ComfyNodeBaseInternal(_ComfyNodeInternal):
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
from typing import TypedDict
|
||||||
from typing_extensions import override
|
from typing_extensions import override
|
||||||
from comfy_api.latest import ComfyExtension, io
|
from comfy_api.latest import ComfyExtension, io
|
||||||
|
|
||||||
@ -35,6 +36,12 @@ class SwitchNode(io.ComfyNode):
|
|||||||
|
|
||||||
|
|
||||||
class DCTestNode(io.ComfyNode):
|
class DCTestNode(io.ComfyNode):
|
||||||
|
class DCValues(TypedDict):
|
||||||
|
combo: str
|
||||||
|
string: str
|
||||||
|
integer: int
|
||||||
|
image: io.Image.Type
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def define_schema(cls):
|
def define_schema(cls):
|
||||||
return io.Schema(
|
return io.Schema(
|
||||||
@ -52,15 +59,16 @@ class DCTestNode(io.ComfyNode):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def execute(cls, combo, **kwargs) -> io.NodeOutput:
|
def execute(cls, combo: DCValues) -> io.NodeOutput:
|
||||||
if combo == "option1":
|
combo_val = combo["combo"]
|
||||||
return io.NodeOutput(kwargs["string"])
|
if combo_val == "option1":
|
||||||
elif combo == "option2":
|
return io.NodeOutput(combo["string"])
|
||||||
return io.NodeOutput(kwargs["integer"])
|
elif combo_val == "option2":
|
||||||
elif combo == "option3":
|
return io.NodeOutput(combo["integer"])
|
||||||
return io.NodeOutput(kwargs["image"])
|
elif combo_val == "option3":
|
||||||
|
return io.NodeOutput(combo["image"])
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Invalid combo: {combo}")
|
raise ValueError(f"Invalid combo: {combo_val}")
|
||||||
|
|
||||||
|
|
||||||
class LogicExtension(ComfyExtension):
|
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.progress import get_progress_state, reset_progress_state, add_progress_handler, WebUIProgressHandler
|
||||||
from comfy_execution.utils import CurrentNodeContext
|
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.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):
|
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()
|
type_obj.VALIDATE_CLASS()
|
||||||
class_clone = type_obj.PREPARE_CLASS_CLONE(v3_data)
|
class_clone = type_obj.PREPARE_CLASS_CLONE(v3_data)
|
||||||
f = make_locked_method_func(type_obj, func, class_clone)
|
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
|
# V1
|
||||||
else:
|
else:
|
||||||
f = getattr(obj, func)
|
f = getattr(obj, func)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user