From 43ea917122439c2b4246ff9c06f3df231baa1fd3 Mon Sep 17 00:00:00 2001 From: Terry Jia Date: Thu, 25 Sep 2025 22:39:13 -0400 Subject: [PATCH 1/4] Merge 3d animation node --- comfy_extras/nodes_load_3d.py | 87 +---------------------------------- 1 file changed, 2 insertions(+), 85 deletions(-) diff --git a/comfy_extras/nodes_load_3d.py b/comfy_extras/nodes_load_3d.py index 899608149aba..9b2a58259177 100644 --- a/comfy_extras/nodes_load_3d.py +++ b/comfy_extras/nodes_load_3d.py @@ -34,58 +34,6 @@ def INPUT_TYPES(s): "height": ("INT", {"default": 1024, "min": 1, "max": 4096, "step": 1}), }} - RETURN_TYPES = ("IMAGE", "MASK", "STRING", "IMAGE", "IMAGE", "LOAD3D_CAMERA", IO.VIDEO) - RETURN_NAMES = ("image", "mask", "mesh_path", "normal", "lineart", "camera_info", "recording_video") - - FUNCTION = "process" - EXPERIMENTAL = True - - CATEGORY = "3d" - - def process(self, model_file, image, **kwargs): - image_path = folder_paths.get_annotated_filepath(image['image']) - mask_path = folder_paths.get_annotated_filepath(image['mask']) - normal_path = folder_paths.get_annotated_filepath(image['normal']) - lineart_path = folder_paths.get_annotated_filepath(image['lineart']) - - load_image_node = nodes.LoadImage() - output_image, ignore_mask = load_image_node.load_image(image=image_path) - ignore_image, output_mask = load_image_node.load_image(image=mask_path) - normal_image, ignore_mask2 = load_image_node.load_image(image=normal_path) - lineart_image, ignore_mask3 = load_image_node.load_image(image=lineart_path) - - video = None - - if image['recording'] != "": - recording_video_path = folder_paths.get_annotated_filepath(image['recording']) - - video = VideoFromFile(recording_video_path) - - return output_image, output_mask, model_file, normal_image, lineart_image, image['camera_info'], video - -class Load3DAnimation(): - @classmethod - def INPUT_TYPES(s): - input_dir = os.path.join(folder_paths.get_input_directory(), "3d") - - os.makedirs(input_dir, exist_ok=True) - - input_path = Path(input_dir) - base_path = Path(folder_paths.get_input_directory()) - - files = [ - normalize_path(str(file_path.relative_to(base_path))) - for file_path in input_path.rglob("*") - if file_path.suffix.lower() in {'.gltf', '.glb', '.fbx'} - ] - - return {"required": { - "model_file": (sorted(files), {"file_upload": True}), - "image": ("LOAD_3D_ANIMATION", {}), - "width": ("INT", {"default": 1024, "min": 1, "max": 4096, "step": 1}), - "height": ("INT", {"default": 1024, "min": 1, "max": 4096, "step": 1}), - }} - RETURN_TYPES = ("IMAGE", "MASK", "STRING", "IMAGE", "LOAD3D_CAMERA", IO.VIDEO) RETURN_NAMES = ("image", "mask", "mesh_path", "normal", "camera_info", "recording_video") @@ -140,43 +88,12 @@ def process(self, model_file, **kwargs): } } -class Preview3DAnimation(): - @classmethod - def INPUT_TYPES(s): - return {"required": { - "model_file": ("STRING", {"default": "", "multiline": False}), - }, - "optional": { - "camera_info": ("LOAD3D_CAMERA", {}) - }} - - OUTPUT_NODE = True - RETURN_TYPES = () - - CATEGORY = "3d" - - FUNCTION = "process" - EXPERIMENTAL = True - - def process(self, model_file, **kwargs): - camera_info = kwargs.get("camera_info", None) - - return { - "ui": { - "result": [model_file, camera_info] - } - } - NODE_CLASS_MAPPINGS = { "Load3D": Load3D, - "Load3DAnimation": Load3DAnimation, "Preview3D": Preview3D, - "Preview3DAnimation": Preview3DAnimation } NODE_DISPLAY_NAME_MAPPINGS = { - "Load3D": "Load 3D", - "Load3DAnimation": "Load 3D - Animation", - "Preview3D": "Preview 3D", - "Preview3DAnimation": "Preview 3D - Animation" + "Load3D": "Load 3D & Animation", + "Preview3D": "Preview 3D & Animation", } From 5798b73483a5a41eedcadcadb79bbb39176da1fd Mon Sep 17 00:00:00 2001 From: Terry Jia Date: Sat, 4 Oct 2025 10:32:06 -0400 Subject: [PATCH 2/4] use image input as background --- comfy_extras/nodes_load_3d.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/comfy_extras/nodes_load_3d.py b/comfy_extras/nodes_load_3d.py index 9b2a58259177..54c66ef68ca9 100644 --- a/comfy_extras/nodes_load_3d.py +++ b/comfy_extras/nodes_load_3d.py @@ -7,6 +7,10 @@ from pathlib import Path +from PIL import Image +import numpy as np + +import uuid def normalize_path(path): return path.replace('\\', '/') @@ -68,7 +72,8 @@ def INPUT_TYPES(s): "model_file": ("STRING", {"default": "", "multiline": False}), }, "optional": { - "camera_info": ("LOAD3D_CAMERA", {}) + "camera_info": ("LOAD3D_CAMERA", {}), + "bg_image": ("IMAGE", {}) }} OUTPUT_NODE = True @@ -81,10 +86,24 @@ def INPUT_TYPES(s): def process(self, model_file, **kwargs): camera_info = kwargs.get("camera_info", None) + bg_image = kwargs.get("bg_image", None) + + bg_image_path = None + if bg_image is not None: + + img_array = (bg_image[0].cpu().numpy() * 255).astype(np.uint8) + img = Image.fromarray(img_array) + + temp_dir = folder_paths.get_temp_directory() + filename = f"bg_{uuid.uuid4().hex}.png" + bg_image_path = os.path.join(temp_dir, filename) + img.save(bg_image_path, compress_level=1) + + bg_image_path = f"temp/{filename}" return { "ui": { - "result": [model_file, camera_info] + "result": [model_file, camera_info, bg_image_path] } } From 3a60245893df0543aa803333646ed0ed10a13b1f Mon Sep 17 00:00:00 2001 From: Terry Jia Date: Sun, 5 Oct 2025 09:47:20 -0400 Subject: [PATCH 3/4] use image input as background for load3d --- comfy_extras/nodes_load_3d.py | 45 +++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/comfy_extras/nodes_load_3d.py b/comfy_extras/nodes_load_3d.py index 54c66ef68ca9..0f717a72fdd0 100644 --- a/comfy_extras/nodes_load_3d.py +++ b/comfy_extras/nodes_load_3d.py @@ -31,13 +31,20 @@ def INPUT_TYPES(s): if file_path.suffix.lower() in {'.gltf', '.glb', '.obj', '.fbx', '.stl'} ] - return {"required": { - "model_file": (sorted(files), {"file_upload": True}), - "image": ("LOAD_3D", {}), - "width": ("INT", {"default": 1024, "min": 1, "max": 4096, "step": 1}), - "height": ("INT", {"default": 1024, "min": 1, "max": 4096, "step": 1}), - }} + return { + "required": { + "model_file": (sorted(files), {"file_upload": True}), + "image": ("LOAD_3D", {}), + "width": ("INT", {"default": 1024, "min": 1, "max": 4096, "step": 1}), + "height": ("INT", {"default": 1024, "min": 1, "max": 4096, "step": 1}), + }, + "optional": { + "camera_info": ("LOAD3D_CAMERA", {}), + "bg_image": ("IMAGE", {}) + } + } + OUTPUT_NODE = True RETURN_TYPES = ("IMAGE", "MASK", "STRING", "IMAGE", "LOAD3D_CAMERA", IO.VIDEO) RETURN_NAMES = ("image", "mask", "mesh_path", "normal", "camera_info", "recording_video") @@ -63,7 +70,31 @@ def process(self, model_file, image, **kwargs): video = VideoFromFile(recording_video_path) - return output_image, output_mask, model_file, normal_image, image['camera_info'], video + camera_info = kwargs.get("camera_info", None) + bg_image = kwargs.get("bg_image", None) + + bg_image_path = None + if bg_image is not None: + img_array = (bg_image[0].cpu().numpy() * 255).astype(np.uint8) + img = Image.fromarray(img_array) + + temp_dir = folder_paths.get_temp_directory() + filename = f"bg_{uuid.uuid4().hex}.png" + bg_image_path = os.path.join(temp_dir, filename) + img.save(bg_image_path, compress_level=1) + + bg_image_path = f"temp/{filename}" + + ui_data = { + "model_file": [model_file], + "camera_info": [camera_info] if camera_info else [], + "bg_image_path": [bg_image_path] if bg_image_path else [] + } + + return { + "ui": ui_data, + "result": (output_image, output_mask, model_file, normal_image, image['camera_info'], video) + } class Preview3D(): @classmethod From 185a2baeb2d5d5602c80651b6815dade4c7b4f45 Mon Sep 17 00:00:00 2001 From: Terry Jia Date: Tue, 7 Oct 2025 21:05:18 -0400 Subject: [PATCH 4/4] revert --- comfy_extras/nodes_load_3d.py | 45 ++++++----------------------------- 1 file changed, 7 insertions(+), 38 deletions(-) diff --git a/comfy_extras/nodes_load_3d.py b/comfy_extras/nodes_load_3d.py index 0f717a72fdd0..54c66ef68ca9 100644 --- a/comfy_extras/nodes_load_3d.py +++ b/comfy_extras/nodes_load_3d.py @@ -31,20 +31,13 @@ def INPUT_TYPES(s): if file_path.suffix.lower() in {'.gltf', '.glb', '.obj', '.fbx', '.stl'} ] - return { - "required": { - "model_file": (sorted(files), {"file_upload": True}), - "image": ("LOAD_3D", {}), - "width": ("INT", {"default": 1024, "min": 1, "max": 4096, "step": 1}), - "height": ("INT", {"default": 1024, "min": 1, "max": 4096, "step": 1}), - }, - "optional": { - "camera_info": ("LOAD3D_CAMERA", {}), - "bg_image": ("IMAGE", {}) - } - } + return {"required": { + "model_file": (sorted(files), {"file_upload": True}), + "image": ("LOAD_3D", {}), + "width": ("INT", {"default": 1024, "min": 1, "max": 4096, "step": 1}), + "height": ("INT", {"default": 1024, "min": 1, "max": 4096, "step": 1}), + }} - OUTPUT_NODE = True RETURN_TYPES = ("IMAGE", "MASK", "STRING", "IMAGE", "LOAD3D_CAMERA", IO.VIDEO) RETURN_NAMES = ("image", "mask", "mesh_path", "normal", "camera_info", "recording_video") @@ -70,31 +63,7 @@ def process(self, model_file, image, **kwargs): video = VideoFromFile(recording_video_path) - camera_info = kwargs.get("camera_info", None) - bg_image = kwargs.get("bg_image", None) - - bg_image_path = None - if bg_image is not None: - img_array = (bg_image[0].cpu().numpy() * 255).astype(np.uint8) - img = Image.fromarray(img_array) - - temp_dir = folder_paths.get_temp_directory() - filename = f"bg_{uuid.uuid4().hex}.png" - bg_image_path = os.path.join(temp_dir, filename) - img.save(bg_image_path, compress_level=1) - - bg_image_path = f"temp/{filename}" - - ui_data = { - "model_file": [model_file], - "camera_info": [camera_info] if camera_info else [], - "bg_image_path": [bg_image_path] if bg_image_path else [] - } - - return { - "ui": ui_data, - "result": (output_image, output_mask, model_file, normal_image, image['camera_info'], video) - } + return output_image, output_mask, model_file, normal_image, image['camera_info'], video class Preview3D(): @classmethod