2026-09-19 21:02:34 +08:00
2026-09-16 21:04:28 +08:00
2026-09-19 15:08:57 +08:00
2026-09-19 15:08:57 +08:00
2026-09-19 15:08:57 +08:00
2026-09-16 21:04:28 +08:00

orisys-sensor-design

Nominal sensor geometry and Blender authoring for Orisys. This repo is the design source of truth. Measured calibration, unroll maps, and shipped runtime bundles live in orisys-sdk and compiled .oryscene packages live in orisys-app.

For the full long-term workflow see sensor-design-workflow.md in the workspace root. This repository intentionally implements a minimal v1 for a small team.

Two buckets (daily work)

Bucket Where Contents
Design this repo .blend, nominal export, release manifest
Runtime orisys-sdk / orisys-app measured cal, unroll map, wheel/App assets, .oryscene

Never copy runtime NPZ files into this repo to “fix” a mismatch. Promote through a manifest + lock file instead.

Layout

sensors/finger/
├── current.yaml              # active revision pointer
└── r01/
    ├── design.yaml           # units, frames, object names
    ├── source/geometry.blend # named collections + objects
    ├── profiles/
    │   ├── compute.yaml      # → SDK bundle layout (Studio runtime)
    │   └── studio.yaml       # inputs for orisys-app compile_scene
    └── golden/               # optional preview + metrics

Prerequisites

  • Python 3.10+
  • Blender version pinned in blender.lock.json (install separately)

No Git LFS is required — binaries are committed as ordinary files.

Finding Blender on Windows

The CLI looks for Blender in this order:

  1. Env ORISYS_BLENDER or BLENDER_EXE (absolute path to blender.exe)
  2. blender / blender.exe on PATH
  3. Auto-scan %ProgramFiles%\Blender Foundation\Blender *\blender.exe (prefers blender.lock.json version when installed, otherwise newest)
  4. Optional "executable" in blender.lock.json (local only; do not commit machine paths)
$env:ORISYS_BLENDER = "C:\Program Files\Blender Foundation\Blender 5.0\blender.exe"

Convenience launcher (auto-finds Blender for this process, then exports):

.\tools\export-compute.bat

Commands

Install the CLI (editable):

cd D:\orisys_ws\orisys-sensor-design
python -m pip install -e .

Four commands for v1:

# 1) Check revision metadata and (optionally) Blender scene conventions
orisys-design validate sensors/finger/r01

# 2) Export nominal geometry for a profile (compute → SDK layout)
orisys-design export sensors/finger/r01 --profile compute

# 3) Generate the colored patch texture headlessly
orisys-design texture sensors/finger/r01

# 4) Write release manifest with file hashes
orisys-design manifest sensors/finger/r01

Headless Blender export uses a JSON job file (stdlib json inside Blender — no PyYAML required). orisys-design export writes it automatically from design.yaml:

orisys-design export sensors/finger/r01 --profile compute
# writes build/export/finger/r01/_export_job.json then runs Blender

Manual Blender invocation with the same job:

