Skip to content

Commit 926a2b1

Browse files
authored
Merge pull request #8879 from comfyanonymous/v3-definition-wip
V3 update - make id on Outputs optional, make widgetType only included with MultiType
2 parents eabd053 + 5ee63e2 commit 926a2b1

File tree

6 files changed

+47
-59
lines changed

6 files changed

+47
-59
lines changed

comfy_api/v3/io.py

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ def as_dict_V1(self):
202202
return super().as_dict_V1() | prune_dict({
203203
"default": self.default,
204204
"socketless": self.socketless,
205+
"widgetType": self.widgetType,
205206
"forceInput": self.force_input,
206207
})
207208

@@ -210,7 +211,7 @@ def get_io_type_V1(self):
210211

211212

212213
class OutputV3(IO_V3):
213-
def __init__(self, id: str, display_name: str=None, tooltip: str=None,
214+
def __init__(self, id: str=None, display_name: str=None, tooltip: str=None,
214215
is_output_list=False):
215216
self.id = id
216217
self.display_name = display_name
@@ -296,7 +297,7 @@ class Input(WidgetInputV3):
296297
def __init__(self, id: str, display_name: str=None, optional=False, tooltip: str=None, lazy: bool=None,
297298
default: bool=None, label_on: str=None, label_off: str=None,
298299
socketless: bool=None, force_input: bool=None):
299-
super().__init__(id, display_name, optional, tooltip, lazy, default, socketless, self.io_type, force_input)
300+
super().__init__(id, display_name, optional, tooltip, lazy, default, socketless, None, force_input)
300301
self.label_on = label_on
301302
self.label_off = label_off
302303
self.default: bool
@@ -319,7 +320,7 @@ class Input(WidgetInputV3):
319320
def __init__(self, id: str, display_name: str=None, optional=False, tooltip: str=None, lazy: bool=None,
320321
default: int=None, min: int=None, max: int=None, step: int=None, control_after_generate: bool=None,
321322
display_mode: NumberDisplay=None, socketless: bool=None, force_input: bool=None):
322-
super().__init__(id, display_name, optional, tooltip, lazy, default, socketless, self.io_type, force_input)
323+
super().__init__(id, display_name, optional, tooltip, lazy, default, socketless, None, force_input)
323324
self.min = min
324325
self.max = max
325326
self.step = step
@@ -348,7 +349,7 @@ class Input(WidgetInputV3):
348349
def __init__(self, id: str, display_name: str=None, optional=False, tooltip: str=None, lazy: bool=None,
349350
default: float=None, min: float=None, max: float=None, step: float=None, round: float=None,
350351
display_mode: NumberDisplay=None, socketless: bool=None, force_input: bool=None):
351-
super().__init__(id, display_name, optional, tooltip, lazy, default, socketless, self.io_type, force_input)
352+
super().__init__(id, display_name, optional, tooltip, lazy, default, socketless, None, force_input)
352353
self.min = min
353354
self.max = max
354355
self.step = step
@@ -374,7 +375,7 @@ class Input(WidgetInputV3):
374375
def __init__(self, id: str, display_name: str=None, optional=False, tooltip: str=None, lazy: bool=None,
375376
multiline=False, placeholder: str=None, default: str=None,
376377
socketless: bool=None, force_input: bool=None):
377-
super().__init__(id, display_name, optional, tooltip, lazy, default, socketless, self.io_type, force_input)
378+
super().__init__(id, display_name, optional, tooltip, lazy, default, socketless, None, force_input)
378379
self.multiline = multiline
379380
self.placeholder = placeholder
380381
self.default: str
@@ -396,7 +397,7 @@ def __init__(self, id: str, options: list[str]=None, display_name: str=None, opt
396397
image_upload: bool=None, image_folder: FolderType=None, content_types: list[Literal["image", "video", "audio", "model"]]=None,
397398
remote: RemoteOptions=None,
398399
socketless: bool=None):
399-
super().__init__(id, display_name, optional, tooltip, lazy, default, socketless, self.io_type)
400+
super().__init__(id, display_name, optional, tooltip, lazy, default, socketless)
400401
self.multiselect = False
401402
self.options = options
402403
self.control_after_generate = control_after_generate
@@ -456,7 +457,7 @@ def __init__(
456457
self, id: str, display_name: str=None, optional=False,
457458
tooltip: str=None, lazy: bool=None, default: str=None, socketless: bool=None
458459
):
459-
super().__init__(id, display_name, optional, tooltip, lazy, default, socketless, self.io_type)
460+
super().__init__(id, display_name, optional, tooltip, lazy, default, socketless)
460461

461462

462463
@comfytype(io_type="MASK")
@@ -739,12 +740,15 @@ def __init__(self, id: str | InputV3, types: list[type[ComfyType] | ComfyType],
739740
# if id is an Input, then use that Input with overridden values
740741
self.input_override = None
741742
if isinstance(id, InputV3):
742-
self.input_override = id
743+
self.input_override = copy.copy(id)
743744
optional = id.optional if id.optional is True else optional
744745
tooltip = id.tooltip if id.tooltip is not None else tooltip
745746
display_name = id.display_name if id.display_name is not None else display_name
746747
lazy = id.lazy if id.lazy is not None else lazy
747748
id = id.id
749+
# if is a widget input, make sure widgetType is set appropriately
750+
if isinstance(self.input_override, WidgetInputV3):
751+
self.input_override.widgetType = self.input_override.get_io_type_V1()
748752
super().__init__(id, display_name, optional, tooltip, lazy, extra_dict)
749753
self._io_types = types
750754

@@ -786,6 +790,10 @@ class DynamicOutput(OutputV3, ABC):
786790
'''
787791
Abstract class for dynamic output registration.
788792
'''
793+
def __init__(self, id: str, display_name: str=None, tooltip: str=None,
794+
is_output_list=False):
795+
super().__init__(id, display_name, tooltip, is_output_list)
796+
789797
@abstractmethod
790798
def get_dynamic(self) -> list[OutputV3]:
791799
...
@@ -987,7 +995,7 @@ def validate(self):
987995
raise ValueError("\n".join(issues))
988996

989997
def finalize(self):
990-
"""Add hidden based on selected schema options."""
998+
"""Add hidden based on selected schema options, and give outputs without ids default ids."""
991999
# if is an api_node, will need key-related hidden
9921000
if self.is_api_node:
9931001
if self.hidden is None:
@@ -1004,6 +1012,11 @@ def finalize(self):
10041012
self.hidden.append(Hidden.prompt)
10051013
if Hidden.extra_pnginfo not in self.hidden:
10061014
self.hidden.append(Hidden.extra_pnginfo)
1015+
# give outputs without ids default ids
1016+
if self.outputs is not None:
1017+
for i, output in enumerate(self.outputs):
1018+
if output.id is None:
1019+
output.id = f"_{i}_{output.io_type}_"
10071020

10081021

10091022
class Serializer:

comfy_api/v3/ui.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def as_dict(self):
2727
}
2828

2929
class PreviewImage(_UIOutput):
30-
def __init__(self, image: Image.Type, animated: bool=False, node: ComfyNodeV3=None, **kwargs):
30+
def __init__(self, image: Image.Type, animated: bool=False, cls: ComfyNodeV3=None, **kwargs):
3131
output_dir = folder_paths.get_temp_directory()
3232
type = "temp"
3333
prefix_append = "_temp_" + ''.join(random.choice("abcdefghijklmnopqrstupvxyz") for x in range(5))
@@ -41,13 +41,13 @@ def __init__(self, image: Image.Type, animated: bool=False, node: ComfyNodeV3=No
4141
i = 255. * image.cpu().numpy()
4242
img = PILImage.fromarray(np.clip(i, 0, 255).astype(np.uint8))
4343
metadata = None
44-
if not args.disable_metadata and node is not None:
44+
if not args.disable_metadata and cls is not None:
4545
metadata = PngInfo()
46-
if node.hidden.prompt is not None:
47-
metadata.add_text("prompt", json.dumps(node.hidden.prompt))
48-
if node.hidden.extra_pnginfo is not None:
49-
for x in node.hidden.extra_pnginfo:
50-
metadata.add_text(x, json.dumps(node.hidden.extra_pnginfo[x]))
46+
if cls.hidden.prompt is not None:
47+
metadata.add_text("prompt", json.dumps(cls.hidden.prompt))
48+
if cls.hidden.extra_pnginfo is not None:
49+
for x in cls.hidden.extra_pnginfo:
50+
metadata.add_text(x, json.dumps(cls.hidden.extra_pnginfo[x]))
5151

5252
filename_with_batch_num = filename.replace("%batch_num%", str(batch_number))
5353
file = f"{filename_with_batch_num}_{counter:05}_.png"
@@ -66,9 +66,9 @@ def as_dict(self):
6666
}
6767

6868
class PreviewMask(PreviewImage):
69-
def __init__(self, mask: PreviewMask.Type, animated: bool=False, node: ComfyNodeV3=None, **kwargs):
69+
def __init__(self, mask: PreviewMask.Type, animated: bool=False, cls: ComfyNodeV3=None, **kwargs):
7070
preview = mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])).movedim(1, -1).expand(-1, -1, -1, 3)
71-
super().__init__(preview, animated, node, **kwargs)
71+
super().__init__(preview, animated, cls, **kwargs)
7272

