# Modular smith300 rebuild — state & plan

## ✅ CORRECTED APPROACH DONE (2026-06-24) — smith300_faithful.urdf
Built `param_smith_arm.py` -> `smith300_faithful.urdf` the RIGHT way (no hack):
- Every joint rotates about OUR servo's real output (+Y) -> every servo physically plausible.
- Reproduced smith300's joint-axis RELATIONSHIPS by CONJUGATING smith300's joint rpy into our +Y
  convention: `R_our = C^-1 @ R_smith @ C`, C=Rx(90deg) (our +Y -> smith300 +Z). `compute_bends.py`
  emits the BENDS + VERIFIES consecutive joint-axis angles match smith300 exactly (90,0,0,90,90,0 deg).
- Clean MODULAR connected chain (yoke-reach), N=7 joints + gripper + fidex boards. Verified:
  sweep q=-1.2..+1.2 all joints -> 0 self-collision (`smith_faithful_sweep.png`). Render
  `smith_faithful_v1.png` (base flat on table -> perpendicular shoulder lift rises vertical ->
  parallel links -> perpendicular wrist). PINGED Cameron to verify shape/plausibility.
- NEXT (after his OK): (a) tune ROOT_RPY / first-bend so the lift direction best traces smith300;
  (b) optional creative per-joint connector geometry between modules; (c) regen fidex boards/markers
  for the 7-link config (fidex_config_faithful.json emitted); (d) fold into build_all.sh.

## ⭐ (superseded) earlier RESUME HERE — KEY LEARNING + NEW APPROACH
**What we learned:** our servo `st3215.stl` and smith300's `sts3215_03a_v1.stl` are DIFFERENT CAD
models (ours 19151 verts/42537mm3, smith 9526 verts/36215mm3, bbox 37.8 vs 39.6 mid-dim). So NO exact
rigid transform aligns them → all the mesh registration (servo_align.py, cand0/1/2 facings) is a dead
end. Decision: use OUR model only (it's tied to the correct STEP + extracted holes); ignore smith300's
servo mesh.
**REJECTED HACK (don't repeat):** using smith300's joint-origin rpy directly as the link frames + a fixed
ALIGN made the joints MOVE right (kept smith300 axes) but left the servo bodies in physically implausible
orientations — the servo's real output axis no longer matched the joint definition. Cameron: that's
hacking the axes; it's wrong.
**CORRECT APPROACH (from scratch, Cameron 2026-06-24):** build a clean modular chain from OUR parts where
**each joint axis IS our servo's own output axis (+Y)** — physically correct, plausible servo mounting.
Then make the chain's joint-axis RELATIONSHIPS (joint i+1 axis relative to joint i axis) match smith300's,
using smith300 only as a GUIDE for how the DOF should vary (perpendicular pitch/yaw/etc). I.e.: extract
smith300's consecutive joint-axis relative rotations (world joint i axis vs i+1 axis, from smith300.urdf
cumulative FK) and reproduce them via the CONNECTOR orientation between our modules — so our arm has
smith300-like varied DOF but every servo is mounted sensibly (output = its joint). This is the param_arm.py
clean-modular structure (joint about our output) BUT with per-joint connector rotations derived from
smith300's joint-axis relationships (not uniform like the old param_arm BENDS, and not smith300's raw rpy).
**TO-DOs:** (a) compute smith300's per-joint axis directions (FK of smith300 chain) + the relative rotation
between consecutive joint axes. (b) build modular chain (our servo+holder+yoke per module, joint about our
+Y output) with each connector oriented to realize that relative rotation -> smith300-like DOF, plausible
servos. (c) verify servo orientations are physical + joints move right. (d) connectors bridge the modules.
(e) fold back fidex + finalize.  Drop the old _place/_CANDS/SMITH_SERVO/servo_align (mesh-registration dead end).
**Reference:** smith300.urdf (joint chain + axes). Clean-modular full robot (param_arm.py, fidex, etc.)
is DONE + parked; this smith-pose track is a separate, more-faithful layout.


## ✅ COMPLETE (2026-06-24) — continuous loop 0b89a28b STOPPED (best-judgment wind-down, ~73min pre-deadline)
Full goal achieved + wired end-to-end. Regenerate everything: `bash smith300_para_stuff/build_all.sh`.
Deliverable summary in outbox.md top. Resume = re-create a cron loop. Below = full iteration history.


