# Experiment: both-sides WRAPPING servo holder (STS3215) — loop driver

**Cameron's brief (2026-06-23):** TOM's robot is good, but its servo holder mounts on **one side** of the servo (cantilevered L-bracket). Build a holder that **fully wraps around the servo and grips BOTH sides**, like Cameron's own robot — more robust. Take inspiration from TOM (its exactness), but improve on the one-sidedness. Keep iterating until it's *really good*. Cameron will also pass over it by hand.

**Loop:** cron repointed here. Deadline 2026-06-24T18:15 PT (epoch 1782350134) OR quality bar met → render a clean final candidate, ask Cameron to review in outbox, wind down. Each firing: ONE concrete iteration, render/verify on lab, log below, append 1-2 lines to outbox.md.

## Quality bar ("really good")
1. Captures the servo on **two opposing sides** (not cantilevered) — currently 4-wall sleeve + flanges on both ±Y mount faces. ✓ v1
2. Bolts to the servo's **real extracted holes** (`hole_markers.json`) on BOTH ±Y faces — TOM-grade exactness, no eyeballing. ✓ v1
3. **No collision** with: servo body (0.3–0.4 mm print clearance), the `yoke_exact` (horn+idler grip), and the next link. ✓ v3 (exact boolean check: both 0.00 mm³)
4. **Wire/connector access** — STS3215 cable must exit (cutout/window); don't fully entomb the servo. ✓ v4 (±Y ends open + framed ±Z windows)
5. **Printable** — sane wall thickness (~2.5–3 mm), avoid unsupported enclosed overhangs, note print orientation; fillets on stress corners. ~✓ v6 (all walls ≥3 mm by construction; M3 counterbores; print OUTPUT-AXIS-VERTICAL → sweep-relief = clean vertical bore, only the top flange is a minor overhang. Optional: corner fillets later.)
6. **Seats deterministically** — locating geometry so it only assembles one way; servo can't pull out axially or spin. ✓ v5 (snug 4-side clamp + 4 bolts pin all DOF)
7. Lightweight but rigid (ribs/pockets, not a solid block). ✓ v4 (framed windows, 15943→10009 mm³, still 1 solid)

## Servo facts (STEP frame, mm) — from hole_markers.json
- Body bbox X[-35.6,9.6] Y[-28.2,9.6] Z[-12.4,12.4]. Output axis **+Y through (-25.5,0)**; horn protrudes +Y at x=-25.5 (bolt circle R7), idler at -Y x=-25.5.
- **+Y mount face** y=6.4, holes (x,z): (-17.2,±10.2),(3.5,±10.2), r=1.75 (M3).
- **-Y mount face** y=-25.6, holes (x,z): (-17.2,±10.2),(7.2,±10.2), r=1.75 (M3).
- Mount holes cluster toward +X; horn/idler at -X (x=-25.5) → flanges live at x≥-20.5 and leave the horn/idler clear for the yoke.

## Build / render recipe (all on lab `ssh cameronsmith@100.74.71.38`)
- Part: `/data/cameron/repos/smith300_para_stuff/wrap_holder.py` → `.venv_cad/bin/python wrap_holder.py` → `wrap_holder.stl`.
- Overlay-render vs servo: `MUJOCO_GL=egl <mujoco-venv>/bin/python render_overlay.py st3215.stl wrap_holder.stl <out>.png` (holder translucent; 4 views).
- 3-body check (servo+holder+yoke) when needed: extend render_overlay or build a small URDF; reuse `yoke_exact.stl`.
- Viewer: `cad.omidlab.net/?dir=/data/cameron/repos/smith300_para_stuff&file=<urdf>`.

## Iteration log
- **v1 (2026-06-23, DONE):** `wrap_holder.py` = 4-wall sleeve (WALL 3, CLEAR 0.4) over body Y-span + flanges on both ±Y faces with the 4+4 real bolt holes; flanges start x=-20.5 (horn clear). Render `wrap_holder_v1.png`: wraps body on all 4 long sides, bolts both faces, horn exposed. **Issues to fix:** fully enclosed (no wire window), boxy/heavy (no lightening/fillets), yoke-collision not yet verified, no locating/anti-rotation feature beyond the bolts.

