# Claude Workflow: Sliding-Drawer Instrument Cabinet (starter)

Starter recipe for a cabinet built from T-slot aluminum extrusion with **two or more tiers of sliding drawers**, each drawer a tapped carrier plate riding on a pair of 3rd-party drawer slides, carrying a 3rd-party instrument. Generic method and proven request shapes live in the user-level skill `~/.claude/skills/onshape-cad-workflow/`; this file holds the use-case rules, inputs, and step order. Fill the `[ ]` items per project; the Take 1 execution of this recipe is recorded in `cabinet.md` / `take1_results.md`.

## 0. Gate: the Onshape MCP must be connected

This lab notebook exists to test the Onshape MCP. **Do not start, and do not continue, a build unless the MCP tools are live** (`ToolSearch select:mcp__onshape__*` shows more than `authenticate`; `mcp__onshape__whoami` answers; `mcp__onshape__test_featurescript` returns a `libraryVersion`). If only `authenticate` is listed, run it, hand the user the URL, and **stop until the tools appear** — an expired OAuth link is not a reason to fall back to browser-REST feature studios. Disclose that MCP and REST calls consume the user's Onshape API allocation.

Channel rules once connected:

| Work | Channel |
|---|---|
| Every FeatureScript feature: author → `test_feature` → `create_feature_studio` / `put_featurescript` | **MCP** |
| Vendor geometry measurement, documents, versions, Variables, feature insertion, assembly instances, metadata, BOM read | Browser REST from the logged-in tab |
| Mates, mirror-plane edits, suppress/unsuppress, occurrence transforms | Browser REST — clone an existing feature's JSON (any mate in any of your documents works as a template), strip ids, POST; see the skill's `rest-recipes.md` → "Assembly features by REST" |
| Fix, groups, visual checks, stills | Browser UI keyboard-first (`k` hides connectors for stills; Fix has no key) |

## 1. Preconditions (design rules)

- 3rd-party models live in the designated 3rd Party Models folder, one document per vendor item, **Version cut after** grouping/mates/connectors/metadata and before any cross-document insert (versioned instances are read-only; changes need a new version + Reference manager "Update linked document" per instance).
- Custom parts and assemblies live in their own documents in the account root: one for the frame (its default Assembly becomes the cabinet), one for the carrier (its default Assembly becomes the tier).
- Metric throughout. Thread size follows the mating clearance hole (No. 8 / Ø5.6 → M4; Ø11 → M10). Tapped holes are native Onshape `hole()` features with the ISO Tapped lookup.
- Every critical dimension is a tree Variable; names, descriptions, and cut lengths are computed inside features.
- "Mate" means a native mate between mate connectors created **in the Part Studios by FeatureScript** with one frame convention (Z out of the mounting face, X along the slide length). Group is not a mate.
- **Fix the anchor before the first mate** (frame in the cabinet, plate in the tier) — with nothing fixed the solver moves the anchor onto the first mated instance and a later Fix freezes that pose (Take 3). Mates then go in by REST.
- Measure **signs**, not just spans: the slide's cabinet and drawer slot rows are on opposite sides of its centreline (z = −22.2 / +22.2); with a mirrored pair that 44.4 mm decides the carrier's row positions and width.
- Mate connectors need a real owner: pick the owner body by an explicit bounding-box test over `evaluateQuery(qAllModifiableSolidBodies())` — an owner query that resolves to nothing produces connectors that are invisible to the API and unmateable, with no error.
- Every REST-created mate sets `secondaryAxisAlignment = PLUS_X`; the UI default (`PLUS_Y`/`MINUS_Y`, "least motion") rotates parts 90° in plan.
- Frame profiles come from Onshape's public 80/20 45 Series library (pinned version); cut list is the native `cutlist.fs` feature.
- **Every tier has the same height unless specified otherwise.** One `#tierPitch` for all N tiers, and the top tier gets the same pitch as the rest (`#topClearance` = `#tierPitch`), so the cross-bar pairs sit at equal intervals: `#frameHeight` = 3·`#profileSize` + N·pitch (the top ring's underside is at H − p and the bottom tier's bar must clear the bottom ring; 1360 = 2p + 3·423 gave only two tiers in Take 7, 1405 gives three). Size the pitch from the tallest stack the cabinet must hold (slide 39.2 + `#plateThickness` + tallest instrument + clearance + one bar) and let `#frameHeight` follow, rather than giving one tier extra headroom and squeezing the others. A cabinet whose spec names a taller tier ("the printer tier is 420") is the "specified otherwise" case: record it in §2.1 and keep every other tier equal. *(Review feedback, 2026-08-29: Takes 4–6 used `#topClearance` 390 with `#tierPitch` 185, i.e. one tall tier over two shallow ones.)*
- **Every tier is enclosed by the frame.** The cabinet is a box that contains its shelves: N tiers → N pairs of front/back cross bars butted between the posts, all *below* the top ring. The top and bottom rings are structural only — no tier, carrier plate, or instrument sits on the exterior top of the frame. Headroom check: top-tier cross-bar top + slide height + `#plateThickness` + instrument height + clearance ≤ `#frameHeight` − `#profileSize` (underside of the top ring). Derive `#tierPitch` (or a `#topClearance` Variable) from that, never by letting the last tier land on the ring. *(Review feedback on Take 1, 2026-08-28.)*

