174 lines
4.8 KiB
Python
174 lines
4.8 KiB
Python
# @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))
|