| … | … | @@ -263,6 +263,11 @@ def _poll_timer_impl(generation: int): |
| 263 | 263 | print(f"BlenderBridge: Registered texture '{img_name}' id={tex_id}") |
| 264 | 264 | # Send full image file to Godot (replaces VT tile pipeline) |
| 265 | 265 | _send_image_file(img_name, tex_id) |
| 266 | # Force re-flush of meshes using this texture so tex_id is written | |
| 267 | # to the material blob (first flush had tex_id=0 because registration | |
| 268 | # hadn't completed yet) | |
| 269 | if _detector is not None: | |
| 270 | _detector.invalidate_meshes_with_image(img_name) | |
| 266 | 271 | |
| 267 | 272 | # Flush accumulated Blender changes to SHM and notify Godot |
| 268 | 273 | if _detector is not None: |
| … | … | @@ -2,7 +2,7 @@ schema_version = "1.0.0" |
| 2 | 2 | id = "blender_godot_bridge" |
| 3 | 3 | name = "Blender-Godot Bridge" |
| 4 | 4 | tagline = "Real-time bidirectional scene sync with Godot" |
| 5 | version = "0.18.0" | |
| 5 | version = "0.18.1" | |
| 6 | 6 | blender_version_min = "4.2.0" |
| 7 | 7 | type = "add-on" |
| 8 | 8 | maintainer = "blender-bridge <noreply@localhost>" |
| … | … | @@ -195,6 +195,35 @@ class ChangeDetector: |
| 195 | 195 | self._had_shading_update = False |
| 196 | 196 | return val |
| 197 | 197 | |
| 198 | def invalidate_meshes_with_image(self, image_name: str): | |
| 199 | """Force re-flush of meshes whose Base Color uses the given image. | |
| 200 | ||
| 201 | Called after texture registration so the material blob gets the correct | |
| 202 | tex_id (first flush had tex_id=0 because registration was pending). | |
| 203 | """ | |
| 204 | if bpy is None: | |
| 205 | return | |
| 206 | for obj in bpy.data.objects: | |
| 207 | if obj.type != "MESH" or obj.active_material is None: | |
| 208 | continue | |
| 209 | mat = obj.active_material | |
| 210 | if not mat.use_nodes or not mat.node_tree: | |
| 211 | continue | |
| 212 | for node in mat.node_tree.nodes: | |
| 213 | if node.type != "BSDF_PRINCIPLED": | |
| 214 | continue | |
| 215 | bc = node.inputs.get("Base Color") | |
| 216 | if bc is None or not bc.is_linked: | |
| 217 | continue | |
| 218 | for link in bc.links: | |
| 219 | if (link.from_node.type == "TEX_IMAGE" and | |
| 220 | link.from_node.image and | |
| 221 | link.from_node.image.name == image_name): | |
| 222 | # Clear geometry hash → forces UPDATE_VERTEX re-flush | |
| 223 | self._geometry_hashes.pop(obj.name, None) | |
| 224 | self._pending[obj.name] = UPDATE_VERTEX | |
| 225 | break | |
| 226 | ||
| 198 | 227 | def set_resize_callback(self, callback): |
| 199 | 228 | """Set a callback for SHM resize notifications: callback(new_size, new_region_size).""" |
| 200 | 229 | self._resize_callback = callback |
| … | … | @@ -264,7 +264,7 @@ Error BlenderBridge::start_server(const String &socket_path) { |
| 264 | 264 | _known_instance_ids.clear(); |
| 265 | 265 | _next_object_id = 1; |
| 266 | 266 | |
| 267 | UtilityFunctions::print("BlenderBridge v0.18.0: Server started (shm=", _shm_name, ", sock=", socket_path, ", regions=2x", (int)_region_size, ")"); | |
| 267 | UtilityFunctions::print("BlenderBridge v0.18.1: Server started (shm=", _shm_name, ", sock=", socket_path, ", regions=2x", (int)_region_size, ")"); | |
| 268 | 268 | return OK; |
| 269 | 269 | } |
| 270 | 270 |