blender -b sensors/finger/r01/source/geometry.blend -P blender/scripts/export.py -- `
  --job D:\orisys_ws\orisys-sensor-design\build\export\finger\r01\_export_job.json

Optional flag overrides (--output, --mesh, --cam, --origin) win over job values.

Export output is grouped by revision:

build/export/finger/r01/
├── _export_job.json          # Blender input, regenerated by export
├── manifest.json             # release manifest from `orisys-design manifest`
└── compute/                  # clean SDK-shaped payload
    ├── mesh.npz
    ├── camera_intrinsics.npz
    └── world_to_cam.npy

Texture patches (headless)

orisys-design texture generates a PNG, editable SVG, and native vector PDF for each UV map on the sensing surface. The SVG/PDF are built directly from the UV mesh and camera-space patch polygons — they do not embed or trace the PNG. UV unwrapping is still an in-app Blender step: this command consumes the UV maps already stored in source_blend and never creates or unwraps one. Unwrap the mesh in the Blender GUI, save the .blend, then run the command. A .blend saved in Edit mode is fine — the headless run enters Object mode in memory and never writes the file.

orisys-design texture sensors/finger/r01
# writes build/textures/finger/r01/_texture_job.json then runs Blender

Output is revision-scoped, so nothing has to be copied by hand:

build/textures/finger/r01/
├── _texture_job.json           # Blender input, regenerated by texture (gitignored)
├── patch_texture_produce.png   # raster preview/use (tracked)
├── patch_texture_produce.svg   # editable grouped vector artwork
├── patch_texture_produce.pdf   # vector production/print handoff (tracked)
├── patch_texture_compute.png
├── patch_texture_compute.svg
└── patch_texture_compute.pdf

produce is the hardware-production layout and compute the compute/runtime layout; they are the two UV maps authored on sensing_surface. Both outputs share one set of settings and one seed, and both read the same camera-space patch grid, so a given physical patch carries the same colour in either file — the images differ only in how the unwrap arranges the patches.

Square in which space? The patch grid is defined in the camera image, not in flat UV coordinates or physical surface distances. For square camera pixels, nx / ny must match the camera image aspect ratio (80×80 for a square frame, 80×45 for 16:9). Changing the PNG dimensions does not change the camera grid. Curvature and UV unwrapping can make these camera-space squares look rectangular, skewed, or curved in the flat texture; that alone is not a defect. Thin spikes outside the UV surface, however, are not expected projection distortion.

Generated artifacts are committed so a revision's textures can be synced to hardware production. They are stored as real files, not Git LFS pointers, so a plain git clone yields usable images and .blend files; .gitattributes only marks binaries as non-text to protect them from line-ending conversion. Only _texture_job.json is ignored, because it records absolute machine paths and is rewritten every run.

SVG imports into CorelDRAW as named groups (background, mesh, patches, and reference-cross) containing individual editable polygons. PDF contains the same filled vector paths on a correctly sized page, with no embedded bitmap. Seam-wrapped duplicate polygons are clipped to the page, so no stray objects sit outside the artwork.

Defaults come from the texture_patch block in design.yaml — mesh, camera, the uv_outputs map (UV map → artifact stem), raster dimensions, vector page size in millimetres, patch grid, colour separation, edge sampling, reference cross, and an optional seed. The current production page is configurable as:

vector:
  width_mm: 164
  height_mm: 160

CLI flags override the shared rendering values for every output:

orisys-design texture sensors/finger/r01 `
  --width 1024 --height 1024 --nx 16 --ny 12 --seed 7 --no-cross

Manual Blender invocation with the same job:

