feat: unify order-line render invocation paths
This commit is contained in:
@@ -75,6 +75,7 @@ def render_order_line_task(self, order_line_id: str):
|
||||
from sqlalchemy.orm import Session
|
||||
from app.config import settings as app_settings
|
||||
from app.domains.rendering.workflow_runtime_services import (
|
||||
build_order_line_render_invocation,
|
||||
emit_order_line_render_notifications,
|
||||
persist_order_line_output,
|
||||
prepare_order_line_render_context,
|
||||
@@ -100,9 +101,6 @@ def render_order_line_task(self, order_line_id: str):
|
||||
|
||||
line = setup.order_line
|
||||
cad_file = setup.cad_file
|
||||
materials_source = setup.materials_source
|
||||
usd_render_path = setup.usd_render_path
|
||||
glb_reuse_path = setup.glb_reuse_path
|
||||
part_colors = setup.part_colors
|
||||
render_start = setup.render_start
|
||||
|
||||
@@ -112,10 +110,7 @@ def render_order_line_task(self, order_line_id: str):
|
||||
emit=emit,
|
||||
)
|
||||
template = template_context.template
|
||||
material_library = template_context.material_library
|
||||
material_map = template_context.material_map
|
||||
use_materials = template_context.use_materials
|
||||
override_mat = template_context.override_material
|
||||
|
||||
cad_name = cad_file.original_name if cad_file else "?"
|
||||
position_context = resolve_render_position_context(session, line, emit=emit)
|
||||
@@ -125,150 +120,62 @@ def render_order_line_task(self, order_line_id: str):
|
||||
focal_length_mm = position_context.focal_length_mm
|
||||
sensor_width_mm = position_context.sensor_width_mm
|
||||
|
||||
emit(order_line_id, f"Starting render for {cad_name} ({len(part_colors)} coloured parts)")
|
||||
|
||||
# Determine if this is an animation output type
|
||||
is_animation = bool(line.output_type and getattr(line.output_type, 'is_animation', False))
|
||||
|
||||
# Detect cinematic render type (render_settings.cinematic flag)
|
||||
is_cinematic = bool(
|
||||
line.output_type and
|
||||
line.output_type.render_settings and
|
||||
line.output_type.render_settings.get("cinematic")
|
||||
render_invocation = build_order_line_render_invocation(
|
||||
setup,
|
||||
template_context=template_context,
|
||||
position_context=position_context,
|
||||
)
|
||||
emit(order_line_id, f"Starting render for {cad_name} ({len(part_colors)} coloured parts)")
|
||||
if getattr(line, "render_overrides", None):
|
||||
emit(order_line_id, f"Render overrides active: {line.render_overrides}")
|
||||
if (
|
||||
line.output_type
|
||||
and line.output_type.render_settings
|
||||
and render_invocation.samples is not None
|
||||
and line.output_type.render_settings.get("samples")
|
||||
and render_invocation.width is not None
|
||||
and render_invocation.height is not None
|
||||
):
|
||||
base_samples = int(line.output_type.render_settings["samples"])
|
||||
if render_invocation.samples < base_samples:
|
||||
emit(
|
||||
order_line_id,
|
||||
f"Auto-scaled samples {base_samples} -> {render_invocation.samples} "
|
||||
f"for {render_invocation.width}x{render_invocation.height}",
|
||||
)
|
||||
|
||||
# Determine output format/extension
|
||||
out_ext = "jpg"
|
||||
if line.output_type and line.output_type.output_format:
|
||||
fmt = line.output_type.output_format.lower()
|
||||
if fmt == "mp4":
|
||||
out_ext = "mp4"
|
||||
elif fmt == "webp":
|
||||
out_ext = "webp"
|
||||
elif fmt in ("png", "jpg", "jpeg"):
|
||||
out_ext = "png" if fmt == "png" else "jpg"
|
||||
|
||||
# Build meaningful output filename
|
||||
import re
|
||||
def _sanitize(s: str) -> str:
|
||||
return re.sub(r'[^\w\-.]', '_', s.strip())[:100]
|
||||
|
||||
product_name = line.product.name or line.product.pim_id or "product"
|
||||
ot_name = line.output_type.name if line.output_type else "render"
|
||||
filename = f"{_sanitize(product_name)}_{_sanitize(ot_name)}.{out_ext}"
|
||||
|
||||
# Render to per-line output directory
|
||||
from pathlib import Path as _Path
|
||||
render_dir = _Path(app_settings.upload_dir) / "renders" / order_line_id
|
||||
render_dir.mkdir(parents=True, exist_ok=True)
|
||||
output_path = str(render_dir / filename)
|
||||
|
||||
# Extract per-output-type render settings
|
||||
render_width = None
|
||||
render_height = None
|
||||
render_engine = None
|
||||
render_samples = None
|
||||
noise_threshold = ""
|
||||
denoiser = ""
|
||||
denoising_input_passes = ""
|
||||
denoising_prefilter = ""
|
||||
denoising_quality = ""
|
||||
denoising_use_gpu = ""
|
||||
frame_count = 24
|
||||
fps = 25
|
||||
bg_color = ""
|
||||
turntable_axis = "world_z"
|
||||
if line.output_type and line.output_type.render_settings:
|
||||
rs = line.output_type.render_settings
|
||||
if rs.get("width"):
|
||||
render_width = int(rs["width"])
|
||||
if rs.get("height"):
|
||||
render_height = int(rs["height"])
|
||||
if rs.get("engine"):
|
||||
render_engine = rs["engine"]
|
||||
if rs.get("samples"):
|
||||
render_samples = int(rs["samples"])
|
||||
if rs.get("frame_count"):
|
||||
frame_count = int(rs["frame_count"])
|
||||
if rs.get("fps"):
|
||||
fps = int(rs["fps"])
|
||||
bg_color = rs.get("bg_color", "")
|
||||
turntable_axis = rs.get("turntable_axis", "world_z")
|
||||
noise_threshold = str(rs.get("noise_threshold", ""))
|
||||
denoiser = str(rs.get("denoiser", ""))
|
||||
denoising_input_passes = str(rs.get("denoising_input_passes", ""))
|
||||
denoising_prefilter = str(rs.get("denoising_prefilter", ""))
|
||||
denoising_quality = str(rs.get("denoising_quality", ""))
|
||||
denoising_use_gpu = str(rs.get("denoising_use_gpu", ""))
|
||||
|
||||
# Auto-scale samples for lower resolutions (thumbnails/previews).
|
||||
# Only applies when the output type provides both samples and dimensions.
|
||||
if render_samples and render_width and render_height:
|
||||
max_dim = max(render_width, render_height)
|
||||
if max_dim <= 1024:
|
||||
scaled = max(32, int(render_samples * max_dim / 2048))
|
||||
if scaled < render_samples:
|
||||
emit(order_line_id, f"Auto-scaled samples {render_samples} \u2192 {scaled} for {render_width}x{render_height}")
|
||||
render_samples = scaled
|
||||
|
||||
transparent_bg = bool(line.output_type and line.output_type.transparent_bg)
|
||||
cycles_device_val = (line.output_type.cycles_device or "auto") if line.output_type else "auto"
|
||||
|
||||
# Apply per-line render overrides (format, resolution, samples, etc.)
|
||||
_render_overrides = getattr(line, 'render_overrides', None)
|
||||
if _render_overrides and isinstance(_render_overrides, dict):
|
||||
if 'width' in _render_overrides:
|
||||
render_width = int(_render_overrides['width'])
|
||||
if 'height' in _render_overrides:
|
||||
render_height = int(_render_overrides['height'])
|
||||
if 'samples' in _render_overrides:
|
||||
render_samples = int(_render_overrides['samples'])
|
||||
if 'engine' in _render_overrides:
|
||||
render_engine = _render_overrides['engine']
|
||||
if 'frame_count' in _render_overrides:
|
||||
frame_count = int(_render_overrides['frame_count'])
|
||||
if 'fps' in _render_overrides:
|
||||
fps = int(_render_overrides['fps'])
|
||||
if 'bg_color' in _render_overrides:
|
||||
bg_color = _render_overrides['bg_color']
|
||||
if 'turntable_axis' in _render_overrides:
|
||||
turntable_axis = _render_overrides['turntable_axis']
|
||||
if 'noise_threshold' in _render_overrides:
|
||||
noise_threshold = str(_render_overrides['noise_threshold'])
|
||||
if 'denoiser' in _render_overrides:
|
||||
denoiser = str(_render_overrides['denoiser'])
|
||||
if 'denoising_input_passes' in _render_overrides:
|
||||
denoising_input_passes = str(_render_overrides['denoising_input_passes'])
|
||||
if 'denoising_prefilter' in _render_overrides:
|
||||
denoising_prefilter = str(_render_overrides['denoising_prefilter'])
|
||||
if 'denoising_quality' in _render_overrides:
|
||||
denoising_quality = str(_render_overrides['denoising_quality'])
|
||||
if 'denoising_use_gpu' in _render_overrides:
|
||||
denoising_use_gpu = str(_render_overrides['denoising_use_gpu'])
|
||||
if 'transparent_bg' in _render_overrides:
|
||||
transparent_bg = bool(_render_overrides['transparent_bg'])
|
||||
if 'cycles_device' in _render_overrides:
|
||||
cycles_device_val = _render_overrides['cycles_device']
|
||||
emit(order_line_id, f"Render overrides active: {_render_overrides}")
|
||||
|
||||
# Apply output_format override (affects out_ext and filename)
|
||||
if 'output_format' in _render_overrides:
|
||||
fmt_override = _render_overrides['output_format'].lower()
|
||||
if fmt_override == "mp4":
|
||||
out_ext = "mp4"
|
||||
elif fmt_override == "webp":
|
||||
out_ext = "webp"
|
||||
elif fmt_override in ("png", "jpg", "jpeg"):
|
||||
out_ext = "png" if fmt_override == "png" else "jpg"
|
||||
# Rebuild filename with new extension
|
||||
filename = f"{_sanitize(product_name)}_{_sanitize(ot_name)}.{out_ext}"
|
||||
output_path = str(render_dir / filename)
|
||||
|
||||
# Build ordered part names list for index-based Blender matching
|
||||
part_names_ordered = None
|
||||
if cad_file and cad_file.parsed_objects:
|
||||
part_names_ordered = cad_file.parsed_objects.get("objects", []) or None
|
||||
|
||||
is_animation = render_invocation.is_animation
|
||||
is_cinematic = render_invocation.is_cinematic
|
||||
product_name = render_invocation.product_name
|
||||
ot_name = render_invocation.output_type_name
|
||||
output_path = render_invocation.output_path
|
||||
_Path(output_path).parent.mkdir(parents=True, exist_ok=True)
|
||||
render_width = render_invocation.width
|
||||
render_height = render_invocation.height
|
||||
render_engine = render_invocation.engine
|
||||
render_samples = render_invocation.samples
|
||||
frame_count = render_invocation.frame_count
|
||||
fps = render_invocation.fps
|
||||
bg_color = render_invocation.bg_color
|
||||
turntable_axis = render_invocation.turntable_axis
|
||||
noise_threshold = render_invocation.noise_threshold
|
||||
denoiser = render_invocation.denoiser
|
||||
denoising_input_passes = render_invocation.denoising_input_passes
|
||||
denoising_prefilter = render_invocation.denoising_prefilter
|
||||
denoising_quality = render_invocation.denoising_quality
|
||||
denoising_use_gpu = render_invocation.denoising_use_gpu
|
||||
transparent_bg = render_invocation.transparent_bg
|
||||
cycles_device_val = render_invocation.cycles_device
|
||||
part_names_ordered = render_invocation.part_names_ordered
|
||||
material_override = render_invocation.material_override
|
||||
target_collection = render_invocation.target_collection
|
||||
template_path = render_invocation.template_path
|
||||
material_library_path = render_invocation.material_library_path
|
||||
camera_orbit = render_invocation.camera_orbit
|
||||
lighting_only = render_invocation.lighting_only
|
||||
shadow_catcher = render_invocation.shadow_catcher
|
||||
usd_path = render_invocation.usd_path
|
||||
step_path = _Path(cad_file.stored_path)
|
||||
tmpl_info = f" template={template.name}" if template else ""
|
||||
|
||||
if is_cinematic:
|
||||
@@ -285,33 +192,22 @@ def render_order_line_task(self, order_line_id: str):
|
||||
from app.services.step_processor import _get_all_settings
|
||||
_sys = _get_all_settings()
|
||||
try:
|
||||
service_data = render_cinematic_to_file(
|
||||
step_path=_Path(cad_file.stored_path),
|
||||
cinematic_kwargs = render_invocation.as_cinematic_renderer_kwargs(
|
||||
step_path=step_path,
|
||||
output_path=_Path(output_path),
|
||||
width=render_width or 1920,
|
||||
height=render_height or 1080,
|
||||
engine=render_engine or _sys.get("blender_engine", "cycles"),
|
||||
samples=render_samples or int(_sys.get(f"blender_{render_engine or _sys.get('blender_engine','cycles')}_samples", 128)),
|
||||
default_width=1920,
|
||||
default_height=1080,
|
||||
default_engine=_sys.get("blender_engine", "cycles"),
|
||||
default_samples=int(
|
||||
_sys.get(
|
||||
f"blender_{render_engine or _sys.get('blender_engine', 'cycles')}_samples",
|
||||
128,
|
||||
)
|
||||
),
|
||||
smooth_angle=int(_sys.get("blender_smooth_angle", 30)),
|
||||
cycles_device=cycles_device_val,
|
||||
transparent_bg=transparent_bg,
|
||||
part_colors=part_colors or None,
|
||||
template_path=template.blend_file_path if template else None,
|
||||
target_collection=template.target_collection if template else "Product",
|
||||
material_library_path=material_library if use_materials else None,
|
||||
material_map=material_map,
|
||||
part_names_ordered=part_names_ordered,
|
||||
lighting_only=bool(template.lighting_only) if template else False,
|
||||
shadow_catcher=bool(template.shadow_catcher_enabled) if template else False,
|
||||
rotation_x=rotation_x,
|
||||
rotation_y=rotation_y,
|
||||
rotation_z=rotation_z,
|
||||
usd_path=usd_render_path,
|
||||
focal_length_mm=focal_length_mm,
|
||||
sensor_width_mm=sensor_width_mm,
|
||||
material_override=override_mat,
|
||||
log_callback=lambda line: emit(order_line_id, line),
|
||||
)
|
||||
service_data = render_cinematic_to_file(**cinematic_kwargs)
|
||||
success = True
|
||||
render_log = {
|
||||
"renderer": "blender",
|
||||
@@ -352,37 +248,21 @@ def render_order_line_task(self, order_line_id: str):
|
||||
from app.services.step_processor import _get_all_settings
|
||||
_sys = _get_all_settings()
|
||||
try:
|
||||
service_data = render_turntable_to_file(
|
||||
step_path=_Path(cad_file.stored_path),
|
||||
turntable_kwargs = render_invocation.as_turntable_renderer_kwargs(
|
||||
step_path=step_path,
|
||||
output_path=_Path(output_path),
|
||||
frame_count=frame_count,
|
||||
fps=fps,
|
||||
width=render_width or 1920,
|
||||
height=render_height or 1920,
|
||||
engine=render_engine or _sys.get("blender_engine", "cycles"),
|
||||
samples=render_samples or int(_sys.get(f"blender_{render_engine or _sys.get('blender_engine','cycles')}_samples", 128)),
|
||||
default_width=1920,
|
||||
default_height=1920,
|
||||
default_engine=_sys.get("blender_engine", "cycles"),
|
||||
default_samples=int(
|
||||
_sys.get(
|
||||
f"blender_{render_engine or _sys.get('blender_engine', 'cycles')}_samples",
|
||||
128,
|
||||
)
|
||||
),
|
||||
smooth_angle=int(_sys.get("blender_smooth_angle", 30)),
|
||||
cycles_device=cycles_device_val,
|
||||
transparent_bg=transparent_bg,
|
||||
bg_color=bg_color,
|
||||
turntable_axis=turntable_axis,
|
||||
part_colors=part_colors or None,
|
||||
template_path=template.blend_file_path if template else None,
|
||||
target_collection=template.target_collection if template else "Product",
|
||||
material_library_path=material_library if use_materials else None,
|
||||
material_map=material_map,
|
||||
part_names_ordered=part_names_ordered,
|
||||
lighting_only=bool(template.lighting_only) if template else False,
|
||||
shadow_catcher=bool(template.shadow_catcher_enabled) if template else False,
|
||||
rotation_x=rotation_x,
|
||||
rotation_y=rotation_y,
|
||||
rotation_z=rotation_z,
|
||||
camera_orbit=bool(template.camera_orbit) if template else True,
|
||||
usd_path=usd_render_path,
|
||||
focal_length_mm=focal_length_mm,
|
||||
sensor_width_mm=sensor_width_mm,
|
||||
material_override=override_mat,
|
||||
)
|
||||
service_data = render_turntable_to_file(**turntable_kwargs)
|
||||
success = True
|
||||
render_log = {
|
||||
"renderer": "blender",
|
||||
@@ -414,43 +294,18 @@ def render_order_line_task(self, order_line_id: str):
|
||||
logger.error("Turntable render failed for %s: %s", order_line_id, exc)
|
||||
else:
|
||||
# ── Still image path ────────────────────────────────────────
|
||||
_render_path_label = "USD → Blender" if usd_render_path else "STEP → GLB → Blender"
|
||||
_render_path_label = "USD → Blender" if usd_path else "STEP → GLB → Blender"
|
||||
emit(order_line_id, f"Calling renderer ({_render_path_label}) {render_width or 'default'}x{render_height or 'default'}{' [transparent]' if transparent_bg else ''}{f' engine={render_engine}' if render_engine else ''}{f' samples={render_samples}' if render_samples else ''}{tmpl_info}")
|
||||
pl.step_start("blender_still", {"width": render_width, "height": render_height})
|
||||
from app.services.step_processor import render_to_file
|
||||
|
||||
success, render_log = render_to_file(
|
||||
step_path=cad_file.stored_path,
|
||||
output_path=output_path,
|
||||
part_colors=part_colors,
|
||||
width=render_width,
|
||||
height=render_height,
|
||||
transparent_bg=transparent_bg,
|
||||
engine=render_engine,
|
||||
samples=render_samples,
|
||||
template_path=template.blend_file_path if template else None,
|
||||
target_collection=template.target_collection if template else "Product",
|
||||
material_library_path=material_library if use_materials else None,
|
||||
material_map=material_map,
|
||||
part_names_ordered=part_names_ordered,
|
||||
lighting_only=bool(template.lighting_only) if template else False,
|
||||
shadow_catcher=bool(template.shadow_catcher_enabled) if template else False,
|
||||
cycles_device=line.output_type.cycles_device if line.output_type else None,
|
||||
rotation_x=rotation_x,
|
||||
rotation_y=rotation_y,
|
||||
rotation_z=rotation_z,
|
||||
focal_length_mm=focal_length_mm,
|
||||
sensor_width_mm=sensor_width_mm,
|
||||
material_override=override_mat,
|
||||
job_id=order_line_id,
|
||||
order_line_id=order_line_id,
|
||||
noise_threshold=noise_threshold,
|
||||
denoiser=denoiser,
|
||||
denoising_input_passes=denoising_input_passes,
|
||||
denoising_prefilter=denoising_prefilter,
|
||||
denoising_quality=denoising_quality,
|
||||
denoising_use_gpu=denoising_use_gpu,
|
||||
usd_path=usd_render_path,
|
||||
**render_invocation.as_still_renderer_kwargs(
|
||||
step_path=cad_file.stored_path,
|
||||
output_path=output_path,
|
||||
job_id=order_line_id,
|
||||
order_line_id=order_line_id,
|
||||
)
|
||||
)
|
||||
if success:
|
||||
pl.step_done("blender_still")
|
||||
|
||||
Reference in New Issue
Block a user