## 2. Inputs

### 2.1 Frame
- Profile: `[ 80/20 45-4545 ]` (P45 import path pinned in the Chapter 1 frame code).
- `#frameWidth` `[ 600 ]` · `#frameHeight` `[ 900 ]` · `#profileSize` `[ 45 ]` · `#tierPitch` `[ ]` · number of tiers `[ 3 ]` — every tier has its own cross-bar pair inside the frame (§1 headroom rule); the rings carry nothing. Members = 8 ring rails + 4 posts + 2·N cross bars (18 for N = 3).
- `#frameDepth` is **derived**: cabinet-member end-slot span + `#profileSize` (§3.2). Do not set it by hand.
- `#slideRowLeftX` / `#slideRowRightX`: **cabinet-rail** slot-row x positions, symmetric `[ ±245 ]` = (W − 2p)/2 − 10 so the cabinet rails hug the posts; the mirror plane is x = 0. The drawer-rail rows sit 44.4 mm inboard (`[ ±200.6 ]`) and drive the carrier's lateral tapped rows and its width (`[ 430 ]`).
- `#topClearance` = `#tierPitch` unless a tier is specified taller (§1 equal-tier rule). For the fast-af printer (323 mm tall) every tier needs 39.2 + 6 + 323 + ~10 = 378 clear → pitch `[ 423 ]`, so three equal tiers make `#frameHeight` `[ 1405 ]` (3 × 45 + 3 × 423, bar tops at 91 / 514 / 937); a 900 mm frame holds only one such tier. Decide the frame height from the stack, not the other way round.

### 2.2 Drawer slides (3rd-party)
- Vendor / item: `[ McMaster-Carr 15765A28, hold-closed base-mount, 28" ]`. Source STEP: `[ part_3/3rd Party Models/15765A28.step ]`.
- Expect a **single multi-body part, modelled fully extended**, single-hand and chiral.
- Expect 5 rigid members, not 3: three full-length rails alternating with two roughly-half-length ball cages. Quantity: one pair per tier, the pair created by an **assemblyMirror** feature inside the vendor document itself (§3.1.8), so a tier inserts ONE slide-pair subassembly rather than two slide instances.

### 2.3 Instrument (3rd-party)
- `[ Table Vise V33450, document "Table Vise V33450" V2, MC hole A at (−95, 12.5, −8) Z up ]`; mounting pattern `[ 4× Ø11 on 190 × 185 ]` → `[ M10 ]` tapped.
- Tier that carries it: `[ top ]`.
- Next test run: the **"fast af" 3D printer** (`part_3/3rd Party Models/fast-af.stp`, 343 bodies, 24.9 MB) — file facts, import options (flatten + composite; manual upload, the tool caps at 10 MB), and the measurements that drive the Variables are in `fast_af_prep.md`.