blender -b sensors/finger/r01/source/geometry.blend -P blender/scripts/random_patch_dd02c.py -- `
  --job D:\orisys_ws\orisys-sensor-design\build\textures\finger\r01\_texture_job.json

A single manual target is also possible (this cannot be combined with --job). --output names the PNG; same-stem SVG/PDF files are generated alongside it. The vector page defaults to 164×160 mm and can be overridden manually:

blender -b sensors/finger/r01/source/geometry.blend -P blender/scripts/random_patch_dd02c.py -- `
  --uv-map produce --output D:\tmp\produce.png --width 1024 --height 999 `
  --vector-width-mm 164 --vector-height-mm 160

Only the colored "patches" mode is reachable headlessly; the checker and dot helpers remain in random_patch_dd02c.py but are not exposed by the CLI. The default 3280×3200 grid matches the in-app script and is deliberately slow (pure-Python rasterization) — use smaller --width/--height/--nx/--ny for quick check runs, and the full default grid for the textures you actually ship.

Collection check:

blender -b sensors/finger/r01/source/geometry.blend -P blender/scripts/validate.py -- sensors/finger/r01

Profiles

  • compute — sensing_surface + cam_sensor relative to origin_sensor. Writes mesh.npz, camera_intrinsics.npz, world_to_cam.npy (here “world” means the sensor frame). Studio and the wheel build consume this tree via orisys-sdk → assets_dir.
  • studio — documents how to compile a .oryscene from the compute export plus the measured unroll map in SDK. Observation cameras in Studio are derived from mesh bounds; no Blender cam_viz is required.

Promotion (SDK / App)

After export:

  1. Tag this repo, e.g. finger-r01-assets-v1.0.0.
  2. Copy or attach the build/export/finger/r01/compute/ folder to the SDK asset path (or publish as a release artifact).
  3. Keep build/export/finger/r01/manifest.json beside the payload, and update optional design-lock.json in orisys-sdk with commit + bundle SHA256.
  4. Run orisys-app compile_scene.py using the locked compute export + SDK unroll map.

Example lock file: examples/design-lock.json.

Conventions

  • Units: metres at export boundaries. Author in mm if you like; set meters_per_source_unit: 0.001 in design.yaml.
  • Collections: FRAMES, EXPORT_COMPUTE, ASSEMBLY, DESIGN_SIM, EXCLUDE.
  • Objects: origin_sensor, sensing_surface, cam_sensor.
  • Matrix names must state direction; exported pose file remains world_to_cam.npy for SDK compatibility (sensor frame → camera).
  • Binary files: .blend, textures and exported *.npz are committed as ordinary files (no Git LFS). .gitattributes marks them -text only.

Git LFS on Gitea (NAS) — not currently used

Status: this repository does not use Git LFS. .gitattributes only marks binaries as non-text, and real .blend / .png / .pdf bytes are committed directly. This is deliberate: LFS pointers are unusable without a reachable LFS server, so a plain clone or git checkout would hand you 131-byte files instead of images. The notes below are kept as a migration guide for when the NAS server is ready.

Short answer: not difficult. Gitea has built-in LFS; NAS hosting is common. Plan for extra disk on the NAS volume that holds LFS objects, not just the git bare repos.

Server (one-time, ~5 minutes)

  1. Enable LFS in Gitea — edit app.ini (or Docker env):

    [server]
    LFS_START_SERVER = true
    
    [lfs]
    PATH = /data/git/lfs
    

    Docker Compose equivalent:

    environment:
      - GITEA__server__LFS_START_SERVER=true
    

    Point PATH at persistent NAS storage (same volume as /data is fine).

  2. Restart Gitea after changing config.

  3. Optional: set LFS_JWT_SECRET in Docker/env for stable LFS auth across restarts (generate once, keep in your NAS secrets).

  4. Reverse proxy: LFS uploads use HTTP(S) batch API. Ensure your NAS proxy allows large request bodies (nginx client_max_body_size, etc.). SSH git push still works for normal objects; LFS file transfer is usually HTTPS unless you enable pure SSH LFS.

  5. Backup: include the LFS directory in NAS backup — it is not inside git history.

Requirements: Git ≥ 2.1.2 on the server (Gitea’s embedded git is enough).

Clients (each developer)

git lfs install
git clone git@your-nas:team/orisys-sensor-design.git
cd orisys-sensor-design
git lfs pull

This repo’s .gitattributes does not track anything through LFS today. To turn LFS on, add the extensions back with a filter (the file contains a ready-made example line), run git add --renormalize . so real bytes become pointers, and commit .gitattributes together with the renormalized files.

Typical pitfalls

Issue Fix
Push succeeds but .blend is a tiny pointer file Run git lfs install before commit; re-add file
LFS upload 413 / timeout Increase proxy body size and Gitea [server] LFS_HTTP_TIMEOUT if needed
Wrong permissions on NAS volume Match container USER_UID / USER_GID to volume owner
Repo already has large blobs in history Do not rewrite history casually; use git lfs migrate only with team agreement

Pure SSH LFS (LFS_ALLOW_PURE_SSH) is optional and still has client quirks; HTTPS LFS behind your NAS reverse proxy is the path of least resistance.

Phase plan

Phase Goal
A (now) Repo + r01 + headless compute export
B Lock file in SDK/App + CI reproduces export hashes
C (later) Markers, camera metrics, simulation datasets
S
Description
No description provided
Readme 90 MiB
Languages
Python 99.9%