---
name: project_blender_connector_interface
description: "Blender<->procedural-connector pipeline (WORKING N-servo, 2026-07-01): Cameron poses servos in Blender (GLB parent-chain), agent regenerates connectors + base + URDF. smith300_para_stuff/gen_two_servo.py + apply_glb_layout.py."
metadata: 
  node_type: memory
  type: project
  originSessionId: f6b5d2b8-94fd-4193-ba95-68aa32076e95
---

Division of labor: **Cameron owns the LAYOUT** (poses each servo 6-DOF in Blender), **agent owns the FILL**
(connectors, base union, URDF/GLB). Ordered CHAIN, servos numbered. Everything expressed in **servo_0's frame**
(= base = identity). Exchange = **GLB** at **GLB_SCALE=10** (blow up for Blender; `read_layout` divides it back
out). Files in `smith300_para_stuff/`: `gen_two_servo.py` (LAYOUT->URDF+GLB+connectors), `apply_glb_layout.py`
(reads Cameron's GLB -> rebuilds everything), `inspect_hier.py`/`check_hier.py`/`diag_base.py` (diagnostics).

## STANDING CONVENTIONS (Cameron confirmed 2026-07-01 — "always do that")
- **ALWAYS export the GLB as a PARENT-CHAIN HIERARCHY**: `world -> servo_0 -> servo_1 -> ... -> servo_N`, each
  servo's transform RELATIVE to its parent (`inv(LAYOUT[i-1]) @ LAYOUT[i]`); connector_i parented under servo_i,
  base pieces under servo_0. In Blender, moving/rotating any servo then carries all DOWNSTREAM servos (kinematic
  posing). trimesh preserves the node tree on GLB export (verified via `check_hier.py`); round-trip is unaffected
  (`read_layout` resolves world poses through the chain). `gen_glb(path, src_glb=)` does this.
- **Connector = PERPENDICULAR BLOCK, ROUNDED by default** (Cameron 2026-07-02 "always do the rounding"): take
  the HOLDER's yoke-facing face (box face whose outward normal points most at the yoke — dynamic per pose) and
  extrude it to the yoke, reaching the FAR (bottom) extent of the yoke arm +4mm. `connector_mesh(i)` uses a
  ROUNDED-rectangle cross-section (`CONNECTOR_ROUND=7`mm corner radius, shapely; 0 = square legacy via
  `_loft_faces`). Watertight. Web viewer: `two_servo.urdf`.
- **ALIGNED variant** (`gen_aligned.py` -> `two_servo_aligned.urdf`, SEPARATE files so it can be discarded):
  Cameron poses servos roughly in Blender so some connectors go slightly diagonal. snap_layout() rounds each
  orientation to the nearest 90deg and zeroes position-offset components < 15mm between consecutive servos
  (keeps the big intentional offsets), making those connectors orthogonal. Don't touch the default arm files.
- **Base pieces**: Cameron adds `base_cube*` boxes in Blender (table mount, fiducial-board holder, connector
  bar...). Agent unions ALL `base_cube*` nodes (each via its exact `bounding_box_oriented` — raw GLB box meshes
  aren't manifold) into servo_0's holder -> `base_holder.stl` (one printable solid; check `bodies==1`). Synced GLB
  passes the base pieces through as separate EDITABLE nodes (so they round-trip); only servo_0 gets a base.
- **Z-up display fix**: trimesh loads GLB in glTF **Y-up**, but the cad viewer AND MuJoCo are **Z-up** (viewer
  default up `[0,0,1]`). So the URDF gets a `world` root link + fixed joint with **rpy `1.5708 0 0`** (+90 X,
  Y-up->Z-up) via `VIEW_RPY`, making gravity match Blender. Verify orientation with a MuJoCo render (also Z-up).

## N-servo ordering (Cameron's hacky Blender duplication)
He copies servos as `servo_1.001, .002, ...` (frames `world.00N`); the actual poses live on the `servo_1.00N`
MESH nodes. `read_layout` sorts servo_* by `(base_idx, sub_idx)` with **bare `servo_N` sorting LAST** (his bare
`servo_1` was actually the 6th/end servo). gen_glb re-exports them cleanly renamed `servo_0..servo_N` in order.

## Serial-chain URDF
servo_0 -> output_0(revolute +Y) -> yoke_0 -> connect_1(fixed) -> servo_1 -> ... servo_i CARRIED BY yoke_{i-1}
(`connect_i = inv(OUT) @ inv(LAYOUT[i-1]) @ LAYOUT[i]`, OUT=translate(-0.0255)). `holder_approx_drilled.stl` =
carved holder; `yoke_exact.stl`; `st3215.stl`. Yoke visual gets +0.0255 X offset.

**Delivery:** Mac mount on lab = `/home/cameronsmith/mnt/mac` (exposes Mac `~/Projects/robotics_testing/` only).
Cameron drops/reads GLB at `mnt/mac/stl_transfers/two_servo.glb`. His Blender export is ~8MB (bakes full meshes)
so the pull needs a long `cp` timeout (~120-180s). Mount goes STALE on Mac sleep — check backing only, never
`ls` the path (D-state hang); see [[feedback_fuse_wedge_recovery]]. Viewer: `cad.omidlab.net/?dir=...&file=...`.
