refactor(P1): complete pipeline cleanup — M1 dead code + M3 blender split
M1 dead code removal: - admin.py: remove VALID_STL_QUALITIES + stl_quality (7 locations) - frontend: remove stl_quality from 6 files (api/orders.ts, api/worker.ts, WorkerActivity.tsx, RenderInfoModal.tsx, helpTexts.ts, mocks/handlers.ts) - blender_render.py: delete _mark_sharp_and_seams() — dead, never called (62 lines) - step_processor.py: delete _render_via_service() + 2 elif renderer=="threejs" branches - renderproblems_tmp/: remove 3 orphaned debug images M3 blender_render.py decomposition (858 → 248 lines): - _blender_gpu.py: activate_gpu(), configure_engine() - _blender_import.py: import_glb(), apply_rotation() - _blender_materials.py: FAILED_MATERIAL_NAME, assign_failed_material(), build_mat_map_lower(), apply_material_library() - _blender_camera.py: setup_auto_camera(), setup_auto_lights() - _blender_scene.py: ensure_collection(), apply_smooth_batch(), apply_sharp_edges_from_occ(), setup_shadow_catcher() - Entry-point: sys.path.insert for submodule discovery; arg-parse + Mode A/B orchestration only Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
"""GLB import and geometry helpers for Blender headless renders."""
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
import sys
|
||||
|
||||
|
||||
def import_glb(glb_file: str) -> list:
|
||||
"""Import OCC-generated GLB into Blender.
|
||||
|
||||
OCC exports one mesh object per STEP part, already in metres.
|
||||
Blender's native GLTF importer preserves part names.
|
||||
|
||||
Returns list of Blender mesh objects, centred at world origin.
|
||||
"""
|
||||
import bpy # type: ignore[import]
|
||||
from mathutils import Vector # type: ignore[import]
|
||||
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
bpy.ops.import_scene.gltf(filepath=glb_file)
|
||||
parts = [o for o in bpy.context.selected_objects if o.type == 'MESH']
|
||||
|
||||
if not parts:
|
||||
print(f"ERROR: No mesh objects imported from {glb_file}")
|
||||
sys.exit(1)
|
||||
|
||||
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:
|
||||
all_corners.extend(p.matrix_world @ Vector(c) for c in p.bound_box)
|
||||
|
||||
if all_corners:
|
||||
mins = Vector((min(v.x for v in all_corners),
|
||||
min(v.y for v in all_corners),
|
||||
min(v.z for v in all_corners)))
|
||||
maxs = Vector((max(v.x for v in all_corners),
|
||||
max(v.y for v in all_corners),
|
||||
max(v.z for v in all_corners)))
|
||||
center = (mins + maxs) * 0.5
|
||||
# Move root objects (parentless) to centre. Adjusting a child's local
|
||||
# .location by a world-space vector gives wrong results when the GLB has
|
||||
# Empty parent nodes (OCC assembly hierarchy). Shifting the root moves
|
||||
# the entire hierarchy correctly.
|
||||
all_imported = list(bpy.context.selected_objects)
|
||||
root_objects = [o for o in all_imported if o.parent is None]
|
||||
for obj in root_objects:
|
||||
obj.location -= center
|
||||
|
||||
return parts
|
||||
|
||||
|
||||
def apply_rotation(parts: list, rx: float, ry: float, rz: float) -> None:
|
||||
"""Apply Euler rotation (degrees, XYZ order) to all parts around world origin.
|
||||
|
||||
After import_glb the combined bbox center is at world origin,
|
||||
so rotating around origin is equivalent to rotating around the assembly center.
|
||||
"""
|
||||
if not parts or (rx == 0.0 and ry == 0.0 and rz == 0.0):
|
||||
return
|
||||
import bpy # type: ignore[import]
|
||||
from mathutils import Euler # type: ignore[import]
|
||||
|
||||
rot_mat = Euler((math.radians(rx), math.radians(ry), math.radians(rz)), 'XYZ').to_matrix().to_4x4()
|
||||
for p in parts:
|
||||
p.matrix_world = rot_mat @ p.matrix_world
|
||||
# Bake rotation into mesh data so camera bbox calculations see the rotated geometry
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
for p in parts:
|
||||
p.select_set(True)
|
||||
bpy.context.view_layer.objects.active = parts[0]
|
||||
bpy.ops.object.transform_apply(location=False, rotation=True, scale=False)
|
||||
print(f"[blender_render] applied rotation ({rx}°, {ry}°, {rz}°) to {len(parts)} parts")
|
||||
Reference in New Issue
Block a user