From a47aa730ac9119a896019d45c0cec1b22f07a306 Mon Sep 17 00:00:00 2001 From: joshreve Date: Mon, 26 Oct 2020 16:53:46 -0400 Subject: Added cadquery form of dactyl-manuform. Code is double translated (Clojure->Python, OpenSCAD->OpenCASCADE) and a mess but produces a .step file version of the dactyl-manuform design. --- src/dactyl_manuform.py | 12 +- src/dactyl_manuform_cadquery.py | 1531 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 1537 insertions(+), 6 deletions(-) create mode 100644 src/dactyl_manuform_cadquery.py (limited to 'src') diff --git a/src/dactyl_manuform.py b/src/dactyl_manuform.py index 95bd192..87f2fde 100644 --- a/src/dactyl_manuform.py +++ b/src/dactyl_manuform.py @@ -17,7 +17,7 @@ def rad2deg(rad: float) -> float: # ###################### -nrows = 5 # key rows +nrows = 6 # key rows ncols = 6 # key columns alpha = pi / 12.0 # curvature of the columns @@ -95,20 +95,20 @@ plate_offset = 0.0 def single_plate(cylinder_segments=100): top_wall = sl.cube([keyswitch_width + 3, 1.5, plate_thickness], center=True) top_wall = sl.translate( - [0, (1.5 / 2) + (keyswitch_height / 2), plate_thickness / 2] + (0, (1.5 / 2) + (keyswitch_height / 2), plate_thickness / 2) )(top_wall) left_wall = sl.cube([1.5, keyswitch_height + 3, plate_thickness], center=True) left_wall = sl.translate( - [(1.5 / 2) + (keyswitch_width / 2), 0, plate_thickness / 2] + ((1.5 / 2) + (keyswitch_width / 2), 0, plate_thickness / 2) )(left_wall) side_nub = sl.cylinder(1, 2.75, segments=cylinder_segments, center=True) side_nub = sl.rotate(rad2deg(pi / 2), [1, 0, 0])(side_nub) - side_nub = sl.translate([keyswitch_width / 2, 0, 1])(side_nub) + side_nub = sl.translate((keyswitch_width / 2, 0, 1))(side_nub) nub_cube = sl.cube([1.5, 2.75, plate_thickness], center=True) nub_cube = sl.translate( - [(1.5 / 2) + (keyswitch_width / 2), 0, plate_thickness / 2] + ((1.5 / 2) + (keyswitch_width / 2), 0, plate_thickness / 2) )(nub_cube) side_nub = sl.hull()(side_nub, nub_cube) @@ -1170,7 +1170,7 @@ def screw_insert(column, row, bottom_radius, top_radius, height): ) shape = screw_insert_shape(bottom_radius, top_radius, height) - shape = sl.translate([position[0], position[1], height / 2])(shape) + shape = sl.translate((position[0], position[1], height / 2))(shape) return shape diff --git a/src/dactyl_manuform_cadquery.py b/src/dactyl_manuform_cadquery.py new file mode 100644 index 0000000..7daf6fb --- /dev/null +++ b/src/dactyl_manuform_cadquery.py @@ -0,0 +1,1531 @@ +import cadquery as cq + +import numpy as np +from numpy import pi +import os.path as path + +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 + + +# ###################### +# ## Shape parameters ## +# ###################### + +show_caps = True + +nrows = 5 # key rows +ncols = 6 # key columns + +alpha = pi / 12.0 # curvature of the columns +beta = pi / 36.0 # curvature of the rows +centerrow = nrows - 3 # controls front_back tilt +centercol = 3 # controls left_right tilt / tenting (higher number is more tenting) +tenting_angle = pi / 12.0 # or, change this for more precise tenting control + +if nrows > 5: + column_style = "orthographic" +else: + column_style = "standard" # options include :standard, :orthographic, and :fixed + + +# 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 column_offset(column: int) -> list: + # print('column_offset()') + if column == 2: + return [0, 2.82, -4.5] + elif column >= 4: + return [0, -12, 5.64] # original [0 -5.8 5.64] + else: + return [0, 0, 0] + + +thumb_offsets = [6, -3, 7] +keyboard_z_offset = ( + 9 # controls overall height# original=9 with centercol=3# use 16 for centercol=2 +) + +extra_width = 2.5 # extra space between the base of keys# original= 2 +extra_height = 1.0 # original= 0.5 + +wall_z_offset = -15 # length of the first downward_sloping part of the wall (negative) +wall_xy_offset = 5 # offset in the x and/or y direction for the first downward_sloping part of the wall (negative) +wall_thickness = 2 # wall thickness parameter# originally 5 + +## Settings for column_style == :fixed +## The defaults roughly match Maltron settings +## http://patentimages.storage.googleapis.com/EP0219944A2/imgf0002.png +## fixed_z overrides the z portion of the column ofsets above. +## NOTE: THIS DOESN'T WORK QUITE LIKE I'D HOPED. +fixed_angles = [deg2rad(10), deg2rad(10), 0, 0, 0, deg2rad(-15), deg2rad(-15)] +fixed_x = [-41.5, -22.5, 0, 20.3, 41.4, 65.5, 89.6] # relative to the middle finger +fixed_z = [12.1, 8.3, 0, 5, 10.7, 14.5, 17.5] +fixed_tenting = deg2rad(0) + +####################### +## General variables ## +####################### + +lastrow = nrows - 1 +cornerrow = lastrow - 1 +lastcol = ncols - 1 + +################# +## Switch Hole ## +################# + +keyswitch_height = 14.4 ## Was 14.1, then 14.25 +keyswitch_width = 14.4 + +sa_profile_key_height = 12.7 + +plate_thickness = 4 +mount_width = keyswitch_width + 3 +mount_height = keyswitch_height + 3 +mount_thickness = plate_thickness + +SWITCH_WIDTH = 14 +SWITCH_HEIGHT = 14 +CLIP_THICKNESS = 1.4 +CLIP_UNDERCUT = 1.0 +UNDERCUT_TRANSITION = .2 + + +def single_plate1(): + # print('single_plate()') + + shape = cq.Workplane("XY").box(mount_width, mount_height, mount_thickness) + shape = shape.translate((0.0, 0.0, -mount_thickness / 2.0)) + + shape_cut = cq.Workplane("XY").box(SWITCH_WIDTH, SWITCH_HEIGHT, mount_thickness * 2) + shape_cut = shape_cut.translate((0.0, 0.0, -mount_thickness)) + + shape = shape.cut(shape_cut) + + undercut = cq.Workplane("XY").box( + SWITCH_WIDTH + 2 * CLIP_UNDERCUT, + SWITCH_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) + + shape = shape.cut(undercut) + + shape = shape.translate((0, 0, plate_thickness)) + + return shape + + +def single_plate(cylinder_segments=100): + top_wall = cq.Workplane("XY").box(keyswitch_width + 3, 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, keyswitch_height + 3, 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) + + 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(): + 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(), 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 = 3.5 + .5 +post_size = 0.1 + + +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 + + +post_adj = post_size / 2 + + +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(): + print('thumb()') + shape = thumb_1x_layout(single_plate()) + shape = shape.union(thumb_15x_layout(single_plate())) + 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, 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_right(): + print('model_right()') + shape = cq.Workplane('XY').union(key_holes()) + shape = shape.union(connectors()) + shape = shape.union(thumb()) + shape = shape.union(thumb_connectors()) + s2 = cq.Workplane('XY').union(case_walls()) + s2 = union([s2, *screw_insert_outers]) + # s2 = s2.union(teensy_holder()) + s2 = s2.union(usb_holder()) + + s2 = s2.cut(rj9_space()) + s2 = s2.cut(usb_holder_hole()) + s2 = s2.cut(union(screw_insert_holes)) + + shape = shape.union(rj9_holder()) + shape = shape.union(s2, tol=.01) + # shape = shape.union(wire_posts()) + 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()) + + return shape + + +mod_r = model_right() + +cq.exporters.export(w=mod_r, fname=path.join(r"..", "things", r"right_og_py.step"), exportType='STEP') + +cq.exporters.export(w=mod_r.mirror('YZ'), fname=path.join(r"..", "things", r"left_og_py.step"), exportType='STEP') + + +def baseplate(): + shape = mod_r + + shape = shape.translate((0, 0, -0.1)) + + 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) + + return shape + + +base = baseplate() + +cq.exporters.export(w=base, fname=path.join(r"..", "things", r"plate_og_py.step"), exportType='STEP') +cq.exporters.export(w=base, fname=path.join(r"..", "things", r"plate_og_py.dxf"), exportType='DXF') -- cgit v1.2.3