refactor(P1): M1 dead code removal + M3 blender_render.py split

M1 — dead code removed:
- Delete blender-renderer/ and threejs-renderer/ source files
- Remove PIL/Pillow fallback block from step_processor.py
  (_generate_thumbnail_placeholder, _finalise_image JPG path)
- Remove stl_quality param from render_blender.py, render_still_task,
  render_turntable_task (was always "low"; hardcode deflection values)
- render_turntable_task now reads scene_linear/angular_deflection from
  system_settings (consistent with export_glb.py pipeline)

M3 — blender_render.py split from 263 → 68 lines:
- _blender_args.py: parse_args() — all 25 positional + named args
- _blender_scene_setup.py: setup_scene() — MODE A/B including USD import
- _blender_render_config.py: configure_and_render() — engine + output

Post-review fixes:
- _db_engine.dispose() after settings read in render_turntable_task
- _finalise_image() fmt param removed (always PNG; PIL never installed)
- _blender_import.py committed together with new submodules to satisfy
  import_usd_file dependency

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 12:54:40 +01:00
parent 393e4b92a7
commit 47b5d42bb5
10 changed files with 471 additions and 496 deletions
@@ -0,0 +1,129 @@
"""MODE A / MODE B scene setup for blender_render.py.
MODE A — factory settings (no template): auto-camera + auto-lights
MODE B — template file: load .blend, import into named collection
"""
import time as _time
from typing import Callable
import bpy # type: ignore[import]
from _blender_camera import setup_auto_camera, setup_auto_lights
from _blender_import import import_glb, apply_rotation, import_usd_file
from _blender_materials import (
assign_failed_material,
build_mat_map_lower,
apply_material_library,
)
from _blender_scene import (
ensure_collection,
apply_smooth_batch,
apply_sharp_edges_from_occ,
setup_shadow_catcher,
)
def setup_scene(args, lap_fn: Callable[[str], None]) -> None:
"""Set up the Blender scene according to args (MODE A or B).
Handles import, rotation, smooth shading, material assignment, shadow
catcher, and auto-camera/lights. lap_fn is called with a label string
at each timing checkpoint.
"""
if args.use_template:
_setup_mode_b(args, lap_fn)
else:
_setup_mode_a(args)
def _setup_mode_b(args, lap_fn: Callable[[str], None]) -> None:
"""MODE B: Template-based render — load .blend, import into collection."""
print(f"[blender_render] Opening template: {args.template_path}")
bpy.ops.wm.open_mainfile(filepath=args.template_path)
lap_fn("template_load")
target_col = ensure_collection(args.target_collection)
if args.usd_path:
parts = import_usd_file(args.usd_path)
else:
parts = import_glb(args.glb_path)
lap_fn("glb_import")
apply_rotation(parts, args.rotation_x, args.rotation_y, args.rotation_z)
lap_fn("rotation")
for part in parts:
for col in list(part.users_collection):
col.objects.unlink(part)
target_col.objects.link(part)
apply_smooth_batch(parts, args.smooth_angle)
if not args.usd_path:
_occ_pairs = args.mesh_attributes.get("sharp_edge_pairs") or []
if _occ_pairs:
apply_sharp_edges_from_occ(parts, _occ_pairs)
lap_fn("smooth_shading")
if args.material_library_path and args.material_map:
apply_material_library(
parts, args.material_library_path,
build_mat_map_lower(args.material_map), args.part_names_ordered,
)
else:
for part in parts:
assign_failed_material(part)
lap_fn("material_assign")
if args.shadow_catcher:
setup_shadow_catcher(parts)
needs_auto_camera = (
(args.lighting_only and not args.shadow_catcher)
or not bpy.context.scene.camera
)
if args.lighting_only and not args.shadow_catcher:
print("[blender_render] lighting_only mode: using template World/HDRI, forcing auto-camera")
elif needs_auto_camera:
print("[blender_render] WARNING: template has no camera — will create auto-camera")
if not needs_auto_camera and bpy.context.scene.camera:
bpy.context.scene.camera.data.clip_start = 0.001
print(f"[blender_render] template mode: {len(parts)} parts imported into collection '{args.target_collection}'")
if needs_auto_camera:
setup_auto_camera(parts, args.width, args.height)
def _setup_mode_a(args) -> None:
"""MODE A: Factory settings — auto-camera + auto-lights."""
bpy.ops.wm.read_factory_settings(use_empty=True)
if args.usd_path:
parts = import_usd_file(args.usd_path)
else:
parts = import_glb(args.glb_path)
apply_rotation(parts, args.rotation_x, args.rotation_y, args.rotation_z)
_t = _time.time()
apply_smooth_batch(parts, args.smooth_angle)
if not args.usd_path:
_occ_pairs = args.mesh_attributes.get("sharp_edge_pairs") or []
if _occ_pairs:
apply_sharp_edges_from_occ(parts, _occ_pairs)
for part in parts:
assign_failed_material(part)
print(f"[blender_render] smooth+fallback-material: {len(parts)} parts ({_time.time()-_t:.2f}s)", flush=True)
if args.material_library_path and args.material_map:
apply_material_library(
parts, args.material_library_path,
build_mat_map_lower(args.material_map), args.part_names_ordered,
)
bbox_center, bsphere_radius = setup_auto_camera(parts, args.width, args.height)
setup_auto_lights(bbox_center, bsphere_radius)
world = bpy.data.worlds.new("World")
bpy.context.scene.world = world
world.use_nodes = True
bg = world.node_tree.nodes["Background"]
bg.inputs["Color"].default_value = (0.96, 0.96, 0.97, 1.0)
bg.inputs["Strength"].default_value = 0.15