diff --git a/render-worker/scripts/blender_render.py b/render-worker/scripts/blender_render.py index 2b2b17e..4afe887 100644 --- a/render-worker/scripts/blender_render.py +++ b/render-worker/scripts/blender_render.py @@ -342,6 +342,16 @@ def _import_glb(glb_file): print(f"[blender_render] imported {len(parts)} part(s) from GLB: " f"{[p.name for p in parts[:5]]}") + # Remove OCC-baked custom normals so shade_smooth_by_angle can recompute + # normals from scratch (respecting our sharp edge marks). + cleared = 0 + for p in parts: + if "custom_normal" in p.data.attributes: + p.data.attributes.remove(p.data.attributes["custom_normal"]) + cleared += 1 + if cleared: + print(f"[blender_render] cleared OCC custom_normal from {cleared} mesh objects") + # Centre combined bbox at world origin all_corners = [] for p in parts: diff --git a/render-worker/scripts/export_gltf.py b/render-worker/scripts/export_gltf.py index b04dc4a..c5ec134 100644 --- a/render-worker/scripts/export_gltf.py +++ b/render-worker/scripts/export_gltf.py @@ -116,6 +116,20 @@ def main() -> None: mesh_objects = [o for o in bpy.data.objects if o.type == "MESH"] print(f"Imported geometry GLB: {args.glb_path} ({len(mesh_objects)} mesh objects)") + # Remove OCC-baked custom normals from the geometry GLB. + # RWGltf_CafWriter embeds per-corner normals from OCC tessellation as a + # 'custom_normal' attribute (CORNER, INT16_2D). If left in place, Blender's + # glTF exporter re-exports these pre-baked normals unchanged, ignoring our + # shade_smooth_by_angle processing and sharp edge marks entirely. + # Removing this attribute forces Blender to recompute normals from scratch. + cleared_normals = 0 + for obj in mesh_objects: + if "custom_normal" in obj.data.attributes: + obj.data.attributes.remove(obj.data.attributes["custom_normal"]) + cleared_normals += 1 + if cleared_normals: + print(f"Cleared OCC custom_normal attribute from {cleared_normals} mesh objects") + # Apply smooth shading using the configured angle threshold smooth_rad = _math.radians(args.smooth_angle) print(f"Applying smooth shading at {args.smooth_angle}° ({smooth_rad:.3f} rad)") @@ -235,6 +249,14 @@ def main() -> None: if fallback: print(f"Single-material fallback: applied '{default_mat_name}' to {fallback} unmatched objects") + # Purge orphan data-blocks (palette materials mat_0/mat_1/... from the geometry + # GLB that now have users=0 after library material substitution). + # This prevents stale materials from appearing as duplicates in the export. + try: + bpy.ops.outliner.orphans_purge(do_recursive=True) + except Exception: + pass # non-critical; export proceeds regardless + # Export production GLB with full PBR material data try: bpy.ops.export_scene.gltf(