OverviewFilesCommitsIssuesDocsReleasesPeers

blender-bridge commits / a830376

Fix material detection and geometry nodes instance realization (v0.12.1)
Material changes (shading-only edits) were never detected because the depsgraph handler only checked is_updated_geometry and is_updated_transform. Added is_updated_shading check that promotes material changes to UPDATE_VERTEX so the full blob including material data gets written. Geometry nodes "Instance on Points" produces instances that to_mesh() does not realize, returning an empty mesh. Added _realize_instances() that collects instances from depsgraph.object_instances, transforms vertices to parent local space, and combines into a single mesh for export. Includes proper cleanup of temporary bpy.data.meshes objects. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
a83037643d3188de8b782468f62f150e41a1fdd1
by plankatron <fleeky@gmail.com> · 2026-03-03T19:27:53.000Z

Changed files (5) +97 -3

blender_addon/__init__.py +1 -1
……@@ -11,7 +11,7 @@ Usage:
1111
1212import bpy
1313
14ADDON_VERSION = "0.12.0"
14ADDON_VERSION = "0.12.1"
1515
1616# Support hot-reload (F3 > Reload Scripts)
1717if "shm_protocol" in locals():
blender_addon/blender_manifest.toml +1 -1
……@@ -2,7 +2,7 @@ schema_version = "1.0.0"
22id = "blender_godot_bridge"
33name = "Blender-Godot Bridge"
44tagline = "Real-time bidirectional scene sync with Godot"
5version = "0.12.0"
5version = "0.12.1"
66blender_version_min = "4.2.0"
77type = "add-on"
88maintainer = "blender-bridge <noreply@localhost>"
blender_addon/change_detector.py +94 -0
……@@ -199,6 +199,18 @@ class ChangeDetector:
199199 self._pending[name] = UPDATE_FULL
200200 # else: already VERTEX or FULL, keep it
201201
202 # Material/shading change: needs geometry re-export to include
203 # updated material data in the blob
204 if update.is_updated_shading and obj.type == "MESH":
205 existing = self._pending.get(name, -1)
206 if existing == -1:
207 if name not in self._name_to_id:
208 self._pending[name] = UPDATE_FULL
209 else:
210 self._pending[name] = UPDATE_VERTEX
211 elif existing == UPDATE_TRANSFORM:
212 self._pending[name] = UPDATE_VERTEX
213
202214 if update.is_updated_transform:
203215 # Hash-based jitter reduction: skip if transform unchanged
204216 new_hash = self._hash_transform(obj.matrix_world)
……@@ -213,6 +225,65 @@ class ChangeDetector:
213225 self._pending[name] = UPDATE_TRANSFORM
214226 # else: don't downgrade from VERTEX/FULL to TRANSFORM
215227
228 # -- Geometry nodes instance realization --------------------------------
229
230 @staticmethod
231 def _realize_instances(obj, depsgraph):
232 """Collect geometry nodes instances and combine into a single Blender mesh.
233
234 When geometry nodes use Instance on Points (or similar), to_mesh()
235 returns empty because instances aren't realized. This method iterates
236 depsgraph.object_instances, collects all instances parented to `obj`,
237 transforms their vertices into obj's local space, and creates a
238 combined mesh.
239 """
240 parent_inv = obj.matrix_world.inverted()
241 all_verts = []
242 all_faces = []
243 vert_offset = 0
244
245 for inst in depsgraph.object_instances:
246 if not inst.is_instance:
247 continue
248 if inst.parent is None or inst.parent.original != obj:
249 continue
250
251 inst_obj = inst.object
252 inst_mesh = inst_obj.to_mesh()
253 if inst_mesh is None or len(inst_mesh.vertices) == 0:
254 if inst_mesh is not None:
255 inst_obj.to_mesh_clear()
256 continue
257
258 inst_mesh.calc_loop_triangles()
259 if len(inst_mesh.loop_triangles) == 0:
260 inst_obj.to_mesh_clear()
261 continue
262
263 # Transform instance verts to parent local space
264 local_mat = parent_inv @ inst.matrix_world
265 for v in inst_mesh.vertices:
266 pos = local_mat @ v.co
267 all_verts.append((pos.x, pos.y, pos.z))
268
269 for tri in inst_mesh.loop_triangles:
270 all_faces.append((
271 tri.vertices[0] + vert_offset,
272 tri.vertices[1] + vert_offset,
273 tri.vertices[2] + vert_offset,
274 ))
275
276 vert_offset += len(inst_mesh.vertices)
277 inst_obj.to_mesh_clear()
278
279 if not all_verts:
280 return None
281
282 combined = bpy.data.meshes.new("_bridge_realized_tmp")
283 combined.from_pydata(all_verts, [], all_faces)
284 combined.update()
285 return combined
286
216287 # -- Flush accumulated changes to SHM ----------------------------------
217288
218289 def flush(self) -> int:
……@@ -327,6 +398,23 @@ class ChangeDetector:
327398 else:
328399 mesh = obj.data
329400 eval_obj = None
401 realized_mesh = None # Track standalone mesh that needs bpy.data.meshes.remove()
402
403 if mesh is not None:
404 # Geometry nodes with instances (Instance on Points etc):
405 # to_mesh() doesn't realize instances. Collect them from
406 # depsgraph and combine into a single mesh.
407 mesh.calc_loop_triangles()
408 if (
409 len(mesh.loop_triangles) == 0
410 and depsgraph is not None
411 and any(m.type == "NODES" for m in obj.modifiers)
412 ):
413 if eval_obj is not None:
414 eval_obj.to_mesh_clear()
415 mesh = self._realize_instances(obj, depsgraph)
416 realized_mesh = mesh # Remember for cleanup
417 eval_obj = None # mesh is standalone, no clear needed
330418
331419 if mesh is not None:
332420 # Geometry hash check: skip re-export if evaluated mesh
……@@ -341,6 +429,9 @@ class ChangeDetector:
341429 # Mesh unchanged — downgrade to transform-only or skip
342430 if eval_obj is not None:
343431 eval_obj.to_mesh_clear()
432 if realized_mesh is not None:
433 bpy.data.meshes.remove(realized_mesh)
434 realized_mesh = None
344435 if update_type == UPDATE_VERTEX:
345436 # Only geometry was flagged but unchanged — skip entirely
346437 blob_abs_offset = region1_base + data_rel_start
……@@ -464,6 +555,9 @@ class ChangeDetector:
464555
465556 if eval_obj is not None:
466557 eval_obj.to_mesh_clear()
558 if realized_mesh is not None:
559 bpy.data.meshes.remove(realized_mesh)
560 realized_mesh = None
467561
468562 # Write material data (for meshes, after vertex/UV data)
469563 if obj.type == "MESH" and obj.active_material is not None:
blender_bridge_addon.zip +0 -0
(no content changes — binary or rename)
src/blender_bridge.cpp +1 -1
……@@ -163,7 +163,7 @@ Error BlenderBridge::start_server(const String &socket_path) {
163163 _known_instance_ids.clear();
164164 _next_object_id = 1;
165165
166 UtilityFunctions::print("BlenderBridge v0.12.0: Server started (shm=", _shm_name, ", sock=", socket_path, ", regions=2x", (int)_region_size, ")");
166 UtilityFunctions::print("BlenderBridge v0.12.1: Server started (shm=", _shm_name, ", sock=", socket_path, ", regions=2x", (int)_region_size, ")");
167167 return OK;
168168}
169169