- **v2 (2026-06-23):** flanges → two outer-z strips (open center z∈(-9.5,9.5)) to clear horn/idler + lighten. Built `check_collision.py` (exact boolean-intersection volume — the rigorous fit gate, beats eyeballing translucent renders).
- **v3 (2026-06-23, DONE):** collision-resolved. Found (via overlap bbox) the yoke grips BOTH ±Y faces full-width across the entire -X half (to x~-15), so inboard (-17.2) holes are unusable — set BOTH flanges outboard-only (TOP_X0=BOT_X0=-2.0), bolting the 4 outboard holes (2 top + 2 bottom, both sides). Result: **holder∩yoke=0.00, holder∩servo=0.00** mm³. Servo fully captured = sleeve (X,Z,+both X-ends) + 4 ±Y bolts. Renders `wrap_holder_v3_3body.png`. Holder vol 15943 mm³.

- **v4 (2026-06-23, DONE):** framed lightening windows in both large ±Z side walls (border WIN_B=4.5). Mass 15943→**10009 mm³** (-37%), still **solids=1** (frame intact), collisions still 0.00. ±Y ends already open for cable → wire access OK. Renders `wrap_holder_v4.png` / `_v4_3body.png`. Looks like a proper cage now.

- **v5 (2026-06-23, DONE):** built `swept_check.py` (rotate yoke through joint range, exact overlap per angle) + `wrap_module.urdf` + `render_module.py`. **Caught a real blocker the static q=0 check missed:** rotating yoke hit the holder (550mm³ @ +50°) — the yoke spine orbits radius ~19.5 about the output axis and clips any holder material there. Added a **cylindrical sweep-relief** about the output axis (R_SWEEP=24). Result over the declared **±100° (±1.74 rad)** range: **swept overlap = 0.00**, static still 0.00. Renders `wrap_holder_v5.png`, `wrap_module_v5.png`. Holder vol 5162 mm³ (1 solid).
  - **KEY DESIGN FORK for Cameron:** clearing the yoke forces the holder to relieve the OUTPUT half → it's now a robust **+X end-clamp** (wraps the rear body on 4 sides + bolts BOTH ±Y faces, outboard holes), NOT a full-length wrap. Full-length wrap is geometrically incompatible with this yoke's swing radius. Options: (a) accept the rear-clamp (still two-sided/robust); (b) make a more COMPACT yoke (smaller spine orbit) so the holder can wrap more; (c) limit joint range further to wrap a bit more. Needs Cameron's call.

- **v6 (2026-06-23, DONE):** M3 socket-head **counterbores** on the 4 bolt holes (CB_D=6.4, depth 2.4) → heads flush, flange faces can mate. Print orientation chosen (output-axis vertical). Still 1 solid, collisions 0.00, vol 5037 mm³. Render `wrap_holder_v6.png`. **Option (a) now meets all 7 quality-bar items** — solid fallback candidate pending Cameron's fork call.

- **v7 / analysis (2026-06-23):** `opt_rsweep.py` swept R_SWEEP 18→24 over ±100°. Wrap is **nearly flat in R** (vol 6024→5037 mm³, only ~20% range) AND the holder **fragments (solids=3) below R=21**; min swept-clear ≈ R24. ⇒ **Tuning the relief reclaims ~nothing — option (a) is fundamentally a rear-clamp.** A genuinely fuller wrap REQUIRES a different yoke (option b), now proven. Option (c) "limit range" also won't help (wrap is R-bound, not range-bound). Kept R_SWEEP=24.

## Backlog (next iterations — reorder by judgement)
- **(b) compact-yoke experiment (toward "fully wraps"):** current yoke spine sits at x=-45 (orbit r≈19.5) because it bridges horn↔idler AROUND the body's -X end. Try a SIDE-spine yoke (bridge via a ±Z face at x≈-25.5, z>12.4 → orbit r≈13-15) so the holder can wrap further -X. Copy to `yoke_compact.py` (don't touch working `yoke_exact.py`); measure max wrap via opt_rsweep. Bounded: one concept, then report to Cameron.
- v5: fillets/chamfers on stress edges; verify min wall + pick print orientation; item 5 + 6 (locating/anti-rotation; wrap+bolts already mostly do this).
- v6: assemble module URDF (servo + wrap_holder + yoke) + drop into a 2-servo chain; render + viewer link for Cameron to review.
- vN: re-tune CLEAR/bolt-D after Cameron's hands-on / test-print feedback.
