fix(export_gltf): use edit-mode mark_sharp+mark_seam for proper GLB sharp edges

Replace shade_smooth_by_angle with explicit edit-mode operators:
- edges_select_sharp(angle) → mark_sharp() + mark_seam()
- Produces vertex splits at sharp edges (6027 split positions verified)
- Remove OCC custom_normal attribute before processing to prevent
  pre-baked normals overriding Blender's sharp edge processing
- Update comment: calc_normals_split() removed in Blender 5.0

Verified: production GLB has 812 extra vertices vs geometry GLB,
6027 positions with multiple normals = sharp edges correctly encoded.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 11:20:54 +01:00
parent d1c7feacf6
commit e189934b12
+34 -10
View File
@@ -130,21 +130,45 @@ def main() -> None:
if cleared_normals:
print(f"Cleared OCC custom_normal attribute from {cleared_normals} mesh objects")
# Apply smooth shading using the configured angle threshold
# Mark sharp edges and seams using the configured angle threshold.
# We use Blender's edit-mode operators (mark_sharp + mark_seam) rather than
# shade_smooth_by_angle alone, because:
# 1. mark_sharp() sets the sharp_edge boolean attribute on edges — the glTF
# exporter creates vertex splits (duplicate vertices with different normals)
# at sharp edges, which is how glTF encodes hard edges.
# 2. mark_seam() ensures UV splits at the same edges (stepper-addon behaviour).
# Note: calc_normals_split() was removed in Blender 5.0 — not needed here
# because export_apply=True triggers vertex splitting automatically.
smooth_rad = _math.radians(args.smooth_angle)
print(f"Applying smooth shading at {args.smooth_angle}° ({smooth_rad:.3f} rad)")
print(f"Marking sharp edges + seams at {args.smooth_angle}° ({smooth_rad:.3f} rad)")
bpy.ops.object.select_all(action='DESELECT')
total_sharp = 0
for obj in mesh_objects:
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
try:
bpy.ops.object.shade_smooth_by_angle(angle=smooth_rad)
except Exception:
bpy.ops.object.shade_smooth()
if hasattr(obj.data, 'use_auto_smooth'):
obj.data.use_auto_smooth = True
obj.data.auto_smooth_angle = smooth_rad
# Apply OCC sharp edges if available (overrides pure dihedral-angle shading)
# Set all faces smooth
bpy.ops.object.mode_set(mode='OBJECT')
for poly in obj.data.polygons:
poly.use_smooth = True
# Enter edit mode, deselect, select sharp edges by angle, mark sharp+seam
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.mesh.edges_select_sharp(sharpness=smooth_rad)
bpy.ops.mesh.mark_sharp()
bpy.ops.mesh.mark_seam()
bpy.ops.object.mode_set(mode='OBJECT')
# Count how many edges were marked
n_sharp = sum(1 for e in obj.data.edges if e.use_edge_sharp)
total_sharp += n_sharp
obj.select_set(False)
print(f"Marked {total_sharp} sharp/seam edges across {len(mesh_objects)} objects")
# Apply OCC sharp edges if available (additional explicit sharp edges from CAD data)
sharp_pairs = mesh_attributes.get("sharp_edge_pairs") or []
if sharp_pairs:
_apply_sharp_edges_from_occ(mesh_objects, sharp_pairs)