**Goal (Cameron 2026-06-24):** reproduce smith300's rough servo positions/orientations,
rebuilt from the LOCKED base parts (`wrap_holder` + `yoke_exact`) + new inter-servo
connector geometry. VERY MODULAR — whole arm = `f(holder, yoke, connector, chain)`.
Iterate until it looks good. THEN add ArUco/fidex exoskeleton holders per link.

## Base parts (locked — see memory project_locked_base_parts)
`smith300_para_stuff/{wrap_holder,yoke_exact}.stl` + servo `st3215.stl`. Clearance via
`voxel_trim.py` (uniform ~0.3mm). Our servo output axis = +Y through (-25.5,0)mm.

## smith300 chain (servo-to-servo, xyz m / rpy rad, joints rotate local +Z)
j1 (0.0388,0,0.0624)/(π,0,π) · j2 (-0.0612,-0.0204,-0.0549)/(0,-π/2,-π/2) ·
j3 (-0.1227,-0.0558,-0.002)/(0,0,π/2) · j4 (-0.143,0,0)/(0,0,0) ·
j5 (-0.065,0.0214,0.0224)/(π/2,0,0) · j6 (-0.0168,-0.0055,0.0892)/(0,π/2,-π) ·
j7 (-0.0455,-0.038,0)/(0,0,0).  Source: `smith300_para_stuff/smith300.urdf` (8 links/7 joints).

## Build/render
`param_arm.py` → `smith300_modular.urdf`; `render_arm.py <urdf> <out.png> [q]` (auto-framed,
≤480px for MuJoCo offscreen). Viewer: `cad.omidlab.net/?dir=/data/cameron/repos/smith300_para_stuff&file=smith300_modular.urdf`.

## Iteration log
- **v1 (2026-06-24):** param generator places servo+holder at each joint via a single
  ALIGN (our +Y output → joint +Z, shift x=-25.5→origin). Loads nq=7. Render `arm_v1.png`:
  roughly chain-shaped but modules **float disconnected + mis-oriented**. No yoke/connector yet.

- **v2 (2026-06-24, DONE):** rewrote `param_arm.py` as a CONNECTED modular chain — each
  module's yoke carries the next servo at reach SERVO_OFF=-0.1047m (f(yoke link_len)); joints
  at OUT_X=-0.0255 (first) / SERVO_OFF+OUT_X (rest), axis +Y. N=6, nq=6. Render `arm_v2.png`:
  modules now CONNECT into a clean chain (servo-yoke-servo). Straight (BENDS all 0). Modularity proven.

- **v4-v5 (2026-06-24):** tuned per-joint BENDS. v4 planar-Z bends zigzagged flat; v5 switched
  to pitch-axis bends → arm RISES vertically from base then curves over to wrist (`arm_v5.png`).
  Reads as a real arm roughly tracing smith300. Good shape milestone. (Ref render `smith300_ref.png`.)
- **v6 (2026-06-24):** added FIDEX layer — one ArUco marker plate (30mm) per link on the holder's
  +Z face (`_aruco`, param FIDEX/ARUCO_SIZE/ARUCO_DZ). `arm_v6_fidex.png`: arm + per-link fiducials.

- **v7 (2026-06-24):** fidex markers → proper HOLDERS (standoff + dark frame + plate); added a
  simple parametric gripper (palm + 2 fingers) fixed at the tip. `arm_v7.png`. Reads as complete arm.
- **v8 (2026-06-24):** made fidex FAITHFUL to medium_single_redo (read vault fidex_notes): base =
  3x3 board @ 50mm (id 0), shoulders = 2x2 @ 15mm (ids 20,24,28,32,36,40), DICT_4X4_250. Generator
  now EMITS `fidex_config.json` = per-link `aruco_offset_pos_mm`/`rot` (the LinkConfig CAD↔vision
  coupling). `arm_v8.png`. So arm is now fidex-ready (parametric boards + emitted poses).

- **v9 (2026-06-24):** added a base plate (table mount) under L0; confirmed the arm loads in the
  cad viewer (catalog HTTP 200). `arm_v9.png`. Full package: base plate + base 3x3 board + 6 modules
  w/ 2x2 boards + gripper + `fidex_config.json` (7 boards). Viewer:
  `cad.omidlab.net/?dir=/data/cameron/repos/smith300_para_stuff&file=smith300_modular.urdf`.

- **v10 (2026-06-24):** compactness — regenerated the connector yoke at 45mm (`yoke_arm.stl`, vs
  locked yoke_exact 70mm) and wired SERVO_OFF=-(34.7+L)/1000. Arm is tighter/more arm-like, traces
  smith300 better (`arm_v10.png`). Connector length now a clean param (ARM_YOKE_LEN).

