# @author Arnaud Morin # 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 = 5.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()