| … | … | @@ -219,9 +219,6 @@ def _poll_timer_impl(generation: int): |
| 219 | 219 | # Bake immediately so tiles flow on this tick |
| 220 | 220 | _baker.bake_full_image(img_name) |
| 221 | 221 | print(f"BlenderBridge: Baked texture '{img_name}'") |
| 222 | # Re-flush objects using this texture so texture_id is sent | |
| 223 | if _detector is not None: | |
| 224 | _detector.mark_texture_objects_dirty(img_name) | |
| 225 | 222 | |
| 226 | 223 | # Flush accumulated Blender changes to SHM and notify Godot |
| 227 | 224 | if _detector is not None: |
| … | … | @@ -301,16 +298,10 @@ def _attempt_reconnect(): |
| 301 | 298 | _detector._geometry_hashes.clear() # Force re-detect all geometry |
| 302 | 299 | _detector.register() |
| 303 | 300 | _detector.set_resize_callback(_on_shm_resized) |
| 304 | _detector.set_texture_lookup( | |
| 305 | lambda name: _baker._texture_ids.get(name, 0) if _baker else 0 | |
| 306 | ) | |
| 307 | 301 | else: |
| 308 | 302 | _detector = ChangeDetector(_shm, MAX_OBJECTS) |
| 309 | 303 | _detector.register() |
| 310 | 304 | _detector.set_resize_callback(_on_shm_resized) |
| 311 | _detector.set_texture_lookup( | |
| 312 | lambda name: _baker._texture_ids.get(name, 0) if _baker else 0 | |
| 313 | ) | |
| 314 | 305 | |
| 315 | 306 | # Reuse existing baker to preserve _texture_ids (prevents re-requesting |
| 316 | 307 | # all textures on reconnect). Only update SHM reference and ring buffer. |
| … | … | @@ -472,9 +463,6 @@ class BRIDGE_OT_connect(bpy.types.Operator): |
| 472 | 463 | tile_ring_size = TILE_RING_SIZE |
| 473 | 464 | _baker = TileBaker(_shm, tile_size=128, max_mips=8) |
| 474 | 465 | _baker.init_ring_buffer(tile_ring_offset, tile_ring_size) |
| 475 | _detector.set_texture_lookup( | |
| 476 | lambda name: _baker._texture_ids.get(name, 0) if _baker else 0 | |
| 477 | ) | |
| 478 | 466 | |
| 479 | 467 | # Set up non-blocking procedural baker |
| 480 | 468 | _proc_baker = ProceduralBaker() |
| … | … | @@ -104,8 +104,6 @@ class ChangeDetector: |
| 104 | 104 | self._had_shading_update = False |
| 105 | 105 | # Callback for notifying Godot of SHM resize: fn(new_size, new_region_size) |
| 106 | 106 | self._resize_callback = None |
| 107 | # Callback to resolve image name → VT texture_id (0 = not registered) | |
| 108 | self._texture_lookup = None | |
| 109 | 107 | |
| 110 | 108 | def register(self): |
| 111 | 109 | """Register the depsgraph handler.""" |
| … | … | @@ -186,35 +184,6 @@ class ChangeDetector: |
| 186 | 184 | """Set a callback for SHM resize notifications: callback(new_size, new_region_size).""" |
| 187 | 185 | self._resize_callback = callback |
| 188 | 186 | |
| 189 | def set_texture_lookup(self, callback): | |
| 190 | """Set a callback to resolve image name → VT texture_id (0 = not registered).""" | |
| 191 | self._texture_lookup = callback | |
| 192 | ||
| 193 | def mark_texture_objects_dirty(self, image_name: str): | |
| 194 | """Mark all objects using a texture image for UPDATE_FULL re-flush. | |
| 195 | ||
| 196 | Called when a texture_id becomes available after registration, | |
| 197 | so the material blob includes the correct texture_id. | |
| 198 | """ | |
| 199 | if bpy is None: | |
| 200 | return | |
| 201 | for obj in bpy.data.objects: | |
| 202 | if obj.type != "MESH" or obj.active_material is None: | |
| 203 | continue | |
| 204 | mat = obj.active_material | |
| 205 | if not mat.use_nodes or not mat.node_tree: | |
| 206 | continue | |
| 207 | for node in mat.node_tree.nodes: | |
| 208 | if node.type != "BSDF_PRINCIPLED": | |
| 209 | continue | |
| 210 | bc_input = node.inputs.get("Base Color") | |
| 211 | if bc_input and bc_input.is_linked: | |
| 212 | from_node = bc_input.links[0].from_node | |
| 213 | if (from_node.type == "TEX_IMAGE" and from_node.image | |
| 214 | and from_node.image.name == image_name): | |
| 215 | self._pending[obj.name] = UPDATE_FULL | |
| 216 | break | |
| 217 | ||
| 218 | 187 | @staticmethod |
| 219 | 188 | def _hash_transform(mat) -> int: |
| 220 | 189 | """FNV-1a hash of 4×4 matrix floats for change detection.""" |
| … | … | @@ -741,19 +710,6 @@ class ChangeDetector: |
| 741 | 710 | blob_abs_offset += written |
| 742 | 711 | blob_rel_offset += written |
| 743 | 712 | |
| 744 | # Write texture_id (uint32): 0 = no texture, >0 = VT texture ID | |
| 745 | tex_id = 0 | |
| 746 | if self._texture_lookup is not None: | |
| 747 | bc_input = bsdf.inputs.get("Base Color") | |
| 748 | if bc_input and bc_input.is_linked: | |
| 749 | from_node = bc_input.links[0].from_node | |
| 750 | if from_node.type == "TEX_IMAGE" and from_node.image: | |
| 751 | tex_id = self._texture_lookup(from_node.image.name) | |
| 752 | self._shm.write_blob( | |
| 753 | blob_abs_offset, struct.pack("<I", tex_id) | |
| 754 | ) | |
| 755 | blob_abs_offset += 4 | |
| 756 | blob_rel_offset += 4 | |
| 757 | 713 | if not has_mat: |
| 758 | 714 | self._shm.write_blob( |
| 759 | 715 | blob_abs_offset, struct.pack("<I", 0) |
| … | … | @@ -296,13 +296,12 @@ class SceneImporter: |
| 296 | 296 | if cursor + 4 <= data_end: |
| 297 | 297 | has_material = struct.unpack("<I", bytes(self._shm._mm[cursor:cursor + 4]))[0] |
| 298 | 298 | cursor += 4 |
| 299 | if has_material and cursor + 48 <= data_end: | |
| 299 | if has_material and cursor + 44 <= data_end: | |
| 300 | 300 | mat_data = np.frombuffer( |
| 301 | 301 | bytes(self._shm._mm[cursor:cursor + 44]), |
| 302 | 302 | dtype=np.float32, |
| 303 | 303 | ) |
| 304 | 304 | cursor += 44 |
| 305 | cursor += 4 # skip texture_id (VT, not used in Blender) | |
| 306 | 305 | self._apply_material_to_mesh(mesh, mat_data) |
| 307 | 306 | |
| 308 | 307 | obj = bpy.data.objects.new(mesh.name, mesh) |
| … | … | @@ -512,11 +511,10 @@ class SceneImporter: |
| 512 | 511 | if cursor + 4 <= data_end: |
| 513 | 512 | has_material = struct.unpack("<I", bytes(self._shm._mm[cursor:cursor + 4]))[0] |
| 514 | 513 | cursor += 4 |
| 515 | if has_material and cursor + 48 <= data_end: | |
| 514 | if has_material and cursor + 44 <= data_end: | |
| 516 | 515 | mat_data = np.frombuffer( |
| 517 | 516 | bytes(self._shm._mm[cursor:cursor + 44]), |
| 518 | 517 | dtype=np.float32, |
| 519 | 518 | ) |
| 520 | 519 | cursor += 44 |
| 521 | cursor += 4 # skip texture_id (VT, not used in Blender) | |
| 522 | 520 | self._apply_material_to_mesh(mesh, mat_data) |
| … | … | @@ -570,10 +570,6 @@ uint32_t BlenderBridge::_export_node(Node3D *node, uint32_t index, uint8_t *blob |
| 570 | 570 | mat_data[10] = std_mat->get_emission_energy_multiplier(); |
| 571 | 571 | memcpy(blob_base + blob_offset, mat_data, 44); |
| 572 | 572 | blob_offset += 44; |
| 573 | // texture_id = 0 (Godot-origin meshes have no VT texture) | |
| 574 | uint32_t tex_id = 0; | |
| 575 | memcpy(blob_base + blob_offset, &tex_id, 4); | |
| 576 | blob_offset += 4; | |
| 577 | 573 | } else { |
| 578 | 574 | uint32_t has_material = 0; |
| 579 | 575 | memcpy(blob_base + blob_offset, &has_material, 4); |
| … | … | @@ -1085,48 +1081,28 @@ void BlenderBridge::import_changes(Node *root) { |
| 1085 | 1081 | memcpy(&has_material, base + cursor, 4); |
| 1086 | 1082 | cursor += 4; |
| 1087 | 1083 | } |
| 1088 | if (has_material && cursor + 48 <= data_end) { | |
| 1084 | if (has_material && cursor + 44 <= data_end) { | |
| 1089 | 1085 | float mat_data[11]; |
| 1090 | 1086 | memcpy(mat_data, base + cursor, 44); |
| 1091 | 1087 | cursor += 44; |
| 1092 | 1088 | |
| 1093 | uint32_t texture_id = 0; | |
| 1094 | memcpy(&texture_id, base + cursor, 4); | |
| 1095 | cursor += 4; | |
| 1096 | ||
| 1097 | 1089 | Color albedo(mat_data[0], mat_data[1], mat_data[2], mat_data[3]); |
| 1098 | 1090 | float roughness_val = mat_data[4]; |
| 1099 | 1091 | float metallic_val = mat_data[5]; |
| 1100 | 1092 | |
| 1101 | // Apply VT material if this mesh has a registered texture | |
| 1102 | bool used_vt = false; | |
| 1103 | if (texture_id > 0 && !_virtual_texture_path.is_empty()) { | |
| 1104 | Node *vt_node = get_node_or_null(_virtual_texture_path); | |
| 1105 | VirtualTexture *vt = Object::cast_to<VirtualTexture>(vt_node); | |
| 1106 | if (vt) { | |
| 1107 | Ref<ShaderMaterial> vt_mat = vt->create_vt_material(albedo, roughness_val, metallic_val); | |
| 1108 | if (vt_mat.is_valid()) { | |
| 1109 | mesh_inst->set_surface_override_material(0, vt_mat); | |
| 1110 | used_vt = true; | |
| 1111 | } | |
| 1112 | } | |
| 1113 | } | |
| 1114 | ||
| 1115 | // Fallback: StandardMaterial3D with flat colors | |
| 1116 | if (!used_vt) { | |
| 1117 | Ref<StandardMaterial3D> std_mat; | |
| 1118 | std_mat.instantiate(); | |
| 1119 | std_mat->set_albedo(albedo); | |
| 1120 | std_mat->set_roughness(roughness_val); | |
| 1121 | std_mat->set_metallic(metallic_val); | |
| 1122 | Color emission(mat_data[6], mat_data[7], mat_data[8], mat_data[9]); | |
| 1123 | if (emission.r > 0.0f || emission.g > 0.0f || emission.b > 0.0f) { | |
| 1124 | std_mat->set_feature(BaseMaterial3D::FEATURE_EMISSION, true); | |
| 1125 | std_mat->set_emission(emission); | |
| 1126 | std_mat->set_emission_energy_multiplier(mat_data[10]); | |
| 1127 | } | |
| 1128 | mesh_inst->set_surface_override_material(0, std_mat); | |
| 1093 | // StandardMaterial3D with Blender PBR properties | |
| 1094 | Ref<StandardMaterial3D> std_mat; | |
| 1095 | std_mat.instantiate(); | |
| 1096 | std_mat->set_albedo(albedo); | |
| 1097 | std_mat->set_roughness(roughness_val); | |
| 1098 | std_mat->set_metallic(metallic_val); | |
| 1099 | Color emission(mat_data[6], mat_data[7], mat_data[8], mat_data[9]); | |
| 1100 | if (emission.r > 0.0f || emission.g > 0.0f || emission.b > 0.0f) { | |
| 1101 | std_mat->set_feature(BaseMaterial3D::FEATURE_EMISSION, true); | |
| 1102 | std_mat->set_emission(emission); | |
| 1103 | std_mat->set_emission_energy_multiplier(mat_data[10]); | |
| 1129 | 1104 | } |
| 1105 | mesh_inst->set_surface_override_material(0, std_mat); | |
| 1130 | 1106 | } |
| 1131 | 1107 | |
| 1132 | 1108 | } |
| … | … | @@ -1578,9 +1554,6 @@ void BlenderBridge::_detect_and_push_changes() { |
| 1578 | 1554 | mat_data[10] = std_mat->get_emission_energy_multiplier(); |
| 1579 | 1555 | memcpy(base + blob_offset, mat_data, 44); |
| 1580 | 1556 | blob_offset += 44; |
| 1581 | uint32_t tex_id = 0; | |
| 1582 | memcpy(base + blob_offset, &tex_id, 4); | |
| 1583 | blob_offset += 4; | |
| 1584 | 1557 | } else { |
| 1585 | 1558 | uint32_t has_material = 0; |
| 1586 | 1559 | memcpy(base + blob_offset, &has_material, 4); |