173 lines
6.5 KiB
Markdown
173 lines
6.5 KiB
Markdown
# pirouette
|
||
|
||
Parametric [CadQuery](https://cadquery.readthedocs.io) model of a 3" FPV race
|
||
quad frame. It is an **interlocking space frame**: the parts key into each
|
||
other with tenons and mortises, so the joints hold it square and there is no
|
||
jig to build and no bolted-together stack of spacers.
|
||
|
||
Everything is generated from source — change a number, rerun, get new cut
|
||
files. Nothing is drawn by hand in a GUI.
|
||
|
||
```
|
||
./build.sh
|
||
```
|
||
|
||
That is the whole workflow. It writes `build/`.
|
||
|
||
---
|
||
|
||
## Requirements
|
||
|
||
- **[uv](https://docs.astral.sh/uv/)** — that's it. `build.sh` creates the
|
||
virtualenv from `pyproject.toml` on the first run and reuses it afterwards.
|
||
|
||
```
|
||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||
```
|
||
|
||
- Python 3.11 or newer, which uv will fetch for you if the system one is older.
|
||
|
||
The first run downloads about 1.7 GB. That is not a mistake: CadQuery depends
|
||
on OpenCascade and pulls VTK and trame in with it, whether or not you ever open
|
||
a viewer. After that a full build takes roughly 50 seconds, most of it the
|
||
PNG renders — `./build.sh --no-png` cuts it to under 30.
|
||
|
||
`uv.lock` pins every version, so a build here is the build you get.
|
||
|
||
---
|
||
|
||
## What you get
|
||
|
||
| Directory | Files | What it's for |
|
||
|---|---|---|
|
||
| `build/dxf/` | 4 | flat patterns, one per carbon part — feed these to the router |
|
||
| `build/stl/` | 7 | meshes for the slicer (the printed parts) or for a quick look |
|
||
| `build/step/` | 7 | solid models, if you want to take a part into other CAD |
|
||
| `build/png/` | 4 | assembly renders: iso, top, front, and a grid of all three |
|
||
|
||
`build/` is generated and gitignored. Delete it freely.
|
||
|
||
Only four parts get a DXF. The other three are printed, so a flat pattern
|
||
would mean nothing for them.
|
||
|
||
**The DXF is the exact part outline, with no kerf compensation.** Offset it by
|
||
half your tool diameter in CAM, or every part comes out undersize by a whole
|
||
cutter width.
|
||
|
||
---
|
||
|
||
## Bill of materials
|
||
|
||
Straight from the last build — `build.sh` prints this table every time, so
|
||
trust the run over this copy of it.
|
||
|
||
| Part | Qty | Stock | Material | g each | g total |
|
||
|---|---|---|---|---|---|
|
||
| plate | 2 | 2 mm | carbon fibre | 3.86 | 7.72 |
|
||
| arm | 4 | 2 mm | carbon fibre | 0.75 | 3.02 |
|
||
| motor_base | 8 | 2 mm | carbon fibre | 0.87 | 6.97 |
|
||
| spar | 4 | 2 mm | carbon fibre | 1.19 | 4.76 |
|
||
| camera_mount | 1 | printed | TPU | 7.87 | 7.87 |
|
||
| standoff | 4 | Ø3.5 mm | aluminium | 0.39 | 1.56 |
|
||
| lollipop | 1 | printed | TPU | 1.15 | 1.15 |
|
||
| **frame** | **24** | | | | **33.05** |
|
||
|
||
Carbon at 1.55 g/cm³, TPU at 1.21, aluminium at 2.70.
|
||
|
||
**Not counted, and not modelled:** screws, motors, props, camera, VTX,
|
||
receiver, flight controller. The standoffs are the only hardware in the model.
|
||
|
||
### Hardware you supply
|
||
|
||
- 4 × M2 aluminium standoff, 3.5 mm across, **15 mm long** — that length is the
|
||
plate gap, so the standoffs are what sets it. Different standoffs mean a
|
||
different frame; see the levers below.
|
||
- M2 screws for those standoffs, and for the flight controller.
|
||
- The plates carry both a 20×20 and a 25.5×25.5 mounting pattern, drilled 2.2 mm
|
||
for M2.
|
||
|
||
### It is built around
|
||
|
||
- 3" props — **78 mm**, and neighbouring discs just touch on this true-X layout.
|
||
The renders draw them as rings so you can see that.
|
||
- A **Foxeer Predator 5 Nano** camera, which the camera mount is cut for.
|
||
- A **micro lollipop** antenna, held at 45° by `lollipop.py`, which clips over
|
||
the rear standoff pair.
|
||
|
||
---
|
||
|
||
## Making the parts
|
||
|
||
**Carbon** — the four DXFs cut from 2 mm plate. Every internal corner at a
|
||
joint already carries a T-bone relief, so a round cutter can actually make the
|
||
corner; don't "clean up" those little circles, the mating part needs that room.
|
||
|
||
**TPU** — `camera_mount.stl` and `lollipop.stl`. The STLs are exported at 0.01 mm
|
||
chordal deviation, far finer than an FDM printer resolves. Rerun the export at a
|
||
tighter tolerance if you are going to resin.
|
||
|
||
**Assembly** — dry fit before anything else. Horizontal parts (plates, motor
|
||
bases) carry the mortises, vertical parts (arms, spars) carry the tenons, and
|
||
every tenon should land *flush* with the far face it passes through. Proud jams
|
||
the joint; short leaves it unseated. The four spars half-lap their neighbours at
|
||
each corner and are handed — two notches up, two down, alternating round the
|
||
ring — so check the renders before you glue.
|
||
|
||
---
|
||
|
||
## Changing it
|
||
|
||
Shared dimensions live in `frame_params.py`, and every part imports them. The
|
||
usual things to reach for:
|
||
|
||
| You want | Change |
|
||
|---|---|
|
||
| a different prop size | the motor-to-motor gap |
|
||
| different carbon stock | the shared thickness |
|
||
| a shallower arm | the plate gap — costs stack room |
|
||
| looser or tighter joints | the shared fit allowance |
|
||
| a taller stack | the plate gap, and buy standoffs to match |
|
||
|
||
Rerun `./build.sh` and the cut files follow. Read `AGENT.md` first if you are
|
||
changing geometry rather than numbers — it records the invariants and the
|
||
CadQuery traps that have already cost time here.
|
||
|
||
---
|
||
|
||
## Working on a single part
|
||
|
||
Each part module builds and checks itself, so you can run one on its own
|
||
without the assembly:
|
||
|
||
```
|
||
uv run python arm.py # asserts it is one solid, then reports mass
|
||
uv run python frame.py # assembly, BOM, exports, renders
|
||
```
|
||
|
||
They are also CQ-editor scripts: open any of them and it shows the part.
|
||
`frame.py` shows the whole assembly.
|
||
|
||
---
|
||
|
||
## Licence
|
||
|
||
[Apache License 2.0](LICENSE). Use it, change it, build frames from it, sell
|
||
those frames. The conditions are light: keep the copyright notice, note in any
|
||
file you modify that you changed it, and pass on a copy of the licence.
|
||
|
||
Two things worth knowing about the fit between this licence and a physical part:
|
||
|
||
- **It covers the files, not the shape.** The Python, the comments and the
|
||
exported drawings are copyrighted work and the licence governs them.
|
||
The outline of a cut piece of carbon generally is not — functional shapes
|
||
aren't protected by copyright. Someone who buys a frame, measures it and
|
||
redraws it in their own CAD is outside this licence entirely. That is normal
|
||
for open hardware, and not something a different licence would fix.
|
||
- **It includes a patent grant.** Apache-2.0 §3 gives every user a patent
|
||
licence covering the contributions in here, and revokes it from anyone who
|
||
sues over the design. This is the main thing it adds over MIT.
|
||
|
||
No warranty of any kind — see §7. Worth taking literally on a frame: nothing
|
||
here has been flown, and the design is deliberately built close to its material
|
||
limits. Check the parts before you trust them at 100 km/h.
|