From e189934b1295aa46ae3044c7f5317aa745b270f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hartmut=20N=C3=B6renberg?= Date: Wed, 11 Mar 2026 11:20:54 +0100 Subject: [PATCH] fix(export_gltf): use edit-mode mark_sharp+mark_seam for proper GLB sharp edges MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- render-worker/scripts/export_gltf.py | 44 +++++++++++++++++++++------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/render-worker/scripts/export_gltf.py b/render-worker/scripts/export_gltf.py index c5ec134..c2457eb 100644 --- a/render-worker/scripts/export_gltf.py +++ b/render-worker/scripts/export_gltf.py @@ -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)