### 2.4 Carrier plate
- AL 6061, `#plateThickness` `[ 6 ]`; `#plateWidth` = inner frame width − 2 × slide width − clearance `[ 380 ]`; `#plateDepth` ≥ drawer-member slot span + 2 × edge offset, ≤ slide closed length `[ 690 ]`.
- Lateral tapped rows `[ M4 ]` at the drawer member's slot positions (`#lateralHole0Y` + offsets), one row per lateral edge, on the underside.
- Instrument tapped pattern `[ M10, 190 × 185 ]` centred; connectors `MC slide L front`, `MC slide R front` (Z up, X = +Y) and `MC hole A` (Z up, X = +X, matching the instrument's connector).

## 3. Steps

### 3.1 Import the slide and make it a real slide

Pattern reference: `~/.claude/skills/onshape-cad-workflow/references/imported-mechanism-pattern.md`, reverse-engineered from Vincent's model `760cba9b78877d262f9f1038`. Stages: bodies → members → joints → range → pair.

0. **If the vendor document already exists and is processed** (Take 3: Vincent's `760cba9b…` had composites, Sliders, limits and the Mirror), do not re-import — add the mount connectors, metadata and a new Version to *that* document (steps 5, 10, 11) and skip the rest.
1. Upload the STEP through the folder page's hidden file input (copy the file into the session scratchpad first — the upload tool only accepts session-visible paths). Import to a single document; rename to `<vendor item>`.
2. **Orient the import for downstream use, before measuring.** Scan the bounding box and the mounting faces; the 15765A28 STEP arrives with X = length (travel), Y = height (mount faces at y = ∓19.6), Z = width. Target frame for a base-mount slide in this cabinet: **Z up** (cabinet mount flange facing −Z, so the slide sits on a cross bar with no axis flip), **X along travel**, Y right-handed, origin at the cabinet rail's front slot on the mount face. If the import differs, insert an "Orient" custom feature at the top of the tree (`opTransform` on `qEverything(EntityType.BODY)`; pattern in the skill's `featurescript-patterns.md`) — for this STEP a +90° rotation about X (height → Z) plus a translation of the front slot to the origin. Re-run the measurement afterwards; every number below (composites, slot rows, connector origins, limits) is written in the oriented frame. *(The reference model kept the vendor frame and let its connectors carry the Z = +Y correction; a fresh import should orient instead so the connector frames read plainly and the parent mates need no `primaryAxisAlignment` flips.)*
3. **Measure to find the members, not the bodies.** Per-body bounding boxes and lengths via REST `/featurescript`. For a telescoping slide expect three full-length rails alternating with two roughly-half-length ball cages — 15 bodies, 5 members. Also record the slot rows on the two slotted rails and the travel.
4. **Bodies → members: one composite part per rigid member** (`compositePart` feature × 5, closed, so the Part Studio reports exactly 5 composites and no loose solids). Members for 15765A28: cabinet rail 711 mm, cage 329, intermediate rail 704, cage 342, drawer rail 713.
5. **Stop connectors on the stop faces** (Vincent's pattern, `features/slide-stop-connectors.fs`): one connector at the centre of every planar ±X face that touches another member — cage end-stop bodies, the rail lips the cages hit, rail end faces, the cabinet's back stop body and front tab, the drawer's front tab and bracket — found by code as the nearest opposing +X/−X face pair per scheduled member pair; **Z = +X (travel), X = +Y for all of them, owner = the composite**; 14 for 15765A28, matching his hand-placed set. Never the composite box extremes (Take 5's first version did that and was rejected: ~5 mm over-travel per stage). Name them from the +Y sort order, never with `getProperty` (refused during regeneration; only the Feature Studio notices panel shows the message — Take 5). Then the **Slider-axis connectors** (step 5 below) and the **mount connectors**.
5b. **Connectors on the motion axes**, in the Part Studio, **Z along the travel direction** (the reference uses ±X, six connectors, one per member per interface, at repeatable y/z offsets on each rail face). Slider limits are `limitZ*`, so this convention is what makes the limits read as plain travel numbers. Add the mounting connectors for the parent here too (cabinet-rail slots, drawer-rail slots).
6. **Members → joints: one Slider per interface, in series** — cabinet → cage → intermediate → cage → drawer = 4 Sliders. Never mate drawer straight to cabinet. Add Parallel mates where the chain leaves residual yaw (3 in the reference). **Group is not a joint**: the earlier take's three rigid Groups + Fastened mates produced a slide that could not slide.
7. **Joints → range: end stops, not Slider ranges.** Leave every Slider's own limits OFF (the Slider only defines the axis) and add one **Parallel mate with a one-sided limit (Z min 0 or Z max 0)** per physical stop, between the two connectors of a stop face pair (−X face first, +X face second, Z min 0): cage A front ↔ cabinet lip (engaged), cage B front ↔ intermediate lip (engaged), intermediate lip ↔ cage A back (engaged), drawer lip ↔ cage B back (engaged), cabinet back stop ↔ intermediate back end (373.0), intermediate front end ↔ drawer front tab (359.8), cabinet front tab ↔ drawer front bracket (735.0 — the closed position), cage B front ↔ drawer front tab (361.2). Every limit is 0; the geometry supplies the travel; each mate reads as one fact about the hardware. Rationale, sign convention and the REST encoding of the open side: skill `imported-mechanism-pattern.md` §4 and `rest-recipes.md` — **open sides go out as `isNull: true`**, otherwise every stop reads "Mate overdefines the assembly" (Take 5). Compute each stop's current value from the definition to check: four read 0 at full extension, four read their travel; anything else means a wrong face. **Proof on camera: right-click the closed-position stop → Apply limit position → Min Z** — the chain collapses to the 660 mm closed length (Animate refuses every range while stops exist). Test by dragging the drawer rail in the UI — it must stop exactly where the ends meet. *(Superseded approach, kept for the record:)* enable limits on every Slider, authored relative to the coincident state. When a Slider is created by REST the solver puts its two connectors coincident (offset 0), so write the limits around 0: **closed = 0**, cage B↔drawer `−361 … 0`, cage A↔intermediate `−351 … 0` (extension is the negative direction along the drawer-side connector's Z; the two free stages sum to ≈ 713 mm full extension), the two cage-to-rail stages locked `0 … 0`. **Do not clone another assembly's numbers** (the reference's 9.35…370.55 / 5.77 / 10.53…0 were read at *its* solved positions): Take 4 did, every Slider then sat at offset 0 outside its range, the mates reported OK, and the drawer could not be dragged. Verify by computing each mate's current offset from the definition (`matedCS` origins transformed by the occurrence transforms, projected on the first connector's Z) and checking it lies inside the range. **When writing limits by REST set both fields of each quantity — `expression: '-361 mm'` and `value: -0.361` (metres)** — the solver reads `value`; an `expression`-only edit leaves the mate pinned at the old value (the actual cause of the stuck drawer in Take 4 and the first half of the test run). Then **prove it**: right-click the Slider → Animate… → Play; "Current value" must sweep the range and the drawer rail must move on screen.
8. **The pair is one `assemblyMirror` feature** — `seedInstances` = all five composites, `mirrorPlane` = the `MC mirror` connector from step 5 (Part Studio connector at cabinet slot row + half spacing, Z along the slide's width), so the pair spacing is a feature parameter; re-point an existing Mirror by REST if it was built on an implicit connector. Onshape creates the mirrored instances and an auto-generated `<name>-Mirrored` Part Studio holding a single `derivedMirror`, so the pair stays associative to the seed. Never insert a second same-hand slide: the part is chiral.
9. **Post-import metadata**, then Version — steps 10–11 below.
10. Post-import metadata: on the assembly element set name, part number = vendor item number, vendor, description, and **`Subassembly BOM behavior` = `"1"` (Show Assembly only)** — the import default `"0"` lists the vendor item plus every body in the parent BOM. On the bodies set part number and vendor (**material** stays blank: STEP carries none and the API wants a material object). All of it **before** the Version, which freezes it.
11. **Cut the Version.** The closing step of post-import processing: nothing may reference this document until it exists, and it freezes steps 1–10. A late fix means another Version plus a Reference-manager "Update linked document" on every instance in every parent.

### 3.2 Derive the frame depth and plate depth from the slide
- Cabinet-member end-slot span S (mm) → `#frameDepth` = S + `#profileSize` (front/back cross-bar centrelines on the slots; slot must land within ±5 mm of the T-slot centreline).
- Drawer-member slot positions → carrier lateral hole offsets; `#plateDepth` = last offset + 2 × edge offset.
- Record the measured numbers in the recipe before building anything.

### 3.3 Frame document
0. **Every take recreates the frame document from scratch** — new document, Variables, feature, cut list, assembly, all on camera. Never reuse a previous take's frame document (Take 4 reused the Take 3 frame and was rejected in review). Old frame documents go to Trash before the take with the carrier and slide documents.
1. Create the document (REST), rename the Part Studio "Cabinet Frame" and the Assembly "Instrument Cabinet"; add the Variables first.
2. **MCP:** "Cabinet Frame" feature = Chapter 1 frame code (mitered rings from a helper box's cap edges, butted posts from its vertical edges) + one sketch of two lines per tier (N sketches, all below the top ring) for the front/back cross bars + names/cut descriptions + `cutlist()` + slide connectors `MC slide L/R tier k` at (row x, front-bar centreline, tier top) Z up, X = +Y, owners by bounding-box test. `test_feature` before delivery; insert with `parameters: []` (the feature reads Variables).
3. Verify by REST eval: member count, envelope, cut descriptions, connector origins. Regenerate `cutlist.md` from the part descriptions.

### 3.4 Carrier document
1. Create, rename Part Studio "Carrier Plate" / Assembly "Slide Carrier Tier"; Variables first.
2. **MCP:** "Slide Carrier Plate" feature: plate + corner rounds + two native tapped rows + instrument pattern + three connectors + name/description/material. `test_feature`, deliver, insert bound to `#variables`; verify 1 solid, tapped face counts, connector frames.

### 3.5 Tier assembly (in the carrier document)
1. Insert the plate (same document, `partId`) and **one slide-pair subassembly** (vendor document's assembly at its Version) via REST; Fix the plate (UI) before mating.
2. One REST Fastened mate: plate `MC slide L front` ↔ the **seed** slide's `MC drawer front` (path `[pairInstance, drawerRailInstance]`), `secondaryAxisAlignment = PLUS_X`. The mirrored slide follows because the pair is one rigid subassembly; the plate's right-hand row lands on the mirrored drawer rail's slots when `#lateralRowX` = cabinet row − 44.4 (Take 3: −256, 24, 227 mm plate origin in the pair frame, both rows on their slots).
3. **There is no "Flexible" step in Onshape** — subassemblies are flexible by default; the row's padlock icon and the *Lock/follow position* menu are the opposite (rigid/locked), so leave them alone. Take 4's stuck drawer was the pinned Slider `value`, not rigidity. Then metadata (tier part number, plate part number, **vendor on the in-house parts too**, or the BOM shows blanks), Version.

### 3.6 Cabinet assembly (in the frame document)
1. Insert the frame as its **composite part** (the one `cutlist` created: `partId` of the part with `bodyType == "composite"`, `isWholePartStudio: false`) so the cabinet gets one frame instance and one BOM line — never `isWholePartStudio: true`, which inserts every rail separately. **Fix it now, in the UI, before any mate exists** (Take 3: mates created first made the solver move the frame onto tier 1, and the later Fix froze it lying on its side; recovery = Unfix → suppress the mates → `POST …/occurrencetransforms` with `isRelative: true` and the inverse of the current transform (an absolute identity transform is silently ignored) → Fix → unsuppress). Verify with `?includeNonSolids=true`, and allow ~5 s for the definition to catch up.
2. Insert N tier instances (carrier Version) and the instrument (its Version) via REST. Subassemblies are flexible by default, so the pull-out motion reaches the cabinet level without any setting; do not click the row's padlock (that locks the instance at its current position).
3. Mates via REST, cloned from a template `BTMMate` (any existing Fastened): per tier `slide-L cabinet front ↔ MC slide L tier k`; instrument `MC hole A ↔ carrier MC hole A` on its tier. Query shape: `{featureId: "<featureId>.<opSuffix>", path: [occurrence ids…]}`; custom-feature IDs already end in `_0` (e.g. `F5KPMtg7dyl9Huk_0.mc1`); read connector IDs from the assembly definition (`?includeMateConnectors=true&includeMateFeatures=true`) **at the Version being inserted** (`/d/{did}/v/{vid}/e/{asm}`) — a custom feature's ID changes between versions (the vise connector was `…XQ_0.mcA` at V1 and `…OJS_127.mc…` at V2; cloning the old ID gives a mate in `ERROR`). Connector IDs for your own custom features are `<featureId>.<opId>` (e.g. `FSQmGPywOBJOvM7_0.mcL`), paths run outer → inner through subassemblies (`[tier, pair, cabinetRail]`).
4. Verify: every mate `OK` in `featureStates`, then a **screenshot** — the definition endpoint reports subassembly occurrences at identity even when they are placed (Take 3), so it cannot confirm tier heights; the top-level part/vise transforms are trustworthy (vise z = top-tier bar top + 39.2 + 6 + connector offset).

### 3.7 Properties and BOM
- Frame members: part number `FR-<profile>-<kind>-<length>`, vendor "80/20 Inc."; plate / tier / cabinet part numbers; vendor items with vendor + vendor part number on the assembly and on every body; **materials on vendor bodies** (needs a material object, set before the vendor Version).
- Read `GET …/bom?indented=false&multiLevel=true` → `bomTable.items[]` (fields `name, partNumber, description, vendor, material, quantity`) and assert no blanks.

### 3.8 Test pipeline
- Slide pair rating vs tier load (plate + instrument) → SF ≥ 1.3.
- Lateral thread pull-out (plate thickness = engagement) with torque limit; instrument threads per Part 2B method.
- Document in `strength_check.md`.

## 4. Output items
- Frame Part Studio + native Cut list + `cutlist.md`; carrier part + tier assembly; slide document with real internal mates; cabinet assembly with every mate solved; BOM with no blank cells; `strength_check.md`; final stills; recording of the replay.

## 5. Known gotchas (from Takes 1, 3, 4 and 5)
- Take 7 (unrecorded, from scratch with the equal-tier rule): Variable features need the exact payload in `rest-recipes.md` (`mode`, `lengthValue`, name template) or the feature silently uses its defaults; N equal tiers need H = 3p + N·pitch; "Flexible" is the default and the padlock is *Lock*; read assembly instance ids by name *prefix* (the frame composite is "Composite part 1") — a wrong path answers 500 "internal error"; stamp Vendor on in-house parts before versioning or the BOM has blanks.
- Take 5 review fix: stop connectors go on the **stop faces**, found by code (`features/slide-stop-connectors.fs`), not on box extremes — four stops read exactly 0 at full extension, and a stop that "needs" a non-zero limit has a wrong connector. Every REST write of a limit mate (suppress/unsuppress/edit) must re-apply `isNull: true` on the open sides; the read-back JSON re-pins the mate and drags the parts to the limit. Read the assembly pose and each stop's value from the definition after every mate write; never infer them from Part Studio coordinates.
- Take 5 (recorded, slide pair only): a feature that errors in the document while the same code runs in the evaluator and in `test_feature` — open the **Feature Studio notices panel**; it named `getProperty` during regeneration after four blind code variants. REST-created Parallel stops must send open limit sides as `isNull: true` (the GET shape pins them → "Mate overdefines the assembly"). Animate is dead with stops present; **Apply limit position** is the proof. Mirror dialog: shift-click does not range-select instances (click rows one by one) and the plane connector is only pickable in the field's mate-connector mode. The Import dialog auto-creates a version named V1 — rename it before cutting yours. Version POST needs `documentId`.
- Take 4 (recorded): a file over 10 MB must be imported by REST from a local file server (`rest-recipes.md`), and that route ignores the composite flag — plan a `Rigid Composite` feature. `assemblyMirror` cannot be created by REST; do it in the UI (`shift+m`) and press **Update** in the strategy table. Slider limits cloned from another assembly left the pair stuck at offset 0 (§3.1.7). `box` is a reserved word in FeatureScript. The printer's screenshot looked Z-up; measurement showed it lying on its back — always measure before orienting.
- Take 3: the frame walked because it was mated before it was Fixed (§3.6); the vise mate errored because its connector ID was cloned from a V1 template (§3.6.3); the definition endpoint showed placed tiers at identity (§3.6.4). Full list in `take3_results.md`.
- Take 1 let the top ring double as tier 3, so the top carrier and the vise sat above the frame (vise origin z 953 on a 900 mm frame). Rejected in review: tiers must be enclosed (§1). Budget the top tier's full stack height under the ring before picking `#tierPitch`.
- The drawing's hole callouts can mislead — measure the model (Take 1: 635 mm read vs 660.4 mm measured).
- REST instance delete answers 404 on every endpoint tried; delete via the UI or avoid duplicate inserts.
- The tool output filter blocks IDs and `=`/`;` runs; chunk IDs, use full-width `＝`/`；` when returning code.
- `featureStates` is an array of `{key, value}`; the parts API's `withMateConnectors` did not list workspace connectors — use the assembly definition instead.
