dactyl-high-profile/src/main.py

1394 lines
38 KiB
Python

"""
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():
x, y, z = TRRS_HOLDER_SIZE
rect_hole = cube(x, y, z)
rect_hole = translate(
-x / 2, -y - 1, -z / 2,
)(rect_hole)
rect2 = cube(x, y, z - 3)
rect2 = translate(
-x / 2 + 5, -y - 1, -z / 3,
)(rect2)
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(
union(
rect_hole,
rect2,
),
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()