7373
# class UILatent(_UIOutput):
7474
# def __init__(self, values: list[SavedResult | dict], **kwargs):

comfy_extras/nodes_v3_test.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ def DEFINE_SCHEMA(cls):
6262
# ]]
6363
],
6464
outputs=[
65-
io.Int.Output("int_output"),
66-
io.Image.Output("img_output", display_name="img🖼️", tooltip="This is an image"),
65+
io.Int.Output(),
66+
io.Image.Output(display_name="img🖼️", tooltip="This is an image"),
6767
],
6868
hidden=[
6969
io.Hidden.prompt,
@@ -105,7 +105,7 @@ def execute(cls, image: io.Image.Type, some_int: int, combo: io.Combo.Type, comb
105105
if hasattr(cls, "doohickey"):
106106
raise Exception("The 'cls' variable leaked state on class properties between runs!")
107107
cls.doohickey = "LOLJK"
108-
return io.NodeOutput(some_int, image, ui=ui.PreviewImage(image))
108+
return io.NodeOutput(some_int, image, ui=ui.PreviewImage(image, cls=cls))
109109

110110

111111
class V3LoraLoader(io.ComfyNodeV3):
@@ -142,8 +142,8 @@ def DEFINE_SCHEMA(cls):
142142
),
143143
],
144144
outputs=[
145-
io.Model.Output("model_out"),
146-
io.Clip.Output("clip_out"),
145+
io.Model.Output(),
146+
io.Clip.Output(),
147147
],
148148
)
149149

