Add stroke texture PNG, SVG, and PDF export
Reuse colour artwork serializers and record source hashes. Verified single and multiview exports plus targeted tests.
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
"""The stroke CLI must preserve colour sources and verify all exported formats."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from orisys_sensor_design import cli
|
||||
from orisys_sensor_design.manifest import RevisionPaths, sha256_file
|
||||
|
||||
REVISION = Path(__file__).resolve().parents[1] / "sensors" / "finger-xynova" / "r01"
|
||||
|
||||
|
||||
def _args(job: Path, output_dir: Path) -> argparse.Namespace:
|
||||
return argparse.Namespace(
|
||||
views=False,
|
||||
source_job=job,
|
||||
output_dir=output_dir,
|
||||
camera_width_fraction=0.5,
|
||||
seed=13,
|
||||
)
|
||||
|
||||
|
||||
def _job(tmp_path: Path) -> tuple[Path, Path, Path]:
|
||||
paths = RevisionPaths.from_revision_dir(REVISION)
|
||||
png = tmp_path / "colour.png"
|
||||
svg = tmp_path / "colour.svg"
|
||||
png.write_bytes(b"colour pixels")
|
||||
svg.write_bytes(b"<svg>colour geometry</svg>")
|
||||
job_path = tmp_path / "_texture_job.json"
|
||||
job_path.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"schema": "orisys-sensor-design/texture-job-v3",
|
||||
"revision_dir": str(paths.root),
|
||||
"source_blend": str(paths.source_blend),
|
||||
"outputs": [
|
||||
{
|
||||
"uv_map": "compute",
|
||||
"artifacts": {"png": str(png), "svg": str(svg)},
|
||||
}
|
||||
],
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
return job_path, png, svg
|
||||
|
||||
|
||||
def test_stroke_cli_exports_three_formats_and_manifest(tmp_path, monkeypatch):
|
||||
job, png, svg = _job(tmp_path)
|
||||
output_dir = tmp_path / "strokes"
|
||||
calls = []
|
||||
|
||||
def run_blender(script, blend, args):
|
||||
calls.append((script, blend, args))
|
||||
output_dir.mkdir()
|
||||
for fmt in ("png", "svg", "pdf"):
|
||||
(output_dir / f"stroke_texture_compute.{fmt}").write_bytes(fmt.encode())
|
||||
return 0
|
||||
|
||||
monkeypatch.setattr(cli, "_run_blender_script", run_blender)
|
||||
assert cli.cmd_stroke_texture(REVISION, _args(job, output_dir)) == 0
|
||||
assert calls[0][0] == "random_stroke_pattern.py"
|
||||
assert calls[0][2][-4:] == ["--seed", "13", "--camera-width-fraction", "0.5"]
|
||||
manifest = json.loads((output_dir / "stroke_texture_manifest.json").read_text())
|
||||
assert manifest["schema"] == "orisys-sensor-design/stroke-texture-manifest-v1"
|
||||
assert manifest["source_job"]["sha256"] == sha256_file(job)
|
||||
assert manifest["outputs"][0]["sources"]["png"]["sha256"] == sha256_file(png)
|
||||
assert manifest["outputs"][0]["sources"]["svg"]["sha256"] == sha256_file(svg)
|
||||
assert set(manifest["outputs"][0]["artifacts"]) == {"png", "svg", "pdf"}
|
||||
assert png.read_bytes() == b"colour pixels"
|
||||
assert cli.cmd_stroke_texture(REVISION, _args(job, output_dir)) == 2
|
||||
assert len(calls) == 1
|
||||
|
||||
|
||||
def test_stroke_cli_refuses_missing_source_svg(tmp_path, monkeypatch):
|
||||
job, _png, svg = _job(tmp_path)
|
||||
svg.unlink()
|
||||
monkeypatch.setattr(
|
||||
cli,
|
||||
"_run_blender_script",
|
||||
lambda *_: (_ for _ in ()).throw(AssertionError("Blender must not run")),
|
||||
)
|
||||
assert cli.cmd_stroke_texture(REVISION, _args(job, tmp_path / "strokes")) == 3
|
||||
|
||||
|
||||
def test_stroke_cli_rejects_incomplete_source_job(tmp_path, monkeypatch):
|
||||
job, _png, _svg = _job(tmp_path)
|
||||
data = json.loads(job.read_text(encoding="utf-8"))
|
||||
del data["outputs"][0]["artifacts"]["svg"]
|
||||
job.write_text(json.dumps(data), encoding="utf-8")
|
||||
monkeypatch.setattr(
|
||||
cli,
|
||||
"_run_blender_script",
|
||||
lambda *_: (_ for _ in ()).throw(AssertionError("Blender must not run")),
|
||||
)
|
||||
assert cli.cmd_stroke_texture(REVISION, _args(job, tmp_path / "strokes")) == 2
|
||||
Reference in New Issue
Block a user