mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-01-03 02:30:52 +08:00
* Support Combo outputs in a more sane way * Remove test validate_inputs function on test node * Make curr_prefix be a list of strings instead of string for easier parsing as keys get added to dynamic types * Start to account for id prefixes from frontend, need to fix bug with nested dynamics * Ensure inputs/outputs/hidden are lists in schema finalize function, remove no longer needed 'is not None' checks * Add raw_link and extra_dict to all relevant Inputs * Make nested DynamicCombos work properly with prefixed keys on latest frontend; breaks old Autogrow, but is pretty much ready for upcoming Autogrow keys * Replace ... usage with a MISSING sentinel for clarity in nodes_logic.py * Added CustomCombo node in backend to reflect frontend node * Prepare Autogrow's expand_schema_for_dynamic to work with upcoming frontend changes * Prepare for look up table for dynamic input stuff * More progress towards dynamic input lookup function stuff * Finished converting _expand_schema_for_dynamic to be done via lookup instead of OOP to guarantee working with process isolation, did refactoring to remove old implementation + cleaning INPUT_TYPES definition including v3 hidden definition * Change order of functions * Removed some unneeded functions after dynamic refactor * Make MatchType's output default displayname "MATCHTYPE" * Fix DynamicSlot get_all * Removed redundant code - dynamic stuff no longer happens in OOP way * Natively support AnyType (*) without __ne__ hacks * Remove stray code that made it in * Remove expand_schema_for_dynamic left over on DynamicInput class * get_dynamic() on DynamicInput/Output was not doing anything anymore, so removed it * Make validate_inputs validate combo input correctly * Temporarily comment out conversion to 'new' (9 month old) COMBO format in get_input_info * Remove refrences to resources feature scrapped from V3 * Expose DynamicCombo in public API * satisfy ruff after some code got commented out * Make missing input error prettier for dynamic types * Created a Switch2 node as a side-by-side test, will likely go with Switch2 as the initial switch node * Figured out Switch situation * Pass in v3_data in IsChangedCache.get function's fingerprint_inputs, add a from_v3_data helper method to HiddenHolder * Switch order of Switch and Soft Switch nodes in file * Temp test node for MatchType * Fix missing v3_data for v1 nodes in validation * For now, remove chacking duplicate id's for dynamic types * Add Resize Image/Mask node that thanks to MatchType+DynamicCombo is 16-nodes-in-1 * Made DynamicCombo references in DCTestNode use public interface * Add an AnyTypeTestNode * Make lazy status for specific inputs on DynamicInputs work by having the values of the dictionary for check_lazy_status be a tuple, where the second element is the key of the input that can be returned * Comment out test logic nodes * Make primitive float's step make more sense * Add (and leave commented out) some potential logic nodes * Change default crop option to "center" on Resize Image/Mask node * Changed copy.copy(d) to d.copy() * Autogrow is available in stable frontend, so exposing it in public API * Use outputs id as display_name if no display_name present, remove v3 outputs id restriction that made them have to have unique IDs from the inputs * Enable Custom Combo node as stable frontend now supports it * Make id properly act like display_name on outputs * Add Batch Images/Masks/Latents node * Comment out Batch Images/Masks/Latents node for now, as Autogrow has a bug with MatchType where top connection is disconnected upon refresh * Removed code for a couple test nodes in nodes_logic.py * Add Batch Images, Batch Masks, and Batch Latents nodes with Autogrow, deprecate old Batch Images + LatentBatch nodes
269 lines
9.3 KiB
Python
269 lines
9.3 KiB
Python
from __future__ import annotations
|
|
from typing import TypedDict
|
|
from typing_extensions import override
|
|
from comfy_api.latest import ComfyExtension, io
|
|
from comfy_api.latest import _io
|
|
|
|
# sentinel for missing inputs
|
|
MISSING = object()
|
|
|
|
|
|
class SwitchNode(io.ComfyNode):
|
|
@classmethod
|
|
def define_schema(cls):
|
|
template = io.MatchType.Template("switch")
|
|
return io.Schema(
|
|
node_id="ComfySwitchNode",
|
|
display_name="Switch",
|
|
category="logic",
|
|
is_experimental=True,
|
|
inputs=[
|
|
io.Boolean.Input("switch"),
|
|
io.MatchType.Input("on_false", template=template, lazy=True),
|
|
io.MatchType.Input("on_true", template=template, lazy=True),
|
|
],
|
|
outputs=[
|
|
io.MatchType.Output(template=template, display_name="output"),
|
|
],
|
|
)
|
|
|
|
@classmethod
|
|
def check_lazy_status(cls, switch, on_false=None, on_true=None):
|
|
if switch and on_true is None:
|
|
return ["on_true"]
|
|
if not switch and on_false is None:
|
|
return ["on_false"]
|
|
|
|
@classmethod
|
|
def execute(cls, switch, on_true, on_false) -> io.NodeOutput:
|
|
return io.NodeOutput(on_true if switch else on_false)
|
|
|
|
|
|
class SoftSwitchNode(io.ComfyNode):
|
|
@classmethod
|
|
def define_schema(cls):
|
|
template = io.MatchType.Template("switch")
|
|
return io.Schema(
|
|
node_id="ComfySoftSwitchNode",
|
|
display_name="Soft Switch",
|
|
category="logic",
|
|
is_experimental=True,
|
|
inputs=[
|
|
io.Boolean.Input("switch"),
|
|
io.MatchType.Input("on_false", template=template, lazy=True, optional=True),
|
|
io.MatchType.Input("on_true", template=template, lazy=True, optional=True),
|
|
],
|
|
outputs=[
|
|
io.MatchType.Output(template=template, display_name="output"),
|
|
],
|
|
)
|
|
|
|
@classmethod
|
|
def check_lazy_status(cls, switch, on_false=MISSING, on_true=MISSING):
|
|
# We use MISSING instead of None, as None is passed for connected-but-unevaluated inputs.
|
|
# This trick allows us to ignore the value of the switch and still be able to run execute().
|
|
|
|
# One of the inputs may be missing, in which case we need to evaluate the other input
|
|
if on_false is MISSING:
|
|
return ["on_true"]
|
|
if on_true is MISSING:
|
|
return ["on_false"]
|
|
# Normal lazy switch operation
|
|
if switch and on_true is None:
|
|
return ["on_true"]
|
|
if not switch and on_false is None:
|
|
return ["on_false"]
|
|
|
|
@classmethod
|
|
def validate_inputs(cls, switch, on_false=MISSING, on_true=MISSING):
|
|
# This check happens before check_lazy_status(), so we can eliminate the case where
|
|
# both inputs are missing.
|
|
if on_false is MISSING and on_true is MISSING:
|
|
return "At least one of on_false or on_true must be connected to Switch node"
|
|
return True
|
|
|
|
@classmethod
|
|
def execute(cls, switch, on_true=MISSING, on_false=MISSING) -> io.NodeOutput:
|
|
if on_true is MISSING:
|
|
return io.NodeOutput(on_false)
|
|
if on_false is MISSING:
|
|
return io.NodeOutput(on_true)
|
|
return io.NodeOutput(on_true if switch else on_false)
|
|
|
|
|
|
class CustomComboNode(io.ComfyNode):
|
|
"""
|
|
Frontend node that allows user to write their own options for a combo.
|
|
This is here to make sure the node has a backend-representation to avoid some annoyances.
|
|
"""
|
|
@classmethod
|
|
def define_schema(cls):
|
|
return io.Schema(
|
|
node_id="CustomCombo",
|
|
display_name="Custom Combo",
|
|
category="utils",
|
|
is_experimental=True,
|
|
inputs=[io.Combo.Input("choice", options=[])],
|
|
outputs=[io.String.Output()]
|
|
)
|
|
|
|
@classmethod
|
|
def validate_inputs(cls, choice: io.Combo.Type) -> bool:
|
|
# NOTE: DO NOT DO THIS unless you want to skip validation entirely on the node's inputs.
|
|
# I am doing that here because the widgets (besides the combo dropdown) on this node are fully frontend defined.
|
|
# I need to skip checking that the chosen combo option is in the options list, since those are defined by the user.
|
|
return True
|
|
|
|
@classmethod
|
|
def execute(cls, choice: io.Combo.Type) -> io.NodeOutput:
|
|
return io.NodeOutput(choice)
|
|
|
|
|
|
class DCTestNode(io.ComfyNode):
|
|
class DCValues(TypedDict):
|
|
combo: str
|
|
string: str
|
|
integer: int
|
|
image: io.Image.Type
|
|
subcombo: dict[str]
|
|
|
|
@classmethod
|
|
def define_schema(cls):
|
|
return io.Schema(
|
|
node_id="DCTestNode",
|
|
display_name="DCTest",
|
|
category="logic",
|
|
is_output_node=True,
|
|
inputs=[io.DynamicCombo.Input("combo", options=[
|
|
io.DynamicCombo.Option("option1", [io.String.Input("string")]),
|
|
io.DynamicCombo.Option("option2", [io.Int.Input("integer")]),
|
|
io.DynamicCombo.Option("option3", [io.Image.Input("image")]),
|
|
io.DynamicCombo.Option("option4", [
|
|
io.DynamicCombo.Input("subcombo", options=[
|
|
io.DynamicCombo.Option("opt1", [io.Float.Input("float_x"), io.Float.Input("float_y")]),
|
|
io.DynamicCombo.Option("opt2", [io.Mask.Input("mask1", optional=True)]),
|
|
])
|
|
])]
|
|
)],
|
|
outputs=[io.AnyType.Output()],
|
|
)
|
|
|
|
@classmethod
|
|
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"])
|
|
elif combo_val == "option4":
|
|
return io.NodeOutput(f"{combo['subcombo']}")
|
|
else:
|
|
raise ValueError(f"Invalid combo: {combo_val}")
|
|
|
|
|
|
class AutogrowNamesTestNode(io.ComfyNode):
|
|
@classmethod
|
|
def define_schema(cls):
|
|
template = _io.Autogrow.TemplateNames(input=io.Float.Input("float"), names=["a", "b", "c"])
|
|
return io.Schema(
|
|
node_id="AutogrowNamesTestNode",
|
|
display_name="AutogrowNamesTest",
|
|
category="logic",
|
|
inputs=[
|
|
_io.Autogrow.Input("autogrow", template=template)
|
|
],
|
|
outputs=[io.String.Output()],
|
|
)
|
|
|
|
@classmethod
|
|
def execute(cls, autogrow: _io.Autogrow.Type) -> io.NodeOutput:
|
|
vals = list(autogrow.values())
|
|
combined = ",".join([str(x) for x in vals])
|
|
return io.NodeOutput(combined)
|
|
|
|
class AutogrowPrefixTestNode(io.ComfyNode):
|
|
@classmethod
|
|
def define_schema(cls):
|
|
template = _io.Autogrow.TemplatePrefix(input=io.Float.Input("float"), prefix="float", min=1, max=10)
|
|
return io.Schema(
|
|
node_id="AutogrowPrefixTestNode",
|
|
display_name="AutogrowPrefixTest",
|
|
category="logic",
|
|
inputs=[
|
|
_io.Autogrow.Input("autogrow", template=template)
|
|
],
|
|
outputs=[io.String.Output()],
|
|
)
|
|
|
|
@classmethod
|
|
def execute(cls, autogrow: _io.Autogrow.Type) -> io.NodeOutput:
|
|
vals = list(autogrow.values())
|
|
combined = ",".join([str(x) for x in vals])
|
|
return io.NodeOutput(combined)
|
|
|
|
class ComboOutputTestNode(io.ComfyNode):
|
|
@classmethod
|
|
def define_schema(cls):
|
|
return io.Schema(
|
|
node_id="ComboOptionTestNode",
|
|
display_name="ComboOptionTest",
|
|
category="logic",
|
|
inputs=[io.Combo.Input("combo", options=["option1", "option2", "option3"]),
|
|
io.Combo.Input("combo2", options=["option4", "option5", "option6"])],
|
|
outputs=[io.Combo.Output(), io.Combo.Output()],
|
|
)
|
|
|
|
@classmethod
|
|
def execute(cls, combo: io.Combo.Type, combo2: io.Combo.Type) -> io.NodeOutput:
|
|
return io.NodeOutput(combo, combo2)
|
|
|
|
class ConvertStringToComboNode(io.ComfyNode):
|
|
@classmethod
|
|
def define_schema(cls):
|
|
return io.Schema(
|
|
node_id="ConvertStringToComboNode",
|
|
display_name="Convert String to Combo",
|
|
category="logic",
|
|
inputs=[io.String.Input("string")],
|
|
outputs=[io.Combo.Output()],
|
|
)
|
|
|
|
@classmethod
|
|
def execute(cls, string: str) -> io.NodeOutput:
|
|
return io.NodeOutput(string)
|
|
|
|
class InvertBooleanNode(io.ComfyNode):
|
|
@classmethod
|
|
def define_schema(cls):
|
|
return io.Schema(
|
|
node_id="InvertBooleanNode",
|
|
display_name="Invert Boolean",
|
|
category="logic",
|
|
inputs=[io.Boolean.Input("boolean")],
|
|
outputs=[io.Boolean.Output()],
|
|
)
|
|
|
|
@classmethod
|
|
def execute(cls, boolean: bool) -> io.NodeOutput:
|
|
return io.NodeOutput(not boolean)
|
|
|
|
class LogicExtension(ComfyExtension):
|
|
@override
|
|
async def get_node_list(self) -> list[type[io.ComfyNode]]:
|
|
return [
|
|
SwitchNode,
|
|
CustomComboNode,
|
|
# SoftSwitchNode,
|
|
# ConvertStringToComboNode,
|
|
# DCTestNode,
|
|
# AutogrowNamesTestNode,
|
|
# AutogrowPrefixTestNode,
|
|
# ComboOutputTestNode,
|
|
# InvertBooleanNode,
|
|
]
|
|
|
|
async def comfy_entrypoint() -> LogicExtension:
|
|
return LogicExtension()
|