@protoboardai/ext-frame-cad 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/NOTICE +13 -0
- package/checks/frames.ts +25 -0
- package/checks/hardware.ts +26 -0
- package/evals/regenerate-after-vtx-move/case.json +175 -0
- package/lib/alias.ts +57 -0
- package/lib/artifacts.ts +185 -0
- package/lib/hardware-cad.ts +127 -0
- package/lib/hardware.ts +100 -0
- package/lib/params.ts +255 -0
- package/lib/plate.ts +233 -0
- package/lib/run.ts +24 -0
- package/ops/cad.ts +78 -0
- package/ops/frame-cad.ts +46 -0
- package/ops/hardware-cad.ts +54 -0
- package/ops/package.ts +96 -0
- package/ops/rebind.ts +94 -0
- package/package.json +42 -4
- package/protoboard-extension.json +187 -0
- package/py/frame_cad.py +343 -0
- package/py/hardware_cad.py +111 -0
- package/skills/frame-cad/SKILL.md +279 -0
- package/skills/frame-cad/references/generator.md +110 -0
- package/skills/frame-cad/references/manufacturing.md +42 -0
- package/skills/frame-cad-verify/SKILL.md +117 -0
- package/skills/frame-cad-verify/scripts/dxf_report.py +124 -0
- package/README.md +0 -3
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""hardware-cad: nominal bodies for fastener modules (build123d).
|
|
2
|
+
|
|
3
|
+
python hardware_cad.py <name>.params.json <out_dir>
|
|
4
|
+
|
|
5
|
+
Moved from the UHD library's quadcopter generator (library/cad/py/hardware.py
|
|
6
|
+
with the writer from common.py, @deltarobotics/uhd) and made generic: the
|
|
7
|
+
params file (written by lib/hardware.ts from the module) gives the kind and
|
|
8
|
+
the sizes, and nothing about a particular vehicle is in here.
|
|
9
|
+
|
|
10
|
+
Kinds and shapes (nominal; no thread form):
|
|
11
|
+
screw socket head (head_diameter x head_height, hex socket 0.6 deep)
|
|
12
|
+
under z = 0, plain shank 0.95 d along +Z for `length`
|
|
13
|
+
nut hex, `outer` across flats, bore d, `length` high
|
|
14
|
+
standoff hex, `outer` across flats, bore 0.85 d, `length` long
|
|
15
|
+
spacer tube, `outer` diameter, bore d + 0.4, `length` long
|
|
16
|
+
A nut's, standoff's or spacer's `bore`, when the params give one (the maker
|
|
17
|
+
states it), replaces the nominal bore.
|
|
18
|
+
Each part's origin is where its fastenerStack position is measured: a
|
|
19
|
+
screw's is under its head, thread along +Z; the others sit on z = 0 and
|
|
20
|
+
extend along +Z.
|
|
21
|
+
|
|
22
|
+
Writes, into <out_dir>:
|
|
23
|
+
<name>.step body (label = kind)
|
|
24
|
+
<name>.glb the same for viewers (glTF: metres, Y-up)
|
|
25
|
+
<name>.manifest.json volume (mass = volume x the module's material
|
|
26
|
+
density), tool, version, source digest
|
|
27
|
+
and prints a JSON summary on the last stdout line. Outputs are deterministic
|
|
28
|
+
for fixed params and build123d version: the STEP timestamp is normalised.
|
|
29
|
+
"""
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import hashlib
|
|
33
|
+
import json
|
|
34
|
+
import math
|
|
35
|
+
import re
|
|
36
|
+
import sys
|
|
37
|
+
from pathlib import Path
|
|
38
|
+
|
|
39
|
+
import build123d as bd
|
|
40
|
+
from build123d import Align, BuildPart, BuildSketch, Circle, Compound, Cylinder, Locations, Mode, Pos, RegularPolygon, export_gltf, export_step, extrude
|
|
41
|
+
|
|
42
|
+
MIN = (Align.CENTER, Align.CENTER, Align.MIN)
|
|
43
|
+
GENERATOR = Path(__file__)
|
|
44
|
+
FIXED_TIME = "2000-01-01T00:00:00"
|
|
45
|
+
KINDS = ("screw", "nut", "standoff", "spacer")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def body(part: dict) -> bd.Part:
|
|
49
|
+
kind, d, length = part["kind"], part["diameter"], part["length"]
|
|
50
|
+
bore = part.get("bore")
|
|
51
|
+
with BuildPart() as p:
|
|
52
|
+
if kind == "screw":
|
|
53
|
+
with Locations(Pos(0, 0, -part["head_height"])):
|
|
54
|
+
Cylinder(part["head_diameter"] / 2, part["head_height"], align=MIN)
|
|
55
|
+
with BuildSketch():
|
|
56
|
+
RegularPolygon(d * 0.45, 6)
|
|
57
|
+
extrude(amount=part["head_height"] * 0.6, mode=Mode.SUBTRACT)
|
|
58
|
+
Cylinder(d / 2 * 0.95, length, align=MIN)
|
|
59
|
+
elif kind == "nut":
|
|
60
|
+
with BuildSketch():
|
|
61
|
+
RegularPolygon(part["outer"] / math.sqrt(3), 6)
|
|
62
|
+
Circle((bore or d) / 2, mode=Mode.SUBTRACT)
|
|
63
|
+
extrude(amount=length)
|
|
64
|
+
elif kind == "standoff":
|
|
65
|
+
with BuildSketch():
|
|
66
|
+
RegularPolygon(part["outer"] / math.sqrt(3), 6)
|
|
67
|
+
Circle((bore or d * 0.85) / 2, mode=Mode.SUBTRACT)
|
|
68
|
+
extrude(amount=length)
|
|
69
|
+
elif kind == "spacer":
|
|
70
|
+
Cylinder(part["outer"] / 2, length, align=MIN)
|
|
71
|
+
Cylinder((bore or d + 0.4) / 2, length, align=MIN, mode=Mode.SUBTRACT)
|
|
72
|
+
else:
|
|
73
|
+
raise SystemExit(f"unknown hardware kind {kind!r} (one of {', '.join(KINDS)})")
|
|
74
|
+
return p.part
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def build(params: dict, out: Path) -> dict:
|
|
78
|
+
name, part = params["name"], params["part"]
|
|
79
|
+
shape = body(part)
|
|
80
|
+
shape.label = part["kind"]
|
|
81
|
+
whole = Compound(label=name, children=[shape])
|
|
82
|
+
|
|
83
|
+
out.mkdir(parents=True, exist_ok=True)
|
|
84
|
+
step = out / f"{name}.step"
|
|
85
|
+
export_step(whole, str(step))
|
|
86
|
+
step.write_text(re.sub(r"FILE_NAME\('([^']*)','[^']*'", lambda m: f"FILE_NAME('{m.group(1)}','{FIXED_TIME}'", step.read_text(), count=1))
|
|
87
|
+
export_gltf(whole, str(out / f"{name}.glb"), binary=True, linear_deflection=0.05, angular_deflection=0.3)
|
|
88
|
+
|
|
89
|
+
digest = hashlib.sha256(GENERATOR.read_bytes() + json.dumps(params, sort_keys=True).encode()).hexdigest()
|
|
90
|
+
volume = round(shape.volume, 3)
|
|
91
|
+
manifest = {
|
|
92
|
+
"artifact": name,
|
|
93
|
+
"tool": "build123d",
|
|
94
|
+
"toolVersion": bd.__version__,
|
|
95
|
+
"generator": "@protoboardai/ext-frame-cad py/hardware_cad.py",
|
|
96
|
+
"params": f"{name}.params.json",
|
|
97
|
+
"sourceDigest": digest,
|
|
98
|
+
"units": "mm",
|
|
99
|
+
# solid volume, so mass = volume x the module's declared material density
|
|
100
|
+
"volume_mm3": volume,
|
|
101
|
+
"bodies": {part["kind"]: volume},
|
|
102
|
+
"features": {},
|
|
103
|
+
}
|
|
104
|
+
(out / f"{name}.manifest.json").write_text(json.dumps(manifest, indent=2) + "\n")
|
|
105
|
+
return {"name": name, "toolVersion": bd.__version__, "sourceDigest": digest, "volume_mm3": volume}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
if __name__ == "__main__":
|
|
109
|
+
params_path, out_dir = sys.argv[1], sys.argv[2]
|
|
110
|
+
result = build(json.loads(Path(params_path).read_text()), Path(out_dir))
|
|
111
|
+
print(json.dumps(result))
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: frame-cad
|
|
3
|
+
description: Generate a custom structure's CAD (plates, arms, raised pads, side clamps) from its UHD interfaces with ext-frame-cad, a provider of the cad capability (build123d) — bolt patterns and frames in, STEP/GLB/DXF plus a feature manifest out (protoboard run cad:generate / cad:export --module <id>, or the frame-cad alias) — keep geometry bindings honest, re-bind explicitly after a reviewed change, generate nominal fastener bodies (hardware-cad), and export for manufacturing (DXF/STEP for CNC-routed carbon). Use when a custom frame, plate or bracket needs geometry, when its interfaces change, or when `protoboard check` reports FRAME_CAD_* or UHD_GEOMETRY_BINDING findings; follow with frame-cad-verify.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Frame CAD from interfaces
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
module interfaces (bolt patterns + frames) + cad.config.ts constraints
|
|
10
|
+
│ protoboard run cad:generate --module <id>
|
|
11
|
+
▼
|
|
12
|
+
frame-cad/params.json ─► py/frame_cad.py (build123d) ─► frame-cad/frames.json (native design)
|
|
13
|
+
│ protoboard run cad:export --module <id>
|
|
14
|
+
▼
|
|
15
|
+
out/body.step / body.glb / body.manifest.json / body.dxf / interfaces/*.glb (standard outputs)
|
|
16
|
+
│
|
|
17
|
+
└── cad:check: model frames == generated frames, params and export current
|
|
18
|
+
geometry bindings: feature signatures == manifest
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
This extension is a provider of the `cad` capability
|
|
22
|
+
(`docs/specs/tool-integrations.md`): each plate module has a provider
|
|
23
|
+
directory, the folder of its `cad.config.ts`, with the native design in
|
|
24
|
+
`frame-cad/` and the standard outputs (the only files anything downstream
|
|
25
|
+
reads) in `out/`.
|
|
26
|
+
|
|
27
|
+
A custom structure is designed interface-first: it exists as requirements and
|
|
28
|
+
mating interfaces (each mirroring a selected part's pattern, cited) before it
|
|
29
|
+
has geometry. The generator derives the plate from those interfaces, so the
|
|
30
|
+
CAD cannot drift from the model silently: a change in either is a finding.
|
|
31
|
+
|
|
32
|
+
Worked example: `examples/quadcopter-5in/src/modules/frame/` — the frame
|
|
33
|
+
(stack, standoff and air-unit hole patterns, four raised motor pads on arms,
|
|
34
|
+
camera side clamp) — and `src/modules/top-plate/` (standoffs, GPS pattern,
|
|
35
|
+
strap slots, explicit outline): each a `.uhd.ts`, a `cad.config.ts`, the
|
|
36
|
+
native `frame-cad/` and the exported `out/`.
|
|
37
|
+
|
|
38
|
+
## Purpose
|
|
39
|
+
|
|
40
|
+
Give a plate-like structure (frame, top plate, bracket, side clamp) its
|
|
41
|
+
geometry from the interfaces it must hold, and keep the model and the CAD
|
|
42
|
+
honest about each other. Use it when a custom frame, plate or bracket needs
|
|
43
|
+
geometry, when its interfaces or design values change, and when `protoboard
|
|
44
|
+
check` reports `FRAME_CAD_*` or `UHD_GEOMETRY_BINDING`. Cases and covers
|
|
45
|
+
around a board are **enclosure-cad**; routed cables are **harness-cad**. In
|
|
46
|
+
**build-a-project** it is stage 6 (structures), after the system is composed;
|
|
47
|
+
**frame-cad-verify** follows.
|
|
48
|
+
|
|
49
|
+
## Inputs
|
|
50
|
+
|
|
51
|
+
| Input | Where | From |
|
|
52
|
+
| --- | --- | --- |
|
|
53
|
+
| The plate module: `plate_thickness_mm`, bolt-pattern interfaces with frames | `src/modules/<module>/<module>.uhd.ts` | **system-compose** |
|
|
54
|
+
| The mating parts' patterns (cited) | the parts' interfaces (`npx protoboard inspect <instance>`) | catalog or **uhd-part-author** |
|
|
55
|
+
| Provider config and design values | `src/modules/<module>/cad.config.ts` | this SOP, step 2 |
|
|
56
|
+
| The `cad` runtime | `npx protoboard doctor` shows `python-build123d` ✓ | `npx protoboard tools install cad` |
|
|
57
|
+
|
|
58
|
+
## Steps
|
|
59
|
+
|
|
60
|
+
### 1. Runtime
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npx protoboard doctor # python-build123d: ✓ build123d 0.13.0, or ✗ with the install hint
|
|
64
|
+
npx protoboard tools install cad # if missing: a project venv with the pinned build123d (.protoboard/tools/cad)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`PROTOBOARD_PYTHON=/path/to/python` overrides the project runtime (default
|
|
68
|
+
without either: `python3`).
|
|
69
|
+
|
|
70
|
+
If the tool is missing, `run cad:generate`, `cad:export` and `frame-cad` are
|
|
71
|
+
refused before anything runs (`cad:check` and `extract` need no runtime). Do
|
|
72
|
+
not hand-write CAD to work around it; install the runtime or report it.
|
|
73
|
+
|
|
74
|
+
### 2. Model the structure
|
|
75
|
+
|
|
76
|
+
A **plate module** is a local definition whose mechanical metadata states
|
|
77
|
+
`plate_thickness_mm`. Coordinates: module frame, mm, Z up, origin on the
|
|
78
|
+
plate's underside. Each bolt-pattern interface (`BoltPattern(...)`) needs a
|
|
79
|
+
frame (`withGeometry`):
|
|
80
|
+
|
|
81
|
+
| Interface frame | Becomes | Frame the generator writes back |
|
|
82
|
+
| --- | --- | --- |
|
|
83
|
+
| normal +Z, square/rectangle | through holes, on the top face | `origin z = thickness` |
|
|
84
|
+
| normal −Z | through holes, on the underside | `origin z = 0` |
|
|
85
|
+
| normal +Z, circle (or listed in the config's `pads` constraint) | a raised pad on a hub, joined to the core by an arm (motor mounts); the pad top is the mating face | `z = thickness + pad height` |
|
|
86
|
+
| horizontal normal, `hole_spacing_y` | two vertical side plates `hole_spacing_y` apart (the clamped width), holes `hole_spacing` apart vertically (camera clamp) | `z = thickness + clamp height` (mid-plane of the clamp) |
|
|
87
|
+
|
|
88
|
+
X/Y positions and axes come from the frames; hole positions follow UHD's
|
|
89
|
+
`boltPatternHoles` (a circle's first hole on the frame's xAxis). Design values
|
|
90
|
+
(outline, fillets, arm width, pad and clamp heights, clearances, slots) are
|
|
91
|
+
generator defaults overridden per module by the constraints in its
|
|
92
|
+
`cad.config.ts`: see [generator reference](references/generator.md). Put
|
|
93
|
+
every design value there with a comment; it is part of the design record, and
|
|
94
|
+
it is this provider's, so it stays out of the UHD module.
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
// src/modules/frame/cad.config.ts, next to frame.uhd.ts
|
|
98
|
+
import { defineCapabilityConfig } from "@protoboardai/core";
|
|
99
|
+
|
|
100
|
+
export default defineCapabilityConfig({
|
|
101
|
+
provider: "@protoboardai/ext-frame-cad",
|
|
102
|
+
process: "cut", // CNC-routed plate: DXF is the manufacturing export
|
|
103
|
+
constraints: { clamp_height_mm: 10, pad_height_mm: 0.5, arm_width_mm: 14 },
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The config's folder must declare one module (or name it with `module`).
|
|
108
|
+
`process` is `cut` or `machine`; `print` is refused (this provider writes no
|
|
109
|
+
STL or 3MF). A constraint key the provider does not know is a warning.
|
|
110
|
+
|
|
111
|
+
Declare the generated files on the module so checks and releases see them:
|
|
112
|
+
`withGeometry(def, geometry, plateCadArtifacts({ dir, name, interfaces }))`,
|
|
113
|
+
with `plateCadArtifacts` from `@protoboardai/ext-frame-cad/artifacts` and `dir`
|
|
114
|
+
the folder of the config (body, manifest, per-interface GLBs and DXF in
|
|
115
|
+
`out/`; params and frames in `frame-cad/`). `withGeometry`, `feature`, `own`
|
|
116
|
+
and `procedural` are UHD's pure helpers; the file layout is this extension's.
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
npx protoboard capabilities # the module is bound to @protoboardai/ext-frame-cad ✓
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### 3. Generate and export
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
npx protoboard run cad:generate --module <id> --format json # native design: frame-cad/params.json, frames.json
|
|
126
|
+
npx protoboard run cad:export --module <id> # standard outputs: out/ (emptied first)
|
|
127
|
+
npx protoboard run frame-cad --input module=<id>,<id> # alias: generate + export for the named plate modules (every one without --input)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Every file written is listed, with digests, in the operation record under
|
|
131
|
+
`operations/`. For fixed params and build123d version the output is
|
|
132
|
+
byte-identical, so a re-run that changes files means an input changed.
|
|
133
|
+
`cad:extract` returns the generated frames and the module's artifacts as
|
|
134
|
+
facts; it never edits the module (that is `frame-cad-rebind`, step 4).
|
|
135
|
+
|
|
136
|
+
`data.rebind` (`data.modules[].rebind` for the alias) is the worklist:
|
|
137
|
+
`signatures` (bound feature signatures that no longer match the generated
|
|
138
|
+
ones) and `frames` (model frame origins that differ from the generated ones).
|
|
139
|
+
Empty lists: nothing to do.
|
|
140
|
+
|
|
141
|
+
### 4. Bindings and re-binding
|
|
142
|
+
|
|
143
|
+
Each interface binds to its geometry three ways (UHD geometry artifacts):
|
|
144
|
+
`feature("<id>", signature)` — a named sub-shape in the STEP/GLB with an area /
|
|
145
|
+
centroid / normal signature (D1, primary); `own("<id>")` — the per-interface
|
|
146
|
+
GLB (D2); `procedural("bolt_pattern")` — derived from parameters (D3).
|
|
147
|
+
|
|
148
|
+
After a change, `protoboard check` reports, and never silently accepts
|
|
149
|
+
(`ext:@protoboardai/ext-frame-cad/cad:check`, one run per module): see
|
|
150
|
+
**Failure handling**.
|
|
151
|
+
|
|
152
|
+
Review the change first: does the new geometry still mate (stack-ups, prop
|
|
153
|
+
clearance, standoff lengths)? Then re-bind explicitly:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
npx protoboard run frame-cad-rebind --input module=<id> # out/ signatures and frame-cad/ frame origins into the module source
|
|
157
|
+
npx protoboard check # 0 errors again
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
It edits only the module's own `withGeometry(...)` block, lists every change
|
|
161
|
+
(`data.changes`), and leaves an operation record. Commit the source change
|
|
162
|
+
with the regenerated CAD and say why in the commit. Then revisit what depends
|
|
163
|
+
on the moved faces: fastener stacks (**harness-author** step 3), mate gaps,
|
|
164
|
+
documents.
|
|
165
|
+
|
|
166
|
+
Worked through in the extension's tests: raising the frame's
|
|
167
|
+
`plate_thickness_mm` from 5 to 6 → `FRAME_CAD_STALE` → `cad:generate` (16
|
|
168
|
+
changed bindings: hole areas 201.1 → 241.3 mm², pads and frames up 1 mm;
|
|
169
|
+
`FRAME_CAD_EXPORT_STALE` until exported) → `cad:export` → 8
|
|
170
|
+
`UHD_GEOMETRY_BINDING` + 8 `FRAME_CAD_FRAME_MISMATCH` errors → rebind → 0
|
|
171
|
+
errors. The stack's M3 × 30 then has 1 mm less grip; re-derive it.
|
|
172
|
+
|
|
173
|
+
### 5. Manufacturing export
|
|
174
|
+
|
|
175
|
+
`out/body.dxf` holds the profiles to cut, in mm: layer `plate` (outline with
|
|
176
|
+
every through hole: the bottom face of the plate) and one layer per side-clamp
|
|
177
|
+
plate (`clamp_<interface>`, drawn beside the plate). The manifest's
|
|
178
|
+
`manufacturing` block lists layers, quantities (clamp plates × 2) and
|
|
179
|
+
thicknesses, and what is not in the DXF (raised pads). The STEP is the 3D
|
|
180
|
+
reference for the shop and for inspection. Carbon routing specifics:
|
|
181
|
+
[manufacturing](references/manufacturing.md). The Manufacturing Package
|
|
182
|
+
copies these into the unit's `files/mechanical/<module>/` (**release**).
|
|
183
|
+
|
|
184
|
+
### 6. Mounting hardware (the `cad` capability)
|
|
185
|
+
|
|
186
|
+
Screws, nuts, standoffs and spacers that are local modules (bought parts
|
|
187
|
+
researched in the project, `parts/local/`; catalog parts bring their own
|
|
188
|
+
CAD) get nominal bodies from their sizes through the `cad` capability, so
|
|
189
|
+
`assemble()` can place them and their mass follows from volume × material.
|
|
190
|
+
Each has a `cad.config.ts` in a folder of its own,
|
|
191
|
+
`parts/local/<id>/cad.config.ts`:
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
export default defineCapabilityConfig({ provider: "@protoboardai/ext-frame-cad", module: "<id>" }); // bought: no process
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
npx protoboard run cad:generate --module <id> # frame-cad/params.json from the module's sizes
|
|
199
|
+
npx protoboard run cad:export --module <id> # out/body.step, body.glb, body.manifest.json (volume)
|
|
200
|
+
npx protoboard run hardware-cad [--input module=<id>,<id>] # alias: both, for every hardware module (or the named ones)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
The release traces each module's `out/` to its own `cad:export` record. The
|
|
204
|
+
aliases (`hardware-cad`, `frame-cad`, `harness-cad`) run `cad:generate` and
|
|
205
|
+
`cad:export` for each module and leave those per-module records (their own
|
|
206
|
+
record lists them in `data.steps`), so either way is traced.
|
|
207
|
+
|
|
208
|
+
A hardware module is tagged `fastener` and one of `screw`, `nut`, `standoff`,
|
|
209
|
+
`spacer`; an interface states `fastener_diameter` and `length`; mechanical
|
|
210
|
+
metadata states `head_diameter_mm` and `head_height_mm` (screw) or `outer_mm`
|
|
211
|
+
(across flats, or a tube's diameter), and `bore_mm` when the maker states a
|
|
212
|
+
nut's, standoff's or spacer's bore (otherwise the nominal bore below). A missing size fails the run; nothing
|
|
213
|
+
is defaulted. Shapes are nominal (plain shank at 0.95 d, no thread form):
|
|
214
|
+
see the [generator reference](references/generator.md). Declare the files with
|
|
215
|
+
`hardwareCadArtifacts({ dir: "parts/local/<id>", name: "<id>" })` from
|
|
216
|
+
`@protoboardai/ext-frame-cad/artifacts`. Export reports each body's volume and
|
|
217
|
+
the one it replaced. A bought part's body is a model of it, not a
|
|
218
|
+
fabrication file: the release lists it as bought (or included), never as a
|
|
219
|
+
fabrication order. Worked example: the quadcopter's `parts/local/*.uhd.ts`
|
|
220
|
+
(built with `parts/local/hardware.ts`), each with `parts/local/<id>/cad.config.ts`.
|
|
221
|
+
|
|
222
|
+
## Decision points
|
|
223
|
+
|
|
224
|
+
| Decision | Rule |
|
|
225
|
+
| --- | --- |
|
|
226
|
+
| This provider or another | Plates and frames whose features are bolt patterns: here. A case or cover around a board: **enclosure-cad**. |
|
|
227
|
+
| `process` | `cut` for routed sheet (DXF required); `machine` for a machined part (STEP). Never `print` with this provider. |
|
|
228
|
+
| A design value | A constraint in `cad.config.ts` with a comment saying why; never UHD metadata, never a hand edit of `frame-cad/`. |
|
|
229
|
+
| Re-bind | Only after reviewing what moved and what depends on it (stack-ups, mate gaps, clearances). |
|
|
230
|
+
| A structure change | Re-derive every fastener stack on the moved faces before moving on. |
|
|
231
|
+
|
|
232
|
+
## Outputs
|
|
233
|
+
|
|
234
|
+
| Output | Where |
|
|
235
|
+
| --- | --- |
|
|
236
|
+
| Native design (generated; never edit) | `<module dir>/frame-cad/params.json`, `frames.json` |
|
|
237
|
+
| Standard outputs (generated; never edit) | `<module dir>/out/body.step`, `body.glb`, `body.manifest.json`, `body.dxf`, `interfaces/*.glb` |
|
|
238
|
+
| Re-bound module source | `<module dir>/<module>.uhd.ts` (`withGeometry` block, by `frame-cad-rebind`) |
|
|
239
|
+
| Fastener bodies | next to the hardware definitions (the quad: `src/harnesses/cad/`) |
|
|
240
|
+
| Operation records | `operations/` |
|
|
241
|
+
|
|
242
|
+
## Verification
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
npx protoboard check --format json # 0 errors; the module's cad:check and protoboard/standard-outputs ran with no findings
|
|
246
|
+
npx protoboard run frame-cad --format json # every modules[].rebind list empty; git status shows no changed generated file
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Then **frame-cad-verify**, by a fresh agent, before anything is cut.
|
|
250
|
+
|
|
251
|
+
## Failure handling
|
|
252
|
+
|
|
253
|
+
| Code | Means | Do |
|
|
254
|
+
| --- | --- | --- |
|
|
255
|
+
| `FRAME_CAD_NOT_CONFIGURED` (info, the `frames` check) | A plate module has no `cad.config.ts` | Add one naming `@protoboardai/ext-frame-cad` in its folder |
|
|
256
|
+
| `FRAME_CAD_NOT_A_PLATE` | The config points at a module without `plate_thickness_mm` | Add the plate metadata, or use another `cad` provider |
|
|
257
|
+
| `FRAME_CAD_NOT_GENERATED` | No generated CAD yet | `run cad:generate --module <id>`, then `cad:export` |
|
|
258
|
+
| `FRAME_CAD_STALE` (warning) | Interfaces or design values changed since generation | `run cad:generate`, then `cad:export`; review the re-bind list |
|
|
259
|
+
| `FRAME_CAD_EXPORT_STALE` (warning) | `out/` was exported from other parameters than `frame-cad/params.json` | `run cad:export` |
|
|
260
|
+
| `FRAME_CAD_FRAME_MISMATCH` (error) | A model frame differs from the generated plate | Review; `run frame-cad-rebind` if the new plate is right; check dependent stack-ups |
|
|
261
|
+
| `FRAME_CAD_UNKNOWN_INTERFACE` | Generated frames name an interface the module no longer has | Regenerate |
|
|
262
|
+
| `FRAME_CAD_FRAME_MISSING` | Generated geometry for an interface without a model frame | Re-bind |
|
|
263
|
+
| `UHD_GEOMETRY_BINDING` (error) | "changed since binding; re-bind explicitly" | Review, then `run frame-cad-rebind` |
|
|
264
|
+
| `HARDWARE_CAD_PARAMS`, `HARDWARE_CAD_STALE`, `HARDWARE_CAD_EXPORT_STALE` | A fastener module lacks a size, changed since its body was made, or its `out/` is older than its params | Add the size from the listing; `run cad:generate` then `cad:export --module <id>` |
|
|
265
|
+
| `HARDWARE_CAD_NOT_CONFIGURED` (info) | A local fastener module has no body and no `cad.config.ts` | Add `parts/local/<id>/cad.config.ts` naming this provider |
|
|
266
|
+
| `PB_CAPABILITY_CONSTRAINT_UNKNOWN` | A constraint this provider does not know | Check the name in the [generator reference](references/generator.md) |
|
|
267
|
+
| `PB_OUTPUT_MISSING` (`body.dxf`) | `process: cut` without a DXF | Re-export; check the plate has profiles to cut |
|
|
268
|
+
| `run` refused: tool `python-build123d` missing | No `cad` runtime | `npx protoboard tools install cad` |
|
|
269
|
+
|
|
270
|
+
Every other code: **system-compose** [diagnostic codes](../system-compose/references/diagnostic-codes.md).
|
|
271
|
+
|
|
272
|
+
## Hard rules
|
|
273
|
+
|
|
274
|
+
1. Never edit generated files (`frame-cad/`, `out/`); change the module
|
|
275
|
+
(interfaces, frames) or its `cad.config.ts` constraints and regenerate.
|
|
276
|
+
2. Never re-bind to make a check pass without reviewing what moved.
|
|
277
|
+
3. Keep the frames in the model: they are how every mating part is placed.
|
|
278
|
+
4. A generated plate is geometry, not a strength, stiffness or
|
|
279
|
+
manufacturability claim; those need their own checks or tests.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Generator reference (`@protoboardai/ext-frame-cad`)
|
|
2
|
+
|
|
3
|
+
Files: `lib/params.ts` (model + constraints → params), `py/frame_cad.py`
|
|
4
|
+
(params → CAD), `lib/plate.ts` (the `cad` capability's generate, export,
|
|
5
|
+
extract and check), `ops/cad.ts` (their entries), `ops/frame-cad.ts` (the
|
|
6
|
+
alias), `ops/rebind.ts`, `checks/frames.ts`; for hardware,
|
|
7
|
+
`lib/hardware.ts`, `py/hardware_cad.py`, `ops/hardware-cad.ts`,
|
|
8
|
+
`checks/hardware.ts`; `lib/artifacts.ts` (the files both write, as UHD
|
|
9
|
+
artifacts: `cadArtifacts`, `plateCadArtifacts`, `hardwareCadArtifacts`,
|
|
10
|
+
exported as `@protoboardai/ext-frame-cad/artifacts`).
|
|
11
|
+
|
|
12
|
+
`frame_cad.py` is the merged frame generator. UHD's library held the first
|
|
13
|
+
one, `library/cad/py/frame.py` with `common.py`, which built the reference
|
|
14
|
+
quadcopter's plates from hard-coded positions; `frame_cad.py` places the same
|
|
15
|
+
features (core holes, raised pads on arms, side clamps, slots) from any
|
|
16
|
+
module's bolt patterns, and generates the quadcopter's frame and top plate
|
|
17
|
+
from its interfaces. For the quadcopter frame, all eight interface features
|
|
18
|
+
have the same signatures as frame.py's (compared manifest to manifest); the
|
|
19
|
+
body differs in outline details, so its volume, and the mass computed from
|
|
20
|
+
it, is 57 154 mm³ against frame.py's 59 863 mm³. UHD's `frame.py` and `hardware.py` were removed from UHD
|
|
21
|
+
; `hardware.py` became `hardware_cad.py` below.
|
|
22
|
+
|
|
23
|
+
## Which modules
|
|
24
|
+
|
|
25
|
+
Local definitions used by the project whose mechanical domain metadata has
|
|
26
|
+
`plate_thickness_mm`, each with a `cad.config.ts` naming this provider. The
|
|
27
|
+
capability operations take one module (`--module <id>`); the `frame-cad`
|
|
28
|
+
alias takes every plate module, or `--input module=a,b`, and refuses a
|
|
29
|
+
plate module with no config. A catalog definition is refused (derive a local
|
|
30
|
+
variant). Bolt-pattern interfaces without a frame, patterns missing
|
|
31
|
+
`hole_spacing` / `fastener_diameter`, and frames that are neither vertical
|
|
32
|
+
nor horizontal fail the run with the reason.
|
|
33
|
+
|
|
34
|
+
## Constraints in `cad.config.ts` (all optional, mm)
|
|
35
|
+
|
|
36
|
+
| Key | Default | Meaning |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| `outline_mm` | extent of the core holes and clamps + `margin_mm` | `{ x: [min, max], y: [min, max] }` of the core plate |
|
|
39
|
+
| `margin_mm` | 3 | outline margin around core holes and clamp plates |
|
|
40
|
+
| `corner_radius_mm` | 1.5 | fillet on vertical edges (skipped if the kernel rejects it) |
|
|
41
|
+
| `arm_width_mm` | 14 | arm from the core to each pad hub |
|
|
42
|
+
| `arm_slot_width_mm` | 5 | lightening slot down each arm, outside the core and ≥ 4 mm from hub and core; 0 = none |
|
|
43
|
+
| `hub_extra_mm` | 1 | hub disc radius beyond the pad |
|
|
44
|
+
| `pad_margin_mm` | 3 | pad radius beyond the pattern (circle: spacing/2 + 3) |
|
|
45
|
+
| `pad_height_mm` | 0.5 | raised pad height (its top is the mating face) |
|
|
46
|
+
| `pad_center_relief_mm` | 3.5 | centre hole radius in circle pads (shaft / circlip) |
|
|
47
|
+
| `pads` | circle patterns facing +Z | interface ids to build as raised pads |
|
|
48
|
+
| `hole_clearance_mm` | 0.1 | radial clearance (M3 → Ø 3.2) |
|
|
49
|
+
| `clamp_wall_mm` | 2 | side-plate thickness |
|
|
50
|
+
| `clamp_margin_mm` | 4 | side-plate width = spacing + margin; top = centre + spacing/2 + margin |
|
|
51
|
+
| `clamp_height_mm` | frame origin z − thickness | clamp centre above the plate top; state it explicitly so a thickness change moves the clamp with the plate |
|
|
52
|
+
| `slots_mm` | none | extra rectangular cut-outs `[{ center: [x, y], size: [dx, dy] }]` (strap slots) |
|
|
53
|
+
|
|
54
|
+
The quad frame sets `clamp_height_mm: 10, pad_height_mm: 0.5, arm_width_mm: 14`;
|
|
55
|
+
the top plate sets its outline (covers the battery), `corner_radius_mm: 4` and
|
|
56
|
+
two strap slots.
|
|
57
|
+
|
|
58
|
+
## Params file (`frame-cad/params.json`, schema `protoboard.frame-cad.params/v0`)
|
|
59
|
+
|
|
60
|
+
`plate` (thickness and the values above) and `interfaces[]`: `id`, `shape`,
|
|
61
|
+
`spacing`, `spacing_y`, `hole_count`, `fastener_d`, `placement`
|
|
62
|
+
(`holes_top | holes_bottom | pad | clamp`), `origin` (x, y), `normal`,
|
|
63
|
+
`x_axis`, `symmetry`, `height`, `holes` (world x, y). `name` is the module
|
|
64
|
+
id; it labels the STEP compound and seeds the DXF GUIDs. It is written by
|
|
65
|
+
`cad:generate` and read by `cad:check`: if recomputing it from the model and
|
|
66
|
+
constraints gives something different, the CAD is stale.
|
|
67
|
+
|
|
68
|
+
## Outputs
|
|
69
|
+
|
|
70
|
+
The generator writes `<name>.*` into a scratch folder; generate keeps the
|
|
71
|
+
frames, export copies the rest to the standard names.
|
|
72
|
+
|
|
73
|
+
| File | Content |
|
|
74
|
+
| --- | --- |
|
|
75
|
+
| `frame-cad/frames.json` | interface → `{ origin, normal, xAxis, symmetryDeg }` of the generated geometry (native design, with `params.json`) |
|
|
76
|
+
| `out/body.step` | compound: `plate`, `pads`, `clamps`, and one named sub-shape per interface feature (thin solid over its faces) |
|
|
77
|
+
| `out/body.glb` | the same, tessellated (glTF metres, Y-up) |
|
|
78
|
+
| `out/body.manifest.json` | `features: { <interface>: { area_mm2, centroid, faces, normal? } }`, tool, toolVersion, generator, params, `sourceDigest` = sha256(generator + params), `manufacturing`, and `exportedFrom` (the params file and its digest, for the export-stale check) |
|
|
79
|
+
| `out/body.dxf` | profiles: layer `plate`, layers `clamp_<interface>` |
|
|
80
|
+
| `out/interfaces/<id>.glb` | one file per interface feature (D2) |
|
|
81
|
+
|
|
82
|
+
Feature selection: hole patterns → the plate's cylindrical hole walls at the
|
|
83
|
+
pattern's positions and radius; pads → the pad's top face; clamps → the two
|
|
84
|
+
inner faces of the side plates. Signatures are rounded to 0.001.
|
|
85
|
+
|
|
86
|
+
Determinism: the STEP header time and the DXF timestamps and GUIDs are fixed
|
|
87
|
+
and the interpreter runs with `PYTHONHASHSEED=0`, so equal inputs give equal
|
|
88
|
+
bytes with the same build123d version.
|
|
89
|
+
|
|
90
|
+
## Hardware (`hardware-cad`)
|
|
91
|
+
|
|
92
|
+
Params file `<name>.params.json`, schema `protoboard.hardware-cad.params/v0`:
|
|
93
|
+
`part: { kind, diameter, length, head_diameter, head_height, outer }` (null
|
|
94
|
+
where the kind does not use it), read by `lib/hardware.ts` from the module
|
|
95
|
+
(see SKILL.md §5).
|
|
96
|
+
|
|
97
|
+
| Kind | Body (origin, +Z) |
|
|
98
|
+
| --- | --- |
|
|
99
|
+
| `screw` | head cylinder `head_diameter` × `head_height` below z = 0 with a hex socket (circumradius 0.45 d, 0.6 × head height deep); shank 0.95 d × `length` above |
|
|
100
|
+
| `nut` | hex prism, `outer` across flats, bore d, `length` high |
|
|
101
|
+
| `standoff` | hex prism, `outer` across flats, bore 0.85 d, `length` long |
|
|
102
|
+
| `spacer` | tube, `outer` diameter, bore d + 0.4, `length` long |
|
|
103
|
+
|
|
104
|
+
A stated `bore_mm` (the params' `bore`) replaces the nominal bore of a nut,
|
|
105
|
+
standoff or spacer.
|
|
106
|
+
|
|
107
|
+
Outputs: `<name>.step` (one body labelled with the kind), `<name>.glb`,
|
|
108
|
+
`<name>.manifest.json` (`volume_mm3`, `bodies`, empty `features`, tool,
|
|
109
|
+
generator, params, `sourceDigest` = sha256(generator + params)). One process
|
|
110
|
+
per module and a fixed STEP time, so equal inputs give equal bytes.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Manufacturing export: CNC-routed carbon plates
|
|
2
|
+
|
|
3
|
+
The generator produces geometry; the shop needs profiles, a material and
|
|
4
|
+
thickness, quantities, and tolerances. Send:
|
|
5
|
+
|
|
6
|
+
| File | Use |
|
|
7
|
+
| --- | --- |
|
|
8
|
+
| `out/body.dxf` layer `plate` | 2D toolpath source: outline + every through hole, mm, 1:1. Qty 1 per frame. |
|
|
9
|
+
| `out/body.dxf` layers `clamp_<interface>` | side-clamp plates, drawn beside the plate. Qty 2 each (manifest `manufacturing.quantities`). |
|
|
10
|
+
| `out/body.step` | 3D reference for the shop and for incoming inspection. |
|
|
11
|
+
| a drawing or note | material (e.g. 3K twill carbon, stated grade), thickness per layer (`manufacturing.thickness_mm`), hole tolerance, edge finish (chamfer/sand edges; carbon edges fray and cut wires), quantity, revision. |
|
|
12
|
+
|
|
13
|
+
Before sending:
|
|
14
|
+
|
|
15
|
+
1. **Thickness exists.** Carbon sheet comes in fixed thicknesses (e.g. 1.5, 2,
|
|
16
|
+
3, 4, 5, 6 mm depending on the supplier). Check the stated thickness is a
|
|
17
|
+
stock item from the supplier you will use and cite the listing; otherwise
|
|
18
|
+
change `plate_thickness_mm` and regenerate (then re-bind and re-derive the
|
|
19
|
+
fastener stacks).
|
|
20
|
+
2. **Minimum features.** Router bit diameter (commonly 1.5–3 mm for carbon)
|
|
21
|
+
sets the smallest inside radius and slot width: slots narrower than the bit
|
|
22
|
+
cannot be cut, and inside corners get the bit radius. The quad's 3 mm strap
|
|
23
|
+
slots and 5 mm arm slots need a bit of 3 mm or less. Holes below ~1.5× the
|
|
24
|
+
plate thickness in diameter are better drilled than routed; say so.
|
|
25
|
+
3. **Hole sizes.** Holes are fastener + 2 × `hole_clearance_mm` (M3 → Ø 3.2,
|
|
26
|
+
M2 → Ø 2.2). A shop's routing tolerance can eat that clearance; for press
|
|
27
|
+
or tight fits change `hole_clearance_mm` in the module's `cad.config.ts`,
|
|
28
|
+
not in the DXF.
|
|
29
|
+
4. **Edge distance.** A hole closer than about one diameter to an edge in
|
|
30
|
+
carbon risks breakout; check the stack and standoff holes against the
|
|
31
|
+
outline (**frame-cad-verify**).
|
|
32
|
+
5. **What the DXF leaves out.** The raised motor pads (0.5 mm) are a modelling
|
|
33
|
+
device giving each motor a distinct mating face; a flat carbon plate has
|
|
34
|
+
none. Either accept the flat face (bindings stay; the pad is 0.5 mm of
|
|
35
|
+
stack-up to remove from the motor screw arithmetic) or make them as shims.
|
|
36
|
+
Clamp plates need a joint to the plate (tabs and slots, or brackets) that
|
|
37
|
+
the generator does not draw yet: agree it with the shop and record it.
|
|
38
|
+
|
|
39
|
+
Record the order (supplier, quote, material, thickness, file digests from the
|
|
40
|
+
operation record) against the release (**release**), and inspect the parts
|
|
41
|
+
on arrival against the STEP and the hole patterns (the test plan's
|
|
42
|
+
`frame-inspection`).
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: frame-cad-verify
|
|
3
|
+
description: Verify a frame-cad structure before it is cut — bindings and frames clean, regeneration reproducible, every mating part placed on the generated faces, and the DXF manufacturable (hole edge distance, webs, router-bit limits, stock thickness) — and record the findings. Run by a fresh agent after frame-cad and before any manufacturing order or release; never edits the model.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Frame CAD verify
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
Prove that a generated plate is what the model says, reproducible from it,
|
|
11
|
+
assembles with its mating parts, and can be cut. Use it after **frame-cad**,
|
|
12
|
+
after any change to a plate's interfaces or design values, and before a
|
|
13
|
+
release puts its DXF in the Manufacturing Package. In **build-a-project** it
|
|
14
|
+
closes stage 6 for structures. It records findings; it never edits the model
|
|
15
|
+
or the CAD.
|
|
16
|
+
|
|
17
|
+
## Inputs
|
|
18
|
+
|
|
19
|
+
| Input | Where | From |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| Plate modules and their configs | `src/modules/<module>/<module>.uhd.ts`, `cad.config.ts` | **frame-cad** |
|
|
22
|
+
| Generated CAD | `<module dir>/frame-cad/`, `<module dir>/out/` | **frame-cad** |
|
|
23
|
+
| The router bit and stock | the shop's bit diameter and sheet thicknesses | the person or the chosen fabricator's published capability |
|
|
24
|
+
| Fastener stacks on the plates | `src/harnesses/` | **harness-author** |
|
|
25
|
+
| The `cad` runtime | `npx protoboard doctor` shows `python-build123d` ✓ | `npx protoboard tools install cad` |
|
|
26
|
+
|
|
27
|
+
## Steps
|
|
28
|
+
|
|
29
|
+
### 1. Automated
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx protoboard doctor # python-build123d available
|
|
33
|
+
npx protoboard check --format json # 0 errors; each plate's cad:check and standard-outputs ran with 0 findings; geometryBindings ran
|
|
34
|
+
npx protoboard run frame-cad --format json # (generate + export of every plate) every modules[].rebind list empty
|
|
35
|
+
git status --short src/ # no generated file changed: the committed CAD is what the model produces
|
|
36
|
+
"${PROTOBOARD_PYTHON:-.protoboard/tools/cad/bin/python}" node_modules/@protoboardai/ext-frame-cad/skills/frame-cad-verify/scripts/dxf_report.py <module dir>/out/body.dxf --bit <router bit mm>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
A file that changes on regeneration means the committed CAD was not produced
|
|
40
|
+
from the committed model: find out why before anything else.
|
|
41
|
+
|
|
42
|
+
`dxf_report.py` prints each layer's extent, holes by diameter, the smallest
|
|
43
|
+
hole-to-edge distance and web, and flags: a hole closer to an edge than its
|
|
44
|
+
diameter (breakout risk in carbon), webs under 1 mm, straight cut-outs
|
|
45
|
+
narrower than the bit, and holes smaller than the bit (to be drilled; noted,
|
|
46
|
+
not failed). Exit 1 on the hard rules.
|
|
47
|
+
|
|
48
|
+
### 2. Review
|
|
49
|
+
|
|
50
|
+
1. **Interfaces.** Each plate interface still mirrors its mating part's
|
|
51
|
+
interface (spacing, fastener, pattern shape) and cites it.
|
|
52
|
+
2. **Placement.** `protoboard check` placed every mating part through the
|
|
53
|
+
generated frames (no `UHD_ASSEMBLY`); look at the assembly in the viewer
|
|
54
|
+
(`npx protoboard dev --open`, stop it with `Ctrl+C`) or the documents if
|
|
55
|
+
available: motors on pads, props clear (brief: ≥ 5 mm), stack centred,
|
|
56
|
+
camera between its plates.
|
|
57
|
+
3. **Stack-ups.** Every fastener stack on this structure uses the current
|
|
58
|
+
plate thickness (**harness-verify**).
|
|
59
|
+
4. **Manufacturing.** Thickness is a stock item of the chosen supplier;
|
|
60
|
+
minimum features fit the bit; what the DXF leaves out (raised pads, clamp
|
|
61
|
+
joints) has a decision (frame-cad `references/manufacturing.md`).
|
|
62
|
+
|
|
63
|
+
### 3. Calibrate on the reference quadcopter
|
|
64
|
+
|
|
65
|
+
Running the report on `examples/quadcopter-5in` (3 mm bit) finds, in the
|
|
66
|
+
geometry inherited from the UHD generator:
|
|
67
|
+
|
|
68
|
+
- the 16 motor-pad holes sit 2.39 mm from the hub edge (hub radius 12 mm,
|
|
69
|
+
holes on an 8 mm radius, Ø 3.2): under one diameter;
|
|
70
|
+
- the top plate's four standoff holes are 2.4 mm from its long edges
|
|
71
|
+
(y ± 24 in a 56 mm plate);
|
|
72
|
+
- each camera side plate's lower hole is 0.9 mm from the plate's bottom edge
|
|
73
|
+
(hole 2 mm above the frame's top face);
|
|
74
|
+
- the M2 holes (Ø 2.2) are below a 3 mm bit: drill them.
|
|
75
|
+
|
|
76
|
+
None of these moves an interface; each is a design decision (`hub_extra_mm`,
|
|
77
|
+
the top plate's `outline_mm`, the clamp height or margin) for the design
|
|
78
|
+
owner, with a regenerate + verify after.
|
|
79
|
+
|
|
80
|
+
### 4. Write the findings
|
|
81
|
+
|
|
82
|
+
`docs/reviews/frame-cad-verify-<YYYY-MM-DD>.md`: the automated results
|
|
83
|
+
(commands, exit codes, report JSON) and findings
|
|
84
|
+
`{ id, severity, module, layer/feature, finding, evidence, fix }`.
|
|
85
|
+
|
|
86
|
+
## Decision points
|
|
87
|
+
|
|
88
|
+
| Decision | Rule |
|
|
89
|
+
| --- | --- |
|
|
90
|
+
| Severity | **blocker** for anything that cannot be made or will not assemble; **major** for a margin under the rule (hole under one diameter from an edge); **minor** otherwise. |
|
|
91
|
+
| A hole smaller than the bit | Not a failure: noted to be drilled. |
|
|
92
|
+
| A finding that needs a design change | The fix names the constraint to change; the design owner decides and regenerates. |
|
|
93
|
+
|
|
94
|
+
## Outputs
|
|
95
|
+
|
|
96
|
+
| Output | Where |
|
|
97
|
+
| --- | --- |
|
|
98
|
+
| The verification report | `docs/reviews/frame-cad-verify-<YYYY-MM-DD>.md` |
|
|
99
|
+
|
|
100
|
+
## Verification
|
|
101
|
+
|
|
102
|
+
The report lists every plate module, the exit code of each step-1 command,
|
|
103
|
+
and the `dxf_report.py` JSON per DXF; every finding cites a layer or feature
|
|
104
|
+
and its evidence. `git status --short src/` was clean after regeneration.
|
|
105
|
+
|
|
106
|
+
## Failure handling
|
|
107
|
+
|
|
108
|
+
| Code or symptom | Means | Do |
|
|
109
|
+
| --- | --- | --- |
|
|
110
|
+
| A generated file changed on regeneration | The committed CAD is not the model's output | Stop; find the changed input (config, interfaces, tool version) before reviewing |
|
|
111
|
+
| `FRAME_CAD_STALE`, `FRAME_CAD_EXPORT_STALE` | The CAD is behind the model | Hand back to **frame-cad** to regenerate; do not review stale CAD |
|
|
112
|
+
| `FRAME_CAD_FRAME_MISMATCH`, `UHD_GEOMETRY_BINDING` | Model and CAD disagree | Finding (blocker) until the author reviews and re-binds |
|
|
113
|
+
| `UHD_ASSEMBLY` | A mating part does not seat | Finding (blocker) |
|
|
114
|
+
| `dxf_report.py` exit 1 | A hard manufacturing rule failed | Finding per flagged item, with the constraint to change |
|
|
115
|
+
| `doctor` shows `python-build123d` ✗ | No runtime to regenerate or read the DXF | `npx protoboard tools install cad` |
|
|
116
|
+
|
|
117
|
+
Every other code: **system-compose** [diagnostic codes](../system-compose/references/diagnostic-codes.md).
|