Files
HartOMat/render-worker/scripts/export_gltf.py
T
Hartmut 5ee4b2e3b5 fix(export_gltf): update for Blender 5.0 API (wm.stl_import, shade_smooth_by_angle)
- bpy.ops.import_mesh.stl → bpy.ops.wm.stl_import (removed in Blender 4.0)
- mesh.use_auto_smooth = True → bpy.ops.object.shade_smooth_by_angle()
  (use_auto_smooth removed in Blender 4.1)
- Apply smooth shading to all objects unconditionally after scale, so
  GLB is smooth-shaded even when no sharp edge midpoints are present

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 14:30:03 +01:00

145 lines
4.8 KiB
Python

"""Blender headless script: export a STEP-derived scene as a production GLB.
Usage:
blender --background --python export_gltf.py -- \\
--stl_path /path/to/file.stl \\
--output_path /path/to/output.glb \\
[--asset_library_blend /path/to/library.blend] \\
[--material_map '{"SrcMat": "LibMat"}']
The script:
1. Imports the STL file (with mm→m scale).
2. Optionally applies asset library materials from a .blend.
3. Exports as GLB (Draco-compressed if available, otherwise standard).
"""
from __future__ import annotations
import argparse
import json
import sys
import traceback
def parse_args() -> argparse.Namespace:
argv = sys.argv
if "--" not in argv:
print("No arguments after --", file=sys.stderr)
sys.exit(1)
rest = argv[argv.index("--") + 1:]
parser = argparse.ArgumentParser()
parser.add_argument("--stl_path", required=True)
parser.add_argument("--output_path", required=True)
parser.add_argument("--asset_library_blend", default=None)
parser.add_argument("--material_map", default="{}")
parser.add_argument("--sharp_edges_json", default="[]",
help="JSON array of [x, y, z] midpoints (mm) to mark as sharp edges")
return parser.parse_args(rest)
def mark_sharp_edges_by_proximity(midpoints_mm: list, threshold_mm: float = 1.0) -> None:
"""Mark Blender mesh edges as sharp based on proximity to OCC-derived midpoints.
midpoints_mm: list of [x, y, z] in mm (from OCC coordinate space).
After STL import + scale-apply (mm→m), Blender vertices are in meters, so we
convert the edge midpoint back to mm before comparing.
threshold_mm: snap distance in mm (default 1.0 mm).
"""
if not midpoints_mm:
return
import bpy # type: ignore[import]
import math
for obj in bpy.data.objects:
if obj.type != "MESH":
continue
mesh = obj.data
# Blender 4.1+ removed use_auto_smooth — use shade_smooth_by_angle instead
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
try:
bpy.ops.object.shade_smooth_by_angle(angle=math.radians(30))
except Exception:
pass # fallback: stay flat-shaded
mw = obj.matrix_world
for edge in mesh.edges:
v1 = mw @ mesh.vertices[edge.vertices[0]].co
v2 = mw @ mesh.vertices[edge.vertices[1]].co
# Convert Blender meters → mm for comparison
mid_mm = [
(v1.x + v2.x) / 2 * 1000,
(v1.y + v2.y) / 2 * 1000,
(v1.z + v2.z) / 2 * 1000,
]
for hint in midpoints_mm:
dist_sq = sum((a - b) ** 2 for a, b in zip(mid_mm, hint))
if dist_sq < threshold_mm ** 2:
edge.use_edge_sharp = True
break
def main() -> None:
args = parse_args()
material_map: dict = json.loads(args.material_map)
sharp_edge_midpoints: list = json.loads(args.sharp_edges_json)
import bpy # type: ignore[import]
# Clean scene
bpy.ops.wm.read_factory_settings(use_empty=True)
# Import STL (bpy.ops.wm.stl_import is the Blender 4.0+ API)
bpy.ops.wm.stl_import(filepath=args.stl_path)
# Scale mm → m
for obj in bpy.context.selected_objects:
obj.scale = (0.001, 0.001, 0.001)
bpy.context.view_layer.objects.active = obj
bpy.ops.object.transform_apply(scale=True)
# Apply smooth shading with 30° angle threshold (Blender 4.1+ API)
import math as _math
for obj in bpy.data.objects:
if obj.type == "MESH":
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
try:
bpy.ops.object.shade_smooth_by_angle(angle=_math.radians(30))
except Exception:
pass
# Mark sharp edges for better UV seams
if sharp_edge_midpoints:
mark_sharp_edges_by_proximity(sharp_edge_midpoints)
print(f"Marked sharp edges from {len(sharp_edge_midpoints)} hint points")
# Apply asset library materials if provided
if args.asset_library_blend and material_map:
import os
sys.path.insert(0, os.path.dirname(__file__))
from asset_library import apply_asset_library_materials
apply_asset_library_materials(args.asset_library_blend, material_map)
# Export GLB
try:
bpy.ops.export_scene.gltf(
filepath=args.output_path,
export_format="GLB",
export_apply=True,
use_selection=False,
)
except Exception as exc:
print(f"GLB export failed: {exc}", file=sys.stderr)
sys.exit(1)
print(f"GLB exported to {args.output_path}")
try:
main()
except SystemExit:
raise
except Exception:
traceback.print_exc()
sys.exit(1)