OverviewFilesCommitsIssuesDocsReleasesPeers

blender-bridge commits / 3b44be1

Export per-loop-corner normals for correct modifier shading
Per-vertex normals (mesh.vertices.normal) are basic averaged normals that don't include modifier effects. Weighted Normal, Geometry Nodes, and auto-smooth all set per-loop (per face corner) normals stored in mesh.corner_normals. Changed Blender→Godot export to per-loop-triangle-corner data: - Positions indexed by loop triangle vertex indices - Normals indexed by loop triangle loop indices (from corner_normals) - No index buffer needed (sequential per-corner vertices) - No smooth flags needed (normals already encode shading) This correctly transfers all modifier normals including weighted normal, geometry nodes, and custom split normals. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
3b44be1f5f41f895eefb43f0a9b48371fd5e0a9e
by plankatron <fleeky@gmail.com> · 2026-03-03T17:39:39.000Z

Changed files (2) +52 -43

blender_addon/change_detector.py +52 -43
……@@ -293,73 +293,82 @@ class ChangeDetector:
293293 eval_obj = None
294294
295295 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
298331 # Write vertex count
299332 self._shm.write_blob(
300 blob_abs_offset, struct.pack("<I", vert_count)
333 blob_abs_offset,
334 struct.pack("<I", out_vert_count),
301335 )
302336 blob_abs_offset += 4
303337 blob_rel_offset += 4
304338
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 )
309343 blob_abs_offset += written
310344 blob_rel_offset += written
311345
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 )
316350 blob_abs_offset += 4
317351 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 )
319355 blob_abs_offset += written
320356 blob_rel_offset += written
321357
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)
326359 self._shm.write_blob(
327 blob_abs_offset, struct.pack("<I", idx_count)
360 blob_abs_offset, struct.pack("<I", 0)
328361 )
329362 blob_abs_offset += 4
330363 blob_rel_offset += 4
331364
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)
341366 self._shm.write_blob(
342 blob_abs_offset, struct.pack("<I", tri_count)
367 blob_abs_offset, struct.pack("<I", 0)
343368 )
344369 blob_abs_offset += 4
345370 blob_rel_offset += 4
346371
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
363372 if eval_obj is not None:
364373 eval_obj.to_mesh_clear()
365374
blender_bridge_addon.zip +0 -0
(no content changes — binary or rename)