Files
orisys-sensor-design/tests/test_stroke_texture_cli.py
T
ykcheungab 30a903a299 Give the stroke pattern one mode and design.yaml tuning
The twelve experimental modes collapse to camera_compensated_width, whose ten
knobs now come from a `stroke_pattern` block, and the PNG-only binary-preview
command goes with them as a strict subset of stroke-texture.

Verified: output is byte-identical at the defaults against the previous code, a
knob change is visible in the artifacts, the suite is green and the doc gate is
clean.
2026-10-04 09:40:17 +08:00

108 lines
4.0 KiB
Python

"""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))
# exist_ok: the CLI writes _stroke_pattern_params.json into the output directory before
# it launches Blender, so the directory already exists.
output_dir.mkdir(parents=True, exist_ok=True)
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"
# `--seed` and `--camera-width-fraction` are overrides folded into the params file, so the
# contract is now the effective tuning the script reads.
params = json.loads(
Path(calls[0][2][calls[0][2].index("--params") + 1]).read_text(encoding="utf-8")
)
assert params["seed"] == 13
assert params["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