Created
April 29, 2022 17:40
-
-
Save jcmkk3/881395d15557b6d67f497cdf7d55e6b5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import cadquery as cq, math | |
from typing import Optional | |
from dataclasses import dataclass | |
rot_axis_z = ((0, 0, 0), (0, 0, 1)) | |
@dataclass | |
class Config: | |
x: float = 18 # Width of key (not keycap) outline; length for thumbs | |
y: float = 17 # Length of key (not keycap) outline; width for thumbs | |
r: float = 60 # Convex surface radius | |
eh: float = 2 # Top/Bottom edge max height | |
gap: float = 1 # Gap between cap outlines | |
cr: float = 60 # Radius of concave/cut sphere; only for alphas | |
ch: float = 1.5 # Minimum height in the center; only for alphas | |
sh: Optional[float] = None # Height at which to be sliced, measured from top of stem; only for alphas | |
ang: float = 30 # Angle of tangent of convex surface hits the top/bottom edge | |
off: float = 0 # X-offset of cut sphere from the center of stem | |
sl: float = 3.5 # Stem length | |
def __add__(self, c: 'Config'): | |
default = vars(Config()) | |
ret = Config() | |
for k, v in default.items(): | |
if getattr(self, k) != default[k]: | |
setattr(ret, k, getattr(self, k)) | |
if getattr(c, k) != default[k]: | |
setattr(ret, k, getattr(c, k)) | |
return ret | |
class Boxed: | |
def __init__(self, thing, x, y): | |
self.thing = thing | |
self.x = x | |
self.y = y | |
def hcat(self, other): | |
total_x, total_y = self.x + other.x, max(self.y, other.y) | |
return Boxed(self.thing.translate(((self.x - total_x) / 2, 0, 0)) | |
.union(other.thing.translate(((total_x - other.x) / 2, 0, 0))), total_x, total_y) | |
def vcat(self, other): | |
total_x, total_y = max(self.x, other.x), self.y + other.y | |
return Boxed(self.thing.translate((0, -self.y / 2, 0)) | |
.union(other.thing.translate((0, other.y / 2, 0))), total_x, total_y) | |
def unwrap(self): | |
return self.thing | |
def __or__(self, other): return self.hcat(other) | |
def __add__(self, other): return self.vcat(other) | |
def add_stem(obj, c: Config, thumb=False): | |
stem_dist = 5.7 | |
stem = ( | |
cq.Workplane().box(1.2, 3, c.sl, (True, True, False)).faces("<X or |Z").shell(-0.8) | |
.translate((stem_dist / 2, 0, -c.sl)) | |
) | |
stems = stem.union(stem.rotate(*rot_axis_z, 180)) | |
if thumb: | |
stems = stems.rotate(*rot_axis_z, 90) | |
return ( | |
# obj.faces("<Z").workplane().sketch().face(obj.wires("<Z").val()).wires().offset(-1, mode="s").finalize().extrude(1) | |
obj.union(obj.wires("<Z").toPending().extrude(-1, combine=False).faces("|Z").shell(-1)) | |
.union(stems.translate((c.off, 0, 0))) | |
) | |
def base(c: Config, pos: str): | |
base = cq.Workplane().box(c.x - c.gap, c.y - c.gap, 10, (True, True, False)).edges("|Z").chamfer(1) | |
if pos == "mid": | |
return add_stem( | |
base | |
.intersect(cq.Workplane().transformed(offset=(c.off, 0, 10 - c.r)).sphere(c.r)), | |
c, | |
) | |
y_mul = -1 if pos == "top" else 1 | |
return add_stem( | |
base | |
.intersect(cq.Workplane().transformed(offset=(c.off, | |
y_mul * (c.r * math.sin(math.radians(c.ang)) - c.y / 2), | |
c.eh - c.r * math.cos(math.radians(c.ang)))).sphere(c.r)), | |
c, | |
) | |
def col(c: Config): | |
b = lambda p: base(c, p) | |
col = ( | |
b("mid").union(b("top").translate((0, c.y))).union(b("bot").translate((0, -c.y))) | |
.cut(cq.Workplane().transformed(offset=(c.off, 0, c.cr + c.ch)).sphere(c.cr)) | |
) | |
if c.sh != None: | |
col = col.workplane(c.sh).split(keepBottom=True) | |
return Boxed(col, c.x, c.y * 3) | |
def thumb(c: Config): | |
return add_stem( | |
cq.Workplane().box(c.y - c.gap, c.x - c.gap, 10, (True, True, False)) | |
.edges("|Z").chamfer(1) | |
.intersect(cq.Workplane().transformed(offset=(0, | |
c.r * math.sin(math.radians(c.ang)) - c.x / 2, | |
c.eh - c.r * math.cos(math.radians(c.ang)))).sphere(c.r)), | |
c, thumb=True | |
) | |
def thumb_col(c: Config): | |
T = thumb(c) | |
return Boxed(T.union(T.translate((-c.y, 0, 0))).union(T.translate((c.y, 0, 0))).rotate(*rot_axis_z, -90), | |
c.x, c.y * 3) | |
def export_color(obj, name): | |
cq.Assembly(obj, color=cq.Color("gray")).save(name + ".step") | |
# ----- Generate ----------------------------------------------- | |
chocmin_x, chocmin_y, chocmin_nnx = 18, 15, 15 | |
chocmin_nx = (chocmin_x + chocmin_nnx) / 2 | |
chocmin_off = chocmin_nx / 2 - (chocmin_nx - chocmin_x / 2) | |
gap = 0.6 | |
c = Config(gap=gap, ch=1, cr=50, ang=30, eh=1, r=55) | |
F = col(c + Config(chocmin_x, chocmin_y)) | |
R = col(c + Config(chocmin_nx, chocmin_y, off=-chocmin_off)) | |
L = col(c + Config(chocmin_nx, chocmin_y, off=chocmin_off)) | |
N = col(c + Config(chocmin_nnx, chocmin_y)) | |
T = thumb_col(c + Config(chocmin_x, chocmin_y, r=60, eh=1, ang=15)) | |
right = T | L | R | F | L | N | |
left = T | L | R | F | R | N | |
whole = left + right | |
show_object(whole.thing) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment