Skip to content

Commit 440268d

Browse files
authored
convert nodes_load_3d.py to V3 schema (#10990)
1 parent 87c104b commit 440268d

File tree

2 files changed

+70
-68
lines changed

2 files changed

+70
-68
lines changed

comfy_api/latest/_ui.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import json
44
import os
55
import random
6+
import uuid
67
from io import BytesIO
78
from typing import Type
89

@@ -436,9 +437,19 @@ class PreviewUI3D(_UIOutput):
436437
def __init__(self, model_file, camera_info, **kwargs):
437438
self.model_file = model_file
438439
self.camera_info = camera_info
440+
self.bg_image_path = None
441+
bg_image = kwargs.get("bg_image", None)
442+
if bg_image is not None:
443+
img_array = (bg_image[0].cpu().numpy() * 255).astype(np.uint8)
444+
img = PILImage.fromarray(img_array)
445+
temp_dir = folder_paths.get_temp_directory()
446+
filename = f"bg_{uuid.uuid4().hex}.png"
447+
bg_image_path = os.path.join(temp_dir, filename)
448+
img.save(bg_image_path, compress_level=1)
449+
self.bg_image_path = f"temp/{filename}"
439450

440451
def as_dict(self):
441-
return {"result": [self.model_file, self.camera_info]}
452+
return {"result": [self.model_file, self.camera_info, self.bg_image_path]}
442453

443454

444455
class PreviewText(_UIOutput):

comfy_extras/nodes_load_3d.py

Lines changed: 58 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,18 @@
22
import folder_paths
33
import os
44

5-
from comfy.comfy_types import IO
6-
from comfy_api.input_impl import VideoFromFile
5+
from typing_extensions import override
6+
from comfy_api.latest import IO, ComfyExtension, InputImpl, UI
77

88
from pathlib import Path
99

10-
from PIL import Image
11-
import numpy as np
12-
13-
import uuid
1410

1511
def normalize_path(path):
1612
return path.replace('\\', '/')
1713

18-
class Load3D():
14+
class Load3D(IO.ComfyNode):
1915
@classmethod
20-
def INPUT_TYPES(s):
16+
def define_schema(cls):
2117
input_dir = os.path.join(folder_paths.get_input_directory(), "3d")
2218

2319
os.makedirs(input_dir, exist_ok=True)
@@ -30,23 +26,29 @@ def INPUT_TYPES(s):
3026
for file_path in input_path.rglob("*")
3127
if file_path.suffix.lower() in {'.gltf', '.glb', '.obj', '.fbx', '.stl'}
3228
]
29+
return IO.Schema(
30+
node_id="Load3D",
31+
display_name="Load 3D & Animation",
32+
category="3d",
33+
is_experimental=True,
34+
inputs=[
35+
IO.Combo.Input("model_file", options=sorted(files), upload=IO.UploadType.model),
36+
IO.Load3D.Input("image"),
37+
IO.Int.Input("width", default=1024, min=1, max=4096, step=1),
38+
IO.Int.Input("height", default=1024, min=1, max=4096, step=1),
39+
],
40+
outputs=[
41+
IO.Image.Output(display_name="image"),
42+
IO.Mask.Output(display_name="mask"),
43+
IO.String.Output(display_name="mesh_path"),
44+
IO.Image.Output(display_name="normal"),
45+
IO.Load3DCamera.Output(display_name="camera_info"),
46+
IO.Video.Output(display_name="recording_video"),
47+
],
48+
)
3349

