Merge pull request #8879 from comfyanonymous/v3-definition-wip

V3 update - make id on Outputs optional, make widgetType only included with MultiType
This commit is contained in:
Jedrzej Kosinski 2025-07-11 15:51:51 -07:00 committed by GitHub
commit 926a2b1579
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 47 additions and 59 deletions

View File

@ -202,6 +202,7 @@ class WidgetInputV3(InputV3):
return super().as_dict_V1() | prune_dict({
"default": self.default,
"socketless": self.socketless,
"widgetType": self.widgetType,
"forceInput": self.force_input,
})
@ -210,7 +211,7 @@ class WidgetInputV3(InputV3):
class OutputV3(IO_V3):
def __init__(self, id: str, display_name: str=None, tooltip: str=None,
def __init__(self, id: str=None, display_name: str=None, tooltip: str=None,
is_output_list=False):
self.id = id
self.display_name = display_name
@ -296,7 +297,7 @@ class Boolean:
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, force_input: bool=None):
super().__init__(id, display_name, optional, tooltip, lazy, default, socketless, self.io_type, force_input)
super().__init__(id, display_name, optional, tooltip, lazy, default, socketless, None, force_input)
self.label_on = label_on
self.label_off = label_off
self.default: bool
@ -319,7 +320,7 @@ class 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, force_input: bool=None):
super().__init__(id, display_name, optional, tooltip, lazy, default, socketless, self.io_type, force_input)
super().__init__(id, display_name, optional, tooltip, lazy, default, socketless, None, force_input)
self.min = min
self.max = max
self.step = step
@ -348,7 +349,7 @@ class Float(ComfyTypeIO):
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, force_input: bool=None):
super().__init__(id, display_name, optional, tooltip, lazy, default, socketless, self.io_type, force_input)
super().__init__(id, display_name, optional, tooltip, lazy, default, socketless, None, force_input)
self.min = min
self.max = max
self.step = step
@ -374,7 +375,7 @@ class String(ComfyTypeIO):
def __init__(self, id: str, display_name: str=None, optional=False, tooltip: str=None, lazy: bool=None,
multiline=False, placeholder: str=None, default: str=None,
socketless: bool=None, force_input: bool=None):
super().__init__(id, display_name, optional, tooltip, lazy, default, socketless, self.io_type, force_input)
super().__init__(id, display_name, optional, tooltip, lazy, default, socketless, None, force_input)
self.multiline = multiline
self.placeholder = placeholder
self.default: str
@ -396,7 +397,7 @@ class Combo(ComfyType):
image_upload: bool=None, image_folder: FolderType=None, content_types: list[Literal["image", "video", "audio", "model"]]=None,
remote: RemoteOptions=None,
socketless: bool=None):
super().__init__(id, display_name, optional, tooltip, lazy, default, socketless, self.io_type)
super().__init__(id, display_name, optional, tooltip, lazy, default, socketless)
self.multiselect = False
self.options = options
self.control_after_generate = control_after_generate
@ -456,7 +457,7 @@ class Webcam(ComfyTypeIO):
self, id: str, display_name: str=None, optional=False,
tooltip: str=None, lazy: bool=None, default: str=None, socketless: bool=None
):
super().__init__(id, display_name, optional, tooltip, lazy, default, socketless, self.io_type)
super().__init__(id, display_name, optional, tooltip, lazy, default, socketless)
@comfytype(io_type="MASK")
@ -739,12 +740,15 @@ class MultiType:
# if id is an Input, then use that Input with overridden values
self.input_override = None
if isinstance(id, InputV3):
self.input_override = id
self.input_override = copy.copy(id)
optional = id.optional if id.optional is True else optional
tooltip = id.tooltip if id.tooltip is not None else tooltip
display_name = id.display_name if id.display_name is not None else display_name
lazy = id.lazy if id.lazy is not None else lazy
id = id.id
# if is a widget input, make sure widgetType is set appropriately
if isinstance(self.input_override, WidgetInputV3):
self.input_override.widgetType = self.input_override.get_io_type_V1()
super().__init__(id, display_name, optional, tooltip, lazy, extra_dict)
self._io_types = types
@ -786,6 +790,10 @@ class DynamicOutput(OutputV3, ABC):
'''
Abstract class for dynamic output registration.
'''
def __init__(self, id: str, display_name: str=None, tooltip: str=None,
is_output_list=False):
super().__init__(id, display_name, tooltip, is_output_list)
@abstractmethod
def get_dynamic(self) -> list[OutputV3]:
...
@ -987,7 +995,7 @@ class SchemaV3:
raise ValueError("\n".join(issues))
def finalize(self):
"""Add hidden based on selected schema options."""
"""Add hidden based on selected schema options, and give outputs without ids default ids."""
# if is an api_node, will need key-related hidden
if self.is_api_node:
if self.hidden is None:
@ -1004,6 +1012,11 @@ class SchemaV3:
self.hidden.append(Hidden.prompt)
if Hidden.extra_pnginfo not in self.hidden:
self.hidden.append(Hidden.extra_pnginfo)
# give outputs without ids default ids
if self.outputs is not None:
for i, output in enumerate(self.outputs):
if output.id is None:
output.id = f"_{i}_{output.io_type}_"
class Serializer:

