import cadquery as cq from cadquery import exporters import screen_pillars from modelo import ( height, mounting_pillar_positions, ti_depth, ti_radius, width, thickness as model_thickness, shell_t, ) import screen_mount import keyboard hinge_radius = 5.5 screw_radius = 1.5 # M3 ring_radius = 5 # M3 hinge_offset = max(p[1] for p in mounting_pillar_positions) + 6 hinge_width = 25 thickness = 43 mounting_pillar_positions = [(x, -y) for x, y in mounting_pillar_positions] mounting_pillars = ( cq.Sketch() .push(mounting_pillar_positions) .trapezoid(-12, 12, 90, mode="a") .circle(ti_radius, mode="s") .clean() ) # This is a constant used to control how far back the hinges go # when open. It's arbitrary and can be adjusted experimentally # printing small samples hinge_slant = shell_t + 2 def model(): # Create a 2-part hinged lid model = ( cq.Workplane("XY") # Hollow box .workplane(offset=-thickness / 2) .box(width, height, thickness) .tag("base") .edges("|X and >Z and Z and >Y") .fillet(5) .edges("|Z") .fillet(2) .faces("X") .workplane() .center(height / 2 - hinge_offset, thickness / 2 - hinge_radius) .tag("rightSide") # Outer surface of the hinge .workplaneFromTagged("rightSide") .placeSketch(cq.Sketch().circle(hinge_radius)) .extrude(-hinge_width) .workplaneFromTagged("rightSide") .workplane(offset=-width + hinge_width) .placeSketch(cq.Sketch().circle(hinge_radius)) .extrude(-hinge_width) # Cut middle section between the hinges .workplaneFromTagged("rightSide") .workplane(offset=-hinge_width) .placeSketch( cq.Sketch().polygon( [ (-hinge_radius, -hinge_radius), (-hinge_radius, 0), (-hinge_radius - hinge_slant, hinge_radius), (-hinge_slant, hinge_radius), (-hinge_slant, hinge_radius - hinge_slant), (hinge_radius, hinge_radius - hinge_slant), (hinge_radius, -hinge_radius), (-hinge_radius, -hinge_radius), ] ) ) .cutBlind(-width + 2 * hinge_width - 1) # Pillars to attach to base .workplaneFromTagged("base") .workplane( centerOption="CenterOfBoundBox", offset=model_thickness - thickness / 2 ) .workplaneFromTagged("base") .workplane(offset=thickness / 2 - shell_t) .center(-width / 2, height / 2 - shell_t) .placeSketch(mounting_pillars) .extrude(-10) # Hole for screws .workplaneFromTagged("rightSide") .placeSketch(cq.Sketch().circle(screw_radius)) .cutBlind(-hinge_width) .workplaneFromTagged("rightSide") .workplane(offset=-width + hinge_width) .placeSketch(cq.Sketch().circle(screw_radius)) .cutBlind(-hinge_width) # Holes for rings & screw heads .workplaneFromTagged("rightSide") .placeSketch(cq.Sketch().circle(ring_radius)) .cutBlind(-5) .workplaneFromTagged("rightSide") .workplane(offset=-width + 4) .placeSketch(cq.Sketch().circle(ring_radius)) .cutBlind(-5) # Split hinge halves .faces(">X") .workplaneFromTagged("rightSide") .workplane(offset=-hinge_width / 2) .placeSketch(cq.Sketch().trapezoid(hinge_radius * 2 + 1, hinge_radius * 2, 90)) .cutBlind(-1) .workplaneFromTagged("rightSide") .workplane(offset=-hinge_width) .placeSketch(cq.Sketch().trapezoid(hinge_radius * 2 + 1, hinge_radius * 2, 90)) .cutBlind(-1) .workplaneFromTagged("rightSide") .workplane(offset=-width + hinge_width / 2) .placeSketch(cq.Sketch().trapezoid(hinge_radius * 2 + 1, hinge_radius * 2, 90)) .cutBlind(-1) .workplaneFromTagged("rightSide") .workplane(offset=-width + hinge_width) .placeSketch(cq.Sketch().trapezoid(hinge_radius * 2 + 1, hinge_radius * 2, 90)) .cutBlind(-1) # Threaded inserts .workplaneFromTagged("rightSide") .workplane(offset=-hinge_width / 2) .placeSketch(cq.Sketch().circle(ti_radius)) .cutBlind(-ti_depth) .workplaneFromTagged("rightSide") .workplane(offset=-width + hinge_width / 2) .placeSketch(cq.Sketch().circle(ti_radius)) .cutBlind(ti_depth) # Split two halves # First cut for the right hinge .workplaneFromTagged("rightSide") .placeSketch( cq.Sketch() .polygon( [ (0, 0), (-hinge_radius - 0.2, 0), (-hinge_radius - hinge_slant, hinge_radius), (0, hinge_radius), (0, 0), ] ) .polygon( [ (-hinge_radius - 0.2, 0), (-hinge_radius - 0.2, -1000), (-hinge_radius, -1000), (-hinge_radius, 0), (-hinge_radius - 0.2, 0), ] ) .circle(hinge_radius, mode="s") ) .cutBlind(-hinge_width / 2 - 1) # Second cut for the right hinge .workplaneFromTagged("rightSide") .workplane(offset=-hinge_width / 2) .placeSketch( cq.Sketch() .polygon( [ (0, 0), (hinge_radius + 0.2, 0), (hinge_radius + 0.2 + hinge_slant, hinge_radius), (0, hinge_radius), (0, 0), ] ) .circle(hinge_radius, mode="s") ) .cutBlind(-hinge_width / 2 - 1) # First cut for the left hinge .workplaneFromTagged("rightSide") .workplane(offset=-width + hinge_width) .placeSketch( cq.Sketch() .polygon( [ (0, 0), (hinge_radius + 0.2, 0), (hinge_radius + 0.2 + hinge_slant, hinge_radius), (0, hinge_radius), (0, 0), ] ) .circle(hinge_radius, mode="s") ) .cutBlind(-hinge_width / 2 - 1) # Second cut for the left hinge .workplaneFromTagged("rightSide") .workplane(offset=-width + hinge_width / 2) .placeSketch( cq.Sketch() .polygon( [ (0, 0), (-hinge_radius - 0.2, 0), (-hinge_radius - hinge_slant, hinge_radius), (0, hinge_radius), (0, 0), ] ) .polygon( [ (-hinge_radius - 0.2, 0), (-hinge_radius - 0.2, -1000), (-hinge_radius, -1000), (-hinge_radius, 0), (-hinge_radius - 0.2, 0), ] ) .circle(hinge_radius, mode="s") ) .cutBlind(-hinge_width / 2 - 1) ) # M3 hex nut dimensions m3_hn_diam = 5.5 / 2 m3_hn_hole = 1.5 m3_hn_thickness = 2.5 bezel_width = 5 bezel_height = shell_t # Screen mount model = ( # 1st layer model.workplaneFromTagged("base") .center(0, -32) .workplane(offset=thickness / 2 - shell_t) .tag("screen_plane") .placeSketch( cq.Sketch() .trapezoid( screen_mount.scr_w + 2 * bezel_width, screen_mount.scr_h + 2 * bezel_height, 90, ) .vertices() .fillet(2) ) .extrude(-2 - screen_mount.scr_thickness) # Holes for captured nuts .workplaneFromTagged("screen_plane") .workplane(offset=1) .rect( screen_mount.scr_w + 2 * bezel_width - m3_hn_diam - 1, screen_mount.scr_h + 2 * bezel_height - m3_hn_diam - 1, forConstruction=True, ) .vertices() .hole(m3_hn_diam, depth=m3_hn_thickness + 0.5) # Hole for screws .workplaneFromTagged("screen_plane") .workplane(offset=1) .rect( screen_mount.scr_w + 2 * bezel_width - m3_hn_diam - 1, screen_mount.scr_h + 2 * bezel_height - m3_hn_diam - 1, forConstruction=True, ) .vertices() .hole(m3_hn_hole, depth=10) # Hole to place screen .workplaneFromTagged("screen_plane") .placeSketch( cq.Sketch().trapezoid( screen_mount.scr_w - 40, screen_mount.scr_h + 2 * shell_t, 90 ) ) .cutBlind(-100) # Remove middle of the screen holder .workplaneFromTagged("screen_plane") .workplane(offset=-screen_mount.scr_thickness - 2) .placeSketch(cq.Sketch().trapezoid(screen_mount.scr_w, screen_mount.scr_h, 90)) .cutBlind(screen_mount.scr_thickness) ) # Cut off shape of the base model = ( model.workplaneFromTagged("rightSide") .center(-height + hinge_offset, -thickness + hinge_radius) .placeSketch( cq.Sketch().polygon( [ (0, 0), (0, keyboard.front_thickness), (shell_t, keyboard.front_thickness), (keyboard.actual_height + shell_t, keyboard.back_thickness), (keyboard.actual_height + shell_t, model_thickness), (height, model_thickness), (height, 0), (0, 0), ] ) ) .cutBlind(-1000) ) return model if __name__ == "__main__": model = model() exporters.export(model, "hinged_lid.stl") exporters.export( model, "hinged_lid.svg", opt={ "projectionDir": (0, 0, -1), "strokeWidth": 0.3, }, ) offset_width = -width / 2 right_side = ( model.faces(">X") .workplane(centerOption="CenterOfBoundBox", offset=offset_width) .split(keepTop=True) ) exporters.export(right_side, "right_hinged_lid.stl") left_side = ( model.faces(">X") .workplane(centerOption="CenterOfBoundBox", offset=offset_width) .split(keepBottom=True) ) exporters.export(left_side, "left_hinged_lid.stl")