mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2025-07-27 16:26:39 +00:00
Refactored v3 code so that v3_01 becomes v3, v3_01 is deleted since no longer necessary
This commit is contained in:
parent
38721fdb64
commit
f9aec12ef1
@ -1,20 +1,13 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from typing import Any, Literal, TYPE_CHECKING, TypeVar
|
from typing import Any, Literal, TYPE_CHECKING, TypeVar, Callable, Optional, cast
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from dataclasses import dataclass, asdict
|
from dataclasses import dataclass, asdict
|
||||||
from comfy.comfy_types.node_typing import IO
|
from comfy.comfy_types.node_typing import IO
|
||||||
|
|
||||||
# if TYPE_CHECKING:
|
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
|
|
||||||
class InputBehavior(str, Enum):
|
|
||||||
'''Likely deprecated; required/optional can be a bool, unlikely to be more categories that fit.'''
|
|
||||||
required = "required"
|
|
||||||
optional = "optional"
|
|
||||||
|
|
||||||
|
|
||||||
class FolderType(str, Enum):
|
class FolderType(str, Enum):
|
||||||
input = "input"
|
input = "input"
|
||||||
output = "output"
|
output = "output"
|
||||||
@ -55,26 +48,111 @@ def is_class(obj):
|
|||||||
return isinstance(obj, type)
|
return isinstance(obj, type)
|
||||||
|
|
||||||
|
|
||||||
|
def copy_class(cls: type) -> type:
|
||||||
|
'''
|
||||||
|
Copy a class and its attributes.
|
||||||
|
'''
|
||||||
|
if cls is None:
|
||||||
|
return None
|
||||||
|
cls_dict = {
|
||||||
|
k: v for k, v in cls.__dict__.items()
|
||||||
|
if k not in ('__dict__', '__weakref__', '__module__', '__doc__')
|
||||||
|
}
|
||||||
|
# new class
|
||||||
|
new_cls = type(
|
||||||
|
cls.__name__,
|
||||||
|
(cls,),
|
||||||
|
cls_dict
|
||||||
|
)
|
||||||
|
# metadata preservation
|
||||||
|
new_cls.__module__ = cls.__module__
|
||||||
|
new_cls.__doc__ = cls.__doc__
|
||||||
|
return new_cls
|
||||||
|
|
||||||
|
|
||||||
class NumberDisplay(str, Enum):
|
class NumberDisplay(str, Enum):
|
||||||
number = "number"
|
number = "number"
|
||||||
slider = "slider"
|
slider = "slider"
|
||||||
|
|
||||||
|
|
||||||
|
class ComfyType:
|
||||||
|
Type = Any
|
||||||
|
io_type: str = None
|
||||||
|
Input: type[InputV3] = None
|
||||||
|
Output: type[OutputV3] = None
|
||||||
|
|
||||||
|
# NOTE: this is a workaround to make the decorator return the correct type
|
||||||
|
T = TypeVar("T", bound=type)
|
||||||
|
def comfytype(io_type: str, **kwargs):
|
||||||
|
'''
|
||||||
|
Decorator to mark nested classes as ComfyType; io_type will be bound to the class.
|
||||||
|
|
||||||
|
A ComfyType may have the following attributes:
|
||||||
|
- Type = <type hint here>
|
||||||
|
- class Input(InputV3): ...
|
||||||
|
- class Output(OutputV3): ...
|
||||||
|
'''
|
||||||
|
def decorator(cls: T) -> T:
|
||||||
|
if isinstance(cls, ComfyType) or issubclass(cls, ComfyType):
|
||||||
|
# clone Input and Output classes to avoid modifying the original class
|
||||||
|
new_cls = cls
|
||||||
|
new_cls.Input = copy_class(new_cls.Input)
|
||||||
|
new_cls.Output = copy_class(new_cls.Output)
|
||||||
|
else:
|
||||||
|
# copy class attributes except for special ones that shouldn't be in type()
|
||||||
|
cls_dict = {
|
||||||
|
k: v for k, v in cls.__dict__.items()
|
||||||
|
if k not in ('__dict__', '__weakref__', '__module__', '__doc__')
|
||||||
|
}
|
||||||
|
# new class
|
||||||
|
new_cls: ComfyType = type(
|
||||||
|
cls.__name__,
|
||||||
|
(cls, ComfyType),
|
||||||
|
cls_dict
|
||||||
|
)
|
||||||
|
# metadata preservation
|
||||||
|
new_cls.__module__ = cls.__module__
|
||||||
|
new_cls.__doc__ = cls.__doc__
|
||||||
|
# assign ComfyType attributes, if needed
|
||||||
|
# NOTE: do we need __ne__ trick for io_type? (see IO.__ne__ for details)
|
||||||
|
new_cls.io_type = io_type
|
||||||
|
if new_cls.Input is not None:
|
||||||
|
new_cls.Input.Parent = new_cls
|
||||||
|
if new_cls.Output is not None:
|
||||||
|
new_cls.Output.Parent = new_cls
|
||||||
|
return new_cls
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
def Custom(io_type: IO | str) -> type[ComfyType]:
|
||||||
|
'''Create a ComfyType for a custom io_type.'''
|
||||||
|
@comfytype(io_type=io_type)
|
||||||
|
class CustomComfyType(ComfyTypeIO):
|
||||||
|
...
|
||||||
|
return CustomComfyType
|
||||||
|
|
||||||
class IO_V3:
|
class IO_V3:
|
||||||
'''
|
'''
|
||||||
Base class for V3 Inputs and Outputs.
|
Base class for V3 Inputs and Outputs.
|
||||||
'''
|
'''
|
||||||
Type = Any
|
Parent: ComfyType = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def __init_subclass__(cls, io_type: IO | str, **kwargs):
|
# def __init_subclass__(cls, io_type: IO | str, **kwargs):
|
||||||
# TODO: do we need __ne__ trick for io_type? (see IO.__ne__ for details)
|
# # TODO: do we need __ne__ trick for io_type? (see IO.__ne__ for details)
|
||||||
cls.io_type = io_type
|
# cls.io_type = io_type
|
||||||
super().__init_subclass__(**kwargs)
|
# super().__init_subclass__(**kwargs)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def io_type(self):
|
||||||
|
return self.Parent.io_type
|
||||||
|
|
||||||
class InputV3(IO_V3, io_type=None):
|
@property
|
||||||
|
def Type(self):
|
||||||
|
return self.Parent.Type
|
||||||
|
|
||||||
|
class InputV3(IO_V3):
|
||||||
'''
|
'''
|
||||||
Base class for a V3 Input.
|
Base class for a V3 Input.
|
||||||
'''
|
'''
|
||||||
@ -96,7 +174,7 @@ class InputV3(IO_V3, io_type=None):
|
|||||||
def get_io_type_V1(self):
|
def get_io_type_V1(self):
|
||||||
return self.io_type
|
return self.io_type
|
||||||
|
|
||||||
class WidgetInputV3(InputV3, io_type=None):
|
class WidgetInputV3(InputV3):
|
||||||
'''
|
'''
|
||||||
Base class for a V3 Input with widget.
|
Base class for a V3 Input with widget.
|
||||||
'''
|
'''
|
||||||
@ -115,7 +193,7 @@ class WidgetInputV3(InputV3, io_type=None):
|
|||||||
"widgetType": self.widgetType,
|
"widgetType": self.widgetType,
|
||||||
})
|
})
|
||||||
|
|
||||||
class OutputV3(IO_V3, io_type=None):
|
class OutputV3(IO_V3):
|
||||||
def __init__(self, id: str, display_name: str=None, tooltip: str=None,
|
def __init__(self, id: str, display_name: str=None, tooltip: str=None,
|
||||||
is_output_list=False):
|
is_output_list=False):
|
||||||
self.id = id
|
self.id = id
|
||||||
@ -123,414 +201,334 @@ class OutputV3(IO_V3, io_type=None):
|
|||||||
self.tooltip = tooltip
|
self.tooltip = tooltip
|
||||||
self.is_output_list = is_output_list
|
self.is_output_list = is_output_list
|
||||||
|
|
||||||
def CustomType(io_type: IO | str) -> type[IO_V3]:
|
|
||||||
name = f"{io_type}_IO_V3"
|
|
||||||
return type(name, (IO_V3,), {}, io_type=io_type)
|
|
||||||
|
|
||||||
def CustomInput(id: str, io_type: IO | str, display_name: str=None, optional=False, tooltip: str=None, lazy: bool=None) -> InputV3:
|
class ComfyTypeIO(ComfyType):
|
||||||
'''
|
'''ComfyType subclass that has default Input and Output classes; useful for basic Inputs and Outputs.'''
|
||||||
Defines input for 'io_type'. Can be used to stand in for non-core types.
|
class Input(InputV3):
|
||||||
'''
|
...
|
||||||
input_kwargs = {
|
class Output(OutputV3):
|
||||||
"id": id,
|
...
|
||||||
"display_name": display_name,
|
|
||||||
"optional": optional,
|
|
||||||
"tooltip": tooltip,
|
|
||||||
"lazy": lazy,
|
|
||||||
}
|
|
||||||
return type(f"{io_type}Input", (InputV3,), {}, io_type=io_type)(**input_kwargs)
|
|
||||||
|
|
||||||
def CustomOutput(id: str, io_type: IO | str, display_name: str=None, tooltip: str=None) -> OutputV3:
|
|
||||||
'''
|
|
||||||
Defines output for 'io_type'. Can be used to stand in for non-core types.
|
|
||||||
'''
|
|
||||||
input_kwargs = {
|
|
||||||
"id": id,
|
|
||||||
"display_name": display_name,
|
|
||||||
"tooltip": tooltip,
|
|
||||||
}
|
|
||||||
return type(f"{io_type}Output", (OutputV3,), {}, io_type=io_type)(**input_kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class BooleanInput(WidgetInputV3, io_type=IO.BOOLEAN):
|
class NodeState:
|
||||||
'''
|
def __init__(self, node_id: str):
|
||||||
Boolean input.
|
self.node_id = node_id
|
||||||
'''
|
|
||||||
|
|
||||||
|
class NodeStateLocal(NodeState):
|
||||||
|
def __init__(self, node_id: str):
|
||||||
|
super().__init__(node_id)
|
||||||
|
self.local_state = {}
|
||||||
|
|
||||||
|
def __getattr__(self, key: str):
|
||||||
|
local_state = type(self).__getattribute__(self, "local_state")
|
||||||
|
if key in local_state:
|
||||||
|
return local_state[key]
|
||||||
|
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{key}'")
|
||||||
|
|
||||||
|
def __setattr__(self, key: str, value: Any):
|
||||||
|
if key in ['node_id', 'local_state']:
|
||||||
|
super().__setattr__(key, value)
|
||||||
|
else:
|
||||||
|
self.local_state[key] = value
|
||||||
|
|
||||||
|
|
||||||
|
@comfytype(io_type=IO.BOOLEAN)
|
||||||
|
class Boolean:
|
||||||
Type = bool
|
Type = bool
|
||||||
def __init__(self, id: str, display_name: str=None, optional=False, tooltip: str=None, lazy: bool=None,
|
|
||||||
default: bool=None, label_on: str=None, label_off: str=None,
|
|
||||||
socketless: bool=None, widgetType: str=None):
|
|
||||||
super().__init__(id, display_name, optional, tooltip, lazy, default, socketless, widgetType)
|
|
||||||
self.label_on = label_on
|
|
||||||
self.label_off = label_off
|
|
||||||
self.default: bool
|
|
||||||
|
|
||||||
def as_dict_V1(self):
|
class Input(WidgetInputV3):
|
||||||
return super().as_dict_V1() | prune_dict({
|
'''Boolean input.'''
|
||||||
"label_on": self.label_on,
|
def __init__(self, id: str, display_name: str=None, optional=False, tooltip: str=None, lazy: bool=None,
|
||||||
"label_off": self.label_off,
|
default: bool=None, label_on: str=None, label_off: str=None,
|
||||||
})
|
socketless: bool=None, widgetType: str=None):
|
||||||
|
super().__init__(id, display_name, optional, tooltip, lazy, default, socketless, widgetType)
|
||||||
|
self.label_on = label_on
|
||||||
|
self.label_off = label_off
|
||||||
|
self.default: bool
|
||||||
|
|
||||||
|
def as_dict_V1(self):
|
||||||
|
return super().as_dict_V1() | prune_dict({
|
||||||
|
"label_on": self.label_on,
|
||||||
|
"label_off": self.label_off,
|
||||||
|
})
|
||||||
|
|
||||||
class BooleanOutput(OutputV3, io_type=IO.BOOLEAN):
|
class Output(OutputV3):
|
||||||
...
|
...
|
||||||
|
|
||||||
class IntegerInput(WidgetInputV3, io_type=IO.INT):
|
@comfytype(io_type=IO.INT)
|
||||||
'''
|
class Int:
|
||||||
Integer input.
|
|
||||||
'''
|
|
||||||
Type = int
|
Type = int
|
||||||
def __init__(self, id: str, display_name: str=None, optional=False, tooltip: str=None, lazy: bool=None,
|
|
||||||
default: int=None, min: int=None, max: int=None, step: int=None, control_after_generate: bool=None,
|
|
||||||
display_mode: NumberDisplay=None, socketless: bool=None, widgetType: str=None):
|
|
||||||
super().__init__(id, display_name, optional, tooltip, lazy, default, socketless, widgetType)
|
|
||||||
self.min = min
|
|
||||||
self.max = max
|
|
||||||
self.step = step
|
|
||||||
self.control_after_generate = control_after_generate
|
|
||||||
self.display_mode = display_mode
|
|
||||||
self.default: int
|
|
||||||
|
|
||||||
def as_dict_V1(self):
|
class Input(WidgetInputV3):
|
||||||
return super().as_dict_V1() | prune_dict({
|
'''Integer input.'''
|
||||||
"min": self.min,
|
def __init__(self, id: str, display_name: str=None, optional=False, tooltip: str=None, lazy: bool=None,
|
||||||
"max": self.max,
|
default: int=None, min: int=None, max: int=None, step: int=None, control_after_generate: bool=None,
|
||||||
"step": self.step,
|
display_mode: NumberDisplay=None, socketless: bool=None, widgetType: str=None):
|
||||||
"control_after_generate": self.control_after_generate,
|
super().__init__(id, display_name, optional, tooltip, lazy, default, socketless, widgetType)
|
||||||
"display": self.display_mode, # NOTE: in frontend, the parameter is called "display"
|
self.min = min
|
||||||
})
|
self.max = max
|
||||||
|
self.step = step
|
||||||
|
self.control_after_generate = control_after_generate
|
||||||
|
self.display_mode = display_mode
|
||||||
|
self.default: int
|
||||||
|
|
||||||
|
def as_dict_V1(self):
|
||||||
|
return super().as_dict_V1() | prune_dict({
|
||||||
|
"min": self.min,
|
||||||
|
"max": self.max,
|
||||||
|
"step": self.step,
|
||||||
|
"control_after_generate": self.control_after_generate,
|
||||||
|
"display": self.display_mode,
|
||||||
|
})
|
||||||
|
|
||||||
class IntegerOutput(OutputV3, io_type=IO.INT):
|
class Output(OutputV3):
|
||||||
...
|
...
|
||||||
|
|
||||||
class FloatInput(WidgetInputV3, io_type=IO.FLOAT):
|
@comfytype(io_type=IO.FLOAT)
|
||||||
'''
|
class Float:
|
||||||
Float input.
|
|
||||||
'''
|
|
||||||
Type = float
|
Type = float
|
||||||
def __init__(self, id: str, display_name: str=None, optional=False, tooltip: str=None, lazy: bool=None,
|
|
||||||
default: float=None, min: float=None, max: float=None, step: float=None, round: float=None,
|
|
||||||
display_mode: NumberDisplay=None, socketless: bool=None, widgetType: str=None):
|
|
||||||
super().__init__(id, display_name, optional, tooltip, lazy, default, socketless, widgetType)
|
|
||||||
self.default = default
|
|
||||||
self.min = min
|
|
||||||
self.max = max
|
|
||||||
self.step = step
|
|
||||||
self.round = round
|
|
||||||
self.display_mode = display_mode
|
|
||||||
self.default: float
|
|
||||||
|
|
||||||
def as_dict_V1(self):
|
class Input(WidgetInputV3):
|
||||||
return super().as_dict_V1() | prune_dict({
|
'''Float input.'''
|
||||||
"min": self.min,
|
def __init__(self, id: str, display_name: str=None, optional=False, tooltip: str=None, lazy: bool=None,
|
||||||
"max": self.max,
|
default: float=None, min: float=None, max: float=None, step: float=None, round: float=None,
|
||||||
"step": self.step,
|
display_mode: NumberDisplay=None, socketless: bool=None, widgetType: str=None):
|
||||||
"round": self.round,
|
super().__init__(id, display_name, optional, tooltip, lazy, default, socketless, widgetType)
|
||||||
"display": self.display_mode, # NOTE: in frontend, the parameter is called "display"
|
self.default = default
|
||||||
})
|
self.min = min
|
||||||
|
self.max = max
|
||||||
|
self.step = step
|
||||||
|
self.round = round
|
||||||
|
self.display_mode = display_mode
|
||||||
|
self.default: float
|
||||||
|
|
||||||
|
def as_dict_V1(self):
|
||||||
|
return super().as_dict_V1() | prune_dict({
|
||||||
|
"min": self.min,
|
||||||
|
"max": self.max,
|
||||||
|
"step": self.step,
|
||||||
|
"round": self.round,
|
||||||
|
"display": self.display_mode,
|
||||||
|
})
|
||||||
|
|
||||||
class FloatOutput(OutputV3, io_type=IO.FLOAT):
|
class Output(OutputV3):
|
||||||
...
|
...
|
||||||
|
|
||||||
class StringInput(WidgetInputV3, io_type=IO.STRING):
|
@comfytype(io_type=IO.STRING)
|
||||||
'''
|
class String:
|
||||||
String input.
|
|
||||||
'''
|
|
||||||
Type = str
|
Type = str
|
||||||
def __init__(self, id: str, display_name: str=None, optional=False, tooltip: str=None, lazy: bool=None,
|
|
||||||
multiline=False, placeholder: str=None, default: int=None,
|
|
||||||
socketless: bool=None, widgetType: str=None):
|
|
||||||
super().__init__(id, display_name, optional, tooltip, lazy, default, socketless, widgetType)
|
|
||||||
self.multiline = multiline
|
|
||||||
self.placeholder = placeholder
|
|
||||||
self.default: str
|
|
||||||
|
|
||||||
def as_dict_V1(self):
|
class Input(WidgetInputV3):
|
||||||
return super().as_dict_V1() | prune_dict({
|
'''String input.'''
|
||||||
"multiline": self.multiline,
|
def __init__(self, id: str, display_name: str=None, optional=False, tooltip: str=None, lazy: bool=None,
|
||||||
"placeholder": self.placeholder,
|
multiline=False, placeholder: str=None, default: int=None,
|
||||||
})
|
socketless: bool=None, widgetType: str=None):
|
||||||
|
super().__init__(id, display_name, optional, tooltip, lazy, default, socketless, widgetType)
|
||||||
|
self.multiline = multiline
|
||||||
|
self.placeholder = placeholder
|
||||||
|
self.default: str
|
||||||
|
|
||||||
|
def as_dict_V1(self):
|
||||||
|
return super().as_dict_V1() | prune_dict({
|
||||||
|
"multiline": self.multiline,
|
||||||
|
"placeholder": self.placeholder,
|
||||||
|
})
|
||||||
|
|
||||||
class StringOutput(OutputV3, io_type=IO.STRING):
|
class Output(OutputV3):
|
||||||
...
|
...
|
||||||
|
|
||||||
class ComboInput(WidgetInputV3, io_type=IO.COMBO):
|
@comfytype(io_type=IO.COMBO)
|
||||||
'''Combo input (dropdown).'''
|
class Combo:
|
||||||
Type = str
|
Type = str
|
||||||
def __init__(self, id: str, options: list[str]=None, display_name: str=None, optional=False, tooltip: str=None, lazy: bool=None,
|
class Input(WidgetInputV3):
|
||||||
default: str=None, control_after_generate: bool=None,
|
'''Combo input (dropdown).'''
|
||||||
image_upload: bool=None, image_folder: FolderType=None,
|
Type = str
|
||||||
remote: RemoteOptions=None,
|
def __init__(self, id: str, options: list[str]=None, display_name: str=None, optional=False, tooltip: str=None, lazy: bool=None,
|
||||||
socketless: bool=None, widgetType: str=None):
|
default: str=None, control_after_generate: bool=None,
|
||||||
super().__init__(id, display_name, optional, tooltip, lazy, default, socketless, widgetType)
|
image_upload: bool=None, image_folder: FolderType=None,
|
||||||
self.multiselect = False
|
remote: RemoteOptions=None,
|
||||||
self.options = options
|
socketless: bool=None, widgetType: str=None):
|
||||||
self.control_after_generate = control_after_generate
|
super().__init__(id, display_name, optional, tooltip, lazy, default, socketless, widgetType)
|
||||||
self.image_upload = image_upload
|
self.multiselect = False
|
||||||
self.image_folder = image_folder
|
self.options = options
|
||||||
self.remote = remote
|
self.control_after_generate = control_after_generate
|
||||||
self.default: str
|
self.image_upload = image_upload
|
||||||
|
self.image_folder = image_folder
|
||||||
def as_dict_V1(self):
|
self.remote = remote
|
||||||
return super().as_dict_V1() | prune_dict({
|
self.default: str
|
||||||
"multiselect": self.multiselect,
|
|
||||||
"options": self.options,
|
def as_dict_V1(self):
|
||||||
"control_after_generate": self.control_after_generate,
|
return super().as_dict_V1() | prune_dict({
|
||||||
"image_upload": self.image_upload,
|
"multiselect": self.multiselect,
|
||||||
"image_folder": self.image_folder.value if self.image_folder else None,
|
"options": self.options,
|
||||||
"remote": self.remote.as_dict() if self.remote else None,
|
"control_after_generate": self.control_after_generate,
|
||||||
})
|
"image_upload": self.image_upload,
|
||||||
|
"image_folder": self.image_folder.value if self.image_folder else None,
|
||||||
|
"remote": self.remote.as_dict() if self.remote else None,
|
||||||
|
})
|
||||||
|
|
||||||
class MultiselectComboWidget(ComboInput, io_type=IO.COMBO):
|
@comfytype(io_type=IO.COMBO)
|
||||||
|
class MultiCombo:
|
||||||
'''Multiselect Combo input (dropdown for selecting potentially more than one value).'''
|
'''Multiselect Combo input (dropdown for selecting potentially more than one value).'''
|
||||||
def __init__(self, id: str, options: list[str], display_name: str=None, optional=False, tooltip: str=None, lazy: bool=None,
|
Type = list[str]
|
||||||
default: list[str]=None, placeholder: str=None, chip: bool=None, control_after_generate: bool=None,
|
class Input(Combo.Input):
|
||||||
socketless: bool=None, widgetType: str=None):
|
def __init__(self, id: str, options: list[str], display_name: str=None, optional=False, tooltip: str=None, lazy: bool=None,
|
||||||
super().__init__(id, options, display_name, optional, tooltip, lazy, default, control_after_generate, socketless, widgetType)
|
default: list[str]=None, placeholder: str=None, chip: bool=None, control_after_generate: bool=None,
|
||||||
self.multiselect = True
|
socketless: bool=None, widgetType: str=None):
|
||||||
self.placeholder = placeholder
|
super().__init__(id, options, display_name, optional, tooltip, lazy, default, control_after_generate, socketless, widgetType)
|
||||||
self.chip = chip
|
self.multiselect = True
|
||||||
self.default: list[str]
|
self.placeholder = placeholder
|
||||||
|
self.chip = chip
|
||||||
def as_dict_V1(self):
|
self.default: list[str]
|
||||||
return super().as_dict_V1() | prune_dict({
|
|
||||||
"multiselect": self.multiselect,
|
def as_dict_V1(self):
|
||||||
"placeholder": self.placeholder,
|
return super().as_dict_V1() | prune_dict({
|
||||||
"chip": self.chip,
|
"multiselect": self.multiselect,
|
||||||
})
|
"placeholder": self.placeholder,
|
||||||
|
"chip": self.chip,
|
||||||
|
})
|
||||||
|
|
||||||
class ImageInput(InputV3, io_type=IO.IMAGE):
|
|
||||||
'''Image input.'''
|
@comfytype(io_type=IO.IMAGE)
|
||||||
|
class Image(ComfyTypeIO):
|
||||||
Type = torch.Tensor
|
Type = torch.Tensor
|
||||||
|
|
||||||
class ImageOutput(OutputV3, io_type=IO.IMAGE):
|
@comfytype(io_type=IO.MASK)
|
||||||
'''Image output.'''
|
class Mask(ComfyTypeIO):
|
||||||
Type = torch.Tensor
|
Type = torch.Tensor
|
||||||
|
|
||||||
class MaskInput(InputV3, io_type=IO.MASK):
|
@comfytype(io_type=IO.LATENT)
|
||||||
'''Mask input.'''
|
class Latent(ComfyTypeIO):
|
||||||
Type = torch.Tensor
|
Type = Any # TODO: make Type a TypedDict
|
||||||
|
|
||||||
class MaskOutput(OutputV3, io_type=IO.MASK):
|
@comfytype(io_type=IO.CONDITIONING)
|
||||||
'''Mask output.'''
|
class Conditioning(ComfyTypeIO):
|
||||||
Type = torch.Tensor
|
Type = Any
|
||||||
|
|
||||||
class LatentInput(InputV3, io_type=IO.LATENT):
|
@comfytype(io_type=IO.SAMPLER)
|
||||||
'''Latent input.'''
|
class Sampler(ComfyTypeIO):
|
||||||
# TODO: make Type a TypedDict
|
Type = Any
|
||||||
...
|
|
||||||
|
|
||||||
class LatentOutput(OutputV3, io_type=IO.LATENT):
|
@comfytype(io_type=IO.SIGMAS)
|
||||||
'''Latent output.'''
|
class Sigmas(ComfyTypeIO):
|
||||||
# TODO: make Type a TypedDict
|
Type = Any
|
||||||
...
|
|
||||||
|
|
||||||
class ConditioningInput(InputV3, io_type=IO.CONDITIONING):
|
@comfytype(io_type=IO.NOISE)
|
||||||
'''Conditioning input.'''
|
class Noise(ComfyTypeIO):
|
||||||
# TODO: make Type a TypedDict
|
Type = Any
|
||||||
...
|
|
||||||
|
|
||||||
class ConditioningOutput(OutputV3, io_type=IO.CONDITIONING):
|
@comfytype(io_type=IO.GUIDER)
|
||||||
'''Conditioning output.'''
|
class Guider(ComfyTypeIO):
|
||||||
# TODO: make Type a TypedDict
|
Type = Any
|
||||||
...
|
|
||||||
|
|
||||||
class SamplerInput(InputV3, io_type=IO.SAMPLER):
|
@comfytype(io_type=IO.CLIP)
|
||||||
'''Sampler input.'''
|
class Clip(ComfyTypeIO):
|
||||||
...
|
Type = Any
|
||||||
|
|
||||||
class SamplerOutput(OutputV3, io_type=IO.SAMPLER):
|
@comfytype(io_type=IO.CONTROL_NET)
|
||||||
'''Sampler output.'''
|
class ControlNet(ComfyTypeIO):
|
||||||
...
|
Type = Any
|
||||||
|
|
||||||
class SigmasInput(InputV3, io_type=IO.SIGMAS):
|
@comfytype(io_type=IO.VAE)
|
||||||
'''Sigmas input.'''
|
class Vae(ComfyTypeIO):
|
||||||
...
|
Type = Any
|
||||||
|
|
||||||
class SigmasOutput(OutputV3, io_type=IO.SIGMAS):
|
@comfytype(io_type=IO.MODEL)
|
||||||
'''Sigmas output.'''
|
class Model(ComfyTypeIO):
|
||||||
...
|
Type = Any
|
||||||
|
|
||||||
class GuiderInput(InputV3, io_type=IO.GUIDER):
|
@comfytype(io_type=IO.CLIP_VISION)
|
||||||
'''Guider input.'''
|
class ClipVision(ComfyTypeIO):
|
||||||
...
|
Type = Any
|
||||||
|
|
||||||
class GuiderOutput(OutputV3, io_type=IO.GUIDER):
|
@comfytype(io_type=IO.CLIP_VISION_OUTPUT)
|
||||||
'''Guider output.'''
|
class ClipVisionOutput(ComfyTypeIO):
|
||||||
...
|
Type = Any
|
||||||
|
|
||||||
class NoiseInput(InputV3, io_type=IO.NOISE):
|
@comfytype(io_type=IO.STYLE_MODEL)
|
||||||
'''Noise input.'''
|
class StyleModel(ComfyTypeIO):
|
||||||
...
|
Type = Any
|
||||||
|
|
||||||
class NoiseOutput(OutputV3, io_type=IO.NOISE):
|
@comfytype(io_type=IO.GLIGEN)
|
||||||
'''Noise output.'''
|
class Gligen(ComfyTypeIO):
|
||||||
...
|
Type = Any
|
||||||
|
|
||||||
class ClipInput(InputV3, io_type=IO.CLIP):
|
@comfytype(io_type=IO.UPSCALE_MODEL)
|
||||||
'''Clip input.'''
|
class UpscaleModel(ComfyTypeIO):
|
||||||
...
|
Type = Any
|
||||||
|
|
||||||
class ClipOutput(OutputV3, io_type=IO.CLIP):
|
@comfytype(io_type=IO.AUDIO)
|
||||||
'''Clip output.'''
|
class Audio(ComfyTypeIO):
|
||||||
...
|
Type = Any
|
||||||
|
|
||||||
class ControlNetInput(InputV3, io_type=IO.CONTROL_NET):
|
@comfytype(io_type=IO.POINT)
|
||||||
'''ControlNet input.'''
|
class Point(ComfyTypeIO):
|
||||||
...
|
Type = Any
|
||||||
|
|
||||||
class ControlNetOutput(OutputV3, io_type=IO.CONTROL_NET):
|
@comfytype(io_type=IO.FACE_ANALYSIS)
|
||||||
'''ControlNet output.'''
|
class FaceAnalysis(ComfyTypeIO):
|
||||||
...
|
Type = Any
|
||||||
|
|
||||||
class VaeInput(InputV3, io_type=IO.VAE):
|
@comfytype(io_type=IO.BBOX)
|
||||||
'''Vae input.'''
|
class BBOX(ComfyTypeIO):
|
||||||
...
|
Type = Any
|
||||||
|
|
||||||
class VaeOutput(OutputV3, io_type=IO.VAE):
|
@comfytype(io_type=IO.SEGS)
|
||||||
'''Vae output.'''
|
class SEGS(ComfyTypeIO):
|
||||||
...
|
Type = Any
|
||||||
|
|
||||||
class ModelInput(InputV3, io_type=IO.MODEL):
|
@comfytype(io_type=IO.VIDEO)
|
||||||
'''Model input.'''
|
class Video(ComfyTypeIO):
|
||||||
...
|
Type = Any
|
||||||
|
|
||||||
class ModelOutput(OutputV3, io_type=IO.MODEL):
|
@comfytype(io_type="COMFY_MULTITYPED_V3")
|
||||||
'''Model output.'''
|
class MultiType:
|
||||||
...
|
Type = Any
|
||||||
|
class Input(InputV3):
|
||||||
class ClipVisionInput(InputV3, io_type=IO.CLIP_VISION):
|
|
||||||
'''ClipVision input.'''
|
|
||||||
...
|
|
||||||
|
|
||||||
class ClipVisionOutput(OutputV3, io_type=IO.CLIP_VISION):
|
|
||||||
'''ClipVision output.'''
|
|
||||||
...
|
|
||||||
|
|
||||||
class ClipVisionOutputInput(InputV3, io_type=IO.CLIP_VISION_OUTPUT):
|
|
||||||
'''CLipVisionOutput input.'''
|
|
||||||
...
|
|
||||||
|
|
||||||
class ClipVisionOutputOutput(OutputV3, io_type=IO.CLIP_VISION_OUTPUT):
|
|
||||||
'''CLipVisionOutput output.'''
|
|
||||||
...
|
|
||||||
|
|
||||||
class StyleModelInput(InputV3, io_type=IO.STYLE_MODEL):
|
|
||||||
'''StyleModel input.'''
|
|
||||||
...
|
|
||||||
|
|
||||||
class StyleModelOutput(OutputV3, io_type=IO.STYLE_MODEL):
|
|
||||||
'''StyleModel output.'''
|
|
||||||
...
|
|
||||||
|
|
||||||
class GligenInput(InputV3, io_type=IO.GLIGEN):
|
|
||||||
'''Gligen input.'''
|
|
||||||
...
|
|
||||||
|
|
||||||
class GligenOutput(OutputV3, io_type=IO.GLIGEN):
|
|
||||||
'''Gligen output.'''
|
|
||||||
...
|
|
||||||
|
|
||||||
class UpscaleModelInput(InputV3, io_type=IO.UPSCALE_MODEL):
|
|
||||||
'''UpscaleModel input.'''
|
|
||||||
...
|
|
||||||
|
|
||||||
class UpscaleModelOutput(OutputV3, io_type=IO.UPSCALE_MODEL):
|
|
||||||
'''UpscaleModel output.'''
|
|
||||||
...
|
|
||||||
|
|
||||||
class AudioInput(InputV3, io_type=IO.AUDIO):
|
|
||||||
'''Audio input.'''
|
|
||||||
...
|
|
||||||
|
|
||||||
class AudioOutput(OutputV3, io_type=IO.AUDIO):
|
|
||||||
'''Audio output.'''
|
|
||||||
...
|
|
||||||
|
|
||||||
class PointInput(InputV3, io_type=IO.POINT):
|
|
||||||
'''Point input.'''
|
|
||||||
...
|
|
||||||
|
|
||||||
class PointOutput(OutputV3, io_type=IO.POINT):
|
|
||||||
'''Point output.'''
|
|
||||||
...
|
|
||||||
|
|
||||||
class FaceAnalysisInput(InputV3, io_type=IO.FACE_ANALYSIS):
|
|
||||||
'''FaceAnalysis input.'''
|
|
||||||
...
|
|
||||||
|
|
||||||
class FaceAnalysisOutput(OutputV3, io_type=IO.FACE_ANALYSIS):
|
|
||||||
'''FaceAnalysis output.'''
|
|
||||||
...
|
|
||||||
|
|
||||||
class BBOXInput(InputV3, io_type=IO.BBOX):
|
|
||||||
'''Bbox input.'''
|
|
||||||
...
|
|
||||||
|
|
||||||
class BBOXOutput(OutputV3, io_type=IO.BBOX):
|
|
||||||
'''Bbox output.'''
|
|
||||||
...
|
|
||||||
|
|
||||||
class SEGSInput(InputV3, io_type=IO.SEGS):
|
|
||||||
'''SEGS input.'''
|
|
||||||
...
|
|
||||||
|
|
||||||
class SEGSOutput(OutputV3, io_type=IO.SEGS):
|
|
||||||
'''SEGS output.'''
|
|
||||||
...
|
|
||||||
|
|
||||||
class VideoInput(InputV3, io_type=IO.VIDEO):
|
|
||||||
'''Video input.'''
|
|
||||||
...
|
|
||||||
|
|
||||||
class VideoOutput(OutputV3, io_type=IO.VIDEO):
|
|
||||||
'''Video output.'''
|
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
class MultitypedInput(InputV3, io_type="COMFY_MULTITYPED_V3"):
|
|
||||||
'''
|
|
||||||
Input that permits more than one input type.
|
|
||||||
'''
|
|
||||||
def __init__(self, id: str, io_types: list[type[IO_V3] | InputV3 | IO |str], display_name: str=None, optional=False, tooltip: str=None,):
|
|
||||||
super().__init__(id, display_name, optional, tooltip)
|
|
||||||
self._io_types = io_types
|
|
||||||
|
|
||||||
@property
|
|
||||||
def io_types(self) -> list[type[InputV3]]:
|
|
||||||
'''
|
'''
|
||||||
Returns list of InputV3 class types permitted.
|
Input that permits more than one input type.
|
||||||
'''
|
'''
|
||||||
io_types = []
|
def __init__(self, id: str, io_types: list[type[ComfyType] | ComfyType | IO |str], display_name: str=None, optional=False, tooltip: str=None,):
|
||||||
for x in self._io_types:
|
super().__init__(id, display_name, optional, tooltip)
|
||||||
if not is_class(x):
|
self._io_types = io_types
|
||||||
io_types.append(type(x))
|
|
||||||
else:
|
@property
|
||||||
io_types.append(x)
|
def io_types(self) -> list[type[InputV3]]:
|
||||||
return io_types
|
'''
|
||||||
|
Returns list of InputV3 class types permitted.
|
||||||
def get_io_type_V1(self):
|
'''
|
||||||
return ",".join(x.io_type for x in self.io_types)
|
io_types = []
|
||||||
|
for x in self._io_types:
|
||||||
|
if not is_class(x):
|
||||||
|
io_types.append(type(x))
|
||||||
|
else:
|
||||||
|
io_types.append(x)
|
||||||
|
return io_types
|
||||||
|
|
||||||
|
def get_io_type_V1(self):
|
||||||
|
return ",".join(x.io_type for x in self.io_types)
|
||||||
|
|
||||||
|
class DynamicInput(InputV3):
|
||||||
class DynamicInput(InputV3, io_type=None):
|
|
||||||
'''
|
'''
|
||||||
Abstract class for dynamic input registration.
|
Abstract class for dynamic input registration.
|
||||||
'''
|
'''
|
||||||
def __init__(self, io_type: str, id: str, display_name: str=None):
|
def __init__(self, io_type: str, id: str, display_name: str=None):
|
||||||
super().__init__(io_type, id, display_name)
|
super().__init__(io_type, id, display_name)
|
||||||
|
|
||||||
class DynamicOutput(OutputV3, io_type=None):
|
class DynamicOutput(OutputV3):
|
||||||
'''
|
'''
|
||||||
Abstract class for dynamic output registration.
|
Abstract class for dynamic output registration.
|
||||||
'''
|
'''
|
||||||
def __init__(self, io_type: str, id: str, display_name: str=None):
|
def __init__(self, io_type: str, id: str, display_name: str=None):
|
||||||
super().__init__(io_type, id, display_name)
|
super().__init__(io_type, id, display_name)
|
||||||
|
|
||||||
class AutoGrowDynamicInput(DynamicInput, io_type="COMFY_MULTIGROW_V3"):
|
# io_type="COMFY_MULTIGROW_V3"
|
||||||
|
class AutoGrowDynamicInput(DynamicInput):
|
||||||
'''
|
'''
|
||||||
Dynamic Input that adds another template_input each time one is provided.
|
Dynamic Input that adds another template_input each time one is provided.
|
||||||
|
|
||||||
Additional inputs are forced to have 'InputBehavior.optional'.
|
Additional inputs are forced to have 'optional=True'.
|
||||||
'''
|
'''
|
||||||
def __init__(self, id: str, template_input: InputV3, min: int=1, max: int=None):
|
def __init__(self, id: str, template_input: InputV3, min: int=1, max: int=None):
|
||||||
super().__init__("AutoGrowDynamicInput", id)
|
super().__init__("AutoGrowDynamicInput", id)
|
||||||
@ -542,11 +540,12 @@ class AutoGrowDynamicInput(DynamicInput, io_type="COMFY_MULTIGROW_V3"):
|
|||||||
self.min = min
|
self.min = min
|
||||||
self.max = max
|
self.max = max
|
||||||
|
|
||||||
class ComboDynamicInput(DynamicInput, io_type="COMFY_COMBODYNAMIC_V3"):
|
# io_type="COMFY_COMBODYNAMIC_V3"
|
||||||
|
class ComboDynamicInput(DynamicInput):
|
||||||
def __init__(self, id: str):
|
def __init__(self, id: str):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
AutoGrowDynamicInput(id="dynamic", template_input=ImageInput(id="image"))
|
AutoGrowDynamicInput(id="dynamic", template_input=Image.Input(id="image"))
|
||||||
|
|
||||||
|
|
||||||
class Hidden:
|
class Hidden:
|
||||||
@ -581,12 +580,10 @@ class Hidden:
|
|||||||
api_key_comfy_org=d.get(HiddenEnum.api_key_comfy_org),
|
api_key_comfy_org=d.get(HiddenEnum.api_key_comfy_org),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class HiddenEnum(str, Enum):
|
class HiddenEnum(str, Enum):
|
||||||
'''
|
'''
|
||||||
Enumerator for requesting hidden variables in nodes.
|
Enumerator for requesting hidden variables in nodes.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
unique_id = "UNIQUE_ID"
|
unique_id = "UNIQUE_ID"
|
||||||
"""UNIQUE_ID is the unique identifier of the node, and matches the id property of the node on the client side. It is commonly used in client-server communications (see messages)."""
|
"""UNIQUE_ID is the unique identifier of the node, and matches the id property of the node on the client side. It is commonly used in client-server communications (see messages)."""
|
||||||
prompt = "PROMPT"
|
prompt = "PROMPT"
|
||||||
@ -746,16 +743,6 @@ class Serializer:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def prepare_class_clone(c: ComfyNodeV3 | type[ComfyNodeV3]) -> type[ComfyNodeV3]:
|
|
||||||
"""Creates clone of real node class to prevent monkey-patching."""
|
|
||||||
c_type: type[ComfyNodeV3] = c if is_class(c) else type(c)
|
|
||||||
type_clone: type[ComfyNodeV3] = type(f"CLEAN_{c_type.__name__}", c_type.__bases__, {})
|
|
||||||
# TODO: what parameters should be carried over?
|
|
||||||
type_clone.SCHEMA = c_type.SCHEMA
|
|
||||||
# TODO: add anything we would want to expose inside node's execute function
|
|
||||||
return type_clone
|
|
||||||
|
|
||||||
|
|
||||||
class classproperty(object):
|
class classproperty(object):
|
||||||
def __init__(self, f):
|
def __init__(self, f):
|
||||||
self.f = f
|
self.f = f
|
||||||
@ -763,11 +750,15 @@ class classproperty(object):
|
|||||||
return self.f(owner)
|
return self.f(owner)
|
||||||
|
|
||||||
|
|
||||||
class ComfyNodeV3(ABC):
|
class ComfyNodeV3:
|
||||||
"""Common base class for all V3 nodes."""
|
"""Common base class for all V3 nodes."""
|
||||||
|
|
||||||
RELATIVE_PYTHON_MODULE = None
|
RELATIVE_PYTHON_MODULE = None
|
||||||
SCHEMA = None
|
SCHEMA = None
|
||||||
|
|
||||||
|
# filled in during execution
|
||||||
|
state: NodeState = None
|
||||||
|
hidden: Hidden = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def GET_NODE_INFO_V3(cls) -> dict[str, Any]:
|
def GET_NODE_INFO_V3(cls) -> dict[str, Any]:
|
||||||
@ -795,6 +786,7 @@ class ComfyNodeV3(ABC):
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
self.local_state: NodeStateLocal = None
|
||||||
self.__class__.VALIDATE_CLASS()
|
self.__class__.VALIDATE_CLASS()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -811,6 +803,7 @@ class ComfyNodeV3(ABC):
|
|||||||
type_clone: type[ComfyNodeV3] = type(f"CLEAN_{c_type.__name__}", c_type.__bases__, {})
|
type_clone: type[ComfyNodeV3] = type(f"CLEAN_{c_type.__name__}", c_type.__bases__, {})
|
||||||
# TODO: what parameters should be carried over?
|
# TODO: what parameters should be carried over?
|
||||||
type_clone.SCHEMA = c_type.SCHEMA
|
type_clone.SCHEMA = c_type.SCHEMA
|
||||||
|
type_clone.hidden = Hidden.from_dict(hidden_inputs)
|
||||||
# TODO: add anything we would want to expose inside node's execute function
|
# TODO: add anything we would want to expose inside node's execute function
|
||||||
return type_clone
|
return type_clone
|
||||||
|
|
||||||
@ -989,14 +982,6 @@ class ComfyNodeV3(ABC):
|
|||||||
#--------------------------------------------
|
#--------------------------------------------
|
||||||
#############################################
|
#############################################
|
||||||
|
|
||||||
# class ReturnedInputs:
|
|
||||||
# def __init__(self):
|
|
||||||
# pass
|
|
||||||
|
|
||||||
# class ReturnedOutputs:
|
|
||||||
# def __init__(self):
|
|
||||||
# pass
|
|
||||||
|
|
||||||
|
|
||||||
class NodeOutput:
|
class NodeOutput:
|
||||||
'''
|
'''
|
||||||
@ -1007,9 +992,11 @@ class NodeOutput:
|
|||||||
self.ui = ui
|
self.ui = ui
|
||||||
self.expand = expand
|
self.expand = expand
|
||||||
self.block_execution = block_execution
|
self.block_execution = block_execution
|
||||||
|
# self.kwargs = kwargs
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def result(self):
|
def result(self):
|
||||||
|
# TODO: use kwargs to refer to outputs by id + organize in proper order
|
||||||
return self.args if len(self.args) > 0 else None
|
return self.args if len(self.args) > 0 else None
|
||||||
|
|
||||||
|
|
||||||
@ -1084,6 +1071,11 @@ class UIText(UIOutput):
|
|||||||
return {"text": (self.value,)}
|
return {"text": (self.value,)}
|
||||||
|
|
||||||
|
|
||||||
|
def create_image_preview(image: Image.Type) -> UIImages:
|
||||||
|
# TODO: finish, right now is just Cursor's hallucination
|
||||||
|
return UIImages([SavedResult("preview.png", "comfy_org", FolderType.output)])
|
||||||
|
|
||||||
|
|
||||||
class TestNode(ComfyNodeV3):
|
class TestNode(ComfyNodeV3):
|
||||||
@classmethod
|
@classmethod
|
||||||
def DEFINE_SCHEMA(cls):
|
def DEFINE_SCHEMA(cls):
|
||||||
@ -1091,11 +1083,11 @@ class TestNode(ComfyNodeV3):
|
|||||||
node_id="TestNode_v3",
|
node_id="TestNode_v3",
|
||||||
display_name="Test Node (V3)",
|
display_name="Test Node (V3)",
|
||||||
category="v3_test",
|
category="v3_test",
|
||||||
inputs=[IntegerInput("my_int"),
|
inputs=[Int.Input("my_int"),
|
||||||
#AutoGrowDynamicInput("growing", ImageInput),
|
#AutoGrowDynamicInput("growing", Image.Input),
|
||||||
MaskInput("thing"),
|
Mask.Input("thing"),
|
||||||
],
|
],
|
||||||
outputs=[ImageOutput("image_output")],
|
outputs=[Image.Output("image_output")],
|
||||||
hidden=[HiddenEnum.api_key_comfy_org, HiddenEnum.auth_token_comfy_org, HiddenEnum.unique_id]
|
hidden=[HiddenEnum.api_key_comfy_org, HiddenEnum.auth_token_comfy_org, HiddenEnum.unique_id]
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1103,26 +1095,25 @@ class TestNode(ComfyNodeV3):
|
|||||||
def execute(cls, **kwargs):
|
def execute(cls, **kwargs):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
print("hello there")
|
print("hello there")
|
||||||
inputs: list[InputV3] = [
|
inputs: list[InputV3] = [
|
||||||
IntegerInput("tessfes", widgetType=IO.STRING),
|
Int.Input("tessfes", widgetType=IO.STRING),
|
||||||
IntegerInput("my_int"),
|
Int.Input("my_int"),
|
||||||
CustomInput("xyz", "XYZ"),
|
Custom("XYZ").Input("xyz"),
|
||||||
CustomInput("model1", "MODEL_M"),
|
Custom("MODEL_M").Input("model1"),
|
||||||
ImageInput("my_image"),
|
Image.Input("my_image"),
|
||||||
FloatInput("my_float"),
|
Float.Input("my_float"),
|
||||||
MultitypedInput("my_inputs", [StringInput, CustomType("MODEL_M"), CustomType("XYZ")]),
|
MultiType.Input("my_inputs", [String, Custom("MODEL_M"), Custom("XYZ")]),
|
||||||
]
|
]
|
||||||
|
Custom("XYZ").Input()
|
||||||
outputs: list[OutputV3] = [
|
outputs: list[OutputV3] = [
|
||||||
ImageOutput("image"),
|
Image.Output("image"),
|
||||||
CustomOutput("xyz", "XYZ")
|
Custom("XYZ").Output("xyz"),
|
||||||
]
|
]
|
||||||
|
|
||||||
for c in inputs:
|
for c in inputs:
|
||||||
if isinstance(c, MultitypedInput):
|
if isinstance(c, MultiType):
|
||||||
print(f"{c}, {type(c)}, {type(c).io_type}, {c.id}, {[x.io_type for x in c.io_types]}")
|
print(f"{c}, {type(c)}, {type(c).io_type}, {c.id}, {[x.io_type for x in c.io_types]}")
|
||||||
print(c.get_io_type_V1())
|
print(c.get_io_type_V1())
|
||||||
else:
|
else:
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,94 +0,0 @@
|
|||||||
import torch
|
|
||||||
from comfy_api.v3_01 import io
|
|
||||||
import logging
|
|
||||||
|
|
||||||
|
|
||||||
@io.comfytype(io_type="XYZ")
|
|
||||||
class XYZ:
|
|
||||||
Type = tuple[int,str]
|
|
||||||
class Input(io.InputV3):
|
|
||||||
...
|
|
||||||
class Output(io.OutputV3):
|
|
||||||
...
|
|
||||||
|
|
||||||
class MyState(io.NodeState):
|
|
||||||
my_str: str
|
|
||||||
my_int: int
|
|
||||||
|
|
||||||
|
|
||||||
class V3TestNode(io.ComfyNodeV3):
|
|
||||||
|
|
||||||
state: MyState
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self.hahajkunless = ";)"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def DEFINE_SCHEMA(cls):
|
|
||||||
return io.SchemaV3(
|
|
||||||
node_id="V3_01_TestNode1",
|
|
||||||
display_name="V3_01 Test Node",
|
|
||||||
description="This is a funky V3_01 node test.",
|
|
||||||
category="v3 nodes",
|
|
||||||
inputs=[
|
|
||||||
io.Image.Input("image", display_name="new_image"),
|
|
||||||
XYZ.Input("xyz", optional=True),
|
|
||||||
io.Custom("JKL").Input("jkl", optional=True),
|
|
||||||
#JKL.Input("jkl", optional=True),
|
|
||||||
#CustomInput("xyz", "XYZ", optional=True),
|
|
||||||
io.Mask.Input("mask", optional=True),
|
|
||||||
io.Int.Input("some_int", display_name="new_name", min=0, max=127, default=42,
|
|
||||||
tooltip="My tooltip 😎", display_mode=io.NumberDisplay.slider),
|
|
||||||
io.Combo.Input("combo", options=["a", "b", "c"], tooltip="This is a combo input"),
|
|
||||||
io.MultiCombo.Input("combo2", options=["a","b","c"]),
|
|
||||||
# ComboInput("combo", image_upload=True, image_folder=FolderType.output,
|
|
||||||
# remote=RemoteOptions(
|
|
||||||
# route="/internal/files/output",
|
|
||||||
# refresh_button=True,
|
|
||||||
# ),
|
|
||||||
# tooltip="This is a combo input"),
|
|
||||||
# IntegerInput("some_int", display_name="new_name", min=0, tooltip="My tooltip 😎", display=NumberDisplay.slider, ),
|
|
||||||
# ComboDynamicInput("mask", behavior=InputBehavior.optional),
|
|
||||||
# IntegerInput("some_int", display_name="new_name", min=0, tooltip="My tooltip 😎", display=NumberDisplay.slider,
|
|
||||||
# dependent_inputs=[ComboDynamicInput("mask", behavior=InputBehavior.optional)],
|
|
||||||
# dependent_values=[lambda my_value: IO.STRING if my_value < 5 else IO.NUMBER],
|
|
||||||
# ),
|
|
||||||
# ["option1", "option2". "option3"]
|
|
||||||
# ComboDynamicInput["sdfgjhl", [ComboDynamicOptions("option1", [IntegerInput("some_int", display_name="new_name", min=0, tooltip="My tooltip 😎", display=NumberDisplay.slider, ImageInput(), MaskInput(), String()]),
|
|
||||||
# CombyDynamicOptons("option2", [])
|
|
||||||
# ]]
|
|
||||||
],
|
|
||||||
outputs=[
|
|
||||||
io.Int.Output("int_output"),
|
|
||||||
io.Image.Output("img_output", display_name="img🖼️", tooltip="This is an image"),
|
|
||||||
],
|
|
||||||
hidden=[
|
|
||||||
|
|
||||||
],
|
|
||||||
is_output_node=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def execute(cls, image: io.Image.Type, some_int: int, combo: io.Combo.Type, combo2: io.MultiCombo.Type, xyz: XYZ.Type=None, mask: io.Mask.Type=None, **kwargs):
|
|
||||||
zzz = cls.hidden.prompt
|
|
||||||
cls.state.my_str = "LOLJK"
|
|
||||||
expected_int = 123
|
|
||||||
cls.state.my_int = expected_int
|
|
||||||
if cls.state.my_int is None:
|
|
||||||
cls.state.my_int = expected_int
|
|
||||||
else:
|
|
||||||
if cls.state.my_int != expected_int:
|
|
||||||
raise Exception(f"Explicit state object did not maintain expected value: {cls.state.my_int} != {expected_int}")
|
|
||||||
#some_int
|
|
||||||
if hasattr(cls, "hahajkunless"):
|
|
||||||
raise Exception("The 'cls' variable leaked instance state between runs!")
|
|
||||||
if hasattr(cls, "doohickey"):
|
|
||||||
raise Exception("The 'cls' variable leaked state on class properties between runs!")
|
|
||||||
cls.doohickey = "LOLJK"
|
|
||||||
return io.NodeOutput(some_int, image)
|
|
||||||
|
|
||||||
|
|
||||||
NODES_LIST: list[io.ComfyNodeV3] = [
|
|
||||||
V3TestNode,
|
|
||||||
]
|
|
@ -1,40 +1,47 @@
|
|||||||
import torch
|
import torch
|
||||||
from comfy_api.v3.io import (
|
from comfy_api.v3 import io
|
||||||
ComfyNodeV3, SchemaV3, NumberDisplay,
|
|
||||||
IntegerInput, MaskInput, ImageInput, ComboInput, CustomInput, StringInput, CustomType,
|
|
||||||
IntegerOutput, ImageOutput, MultitypedInput, InputV3, OutputV3,
|
|
||||||
NodeOutput
|
|
||||||
)
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
|
||||||
class XYZInput(InputV3, io_type="XYZ"):
|
@io.comfytype(io_type="XYZ")
|
||||||
|
class XYZ:
|
||||||
Type = tuple[int,str]
|
Type = tuple[int,str]
|
||||||
|
class Input(io.InputV3):
|
||||||
|
...
|
||||||
|
class Output(io.OutputV3):
|
||||||
|
...
|
||||||
|
|
||||||
class XYZOutput(OutputV3, io_type="XYZ"):
|
class MyState(io.NodeState):
|
||||||
...
|
my_str: str
|
||||||
|
my_int: int
|
||||||
|
|
||||||
|
|
||||||
class V3TestNode(ComfyNodeV3):
|
class V3TestNode(io.ComfyNodeV3):
|
||||||
|
|
||||||
|
state: MyState
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
self.hahajkunless = ";)"
|
self.hahajkunless = ";)"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def DEFINE_SCHEMA(cls):
|
def DEFINE_SCHEMA(cls):
|
||||||
return SchemaV3(
|
return io.SchemaV3(
|
||||||
node_id="V3TestNode1",
|
node_id="V3_01_TestNode1",
|
||||||
display_name="V3 Test Node",
|
display_name="V3 Test Node",
|
||||||
description="This is a funky V3 node test.",
|
description="This is a funky V3 node test.",
|
||||||
category="v3 nodes",
|
category="v3 nodes",
|
||||||
inputs=[
|
inputs=[
|
||||||
ImageInput("image", display_name="new_image"),
|
io.Image.Input("image", display_name="new_image"),
|
||||||
XYZInput("xyz", optional=True),
|
XYZ.Input("xyz", optional=True),
|
||||||
|
io.Custom("JKL").Input("jkl", optional=True),
|
||||||
|
#JKL.Input("jkl", optional=True),
|
||||||
#CustomInput("xyz", "XYZ", optional=True),
|
#CustomInput("xyz", "XYZ", optional=True),
|
||||||
MaskInput("mask", optional=True),
|
io.Mask.Input("mask", optional=True),
|
||||||
IntegerInput("some_int", display_name="new_name", min=0, max=127, default=42,
|
io.Int.Input("some_int", display_name="new_name", min=0, max=127, default=42,
|
||||||
tooltip="My tooltip 😎", display_mode=NumberDisplay.slider),
|
tooltip="My tooltip 😎", display_mode=io.NumberDisplay.slider),
|
||||||
ComboInput("combo", options=["a", "b", "c"], tooltip="This is a combo input"),
|
io.Combo.Input("combo", options=["a", "b", "c"], tooltip="This is a combo input"),
|
||||||
|
io.MultiCombo.Input("combo2", options=["a","b","c"]),
|
||||||
# ComboInput("combo", image_upload=True, image_folder=FolderType.output,
|
# ComboInput("combo", image_upload=True, image_folder=FolderType.output,
|
||||||
# remote=RemoteOptions(
|
# remote=RemoteOptions(
|
||||||
# route="/internal/files/output",
|
# route="/internal/files/output",
|
||||||
@ -53,8 +60,8 @@ class V3TestNode(ComfyNodeV3):
|
|||||||
# ]]
|
# ]]
|
||||||
],
|
],
|
||||||
outputs=[
|
outputs=[
|
||||||
IntegerOutput("int_output"),
|
io.Int.Output("int_output"),
|
||||||
ImageOutput("img_output", display_name="img🖼️", tooltip="This is an image"),
|
io.Image.Output("img_output", display_name="img🖼️", tooltip="This is an image"),
|
||||||
],
|
],
|
||||||
hidden=[
|
hidden=[
|
||||||
|
|
||||||
@ -63,15 +70,25 @@ class V3TestNode(ComfyNodeV3):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def execute(cls, image: ImageInput.Type, some_int: IntegerInput.Type, combo: ComboInput.Type, xyz: XYZInput.Type=None, mask: MaskInput.Type=None):
|
def execute(cls, image: io.Image.Type, some_int: int, combo: io.Combo.Type, combo2: io.MultiCombo.Type, xyz: XYZ.Type=None, mask: io.Mask.Type=None, **kwargs):
|
||||||
|
zzz = cls.hidden.prompt
|
||||||
|
cls.state.my_str = "LOLJK"
|
||||||
|
expected_int = 123
|
||||||
|
cls.state.my_int = expected_int
|
||||||
|
if cls.state.my_int is None:
|
||||||
|
cls.state.my_int = expected_int
|
||||||
|
else:
|
||||||
|
if cls.state.my_int != expected_int:
|
||||||
|
raise Exception(f"Explicit state object did not maintain expected value: {cls.state.my_int} != {expected_int}")
|
||||||
|
#some_int
|
||||||
if hasattr(cls, "hahajkunless"):
|
if hasattr(cls, "hahajkunless"):
|
||||||
raise Exception("The 'cls' variable leaked instance state between runs!")
|
raise Exception("The 'cls' variable leaked instance state between runs!")
|
||||||
if hasattr(cls, "doohickey"):
|
if hasattr(cls, "doohickey"):
|
||||||
raise Exception("The 'cls' variable leaked state on class properties between runs!")
|
raise Exception("The 'cls' variable leaked state on class properties between runs!")
|
||||||
cls.doohickey = "LOLJK"
|
cls.doohickey = "LOLJK"
|
||||||
return NodeOutput(some_int, image)
|
return io.NodeOutput(some_int, image)
|
||||||
|
|
||||||
|
|
||||||
NODES_LIST: list[ComfyNodeV3] = [
|
NODES_LIST: list[io.ComfyNodeV3] = [
|
||||||
V3TestNode,
|
V3TestNode,
|
||||||
]
|
]
|
||||||
|
@ -28,8 +28,7 @@ from comfy_execution.graph import (
|
|||||||
)
|
)
|
||||||
from comfy_execution.graph_utils import GraphBuilder, is_link
|
from comfy_execution.graph_utils import GraphBuilder, is_link
|
||||||
from comfy_execution.validation import validate_node_input
|
from comfy_execution.validation import validate_node_input
|
||||||
from comfy_api.v3.io import NodeOutput, ComfyNodeV3, HiddenEnum
|
from comfy_api.v3.io import NodeOutput, ComfyNodeV3, HiddenEnum, NodeStateLocal
|
||||||
from comfy_api.v3_01.io import NodeStateLocal
|
|
||||||
|
|
||||||
|
|
||||||
class ExecutionResult(Enum):
|
class ExecutionResult(Enum):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user