# @author Arnaud Morin # 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")