View File

@ -27,7 +27,7 @@ class SavedResult:
}
class PreviewImage(_UIOutput):
def __init__(self, image: Image.Type, animated: bool=False, node: ComfyNodeV3=None, **kwargs):
def __init__(self, image: Image.Type, animated: bool=False, cls: ComfyNodeV3=None, **kwargs):
output_dir = folder_paths.get_temp_directory()
type = "temp"
prefix_append = "_temp_" + ''.join(random.choice("abcdefghijklmnopqrstupvxyz") for x in range(5))
@ -41,13 +41,13 @@ class PreviewImage(_UIOutput):
i = 255. * image.cpu().numpy()
img = PILImage.fromarray(np.clip(i, 0, 255).astype(np.uint8))
metadata = None
if not args.disable_metadata and node is not None:
if not args.disable_metadata and cls is not None:
metadata = PngInfo()
if node.hidden.prompt is not None:
metadata.add_text("prompt", json.dumps(node.hidden.prompt))
if node.hidden.extra_pnginfo is not None:
for x in node.hidden.extra_pnginfo:
metadata.add_text(x, json.dumps(node.hidden.extra_pnginfo[x]))
if cls.hidden.prompt is not None:
metadata.add_text("prompt", json.dumps(cls.hidden.prompt))
if cls.hidden.extra_pnginfo is not None:
for x in cls.hidden.extra_pnginfo:
metadata.add_text(x, json.dumps(cls.hidden.extra_pnginfo[x]))
filename_with_batch_num = filename.replace("%batch_num%", str(batch_number))
file = f"{filename_with_batch_num}_{counter:05}_.png"
@ -66,9 +66,9 @@ class PreviewImage(_UIOutput):
}
class PreviewMask(PreviewImage):
def __init__(self, mask: PreviewMask.Type, animated: bool=False, node: ComfyNodeV3=None, **kwargs):
def __init__(self, mask: PreviewMask.Type, animated: bool=False, cls: ComfyNodeV3=None, **kwargs):
preview = mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])).movedim(1, -1).expand(-1, -1, -1, 3)
super().__init__(preview, animated, node, **kwargs)
super().__init__(preview, animated, cls, **kwargs)
# class UILatent(_UIOutput):
# def __init__(self, values: list[SavedResult | dict], **kwargs):

View File

