added svg patch output
This commit is contained in:
+79
-18
@@ -32,6 +32,7 @@ DEFAULT_OUTPUTS = [
|
||||
("produce", "patch_texture_produce.png"),
|
||||
("compute", "patch_texture_compute.png"),
|
||||
]
|
||||
ARTIFACT_FORMATS = ("png", "svg", "pdf")
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
@@ -126,8 +127,15 @@ def test_output_paths_pair_each_target(revision_dir: Path, tmp_path: Path) -> No
|
||||
|
||||
targets = texture_output_paths(tmp_path, settings)
|
||||
|
||||
assert [(target.uv_map, path.name) for target, path in targets] == DEFAULT_OUTPUTS
|
||||
assert all(path.parent == tmp_path.resolve() for _, path in targets)
|
||||
assert [target.uv_map for target, _ in targets] == ["produce", "compute"]
|
||||
for (target, artifacts), (_, png_name) in zip(targets, DEFAULT_OUTPUTS):
|
||||
stem = Path(png_name).stem
|
||||
assert list(artifacts) == list(ARTIFACT_FORMATS)
|
||||
assert {name: path.name for name, path in artifacts.items()} == {
|
||||
format_name: f"{stem}.{format_name}"
|
||||
for format_name in ARTIFACT_FORMATS
|
||||
}
|
||||
assert all(path.parent == tmp_path.resolve() for path in artifacts.values())
|
||||
|
||||
|
||||
def test_job_path_sits_beside_texture(tmp_path: Path) -> None:
|
||||
@@ -138,8 +146,13 @@ def test_output_target_entry_is_absolute(revision_dir: Path, tmp_path: Path) ->
|
||||
target = TextureOutputTarget(uv_map="produce", filename="a.png")
|
||||
entry = target.to_job_entry(tmp_path)
|
||||
assert entry["uv_map"] == "produce"
|
||||
assert Path(entry["output"]).is_absolute()
|
||||
assert Path(entry["output"]).name == "a.png"
|
||||
assert list(entry["artifacts"]) == list(ARTIFACT_FORMATS)
|
||||
assert all(Path(path).is_absolute() for path in entry["artifacts"].values())
|
||||
assert {name: Path(path).name for name, path in entry["artifacts"].items()} == {
|
||||
"png": "a.png",
|
||||
"svg": "a.svg",
|
||||
"pdf": "a.pdf",
|
||||
}
|
||||
|
||||
|
||||
# ------------------------------------------------------- settings merge
|
||||
@@ -154,7 +167,10 @@ def test_defaults_come_from_design_yaml(revision_dir: Path) -> None:
|
||||
assert (settings.nx, settings.ny) == (80, 45)
|
||||
assert settings.color_r == pytest.approx(0.05)
|
||||
assert settings.edge_sample_grid == 5
|
||||
assert settings.seed is None
|
||||
# Pinned in design.yaml so the committed PNG/SVG/PDF set is reproducible.
|
||||
assert settings.seed == 7
|
||||
assert settings.vector_width_mm == pytest.approx(164.0)
|
||||
assert settings.vector_height_mm == pytest.approx(160.0)
|
||||
assert settings.cross_enabled is True
|
||||
assert settings.cross_length_px == 40
|
||||
assert settings.cross_thickness_px == 3
|
||||
@@ -199,6 +215,7 @@ def test_cli_overrides_beat_design_yaml(revision_dir: Path) -> None:
|
||||
"color_r": 0.25,
|
||||
"edge_sample_grid": 3,
|
||||
"seed": 11,
|
||||
"vector": {"width_mm": 82.0, "height_mm": 80.0},
|
||||
"cross": {"enabled": False, "length_px": 8, "color": [0.0, 1.0, 0.0]},
|
||||
},
|
||||
)
|
||||
@@ -209,6 +226,8 @@ def test_cli_overrides_beat_design_yaml(revision_dir: Path) -> None:
|
||||
assert settings.color_r == pytest.approx(0.25)
|
||||
assert settings.edge_sample_grid == 3
|
||||
assert settings.seed == 11
|
||||
assert settings.vector_width_mm == pytest.approx(82.0)
|
||||
assert settings.vector_height_mm == pytest.approx(80.0)
|
||||
assert settings.cross_enabled is False
|
||||
assert settings.cross_length_px == 8
|
||||
# A 3-channel cross colour is widened to RGBA.
|
||||
@@ -249,6 +268,15 @@ def test_unknown_cross_key_is_rejected(tmp_path: Path) -> None:
|
||||
assert "thickness" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_unknown_vector_key_is_rejected(tmp_path: Path) -> None:
|
||||
revision = _write_revision(
|
||||
tmp_path, "texture_patch:\n vector:\n dpi: 508\n"
|
||||
)
|
||||
with pytest.raises(TextureConfigError) as excinfo:
|
||||
resolve_texture_settings(RevisionPaths.from_revision_dir(revision))
|
||||
assert "dpi" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_texture_patch_must_be_a_mapping(tmp_path: Path) -> None:
|
||||
revision = _write_revision(tmp_path, "texture_patch: patches\n")
|
||||
with pytest.raises(TextureConfigError) as excinfo:
|
||||
@@ -266,6 +294,8 @@ def test_texture_patch_must_be_a_mapping(tmp_path: Path) -> None:
|
||||
({"color_r": 2.0}, "color_r"),
|
||||
({"edge_sample_grid": 1}, "edge_sample_grid"),
|
||||
({"seed": "abc"}, "seed"),
|
||||
({"vector": {"width_mm": 0}}, "vector.width_mm"),
|
||||
({"vector": {"height_mm": "large"}}, "vector.height_mm"),
|
||||
({"cross": {"enabled": "yes"}}, "cross.enabled"),
|
||||
({"cross": {"length_px": 0}}, "cross.length_px"),
|
||||
({"cross": {"color": [2.0, 0.0, 0.0, 1.0]}}, "cross.color"),
|
||||
@@ -328,10 +358,14 @@ def test_job_round_trips_with_absolute_paths(revision_dir: Path, tmp_path: Path)
|
||||
}
|
||||
assert "output" not in job
|
||||
assert [entry["uv_map"] for entry in job["outputs"]] == ["produce", "compute"]
|
||||
assert [Path(entry["output"]).name for entry in job["outputs"]] == [
|
||||
filename for _, filename in DEFAULT_OUTPUTS
|
||||
]
|
||||
assert all(Path(entry["output"]).is_absolute() for entry in job["outputs"])
|
||||
for entry, (_, png_name) in zip(job["outputs"], DEFAULT_OUTPUTS):
|
||||
stem = Path(png_name).stem
|
||||
assert list(entry["artifacts"]) == list(ARTIFACT_FORMATS)
|
||||
assert {name: Path(path).name for name, path in entry["artifacts"].items()} == {
|
||||
format_name: f"{stem}.{format_name}"
|
||||
for format_name in ARTIFACT_FORMATS
|
||||
}
|
||||
assert all(Path(path).is_absolute() for path in entry["artifacts"].values())
|
||||
assert set(job["objects"]) == {"mesh", "camera"}
|
||||
assert set(job["settings"]) == {
|
||||
"width",
|
||||
@@ -341,8 +375,13 @@ def test_job_round_trips_with_absolute_paths(revision_dir: Path, tmp_path: Path)
|
||||
"color_r",
|
||||
"edge_sample_grid",
|
||||
"seed",
|
||||
"vector",
|
||||
"cross",
|
||||
}
|
||||
assert job["settings"]["vector"] == {
|
||||
"width_mm": 164.0,
|
||||
"height_mm": 160.0,
|
||||
}
|
||||
assert set(job["settings"]["cross"]) == {
|
||||
"enabled",
|
||||
"length_px",
|
||||
@@ -374,20 +413,29 @@ def test_write_job_creates_output_directory(revision_dir: Path, tmp_path: Path)
|
||||
def _fake_blender(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
exit_code: int,
|
||||
write_pngs: bool = True,
|
||||
write_artifacts: bool = True,
|
||||
skip_uv: str | None = None,
|
||||
skip_format: str | None = None,
|
||||
):
|
||||
calls: list[tuple[str, Path, list[str]]] = []
|
||||
payloads = {
|
||||
"png": b"\x89PNG\r\n\x1a\n",
|
||||
"svg": b"<svg/>",
|
||||
"pdf": b"%PDF-1.4\n",
|
||||
}
|
||||
|
||||
def fake_run(script: str, blend: Path, extra_args: list[str]):
|
||||
calls.append((script, blend, list(extra_args)))
|
||||
if write_pngs:
|
||||
if write_artifacts:
|
||||
job_path = Path(extra_args[extra_args.index("--job") + 1])
|
||||
job = json.loads(job_path.read_text(encoding="utf-8"))
|
||||
for entry in job["outputs"]:
|
||||
if skip_uv is not None and entry["uv_map"] == skip_uv:
|
||||
continue
|
||||
Path(entry["output"]).write_bytes(b"\x89PNG\r\n\x1a\n")
|
||||
for format_name, raw_path in entry["artifacts"].items():
|
||||
if skip_uv is not None and entry["uv_map"] == skip_uv:
|
||||
continue
|
||||
if skip_format is not None and format_name == skip_format:
|
||||
continue
|
||||
Path(raw_path).write_bytes(payloads[format_name])
|
||||
return exit_code
|
||||
|
||||
monkeypatch.setattr(cli, "_run_blender_script", fake_run)
|
||||
@@ -410,13 +458,15 @@ def test_cmd_texture_forwards_job_and_verifies_both_artifacts(
|
||||
assert job_path == out_dir.resolve() / TEXTURE_JOB_FILENAME
|
||||
|
||||
for _, filename in DEFAULT_OUTPUTS:
|
||||
assert (out_dir / filename).is_file()
|
||||
stem = Path(filename).stem
|
||||
for format_name in ARTIFACT_FORMATS:
|
||||
assert (out_dir / f"{stem}.{format_name}").is_file()
|
||||
|
||||
|
||||
def test_cmd_texture_propagates_blender_failure(
|
||||
monkeypatch: pytest.MonkeyPatch, revision_dir: Path, tmp_path: Path
|
||||
) -> None:
|
||||
_fake_blender(monkeypatch, 9, write_pngs=False)
|
||||
_fake_blender(monkeypatch, 9, write_artifacts=False)
|
||||
namespace = _texture_namespace(revision_dir, tmp_path / "out")
|
||||
assert cli.cmd_texture(revision_dir, namespace) == 9
|
||||
|
||||
@@ -424,7 +474,7 @@ def test_cmd_texture_propagates_blender_failure(
|
||||
def test_cmd_texture_detects_both_textures_missing(
|
||||
monkeypatch: pytest.MonkeyPatch, revision_dir: Path, tmp_path: Path
|
||||
) -> None:
|
||||
_fake_blender(monkeypatch, 0, write_pngs=False)
|
||||
_fake_blender(monkeypatch, 0, write_artifacts=False)
|
||||
namespace = _texture_namespace(revision_dir, tmp_path / "out")
|
||||
assert cli.cmd_texture(revision_dir, namespace) == 3
|
||||
|
||||
@@ -438,6 +488,15 @@ def test_cmd_texture_detects_partial_pair(
|
||||
assert cli.cmd_texture(revision_dir, namespace) == 3
|
||||
|
||||
|
||||
def test_cmd_texture_detects_one_missing_vector_format(
|
||||
monkeypatch: pytest.MonkeyPatch, revision_dir: Path, tmp_path: Path
|
||||
) -> None:
|
||||
"""Every UV target must have PNG, SVG, and PDF before success is reported."""
|
||||
_fake_blender(monkeypatch, 0, skip_format="svg")
|
||||
namespace = _texture_namespace(revision_dir, tmp_path / "out")
|
||||
assert cli.cmd_texture(revision_dir, namespace) == 3
|
||||
|
||||
|
||||
def test_main_texture_parses_flags_into_job(
|
||||
monkeypatch: pytest.MonkeyPatch, revision_dir: Path, tmp_path: Path
|
||||
) -> None:
|
||||
@@ -475,7 +534,9 @@ def test_main_texture_parses_flags_into_job(
|
||||
assert job["settings"]["seed"] == 7
|
||||
assert job["settings"]["cross"]["enabled"] is False
|
||||
for _, filename in DEFAULT_OUTPUTS:
|
||||
assert (out_dir / filename).is_file()
|
||||
stem = Path(filename).stem
|
||||
for format_name in ARTIFACT_FORMATS:
|
||||
assert (out_dir / f"{stem}.{format_name}").is_file()
|
||||
|
||||
|
||||
def test_main_texture_rejects_filename_flag(
|
||||
|
||||
Reference in New Issue
Block a user