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 spar.py
|
||||||
.venv/bin/python3 camera_mount.py # printed accessory, not a frame body
|
.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 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
|
.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 |
|
| spar | 4 | 2 mm | carbon fibre | 1.19 | 4.76 |
|
||||||
| camera_mount | 1 | printed | TPU | 7.87 | 7.87 |
|
| camera_mount | 1 | printed | TPU | 7.87 | 7.87 |
|
||||||
| standoff | 4 | Ø3.5 mm | aluminium | 0.39 | 1.56 |
|
| standoff | 4 | Ø3.5 mm | aluminium | 0.39 | 1.56 |
|
||||||
| lollipop | 1 | printed | TPU | 1.15 | 1.15 |
|
| lollipop | 1 | printed | TPU | 2.12 | 2.12 |
|
||||||
| **frame** | **24** | | | | **33.05** |
|
| 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,
|
**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
|
### 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
|
# 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.
|
# 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"
|
run "$part.py"
|
||||||
done
|
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
|
import standoff as SO
|
||||||
from standoff import result as STANDOFF
|
from standoff import result as STANDOFF
|
||||||
from lollipop import result as LOLLIPOP
|
from lollipop import result as LOLLIPOP
|
||||||
|
from fc import result as FC, PCB_DENSITY
|
||||||
|
|
||||||
OUT = "build"
|
OUT = "build"
|
||||||
|
|
||||||
@@ -28,10 +29,9 @@ COLORS = {
|
|||||||
"spar": cq.Color(0.22, 0.62, 0.35), # green
|
"spar": cq.Color(0.22, 0.62, 0.35), # green
|
||||||
"camera_mount": cq.Color(0.95, 0.78, 0.09), # yellow TPU
|
"camera_mount": cq.Color(0.95, 0.78, 0.09), # yellow TPU
|
||||||
"standoff": cq.Color(0.75, 0.76, 0.78), # aluminium
|
"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
|
"prop": cq.Color(0.55, 0.55, 0.60), # opaque: a ring hides
|
||||||
# nothing, so there is no
|
|
||||||
# call for transparency
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DENSITY = {
|
DENSITY = {
|
||||||
@@ -40,6 +40,7 @@ DENSITY = {
|
|||||||
"camera_mount": TPU_DENSITY,
|
"camera_mount": TPU_DENSITY,
|
||||||
"standoff": SO.ALU_DENSITY,
|
"standoff": SO.ALU_DENSITY,
|
||||||
"lollipop": TPU_DENSITY,
|
"lollipop": TPU_DENSITY,
|
||||||
|
"fc": PCB_DENSITY,
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- propellers -------------------------------------------------------------
|
# --- propellers -------------------------------------------------------------
|
||||||
@@ -77,6 +78,7 @@ PARTS = [
|
|||||||
("camera_mount", CAMERA_MOUNT, P.THICKNESS, "solid"),
|
("camera_mount", CAMERA_MOUNT, P.THICKNESS, "solid"),
|
||||||
("standoff", STANDOFF, SO.OD, "solid"),
|
("standoff", STANDOFF, SO.OD, "solid"),
|
||||||
("lollipop", LOLLIPOP, 3.5, "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():
|
def pieces():
|
||||||
"""[(name, tag, solid, Location)] -- the 19 bodies, straight off the
|
"""[(name, tag, solid, Location)] -- the 25 bodies, straight off the
|
||||||
placement helpers: 2 plates, 4 arms, 8 motor bases, 4 spars, 1 camera
|
placement helpers: 2 plates, 4 arms, 8 motor bases, 4 spars, 4 standoffs,
|
||||||
mount."""
|
1 camera mount, 1 antenna holder, 1 fc stack."""
|
||||||
out = []
|
out = []
|
||||||
for i, (x, y, z, rz) in enumerate(P.plate_placements()):
|
for i, (x, y, z, rz) in enumerate(P.plate_placements()):
|
||||||
out.append(("plate", "plate_%s" % ("bottom", "top")[i],
|
out.append(("plate", "plate_%s" % ("bottom", "top")[i],
|
||||||
@@ -121,6 +123,14 @@ def pieces():
|
|||||||
# height that clears both plates.
|
# height that clears both plates.
|
||||||
out.append(("lollipop", "lollipop_0", LOLLIPOP,
|
out.append(("lollipop", "lollipop_0", LOLLIPOP,
|
||||||
_loc(0.0, -22.0, 7.0, 90.0)))
|
_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
|
return out
|
||||||
|
|
||||||
|
|
||||||
@@ -175,11 +185,13 @@ def bom(ps):
|
|||||||
print(" %-12s %4d %6.1f %9.3f %9.3f"
|
print(" %-12s %4d %6.1f %9.3f %9.3f"
|
||||||
% (name, qty, thick, each, each * qty))
|
% (name, qty, thick, each, each * qty))
|
||||||
print(" %-12s %4d %6s %9s %9.3f" % ("FRAME", len(ps), "", "", total))
|
print(" %-12s %4d %6s %9s %9.3f" % ("FRAME", len(ps), "", "", total))
|
||||||
print(" carbon at %.2f g/cm3, TPU camera_mount at %.2f g/cm3,"
|
print(" carbon at %.2f g/cm3, TPU parts at %.2f g/cm3, aluminium"
|
||||||
" aluminium standoffs at %.2f g/cm3;"
|
" standoffs at %.2f g/cm3," % (P.CF_DENSITY * 1e3, TPU_DENSITY * 1e3,
|
||||||
% (P.CF_DENSITY * 1e3, TPU_DENSITY * 1e3, SO.ALU_DENSITY * 1e3))
|
SO.ALU_DENSITY * 1e3))
|
||||||
print(" the standoffs are the only hardware counted -- no screws, motors"
|
print(" fc as a solid block of FR4 at %.2f g/cm3 -- an envelope, not a"
|
||||||
" or electronics")
|
" board" % (PCB_DENSITY * 1e3,))
|
||||||
|
print(" hardware counted is the standoffs and the fc stack -- no screws,"
|
||||||
|
" motors, props or receiver")
|
||||||
return total
|
return total
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user