- **v11 (2026-06-24):** real fidex marker HOLDERS — new parametric part `fidex_holder.py` (pad with
  RECESSED board pocket so the printed marker seats exactly = the CAD↔vision coupling). Generated
  `fidex_holder_sm.stl`(15mm)/`_bg.stl`(50mm); param_arm mounts them per link with the ArUco board in
  the pocket. Locked wrap_holder untouched (separate bracket). `arm_v11.png`. Arm = complete fidex-ready robot.

- **v11 verify (2026-06-24):** `render_sweep.py` swept all joints q=-1.2..+1.2 → **0 self-collision
  contacts at every pose** (CLEAN). Montage `arm_sweep.png` shows it curl/rest/extend, all valid.
  Arm is mechanically sound: complete, parametric, fidex-ready, collision-clean, articulating.

- **v12 (2026-06-24):** generated the REAL ArUco marker boards (`gen_markers.py`, installed
  opencv-contrib-python-headless in .venv_cad) from fidex_config: `fidex_markers/L0.png` (3x3 ids0-8,
  50mm) + L1-L6 (2x2, ids20-43, 15mm), DICT_4X4_250, ~12px/mm. So fidex is COMPLETE both halves:
  CAD pockets at known poses ↔ matching printable ArUco images ↔ fidex_config.json. `arm_hero.png`.

- **v13 (2026-06-24):** base-on-table orientation — added a `world` root link + fixed joint with
  `ROOT_RPY=(π,0,0)` so the base plate sits flat and the arm rises/curves to the gripper (deployable
  desktop-arm look). Clean hero `arm_hero3.png`. (90deg first try laid it on its side; 180 is right.)

- **v14 (2026-06-24):** one-command regenerate — `build_all.sh` rebuilds the whole package end-to-end
  (holder voxel → yoke 45mm → fidex holders → arm URDF+config → ArUco boards → print sheet → hero),
  verified exit 0. `gen_yoke_arm.py` added. Full reproducibility from source of truth.

- **v15 (2026-06-24):** tried textured markers on the render (`param_arm.py tex` → `_tex.urdf`,
  FIDEX_TEX mode). MuJoCo accepts the URDF <texture> material but does NOT map the ArUco image onto
  the box face (boards render dark) — URDF-texture limitation; cad viewer doesn't serve PNGs either.
  Markers stay represented via `fidex_print_sheet.png` + `fidex_markers/*.png`. Main URDF unaffected.

- **v16 (2026-06-24):** marker bracket now BOLT-MOUNTABLE — added 2 M2 holes (d2.2) in the wall
  flanking the pocket in `fidex_holder.py`; regenerated 15/50mm + arm. `arm_hero4.png`.
  **Full goal achieved** — remaining items are diminishing-returns polish (pose-tune to smith300,
  in-part pocket option). Consider reviewing/redirecting.

- **v17 (2026-06-24):** `fidex_config.json` now emits a LinkConfig-compatible row per link
  (mujoco_name, robot_mesh_path, exo_mesh_path, aruco_offset_pos[mm]/rot[rad], aruco_board_name,
  board_length[m], grid, ids, dict, marker_image) → directly feeds Cameron's fidex pipeline. Fixed
  gen_markers/gen_print_sheet to the renamed `board_length`. Pipeline consistent; verified all regen.

- **v18 (2026-06-24):** gripper is now ACTUATED — 2 prismatic finger joints (q=0 open ±12mm →
  q=0.012 closed), replacing the fixed block. nq now 8 (6 arm + 2 fingers). `arm_hero5.png`. Loads clean.

- **v18 verify (2026-06-24):** `render_gripper.py` confirms gripper actuates (nq=8, fingers move
  open→closed), `gripper_openclose.png`. Finger geometry functional/rough (subtle travel) — left as-is.

- **v19 (2026-06-24):** CAD↔fidex BRIDGE — `smith300_exo_config.py` builds a full fidex
  `ExoskeletonConfig` (7 LinkConfigs + GridBoards, DICT_4X4_250) from `fidex_config.json`, mirroring
  exo_configs/so100_adhesive.py. Tested: instantiates clean (7 links/7 boards). Drop into
  FiducialExoskeletons/exo_configs/ + provide an MJCF of the arm to run the estimator. Did NOT modify
  Cameron's repo. **This is the end-to-end capstone: parametric CAD -> working fidex pipeline config.**

