| … | … | @@ -293,73 +293,82 @@ class ChangeDetector: |
| 293 | 293 | eval_obj = None |
| 294 | 294 | |
| 295 | 295 | if mesh is not None: |
| 296 | | vert_count = len(mesh.vertices) |
| 297 | | if vert_count > 0: |
| 296 | mesh.calc_loop_triangles() |
| 297 | tri_count = len(mesh.loop_triangles) |
| 298 | if tri_count > 0: |
| 299 | # Export per-loop-triangle-corner data so modifier |
| 300 | # normals are correct (weighted normal, geometry |
| 301 | # nodes, auto-smooth, etc.) |
| 302 | out_vert_count = tri_count * 3 |
| 303 | |
| 304 | # Read all vertex positions |
| 305 | all_coords = np.empty( |
| 306 | len(mesh.vertices) * 3, dtype=np.float32 |
| 307 | ) |
| 308 | mesh.vertices.foreach_get("co", all_coords) |
| 309 | all_coords = all_coords.reshape(-1, 3) |
| 310 | |
| 311 | # Read loop triangle vertex + loop indices |
| 312 | tri_vert_idx = np.empty(out_vert_count, dtype=np.int32) |
| 313 | mesh.loop_triangles.foreach_get("vertices", tri_vert_idx) |
| 314 | tri_loop_idx = np.empty(out_vert_count, dtype=np.int32) |
| 315 | mesh.loop_triangles.foreach_get("loops", tri_loop_idx) |
| 316 | |
| 317 | # Read all loop normals (per face corner — includes |
| 318 | # weighted normals, auto-smooth, geometry nodes, etc.) |
| 319 | all_loop_normals = np.empty( |
| 320 | len(mesh.loops) * 3, dtype=np.float32 |
| 321 | ) |
| 322 | mesh.corner_normals.foreach_get( |
| 323 | "vector", all_loop_normals |
| 324 | ) |
| 325 | all_loop_normals = all_loop_normals.reshape(-1, 3) |
| 326 | |
| 327 | # Build per-corner positions and normals |
| 328 | out_positions = all_coords[tri_vert_idx].flatten() |
| 329 | out_normals = all_loop_normals[tri_loop_idx].flatten() |
| 330 | |
| 298 | 331 | # Write vertex count |
| 299 | 332 | self._shm.write_blob( |
| 300 | | blob_abs_offset, struct.pack("<I", vert_count) |
| 333 | blob_abs_offset, |
| 334 | struct.pack("<I", out_vert_count), |
| 301 | 335 | ) |
| 302 | 336 | blob_abs_offset += 4 |
| 303 | 337 | blob_rel_offset += 4 |
| 304 | 338 | |
| 305 | | # Bulk-read positions |
| 306 | | coords = np.empty(vert_count * 3, dtype=np.float32) |
| 307 | | mesh.vertices.foreach_get("co", coords) |
| 308 | | written = self._shm.write_blob_array(blob_abs_offset, coords) |
| 339 | # Write positions |
| 340 | written = self._shm.write_blob_array( |
| 341 | blob_abs_offset, out_positions |
| 342 | ) |
| 309 | 343 | blob_abs_offset += written |
| 310 | 344 | blob_rel_offset += written |
| 311 | 345 | |
| 312 | | # has_normals flag + normals |
| 313 | | normals = np.empty(vert_count * 3, dtype=np.float32) |
| 314 | | mesh.vertices.foreach_get("normal", normals) |
| 315 | | self._shm.write_blob(blob_abs_offset, struct.pack("<I", 1)) |
| 346 | # Write normals (always present with per-corner data) |
| 347 | self._shm.write_blob( |
| 348 | blob_abs_offset, struct.pack("<I", 1) |
| 349 | ) |
| 316 | 350 | blob_abs_offset += 4 |
| 317 | 351 | blob_rel_offset += 4 |
| 318 | | written = self._shm.write_blob_array(blob_abs_offset, normals) |
| 352 | written = self._shm.write_blob_array( |
| 353 | blob_abs_offset, out_normals |
| 354 | ) |
| 319 | 355 | blob_abs_offset += written |
| 320 | 356 | blob_rel_offset += written |
| 321 | 357 | |
| 322 | | # Triangle indices |
| 323 | | mesh.calc_loop_triangles() |
| 324 | | tri_count = len(mesh.loop_triangles) |
| 325 | | idx_count = tri_count * 3 |
| 358 | # No index buffer (per-corner data is sequential) |
| 326 | 359 | self._shm.write_blob( |
| 327 | | blob_abs_offset, struct.pack("<I", idx_count) |
| 360 | blob_abs_offset, struct.pack("<I", 0) |
| 328 | 361 | ) |
| 329 | 362 | blob_abs_offset += 4 |
| 330 | 363 | blob_rel_offset += 4 |
| 331 | 364 | |
| 332 | | if idx_count > 0: |
| 333 | | indices = np.empty(idx_count, dtype=np.int32) |
| 334 | | mesh.loop_triangles.foreach_get("vertices", indices) |
| 335 | | raw = indices.tobytes() |
| 336 | | self._shm.write_blob(blob_abs_offset, raw) |
| 337 | | blob_abs_offset += len(raw) |
| 338 | | blob_rel_offset += len(raw) |
| 339 | | |
| 340 | | # Write per-triangle smooth flags |
| 365 | # No smooth flags (normals already encode shading) |
| 341 | 366 | self._shm.write_blob( |
| 342 | | blob_abs_offset, struct.pack("<I", tri_count) |
| 367 | blob_abs_offset, struct.pack("<I", 0) |
| 343 | 368 | ) |
| 344 | 369 | blob_abs_offset += 4 |
| 345 | 370 | blob_rel_offset += 4 |
| 346 | 371 | |
| 347 | | if tri_count > 0: |
| 348 | | poly_indices = np.empty(tri_count, dtype=np.int32) |
| 349 | | mesh.loop_triangles.foreach_get( |
| 350 | | "polygon_index", poly_indices |
| 351 | | ) |
| 352 | | poly_count = len(mesh.polygons) |
| 353 | | poly_smooth = np.empty(poly_count, dtype=np.int32) |
| 354 | | mesh.polygons.foreach_get("use_smooth", poly_smooth) |
| 355 | | smooth_flags = poly_smooth[poly_indices].astype( |
| 356 | | np.uint8 |
| 357 | | ) |
| 358 | | raw = smooth_flags.tobytes() |
| 359 | | self._shm.write_blob(blob_abs_offset, raw) |
| 360 | | blob_abs_offset += len(raw) |
| 361 | | blob_rel_offset += len(raw) |
| 362 | | |
| 363 | 372 | if eval_obj is not None: |
| 364 | 373 | eval_obj.to_mesh_clear() |
| 365 | 374 | |