initial version of random angle strokes
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
"""The binary CLI must keep trials separate from the source colour artwork."""
|
||||
|
||||
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
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
|
||||
def test_binary_preview_uses_job_and_preserves_colour_source(tmp_path, monkeypatch):
|
||||
paths = RevisionPaths.from_revision_dir(REVISION)
|
||||
colour = tmp_path / "colour.png"
|
||||
colour.write_bytes(b"original colour")
|
||||
job = {
|
||||
"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(colour)}}],
|
||||
}
|
||||
job_path = tmp_path / "_texture_job.json"
|
||||
job_path.write_text(json.dumps(job), encoding="utf-8")
|
||||
output_dir = tmp_path / "binary"
|
||||
calls = []
|
||||
|
||||
def run_blender(script, blend, args):
|
||||
calls.append((script, blend, args))
|
||||
output_dir.mkdir()
|
||||
(output_dir / "compute_camera_compensated_width.png").write_bytes(b"binary")
|
||||
return 0
|
||||
|
||||
monkeypatch.setattr(cli, "_run_blender_script", run_blender)
|
||||
assert cli.cmd_binary_preview(REVISION, _args(job_path, output_dir)) == 0
|
||||
assert colour.read_bytes() == b"original colour"
|
||||
assert calls[0][0] == "binary_pattern_from_color.py"
|
||||
assert "--camera-width-fraction" in calls[0][2]
|
||||
assert cli.cmd_binary_preview(REVISION, _args(job_path, output_dir)) == 2
|
||||
assert len(calls) == 1
|
||||
|
||||
|
||||
def test_binary_preview_rejects_job_from_other_scene(tmp_path, monkeypatch):
|
||||
paths = RevisionPaths.from_revision_dir(REVISION)
|
||||
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(tmp_path / "other.blend"),
|
||||
"outputs": [{"uv_map": "compute", "artifacts": {"png": "missing.png"}}],
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
cli,
|
||||
"_run_blender_script",
|
||||
lambda *_: (_ for _ in ()).throw(AssertionError("Blender must not run")),
|
||||
)
|
||||
assert cli.cmd_binary_preview(REVISION, _args(job_path, tmp_path / "binary")) == 2
|
||||
+7
-11
@@ -34,7 +34,7 @@ MANIFEST_SCHEMA = "orisys-sensor-design/probe-render-manifest-v1"
|
||||
#: NOT pinned here: the user changes its render resolution as the bench is re-targeted
|
||||
#: (500x500 -> 250x250), and a literal would call that legitimate edit a regression. What
|
||||
#: the end-to-end test guards instead is that the pipeline never writes the blend.
|
||||
MESH_PX_PER_MM = {"min": 10.4994, "p50": 13.4302, "max": 38.5943}
|
||||
MESH_PX_PER_MM = {"min": 3.80165, "p50": 4.87396, "max": 20.94209}
|
||||
FRAME_COUNT = 31
|
||||
REFERENCE_NAME = "000_reference"
|
||||
REFERENCE_NULL_NAME = "reference_null"
|
||||
@@ -651,14 +651,9 @@ def test_render_exits_3_when_a_required_artifact_is_missing(
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def real_npz(tmp_path_factory) -> Path:
|
||||
"""The deform stage's contract artifact, produced by T1's script if absent."""
|
||||
canonical = deform.probe_artifact_paths(deform.probe_output_dir(REPO, paths_for_deform()))[
|
||||
"frames"
|
||||
]
|
||||
if canonical.is_file() and canonical.stat().st_size > 0:
|
||||
return canonical
|
||||
"""Generate deformation from the current scene so mesh edits cannot reuse stale frames."""
|
||||
if BLENDER is None:
|
||||
pytest.skip("probe_frames.npz absent and no Blender to build it")
|
||||
pytest.skip("no Blender to build probe_frames.npz")
|
||||
scratch = tmp_path_factory.mktemp("t2_deform")
|
||||
rev = RevisionPaths.from_revision_dir(REVISION_DIR)
|
||||
probe = deform.resolve_probe(rev)
|
||||
@@ -694,6 +689,7 @@ def test_end_to_end_render_manifest(paths, settings, real_npz, tmp_path):
|
||||
# --- identity and settings that must never silently change ---
|
||||
assert manifest["schema"] == MANIFEST_SCHEMA
|
||||
assert manifest["uv_map"] == "compute"
|
||||
assert manifest["camera"] == "cam_sensor"
|
||||
width, height = tuple(settings.resolution)
|
||||
assert (manifest["width"], manifest["height"]) == (width, height)
|
||||
applied = manifest["settings_applied"]
|
||||
@@ -736,14 +732,14 @@ def test_end_to_end_render_manifest(paths, settings, real_npz, tmp_path):
|
||||
<= mesh_scale["p95"]
|
||||
<= mesh_scale["max"]
|
||||
)
|
||||
# MESH_PX_PER_MM is measured at 500x500, and px/mm is linear in the render width, so
|
||||
# MESH_PX_PER_MM is measured at 250x250, and px/mm is linear in the render width, so
|
||||
# scale it to the resolution the design declares rather than re-pinning it every time
|
||||
# the bench is re-targeted. Halving the width must halve the scale.
|
||||
factor = width / 500.0
|
||||
factor = width / 250.0
|
||||
assert mesh_scale["min"] == pytest.approx(MESH_PX_PER_MM["min"] * factor, abs=0.02)
|
||||
assert mesh_scale["p50"] == pytest.approx(MESH_PX_PER_MM["p50"] * factor, abs=0.02)
|
||||
assert mesh_scale["max"] == pytest.approx(MESH_PX_PER_MM["max"] * factor, abs=0.05)
|
||||
assert mesh_scale["basis"] == "per-mesh-edge projected px / 3D mm, all 7341 edges"
|
||||
assert mesh_scale["basis"] == "per-mesh-edge projected px / 3D mm, all 7590 edges"
|
||||
|
||||
probe_scale = manifest["probe_px_per_mm"]
|
||||
axis_scales = [probe_scale["x"], probe_scale["y"], probe_scale["z"]]
|
||||
|
||||
Reference in New Issue
Block a user