# @author Arnaud Morin # SPDX-License-Identifier: Apache-2.0 # import cadquery as cq from frame_params import (CF_DENSITY, FIT, TENON_L, THICKNESS, TBONE_D) # frame-wide facts -- hardcoded here to match frame_params' own values; # not linked by import any more, so keep them in step by hand MOTOR_GAP = 78.0 # motor-to-motor gap on a true X, same as the prop SPAR_H = 4.0 # the spar's own clear span SPAR_OFFSET = 2.5 SPAR_TENON_POS = 9 LAP_W = THICKNESS + FIT # How much len we add to add a crossing between lap # I choose to add twice the thickness LAP_OFFSET = 2.0 * THICKNESS # Spar len is motor gap + extra so two spar can cross and fit together # Extras are: # SPAR_OFFSET which is used to position the mortese in motor base # LAP_OFFSET to cross the two spar together SPAR_LEN = MOTOR_GAP + (SPAR_OFFSET + LAP_OFFSET) * 2 # Motor axis MOTOR_CENTER = (SPAR_LEN - MOTOR_GAP) / 2.0 # Tenon position TENON_CENTER = MOTOR_CENTER + SPAR_TENON_POS TENON_START = TENON_CENTER - TENON_L / 2.0 TENON_END = TENON_CENTER + TENON_L / 2.0 def spar(): part = ( cq.Workplane("XZ") .polyline([ (0.0, 0.0), (0.0, SPAR_H/2.0), (TENON_START, SPAR_H/2), (TENON_START, SPAR_H/2 + THICKNESS), (TENON_END, SPAR_H/2 + THICKNESS), (TENON_END, SPAR_H/2), (SPAR_LEN - TENON_END, SPAR_H/2), (SPAR_LEN - TENON_END, SPAR_H/2 + THICKNESS), (SPAR_LEN - TENON_START, SPAR_H/2 + THICKNESS), (SPAR_LEN - TENON_START, SPAR_H/2), (SPAR_LEN, SPAR_H/2), (SPAR_LEN, 0.0), ]) .mirrorX() .extrude(THICKNESS) ) # Cut the overlaps: half-lap notches, centred LAP_OFFSET on each end cut = ( cq.Workplane("XZ") .polyline([ (LAP_OFFSET - LAP_W / 2.0, 0.0), (LAP_OFFSET + LAP_W / 2.0, 0.0), (LAP_OFFSET + LAP_W / 2.0, SPAR_H / 2.0), (LAP_OFFSET - LAP_W / 2.0, SPAR_H / 2.0), ]) .close() .extrude(THICKNESS) ) part = part.cut(cut) # Second cut cut = ( cq.Workplane("XZ") .polyline([ (SPAR_LEN - LAP_OFFSET + LAP_W / 2.0, 0.0), (SPAR_LEN - LAP_OFFSET - LAP_W / 2.0, 0.0), (SPAR_LEN - LAP_OFFSET - LAP_W / 2.0, SPAR_H / 2.0), (SPAR_LEN - LAP_OFFSET + LAP_W / 2.0, SPAR_H / 2.0), ]) .close() .extrude(THICKNESS) ) part = part.cut(cut) # T-Bones on tenons (x, z) tbones = [ # First tenon (upper left) (TENON_START - TBONE_D/2 + 0.05, SPAR_H/2), (TENON_END + TBONE_D/2 - 0.05, SPAR_H/2), # lower left (TENON_START - TBONE_D/2 + 0.05, -SPAR_H/2), (TENON_END + TBONE_D/2 - 0.05, -SPAR_H/2), # upper right (SPAR_LEN - TENON_START + TBONE_D/2 - 0.05, SPAR_H/2), (SPAR_LEN - TENON_END - TBONE_D/2 + 0.05, SPAR_H/2), # lower right (SPAR_LEN - TENON_START + TBONE_D/2 - 0.05, - SPAR_H/2), (SPAR_LEN - TENON_END - TBONE_D/2 + 0.05, - SPAR_H/2), ] # 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)) # Center the part part = part.translate((-SPAR_LEN / 2.0, THICKNESS / 2.0, 0.0)) return part result = spar() assert result.solids().size() == 1, "spar is not one solid" # 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") # Running outside CQ-editor if "show_object" not in globals(): def show_object(*args, **kwargs): pass show_object(result) if __name__ == "__main__": report()