initial version of random angle strokes

This commit is contained in:
ykcheungab
2026-10-03 20:41:30 +08:00
parent 1675ccb4af
commit 59f48d6d06
7 changed files with 695 additions and 22 deletions
+73
View File
@@ -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
View File
@@ -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"]]