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

Upload blender_plugin/interiorfusion_blender.py

Browse files
blender_plugin/interiorfusion_blender.py ADDED
@@ -0,0 +1,326 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Blender plugin for InteriorFusion.
2
+
3
+ Features:
4
+ - Generate 3D scene from reference image
5
+ - Import generated meshes with PBR materials
6
+ - Interactive scene editing within Blender
7
+ - Export to game engines
8
+ """
9
+
10
+ import os
11
+ import tempfile
12
+ import bpy
13
+ import bmesh
14
+ from bpy.props import StringProperty, BoolProperty, EnumProperty, FloatProperty
15
+ from bpy.types import Operator, Panel
16
+ from mathutils import Vector, Matrix
17
+
18
+
19
+ bl_info = {
20
+ "name": "InteriorFusion",
21
+ "author": "InteriorFusion Research Team",
22
+ "version": (0, 1, 0),
23
+ "blender": (3, 6, 0),
24
+ "location": "3D Viewport > Sidebar > InteriorFusion",
25
+ "description": "Single image to editable 3D interior scene",
26
+ "category": "3D View",
27
+ "support": "COMMUNITY",
28
+ }
29
+
30
+
31
+ class INTERIORFUSION_OT_generate_scene(Operator):
32
+ """Generate 3D interior scene from reference image."""
33
+ bl_idname = "interiorfusion.generate_scene"
34
+ bl_label = "Generate 3D Scene"
35
+ bl_options = {'REGISTER', 'UNDO'}
36
+
37
+ image_path: StringProperty(
38
+ name="Image Path",
39
+ description="Path to interior photo",
40
+ subtype='FILE_PATH',
41
+ )
42
+
43
+ room_type: EnumProperty(
44
+ name="Room Type",
45
+ items=[
46
+ ('AUTO', 'Auto Detect', 'Automatically detect room type'),
47
+ ('LIVING_ROOM', 'Living Room', 'Living room / lounge'),
48
+ ('BEDROOM', 'Bedroom', 'Bedroom'),
49
+ ('KITCHEN', 'Kitchen', 'Kitchen'),
50
+ ('DINING_ROOM', 'Dining Room', 'Dining room'),
51
+ ('OFFICE', 'Office', 'Office / study'),
52
+ ],
53
+ default='AUTO',
54
+ )
55
+
56
+ style: EnumProperty(
57
+ name="Style",
58
+ items=[
59
+ ('AUTO', 'Auto Detect', 'Automatically detect style'),
60
+ ('MODERN', 'Modern', 'Modern contemporary'),
61
+ ('SCANDINAVIAN', 'Scandinavian', 'Scandinavian minimalist'),
62
+ ('LUXURY', 'Luxury', 'Luxury / upscale'),
63
+ ('INDUSTRIAL', 'Industrial', 'Industrial loft'),
64
+ ('MINIMALIST', 'Minimalist', 'Minimalist'),
65
+ ],
66
+ default='AUTO',
67
+ )
68
+
69
+ use_pbr: BoolProperty(
70
+ name="Use PBR Materials",
71
+ description="Generate metallic/roughness/normal maps",
72
+ default=True,
73
+ )
74
+
75
+ def execute(self, context):
76
+ from interiorfusion.pipelines import InteriorFusionPipeline
77
+ from PIL import Image
78
+
79
+ # Check image exists
80
+ if not os.path.exists(self.image_path):
81
+ self.report({'ERROR'}, f"Image not found: {self.image_path}")
82
+ return {'CANCELLED'}
83
+
84
+ # Load image
85
+ image = Image.open(self.image_path).convert("RGB")
86
+
87
+ # Generate scene
88
+ self.report({'INFO'}, "Generating 3D scene...")
89
+
90
+ pipeline = InteriorFusionPipeline(
91
+ model_size="L",
92
+ device="cuda" if bpy.app.version >= (3, 5) else "cpu",
93
+ use_pbr=self.use_pbr,
94
+ )
95
+
96
+ output = pipeline(
97
+ image=image,
98
+ room_type_hint=self.room_type if self.room_type != 'AUTO' else None,
99
+ style_hint=self.style if self.style != 'AUTO' else None,
100
+ )
101
+
102
+ # Import scene into Blender
103
+ self.import_scene(context, output)
104
+
105
+ self.report({'INFO'},
106
+ f"Scene generated: {output.room_type} ({output.processing_time:.1f}s)")
107
+
108
+ return {'FINISHED'}
109
+
110
+ def import_scene(self, context, output):
111
+ """Import generated scene into Blender."""
112
+ # Import room shell
113
+ if output.room_shell_mesh is not None:
114
+ self.import_mesh(output.room_shell_mesh, "Room_Shell")
115
+
116
+ # Import objects
117
+ for i, obj_mesh in enumerate(output.object_meshes):
118
+ obj_name = f"Furniture_{i:02d}"
119
+ self.import_mesh(obj_mesh, obj_name)
120
+
121
+ # Create scene graph collection
122
+ scene_collection = bpy.data.collections.new("InteriorFusion_Scene")
123
+ context.scene.collection.children.link(scene_collection)
124
+
125
+ # Move objects to scene collection
126
+ for obj in context.selected_objects:
127
+ scene_collection.objects.link(obj)
128
+ context.scene.collection.objects.unlink(obj)
129
+
130
+ def import_mesh(self, mesh, name):
131
+ """Import a trimesh mesh into Blender."""
132
+ try:
133
+ import trimesh
134
+ except ImportError:
135
+ self.report({'WARNING'}, "trimesh not available, skipping mesh import")
136
+ return None
137
+
138
+ # Create Blender mesh
139
+ bm = bmesh.new()
140
+
141
+ # Add vertices
142
+ verts = {}
143
+ for i, v in enumerate(mesh.vertices):
144
+ verts[i] = bm.verts.new(Vector(v))
145
+
146
+ # Add faces
147
+ bm.verts.ensure_lookup_table()
148
+ for face in mesh.faces:
149
+ try:
150
+ face_verts = [verts[v_idx] for v_idx in face]
151
+ bm.faces.new(face_verts)
152
+ except Exception:
153
+ pass
154
+
155
+ # Create mesh object
156
+ mesh_data = bpy.data.meshes.new(name)
157
+ bm.to_mesh(mesh_data)
158
+ bm.free()
159
+
160
+ obj = bpy.data.objects.new(name, mesh_data)
161
+ bpy.context.collection.objects.link(obj)
162
+
163
+ # Apply materials if available
164
+ if hasattr(mesh, 'materials') and mesh.materials:
165
+ for mat_name, mat_data in mesh.materials.items():
166
+ mat = self.create_pbr_material(mat_name, mat_data)
167
+ mesh_data.materials.append(mat)
168
+
169
+ return obj
170
+
171
+ def create_pbr_material(self, name, material_data):
172
+ """Create a Blender PBR material."""
173
+ mat = bpy.data.materials.new(name=name)
174
+ mat.use_nodes = True
175
+
176
+ # Get principled BSDF
177
+ bsdf = mat.node_tree.nodes["Principled BSDF"]
178
+
179
+ # Set parameters
180
+ albedo = material_data.get("albedo", [0.7, 0.7, 0.7])
181
+ bsdf.inputs['Base Color'].default_value = (*albedo, 1.0)
182
+ bsdf.inputs['Metallic'].default_value = material_data.get("metallic", 0.0)
183
+ bsdf.inputs['Roughness'].default_value = material_data.get("roughness", 0.5)
184
+
185
+ return mat
186
+
187
+
188
+ class INTERIORFUSION_OT_edit_object(Operator):
189
+ """Edit a selected furniture object."""
190
+ bl_idname = "interiorfusion.edit_object"
191
+ bl_label = "Edit Selected Object"
192
+ bl_options = {'REGISTER', 'UNDO'}
193
+
194
+ action: EnumProperty(
195
+ name="Action",
196
+ items=[
197
+ ('MOVE', 'Move', 'Move to new position'),
198
+ ('REPLACE', 'Replace', 'Replace with new object'),
199
+ ('REMOVE', 'Remove', 'Remove from scene'),
200
+ ('SCALE', 'Scale', 'Change dimensions'),
201
+ ],
202
+ )
203
+
204
+ def execute(self, context):
205
+ obj = context.active_object
206
+ if obj is None:
207
+ self.report({'ERROR'}, "No object selected")
208
+ return {'CANCELLED'}
209
+
210
+ if self.action == 'REMOVE':
211
+ bpy.data.objects.remove(obj, do_unlink=True)
212
+ self.report({'INFO'}, f"Removed {obj.name}")
213
+
214
+ elif self.action == 'MOVE':
215
+ # Enter move mode
216
+ bpy.ops.transform.translate('INVOKE_DEFAULT')
217
+
218
+ elif self.action == 'SCALE':
219
+ # Enter scale mode
220
+ bpy.ops.transform.resize('INVOKE_DEFAULT')
221
+
222
+ return {'FINISHED'}
223
+
224
+
225
+ class INTERIORFUSION_PT_panel(Panel):
226
+ """InteriorFusion main panel."""
227
+ bl_label = "InteriorFusion"
228
+ bl_idname = "INTERIORFUSION_PT_panel"
229
+ bl_space_type = 'VIEW_3D'
230
+ bl_region_type = 'UI'
231
+ bl_category = 'InteriorFusion'
232
+
233
+ def draw(self, context):
234
+ layout = self.layout
235
+
236
+ # Scene generation
237
+ box = layout.box()
238
+ box.label(text="Scene Generation", icon='SCENE_DATA')
239
+
240
+ box.prop(context.scene, "interiorfusion_image_path")
241
+ box.prop(context.scene, "interiorfusion_room_type")
242
+ box.prop(context.scene, "interiorfusion_style")
243
+ box.prop(context.scene, "interiorfusion_use_pbr")
244
+
245
+ box.operator("interiorfusion.generate_scene", icon='MESH_CUBE')
246
+
247
+ # Object editing
248
+ box = layout.box()
249
+ box.label(text="Object Editing", icon='OBJECT_DATA')
250
+
251
+ if context.active_object:
252
+ box.label(text=f"Selected: {context.active_object.name}")
253
+ row = box.row()
254
+ row.operator("interiorfusion.edit_object", text="Move").action = 'MOVE'
255
+ row.operator("interiorfusion.edit_object", text="Scale").action = 'SCALE'
256
+ row = box.row()
257
+ row.operator("interiorfusion.edit_object", text="Remove").action = 'REMOVE'
258
+ else:
259
+ box.label(text="Select an object to edit")
260
+
261
+ # Export
262
+ box = layout.box()
263
+ box.label(text="Export", icon='EXPORT')
264
+ box.operator("export_scene.gltf", text="Export GLB", icon='EXPORT')
265
+ box.operator("wm.obj_export", text="Export OBJ", icon='EXPORT')
266
+
267
+
268
+ def register():
269
+ # Scene properties
270
+ bpy.types.Scene.interiorfusion_image_path = StringProperty(
271
+ name="Image Path",
272
+ description="Path to interior photo",
273
+ subtype='FILE_PATH',
274
+ )
275
+
276
+ bpy.types.Scene.interiorfusion_room_type = EnumProperty(
277
+ name="Room Type",
278
+ items=[
279
+ ('AUTO', 'Auto Detect', 'Auto'),
280
+ ('LIVING_ROOM', 'Living Room', 'Living room'),
281
+ ('BEDROOM', 'Bedroom', 'Bedroom'),
282
+ ('KITCHEN', 'Kitchen', 'Kitchen'),
283
+ ('DINING_ROOM', 'Dining Room', 'Dining room'),
284
+ ('OFFICE', 'Office', 'Office'),
285
+ ],
286
+ default='AUTO',
287
+ )
288
+
289
+ bpy.types.Scene.interiorfusion_style = EnumProperty(
290
+ name="Style",
291
+ items=[
292
+ ('AUTO', 'Auto Detect', 'Auto'),
293
+ ('MODERN', 'Modern', 'Modern'),
294
+ ('SCANDINAVIAN', 'Scandinavian', 'Scandinavian'),
295
+ ('LUXURY', 'Luxury', 'Luxury'),
296
+ ('INDUSTRIAL', 'Industrial', 'Industrial'),
297
+ ('MINIMALIST', 'Minimalist', 'Minimalist'),
298
+ ],
299
+ default='AUTO',
300
+ )
301
+
302
+ bpy.types.Scene.interiorfusion_use_pbr = BoolProperty(
303
+ name="Use PBR",
304
+ description="Generate PBR materials",
305
+ default=True,
306
+ )
307
+
308
+ # Register classes
309
+ bpy.utils.register_class(INTERIORFUSION_OT_generate_scene)
310
+ bpy.utils.register_class(INTERIORFUSION_OT_edit_object)
311
+ bpy.utils.register_class(INTERIORFUSION_PT_panel)
312
+
313
+
314
+ def unregister():
315
+ bpy.utils.unregister_class(INTERIORFUSION_PT_panel)
316
+ bpy.utils.unregister_class(INTERIORFUSION_OT_edit_object)
317
+ bpy.utils.unregister_class(INTERIORFUSION_OT_generate_scene)
318
+
319
+ del bpy.types.Scene.interiorfusion_image_path
320
+ del bpy.types.Scene.interiorfusion_room_type
321
+ del bpy.types.Scene.interiorfusion_style
322
+ del bpy.types.Scene.interiorfusion_use_pbr
323
+
324
+
325
+ if __name__ == "__main__":
326
+ register()