Add flight controller
Signed-off-by: Arnaud Morin <arnaud.gfpv@mailops.fr>
This commit is contained in:
2
AGENT.md
2
AGENT.md
@@ -115,6 +115,8 @@ from the stack — don't hand-patch them.
|
||||
.venv/bin/python3 spar.py
|
||||
.venv/bin/python3 camera_mount.py # printed accessory, not a frame body
|
||||
.venv/bin/python3 standoff.py # turned hardware, in the assembly
|
||||
.venv/bin/python3 lollipop.py # printed antenna holder
|
||||
.venv/bin/python3 fc.py # bought electronics, an envelope only
|
||||
.venv/bin/python3 frame.py # assembly, BOM, exports, renders
|
||||
```
|
||||
|
||||
|
||||
12
README.md
12
README.md
@@ -76,13 +76,17 @@ trust the run over this copy of it.
|
||||
| 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** |
|
||||
| lollipop | 1 | printed | TPU | 2.12 | 2.12 |
|
||||
| fc | 1 | bought | electronics | 12.09 | 12.09 |
|
||||
| **frame** | **25** | | | | **46.11** |
|
||||
|
||||
Carbon at 1.55 g/cm³, TPU at 1.21, aluminium at 2.70.
|
||||
Carbon at 1.55 g/cm³, TPU at 1.21, aluminium at 2.70. The `fc` is not a part
|
||||
you make — it is the envelope of a flight controller and 4-in-1 ESC stack,
|
||||
modelled as a solid block of FR4 at 1.85 g/cm³ so the all-up figure is honest.
|
||||
Swap in your own stack's real mass if you know it.
|
||||
|
||||
**Not counted, and not modelled:** screws, motors, props, camera, VTX,
|
||||
receiver, flight controller. The standoffs are the only hardware in the model.
|
||||
receiver. The standoffs and the FC stack are the hardware the model carries.
|
||||
|
||||
### Hardware you supply
|
||||
|
||||
|
||||
2
build.sh
2
build.sh
@@ -27,7 +27,7 @@ run() { uv run --no-sync python "$@"; }
|
||||
|
||||
# Each part asserts it came out as one solid at import time, so a part that
|
||||
# broke stops the build here rather than quietly exporting a bad STL.
|
||||
for part in plate arm motor_base spar camera_mount standoff lollipop; do
|
||||
for part in plate arm motor_base spar camera_mount standoff lollipop fc; do
|
||||
run "$part.py"
|
||||
done
|
||||
|
||||
|
||||
83
fc.py
Normal file
83
fc.py
Normal file
@@ -0,0 +1,83 @@
|
||||
# @author Arnaud Morin <arnaud.gfpv@mailops.fr>
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
import cadquery as cq
|
||||
|
||||
# A flight controller is not a material, it is a stack: two boards, their
|
||||
# spacers, and everything soldered to them. This models only the envelope, as
|
||||
# one solid, so the density is FR4's -- bare PCB -- and the dense parts (FETs,
|
||||
# inductors, connectors) stand in for the air between the boards. It lands the
|
||||
# block at about 12 g, which is what a real 25.5 mm FC plus 4-in-1 ESC weighs.
|
||||
PCB_DENSITY = 1.85e-3 # g/mm3, FR4
|
||||
|
||||
|
||||
def fc():
|
||||
|
||||
box = (cq.Workplane("XY")
|
||||
.box(33, 33, 8, centered=(True, True, True))
|
||||
.edges('Z')
|
||||
.fillet(5)
|
||||
)
|
||||
|
||||
# Remove extra for standoff
|
||||
cut = (cq.Workplane("XY")
|
||||
.pushPoints([
|
||||
(25.5/2, 25.5/2),
|
||||
(25.5/2, -25.5/2),
|
||||
(-25.5/2, 25.5/2),
|
||||
(-25.5/2, -25.5/2),
|
||||
])
|
||||
.circle(7)
|
||||
.extrude(5)
|
||||
.translate((0,0,1.6/2))
|
||||
)
|
||||
box = box.cut(cut).cut(cut.mirror("XY"))
|
||||
|
||||
# Standoff
|
||||
sta = (cq.Workplane("XY")
|
||||
.pushPoints([
|
||||
(25.5/2, 25.5/2),
|
||||
(25.5/2, -25.5/2),
|
||||
(-25.5/2, 25.5/2),
|
||||
(-25.5/2, -25.5/2),
|
||||
])
|
||||
.circle(2.5)
|
||||
.extrude(5, both=True)
|
||||
.translate((0,0,-2))
|
||||
)
|
||||
box = box.union(sta)
|
||||
|
||||
# holes in standoff
|
||||
cut = (cq.Workplane("XY")
|
||||
.pushPoints([
|
||||
(25.5/2, 25.5/2),
|
||||
(25.5/2, -25.5/2),
|
||||
(-25.5/2, 25.5/2),
|
||||
(-25.5/2, -25.5/2),
|
||||
])
|
||||
.circle(1.2)
|
||||
.extrude(10, both=True)
|
||||
)
|
||||
box = box.cut(cut)
|
||||
|
||||
# Make Z 0
|
||||
box = box.translate((0,0,7))
|
||||
return box
|
||||
|
||||
|
||||
result = fc()
|
||||
|
||||
assert result.solids().size() == 1, "fc is not one solid"
|
||||
|
||||
if "show_object" not in globals(): # running outside CQ-editor
|
||||
def show_object(*args, **kwargs):
|
||||
pass
|
||||
|
||||
show_object(result)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
print("fc %.2f g" % (result.val().Volume() * PCB_DENSITY))
|
||||
|
||||
34
frame.py
34
frame.py
@@ -16,6 +16,7 @@ from camera_mount import result as CAMERA_MOUNT
|
||||
import standoff as SO
|
||||
from standoff import result as STANDOFF
|
||||
from lollipop import result as LOLLIPOP
|
||||
from fc import result as FC, PCB_DENSITY
|
||||
|
||||
OUT = "build"
|
||||
|
||||
@@ -28,10 +29,9 @@ COLORS = {
|
||||
"spar": cq.Color(0.22, 0.62, 0.35), # green
|
||||
"camera_mount": cq.Color(0.95, 0.78, 0.09), # yellow TPU
|
||||
"standoff": cq.Color(0.75, 0.76, 0.78), # aluminium
|
||||
"lollipop": cq.Color(0.95, 0.78, 0.09), # yellow TPU, as the mount
|
||||
"lollipop": cq.Color(0.95, 0.78, 0.09), # yellow TPU
|
||||
"fc": cq.Color(0.42, 0.28, 0.62), # PCB purple
|
||||
"prop": cq.Color(0.55, 0.55, 0.60), # opaque: a ring hides
|
||||
# nothing, so there is no
|
||||
# call for transparency
|
||||
}
|
||||
|
||||
DENSITY = {
|
||||
@@ -40,6 +40,7 @@ DENSITY = {
|
||||
"camera_mount": TPU_DENSITY,
|
||||
"standoff": SO.ALU_DENSITY,
|
||||
"lollipop": TPU_DENSITY,
|
||||
"fc": PCB_DENSITY,
|
||||
}
|
||||
|
||||
# --- propellers -------------------------------------------------------------
|
||||
@@ -77,6 +78,7 @@ PARTS = [
|
||||
("camera_mount", CAMERA_MOUNT, P.THICKNESS, "solid"),
|
||||
("standoff", STANDOFF, SO.OD, "solid"),
|
||||
("lollipop", LOLLIPOP, 3.5, "solid"),
|
||||
("fc", FC, 11.0, "solid"),
|
||||
]
|
||||
|
||||
|
||||
@@ -90,9 +92,9 @@ def _loc(x, y, z, rot_z, flip=0.0):
|
||||
|
||||
|
||||
def pieces():
|
||||
"""[(name, tag, solid, Location)] -- the 19 bodies, straight off the
|
||||
placement helpers: 2 plates, 4 arms, 8 motor bases, 4 spars, 1 camera
|
||||
mount."""
|
||||
"""[(name, tag, solid, Location)] -- the 25 bodies, straight off the
|
||||
placement helpers: 2 plates, 4 arms, 8 motor bases, 4 spars, 4 standoffs,
|
||||
1 camera mount, 1 antenna holder, 1 fc stack."""
|
||||
out = []
|
||||
for i, (x, y, z, rz) in enumerate(P.plate_placements()):
|
||||
out.append(("plate", "plate_%s" % ("bottom", "top")[i],
|
||||
@@ -121,6 +123,14 @@ def pieces():
|
||||
# height that clears both plates.
|
||||
out.append(("lollipop", "lollipop_0", LOLLIPOP,
|
||||
_loc(0.0, -22.0, 7.0, 90.0)))
|
||||
# fc: the stack stands on the bottom plate's top face, over the 25.5 square
|
||||
# the plate is drilled for. fc.py puts its own origin at the underside of
|
||||
# its posts, so that face is the whole placement. The 45 deg is not
|
||||
# cosmetic: fc.py sets its posts on the diagonals at (+-12.75, +-12.75),
|
||||
# while the plate drills the same 25.5 square with its holes on the axes at
|
||||
# 25.5/sqrt(2) = 18.03. Same radius, turned an eighth of a turn, so without
|
||||
# this the screws miss.
|
||||
out.append(("fc", "fc_0", FC, _loc(0.0, 0.0, P.THICKNESS, 45.0)))
|
||||
return out
|
||||
|
||||
|
||||
@@ -175,11 +185,13 @@ def bom(ps):
|
||||
print(" %-12s %4d %6.1f %9.3f %9.3f"
|
||||
% (name, qty, thick, each, each * qty))
|
||||
print(" %-12s %4d %6s %9s %9.3f" % ("FRAME", len(ps), "", "", total))
|
||||
print(" carbon at %.2f g/cm3, TPU camera_mount at %.2f g/cm3,"
|
||||
" aluminium standoffs at %.2f g/cm3;"
|
||||
% (P.CF_DENSITY * 1e3, TPU_DENSITY * 1e3, SO.ALU_DENSITY * 1e3))
|
||||
print(" the standoffs are the only hardware counted -- no screws, motors"
|
||||
" or electronics")
|
||||
print(" carbon at %.2f g/cm3, TPU parts at %.2f g/cm3, aluminium"
|
||||
" standoffs at %.2f g/cm3," % (P.CF_DENSITY * 1e3, TPU_DENSITY * 1e3,
|
||||
SO.ALU_DENSITY * 1e3))
|
||||
print(" fc as a solid block of FR4 at %.2f g/cm3 -- an envelope, not a"
|
||||
" board" % (PCB_DENSITY * 1e3,))
|
||||
print(" hardware counted is the standoffs and the fc stack -- no screws,"
|
||||
" motors, props or receiver")
|
||||
return total
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user