new grayscale output for testing laser, not working, will change to new design

This commit is contained in:
ykcheungab
2026-09-30 16:12:06 +08:00
parent 37e3e8400b
commit f0454253c2
27 changed files with 381132 additions and 270 deletions
+59 -142
View File
@@ -8,12 +8,6 @@ meters_per_source_unit: 0.001
source_blend: source/geometry.blend
# Expected sensing surface topology for r01 (from shipped 30fps-0703 runtime).
# First exports from geometry.blend may differ until topology is locked.
expected:
sensing_vertices: 2595
sensing_triangles: 5005
frames:
sensor: origin_sensor
@@ -22,115 +16,79 @@ objects:
cam_sensor: cam_sensor
origin_sensor: origin_sensor
# Headless colored-patch texture generation (`orisys-design texture`).
# UV unwrapping is still done in the Blender GUI: the headless run consumes the
# UV maps already stored in source_blend and never creates or unwraps one.
# One PNG/SVG/PDF set is written per UV map below. All maps and formats share
# the settings and patch colours, so they differ only by UV layout.
# These are the revision defaults; CLI flags override every value.
# Read on every export; changing `assembly` silently changes what ships.
collections:
frames: FRAMES
export_compute: EXPORT_COMPUTE
assembly: ASSEMBLY
design_sim: DESIGN_SIM
exclude: EXCLUDE
# Topology guard. NOT YET WIRED - no code reads these, and the measured topology is
# 2584/5007. Unverified until connected to the export manifest; see
# docs/design-parameters.md part 5.
expected:
sensing_vertices: 2595
sensing_triangles: 5005
# Camera-space artwork on UV unwraps (`orisys-design texture`).
# See docs/design-parameters.md part 1.
texture_patch:
mesh: sensing_surface
camera: cam_sensor
uv_outputs:
produce: patch_texture_produce.png # hardware production layout
compute: patch_texture_compute.png # compute/runtime layout
width: 3700 # 1850*2
height: 3280 # 1640*2
# Camera frame is square (1000x1000), so equal counts make camera-space
# elements square. The original 80x45 grid was copied from a 16:9 camera.
nx: 80
uv_outputs: # ORDER IS OBSERVABLE: becomes the job's outputs[] array
produce: patch_texture_produce.png
compute: patch_texture_compute.png
width: 3700 # raster px; keep == px_per_mm * vector below
height: 3280
nx: 80 # square camera frame, so nx == ny
ny: 80
color_r: 0.05
edge_sample_grid: 5
seed: 7 # fixed so PNG/SVG/PDF share one reproducible colour set
vector:
width_mm: 18.5 # editable SVG/PDF production page width
height_mm: 16.4 # editable SVG/PDF production page height
seed: 7 # pinned: one reproducible colour set across PNG/SVG/PDF
vector: # physical page; the `page: fixed` fallback for texture_patch_views
width_mm: 18.5
height_mm: 16.4
cross:
enabled: true
length_px: 40
thickness_px: 3
color: [1.0, 0.0, 0.0, 1.0] # red reference cross
color: [1.0, 0.0, 0.0, 1.0]
# Multi-view production textures (`orisys-design texture-views`).
#
# `cam_uv_1` / `cam_uv_2` already existed in this blend; `uv_1` / `uv_2` and the seam were
# added to `sensing_surface`. Verified: the marked seam is a valid cut — 45 edges splitting
# the surface into 2 components of 308.314 / 308.311 mm^2 (50.0 / 50.0) — and at the default
# 62 degree limit the partition leaves 0.100 mm^2 of hole (fraction 0.0002), well inside
# max_hole_fraction. Coverage: 4960/5007 triangles owned, 20454 + 21483 fragments emitted.
#
# NOTE: this revision contains SIX sensing-surface variants (`sensing_surface`, `_0`, `_0918`,
# `_0928`, `_45`, `_918_s`). `_0928` is the only one carrying the seam and `uv_1`/`uv_2`, so
# `sensing_surface` is a copy of it. Two consequences to be aware of:
# 1. `_0928` is a COINCIDENT duplicate of `sensing_surface` at identical coordinates. The
# `EXCLUDE` collection has `exclude=False`, so it is not actually excluded from the view
# layer, and the raycast hits the duplicate instead of the target. That rejects every
# fragment and the region cameras return 0 — `texture-views` now fails loudly and names
# the intercepting object rather than writing blank textures. `_0928` is hidden.
# 2. The probe/render baseline (`build/probe/finger-xynova/r01/probe_frames.npz`) was built
# from the 2508-vertex `sensing_surface_918_s`, while `sensing_surface` has 2584 vertices.
# `tests/test_render.py::test_end_to_end_render_manifest` therefore fails on the vertex
# count. That is a fixture/stale-baseline mismatch, not a texture bug: either regenerate
# the probe frames for the current surface, or author the seam and the extra UV layouts
# on whichever surface is canonical. `expected.*` below would have caught this and is
# still read by nothing.
# Projected views with per-triangle ownership (`orisys-design texture-views`).
# See docs/design-parameters.md part 2.
texture_patch_views:
enabled: true
pattern_camera: cam_sensor # the pattern every layout carries, same source as `compute`
# Per-view page sizing. Measured here: the uv_1/uv_2 layouts are 21.1989 x 23.2003 mm
# (aspect 0.9137) and 21.2001 x 23.2003 mm (aspect 0.9138) — PORTRAIT. `vector:` above says
# 18.5 x 16.4 (aspect 1.1280), which is LANDSCAPE, so the artwork was stretched 23% in the
# wrong direction. `auto` derives the page per view and proves the layout really is a
# bounds-projection from the named camera before using the result.
#
# `px_per_mm: 200` reproduces the existing density exactly (3700/18.5 = 200.0).
#
# `fixed` keeps `texture_patch.vector`, which unwrapped layouts (`produce`, `compute`)
# require — they are not projections.
#
# Overlay note: the scene render is 250x250 while these layouts are 0.9138 aspect, so set the
# render resolution to the emitted raster size and use "Stretch to fill".
pattern_camera: cam_sensor # the ink source; a pair's `camera` only decides ownership
page:
mode: auto
mode: auto # auto = derived per view (projections only); fixed = use vector above
px_per_mm: 200
pairs:
- id: uv_1
camera: cam_uv_1 # REGION camera
camera: cam_uv_1 # REGION camera, not the pattern source
uv_map: uv_1
- id: uv_2
camera: cam_uv_2
uv_map: uv_2
merge:
mode: authored # honour the 45-edge seam marked on the mesh
# RE-MEASURED 2026-09-29 after the region cameras moved (cam_uv_1 az 318.9 -> 308.3,
# elevation -43.3 -> -24.5, distance 37.6 -> 32.6 mm). Sweep at min_triangle_coverage 0.60:
# 62deg -> hole 0.0480 FAILS max_hole_fraction (a visible bald patch of 29.6 mm^2)
# 65deg -> hole 0.0130 passes, but barely
# 70deg -> hole 0.0001 passes with margin <-- current
# The limit is a property of the camera poses and the surface, so MOVING THE CAMERAS
# INVALIDATES IT. Re-run the sweep after any re-pose. min_triangle_coverage is not binding
# here: relaxing it to 0 changes nothing at 62 or 70 degrees.
incidence_limit_deg: 70.0
mode: authored # honour the seam marked on the mesh
incidence_limit_deg: 70.0 # DO NOT DELETE: built-in default 62.0 FAILS here (4.8% hole)
max_hole_fraction: 0.02 # enforced: the run fails above this rather than shipping a gap
min_footprint_mm2: 2.0e-3
min_triangle_coverage: 0.60
max_hole_fraction: 0.02 # enforced: the run fails above this rather than shipping a gap
component_policy: best_coverage
seam:
boundary: use_seam
pins: []
# Empty band along the seam, to absorb two-pass registration error. Default total width is
# 2 x alignment_tolerance_mm = 0.2 mm, half per file; `total_mm` overrides the derivation.
gap:
enabled: false # false = hard butt at the seam, no band at all
apply: post_process # post_process = straight edge | geometry_trim = sawtooth
treatment: no_print # the band carries nothing
alignment_tolerance_mm: 0.1 # per-pass placement accuracy, mm
enabled: false # false = hard butt at the seam, no band at all
apply: post_process # post_process = straight edge | geometry_trim = sawtooth
treatment: no_print
alignment_tolerance_mm: 0.1
print:
base_fill: owned # white ground, clipped by the seam
base_fill: owned # white ground, already split by the seam
# Post-generation printability inspection (`orisys-design inspect`). Advisory
# only: it reports a verdict but never blocks generation or changes exit codes.
# Advisory only: reports a verdict, never blocks generation or changes exit codes.
# Millimetres are measured on the vector page declared above.
printability:
printer_dpi: 600 # target UV printer resolution
@@ -140,21 +98,14 @@ printability:
max_sliver_ratio: 4.0 # longest edge / minimum width
min_patch_area_mm2: 1.0e-4 # area above which a polygon is a material feature
# Optical stack above the markers, ordered from the camera inward.
# The markers sit on the sensing surface underneath these layers, so their image
# is displaced relative to the straight-line pinhole model.
#
# ior values are LITERATURE ASSUMPTIONS, not measurements - that is what
# ior_source records, so an assumed index can never pass for a measured one.
# Replace them with measured values when available; nothing else needs to change.
# Optical stack above the markers, ordered camera-inward. ior values are LITERATURE
# ASSUMPTIONS, not measurements - that is what ior_source records.
optics:
marker_surface: sensing_surface
layers:
# Solids, not surface pairs: `shell` and `rubber-1` are each one closed mesh,
# so the ray enters and leaves the same object.
# Stack: camera > shell > rubber-1 > sensing_surface > rubber-2.
# rubber-2 is BEHIND the markers, so it does not refract camera light and is
# deliberately not a layer.
# Solids, not surface pairs: `shell` and `rubber-1` are each one closed mesh, so the
# ray enters and leaves the same object. rubber-2 is BEHIND the markers, so it does
# not refract camera light and is deliberately not a layer.
- name: plastic_shell
solid: shell
ior: 1.59
@@ -165,48 +116,33 @@ optics:
ior_source: assumed_silicone_literature
# Probe sensitivity bench (`orisys-design deform|render|track|sensitivity|optimise`).
# Deforms the vertices of `group` through known x/y/z displacements expressed in a
# local probe-average basis (normal force along z, shear along x/y), renders what
# `camera` sees, and measures DIS optical flow against the zero-displacement frame.
#
# Contract: docs/probe-pipeline.md. The `.blend` is never written by the pipeline.
# Contract: docs/probe-pipeline.md. The .blend is never written by the pipeline.
probe:
mesh: sensing_surface
camera: cam_sensor
group: probe
uv_map: compute
texture: patch_texture_compute.png
# Proportional falloff radius for the outer rubber. Note this mesh has a median
# edge of 0.63 mm, so 0.5 mm reaches only the probe's immediate neighbours; the
# Median mesh edge here is 0.63 mm, so 0.5 mm reaches only immediate neighbours; the
# deform report prints how many vertices actually move rather than hiding it.
radius_mm: 0.5
falloff: smooth
step_mm: 0.01
max_mm: 0.10
axes: [x, y, z]
# Frames are rendered at the revision's real camera resolution. Keep this in step with
# the camera in source/geometry.blend: the pipeline overrides the scene resolution from
# here, so a blend edited to a different size is silently ignored otherwise.
#
# Single scale on purpose. The ROI radius is radius_mm x the probe-local px/mm, so at
# 125x125 it would be 1.66 px against an 8 px DIS window: the estimator measures its own
# regulariser, not the displacement. See docs/probe-findings.md section 2.
# The pipeline overrides the scene resolution from here, so a blend edited to a
# different size is silently ignored. Keep in step with source/geometry.blend.
resolution: [250, 250]
process_resolution: [250, 250]
# Mirrors the SDK sensor tracker keys (orisys-sdk config/finger.json), EXCEPT
# dis_mode. The estimator window is 8 * 2**dis_mode input px (16 at dis_mode=1, 8 at 0)
# and the moving region is ~6.6 px across at 250x250, so the shipped dis_mode=1 gives
# window/moving = 2.42: DIS's smoothness pulls the estimate toward zero and the measured
# response drops ~3x (1.42 -> 4.30 px/mm at the matched cell). dis_mode=0 gives 1.21,
# the same ratio as 500x500 with dis_mode=1, which recovers to ~73%.
# Mirrors the SDK tracker keys EXCEPT dis_mode: the 8*2**dis_mode window against a
# ~6.6 px moving region makes the shipped dis_mode=1 collapse the estimate ~3x.
# See docs/probe-findings.md section 2.
# If the deployed tracker is fixed at dis_mode=1, run the bench at 500x500 instead.
tracker:
algorithm: dis
dis_preset: medium
dis_mode: 0
# Standard (not AgX) and denoise off: a filmic view transform silently distorts
# the marker values the tracker is supposed to measure.
# Standard (not AgX) and denoise off: a filmic view transform silently distorts the
# marker values the tracker is supposed to measure.
render:
engine: CYCLES
samples: 16
@@ -214,39 +150,20 @@ probe:
view_transform: Standard
interpolate: Closest
seed: 0
# Noise calibration and the injected noise model.
#
# The estimator's `floor_px` is measured as reference vs a re-render at seed+1, which
# is Cycles antialiasing jitter, NOT sensor noise: on this data it differs by ~5 grey
# levels RMS on half the pixels and scales with edge contrast. Ranking patterns by
# gain/that-floor is unsound - the ordering reverses under additive noise, and it
# understates the real idle floor measured from the footage below, coarse patterns
# most. So the render-seed floor is kept only as a reproducibility diagnostic and the
# score is anchored to the injected model plus this measured calibration.
# `floor_px` from a re-render at seed+1 is Cycles antialiasing jitter, NOT sensor
# noise, so it is kept only as a reproducibility diagnostic; the score is anchored to
# the injected model plus the idle-footage calibration below.
# See docs/probe-findings.md section 2.
noise:
# Real idle (no-contact) footage, relative to the repo root. Measured: floor
# ~0.23 px at 1280x720 (short baseline == long baseline, so it is white noise and
# the clip is genuinely idle), dominant marker period ~25 px, i.e. ~0.0093 floor
# per period. That is 2-6x the simulated render-seed floor per cell.
idle_footage: ../data/dd02-0703/190.mp4
roi_fraction: 0.18
max_frames: 120
# Additive white Gaussian in grey levels. Defaults are deliberately small: each
# sigma costs a full DIS pass, and the process scale is a resample of the render
# one, so only the render scale is evaluated unless asked.
sigmas: [4.0, 16.0]
draws: 1
seed: 0
models: [render_seed, injected]
scales: [render]
collections:
frames: FRAMES
export_compute: EXPORT_COMPUTE
assembly: ASSEMBLY
design_sim: DESIGN_SIM
exclude: EXCLUDE
notes: |
Canonical source is geometry.blend with named collections and objects.
Compute export uses sensing_surface + cam_sensor relative to origin_sensor.
Binary file not shown.
+52 -104
View File
@@ -8,12 +8,6 @@ meters_per_source_unit: 0.001
source_blend: source/geometry.blend
# Expected sensing surface topology for r01 (from shipped 30fps-0703 runtime).
# First exports from geometry.blend may differ until topology is locked.
expected:
sensing_vertices: 1032
sensing_triangles: 1974
frames:
sensor: origin_sensor
@@ -22,124 +16,85 @@ objects:
cam_sensor: cam_sensor
origin_sensor: origin_sensor
# Headless colored-patch texture generation (`orisys-design texture`).
# UV unwrapping is still done in the Blender GUI: the headless run consumes the
# UV maps already stored in source_blend and never creates or unwraps one.
# One PNG/SVG/PDF set is written per UV map below. All maps and formats share
# the settings and patch colours, so they differ only by UV layout.
# These are the revision defaults; CLI flags override every value.
# Read on every export; changing `assembly` silently changes what ships.
collections:
frames: FRAMES
export_compute: EXPORT_COMPUTE
assembly: ASSEMBLY
design_sim: DESIGN_SIM
exclude: EXCLUDE
# Topology guard. NOT YET WIRED - no code reads these, and the measured topology is
# 1034/1977. Unverified until connected to the export manifest; see
# docs/design-parameters.md part 5.
expected:
sensing_vertices: 1032
sensing_triangles: 1974
# Camera-space artwork on UV unwraps (`orisys-design texture`).
# See docs/design-parameters.md part 1.
texture_patch:
mesh: sensing_surface
camera: cam_sensor
uv_outputs:
produce: patch_texture_produce.png # hardware production layout
compute: patch_texture_compute.png # compute/runtime layout
width: 3280 # 1640*2
height: 3200 # 1600*2
nx: 80
uv_outputs: # ORDER IS OBSERVABLE: becomes the job's outputs[] array
produce: patch_texture_produce.png
compute: patch_texture_compute.png
width: 3280 # raster px; keep == px_per_mm * vector below
height: 3200
nx: 80 # 16:9 camera frame, left as-is from the original grid
ny: 45
color_r: 0.05
edge_sample_grid: 5
seed: 7 # fixed so PNG/SVG/PDF share one reproducible colour set
vector:
width_mm: 16.4 # editable SVG/PDF production page width
height_mm: 16.0 # editable SVG/PDF production page height
seed: 7 # pinned: one reproducible colour set across PNG/SVG/PDF
vector: # physical page; the `page: fixed` fallback for texture_patch_views
width_mm: 16.4
height_mm: 16.0
cross:
enabled: true
length_px: 40
thickness_px: 3
color: [1.0, 0.0, 0.0, 1.0] # red reference cross
color: [1.0, 0.0, 0.0, 1.0]
# Multi-view production textures (`orisys-design texture-views`).
#
# The PATTERN is grounded on the sensing camera, not on the production views. `uv_1` and
# `uv_2` are two different *parameterisations* of the one surface pattern that
# `texture_compute` carries, so the printed marker stays a single pattern across the whole
# surface. `cam_uv_1` / `cam_uv_2` authored those layouts in Blender (Project from View
# (Bounds)) and now serve as each pair's REGION camera: they decide which surface that pair
# may own. Neither is a pattern source.
#
# OWNERSHIP closes the overlap. `uv_1` and `uv_2` are both full-surface layouts, so printing
# both would double-ink essentially the whole part. Every loop triangle is assigned to
# exactly one pair and each file emits only its own triangles, which makes zero double ink a
# property of the partition rather than something to measure after printing.
#
# Rendering settings (page size, grid, palette, seed) come from `texture_patch` above,
# because they are decisions about the printed page rather than about a particular layout.
# `produce`/`compute` and `orisys-design texture` are untouched.
# Projected views with per-triangle ownership (`orisys-design texture-views`).
# The PATTERN is grounded on cam_sensor, not on the production views: uv_1 and uv_2 are two
# parameterisations of the one surface pattern `compute` carries, so the printed marker stays
# a single pattern. `cam_uv_1`/`cam_uv_2` authored those layouts (Project from View (Bounds))
# and now serve as each pair's REGION camera, deciding what that pair may own.
# Ownership closes the overlap: both layouts are full-surface, so without a partition printing
# both would double-ink essentially the whole part.
# See docs/design-parameters.md part 2.
texture_patch_views:
enabled: true
pattern_camera: cam_sensor # the pattern every layout carries, same source as `compute`
# Per-view page sizing. A layout authored by Project from View (Bounds) maps [0,1]^2 to the
# image-plane extent of the mesh through ITS camera, so the page is measurable and differs
# per view: measured 17.5116 x 15.9772 mm for uv_1 (aspect 1.0960) and 17.5114 x 15.9820 mm
# for uv_2 (aspect 1.0957). `vector:` above says 16.4 x 16.0 (aspect 1.0250), which stretched
# the artwork by 6.9% along u. `auto` derives it and proves the layout really is a
# bounds-projection from the named camera before using the result.
#
# `px_per_mm: 200` reproduces the existing density exactly (3280/16.4 = 3700/18.5 = 200.0),
# and the page is set from the rounded raster so page and raster always share an aspect.
#
# `fixed` keeps `texture_patch.vector`. Unwrapped layouts (`produce`, `compute`) are not
# projections and must use it — a camera-derived page would be meaningless there.
#
# To overlay a texture on a render from its camera: set the render resolution to the emitted
# raster size and the background image to "Stretch to fill". At any other resolution the
# scene aspect (1280x720 here) will distort the overlay regardless of the page.
pattern_camera: cam_sensor # the ink source; a pair's `camera` only decides ownership
page:
mode: auto
mode: auto # auto = derived per view (projections only); fixed = use vector above
px_per_mm: 200
pairs:
- id: uv_1 # layout authored from cam_uv_1
camera: cam_uv_1 # REGION camera: what this pair may own
- id: uv_1
camera: cam_uv_1 # REGION camera, not the pattern source
uv_map: uv_1
- id: uv_2
camera: cam_uv_2
uv_map: uv_2
merge:
# `authored` honours the seam marked on the mesh: 32 edges on y = 0, which cuts the
# surface into 2 components of 177.882 / 177.885 mm^2 (50.0 / 50.0). Because the two
# cameras are mirrored about XZ that plane is also where incidence_uv1 == incidence_uv2,
# so the hand-drawn seam coincides with the metric-optimal boundary: measured coverage
# differs from `computed` by 0.0014 at 60 deg and by nothing from 62 deg up.
mode: authored
# Calibrated on this mesh (355.767 mm^2, 1977 tris): 60 deg leaves 0.0218 holes and FAILS
# max_hole_fraction, 62 deg leaves 0.0000. 65 deg was an earlier estimate from the
# pre-edit mesh; 62 is the measured threshold.
incidence_limit_deg: 62.0
min_footprint_mm2: 2.0e-3 # ~1 printer dot at 600 dpi, squared x 1.1
mode: authored # honour the seam marked on the mesh
incidence_limit_deg: 62.0 # calibrated to THIS revision's cameras; re-measure if they move
max_hole_fraction: 0.02 # enforced: the run fails above this rather than shipping a gap
min_footprint_mm2: 2.0e-3
min_triangle_coverage: 0.60
max_hole_fraction: 0.02
component_policy: best_coverage # not `pinned`: both views qualify, so let the metric rank
seam:
boundary: use_seam # read mesh.edges[].use_seam as a hard graph cut
pins: [] # {component, view, reason} to override a component's owner
# Empty band along the seam, to absorb two-pass registration error. Without it the two
# files butt exactly at the seam and any placement error makes them overlap there, which
# is the delamination case.
#
# The width is derived, not guessed. Each pass is placed to within
# `alignment_tolerance_mm`, so the two can close on each other by twice that; the band
# must be at least that wide. Default total = 2 x tolerance = 0.2 mm, half per file, and
# `total_mm` overrides the derivation if a different margin is wanted.
boundary: use_seam # read mesh.edges[].use_seam as a hard graph cut
pins: [] # {component, view, reason} to override a component's owner
gap:
enabled: true # false = hard butt at the seam, no band at all
apply: post_process # post_process = straight edge | geometry_trim = sawtooth
treatment: no_print # the band carries nothing
alignment_tolerance_mm: 0.1 # per-pass placement accuracy, mm
# total_mm: 0.2 # default: 2 x alignment_tolerance_mm
enabled: true # false = hard butt at the seam, no band at all
apply: post_process # post_process = straight edge | geometry_trim = sawtooth
treatment: no_print
alignment_tolerance_mm: 0.1 # per-pass placement accuracy; band total = 2x this
print:
# Every artifact carries a full-page black `<rect id="background-fill">`. That background
# is itself a print element, so it cannot be used as the "no print" marker: the split is
# expressed by WHERE the white mesh layer is drawn and where the colour patches are.
#
# `owned` keeps the same white ground the `compute` texture carries, but clipped to the
# triangles this view owns. So white is split by the seam exactly like the pattern is,
# and neither file paints white over the other's territory.
base_fill: owned
base_fill: owned # white ground, already split by the seam
# Post-generation printability inspection (`orisys-design inspect`). Advisory
# only: it reports a verdict but never blocks generation or changes exit codes.
# Advisory only: reports a verdict, never blocks generation or changes exit codes.
# Millimetres are measured on the vector page declared above.
printability:
printer_dpi: 600 # target UV printer resolution
@@ -149,13 +104,6 @@ printability:
max_sliver_ratio: 4.0 # longest edge / minimum width
min_patch_area_mm2: 1.0e-4 # area above which a polygon is a material feature
collections:
frames: FRAMES
export_compute: EXPORT_COMPUTE
assembly: ASSEMBLY
design_sim: DESIGN_SIM
exclude: EXCLUDE
notes: |
Canonical source is geometry.blend with named collections and objects.
Compute export uses sensing_surface + cam_sensor relative to origin_sensor.