mirror of
https://github.com/Comfy-Org/ComfyUI.git
synced 2026-08-25 10:22:12 +08:00
DynamicOutputs: tighter integration with DynamicCombo / DynamicSlot via FromInput
Lets dynamic-input nodes co-declare per-option outputs so the option list is a single source of truth for both inputs and outputs. * DynamicCombo.Option / DynamicSlot.Option gain an optional outputs=[...] list. * DynamicOutputs.FromInput(input_id) is a positional placeholder in outputs[] that resolves to the referenced input's active-option outputs at finalize time. DynamicCombo selects by literal value; DynamicSlot selects by the upstream slot's resolved type (or when=None when unlinked). * get_finalized_class_outputs gains schema_inputs / live_input_types and the TypeResolver / execute() compute live_input_types only when at least one FromInput → DynamicSlot is present. * Schema.validate(): FromInput must reference an existing DynamicCombo / DynamicSlot input, each input may be referenced at most once, and option output ids stay globally unique. * V1 info synthesizes the dynamic_outputs entry as kind='by_key' for combos and kind='by_slot' for slots, inlining the option outputs. Tests: combo + slot FromInput finalization, FromInput validation (missing / duplicate / id collision), and TypeResolver end-to-end picks for both kinds. Amp-Thread-ID: https://ampcode.com/threads/T-019e8568-f382-743d-a97f-0de3ff29d501 Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
@@ -145,7 +145,7 @@ class TypeResolver:
|
||||
# V3 schemas may declare DynamicOutputs groups whose active slots are
|
||||
# determined by prompt inputs and do not appear in class RETURN_TYPES;
|
||||
# resolve types against the finalized output list when present.
|
||||
finalized = self._get_finalized_outputs(node, class_def)
|
||||
finalized = self._get_finalized_outputs(node_id, node, class_def)
|
||||
if finalized is not None:
|
||||
if slot_idx < 0 or slot_idx >= len(finalized):
|
||||
return ANY_TYPE
|
||||
@@ -232,18 +232,40 @@ class TypeResolver:
|
||||
f"MatchType template '{template_id}' has no bound concrete upstream input; defaulting to AnyType")
|
||||
return ANY_TYPE
|
||||
|
||||
def _get_finalized_outputs(self, node: dict | None, class_def) -> io.FinalizedOutputs | None:
|
||||
"""Return ``FinalizedOutputs`` for V3 nodes with DynamicOutputs groups, else ``None``."""
|
||||
def _get_finalized_outputs(self, node_id: str, node: dict | None, class_def) -> io.FinalizedOutputs | None:
|
||||
"""Return ``FinalizedOutputs`` for V3 nodes with DynamicOutputs groups, else ``None``.
|
||||
|
||||
``FromInput`` placeholders for ``DynamicSlot`` inputs also need
|
||||
``live_input_types`` (computed lazily from the resolver itself) so
|
||||
the active option can be picked by resolved upstream type.
|
||||
"""
|
||||
if not (isinstance(class_def, type) and issubclass(class_def, _ComfyNodeInternal)):
|
||||
return None
|
||||
try:
|
||||
schema = class_def.GET_SCHEMA()
|
||||
except Exception:
|
||||
return None
|
||||
if not any(isinstance(o, io.DynamicOutputs.ByKey) for o in schema.outputs):
|
||||
has_dynamic = any(
|
||||
isinstance(o, (io.DynamicOutputs.ByKey, io.DynamicOutputs.FromInput))
|
||||
for o in schema.outputs
|
||||
)
|
||||
if not has_dynamic:
|
||||
return None
|
||||
prompt_inputs = (node or {}).get("inputs", {}) or {}
|
||||
return io.get_finalized_class_outputs(schema.outputs, prompt_inputs)
|
||||
# live_input_types is only needed for FromInput → DynamicSlot; skip the
|
||||
# resolver pass otherwise to keep the hot path cheap.
|
||||
needs_live_types = any(
|
||||
isinstance(o, io.DynamicOutputs.FromInput)
|
||||
and any(isinstance(i, io.DynamicSlot.Input) and i.id == o.input_id for i in schema.inputs)
|
||||
for o in schema.outputs
|
||||
)
|
||||
live_input_types = self.compute_live_input_types(node_id) if needs_live_types else None
|
||||
return io.get_finalized_class_outputs(
|
||||
schema.outputs,
|
||||
prompt_inputs,
|
||||
schema_inputs=schema.inputs,
|
||||
live_input_types=live_input_types,
|
||||
)
|
||||
|
||||
def finalized_output_count(self, node_id: str) -> int:
|
||||
"""Number of active output slots on ``node_id``'s schema for the current prompt.
|
||||
@@ -256,7 +278,7 @@ class TypeResolver:
|
||||
node, class_def = self._get_class_def_for_node(node_id)
|
||||
if class_def is None:
|
||||
return 0
|
||||
finalized = self._get_finalized_outputs(node, class_def)
|
||||
finalized = self._get_finalized_outputs(node_id, node, class_def)
|
||||
if finalized is not None:
|
||||
return len(finalized)
|
||||
try:
|
||||
@@ -274,7 +296,7 @@ class TypeResolver:
|
||||
result = False
|
||||
node, class_def = self._get_class_def_for_node(node_id)
|
||||
if class_def is not None:
|
||||
finalized = self._get_finalized_outputs(node, class_def)
|
||||
finalized = self._get_finalized_outputs(node_id, node, class_def)
|
||||
if finalized is not None:
|
||||
if 0 <= slot_idx < len(finalized):
|
||||
result = bool(finalized.output_is_list[slot_idx])
|
||||
|
||||
Reference in New Issue
Block a user