- **Step 1e (2026-06-24):** mesh registration (ours st3215 vs smith300 sts3215_03a_v1) is geometrically
  AMBIGUOUS on the facing (ICP top-3 within 0.04mm). Cameron: just use smith300's OWN servo mesh at its
  own poses. Done — `param_smith_arm.py` now renders `assets/sts3215_03a_v1.stl` (scale 1) at SERVO_VIS,
  no flip/registration → servos EXACTLY smith300's. `smith_servos_only.png`. NEXT: attach our locked
  holder + connector to each servo. The holder is keyed to OUR servo frame, so placing it on smith300's
  servo still needs the servo-frame transform — but now isolated + visually verifiable (does it wrap?),
  and the output shaft is the unambiguous feature to pin the facing.
- **Step 1d (2026-06-24):** Cameron: base servo right, others off (position). Root cause: applied only
  the ROTATION of the mesh-frame alignment, not the TRANSLATION. smith300 mesh centred at origin, ours
  offset (-13,-9.3,0)mm → that offset rotated by each servo's pose = the per-servo drift. `servo_align.py`
  now also outputs **t=[-0.013,0,0.0097]m** (bbox-centre align); `_place` applies full rigid R+t
  (xyz += R_smith@t per servo). Math: base now lands EXACTLY (was ~16mm off, looked close). All 7
  servos should match smith300 poses now. PINGED Cameron to verify.
- **Step 1c (2026-06-24):** single-axis flips all wrong (Cameron). `servo_align.py` registers our
  st3215.stl vs smith300's sts3215_03a_v1.stl over the 24 cube rotations → ours->smith300 rpy(XYZ)=
  **[-pi/2, 0, pi]** (Y<->Z swap + 180 re-face; bboxes confirm Y/Z swapped). Locked into `_FLIP` in
  param_smith_arm. `smith300_poses.urdf` regenerated. CAVEAT: orientation change moves bodies (mesh
  origins differ) → positions may need a centroid-translation fix (also computable from the analysis).
- **Step 1b (2026-06-24):** Cameron: servos face BACKWARD (connectors point away). Module is right
  relative to itself, whole thing 180deg off. Added `_flip_rpy` (scipy, composes a local 180 flip with
  smith300's pose; CLI axis x|y|z). Generated `smith300_poses_{none,x,y,z}.urdf` for Cameron to pick the
  axis in the viewer (multi-view renders too scattered to judge). NOTE: flip is about the mesh origin so
  it may shift position slightly — may need a center-of-servo flip / xyz compensation once axis is chosen.
- **NEW DIRECTION (Cameron 2026-06-24):** the clean-modular arm used identical servo orientations →
  not enough DOF. Use smith300's REAL per-servo orientations + varied link lengths. Step 1 (DONE):
  `param_smith_arm.py` → `smith300_poses.urdf` places our servo+holder at smith300's actual joint
  chain + per-link servo visual origins (orientations + link lengths now varied), default yoke
  included but NOT validly connecting yet. Render `smith_poses_v1.png`, viewer-loadable. PINGED Cameron
  to verify servo placement. NEXT (after his OK): make the connectors bridge the differently-oriented
  links (creative per-joint connector direction), keeping the same holder + yoke. CAVEAT: our st3215.stl
  may be offset vs smith300's sts3215_03a_v1.stl (different mesh frame) — may need a per-servo align.

## Plan / backlog (iterate)
- v3 (NEXT): set per-joint BENDS (rpy in param_arm) to bend the connected chain so it roughly
  traces smith300's shape/reach (base up through gripper). Render + compare to smith300.urdf shape.
- v4: tune alignment/orientations until it reads as the smith300 arm; render + viewer link.
- v5: parametrize cleanly as f(holder, yoke, connector, chain); confirm modular.
- v6: add ArUco/fidex exoskeleton holders per link (see vault fidex_notes).

**DECISION (Cameron 2026-06-24): CLEAN MODULAR CHAIN** — build cleanly-connected modules
(holder+yoke+connector) and bend the chain to ROUGHLY trace smith300's shape/reach;
prioritize modularity (everything `f(holder,yoke,connector,chain)`). Do NOT chase a
pixel-exact overlay. Iteration mode = autonomous loop.

## "Looks good" bar
- Modules physically CONNECT (yoke output → connector → next holder; no floating gaps).
- ~7 joints, overall shape/reach roughly traces smith300 (base up through gripper).
- Clean parametric generator: change holder/yoke/connector/chain params → arm regenerates.
- Renders cleanly at rest + through joint motion (no self-collision at rest).
- Then (separate phase) ArUco/fidex holders per link.
