""" Generate Dactyl case """ from math import radians, sin from lib import * from shapes import * import lib import mat SWITCH_THICKNESS = 1.3 SWITCH_TOP_THICKNESS = 2.8 WEB_THICKNESS = 3 SWITCH_RIM_THICKNESS = 2 POST_WIDTH = 0.5 POST_RAD = POST_WIDTH / 2 KEYHOLE_SIZE = 13.8 KEYSWITCH_HEIGHT = 15 KEYSWITCH_WIDTH = 15 PLATE_OUTER_WIDTH = KEYHOLE_SIZE + SWITCH_RIM_THICKNESS * 2 MAX_NUM_ROWS = 4 NUM_COLS = 6 NUM_PINKY_COLUMNS = 2 cols_with_max_rows = [2, 3] SA_PROFILE_KEY_HEIGHT = 2 CAP_TOP_HEIGHT = SWITCH_TOP_THICKNESS + SA_PROFILE_KEY_HEIGHT # extra space between the base of keys EXTRA_HEIGHT = 3 EXTRA_WIDTH = 3 MOUNT_HEIGHT = KEYSWITCH_HEIGHT MOUNT_WIDTH = KEYSWITCH_WIDTH # use 10 for faster prototyping, 15 for real TENTING_ANGLE = 11.0 Z_OFFSET = 9.0 SHOULD_INCLUDE_RISERS = False def is_pinky(col): return col >= NUM_COLS - NUM_PINKY_COLUMNS # aka: alpha def row_curve_deg(col): # I tried to have a different curve for pinkys, but it didn't work well return 17 # aka: beta col_curve_deg = 4.0 column_radius = CAP_TOP_HEIGHT + ((MOUNT_WIDTH + EXTRA_WIDTH) / 2) / sin( radians(col_curve_deg) / 2 ) def row_radius(col): return CAP_TOP_HEIGHT + ((MOUNT_HEIGHT + EXTRA_HEIGHT) / 2) / sin( radians(row_curve_deg(col)) / 2 ) # default 3 # controls left-right tilt / tenting (higher number is more tenting) center_col = 3 center_row = 1 def column_extra_transform(col): if is_pinky(col): # TODO: translate first? return compose(rotate_x(6), rotate_y(-3)) else: return identity() def num_rows_for_col(col): if col in cols_with_max_rows: return MAX_NUM_ROWS else: return MAX_NUM_ROWS - 1 def does_coord_exist(row, col): return col >= 0 and col < NUM_COLS and row >= 0 and row < num_rows_for_col(col) def negative(vect): return [-x for x in vect] def bottom_transform(height): return extrude_linear(height)(project()) def bottom_hull(shape): return hull(shape, bottom_transform(0.1)(shape)) def single_switch_fn(): outer_width = KEYHOLE_SIZE + SWITCH_RIM_THICKNESS * 2 bottom_wall = cube(outer_width, SWITCH_RIM_THICKNESS, SWITCH_THICKNESS) top_wall = translate(0, KEYHOLE_SIZE + SWITCH_RIM_THICKNESS, 0)(bottom_wall) left_wall = cube(SWITCH_RIM_THICKNESS, outer_width, SWITCH_THICKNESS) right_wall = translate(KEYHOLE_SIZE + SWITCH_RIM_THICKNESS, 0, 0)(left_wall) nub_len = 2.75 nub_cyl = translate(0, 0, -1)(rotate_x(90)(cylinder(1, nub_len, 30, center=True))) # nub_cube = translate(-SWITCH_RIM_THICKNESS / 2, 0, 0.)(cube(SWITCH_RIM_THICKNESS, nub_len, 4, center=True)) # left_nub = translate(SWITCH_RIM_THICKNESS, (outer_width) / 2, 0)(hull(nub_cyl, nub_cube)) # right_nub_cube = translate(SWITCH_RIM_THICKNESS / 2, 0, 0)(cube(SWITCH_RIM_THICKNESS, nub_len, 4, center=True)) # right_nub = translate(-SWITCH_RIM_THICKNESS + outer_width, (outer_width) / 2, 0)(hull(nub_cyl, right_nub_cube)) return translate(-outer_width / 2, -outer_width / 2, 0)( union( bottom_wall, top_wall, left_wall, right_wall, # left_nub, # right_nub, ) ) single_switch = single_switch_fn() filled_switch = translate(0, 0, SWITCH_THICKNESS / 2.0)( cube(PLATE_OUTER_WIDTH, PLATE_OUTER_WIDTH, SWITCH_THICKNESS, center=True) ) sa_length = 16.5 sa_width = 17 sa_height = 16.5 sa_base_height = 1.5 sa_extra_height = 2 sa_double_length = 37.5 def sa_cap_fn(): # bl2 = sa_length / 2 m = 20.0 bot = square(sa_width, sa_height, center=True) bot = extrude_linear(0.1)(bot) mid = square(sa_width, sa_height, center=True) mid = extrude_linear(0.1)(mid) mid = translate(0, 0, sa_base_height)(mid) top = square(14, 14, center=True) top = extrude_linear(0.1)(top) top = translate(0, 0, sa_extra_height)(top) return colour(220, 163, 163, 1)( translate(0, 0, SWITCH_TOP_THICKNESS + 1.5)(hull(bot, mid, top)) ) sa_cap = sa_cap_fn() def row_cols(): for row in range(MAX_NUM_ROWS): for col in range(NUM_COLS): if row < num_rows_for_col(col): yield (row, col) def all_of_shape(shape): return [grid_position(row, col, shape) for (row, col) in row_cols()] web_post = translate(-POST_RAD, -POST_RAD, SWITCH_THICKNESS - WEB_THICKNESS)( cube(POST_WIDTH, POST_WIDTH, WEB_THICKNESS) ) short_web_post = translate(-POST_RAD, -POST_RAD, 0)( cube(POST_WIDTH, POST_WIDTH, POST_WIDTH) ) SQUARE_OFFSET_IDXS = [ [[-1, -1], [1, -1]], [[-1, 1], [1, 1]], ] def square_apply(square, fn): return [[fn(x) for x in y] for y in square] def square_translater_at_offset(offset): def fn(idx): return translate(idx[0] * offset, idx[1] * offset, 0) return square_apply(SQUARE_OFFSET_IDXS, fn) def apply_translate_square(square, shape): def fn(translator): return translator(shape) return square_apply(square, fn) outer_post_delta = KEYHOLE_SIZE / 2 + SWITCH_RIM_THICKNESS # - POST_RAD/2 outer_post_translate_square = square_translater_at_offset(outer_post_delta) web_post_tr = translate(outer_post_delta, outer_post_delta, 0)(web_post) web_post_tl = translate(-outer_post_delta, outer_post_delta, 0)(web_post) web_post_br = translate(outer_post_delta, -outer_post_delta, 0)(web_post) web_post_bl = translate(-outer_post_delta, -outer_post_delta, 0)(web_post) web_posts = apply_translate_square(outer_post_translate_square, web_post) short_web_posts = apply_translate_square(outer_post_translate_square, short_web_post) inner_post_delta = KEYHOLE_SIZE / 2 + POST_RAD inner_post_translate_square = square_translater_at_offset(inner_post_delta) inner_web_posts = apply_translate_square(inner_post_translate_square, web_post) short_inner_web_posts = apply_translate_square( inner_post_translate_square, short_web_post ) post_left_edge_indexes = [[0, 0], [1, 0]] post_top_edge_indexes = [[1, 0], [1, 1]] post_right_edge_indexes = [[1, 1], [0, 1]] post_bot_edge_indexes = [[0, 1], [0, 0]] square_idx_tl = [1, 0] square_idx_tr = [1, 1] square_idx_bl = [0, 0] square_idx_br = [0, 1] def get_web_post(idx): return web_posts[idx[0]][idx[1]] post_left_edge = [get_web_post(e) for e in post_left_edge_indexes] post_top_edge = [get_web_post(e) for e in post_top_edge_indexes] post_right_edge = [get_web_post(e) for e in post_right_edge_indexes] post_bot_edge = [get_web_post(e) for e in post_bot_edge_indexes] def get_in_square(square, idx): return square[idx[0]][idx[1]] def get_inner_web_post(idx): return get_in_square(inner_web_posts, idx) def get_short_web_post(idx): return get_in_square(short_web_posts, idx) def get_short_inner_web_post(idx): return get_in_square(short_inner_web_posts, idx) def column_offset(col): if col == 2: return [1, 5, -3] elif col == 3: return [3, 0, -0.5] elif is_pinky(col): return [7.0, -14.5, 5.0] else: return [0, 0, 0] def col_z_rotate(col): if is_pinky(col): return -3.0 else: return 0 def place_on_grid_base(row, column, domain): column_angle = col_curve_deg * (center_col - column) row_angle = row_curve_deg(column) * (center_row - row) return domain.compose( # Row Sphere domain.translate(0, 0, -row_radius(column)), domain.rotate_x(row_angle), domain.translate(0, 0, row_radius(column)), # Row Offset # column_extra_transform(column), # Col sphere domain.translate(0, 0, -column_radius), domain.rotate_y(column_angle), domain.translate(0, 0, column_radius), # Z Fix domain.rotate_z(col_z_rotate(column)), # Column offset domain.translate(*column_offset(column)), # Misc domain.rotate_y(TENTING_ANGLE), domain.translate(0, 0, Z_OFFSET), ) def place_on_grid(row, column): return place_on_grid_base(row, column, lib) def point_on_grid(row, column, x, y, z): point = mat.point3(x, y, z) tx_point = place_on_grid_base(row, column, mat) @ point return tx_point[:3] def grid_position(row, col, shape): return place_on_grid(row, col)(shape) def connectors(): def make_edge_connection(r1, c1, e1, r2, c2, e2): posts1 = [grid_position(r1, c1, e) for e in e1] posts2 = [grid_position(r2, c2, e) for e in e2] return hull(*posts1, *posts2) all_connectors = [] for col in range(NUM_COLS - 1): for row in range(MAX_NUM_ROWS): if does_coord_exist(row, col) and does_coord_exist(row, col + 1): if (row, col) == (0, 3): right_edge = [ translate(1, 1, 0)(web_post_tr), translate(0, 1, 0)(web_post_tr), web_post_br, ] else: right_edge = post_right_edge all_connectors.append( make_edge_connection( row, col, right_edge, row, col + 1, post_left_edge ) ) for col in range(NUM_COLS): for row in range(MAX_NUM_ROWS - 1): if does_coord_exist(row, col) and does_coord_exist(row + 1, col): all_connectors.append( make_edge_connection( row, col, post_bot_edge, row + 1, col, post_top_edge ) ) for col in range(NUM_COLS - 1): row = num_rows_for_col(col) - 1 next_col = col + 1 next_row = num_rows_for_col(next_col) - 1 if next_row == row + 1: this_t = place_on_grid(row, col) next_t = place_on_grid(next_row, next_col) this_level_t = place_on_grid(row, next_col) all_connectors.append( hull( this_t(web_post_br), this_level_t(web_post_bl), next_t(web_post_tl), ) ) all_connectors.append( hull(this_t(web_post_br), next_t(web_post_tl), next_t(web_post_bl),) ) if next_row == row - 1: this_t = place_on_grid(row, col) next_t = place_on_grid(next_row, next_col) this_level_t = place_on_grid(next_row, col) all_connectors.append( hull( this_t(web_post_tr), this_level_t(web_post_br), next_t(web_post_bl), ) ) all_connectors.append( hull(this_t(web_post_tr), this_t(web_post_br), next_t(web_post_bl),) ) def does_diag_exist(row, col): for dr in [0, 1]: for dc in [0, 1]: if not does_coord_exist(row + dr, col + dc): return False return True for col in range(NUM_COLS - 1): for row in range(MAX_NUM_ROWS - 1): if does_diag_exist(row, col): p1 = grid_position(row, col, web_post_br) p2 = grid_position(row + 1, col, web_post_tr) p3 = grid_position(row + 1, col + 1, web_post_tl) p4 = grid_position(row, col + 1, web_post_bl) all_connectors.append(hull(p1, p2, p3, p4)) return union(*all_connectors) SWITCH_RISER_OFFSET = 9.8 + SWITCH_RISER_RADIUS switch_riser_offset_square = square_translater_at_offset(SWITCH_RISER_OFFSET) def get_riser_is_connector(rc1, idx1, rc2, idx2): if rc1 == [0, 3] and rc2 == [0, 4]: return False return True def get_riser_offset_delta(row_col, idx): if row_col == [0, 1] and idx == square_idx_tr: return [-2.5, 0] if row_col == [0, 3] and idx == square_idx_tl: return [2.5, 0] if row_col == [0, 3] and idx == square_idx_tr: return [-0.5, 0] if row_col == [0, 4] and idx == square_idx_tl: return [2.5, 0] if row_col == [2, 4] and idx == square_idx_bl: return [2.5, 0] if row_col == [3, 2] and idx == square_idx_br: return [-3.5, 1] if row_col == [2, 1] and idx == square_idx_br: return [-3, 0] else: return None def wall_connect(row1, col1, idx1, row2, col2, idx2, **kwargs): place_fn1 = place_on_grid(row1, col1) place_fn2 = place_on_grid(row2, col2) delta1 = get_riser_offset_delta([row1, col1], idx1) delta2 = get_riser_offset_delta([row2, col2], idx2) connectors = get_riser_is_connector([row1, col1], idx1, [row2, col2], idx2) return wall_connect_from_placer( place_fn1, idx1, place_fn2, idx2, delta1=delta1, delta2=delta2, connectors=connectors, **kwargs, ) def make_offsetter(idx, delta): offsetter = get_in_square(switch_riser_offset_square, idx) if delta: z = 0 if len(delta) == 3: z = delta[2] offsetter = translate(delta[0], delta[1], z)(offsetter) return offsetter def wall_connect_from_placer( place_fn1, idx1, place_fn2, idx2, *, delta1=None, delta2=None, connectors=True, walls=True ): offsetter1 = make_offsetter(idx1, delta1) offsetter2 = make_offsetter(idx2, delta2) post1 = offsetter1(switch_riser_post) post2 = offsetter2(switch_riser_post) shapes = [] if SHOULD_INCLUDE_RISERS: shapes.append(hull(place_fn1(post1), place_fn2(post2))) if connectors: shapes.append( hull( place_fn1(union(offsetter1(web_post), get_in_square(web_posts, idx1))), place_fn2(union(offsetter2(web_post), get_in_square(web_posts, idx2))), ) ) if walls: shapes.append( bottom_hull( hull( place_fn1(offsetter1(switch_riser_raw_dot)), place_fn2(offsetter2(switch_riser_raw_dot)), ) ) ) return union(*shapes) def case_walls(): all_shapes = [] # Top wall for col in range(0, NUM_COLS): all_shapes.append(wall_connect(0, col, square_idx_tl, 0, col, square_idx_tr)) for col in range(0, NUM_COLS - 1): all_shapes.append( wall_connect(0, col, square_idx_tr, 0, col + 1, square_idx_tl) ) # Right wall max_col = NUM_COLS - 1 for row in range(0, num_rows_for_col(max_col)): all_shapes.append( wall_connect(row, max_col, square_idx_tr, row, max_col, square_idx_br) ) for row in range(0, num_rows_for_col(max_col) - 1): all_shapes.append( wall_connect(row, max_col, square_idx_br, row + 1, max_col, square_idx_tr) ) # Left wall for row in range(0, num_rows_for_col(0)): all_shapes.append(wall_connect(row, 0, square_idx_tl, row, 0, square_idx_bl)) for row in range(0, num_rows_for_col(0) - 1): all_shapes.append( wall_connect(row, 0, square_idx_bl, row + 1, 0, square_idx_tl) ) # Bottom wall def include_wall(col): return col >= 2 for col in range(0, NUM_COLS): all_shapes.append( wall_connect( num_rows_for_col(col) - 1, col, square_idx_bl, num_rows_for_col(col) - 1, col, square_idx_br, walls=include_wall(col), ) ) for col in range(0, NUM_COLS - 1): all_shapes.append( wall_connect( num_rows_for_col(col) - 1, col, square_idx_br, num_rows_for_col(col + 1) - 1, col + 1, square_idx_bl, walls=include_wall(col), ) ) return union(*all_shapes) def all_switches(): return union(*all_of_shape(single_switch)) def filled_switches(): return union(*all_of_shape(filled_switch)) def all_caps(): return union(*all_of_shape(sa_cap)) def post_test(): return union(*[y for x in short_web_posts for y in x],) thumb_basic_postition = point_on_grid(num_rows_for_col(1), 1, 0, 0, 0) thumb_offsets = [16, 4.8, -2.5] # thumb_offsets = [16, 7, -5] thumb_position = [sum(x) for x in zip(thumb_basic_postition, thumb_offsets)] def thumb_placer(rot, move): return compose( rotate_x(rot[0]), rotate_y(rot[1]), rotate_z(rot[2]), translate(*move), rotate_z(-10), translate(*thumb_position), ) thumb_r_placer = thumb_placer([14, -26, 12], [-17.4, -11.8, -4.2]) thumb_m_placer = thumb_placer([8, -17, 22], [-36, -17.6, -12]) thumb_l_placer = thumb_placer([6, -5, 30], [-54, -26.5, -16]) thumb_bl_placer = thumb_placer([4, -10, 27], [-35.3, -38.2, -18]) thumb_br_placer = thumb_placer([4, -26, 18], [-15.8, -30.7, -11.6]) thumb_placement_fns = [ thumb_r_placer, thumb_m_placer, thumb_l_placer, thumb_br_placer, thumb_bl_placer, ] def thumbs_post_offsets(placer, post): if placer == thumb_br_placer: if post == square_idx_bl: return [3, 0, 0] if post == square_idx_tr: return [0, -3.8, 0] if placer == thumb_r_placer: if post == square_idx_bl: return [20, 0, 0] if post == square_idx_tl: return [4, 0, 0] if placer == thumb_bl_placer: if post == square_idx_br: return [-3, 0, 0] if post == square_idx_tl: return [0, -3, 0] if placer == thumb_l_placer: if post == square_idx_br: return [-10.8, 0, -0.5] if placer == thumb_m_placer: if post == square_idx_tr: return [-2, 0, 0] return None def thumb_wall(place_fn1, idx1, place_fn2, idx2, **kwargs): d1 = thumbs_post_offsets(place_fn1, idx1) d2 = thumbs_post_offsets(place_fn2, idx2) return wall_connect_from_placer( place_fn1, idx1, place_fn2, idx2, delta1=d1, delta2=d2, **kwargs ) def get_offset_thumb_placer(placer, idx, shape): delta = thumbs_post_offsets(placer, idx) return placer(make_offsetter(idx, delta)(shape)) def thumb_walls(): return union( thumb_wall(thumb_bl_placer, square_idx_bl, thumb_bl_placer, square_idx_br), thumb_wall(thumb_bl_placer, square_idx_br, thumb_br_placer, square_idx_bl), thumb_wall(thumb_br_placer, square_idx_bl, thumb_br_placer, square_idx_br), thumb_wall(thumb_bl_placer, square_idx_bl, thumb_bl_placer, square_idx_tl), thumb_wall( thumb_bl_placer, square_idx_tl, thumb_l_placer, square_idx_br, connectors=False, ), thumb_wall( thumb_l_placer, square_idx_br, thumb_l_placer, square_idx_bl, connectors=False, ), thumb_wall(thumb_l_placer, square_idx_bl, thumb_l_placer, square_idx_tl), thumb_wall(thumb_l_placer, square_idx_tl, thumb_l_placer, square_idx_tr), thumb_wall(thumb_l_placer, square_idx_tr, thumb_m_placer, square_idx_tl), thumb_wall( thumb_m_placer, square_idx_tl, thumb_m_placer, square_idx_tr, walls=False ), thumb_wall( thumb_m_placer, square_idx_tr, thumb_r_placer, square_idx_tl, walls=False ), thumb_wall( thumb_r_placer, square_idx_tl, thumb_r_placer, square_idx_tr, walls=False ), thumb_wall( thumb_r_placer, square_idx_tr, thumb_r_placer, square_idx_br, walls=False ), thumb_wall(thumb_br_placer, square_idx_br, thumb_br_placer, square_idx_tr), thumb_wall( thumb_r_placer, square_idx_bl, thumb_r_placer, square_idx_br, walls=False, connectors=False, ), hull( get_offset_thumb_placer(thumb_br_placer, square_idx_tr, top_dot), get_offset_thumb_placer(thumb_r_placer, square_idx_bl, top_dot), get_offset_thumb_placer( thumb_r_placer, square_idx_bl, switch_riser_raw_dot ), ), hull( get_offset_thumb_placer(thumb_br_placer, square_idx_tr, top_dot), get_offset_thumb_placer( thumb_br_placer, square_idx_tr, switch_riser_raw_dot ), get_offset_thumb_placer( thumb_r_placer, square_idx_bl, switch_riser_raw_dot ), ), hull( get_offset_thumb_placer( thumb_r_placer, square_idx_br, switch_riser_raw_dot ), get_offset_thumb_placer( thumb_r_placer, square_idx_bl, switch_riser_raw_dot ), get_offset_thumb_placer( thumb_br_placer, square_idx_tr, switch_riser_raw_dot ), ), bottom_hull( hull( get_offset_thumb_placer( thumb_r_placer, square_idx_br, switch_riser_raw_dot ), get_offset_thumb_placer( thumb_br_placer, square_idx_tr, switch_riser_raw_dot ), ) ), ) def thumb_connectors(): right_thumb_cover_sphere = get_offset_thumb_placer( thumb_r_placer, square_idx_bl, translate(0, 1.7, 0)(web_post) ) left_thumb_cover_lower_sphere = get_offset_thumb_placer( thumb_bl_placer, square_idx_tl, translate(0, 2, 0)(web_post) ) left_thumb_cover_upper_sphere = thumb_l_placer(translate(0, 0, 0)(web_post_br)) br_tr_with_offset = thumb_br_placer(translate(0.8, 0.2, 0)(web_post_tr)) thumb_l_special_point = get_offset_thumb_placer( thumb_l_placer, square_idx_bl, translate(10, 1.1, 0)(web_post) ) return union( hull( thumb_m_placer(web_post_tr), thumb_m_placer(web_post_br), thumb_r_placer(web_post_tl), thumb_r_placer(web_post_bl), ), hull( thumb_m_placer(web_post_tl), thumb_l_placer(web_post_tr), thumb_m_placer(web_post_bl), thumb_l_placer(web_post_br), thumb_m_placer(web_post_bl), ), hull( thumb_bl_placer(web_post_tr), thumb_bl_placer(web_post_br), thumb_br_placer(web_post_tl), thumb_br_placer(web_post_bl), ), hull( thumb_bl_placer(web_post_tl), thumb_m_placer(web_post_bl), thumb_l_placer(web_post_br), ), hull( thumb_bl_placer(web_post_tl), thumb_bl_placer(web_post_tr), thumb_m_placer(web_post_bl), ), hull( thumb_bl_placer(web_post_tr), thumb_m_placer(web_post_bl), thumb_m_placer(web_post_br), ), hull( thumb_br_placer(web_post_tl), thumb_bl_placer(web_post_tr), thumb_m_placer(web_post_br), ), hull( thumb_m_placer(web_post_br), thumb_r_placer(web_post_bl), thumb_br_placer(web_post_tl), ), hull( thumb_r_placer(web_post_bl), thumb_br_placer(web_post_tl), br_tr_with_offset, ), hull( thumb_r_placer(web_post_bl), thumb_r_placer(web_post_br), br_tr_with_offset, ), # Right special connectors hull( right_thumb_cover_sphere, get_offset_thumb_placer(thumb_r_placer, square_idx_bl, web_post), thumb_r_placer(web_post_br), get_offset_thumb_placer(thumb_r_placer, square_idx_br, web_post), ), hull( right_thumb_cover_sphere, get_offset_thumb_placer(thumb_r_placer, square_idx_bl, web_post), get_offset_thumb_placer( thumb_br_placer, square_idx_tr, switch_riser_raw_dot ), br_tr_with_offset, ), # Left special connectors hull( thumb_l_placer(web_post_bl), get_offset_thumb_placer(thumb_l_placer, square_idx_bl, web_post), thumb_l_special_point, ), hull( thumb_l_placer(web_post_bl), thumb_l_placer(web_post_br), thumb_l_special_point, ), hull( left_thumb_cover_lower_sphere, get_offset_thumb_placer(thumb_bl_placer, square_idx_tl, web_post), thumb_bl_placer(web_post_tl), ), hull( left_thumb_cover_lower_sphere, thumb_bl_placer(web_post_tl), left_thumb_cover_upper_sphere, ), hull( left_thumb_cover_lower_sphere, left_thumb_cover_upper_sphere, thumb_l_special_point, ), hull( left_thumb_cover_lower_sphere, get_offset_thumb_placer(thumb_l_placer, square_idx_br, web_post), thumb_l_special_point, get_offset_thumb_placer(thumb_bl_placer, square_idx_tl, web_post), ), ) def thumb_to_body_connectors(): return union( bottom_hull( hull( thumb_r_placer( get_in_square(switch_riser_offset_square, square_idx_br)( switch_riser_raw_dot ) ), place_on_grid(3, 2)( get_in_square(switch_riser_offset_square, square_idx_bl)( switch_riser_raw_dot ) ), ) ), hull( thumb_r_placer( get_in_square(switch_riser_offset_square, square_idx_br)( switch_riser_raw_dot ) ), thumb_r_placer( get_in_square(switch_riser_offset_square, square_idx_tr)( switch_riser_raw_dot ) ), place_on_grid(3, 2)( get_in_square(switch_riser_offset_square, square_idx_bl)( switch_riser_raw_dot ) ), ), hull( thumb_r_placer( get_in_square(switch_riser_offset_square, square_idx_tr)( switch_riser_raw_dot ) ), place_on_grid(3, 2)( get_in_square(switch_riser_offset_square, square_idx_bl)( switch_riser_raw_dot ) ), place_on_grid(2, 1)( get_in_square(switch_riser_offset_square, square_idx_br)( switch_riser_raw_dot ) ), ), hull( thumb_r_placer( get_in_square(switch_riser_offset_square, square_idx_tr)( switch_riser_raw_dot ) ), thumb_r_placer( get_in_square(switch_riser_offset_square, square_idx_tl)( switch_riser_raw_dot ) ), place_on_grid(2, 1)( get_in_square(switch_riser_offset_square, square_idx_br)( switch_riser_raw_dot ) ), ), bottom_hull( hull( thumb_m_placer( get_in_square(switch_riser_offset_square, square_idx_tl)( switch_riser_raw_dot ) ), place_on_grid(2, 0)( get_in_square(switch_riser_offset_square, square_idx_bl)( switch_riser_raw_dot ) ), ) ), hull( thumb_m_placer( get_in_square(switch_riser_offset_square, square_idx_tl)( switch_riser_raw_dot ) ), place_on_grid(2, 0)( get_in_square(switch_riser_offset_square, square_idx_bl)( switch_riser_raw_dot ) ), place_on_grid(2, 0)( get_in_square(switch_riser_offset_square, square_idx_br)( switch_riser_raw_dot ) ), ), ) def thumb_switches(): return union(*[fn(single_switch) for fn in thumb_placement_fns]) def filled_thumb_switches(): return union(*[fn(filled_switch) for fn in thumb_placement_fns]) def bottom_edge_at_position(row, col): yield row, col, square_idx_bl yield row, col, square_idx_br def bottom_edge_iterator(): for col in range(NUM_COLS): row = num_rows_for_col(col) - 1 yield from bottom_edge_at_position(row, col) return def thumb_spots(): return [ [thumb_l_placer, square_idx_tl], [thumb_l_placer, square_idx_tr], [thumb_m_placer, square_idx_tl], [thumb_m_placer, square_idx_tr], [thumb_r_placer, square_idx_tl], [thumb_r_placer, square_idx_tr], [thumb_r_placer, square_idx_br], [thumb_br_placer, square_idx_br], ] def thumb_caps(): return union(*[fn(sa_cap) for fn in thumb_placement_fns[-2:]]) def blocker(): size = 500 shape = cube(size, size, size) shape = translate(-size / 2, -size / 2, -size)(shape) return shape screw_insert_height = 4.2 screw_insert_bottom_radius = 5.31 / 2.0 screw_insert_top_radius = 5.1 / 2 screw_insert_width = 2 bottom_height = 2 screw_insert_outer = translate(0, 0, bottom_height)( cylinderr1r2( screw_insert_bottom_radius + screw_insert_width, screw_insert_top_radius + screw_insert_width, screw_insert_height + screw_insert_width, ) ) screw_insert_inner = translate(0, 0, bottom_height)( cylinderr1r2( screw_insert_bottom_radius, screw_insert_top_radius, screw_insert_height ) ) def screw_insert(col, row, shape, ox, oy): postiion = point_on_grid(row, col, 0, 0, 0) postiion[2] = 0 postiion[0] += ox postiion[1] += oy return translate(*postiion)(shape) def screw_insert_all_shapes(shape): return union( screw_insert(2, 0, shape, -5.3, 5.9), screw_insert(NUM_COLS - 1, 0, shape, 6.7, 5.5), screw_insert(NUM_COLS - 1, num_rows_for_col(NUM_COLS - 1), shape, 6.8, 14.4), screw_insert(0, 0, shape, -6.2, -8), screw_insert(1, MAX_NUM_ROWS + 1, shape, -9.8, 3.4), screw_insert(0, MAX_NUM_ROWS - 1, shape, -17.4, -2), ) TRRS_HOLDER_SIZE = [6.0, 11.0, 7.0] TRRS_HOLE_SIZE = [2.6, 10.0] TRRS_HOLDER_THICKNESS = 2.5 TRRS_FRONT_THICKNESS = 1.8 def trrs_key_holder_position(): base_place = point_on_grid(0, 0, 0, KEYSWITCH_WIDTH / 2, 0) return [base_place[0] - 5, base_place[1] + 1.43, 9.0] def trrs_holder(): shape = cube( TRRS_HOLDER_SIZE[0] + TRRS_HOLDER_THICKNESS, TRRS_HOLDER_SIZE[1] + TRRS_FRONT_THICKNESS, TRRS_HOLDER_SIZE[2] + TRRS_HOLDER_THICKNESS * 2, ) placed_shape = translate( -TRRS_HOLDER_SIZE[0] / 2, -TRRS_HOLDER_SIZE[1], -(TRRS_HOLDER_SIZE[2] / 2 + TRRS_HOLDER_THICKNESS), )(shape) return translate(*trrs_key_holder_position())(placed_shape) def trrs_holder_hole(): rect_hole = cube(*TRRS_HOLDER_SIZE) rect_hole = translate( -TRRS_HOLDER_SIZE[0] / 2, -TRRS_HOLDER_SIZE[1], -TRRS_HOLDER_SIZE[2] / 2, )(rect_hole) cylinder_hole = cylinder(*TRRS_HOLE_SIZE, segments=30) cylinder_hole = rotate_x(90)(cylinder_hole) cylinder_hole = translate(0, 5, 0)(cylinder_hole) return translate(*trrs_key_holder_position())(union(rect_hole, cylinder_hole)) usb_holder_hole_dims = [9, 8, 3.5] usb_holder_thickness = 0.5 def usb_holder_position(): base_place = point_on_grid(0, 0, 0, KEYSWITCH_WIDTH / 2, 0) return [base_place[0] + 8, base_place[1] + 2, 4] def usb_holder_rim(): base_shape = cube( usb_holder_hole_dims[0] + usb_holder_thickness * 2, usb_holder_thickness * 2, usb_holder_hole_dims[2] + usb_holder_thickness * 2, ) placed_shape = translate( -usb_holder_hole_dims[0] / 2 - usb_holder_thickness, 0, -usb_holder_hole_dims[2] / 2 - usb_holder_thickness, )(base_shape) return translate(*usb_holder_position())(placed_shape) def usb_holder_hole(): placed_shape = translate( -usb_holder_hole_dims[0] / 2, -usb_holder_hole_dims[1] / 2, -usb_holder_hole_dims[2] / 2, )(cube(*usb_holder_hole_dims)) return translate(*usb_holder_position())(placed_shape) reset_switch_hole_height = 4.2 reset_switch_width = 6.0 reset_switch_hole_depth = 6.5 reset_switch_hole_back = 5.0 reset_switch_hole_radius = 4.3 / 2 reset_switch_total_depth = reset_switch_hole_depth + reset_switch_hole_back reset_switch_total_height = reset_switch_hole_height + 2 * bottom_height def unplaced_reset_switch_body(): shape = cube( reset_switch_width, reset_switch_total_depth, reset_switch_total_height, center=True, ) return translate(0, 0, reset_switch_total_height / 2)(shape) def unplaced_reset_switch_body_hole(): rect = translate( 0, -reset_switch_hole_back / 2.0, reset_switch_hole_height / 2.0 + bottom_height )( cube( reset_switch_width + 0.2, reset_switch_hole_depth, reset_switch_hole_height, center=True, ) ) cyl = translate(0, -reset_switch_hole_back / 2, bottom_height / 2)( cylinder(reset_switch_hole_radius, bottom_height, center=True) ) return union(rect, cyl) def place_reset_switch_shape(shape): base_point = point_on_grid(1, 1, 0, 0, 0) return translate(base_point[0], base_point[1], 0)(shape) reset_switch_body = place_reset_switch_shape(unplaced_reset_switch_body()) reset_switch_body_hole = place_reset_switch_shape(unplaced_reset_switch_body_hole()) def right_shell(): global SHOULD_INCLUDE_RISERS SHOULD_INCLUDE_RISERS = True cover = translate(-60, 20, 0)(cube(15, 15, 20)) full_proto = difference( union( all_switches(), connectors(), case_walls(), screw_insert_all_shapes(screw_insert_outer), # all_caps(), thumb_switches(), thumb_walls(), thumb_connectors(), # thumb_caps(), thumb_to_body_connectors(), trrs_holder(), usb_holder_rim(), ), union( blocker(), screw_insert_all_shapes(screw_insert_inner), trrs_holder_hole(), usb_holder_hole(), ), ) # return intersection(cover, full_proto) return full_proto def left_shell(): return flip_lr()(right_shell()) screw_head_height = 1.0 screw_head_radius = 5.5 / 2 screw_hole_radius = 1.7 def wall_shape(): walls_3d = union(case_walls(), thumb_walls(), thumb_to_body_connectors(),) walls_2d = offset(0.4)(project(cut=True))(translate(0, 0, -0.1)(walls_3d)) return walls_2d def model_outline(): global SHOULD_INCLUDE_RISERS SHOULD_INCLUDE_RISERS = True solid_bottom = project()( union( filled_switches(), connectors(), case_walls(), filled_thumb_switches(), thumb_walls(), thumb_connectors(), thumb_to_body_connectors(), ) ) bottom_2d = difference(solid_bottom, wall_shape()) return extrude_linear(bottom_height)(bottom_2d) weight_width = 19.5 weight_height = 16.5 weight_z_offset = 0.5 def weight_shape(): return translate(0, 0, 1.5 + weight_z_offset)( cube(weight_width, weight_height, 3, center=True) ) def weight_shape_vert(): return rotate_z(90)(weight_shape()) def place_weight_hole(x, y): return translate(x, y, 0)(weight_shape()) def bottom_weight_cutouts(): shapes = [] base_point = point_on_grid(0, NUM_COLS - 1, 0, 0, 0) base_x = base_point[0] - 9 base_y = base_point[1] - 4 space_between = 1 offsets = space_between + weight_width offsets_y = space_between + weight_height r3_y = base_y - 2 * offsets_y topr = base_y + offsets_y for x in range(2): shapes.append(place_weight_hole(1 + base_x - offsets * x, base_y)) for x in range(3, 4): shapes.append(place_weight_hole(base_x - offsets * x + 2, base_y)) for x in range(4, 5): shapes.append(place_weight_hole(base_x - offsets * x - 8, base_y)) for x in range(2, 5): shapes.append(place_weight_hole(base_x - offsets * x - 4, base_y - offsets_y)) for x in range(5): shapes.append(place_weight_hole(base_x - offsets * x, base_y - 2 * offsets_y)) shapes.append(place_weight_hole(base_x - offsets * 2, topr)) shapes.append(place_weight_hole(base_x - offsets * 3, topr)) base_thumb_x = base_x - offsets * 4 + 5 base_thumb_y = base_y - offsets_y * 3 - weight_width + weight_height + 1 angled_shape = rotate_z(107)(weight_shape()) shapes.append( translate(base_thumb_x - offsets_y - 0.4, base_thumb_y - 9.5, 0)(angled_shape) ) shapes.append( translate(base_thumb_x - offsets_y + 16, base_thumb_y - 1, 0)(angled_shape) ) return union(*shapes) reset_switch_body = place_reset_switch_shape(unplaced_reset_switch_body()) reset_switch_body_hole = place_reset_switch_shape(unplaced_reset_switch_body_hole()) def bottom_plate(): return difference( union( model_outline(), # reset_switch_body, # screw_insert_all_shapes(screw_insert_outer), ), union( screw_insert_all_shapes( cylinderr1r2(screw_head_radius, screw_head_radius, screw_head_height) ), screw_insert_all_shapes( cylinderr1r2(screw_hole_radius, screw_hole_radius, bottom_height) ), # bottom_weight_cutouts(), reset_switch_body_hole, ), ) def left_bottom_plate(): return flip_lr()(bottom_plate()) def thumb_corner(): global SHOULD_INCLUDE_RISERS SHOULD_INCLUDE_RISERS = False return difference( union( thumb_switches(), thumb_walls(), thumb_connectors(), # thumb_caps(), thumb_to_body_connectors(), ), union(blocker(),), ) def write_test(): render_to_file(right_shell(), "things/right.scad") render_to_file(left_shell(), "things/left.scad") render_to_file(bottom_plate(), "things/right_bottom_plate.scad") render_to_file(left_bottom_plate(), "things/left_bottom_plate.scad") def run(): write_test() print("done") if __name__ == "__main__": run()