30a903a299
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.
108 lines
4.0 KiB
Python
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
|