Introducing the pirouette FPV frame for GFPV
Signed-off-by: Arnaud Morin <arnaud.gfpv@mailops.fr>
This commit is contained in:
173
plate.py
Normal file
173
plate.py
Normal file
@@ -0,0 +1,173 @@
|
||||
# @author Arnaud Morin <arnaud.gfpv@mailops.fr>
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
from math import cos, radians, sin
|
||||
|
||||
import cadquery as cq
|
||||
|
||||
from frame_params import (CF_DENSITY, FIT, TBONE_D, TENON_L, THICKNESS)
|
||||
|
||||
|
||||
def _custom_filets(wp, corners, tol=1e-4):
|
||||
"""The edges that run through the flat pattern (parallel to Z) at the
|
||||
given (x, y) corners."""
|
||||
picked = []
|
||||
for e in wp.edges().vals():
|
||||
vs = e.Vertices()
|
||||
if len(vs) != 2:
|
||||
continue
|
||||
a, b = vs[0].toTuple(), vs[1].toTuple()
|
||||
if abs(a[0] - b[0]) > 1e-6 or abs(a[1] - b[1]) > 1e-6:
|
||||
continue
|
||||
if any(abs(a[0] - cx) < tol and abs(a[1] - cy) < tol for cx, cy in corners):
|
||||
picked.append(e)
|
||||
return wp.newObject(picked)
|
||||
|
||||
|
||||
def _mortises():
|
||||
"""(x, y, angle) of the four arm root mortises, in the plate's own frame.
|
||||
angle is the mortise's long axis, which is radial."""
|
||||
return [(25.3 * cos(radians(a)),
|
||||
25.3 * sin(radians(a)), a) for a in (45.0, 135.0, 225.0, 315.0)]
|
||||
|
||||
|
||||
def plate():
|
||||
# one quadrant, from the +X axis to the +Y axis
|
||||
quadrant = [
|
||||
(21, 0.0),
|
||||
(15, 7.0),
|
||||
(28.5, 22.9),
|
||||
(13.9, 23.7),
|
||||
(7.5, 20.5),
|
||||
(0.0, 20.5),
|
||||
]
|
||||
# mirrored across the Y axis (x -> -x) gives quadrant 2, joining quadrant
|
||||
# 1 into the top half -- both ends now sit on the X axis, so mirrorX()
|
||||
# below can close the loop by mirroring that half across it in turn
|
||||
top_half = quadrant + [(-x, y) for x, y in reversed(quadrant)][1:]
|
||||
|
||||
p = (
|
||||
cq.Workplane("XY")
|
||||
.polyline(top_half)
|
||||
.mirrorX()
|
||||
.extrude(THICKNESS)
|
||||
)
|
||||
|
||||
# Filets
|
||||
p = _custom_filets(p, [(21, 0), (-21, 0), (-21, -0), (21, -0)]).fillet(2)
|
||||
p = _custom_filets(p, [(15, 7), (-15, 7), (-15, -7), (15, -7)]).fillet(2)
|
||||
p = _custom_filets(p, [(28.5, 22.9), (-28.5, 22.9), (-28.5, -22.9), (28.5, -22.9)]).fillet(4.5)
|
||||
p = _custom_filets(p, [(13.9, 23.7), (-13.9, 23.7), (-13.9, -23.7), (13.9, -23.7)]).fillet(2)
|
||||
p = _custom_filets(p, [(7.5, 20.5), (-7.5, 20.5), (-7.5, -20.5), (7.5, -20.5)]).fillet(6.5)
|
||||
|
||||
# Add holes for 20x20 FC
|
||||
p = (
|
||||
p.faces(">Z")
|
||||
.workplane()
|
||||
.pushPoints([(10, 10), (-10, 10), (-10, -10), (10, -10)])
|
||||
# Holes are 2.2 to fit 2.0 screws
|
||||
.hole(2.2)
|
||||
)
|
||||
|
||||
# Add holes for 25.5x25.5 FC
|
||||
p = (
|
||||
p.faces(">Z")
|
||||
.workplane()
|
||||
.pushPoints([(18.03, 0), (-18.03, 0), (0, -18.03), (0, 18.03)])
|
||||
# Holes are 2.2 to fit 2.0 screws
|
||||
.hole(2.2)
|
||||
)
|
||||
|
||||
# Add holes for standoff
|
||||
x, y = (14, 20)
|
||||
p = p.faces(">Z").workplane().pushPoints(
|
||||
[(sx * x, sy * y) for sx in (-1.0, 1.0) for sy in (-1.0, 1.0)]
|
||||
).hole(2.2)
|
||||
|
||||
# Arm mortises
|
||||
length, width = TENON_L + FIT, THICKNESS
|
||||
r = TBONE_D / 2.0
|
||||
hl, hw = length / 2.0, width / 2.0
|
||||
body = (cq.Workplane("XY")
|
||||
.polyline([(-hl, -hw), (hl, -hw), (hl, hw), (-hl, hw)])
|
||||
.close().extrude(THICKNESS))
|
||||
bones = cq.Workplane("XY")
|
||||
for u in (-(hl - r), hl - r):
|
||||
for v in (-hw, hw):
|
||||
bones = bones.moveTo(u, v).circle(r)
|
||||
bones = bones.extrude(THICKNESS)
|
||||
|
||||
for x, y, a in _mortises():
|
||||
# two separate cuts, not one combined cutter -- a compound of the
|
||||
# rectangle plus 4 tangent circles confuses the boolean into leaving
|
||||
# sliver solids behind
|
||||
p = p.cut(body.rotate((0, 0, 0), (0, 0, 1), a).translate((x, y, 0)))
|
||||
p = p.cut(bones.rotate((0, 0, 0), (0, 0, 1), a).translate((x, y, 0)))
|
||||
|
||||
# Triangle cuts to reduce weight
|
||||
cut1 = (
|
||||
cq.Workplane("XY")
|
||||
.polyline([
|
||||
(6.5, -9),
|
||||
(6.5, 9),
|
||||
(1, 0),
|
||||
]).close()
|
||||
.extrude(THICKNESS)
|
||||
.edges("|Z")
|
||||
.fillet(1.0)
|
||||
)
|
||||
cut2 = (
|
||||
cq.Workplane("XY")
|
||||
.polyline([
|
||||
(9.5, -9),
|
||||
(9.5, 9),
|
||||
(15, 0),
|
||||
]).close()
|
||||
.extrude(THICKNESS)
|
||||
.edges("|Z")
|
||||
.fillet(1.0)
|
||||
)
|
||||
cut3 = (
|
||||
cq.Workplane("XY")
|
||||
.polyline([
|
||||
(11, 15),
|
||||
(-11, 15),
|
||||
(0, 4),
|
||||
]).close()
|
||||
.extrude(THICKNESS)
|
||||
.edges("|Z")
|
||||
.fillet(1.0)
|
||||
)
|
||||
|
||||
p = (
|
||||
p.cut(cut1)
|
||||
.cut(cut1.mirror("YZ"))
|
||||
)
|
||||
p = (
|
||||
p.cut(cut2)
|
||||
.cut(cut2.mirror("YZ"))
|
||||
)
|
||||
p = (
|
||||
p.cut(cut3)
|
||||
.cut(cut3.mirror("XZ"))
|
||||
)
|
||||
|
||||
return p
|
||||
|
||||
|
||||
result = plate()
|
||||
|
||||
assert result.solids().size() == 1, "plate 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__":
|
||||
|
||||
v = result.val().Volume()
|
||||
print("plate mass %.2f g each, %.2f g for two" % (v * CF_DENSITY, 2 * v * CF_DENSITY))
|
||||
Reference in New Issue
Block a user