34-
return {"required": {
35-
"model_file": (sorted(files), {"file_upload": True}),
36-
"image": ("LOAD_3D", {}),
37-
"width": ("INT", {"default": 1024, "min": 1, "max": 4096, "step": 1}),
38-
"height": ("INT", {"default": 1024, "min": 1, "max": 4096, "step": 1}),
39-
}}
40-
41-
RETURN_TYPES = ("IMAGE", "MASK", "STRING", "IMAGE", "LOAD3D_CAMERA", IO.VIDEO)
42-
RETURN_NAMES = ("image", "mask", "mesh_path", "normal", "camera_info", "recording_video")
43-
44-
FUNCTION = "process"
45-
EXPERIMENTAL = True
46-
47-
CATEGORY = "3d"
48-
49-
def process(self, model_file, image, **kwargs):
50+
@classmethod
51+
def execute(cls, model_file, image, **kwargs) -> IO.NodeOutput:
5052
image_path = folder_paths.get_annotated_filepath(image['image'])
5153
mask_path = folder_paths.get_annotated_filepath(image['mask'])
5254
normal_path = folder_paths.get_annotated_filepath(image['normal'])
@@ -61,58 +63,47 @@ def process(self, model_file, image, **kwargs):
6163
if image['recording'] != "":
6264
recording_video_path = folder_paths.get_annotated_filepath(image['recording'])
6365

64-
video = VideoFromFile(recording_video_path)
66+
video = InputImpl.VideoFromFile(recording_video_path)
6567

66-
return output_image, output_mask, model_file, normal_image, image['camera_info'], video
68+
return IO.NodeOutput(output_image, output_mask, model_file, normal_image, image['camera_info'], video)
6769

68-
class Preview3D():
69-
@classmethod
70-
def INPUT_TYPES(s):
71-
return {"required": {
72-
"model_file": ("STRING", {"default": "", "multiline": False}),
73-
},
74-
"optional": {
75-
"camera_info": ("LOAD3D_CAMERA", {}),
76-
"bg_image": ("IMAGE", {})
77-
}}
70+
process = execute # TODO: remove
7871

79-
OUTPUT_NODE = True
80-
RETURN_TYPES = ()
8172

82-
CATEGORY = "3d"
83-
84-
FUNCTION = "process"
85-
EXPERIMENTAL = True
73+
class Preview3D(IO.ComfyNode):
74+
@classmethod
75+
def define_schema(cls):
76+
return IO.Schema(
77+
node_id="Preview3D",
78+
display_name="Preview 3D & Animation",
79+
category="3d",
80+
is_experimental=True,
81+
is_output_node=True,
82+
inputs=[
83+
IO.String.Input("model_file", default="", multiline=False),
84+
IO.Load3DCamera.Input("camera_info", optional=True),
85+
IO.Image.Input("bg_image", optional=True),
86+
],
87+
outputs=[],
88+
)
8689

87-
def process(self, model_file, **kwargs):
90+
@classmethod
91+
def execute(cls, model_file, **kwargs) -> IO.NodeOutput:
8892
camera_info = kwargs.get("camera_info", None)
8993
bg_image = kwargs.get("bg_image", None)
94+
return IO.NodeOutput(ui=UI.PreviewUI3D(model_file, camera_info, bg_image=bg_image))
9095

91-
bg_image_path = None
92-
if bg_image is not None:
96+
process = execute # TODO: remove
9397

94-
img_array = (bg_image[0].cpu().numpy() * 255).astype(np.uint8)
95-
img = Image.fromarray(img_array)
9698

97-
temp_dir = folder_paths.get_temp_directory()
98-
filename = f"bg_{uuid.uuid4().hex}.png"
99-
bg_image_path = os.path.join(temp_dir, filename)
100-
img.save(bg_image_path, compress_level=1)
101-
102-
bg_image_path = f"temp/{filename}"
103-
104-
return {
105-
"ui": {
106-
"result": [model_file, camera_info, bg_image_path]
107-
}
108-
}
99+
class Load3DExtension(ComfyExtension):
100+
@override
101+
async def get_node_list(self) -> list[type[IO.ComfyNode]]:
102+
return [
103+
Load3D,
104+
Preview3D,
105+
]
109106

110-
NODE_CLASS_MAPPINGS = {
111-
"Load3D": Load3D,
112-
"Preview3D": Preview3D,
113-
}
114107

115-
NODE_DISPLAY_NAME_MAPPINGS = {
116-
"Load3D": "Load 3D & Animation",
117-
"Preview3D": "Preview 3D & Animation",
118-
}
108+
async def comfy_entrypoint() -> Load3DExtension:
109+
return Load3DExtension()

0 commit comments

Comments
 (0)