@ -62,8 +62,8 @@ class V3TestNode(io.ComfyNodeV3):
# ]]
],
outputs=[
io.Int.Output("int_output"),
io.Image.Output("img_output", display_name="img🖼", tooltip="This is an image"),
io.Int.Output(),
io.Image.Output(display_name="img🖼", tooltip="This is an image"),
],
hidden=[
io.Hidden.prompt,
@ -105,7 +105,7 @@ class V3TestNode(io.ComfyNodeV3):
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, ui=ui.PreviewImage(image))
return io.NodeOutput(some_int, image, ui=ui.PreviewImage(image, cls=cls))
class V3LoraLoader(io.ComfyNodeV3):
@ -142,8 +142,8 @@ class V3LoraLoader(io.ComfyNodeV3):
),
],
outputs=[
io.Model.Output("model_out"),
io.Clip.Output("clip_out"),
io.Model.Output(),
io.Clip.Output(),
],
)
@ -169,7 +169,7 @@ class NInputsTest(io.ComfyNodeV3):
io.AutogrowDynamic.Input("nmock2", template_input=io.Int.Input("int"), optional=True, min=1, max=4),
],
outputs=[
io.Image.Output("image_out"),
io.Image.Output(),
],
)

View File

@ -24,7 +24,6 @@ class SaveImage_V3(io.ComfyNodeV3):
inputs=[
io.Image.Input(
"images",
display_name="images",
tooltip="The images to save.",
),
io.String.Input(
@ -79,7 +78,6 @@ class PreviewImage_V3(io.ComfyNodeV3):
inputs=[
io.Image.Input(
"images",
display_name="images",
tooltip="The images to preview.",
),
],
@ -89,7 +87,7 @@ class PreviewImage_V3(io.ComfyNodeV3):
@classmethod
def execute(cls, images):
return io.NodeOutput(ui=ui.PreviewImage(images))
return io.NodeOutput(ui=ui.PreviewImage(images, cls=cls))
class LoadImage_V3(io.ComfyNodeV3):
@ -102,7 +100,6 @@ class LoadImage_V3(io.ComfyNodeV3):
inputs=[
io.Combo.Input(
"image",
display_name="image",
image_upload=True,
image_folder=io.FolderType.input,
content_types=["image"],
@ -110,12 +107,8 @@ class LoadImage_V3(io.ComfyNodeV3):
),
],
outputs=[
io.Image.Output(
"IMAGE",
),
io.Mask.Output(
"MASK",
),
io.Image.Output(),
io.Mask.Output(),
],
)
@ -199,7 +192,6 @@ class LoadImageOutput_V3(io.ComfyNodeV3):
inputs=[
io.Combo.Input(
"image",
display_name="image",
image_upload=True,
image_folder=io.FolderType.output,
content_types=["image"],
@ -211,12 +203,8 @@ class LoadImageOutput_V3(io.ComfyNodeV3):
),
],
outputs=[
io.Image.Output(
"IMAGE",
),
io.Mask.Output(
"MASK",
),
io.Image.Output(),
io.Mask.Output(),
],
)

View File

@ -12,13 +12,10 @@ class MaskPreview_V3(io.ComfyNodeV3):
def DEFINE_SCHEMA(cls):
return io.SchemaV3(
node_id="MaskPreview_V3",
display_name="Convert Mask to Image _V3",
display_name="Preview Mask _V3",
category="mask",
inputs=[
io.Mask.Input(
"masks",
display_name="masks",
),
io.Mask.Input("masks"),
],
hidden=[io.Hidden.prompt, io.Hidden.extra_pnginfo],
is_output_node=True,

View File

@ -21,13 +21,9 @@ class WebcamCapture_V3(io.ComfyNodeV3):
display_name="Webcam Capture _V3",
category="image",
inputs=[
io.Webcam.Input(
"image",
display_name="image",
),
io.Webcam.Input("image"),
io.Int.Input(
"width",
display_name="width",
default=0,
min=0,
max=MAX_RESOLUTION,
@ -35,21 +31,15 @@ class WebcamCapture_V3(io.ComfyNodeV3):
),
io.Int.Input(
"height",
display_name="height",
default=0,
min=0,
max=MAX_RESOLUTION,
step=1,
),
io.Boolean.Input(
"capture_on_queue",
default=True,
),
io.Boolean.Input("capture_on_queue", default=True),
],
outputs=[
io.Image.Output(
"IMAGE",
),
io.Image.Output(),
],
)