chore: snapshot workflow migration progress
This commit is contained in:
@@ -5,6 +5,7 @@ import uuid
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from PIL import Image, PngImagePlugin
|
||||
from sqlalchemy import select, text
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
@@ -15,6 +16,7 @@ from app.domains.orders.models import Order, OrderLine, OrderStatus
|
||||
from app.domains.products.models import CadFile, Product
|
||||
from app.domains.rendering.models import OutputType, RenderTemplate
|
||||
from app.domains.rendering.workflow_runtime_services import (
|
||||
_build_effective_material_lookup,
|
||||
auto_populate_materials_for_cad,
|
||||
build_order_line_render_invocation,
|
||||
emit_order_line_render_notifications,
|
||||
@@ -101,6 +103,75 @@ def _seed_order_line_graph(session: Session, tmp_path: Path) -> OrderLine:
|
||||
return line
|
||||
|
||||
|
||||
def _write_png_with_metadata(path: Path, *, rgba: tuple[int, int, int, int], date_text: str) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
image = Image.new("RGBA", (8, 8), rgba)
|
||||
metadata = PngImagePlugin.PngInfo()
|
||||
metadata.add_text("Date", date_text)
|
||||
metadata.add_text("Software", "Blender")
|
||||
image.save(path, pnginfo=metadata)
|
||||
|
||||
|
||||
def test_effective_material_lookup_keeps_product_assignments_authoritative_and_adds_manifest_aliases():
|
||||
cad_file = CadFile(
|
||||
id=uuid.uuid4(),
|
||||
original_name="bearing.step",
|
||||
stored_path="/tmp/bearing.step",
|
||||
file_hash=f"hash-{uuid.uuid4().hex}",
|
||||
resolved_material_assignments={
|
||||
"inner_ring": {
|
||||
"source_name": "InnerRing",
|
||||
"prim_path": "/Root/Assembly/inner_ring",
|
||||
"canonical_material": "HARTOMAT_010101_Steel-Bare",
|
||||
},
|
||||
"usd_only_part": {
|
||||
"source_name": "UsdOnlyPart",
|
||||
"prim_path": "/Root/Assembly/usd_only_part",
|
||||
"canonical_material": "HARTOMAT_050101_Elastomer-Black",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
effective = _build_effective_material_lookup(
|
||||
cad_file,
|
||||
[
|
||||
{"part_name": "InnerRing", "material": "Steel raw"},
|
||||
],
|
||||
)
|
||||
|
||||
assert effective["InnerRing"] == "Steel raw"
|
||||
assert effective["inner_ring"] == "Steel raw"
|
||||
assert effective["UsdOnlyPart"] == "HARTOMAT_050101_Elastomer-Black"
|
||||
assert effective["usd_only_part"] == "HARTOMAT_050101_Elastomer-Black"
|
||||
|
||||
|
||||
def test_effective_material_lookup_backfills_manifest_part_keys_from_legacy_serialized_names():
|
||||
cad_file = CadFile(
|
||||
id=uuid.uuid4(),
|
||||
original_name="bearing.step",
|
||||
stored_path="/tmp/bearing.step",
|
||||
file_hash=f"hash-{uuid.uuid4().hex}",
|
||||
resolved_material_assignments={
|
||||
"rwdr_b_f_802044_tr4_h122bk": {
|
||||
"source_name": "RWDR_B_F-802044_TR4_H122BK",
|
||||
"prim_path": "/Root/Assembly/rwdr_b_f_802044_tr4_h122bk",
|
||||
"canonical_material": "HARTOMAT_010101_Steel-Bare",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
effective = _build_effective_material_lookup(
|
||||
cad_file,
|
||||
[
|
||||
{"part_name": "RWDR_B_F-802044_TR4_H122B-69186", "material": "Steel--Stahl"},
|
||||
],
|
||||
)
|
||||
|
||||
assert effective["RWDR_B_F-802044_TR4_H122B-69186"] == "Steel--Stahl"
|
||||
assert effective["RWDR_B_F-802044_TR4_H122BK"] == "Steel--Stahl"
|
||||
assert effective["rwdr_b_f_802044_tr4_h122bk"] == "Steel--Stahl"
|
||||
|
||||
|
||||
def test_prepare_order_line_render_context_marks_line_processing_and_prefers_usd(sync_session, tmp_path, monkeypatch):
|
||||
from app.config import settings
|
||||
|
||||
@@ -118,7 +189,10 @@ def test_prepare_order_line_render_context_marks_line_processing_and_prefers_usd
|
||||
}
|
||||
usd_asset_path = upload_dir / "usd" / "bearing.usd"
|
||||
usd_asset_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
usd_asset_path.write_text("USD", encoding="utf-8")
|
||||
usd_asset_path.write_text(
|
||||
"hartomat:canonicalMaterialName\nhartomat:partKey\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
sync_session.add(
|
||||
MediaAsset(
|
||||
@@ -127,6 +201,9 @@ def test_prepare_order_line_render_context_marks_line_processing_and_prefers_usd
|
||||
product_id=line.product_id,
|
||||
asset_type=MediaAssetType.usd_master,
|
||||
storage_key="usd/bearing.usd",
|
||||
render_config={
|
||||
"cache_key": "stephash:0.03:0.05:20.0:materialhash:scriptfingerprint",
|
||||
},
|
||||
)
|
||||
)
|
||||
sync_session.commit()
|
||||
@@ -230,6 +307,264 @@ def test_prepare_order_line_render_context_queues_refresh_for_legacy_usd(sync_se
|
||||
assert line.render_status == "processing"
|
||||
|
||||
|
||||
def test_prepare_order_line_render_context_queues_refresh_for_legacy_usd_cache_key(sync_session, tmp_path, monkeypatch):
|
||||
from app.config import settings
|
||||
|
||||
monkeypatch.setattr(settings, "upload_dir", str(tmp_path / "uploads"))
|
||||
upload_dir = Path(settings.upload_dir)
|
||||
upload_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
line = _seed_order_line_graph(sync_session, tmp_path)
|
||||
line.product.cad_file.resolved_material_assignments = {
|
||||
"inner_ring": {
|
||||
"source_name": "InnerRing",
|
||||
"prim_path": "/Root/Assembly/inner_ring",
|
||||
"canonical_material": "HARTOMAT_010101_Steel-Bare",
|
||||
}
|
||||
}
|
||||
|
||||
usd_asset_path = upload_dir / "usd" / "bearing.usd"
|
||||
usd_asset_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
usd_asset_path.write_text(
|
||||
"hartomat:canonicalMaterialName\nhartomat:partKey\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
glb_asset_path = upload_dir / "step_files" / "bearing_thumbnail.glb"
|
||||
glb_asset_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
glb_asset_path.write_text("GLB", encoding="utf-8")
|
||||
|
||||
sync_session.add_all(
|
||||
[
|
||||
MediaAsset(
|
||||
id=uuid.uuid4(),
|
||||
cad_file_id=line.product.cad_file_id,
|
||||
product_id=line.product_id,
|
||||
asset_type=MediaAssetType.usd_master,
|
||||
storage_key="usd/bearing.usd",
|
||||
render_config={
|
||||
"cache_key": "stephash:0.03:0.05:20.0:materialhash",
|
||||
},
|
||||
),
|
||||
MediaAsset(
|
||||
id=uuid.uuid4(),
|
||||
cad_file_id=line.product.cad_file_id,
|
||||
product_id=line.product_id,
|
||||
asset_type=MediaAssetType.gltf_geometry,
|
||||
storage_key="step_files/bearing_thumbnail.glb",
|
||||
),
|
||||
]
|
||||
)
|
||||
sync_session.commit()
|
||||
|
||||
queued: list[str] = []
|
||||
|
||||
class _Task:
|
||||
@staticmethod
|
||||
def delay(cad_file_id: str) -> None:
|
||||
queued.append(cad_file_id)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"app.tasks.step_tasks.generate_usd_master_task",
|
||||
_Task(),
|
||||
)
|
||||
|
||||
result = prepare_order_line_render_context(sync_session, str(line.id))
|
||||
|
||||
expected_glb = tmp_path / "parts" / "bearing_thumbnail.glb"
|
||||
assert result.is_ready
|
||||
assert result.usd_render_path is None
|
||||
assert result.glb_reuse_path == expected_glb
|
||||
assert expected_glb.exists()
|
||||
assert queued == [str(line.product.cad_file_id)]
|
||||
|
||||
|
||||
def test_prepare_order_line_render_context_accepts_binary_usd_without_literal_hartomat_markers(sync_session, tmp_path, monkeypatch):
|
||||
from app.config import settings
|
||||
|
||||
monkeypatch.setattr(settings, "upload_dir", str(tmp_path / "uploads"))
|
||||
upload_dir = Path(settings.upload_dir)
|
||||
upload_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
line = _seed_order_line_graph(sync_session, tmp_path)
|
||||
line.product.cad_file.resolved_material_assignments = {
|
||||
"inner_ring": {
|
||||
"source_name": "InnerRing",
|
||||
"prim_path": "/Root/Assembly/inner_ring",
|
||||
"canonical_material": "HARTOMAT_010101_Steel-Bare",
|
||||
}
|
||||
}
|
||||
|
||||
usd_asset_path = upload_dir / "usd" / "bearing.usd"
|
||||
usd_asset_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
usd_asset_path.write_bytes(b"PXR-USDC\x00binary-usd-with-customdata-not-greppable")
|
||||
|
||||
sync_session.add(
|
||||
MediaAsset(
|
||||
id=uuid.uuid4(),
|
||||
cad_file_id=line.product.cad_file_id,
|
||||
product_id=line.product_id,
|
||||
asset_type=MediaAssetType.usd_master,
|
||||
storage_key="usd/bearing.usd",
|
||||
render_config={
|
||||
"cache_key": "stephash:0.03:0.05:20.0:materialhash:scriptfingerprint",
|
||||
},
|
||||
)
|
||||
)
|
||||
sync_session.commit()
|
||||
|
||||
queued: list[str] = []
|
||||
|
||||
class _Task:
|
||||
@staticmethod
|
||||
def delay(cad_file_id: str) -> None:
|
||||
queued.append(cad_file_id)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"app.tasks.step_tasks.generate_usd_master_task",
|
||||
_Task(),
|
||||
)
|
||||
|
||||
result = prepare_order_line_render_context(sync_session, str(line.id))
|
||||
|
||||
assert result.is_ready
|
||||
assert result.usd_render_path == usd_asset_path
|
||||
assert result.glb_reuse_path is None
|
||||
assert queued == []
|
||||
|
||||
|
||||
def test_prepare_order_line_render_context_queues_refresh_for_legacy_usd_file_markers(sync_session, tmp_path, monkeypatch):
|
||||
from app.config import settings
|
||||
|
||||
monkeypatch.setattr(settings, "upload_dir", str(tmp_path / "uploads"))
|
||||
upload_dir = Path(settings.upload_dir)
|
||||
upload_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
line = _seed_order_line_graph(sync_session, tmp_path)
|
||||
line.product.cad_file.resolved_material_assignments = {
|
||||
"inner_ring": {
|
||||
"source_name": "InnerRing",
|
||||
"prim_path": "/Root/Assembly/inner_ring",
|
||||
"canonical_material": "HARTOMAT_010101_Steel-Bare",
|
||||
}
|
||||
}
|
||||
|
||||
usd_asset_path = upload_dir / "usd" / "bearing.usd"
|
||||
usd_asset_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
usd_asset_path.write_text("legacy-usd-without-hartomat-markers", encoding="utf-8")
|
||||
glb_asset_path = upload_dir / "step_files" / "bearing_thumbnail.glb"
|
||||
glb_asset_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
glb_asset_path.write_text("GLB", encoding="utf-8")
|
||||
|
||||
sync_session.add_all(
|
||||
[
|
||||
MediaAsset(
|
||||
id=uuid.uuid4(),
|
||||
cad_file_id=line.product.cad_file_id,
|
||||
product_id=line.product_id,
|
||||
asset_type=MediaAssetType.usd_master,
|
||||
storage_key="usd/bearing.usd",
|
||||
render_config={
|
||||
"cache_key": "stephash:0.03:0.05:20.0:materialhash:scriptfingerprint",
|
||||
},
|
||||
),
|
||||
MediaAsset(
|
||||
id=uuid.uuid4(),
|
||||
cad_file_id=line.product.cad_file_id,
|
||||
product_id=line.product_id,
|
||||
asset_type=MediaAssetType.gltf_geometry,
|
||||
storage_key="step_files/bearing_thumbnail.glb",
|
||||
),
|
||||
]
|
||||
)
|
||||
sync_session.commit()
|
||||
|
||||
queued: list[str] = []
|
||||
|
||||
class _Task:
|
||||
@staticmethod
|
||||
def delay(cad_file_id: str) -> None:
|
||||
queued.append(cad_file_id)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"app.tasks.step_tasks.generate_usd_master_task",
|
||||
_Task(),
|
||||
)
|
||||
|
||||
result = prepare_order_line_render_context(sync_session, str(line.id))
|
||||
|
||||
expected_glb = tmp_path / "parts" / "bearing_thumbnail.glb"
|
||||
assert result.is_ready
|
||||
assert result.usd_render_path is None
|
||||
assert result.glb_reuse_path == expected_glb
|
||||
assert expected_glb.exists()
|
||||
assert queued == [str(line.product.cad_file_id)]
|
||||
|
||||
|
||||
def test_prepare_order_line_render_context_queues_refresh_for_legacy_usd_material_field(sync_session, tmp_path, monkeypatch):
|
||||
from app.config import settings
|
||||
|
||||
monkeypatch.setattr(settings, "upload_dir", str(tmp_path / "uploads"))
|
||||
upload_dir = Path(settings.upload_dir)
|
||||
upload_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
line = _seed_order_line_graph(sync_session, tmp_path)
|
||||
line.product.cad_file.resolved_material_assignments = {
|
||||
"inner_ring": {
|
||||
"source_name": "InnerRing",
|
||||
"prim_path": "/Root/Assembly/inner_ring",
|
||||
"material": "SCHAEFFLER_010101_Steel-Bare",
|
||||
}
|
||||
}
|
||||
|
||||
usd_asset_path = upload_dir / "usd" / "bearing.usd"
|
||||
usd_asset_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
usd_asset_path.write_text("USD", encoding="utf-8")
|
||||
glb_asset_path = upload_dir / "step_files" / "bearing_thumbnail.glb"
|
||||
glb_asset_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
glb_asset_path.write_text("GLB", encoding="utf-8")
|
||||
|
||||
sync_session.add_all(
|
||||
[
|
||||
MediaAsset(
|
||||
id=uuid.uuid4(),
|
||||
cad_file_id=line.product.cad_file_id,
|
||||
product_id=line.product_id,
|
||||
asset_type=MediaAssetType.usd_master,
|
||||
storage_key="usd/bearing.usd",
|
||||
),
|
||||
MediaAsset(
|
||||
id=uuid.uuid4(),
|
||||
cad_file_id=line.product.cad_file_id,
|
||||
product_id=line.product_id,
|
||||
asset_type=MediaAssetType.gltf_geometry,
|
||||
storage_key="step_files/bearing_thumbnail.glb",
|
||||
),
|
||||
]
|
||||
)
|
||||
sync_session.commit()
|
||||
|
||||
queued: list[str] = []
|
||||
|
||||
class _Task:
|
||||
@staticmethod
|
||||
def delay(cad_file_id: str) -> None:
|
||||
queued.append(cad_file_id)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"app.tasks.step_tasks.generate_usd_master_task",
|
||||
_Task(),
|
||||
)
|
||||
|
||||
result = prepare_order_line_render_context(sync_session, str(line.id))
|
||||
|
||||
expected_glb = tmp_path / "parts" / "bearing_thumbnail.glb"
|
||||
assert result.is_ready
|
||||
assert result.usd_render_path is None
|
||||
assert result.glb_reuse_path == expected_glb
|
||||
assert expected_glb.exists()
|
||||
assert queued == [str(line.product.cad_file_id)]
|
||||
|
||||
|
||||
def test_prepare_order_line_render_context_skips_closed_orders(sync_session, tmp_path, monkeypatch):
|
||||
from app.config import settings
|
||||
|
||||
@@ -322,6 +657,11 @@ def test_build_order_line_render_invocation_applies_output_and_line_overrides(tm
|
||||
material_map={"InnerRing": "SteelPolished"},
|
||||
use_materials=True,
|
||||
override_material="Studio White",
|
||||
target_collection="Assembly",
|
||||
lighting_only=True,
|
||||
shadow_catcher=True,
|
||||
camera_orbit=False,
|
||||
template_inputs={"studio_variant": "warm"},
|
||||
category_key="bearings",
|
||||
output_type_id=str(output_type.id),
|
||||
),
|
||||
@@ -357,6 +697,7 @@ def test_build_order_line_render_invocation_applies_output_and_line_overrides(tm
|
||||
assert invocation.part_names_ordered == ["InnerRing", "OuterRing"]
|
||||
assert invocation.rotation_x == 12.0
|
||||
assert invocation.focal_length_mm == 50.0
|
||||
assert invocation.template_inputs == {"studio_variant": "warm"}
|
||||
|
||||
still_kwargs = invocation.as_still_renderer_kwargs(
|
||||
step_path=str(step_path),
|
||||
@@ -374,6 +715,7 @@ def test_build_order_line_render_invocation_applies_output_and_line_overrides(tm
|
||||
assert still_kwargs["cycles_device"] == "cuda"
|
||||
assert still_kwargs["material_library_path"] == "/libraries/materials.blend"
|
||||
assert still_kwargs["material_override"] == "Studio White"
|
||||
assert still_kwargs["template_inputs"] == {"studio_variant": "warm"}
|
||||
assert still_kwargs["job_id"] == "job-1"
|
||||
assert still_kwargs["order_line_id"] == "line-1"
|
||||
|
||||
@@ -437,6 +779,11 @@ def test_build_order_line_render_invocation_autoscales_samples_and_prefers_mater
|
||||
material_map={"InnerRing": "TemplateSteel"},
|
||||
use_materials=True,
|
||||
override_material="Template White",
|
||||
target_collection="Product",
|
||||
lighting_only=False,
|
||||
shadow_catcher=False,
|
||||
camera_orbit=True,
|
||||
template_inputs={"studio_variant": "warm"},
|
||||
category_key="bearings",
|
||||
output_type_id=str(output_type.id),
|
||||
),
|
||||
@@ -480,11 +827,13 @@ def test_build_order_line_render_invocation_autoscales_samples_and_prefers_mater
|
||||
assert turntable_kwargs["samples"] == 64
|
||||
assert turntable_kwargs["material_map"] == {"InnerRing": "ResolvedSteel"}
|
||||
assert turntable_kwargs["material_library_path"] is None
|
||||
assert turntable_kwargs["template_inputs"] == {"studio_variant": "warm"}
|
||||
assert cinematic_kwargs["width"] == 1024
|
||||
assert cinematic_kwargs["height"] == 512
|
||||
assert cinematic_kwargs["engine"] == "eevee"
|
||||
assert cinematic_kwargs["samples"] == 64
|
||||
assert cinematic_kwargs["material_override"] == "Resolved White"
|
||||
assert cinematic_kwargs["template_inputs"] == {"studio_variant": "warm"}
|
||||
|
||||
|
||||
def test_resolve_order_line_template_context_uses_exact_template_and_override(sync_session, tmp_path, monkeypatch):
|
||||
@@ -584,6 +933,153 @@ def test_resolve_order_line_template_context_supports_explicit_template_and_libr
|
||||
"InnerRing": "resolved:Steel raw",
|
||||
"OuterRing": "resolved:Steel raw",
|
||||
}
|
||||
assert result.target_collection == "ForcedCollection"
|
||||
assert result.lighting_only is False
|
||||
assert result.shadow_catcher is False
|
||||
assert result.camera_orbit is True
|
||||
|
||||
|
||||
def test_resolve_order_line_template_context_applies_template_override_modes(
|
||||
sync_session,
|
||||
tmp_path,
|
||||
monkeypatch,
|
||||
):
|
||||
from app.config import settings
|
||||
|
||||
monkeypatch.setattr(settings, "upload_dir", str(tmp_path / "uploads"))
|
||||
line = _seed_order_line_graph(sync_session, tmp_path)
|
||||
template = RenderTemplate(
|
||||
id=uuid.uuid4(),
|
||||
name="Overrideable Template",
|
||||
category_key="bearings",
|
||||
blend_file_path="/templates/overrideable.blend",
|
||||
original_filename="overrideable.blend",
|
||||
target_collection="TemplateCollection",
|
||||
material_replace_enabled=False,
|
||||
lighting_only=False,
|
||||
shadow_catcher_enabled=False,
|
||||
camera_orbit=True,
|
||||
is_active=True,
|
||||
output_types=[line.output_type],
|
||||
)
|
||||
sync_session.add(template)
|
||||
sync_session.add(
|
||||
AssetLibrary(
|
||||
id=uuid.uuid4(),
|
||||
name="Default Library",
|
||||
blend_file_path="/libraries/materials.blend",
|
||||
is_active=True,
|
||||
)
|
||||
)
|
||||
sync_session.commit()
|
||||
|
||||
monkeypatch.setattr(
|
||||
"app.domains.rendering.workflow_runtime_services.resolve_material_map",
|
||||
lambda raw_map: {key: f"resolved:{value}" for key, value in raw_map.items()},
|
||||
)
|
||||
|
||||
setup = prepare_order_line_render_context(sync_session, str(line.id))
|
||||
result = resolve_order_line_template_context(
|
||||
sync_session,
|
||||
setup,
|
||||
template_id_override=str(template.id),
|
||||
material_library_path_override="/libraries/materials.blend",
|
||||
target_collection_override="NodeCollection",
|
||||
material_replace_mode="enabled",
|
||||
lighting_only_mode="enabled",
|
||||
shadow_catcher_mode="enabled",
|
||||
camera_orbit_mode="disabled",
|
||||
)
|
||||
|
||||
assert result.template is not None
|
||||
assert result.use_materials is True
|
||||
assert result.material_map == {
|
||||
"InnerRing": "resolved:Steel raw",
|
||||
"OuterRing": "resolved:Steel raw",
|
||||
}
|
||||
assert result.target_collection == "NodeCollection"
|
||||
assert result.lighting_only is True
|
||||
assert result.shadow_catcher is True
|
||||
assert result.camera_orbit is False
|
||||
|
||||
|
||||
def test_resolve_order_line_template_context_exposes_template_schema_and_invocation_inputs(
|
||||
sync_session,
|
||||
tmp_path,
|
||||
monkeypatch,
|
||||
):
|
||||
from app.config import settings
|
||||
|
||||
monkeypatch.setattr(settings, "upload_dir", str(tmp_path / "uploads"))
|
||||
line = _seed_order_line_graph(sync_session, tmp_path)
|
||||
template = RenderTemplate(
|
||||
id=uuid.uuid4(),
|
||||
name="Schema Template",
|
||||
category_key="bearings",
|
||||
blend_file_path="/templates/schema-template.blend",
|
||||
original_filename="schema-template.blend",
|
||||
target_collection="Product",
|
||||
material_replace_enabled=True,
|
||||
lighting_only=False,
|
||||
shadow_catcher_enabled=False,
|
||||
camera_orbit=True,
|
||||
workflow_input_schema=[
|
||||
{
|
||||
"key": "studio_variant",
|
||||
"label": "Studio Variant",
|
||||
"type": "select",
|
||||
"section": "Template Inputs",
|
||||
"default": "default",
|
||||
"options": [
|
||||
{"value": "default", "label": "Default"},
|
||||
{"value": "warm", "label": "Warm"},
|
||||
],
|
||||
},
|
||||
{
|
||||
"key": "camera_profile",
|
||||
"label": "Camera Profile",
|
||||
"type": "text",
|
||||
"section": "Template Inputs",
|
||||
"default": "macro",
|
||||
},
|
||||
],
|
||||
is_active=True,
|
||||
output_types=[line.output_type],
|
||||
)
|
||||
sync_session.add(template)
|
||||
sync_session.add(
|
||||
AssetLibrary(
|
||||
id=uuid.uuid4(),
|
||||
name="Default Library",
|
||||
blend_file_path="/libraries/materials.blend",
|
||||
is_active=True,
|
||||
)
|
||||
)
|
||||
sync_session.commit()
|
||||
|
||||
monkeypatch.setattr(
|
||||
"app.domains.rendering.workflow_runtime_services.resolve_material_map",
|
||||
lambda raw_map: {key: f"resolved:{value}" for key, value in raw_map.items()},
|
||||
)
|
||||
|
||||
setup = prepare_order_line_render_context(sync_session, str(line.id))
|
||||
template_context = resolve_order_line_template_context(
|
||||
sync_session,
|
||||
setup,
|
||||
template_id_override=str(template.id),
|
||||
template_input_overrides={"studio_variant": "warm"},
|
||||
)
|
||||
invocation = build_order_line_render_invocation(setup, template_context=template_context)
|
||||
|
||||
assert template_context.workflow_input_schema == template.workflow_input_schema
|
||||
assert template_context.template_inputs == {
|
||||
"studio_variant": "warm",
|
||||
"camera_profile": "macro",
|
||||
}
|
||||
assert invocation.template_inputs == {
|
||||
"studio_variant": "warm",
|
||||
"camera_profile": "macro",
|
||||
}
|
||||
|
||||
|
||||
def test_resolve_order_line_template_context_can_disable_material_resolution(sync_session, tmp_path, monkeypatch):
|
||||
@@ -1077,6 +1573,56 @@ def test_persist_order_line_output_canonicalizes_step_file_outputs(sync_session,
|
||||
assert asset.storage_key == f"renders/{line.id}/{expected_path.name}"
|
||||
|
||||
|
||||
def test_png_persistence_strips_volatile_metadata_for_primary_and_observer_outputs(
|
||||
sync_session,
|
||||
tmp_path,
|
||||
monkeypatch,
|
||||
):
|
||||
from app.config import settings
|
||||
|
||||
upload_dir = tmp_path / "uploads"
|
||||
monkeypatch.setattr(settings, "upload_dir", str(upload_dir))
|
||||
line = _seed_order_line_graph(sync_session, tmp_path)
|
||||
|
||||
primary_source = upload_dir / "step_files" / "renders" / f"line_{line.id}.png"
|
||||
observer_source = upload_dir / "step_files" / "renders" / f"line_{line.id}_shadow.png"
|
||||
_write_png_with_metadata(
|
||||
primary_source,
|
||||
rgba=(12, 34, 56, 255),
|
||||
date_text="2026/04/10 17:05:27",
|
||||
)
|
||||
_write_png_with_metadata(
|
||||
observer_source,
|
||||
rgba=(12, 34, 56, 255),
|
||||
date_text="2026/04/10 17:06:30",
|
||||
)
|
||||
|
||||
primary_result = persist_order_line_output(
|
||||
sync_session,
|
||||
line,
|
||||
success=True,
|
||||
output_path=str(primary_source),
|
||||
render_log={"renderer": "blender", "engine_used": "cycles"},
|
||||
)
|
||||
observer_result = persist_order_line_media_asset(
|
||||
sync_session,
|
||||
line,
|
||||
success=True,
|
||||
output_path=str(observer_source),
|
||||
asset_type=MediaAssetType.still,
|
||||
render_log={"renderer": "blender", "engine_used": "cycles"},
|
||||
)
|
||||
|
||||
primary_bytes = Path(primary_result.result_path or "").read_bytes()
|
||||
observer_bytes = Path(observer_result.result_path or "").read_bytes()
|
||||
|
||||
assert primary_bytes == observer_bytes
|
||||
assert b"Date" not in primary_bytes
|
||||
assert b"Date" not in observer_bytes
|
||||
assert Image.open(primary_result.result_path).getpixel((0, 0)) == (12, 34, 56, 255)
|
||||
assert Image.open(observer_result.result_path).getpixel((0, 0)) == (12, 34, 56, 255)
|
||||
|
||||
|
||||
def test_persist_order_line_output_classifies_blend_outputs_as_blend_assets(sync_session, tmp_path, monkeypatch):
|
||||
from app.config import settings
|
||||
|
||||
|
||||
Reference in New Issue
Block a user