import cadquery as cq import numpy as np from numpy import pi import os.path as path import json import os from scipy.spatial import ConvexHull as sphull def deg2rad(degrees: float) -> float: return degrees * pi / 180 def rad2deg(rad: float) -> float: return rad * 180 / pi debug_exports = False with open('run_config.json', mode='r') as fid: data = json.load(fid) for item in data: locals()[item] = data[item] if oled_mount_type is not None: for item in oled_configurations[oled_mount_type]: locals()[item] = oled_configurations[oled_mount_type][item] if nrows > 5: column_style = column_style_gt5 centerrow = nrows - centerrow_offset lastrow = nrows - 1 cornerrow = lastrow - 1 lastcol = ncols - 1 # Derived values if plate_style in ['NUB', 'HS_NUB']: keyswitch_height = nub_keyswitch_height keyswitch_width = nub_keyswitch_width elif plate_style in ['UNDERCUT', 'HS_UNDERCUT']: keyswitch_height = undercut_keyswitch_height keyswitch_width = undercut_keyswitch_width else: keyswitch_height = hole_keyswitch_height keyswitch_width = hole_keyswitch_width if plate_style in ['HS_UNDERCUT', 'HS_NUB', 'HS_HOLE']: symmetry = "asymmetric" plate_file = path.join("..", "src", r"hot_swap_plate.step") plate_offset = 0.0 mount_width = keyswitch_width + 3 mount_height = keyswitch_height + 3 mount_thickness = plate_thickness if oled_mount_type is not None: left_wall_x_offset = oled_left_wall_x_offset_override left_wall_z_offset = oled_left_wall_z_offset_override spath = path.join("..", "things", save_dir) if not path.isdir(spath): os.mkdir(spath) def column_offset(column: int) -> list: return column_offsets[column] # column_style='fixed' def rotate(shape, angle): # print('rotate()') origin = (0, 0, 0) shape = shape.rotate(axisStartPoint=origin, axisEndPoint=(1, 0, 0), angleDegrees=angle[0]) shape = shape.rotate(axisStartPoint=origin, axisEndPoint=(0, 1, 0), angleDegrees=angle[1]) shape = shape.rotate(axisStartPoint=origin, axisEndPoint=(0, 0, 1), angleDegrees=angle[2]) return shape def translate(shape, vector): # print('translate()') return shape.translate(tuple(vector)) def mirror(shape, plane=None): print('mirror()') return shape.mirror(mirrorPlane=plane) def union(shapes): print('union()') shape = None for item in shapes: if shape is None: shape = item else: shape = shape.union(item) return shape def face_from_points(points): # print('face_from_points()') edges = [] num_pnts = len(points) for i in range(len(points)): p1 = points[i] p2 = points[(i + 1) % num_pnts] edges.append( cq.Edge.makeLine( cq.Vector(p1[0], p1[1], p1[2]), cq.Vector(p2[0], p2[1], p2[2]), ) ) face = cq.Face.makeFromWires(cq.Wire.assembleEdges(edges)) return face def hull_from_points(points): # print('hull_from_points()') hull_calc = sphull(points) n_faces = len(hull_calc.simplices) faces = [] for i in range(n_faces): face_items = hull_calc.simplices[i] fpnts = [] for item in face_items: fpnts.append(points[item]) faces.append(face_from_points(fpnts)) shape = cq.Solid.makeSolid(cq.Shell.makeShell(faces)) shape = cq.Workplane('XY').union(shape) return shape def hull_from_shapes(shapes, points=None): # print('hull_from_shapes()') vertices = [] for shape in shapes: verts = shape.vertices() for vert in verts.objects: vertices.append(np.array(vert.toTuple())) if points is not None: for point in points: vertices.append(np.array(point)) shape = hull_from_points(vertices) return shape def tess_hull(shapes, sl_tol=.5, sl_angTol=1): # print('hull_from_shapes()') vertices = [] solids = [] for wp in shapes: for item in wp.solids().objects: solids.append(item) for shape in solids: verts = shape.tessellate(sl_tol, sl_angTol)[0] for vert in verts: vertices.append(np.array(vert.toTuple())) shape = hull_from_points(vertices) return shape def single_plate(cylinder_segments=100, side="right"): if plate_style in ['NUB', 'HS_NUB']: top_wall = cq.Workplane("XY").box(mount_width, 1.5, plate_thickness) top_wall = top_wall.translate((0, (1.5 / 2) + (keyswitch_height / 2), plate_thickness / 2)) left_wall = cq.Workplane("XY").box(1.5, mount_height, plate_thickness) left_wall = left_wall.translate(((1.5 / 2) + (keyswitch_width / 2), 0, plate_thickness / 2)) side_nub = cq.Workplane("XY").union(cq.Solid.makeCylinder(radius=1, height=2.75)) side_nub = side_nub.translate((0, 0, -2.75 / 2.0)) side_nub = rotate(side_nub, (90, 0, 0)) side_nub = side_nub.translate((keyswitch_width / 2, 0, 1)) nub_cube = cq.Workplane("XY").box(1.5, 2.75, plate_thickness) nub_cube = nub_cube.translate(((1.5 / 2) + (keyswitch_width / 2), 0, plate_thickness / 2)) side_nub2 = tess_hull(shapes=(side_nub, nub_cube)) side_nub2 = side_nub2.union(side_nub).union(nub_cube) plate_half1 = top_wall.union(left_wall).union(side_nub2) plate_half2 = plate_half1 plate_half2 = mirror(plate_half2, 'XZ') plate_half2 = mirror(plate_half2, 'YZ') plate = plate_half1.union(plate_half2) else: # 'HOLE' or default, square cutout for non-nub designs. plate = cq.Workplane("XY").box(mount_width, mount_height, mount_thickness) plate = plate.translate((0.0, 0.0, mount_thickness / 2.0)) shape_cut = cq.Workplane("XY").box(keyswitch_width, keyswitch_height, mount_thickness * 2) shape_cut = shape_cut.translate((0.0, 0.0, mount_thickness)) plate = plate.cut(shape_cut) if plate_style in ['UNDERCUT', 'HS_UNDERCUT']: undercut = cq.Workplane("XY").box( keyswitch_width + 2 * clip_undercut, keyswitch_height + 2 * clip_undercut, mount_thickness ) undercut = undercut.translate((0.0, 0.0, -clip_thickness + mount_thickness / 2.0)) if undercut_transition > 0: undercut = undercut.faces("+Z").chamfer(undercut_transition, clip_undercut) plate = plate.cut(undercut) if plate_file is not None: socket = cq.Workplane('XY').add(cq.importers.importShape( cq.exporters.ExportTypes.STEP, plate_file)) socket = socket.translate([0, 0, plate_thickness + plate_offset]) plate = plate.union(socket) if side == "left": plate = plate.mirror('YZ') return plate ################ ## SA Keycaps ## ################ sa_length = 18.25 sa_double_length = 37.5 def sa_cap(Usize=1): # MODIFIED TO NOT HAVE THE ROTATION. NEEDS ROTATION DURING ASSEMBLY sa_length = 18.25 bw2 = Usize * sa_length / 2 bl2 = sa_length / 2 m = 0 pw2 = 6 * Usize + 1 pl2 = 6 if Usize == 1: m = 17 / 2 k1 = cq.Workplane('XY').polyline([(bw2, bl2), (bw2, -bl2), (-bw2, -bl2), (-bw2, bl2), (bw2, bl2)]) k1 = cq.Wire.assembleEdges(k1.edges().objects) k1 = cq.Workplane('XY').add(cq.Solid.extrudeLinear(outerWire=k1, innerWires=[], vecNormal=cq.Vector(0, 0, 0.1))) k1 = k1.translate((0, 0, 0.05)) k2 = cq.Workplane('XY').polyline([(pw2, pl2), (pw2, -pl2), (-pw2, -pl2), (-pw2, pl2), (pw2, pl2)]) k2 = cq.Wire.assembleEdges(k2.edges().objects) k2 = cq.Workplane('XY').add(cq.Solid.extrudeLinear(outerWire=k2, innerWires=[], vecNormal=cq.Vector(0, 0, 0.1))) k2 = k2.translate((0, 0, 12.0)) if m > 0: m1 = cq.Workplane('XY').polyline([(m, m), (m, -m), (-m, -m), (-m, m), (m, m)]) m1 = cq.Wire.assembleEdges(m1.edges().objects) m1 = cq.Workplane('XY').add(cq.Solid.extrudeLinear(outerWire=m1, innerWires=[], vecNormal=cq.Vector(0, 0, 0.1))) m1 = m1.translate((0, 0, 6.0)) key_cap = hull_from_shapes((k1, k2, m1)) else: key_cap = hull_from_shapes((k1, k2)) key_cap = key_cap.translate((0, 0, 5 + plate_thickness)) # key_cap = key_cap.color((220 / 255, 163 / 255, 163 / 255, 1)) return key_cap ######################### ## Placement Functions ## ######################### def rotate_around_x(position, angle): # print('rotate_around_x()') t_matrix = np.array( [ [1, 0, 0], [0, np.cos(angle), -np.sin(angle)], [0, np.sin(angle), np.cos(angle)], ] ) return np.matmul(t_matrix, position) def rotate_around_y(position, angle): # print('rotate_around_y()') t_matrix = np.array( [ [np.cos(angle), 0, np.sin(angle)], [0, 1, 0], [-np.sin(angle), 0, np.cos(angle)], ] ) return np.matmul(t_matrix, position) cap_top_height = plate_thickness + sa_profile_key_height row_radius = ((mount_height + extra_height) / 2) / (np.sin(alpha / 2)) + cap_top_height column_radius = ( ((mount_width + extra_width) / 2) / (np.sin(beta / 2)) ) + cap_top_height column_x_delta = -1 - column_radius * np.sin(beta) column_base_angle = beta * (centercol - 2) def apply_key_geometry( shape, translate_fn, rotate_x_fn, rotate_y_fn, column, row, column_style=column_style, ): print('apply_key_geometry()') column_angle = beta * (centercol - column) if column_style == "orthographic": column_z_delta = column_radius * (1 - np.cos(column_angle)) shape = translate_fn(shape, [0, 0, -row_radius]) shape = rotate_x_fn(shape, alpha * (centerrow - row)) shape = translate_fn(shape, [0, 0, row_radius]) shape = rotate_y_fn(shape, column_angle) shape = translate_fn( shape, [-(column - centercol) * column_x_delta, 0, column_z_delta] ) shape = translate_fn(shape, column_offset(column)) elif column_style == "fixed": shape = rotate_y_fn(shape, fixed_angles[column]) shape = translate_fn(shape, [fixed_x[column], 0, fixed_z[column]]) shape = translate_fn(shape, [0, 0, -(row_radius + fixed_z[column])]) shape = rotate_x_fn(shape, alpha * (centerrow - row)) shape = translate_fn(shape, [0, 0, row_radius + fixed_z[column]]) shape = rotate_y_fn(shape, fixed_tenting) shape = translate_fn(shape, [0, column_offset(column)[1], 0]) else: shape = translate_fn(shape, [0, 0, -row_radius]) shape = rotate_x_fn(shape, alpha * (centerrow - row)) shape = translate_fn(shape, [0, 0, row_radius]) shape = translate_fn(shape, [0, 0, -column_radius]) shape = rotate_y_fn(shape, column_angle) shape = translate_fn(shape, [0, 0, column_radius]) shape = translate_fn(shape, column_offset(column)) shape = rotate_y_fn(shape, tenting_angle) shape = translate_fn(shape, [0, 0, keyboard_z_offset]) return shape def x_rot(shape, angle): # print('x_rot()') return rotate(shape, [rad2deg(angle), 0, 0]) def y_rot(shape, angle): # print('y_rot()') return rotate(shape, [0, rad2deg(angle), 0]) def key_place(shape, column, row): print('key_place()') return apply_key_geometry(shape, translate, x_rot, y_rot, column, row) def add_translate(shape, xyz): print('add_translate()') vals = [] for i in range(len(shape)): vals.append(shape[i] + xyz[i]) return vals def key_position(position, column, row): print('key_position()') return apply_key_geometry( position, add_translate, rotate_around_x, rotate_around_y, column, row ) def key_holes(side="right"): print('key_holes()') # hole = single_plate() holes = [] for column in range(ncols): for row in range(nrows): if (column in [2, 3]) or (not row == lastrow): holes.append(key_place(single_plate(side=side), column, row)) shape = union(holes) return shape def caps(): caps = None for column in range(ncols): for row in range(nrows): if (column in [2, 3]) or (not row == lastrow): if caps is None: caps = key_place(sa_cap(), column, row) else: caps = caps.add(key_place(sa_cap(), column, row)) return caps #################### ## Web Connectors ## #################### web_thickness = 4.0 post_size = 0.2 post_adj = post_size / 2 # post_adj = 0 def web_post(): print('web_post()') post = cq.Workplane("XY").box(post_size, post_size, web_thickness) post = post.translate((0, 0, plate_thickness - (web_thickness / 2))) return post def web_post_tr(): # print('web_post_tr()') return web_post().translate(((mount_width / 2) - post_adj, (mount_height / 2) - post_adj, 0)) def web_post_tl(): # print('web_post_tl()') return web_post().translate((-(mount_width / 2) + post_adj, (mount_height / 2) - post_adj, 0)) def web_post_bl(): # print('web_post_bl()') return web_post().translate((-(mount_width / 2) + post_adj, -(mount_height / 2) + post_adj, 0)) def web_post_br(): # print('web_post_br()') return web_post().translate(((mount_width / 2) - post_adj, -(mount_height / 2) + post_adj, 0)) def triangle_hulls(shapes): print('triangle_hulls()') hulls = [cq.Workplane('XY')] for i in range(len(shapes) - 2): hulls.append(hull_from_shapes(shapes[i: (i + 3)])) return union(hulls) def connectors(): print('connectors()') hulls = [] for column in range(ncols - 1): for row in range(lastrow): # need to consider last_row? # for row in range(nrows): # need to consider last_row? places = [] places.append(key_place(web_post_tl(), column + 1, row)) places.append(key_place(web_post_tr(), column, row)) places.append(key_place(web_post_bl(), column + 1, row)) places.append(key_place(web_post_br(), column, row)) hulls.append(triangle_hulls(places)) for column in range(ncols): # for row in range(nrows-1): for row in range(cornerrow): places = [] places.append(key_place(web_post_bl(), column, row)) places.append(key_place(web_post_br(), column, row)) places.append(key_place(web_post_tl(), column, row + 1)) places.append(key_place(web_post_tr(), column, row + 1)) hulls.append(triangle_hulls(places)) for column in range(ncols - 1): # for row in range(nrows-1): # need to consider last_row? for row in range(cornerrow): # need to consider last_row? places = [] places.append(key_place(web_post_br(), column, row)) places.append(key_place(web_post_tr(), column, row + 1)) places.append(key_place(web_post_bl(), column + 1, row)) places.append(key_place(web_post_tl(), column + 1, row + 1)) hulls.append(triangle_hulls(places)) return union(hulls) ############ ## Thumbs ## ############ def thumborigin(): # print('thumborigin()') origin = key_position([mount_width / 2, -(mount_height / 2), 0], 1, cornerrow) for i in range(len(origin)): origin[i] = origin[i] + thumb_offsets[i] return origin def thumb_tr_place(shape): print('thumb_tr_place()') shape = rotate(shape, [10, -23, 10]) shape = shape.translate(thumborigin()) shape = shape.translate([-12, -16, 3]) return shape def thumb_tl_place(shape): print('thumb_tl_place()') shape = rotate(shape, [10, -23, 10]) shape = shape.translate(thumborigin()) shape = shape.translate([-32, -15, -2]) return shape def thumb_mr_place(shape): print('thumb_mr_place()') shape = rotate(shape, [-6, -34, 48]) shape = shape.translate(thumborigin()) shape = shape.translate([-29, -40, -13]) return shape def thumb_ml_place(shape): print('thumb_ml_place()') shape = rotate(shape, [6, -34, 40]) shape = shape.translate(thumborigin()) shape = shape.translate([-51, -25, -12]) return shape def thumb_br_place(shape): print('thumb_br_place()') shape = rotate(shape, [-16, -33, 54]) shape = shape.translate(thumborigin()) shape = shape.translate([-37.8, -55.3, -25.3]) return shape def thumb_bl_place(shape): print('thumb_bl_place()') shape = rotate(shape, [-4, -35, 52]) shape = shape.translate(thumborigin()) shape = shape.translate([-56.3, -43.3, -23.5]) return shape def thumb_1x_layout(shape, cap=False): print('thumb_1x_layout()') if cap: shapes = thumb_mr_place(shape) shapes = shapes.add(thumb_ml_place(shape)) shapes = shapes.add(thumb_br_place(shape)) shapes = shapes.add(thumb_bl_place(shape)) else: shapes = union( [ thumb_mr_place(shape), thumb_ml_place(shape), thumb_br_place(shape), thumb_bl_place(shape), ] ) return shapes def thumb_15x_layout(shape, cap=False): print('thumb_15x_layout()') if cap: shape = rotate(shape, (0, 0, 90)) return thumb_tr_place(shape).add(thumb_tl_place(shape).solids().objects[0]) else: return thumb_tr_place(shape).union(thumb_tl_place(shape)) def double_plate(): print('double_plate()') plate_height = (sa_double_length - mount_height) / 3 # plate_height = (2*sa_length-mount_height) / 3 top_plate = cq.Workplane("XY").box(mount_width, plate_height, web_thickness) top_plate = translate(top_plate, [0, (plate_height + mount_height) / 2, plate_thickness - (web_thickness / 2)] ) return union((top_plate, mirror(top_plate, 'XZ'))) def thumbcaps(): t1 = thumb_1x_layout(sa_cap(1), cap=True) # t15 = thumb_15x_layout(rotate(sa_cap(1.5), [0, 0, pi / 2]), cap=True) t15 = thumb_15x_layout(sa_cap(1.5), cap=True) return t1.add(t15) def thumb(side="right"): print('thumb()') shape = thumb_1x_layout(rotate(single_plate(side=side), (0, 0, -90))) shape = shape.union(thumb_15x_layout(rotate(single_plate(side=side), (0, 0, -90)))) shape = shape.union(thumb_15x_layout(double_plate())) return shape def thumb_post_tr(): print('thumb_post_tr()') return translate(web_post(), [(mount_width / 2) - post_adj, (mount_height / 1.15) - post_adj, 0] ) def thumb_post_tl(): print('thumb_post_tl()') return translate(web_post(), [-(mount_width / 2) + post_adj, (mount_height / 1.15) - post_adj, 0] ) def thumb_post_bl(): print('thumb_post_bl()') return translate(web_post(), [-(mount_width / 2) + post_adj, -(mount_height / 1.15) + post_adj, 0] ) def thumb_post_br(): print('thumb_post_br()') return translate(web_post(), [(mount_width / 2) - post_adj, -(mount_height / 1.15) + post_adj, 0] ) def thumb_connectors(): print('thumb_connectors()') hulls = [] # Top two hulls.append( triangle_hulls( [ thumb_tl_place(thumb_post_tr()), thumb_tl_place(thumb_post_br()), thumb_tr_place(thumb_post_tl()), thumb_tr_place(thumb_post_bl()), ] ) ) # bottom two on the right hulls.append( triangle_hulls( [ thumb_br_place(web_post_tr()), thumb_br_place(web_post_br()), thumb_mr_place(web_post_tl()), thumb_mr_place(web_post_bl()), ] ) ) # bottom two on the left hulls.append( triangle_hulls( [ thumb_br_place(web_post_tr()), thumb_br_place(web_post_br()), thumb_mr_place(web_post_tl()), thumb_mr_place(web_post_bl()), ] ) ) # centers of the bottom four hulls.append( triangle_hulls( [ thumb_bl_place(web_post_tr()), thumb_bl_place(web_post_br()), thumb_ml_place(web_post_tl()), thumb_ml_place(web_post_bl()), ] ) ) # top two to the middle two, starting on the left hulls.append( triangle_hulls( [ thumb_br_place(web_post_tl()), thumb_bl_place(web_post_bl()), thumb_br_place(web_post_tr()), thumb_bl_place(web_post_br()), thumb_mr_place(web_post_tl()), thumb_ml_place(web_post_bl()), thumb_mr_place(web_post_tr()), thumb_ml_place(web_post_br()), ] ) ) # top two to the main keyboard, starting on the left hulls.append( triangle_hulls( [ thumb_tl_place(thumb_post_tl()), thumb_ml_place(web_post_tr()), thumb_tl_place(thumb_post_bl()), thumb_ml_place(web_post_br()), thumb_tl_place(thumb_post_br()), thumb_mr_place(web_post_tr()), thumb_tr_place(thumb_post_bl()), thumb_mr_place(web_post_br()), thumb_tr_place(thumb_post_br()), ] ) ) hulls.append( triangle_hulls( [ thumb_tl_place(thumb_post_tl()), key_place(web_post_bl(), 0, cornerrow), thumb_tl_place(thumb_post_tr()), key_place(web_post_br(), 0, cornerrow), thumb_tr_place(thumb_post_tl()), key_place(web_post_bl(), 1, cornerrow), thumb_tr_place(thumb_post_tr()), key_place(web_post_br(), 1, cornerrow), key_place(web_post_tl(), 2, lastrow), key_place(web_post_bl(), 2, lastrow), thumb_tr_place(thumb_post_tr()), key_place(web_post_bl(), 2, lastrow), thumb_tr_place(thumb_post_br()), key_place(web_post_br(), 2, lastrow), key_place(web_post_bl(), 3, lastrow), key_place(web_post_tr(), 2, lastrow), key_place(web_post_tl(), 3, lastrow), key_place(web_post_bl(), 3, cornerrow), key_place(web_post_tr(), 3, lastrow), key_place(web_post_br(), 3, cornerrow), key_place(web_post_bl(), 4, cornerrow), ] ) ) hulls.append( triangle_hulls( [ key_place(web_post_br(), 1, cornerrow), key_place(web_post_tl(), 2, lastrow), key_place(web_post_bl(), 2, cornerrow), key_place(web_post_tr(), 2, lastrow), key_place(web_post_br(), 2, cornerrow), key_place(web_post_bl(), 3, cornerrow), ] ) ) hulls.append( triangle_hulls( [ key_place(web_post_tr(), 3, lastrow), key_place(web_post_br(), 3, lastrow), key_place(web_post_tr(), 3, lastrow), key_place(web_post_bl(), 4, cornerrow), ] ) ) return union(hulls) ########## ## Case ## ########## def bottom_hull(p, height=0.001): print("bottom_hull()") shape = None for item in p: # proj = sl.projection()(p) # t_shape = sl.linear_extrude(height=height, twist=0, convexity=0, center=True)( # proj # ) vertices = [] verts = item.faces('= lastrow) if shift_up: position = key_position( list(np.array(wall_locate2(0, 1)) + np.array([0, (mount_height / 2), 0])), column, row, ) elif shift_down: position = key_position( list(np.array(wall_locate2(0, -1)) - np.array([0, (mount_height / 2), 0])), column, row, ) elif shift_left: position = list( np.array(left_key_position(row, 0)) + np.array(wall_locate3(-1, 0)) ) else: position = key_position( list(np.array(wall_locate2(1, 0)) + np.array([(mount_height / 2), 0, 0])), column, row, ) shape = screw_insert_shape(bottom_radius, top_radius, height) shape = shape.translate([position[0], position[1], height / 2]) return shape def screw_insert_all_shapes(bottom_radius, top_radius, height): print('screw_insert_all_shapes()') shape = ( screw_insert(0, 0, bottom_radius, top_radius, height), screw_insert(0, lastrow-1, bottom_radius, top_radius, height), screw_insert(2, lastrow + 0.3, bottom_radius, top_radius, height), screw_insert(3, 0, bottom_radius, top_radius, height), screw_insert(lastcol, 1, bottom_radius, top_radius, height), ) return shape screw_insert_height = 3.8 screw_insert_bottom_radius = 5.31 / 2 screw_insert_top_radius = 5.1 / 2 screw_insert_holes = screw_insert_all_shapes( screw_insert_bottom_radius, screw_insert_top_radius, screw_insert_height ) screw_insert_outers = screw_insert_all_shapes( screw_insert_bottom_radius + 1.6, screw_insert_top_radius + 1.6, screw_insert_height + 1.5, ) screw_insert_screw_holes = screw_insert_all_shapes(1.7, 1.7, 350) wire_post_height = 7 wire_post_overhang = 3.5 wire_post_diameter = 2.6 def wire_post(direction, offset): print('wire_post()') s1 = cq.Workplane("XY").box( wire_post_diameter, wire_post_diameter, wire_post_height ) s1 = translate(s1, [0, -wire_post_diameter * 0.5 * direction, 0]) s2 = cq.Workplane("XY").box( wire_post_diameter, wire_post_overhang, wire_post_diameter ) s2 = translate(s2, [0, -wire_post_overhang * 0.5 * direction, -wire_post_height / 2] ) shape = union((s1, s2)) shape = shape.translate([0, -offset, (-wire_post_height / 2) + 3]) shape = rotate(shape, [-alpha / 2, 0, 0]) shape = shape.translate((3, -mount_height / 2, 0)) return shape def wire_posts(): print('wire_posts()') shape = thumb_ml_place(wire_post(1, 0).translate([-5, 0, -2])) shape = shape.union(thumb_ml_place(wire_post(-1, 6).translate([0, 0, -2.5]))) shape = shape.union(thumb_ml_place(wire_post(1, 0).translate([5, 0, -2]))) for column in range(lastcol): for row in range(lastrow - 1): shape = union([ shape, key_place(wire_post(1, 0).translate([-5, 0, 0]), column, row), key_place(wire_post(-1, 6).translate([0, 0, 0]), column, row), key_place(wire_post(1, 0).translate([5, 0, 0]), column, row), ]) return shape def model_side(side="right"): print('model_right()') shape = cq.Workplane('XY').union(key_holes(side=side)) if debug_exports: cq.exporters.export(w=shape, fname=path.join(r"..", "things", r"debug_key_plates.step"), exportType='STEP') connector_shape = connectors() shape = shape.union(connector_shape) if debug_exports: cq.exporters.export(w=shape, fname=path.join(r"..", "things", r"debug_connector_shape.step"), exportType='STEP') thumb_shape = thumb(side=side) if debug_exports: cq.exporters.export(w=thumb_shape, fname=path.join(r"..", "things", r"debug_thumb_shape.step"), exportType='STEP') shape = shape.union(thumb_shape) thumb_connector_shape = thumb_connectors() shape = shape.union(thumb_connector_shape) if debug_exports: cq.exporters.export(w=shape, fname=path.join(r"..", "things", r"debug_thumb_connector_shape.step"), exportType='STEP') walls_shape = case_walls() if debug_exports: cq.exporters.export(w=walls_shape, fname=path.join(r"..", "things", r"debug_walls_shape.step"), exportType='STEP') s2 = cq.Workplane('XY').union(walls_shape) s2 = union([s2, *screw_insert_outers]) if controller_mount_type in ['RJ9_USB_TEENSY']: s2 = s2.union(teensy_holder()) if controller_mount_type in ['RJ9_USB_TEENSY', 'RJ9_USB_WALL']: s2 = s2.union(usb_holder()) s2 = s2.cut(usb_holder_hole()) if controller_mount_type in ['RJ9_USB_TEENSY', 'RJ9_USB_WALL']: s2 = s2.cut(rj9_space()) if controller_mount_type in ['EXTERNAL']: s2 = s2.cut(external_mount_hole()) s2 = s2.cut(union(screw_insert_holes)) shape = shape.union(s2) # shape = shape.union(s2, tol=.01) if controller_mount_type in ['RJ9_USB_TEENSY', 'RJ9_USB_WALL']: shape = shape.union(rj9_holder()) if oled_mount_type == "UNDERCUT": hole, frame = oled_undercut_mount_frame() shape = shape.cut(hole) shape = union([shape, frame]) elif oled_mount_type == "SLIDING": hole, frame = oled_sliding_mount_frame() shape = shape.cut(hole) shape = union([shape, frame]) elif oled_mount_type == "CLIP": hole, frame = oled_clip_mount_frame() shape = shape.cut(hole) shape = union([shape, frame]) block = cq.Workplane("XY").box(350, 350, 40) block = block.translate((0, 0, -20)) shape = shape.cut(block) if show_caps: shape = shape.add(thumbcaps()) shape = shape.add(caps()) if side == "left": shape = shape.mirror('YZ') return shape def baseplate(): # shape = mod_r shape = union([case_walls(), *screw_insert_outers]) # tool = translate(screw_insert_screw_holes(), [0, 0, -10]) tool = screw_insert_screw_holes for item in tool: item = translate(item, [0, 0, -10]) shape = shape.cut(item) shape = shape.translate((0, 0, -0.01)) square = cq.Workplane('XY').rect(1000, 1000) for wire in square.wires().objects: plane = cq.Workplane('XY').add(cq.Face.makeFromWires(wire)) shape = shape.intersect(plane) outside = shape.vertices(cq.DirectionMinMaxSelector(cq.Vector(1, 0, 0), True)).objects[0] sizes = [] max_val = 0 inner_index = 0 base_wires = shape.wires().objects for i_wire, wire in enumerate(base_wires): is_outside = False for vert in wire.Vertices(): if vert.toTuple() == outside.toTuple(): outer_wire = wire outer_index = i_wire is_outside = True sizes.append(0) if not is_outside: sizes.append(len(wire.Vertices())) if sizes[-1]>max_val: inner_index = i_wire max_val = sizes[-1] print(sizes) inner_wire = base_wires[inner_index] inner_plate = cq.Workplane('XY').add(cq.Face.makeFromWires(inner_wire)) shape.add(inner_plate) holes = [] for i in range(len(base_wires)): if i not in [inner_index, outer_index]: holes.append(base_wires[i]) shape = cq.Workplane('XY').add(cq.Solid.extrudeLinear(outer_wire, holes, cq.Vector(0, 0, -5))) return shape base = baseplate() cq.exporters.export(w=base, fname=path.join(r"..", "things", save_dir, config_name + r"_plate.step"), exportType='STEP') cq.exporters.export(w=base, fname=path.join(r"..", "things", save_dir, config_name + r"_plate.dxf"), exportType='DXF') """ mod_r = model_side(side="right") cq.exporters.export(w=mod_r, fname=path.join(r"..", "things", save_dir, config_name + r"_right.step"), exportType='STEP') if symmetry == "asymmetric": mod_l = model_side(side="left") cq.exporters.export(w=mod_l, fname=path.join(r"..", "things", save_dir, config_name + r"_left.step"), exportType='STEP') else: cq.exporters.export(w=mod_r.mirror('YZ'), fname=path.join(r"..", "things", save_dir, config_name + r"_left.step"), exportType='STEP') base = baseplate() cq.exporters.export(w=base, fname=path.join(r"..", "things", save_dir, config_name + r"_plate.step"), exportType='STEP') cq.exporters.export(w=base, fname=path.join(r"..", "things", save_dir, config_name + r"_plate.dxf"), exportType='DXF') if oled_mount_type == 'UNDERCUT': cq.exporters.export(w=oled_undercut_mount_frame()[1], fname=path.join(r"..", "things", save_dir, config_name + r"_oled_undercut_test.step"), exportType='STEP') if oled_mount_type == 'SLIDING': cq.exporters.export(w=oled_sliding_mount_frame()[1], fname=path.join(r"..", "things", save_dir, config_name + r"_oled_sliding_test.step"), exportType='STEP') if oled_mount_type == 'CLIP': oled_mount_location_xyz = (0.0, 0.0, -oled_mount_depth / 2) oled_mount_rotation_xyz = (0.0, 0.0, 0.0) cq.exporters.export(w=oled_clip(), fname=path.join(r"..", "things", save_dir, config_name + r"_oled_clip.step"), exportType='STEP') cq.exporters.export(w=oled_clip_mount_frame()[1], fname=path.join(r"..", "things", save_dir, config_name + r"_oled_clip_test.step"), exportType='STEP') cq.exporters.export(w=union((oled_clip_mount_frame()[1], oled_clip())), fname=path.join(r"..", "things", save_dir, config_name + r"_oled_clip_assy_test.step"), exportType='STEP') """