154 lines
4.6 KiB
Python
154 lines
4.6 KiB
Python
# @author Arnaud Morin <arnaud.gfpv@mailops.fr>
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
#
|
|
|
|
import cadquery as cq
|
|
|
|
from frame_params import (CF_DENSITY, PLATE_GAP, SPAR_H, TENON_L,
|
|
THICKNESS, TBONE_D)
|
|
|
|
# The arm spans the plate gap, so its height IS PLATE_GAP -- the body
|
|
# reaches the plates' inner faces at +-HALF_H and every tenon stands one
|
|
# THICKNESS proud of that, landing flush with the far face.
|
|
HALF_H = PLATE_GAP / 2.0
|
|
# and the lower motor base's top face, which hangs SPAR_H below the upper
|
|
# one, i.e. below the top plate
|
|
BASE_Z = HALF_H - SPAR_H
|
|
|
|
|
|
def _custom_filets(wp):
|
|
"""Every vertical edge of the flat pattern (parallel to Y) -- one per
|
|
corner, found automatically instead of being listed by hand."""
|
|
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[2] - b[2]) > 1e-6:
|
|
continue
|
|
picked.append(e)
|
|
return wp.newObject(picked)
|
|
|
|
|
|
def arm():
|
|
part = cq.Workplane("XZ").polyline([
|
|
(0.0, HALF_H),
|
|
# first tenon, the one that fit in top plate
|
|
(3.8, HALF_H),
|
|
(3.8, HALF_H + THICKNESS),
|
|
(3.8 + TENON_L, HALF_H + THICKNESS),
|
|
(3.8 + TENON_L, HALF_H),
|
|
# End of top plate
|
|
(12, HALF_H),
|
|
(12, HALF_H + THICKNESS),
|
|
# Arm to motor base
|
|
(20.0, HALF_H + THICKNESS),
|
|
# Begining of motor base
|
|
(20.0, HALF_H),
|
|
(25.0, HALF_H),
|
|
# Tenon into motor base
|
|
(25, HALF_H + THICKNESS),
|
|
(25 + TENON_L, HALF_H + THICKNESS),
|
|
# Under the motor base
|
|
(25 + TENON_L, HALF_H),
|
|
(32, HALF_H),
|
|
# Come back to bottom plate
|
|
(32, BASE_Z),
|
|
# Tenon under the motor base, symmetrical to the one above
|
|
(25 + TENON_L, BASE_Z),
|
|
(25 + TENON_L, BASE_Z - THICKNESS),
|
|
(25, BASE_Z - THICKNESS),
|
|
(25, BASE_Z),
|
|
(20, BASE_Z),
|
|
# Diag
|
|
(13, -(HALF_H + THICKNESS)),
|
|
(12, -(HALF_H + THICKNESS)),
|
|
(12, -HALF_H),
|
|
# Tenon in bottom plate
|
|
(3.8 + TENON_L, -HALF_H),
|
|
(3.8 + TENON_L, -(HALF_H + THICKNESS)),
|
|
(3.8, -(HALF_H + THICKNESS)),
|
|
(3.8, -HALF_H),
|
|
(1.5, -HALF_H)
|
|
])
|
|
part = (
|
|
part.lineTo(2, -7)
|
|
.threePointArc(
|
|
(5, 0.0),
|
|
(0.0, 6)
|
|
)
|
|
)
|
|
# Extrude. Both = True do the extrusion along the Y axis each face,
|
|
# so no need to translate later :)
|
|
part = part.close().extrude(THICKNESS / 2.0, both=True)
|
|
|
|
# Hole
|
|
cut = (
|
|
cq.Workplane('XZ')
|
|
.polyline([
|
|
(7, 4),
|
|
(17, 4),
|
|
(12.5, -5),
|
|
(6.5, -5),
|
|
])
|
|
.threePointArc(
|
|
(7.5, 0.0),
|
|
(6, 4),
|
|
)
|
|
.close()
|
|
.extrude(THICKNESS / 2.0, both=True)
|
|
)
|
|
|
|
# smooth every sharp corner, no bare angles left
|
|
cut = _custom_filets(cut).fillet(1.0)
|
|
part = part.cut(cut)
|
|
|
|
# T-Bones on tenons (x, z)
|
|
tbones = [
|
|
# root tenon into top plate, shifted 1 mm right
|
|
(3.8 - TBONE_D / 2 + 0.05, HALF_H),
|
|
(3.8 + TENON_L + TBONE_D / 2 - 0.05, HALF_H),
|
|
# tenon into motor base
|
|
(25 - TBONE_D / 2 + 0.05, HALF_H),
|
|
(25 + TENON_L + TBONE_D / 2 - 0.05, HALF_H),
|
|
# tenon under the motor base
|
|
(25 - TBONE_D / 2 + 0.05, BASE_Z),
|
|
(25 + TENON_L + TBONE_D / 2 - 0.05, BASE_Z),
|
|
# root tenon into bottom plate, shifted 1 mm right
|
|
(3.8 - TBONE_D / 2 + 0.05, -HALF_H),
|
|
(3.8 + TENON_L + TBONE_D / 2 - 0.05, -HALF_H),
|
|
# 3 more sharp inside corners that need relief, away from any tenon:
|
|
# where the flat top steps up into the raised motor-base flange...
|
|
(12 - TBONE_D / 2 + 0.05, HALF_H),
|
|
# ... its mirror on the bottom edge ...
|
|
(12 - TBONE_D / 2 + 0.05, -HALF_H),
|
|
# ... and the sharp corner where the diagonal brace meets the tip block
|
|
(20 + TBONE_D / 2 - 0.05, HALF_H),
|
|
]
|
|
|
|
# Cut the T-Bones
|
|
cut = cq.Workplane("XZ")
|
|
for x, z in tbones:
|
|
cut = cut.moveTo(x, z).circle(TBONE_D / 2.0)
|
|
part = part.cut(cut.extrude(THICKNESS / 2.0, both=True))
|
|
|
|
return part
|
|
|
|
|
|
result = arm()
|
|
|
|
assert result.solids().size() == 1, "arm 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__":
|
|
vol = result.val().Volume()
|
|
m = vol * CF_DENSITY
|
|
print(f"arm mass {m:.2f} g each, {4*m:.2f} g for four")
|
|
|