chore: snapshot workflow migration progress
This commit is contained in:
Executable
+246
@@ -0,0 +1,246 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage:
|
||||
./scripts/rerender_closed_legacy_still.sh <order-line-id>
|
||||
|
||||
Description:
|
||||
Re-renders a completed legacy still order line with the full legacy template,
|
||||
material, position, and USD context, then persists the canonical output and
|
||||
updates the linked media asset.
|
||||
EOF
|
||||
}
|
||||
|
||||
if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then
|
||||
usage
|
||||
exit 0
|
||||
fi
|
||||
|
||||
ORDER_LINE_ID="${1:-}"
|
||||
if [ -z "$ORDER_LINE_ID" ]; then
|
||||
usage >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
||||
if [ -z "$REPO_ROOT" ]; then
|
||||
echo "This script must be run inside the repository." >&2
|
||||
exit 1
|
||||
fi
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
docker compose exec -T render-worker python3 - "$ORDER_LINE_ID" <<'PY'
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
import app.models.template # noqa: F401
|
||||
from sqlalchemy import create_engine, select
|
||||
from sqlalchemy.orm import Session, joinedload
|
||||
|
||||
from app.config import settings
|
||||
from app.core.render_paths import resolve_result_path
|
||||
from app.domains.media.models import MediaAsset, MediaAssetType
|
||||
from app.domains.orders.models import OrderLine
|
||||
from app.domains.products.models import Product
|
||||
from app.domains.rendering.workflow_runtime_services import (
|
||||
OrderLineRenderSetupResult,
|
||||
persist_order_line_output,
|
||||
resolve_order_line_template_context,
|
||||
resolve_render_position_context,
|
||||
)
|
||||
from app.services.step_processor import build_part_colors, render_to_file
|
||||
|
||||
|
||||
def _sanitize(value: str) -> str:
|
||||
return re.sub(r"[^\w\-.]", "_", value.strip())[:100] or "output"
|
||||
|
||||
|
||||
order_line_id = sys.argv[1]
|
||||
engine = create_engine(settings.database_url.replace("+asyncpg", ""))
|
||||
|
||||
with Session(engine) as session:
|
||||
line = session.execute(
|
||||
select(OrderLine)
|
||||
.where(OrderLine.id == order_line_id)
|
||||
.options(
|
||||
joinedload(OrderLine.product).joinedload(Product.cad_file),
|
||||
joinedload(OrderLine.output_type),
|
||||
)
|
||||
).scalar_one_or_none()
|
||||
if line is None:
|
||||
raise RuntimeError(f"Order line not found: {order_line_id}")
|
||||
if line.product is None or line.product.cad_file is None:
|
||||
raise RuntimeError(f"Order line {order_line_id} has no linked CAD file")
|
||||
if line.output_type is None:
|
||||
raise RuntimeError(f"Order line {order_line_id} has no output type")
|
||||
|
||||
cad_file = line.product.cad_file
|
||||
render_settings = dict(line.output_type.render_settings or {})
|
||||
if bool(render_settings.get("cinematic")):
|
||||
raise RuntimeError("This support script only handles still outputs, not cinematic outputs")
|
||||
if bool(getattr(line.output_type, "is_animation", False)):
|
||||
raise RuntimeError("This support script only handles still outputs, not animation outputs")
|
||||
|
||||
materials_source = list(line.product.cad_part_materials or [])
|
||||
part_colors = {}
|
||||
parsed_names = []
|
||||
if cad_file.parsed_objects:
|
||||
parsed_names = list(cad_file.parsed_objects.get("objects", []) or [])
|
||||
if materials_source:
|
||||
part_colors = build_part_colors(parsed_names, materials_source)
|
||||
|
||||
usd_render_path = None
|
||||
usd_asset = session.execute(
|
||||
select(MediaAsset)
|
||||
.where(
|
||||
MediaAsset.cad_file_id == cad_file.id,
|
||||
MediaAsset.asset_type == MediaAssetType.usd_master,
|
||||
)
|
||||
.order_by(MediaAsset.created_at.desc())
|
||||
.limit(1)
|
||||
).scalar_one_or_none()
|
||||
if usd_asset is not None:
|
||||
usd_render_path = resolve_result_path(usd_asset.storage_key)
|
||||
|
||||
setup = OrderLineRenderSetupResult(
|
||||
status="ready",
|
||||
order_line=line,
|
||||
cad_file=cad_file,
|
||||
materials_source=materials_source,
|
||||
usd_render_path=usd_render_path,
|
||||
glb_reuse_path=None,
|
||||
part_colors=part_colors,
|
||||
render_start=datetime.utcnow(),
|
||||
)
|
||||
|
||||
template_context = resolve_order_line_template_context(session, setup)
|
||||
position_context = resolve_render_position_context(session, line)
|
||||
|
||||
out_ext = "jpg"
|
||||
fmt = (line.output_type.output_format or "").lower()
|
||||
if fmt == "png":
|
||||
out_ext = "png"
|
||||
elif fmt in {"jpg", "jpeg"}:
|
||||
out_ext = "jpg"
|
||||
elif fmt == "webp":
|
||||
out_ext = "webp"
|
||||
|
||||
render_width = int(render_settings["width"]) if render_settings.get("width") else None
|
||||
render_height = int(render_settings["height"]) if render_settings.get("height") else None
|
||||
render_engine = render_settings.get("engine")
|
||||
render_samples = int(render_settings["samples"]) if render_settings.get("samples") else None
|
||||
noise_threshold = str(render_settings.get("noise_threshold", ""))
|
||||
denoiser = str(render_settings.get("denoiser", ""))
|
||||
denoising_input_passes = str(render_settings.get("denoising_input_passes", ""))
|
||||
denoising_prefilter = str(render_settings.get("denoising_prefilter", ""))
|
||||
denoising_quality = str(render_settings.get("denoising_quality", ""))
|
||||
denoising_use_gpu = str(render_settings.get("denoising_use_gpu", ""))
|
||||
|
||||
transparent_bg = bool(line.output_type.transparent_bg)
|
||||
render_overrides = getattr(line, "render_overrides", None)
|
||||
if 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 "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 "output_format" in render_overrides:
|
||||
fmt_override = str(render_overrides["output_format"]).lower()
|
||||
if fmt_override == "png":
|
||||
out_ext = "png"
|
||||
elif fmt_override in {"jpg", "jpeg"}:
|
||||
out_ext = "jpg"
|
||||
elif fmt_override == "webp":
|
||||
out_ext = "webp"
|
||||
|
||||
product_name = line.product.name or getattr(line.product, "pim_id", None) or "product"
|
||||
output_type_name = line.output_type.name or "render"
|
||||
filename = f"{_sanitize(product_name)}_{_sanitize(output_type_name)}.{out_ext}"
|
||||
output_dir = Path(settings.upload_dir) / "renders" / str(line.id)
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
output_path = str(output_dir / filename)
|
||||
|
||||
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_context.template.blend_file_path if template_context.template else None,
|
||||
target_collection=template_context.template.target_collection if template_context.template else "Product",
|
||||
material_library_path=template_context.material_library if template_context.use_materials else None,
|
||||
material_map=template_context.material_map,
|
||||
part_names_ordered=parsed_names or None,
|
||||
lighting_only=bool(template_context.template.lighting_only) if template_context.template else False,
|
||||
shadow_catcher=bool(template_context.template.shadow_catcher_enabled) if template_context.template else False,
|
||||
cycles_device=line.output_type.cycles_device,
|
||||
rotation_x=position_context.rotation_x,
|
||||
rotation_y=position_context.rotation_y,
|
||||
rotation_z=position_context.rotation_z,
|
||||
focal_length_mm=position_context.focal_length_mm,
|
||||
sensor_width_mm=position_context.sensor_width_mm,
|
||||
material_override=template_context.override_material,
|
||||
job_id=str(line.id),
|
||||
order_line_id=str(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,
|
||||
)
|
||||
if not success:
|
||||
raise RuntimeError(json.dumps(render_log, ensure_ascii=True))
|
||||
|
||||
persisted = persist_order_line_output(
|
||||
session,
|
||||
line,
|
||||
success=True,
|
||||
output_path=output_path,
|
||||
render_log=render_log if isinstance(render_log, dict) else None,
|
||||
render_completed_at=datetime.utcnow(),
|
||||
)
|
||||
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"order_line_id": str(line.id),
|
||||
"result_path": persisted.result_path,
|
||||
"asset_id": persisted.asset_id,
|
||||
"storage_key": persisted.storage_key,
|
||||
"asset_type": persisted.asset_type.value if persisted.asset_type else None,
|
||||
"template": template_context.template.name if template_context.template else None,
|
||||
"material_map_count": len(template_context.material_map or {}),
|
||||
"usd_path": str(usd_render_path) if usd_render_path else None,
|
||||
"duration_s": render_log.get("total_duration_s") if isinstance(render_log, dict) else None,
|
||||
},
|
||||
ensure_ascii=True,
|
||||
)
|
||||
)
|
||||
PY
|
||||
Reference in New Issue
Block a user