refactor(A2): replace blender-renderer HTTP service with render-worker Celery container

- Create render-worker/ with Dockerfile (Ubuntu + cadquery + Blender via host mount)
- Add render-worker/check_version.py: verifies Blender >= 5.0.1 at startup, Exit 1 on failure
- Add render-worker/scripts/: blender_render.py, still_render.py, turntable_render.py
- Create backend/app/services/render_blender.py: direct subprocess rendering
  - convert_step_to_stl() and export_per_part_stls() using cadquery
  - render_still(): STEP → STL → PNG via Blender subprocess
  - is_blender_available(): detects BLENDER_BIN env for render-worker context
- Create backend/app/domains/rendering/tasks.py: render_still_task + render_turntable_task
- Update step_processor.py: use subprocess path when BLENDER_BIN env is set (render-worker)
- Update step_tasks.py: generate_stl_cache uses direct cadquery instead of HTTP
- Remove blender-renderer and threejs-renderer from docker-compose.yml
- Replace worker-thumbnail with render-worker (Ubuntu + cadquery + Blender mount)
- Remove Docker SDK from backend Dockerfile (was only for flamenco scaling)
- Update .env.example: BLENDER_VERSION=5.0.1 documented
- Update celery_app.py: include domains.rendering.tasks in autodiscover

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 15:48:46 +01:00
parent 1d6864fb64
commit 9d1a820295
16 changed files with 3118 additions and 108 deletions
+6 -1
View File
@@ -5,7 +5,11 @@ celery_app = Celery(
"schaefflerautomat",
broker=settings.redis_url,
backend=settings.redis_url,
include=["app.tasks.step_tasks", "app.tasks.ai_tasks"],
include=[
"app.tasks.step_tasks",
"app.tasks.ai_tasks",
"app.domains.rendering.tasks",
],
)
celery_app.conf.update(
@@ -17,6 +21,7 @@ celery_app.conf.update(
task_routes={
"app.tasks.step_tasks.*": {"queue": "step_processing"},
"app.tasks.ai_tasks.*": {"queue": "ai_validation"},
"app.domains.rendering.tasks.*": {"queue": "thumbnail_rendering"},
},
beat_schedule={},
)
+14 -11
View File
@@ -157,7 +157,6 @@ def generate_stl_cache(self, cad_file_id: str, quality: str):
from sqlalchemy.orm import Session
from app.config import settings as app_settings
from app.models.cad_file import CadFile
import httpx
logger.info(f"Generating {quality}-quality STL for CAD file: {cad_file_id}")
@@ -172,16 +171,20 @@ def generate_stl_cache(self, cad_file_id: str, quality: str):
eng.dispose()
try:
resp = httpx.post(
"http://blender-renderer:8100/convert-stl",
json={"step_path": step_path, "quality": quality},
timeout=600.0,
)
if resp.status_code == 200:
data = resp.json()
logger.info(f"STL cached: {data['stl_path']} ({data['size_bytes']} bytes) in {data['duration_s']}s")
else:
raise RuntimeError(f"blender-renderer returned {resp.status_code}: {resp.text[:300]}")
from app.services.render_blender import convert_step_to_stl, export_per_part_stls
from pathlib import Path as _Path
step = _Path(step_path)
stl_out = step.parent / f"{step.stem}_{quality}.stl"
parts_dir = step.parent / f"{step.stem}_{quality}_parts"
if not stl_out.exists() or stl_out.stat().st_size == 0:
convert_step_to_stl(step, stl_out, quality)
if not (parts_dir / "manifest.json").exists():
try:
export_per_part_stls(step, parts_dir, quality)
except Exception as pe:
logger.warning(f"Per-part STL export non-fatal: {pe}")
logger.info(f"STL cached: {stl_out} ({stl_out.stat().st_size // 1024} KB)")
except Exception as exc:
logger.error(f"STL generation failed for {cad_file_id} quality={quality}: {exc}")
raise self.retry(exc=exc, countdown=30, max_retries=2)