@@ -169,7 +169,7 @@ def DEFINE_SCHEMA(cls):
169169
io.AutogrowDynamic.Input("nmock2", template_input=io.Int.Input("int"), optional=True, min=1, max=4),
170170
],
171171
outputs=[
172-
io.Image.Output("image_out"),
172+
io.Image.Output(),
173173
],
174174
)
175175

comfy_extras/v3/nodes_images.py

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ def DEFINE_SCHEMA(cls):
2424
inputs=[
2525
io.Image.Input(
2626
"images",
27-
display_name="images",
2827
tooltip="The images to save.",
2928
),
3029
io.String.Input(
@@ -79,7 +78,6 @@ def DEFINE_SCHEMA(cls):
7978
inputs=[
8079
io.Image.Input(
8180
"images",
82-
display_name="images",
8381
tooltip="The images to preview.",
8482
),
8583
],
@@ -89,7 +87,7 @@ def DEFINE_SCHEMA(cls):
8987

9088
@classmethod
9189
def execute(cls, images):
92-
return io.NodeOutput(ui=ui.PreviewImage(images))
90+
return io.NodeOutput(ui=ui.PreviewImage(images, cls=cls))
9391

9492

9593
class LoadImage_V3(io.ComfyNodeV3):
@@ -102,20 +100,15 @@ def DEFINE_SCHEMA(cls):
102100
inputs=[
103101
io.Combo.Input(
104102
"image",
105-
display_name="image",
106103
image_upload=True,
107104
image_folder=io.FolderType.input,
108105
content_types=["image"],
109106
options=cls.get_files_options(),
110107
),
111108
],
112109
outputs=[
113-
io.Image.Output(
114-
"IMAGE",
115-
),
116-
io.Mask.Output(
117-
"MASK",
118-
),
110+
io.Image.Output(),
111+
io.Mask.Output(),
119112
],
120113
)
121114

@@ -199,7 +192,6 @@ def DEFINE_SCHEMA(cls):
199192
inputs=[
200193
io.Combo.Input(
201194
"image",
202-
display_name="image",
203195
image_upload=True,
204196
image_folder=io.FolderType.output,
205197
content_types=["image"],
@@ -211,12 +203,8 @@ def DEFINE_SCHEMA(cls):
211203
),
212204
],
213205
outputs=[
214-
io.Image.Output(
215-
"IMAGE",
216-
),
217-
io.Mask.Output(
218-
"MASK",
219-
),
206+
io.Image.Output(),
207+
io.Mask.Output(),
220208
],
221209
)
222210

comfy_extras/v3/nodes_mask.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,10 @@ class MaskPreview_V3(io.ComfyNodeV3):
1212
def DEFINE_SCHEMA(cls):
1313
return io.SchemaV3(
1414
node_id="MaskPreview_V3",
15-
display_name="Convert Mask to Image _V3",
15+
display_name="Preview Mask _V3",
1616
category="mask",
1717
inputs=[
18-
io.Mask.Input(
19-
"masks",
20-
display_name="masks",
21-
),
18+
io.Mask.Input("masks"),
2219
],
2320
hidden=[io.Hidden.prompt, io.Hidden.extra_pnginfo],
2421
is_output_node=True,

comfy_extras/v3/nodes_webcam.py

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,35 +21,25 @@ def DEFINE_SCHEMA(cls):
2121
display_name="Webcam Capture _V3",
2222
category="image",
2323
inputs=[
24-
io.Webcam.Input(
25-
"image",
26-
display_name="image",
27-
),
24+
io.Webcam.Input("image"),
2825
io.Int.Input(
2926
"width",
30-
display_name="width",
3127
default=0,
3228
min=0,
3329
max=MAX_RESOLUTION,
3430
step=1,
3531
),
3632
io.Int.Input(
3733
"height",
38-
display_name="height",
3934
default=0,
4035
min=0,
4136
max=MAX_RESOLUTION,
4237
step=1,
4338
),
44-
io.Boolean.Input(
45-
"capture_on_queue",
46-
default=True,
47-
),
39+
io.Boolean.Input("capture_on_queue", default=True),
4840
],
4941
outputs=[
50-
io.Image.Output(
51-
"IMAGE",
52-
),
42+
io.Image.Output(),
5343
],
5444
)
5545

0 commit comments

Comments
 (0)