stevee00 commited on
Commit
40a7603
·
verified ·
1 Parent(s): b048c26

Upload comfyui_nodes/interiorfusion_nodes.py

Browse files
Files changed (1) hide show
  1. comfyui_nodes/interiorfusion_nodes.py +206 -0
comfyui_nodes/interiorfusion_nodes.py ADDED
@@ -0,0 +1,206 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """ComfyUI nodes for InteriorFusion.
2
+
3
+ Nodes:
4
+ - InteriorFusionSceneNode: Full pipeline from image to scene
5
+ - InteriorFusionObjectNode: Generate single furniture object
6
+ - InteriorFusionMaterialNode: Apply PBR materials to mesh
7
+ - InteriorFusionExportNode: Export to various formats
8
+ """
9
+
10
+ import os
11
+ import tempfile
12
+ from pathlib import Path
13
+
14
+ import numpy as np
15
+ import torch
16
+ from PIL import Image
17
+
18
+
19
+ class InteriorFusionSceneNode:
20
+ """Generate complete 3D interior scene from single image."""
21
+
22
+ @classmethod
23
+ def INPUT_TYPES(cls):
24
+ return {
25
+ "required": {
26
+ "image": ("IMAGE",),
27
+ "model_size": (["S", "L", "XL"], {"default": "L"}),
28
+ "room_type": (["auto", "living_room", "bedroom", "kitchen",
29
+ "dining_room", "office"], {"default": "auto"}),
30
+ "style": (["auto", "modern", "scandinavian", "luxury",
31
+ "industrial", "minimalist"], {"default": "auto"}),
32
+ "use_pbr": ("BOOLEAN", {"default": True}),
33
+ "use_gaussian": ("BOOLEAN", {"default": True}),
34
+ }
35
+ }
36
+
37
+ RETURN_TYPES = ("MESH", "SCENE_GRAPH", "STRING")
38
+ RETURN_NAMES = ("scene_mesh", "scene_graph", "metadata")
39
+ FUNCTION = "generate_scene"
40
+ CATEGORY = "InteriorFusion"
41
+
42
+ def __init__(self):
43
+ self.pipeline = None
44
+
45
+ def generate_scene(self, image, model_size, room_type, style, use_pbr, use_gaussian):
46
+ from interiorfusion.pipelines import InteriorFusionPipeline
47
+
48
+ if self.pipeline is None or self.pipeline.model_size != model_size:
49
+ device = "cuda" if torch.cuda.is_available() else "cpu"
50
+ self.pipeline = InteriorFusionPipeline(
51
+ model_size=model_size,
52
+ device=device,
53
+ dtype=torch.float16,
54
+ use_pbr=use_pbr,
55
+ use_gaussian_splatting=use_gaussian,
56
+ )
57
+
58
+ # Convert ComfyUI image tensor to PIL
59
+ # ComfyUI images are [B, H, W, C] in range 0-1
60
+ img_np = (image[0].cpu().numpy() * 255).astype(np.uint8)
61
+ pil_image = Image.fromarray(img_np)
62
+
63
+ # Generate
64
+ output = self.pipeline(
65
+ image=pil_image,
66
+ room_type_hint=room_type if room_type != "auto" else None,
67
+ style_hint=style if style != "auto" else None,
68
+ )
69
+
70
+ metadata = f"""
71
+ Room Type: {output.room_type}
72
+ Style: {output.style}
73
+ Objects: {len(output.object_meshes)}
74
+ Materials: {len(output.pbr_materials)}
75
+ Time: {output.processing_time:.1f}s
76
+ """.strip()
77
+
78
+ return (output.scene_mesh, output.scene_graph, metadata)
79
+
80
+
81
+ class InteriorFusionObjectNode:
82
+ """Generate a single furniture object from image."""
83
+
84
+ @classmethod
85
+ def INPUT_TYPES(cls):
86
+ return {
87
+ "required": {
88
+ "object_image": ("IMAGE",),
89
+ "object_mask": ("MASK",),
90
+ "model_size": (["S", "L", "XL"], {"default": "L"}),
91
+ }
92
+ }
93
+
94
+ RETURN_TYPES = ("MESH",)
95
+ RETURN_NAMES = ("object_mesh",)
96
+ FUNCTION = "generate_object"
97
+ CATEGORY = "InteriorFusion"
98
+
99
+ def generate_object(self, object_image, object_mask, model_size):
100
+ from interiorfusion.models.multiview_generation import MultiViewGenerationModule
101
+ from interiorfusion.models.reconstruction_3d import Reconstruction3DModule
102
+
103
+ device = "cuda" if torch.cuda.is_available() else "cpu"
104
+
105
+ # Convert inputs
106
+ img_np = (object_image[0].cpu().numpy() * 255).astype(np.uint8)
107
+ mask_np = object_mask[0].cpu().numpy()
108
+
109
+ pil_image = Image.fromarray(img_np)
110
+
111
+ # Generate multi-view
112
+ mv_gen = MultiViewGenerationModule(model_size=model_size, device=device)
113
+ multiviews = mv_gen.generate_object_views(pil_image, mask_np)
114
+
115
+ # Reconstruct 3D
116
+ recon = Reconstruction3DModule(model_size=model_size, device=device)
117
+ mesh, _ = recon.reconstruct_object(multiviews)
118
+
119
+ return (mesh,)
120
+
121
+
122
+ class InteriorFusionMaterialNode:
123
+ """Apply PBR materials to a mesh."""
124
+
125
+ @classmethod
126
+ def INPUT_TYPES(cls):
127
+ return {
128
+ "required": {
129
+ "mesh": ("MESH",),
130
+ "material_type": (["wood", "fabric", "metal", "glass",
131
+ "plastic", "leather", "wall", "floor"],
132
+ {"default": "wood"}),
133
+ "color_hex": ("STRING", {"default": "#8B4513"}),
134
+ "metallic": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0}),
135
+ "roughness": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0}),
136
+ }
137
+ }
138
+
139
+ RETURN_TYPES = ("MESH",)
140
+ RETURN_NAMES = ("textured_mesh",)
141
+ FUNCTION = "apply_material"
142
+ CATEGORY = "InteriorFusion"
143
+
144
+ def apply_material(self, mesh, material_type, color_hex, metallic, roughness):
145
+ # Parse color
146
+ color = tuple(int(color_hex.lstrip('#')[i:i+2], 16) / 255.0
147
+ for i in (0, 2, 4))
148
+
149
+ material = {
150
+ "type": material_type,
151
+ "albedo": list(color),
152
+ "metallic": metallic,
153
+ "roughness": roughness,
154
+ }
155
+
156
+ # Apply to mesh
157
+ if hasattr(mesh, 'materials'):
158
+ mesh.materials = {"default": material}
159
+
160
+ return (mesh,)
161
+
162
+
163
+ class InteriorFusionExportNode:
164
+ """Export mesh to various 3D formats."""
165
+
166
+ @classmethod
167
+ def INPUT_TYPES(cls):
168
+ return {
169
+ "required": {
170
+ "mesh": ("MESH",),
171
+ "format": (["glb", "fbx", "obj", "usdz", "ply"],
172
+ {"default": "glb"}),
173
+ "filename": ("STRING", {"default": "scene"}),
174
+ }
175
+ }
176
+
177
+ RETURN_TYPES = ("STRING",)
178
+ RETURN_NAMES = ("file_path",)
179
+ FUNCTION = "export_mesh"
180
+ CATEGORY = "InteriorFusion"
181
+
182
+ def export_mesh(self, mesh, format, filename):
183
+ from interiorfusion.utils.mesh_utils import export_mesh as do_export
184
+
185
+ output_dir = tempfile.gettempdir()
186
+ output_path = os.path.join(output_dir, f"{filename}.{format}")
187
+
188
+ do_export(mesh, output_path, format=format)
189
+
190
+ return (output_path,)
191
+
192
+
193
+ # Node mappings for ComfyUI
194
+ NODE_CLASS_MAPPINGS = {
195
+ "InteriorFusionScene": InteriorFusionSceneNode,
196
+ "InteriorFusionObject": InteriorFusionObjectNode,
197
+ "InteriorFusionMaterial": InteriorFusionMaterialNode,
198
+ "InteriorFusionExport": InteriorFusionExportNode,
199
+ }
200
+
201
+ NODE_DISPLAY_NAME_MAPPINGS = {
202
+ "InteriorFusionScene": "InteriorFusion: Generate Scene",
203
+ "InteriorFusionObject": "InteriorFusion: Generate Object",
204
+ "InteriorFusionMaterial": "InteriorFusion: Apply Material",
205
+ "InteriorFusionExport": "InteriorFusion: Export Mesh",
206
+ }