125 lines
4.5 KiB
Python
125 lines
4.5 KiB
Python
# @author Arnaud Morin <arnaud.gfpv@mailops.fr>
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
#
|
|
from math import sqrt
|
|
|
|
import cadquery as cq
|
|
|
|
from frame_params import (CF_DENSITY, FIT, TBONE_D, TENON_L, THICKNESS)
|
|
|
|
ARM_MORTISE_R = 8.6543 # arm mortise centre, inboard of the
|
|
# motor axis. Not a free number: the
|
|
# arm's tip tenon sits at radius 46.5
|
|
# (its root radius 19.5, plus arm.py's
|
|
# own 25, plus half a tenon), and the
|
|
# motor axis is at 55.1543, so anything
|
|
# else pushes the tenon off centre in
|
|
# its mortise. At 8.8 it overhung the
|
|
# outer end by 0.046 and fouled the base.
|
|
|
|
# spar mortise centre, in the base's own local frame: on the spar
|
|
# centreline, SPAR_OFFSET outboard of the motor axis and SPAR_TENON_POS back
|
|
# along the spar -- both shared with the spar's own tenon, so the mortise
|
|
# and the tenon it keys into can't drift apart. Not linked by import to
|
|
# frame_params or spar.py any more -- keep this pair in step by hand.
|
|
SPAR_OFFSET = 2.5
|
|
SPAR_TENON_POS = 9
|
|
SPAR_MORTISE_X = (SPAR_OFFSET + SPAR_TENON_POS) / sqrt(2.0)
|
|
SPAR_MORTISE_Y = (SPAR_OFFSET - SPAR_TENON_POS) / sqrt(2.0)
|
|
SPAR_MORTISE_ANGLE = 45.0 # +-45 deg to the arm axis
|
|
|
|
MORTISE_L = TENON_L + FIT # 4.2
|
|
MORTISE_W = THICKNESS
|
|
|
|
BORE_D = 4.0 # 1404 bell / boss clearance
|
|
BOLT_SQ = 9.0 # 1404/1105 M2 pattern -- the DIAGONAL between
|
|
# opposite bolt holes, not the square's side
|
|
BOLT_D = 2.2 # M2 clearance
|
|
BOLT_R = BOLT_SQ / (2.0 * sqrt(2.0))
|
|
|
|
|
|
def bolt_points():
|
|
return [(sx * BOLT_R, sy * BOLT_R) for sx in (-1, 1) for sy in (-1, 1)]
|
|
|
|
|
|
def mortises():
|
|
"""(x, y, angle) of the three mortises, in the motor base's own frame
|
|
(origin = motor axis, +Y outboard, arm mortise at -Y). All three are
|
|
MORTISE_L x MORTISE_W -- only position and angle differ."""
|
|
return [
|
|
(0.0, -ARM_MORTISE_R, 90.0),
|
|
(-SPAR_MORTISE_X, SPAR_MORTISE_Y, 90.0 - SPAR_MORTISE_ANGLE),
|
|
(+SPAR_MORTISE_X, SPAR_MORTISE_Y, 90.0 + SPAR_MORTISE_ANGLE),
|
|
]
|
|
|
|
|
|
def motor_base():
|
|
# the open half-profile has to start and end ON the mirror axis (x = 0)
|
|
# for mirrorY() to stitch it into one closed loop -- otherwise the two
|
|
# halves just don't meet
|
|
part = (
|
|
cq.Workplane("XY")
|
|
.polyline([
|
|
(0.0, -15.5),
|
|
(2.0, -15.5),
|
|
(4.0, -10.0),
|
|
(8.5, -9.5),
|
|
(13.0, -4.5),
|
|
(4.0, 7.0),
|
|
(0.0, 7.0),
|
|
])
|
|
.mirrorY()
|
|
.extrude(THICKNESS)
|
|
)
|
|
#return part
|
|
part = part.edges("|Z").fillet(2.8)
|
|
|
|
# motor bell / boss clearance, then the M2 bolt pattern
|
|
part = (part.faces(">Z").workplane(centerOption="ProjectedOrigin")
|
|
.hole(BORE_D))
|
|
part = (part.faces(">Z").workplane(centerOption="ProjectedOrigin")
|
|
.pushPoints(bolt_points()).hole(BOLT_D))
|
|
|
|
# the three mortises: arm inboard at -Y, spars at +-45 deg, T-bone
|
|
# relieved on the long edges. One canonical cutter, all the same size,
|
|
# rotated and translated into each of the 3 spots.
|
|
hl, hw = MORTISE_L / 2.0, MORTISE_W / 2.0
|
|
r = TBONE_D / 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, angle 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
|
|
part = part.cut(body.rotate((0, 0, 0), (0, 0, 1), angle).translate((x, y, 0)))
|
|
part = part.cut(bones.rotate((0, 0, 0), (0, 0, 1), angle).translate((x, y, 0)))
|
|
|
|
return part
|
|
|
|
|
|
result = motor_base()
|
|
|
|
assert result.solids().size() == 1, "motor base is not one solid"
|
|
|
|
if "show_object" not in globals(): # running outside CQ-editor
|
|
def show_object(*args, **kwargs):
|
|
pass
|
|
|
|
show_object(result)
|
|
|
|
# Mass report
|
|
def report():
|
|
vol = result.val().Volume()
|
|
print(f" Mass = {vol * CF_DENSITY:5.2f} g")
|
|
print(f" Mass x4 = {4 * vol * CF_DENSITY:5.2f} g")
|
|
|
|
if __name__ == "__main__":
|
|
report()
|