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:
2026-03-11 22:19:59 +01:00
parent 4f0fe2c8c7
commit 393e4b92a7
19 changed files with 876 additions and 1036 deletions
-9
View File
@@ -19,7 +19,6 @@ router = APIRouter(prefix="/admin", tags=["admin"])
VALID_RENDERERS = {"blender"}
VALID_ENGINES = {"cycles", "eevee"}
VALID_FORMATS = {"jpg", "png"}
VALID_STL_QUALITIES = {"low", "high"}
VALID_CYCLES_DEVICES = {"auto", "gpu", "cpu"}
SETTINGS_DEFAULTS: dict[str, str] = {
"thumbnail_renderer": "blender",
@@ -27,7 +26,6 @@ SETTINGS_DEFAULTS: dict[str, str] = {
"blender_cycles_samples": "256",
"blender_eevee_samples": "64",
"thumbnail_format": "jpg",
"stl_quality": "low",
"blender_smooth_angle": "30",
"cycles_device": "auto",
"render_backend": "celery",
@@ -64,7 +62,6 @@ class SettingsOut(BaseModel):
blender_cycles_samples: int = 256
blender_eevee_samples: int = 64
thumbnail_format: str = "jpg"
stl_quality: str = "low"
blender_smooth_angle: int = 30
cycles_device: str = "auto"
render_backend: str = "celery"
@@ -97,7 +94,6 @@ class SettingsUpdate(BaseModel):
blender_cycles_samples: int | None = None
blender_eevee_samples: int | None = None
thumbnail_format: str | None = None
stl_quality: str | None = None
blender_smooth_angle: int | None = None
cycles_device: str | None = None
render_backend: str | None = None
@@ -216,7 +212,6 @@ def _settings_to_out(raw: dict[str, str]) -> SettingsOut:
blender_cycles_samples=int(raw["blender_cycles_samples"]),
blender_eevee_samples=int(raw["blender_eevee_samples"]),
thumbnail_format=raw["thumbnail_format"],
stl_quality=raw["stl_quality"],
blender_smooth_angle=int(raw["blender_smooth_angle"]),
cycles_device=raw["cycles_device"],
render_backend=raw["render_backend"],
@@ -268,8 +263,6 @@ async def update_settings(
raise HTTPException(400, detail="blender_eevee_samples must be 11024")
if body.thumbnail_format is not None and body.thumbnail_format not in VALID_FORMATS:
raise HTTPException(400, detail=f"Invalid thumbnail_format. Choose: {', '.join(sorted(VALID_FORMATS))}")
if body.stl_quality is not None and body.stl_quality not in VALID_STL_QUALITIES:
raise HTTPException(400, detail=f"Invalid stl_quality. Choose: {', '.join(sorted(VALID_STL_QUALITIES))}")
if body.blender_smooth_angle is not None and not (0 <= body.blender_smooth_angle <= 180):
raise HTTPException(400, detail="blender_smooth_angle must be 0180 degrees")
if body.cycles_device is not None and body.cycles_device not in VALID_CYCLES_DEVICES:
@@ -307,8 +300,6 @@ async def update_settings(
updates["blender_eevee_samples"] = str(body.blender_eevee_samples)
if body.thumbnail_format is not None:
updates["thumbnail_format"] = body.thumbnail_format
if body.stl_quality is not None:
updates["stl_quality"] = body.stl_quality
if body.blender_smooth_angle is not None:
updates["blender_smooth_angle"] = str(body.blender_smooth_angle)
if body.cycles_device is not None:
-43
View File
@@ -517,11 +517,6 @@ def _generate_thumbnail(
"width": 512,
"height": 512,
})
elif renderer == "threejs":
# Three.js renderer removed in v2; treat as pillow fallback
renderer = "pillow"
render_log.update({"renderer": "pillow", "threejs_removed": True})
logger.info(f"Thumbnail renderer={renderer}, format={fmt}")
rendered_png: Path | None = None
@@ -587,41 +582,6 @@ def _finalise_image(src: Path, dst: Path, fmt: str) -> Path | None:
return dst
def _render_via_service(
url: str, step_path: Path, out_path: Path, extra: dict | None = None,
job_id: str | None = None,
) -> tuple[Path | None, dict]:
"""Call an external renderer microservice to generate a thumbnail.
Returns (path_or_None, response_data_dict).
job_id, when provided, is forwarded to the renderer so the render process
can be cancelled via the renderer's /cancel/{job_id} endpoint.
"""
try:
import httpx
payload = {
"step_path": str(step_path),
"output_path": str(out_path),
"width": 512,
"height": 512,
**(extra or {}),
}
if job_id:
payload["job_id"] = job_id
resp = httpx.post(url, json=payload, timeout=300.0)
data = {}
try:
data = resp.json()
except Exception:
pass
if resp.status_code == 200 and out_path.exists():
return out_path, data
logger.warning(f"Renderer service {url} returned {resp.status_code}: {resp.text[:500]}")
except Exception as exc:
logger.warning(f"Renderer service {url} unreachable: {exc}")
return None, {}
def _generate_thumbnail_placeholder(step_path: Path, out_path: Path, fmt: str = "png") -> Path | None:
"""Generate a simple placeholder thumbnail using Pillow."""
try:
@@ -897,9 +857,6 @@ def render_to_file(
rendered_png = None
else:
logger.warning("Blender not available in this container — using Pillow fallback")
elif renderer == "threejs":
# Three.js renderer removed in v2 — fall through to Pillow placeholder
logger.warning("Three.js renderer removed; using Pillow fallback")
if service_data:
for key in ("total_duration_s", "stl_duration_s", "render_duration_s",