322 lines
9.5 KiB
Python
Executable file
322 lines
9.5 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
|
|
"""
|
|
mkbd65 openscad generator
|
|
|
|
Usage:
|
|
python3 generate_scad.py <config.ini> [<layout>]
|
|
|
|
Options:
|
|
-h --help Show this screen.
|
|
|
|
Example:
|
|
python3 generate_scad.py oneplus5t.ini en_us-intl
|
|
"""
|
|
|
|
import sys
|
|
from configparser import ConfigParser
|
|
from importlib import import_module
|
|
|
|
from solid import cube, text, union, difference, linear_extrude, scad_render_to_file, OpenSCADObject, translate, color, \
|
|
polygon, circle
|
|
from solid.utils import Black, White, Green, Aluminum, Cyan
|
|
|
|
|
|
def roundrect(width, height, radius):
|
|
"""
|
|
O/-----\O
|
|
/ \
|
|
| |
|
|
\ /
|
|
O\_____/O
|
|
|
|
:param width:
|
|
:param height:
|
|
:param radius:
|
|
:return:
|
|
"""
|
|
return union()(
|
|
polygon([
|
|
[0, radius],
|
|
[0, height - radius],
|
|
[radius, height],
|
|
[width - radius, height],
|
|
[width, height - radius],
|
|
[width, radius],
|
|
[width - radius, 0],
|
|
[radius, 0]
|
|
]),
|
|
translate([radius, radius, 0])(
|
|
circle(r=radius)
|
|
),
|
|
translate([radius, height - radius, 0])(
|
|
circle(r=radius)
|
|
),
|
|
translate([width - radius, height - radius, 0])(
|
|
circle(r=radius)
|
|
),
|
|
translate([width - radius, radius, 0])(
|
|
circle(r=radius)
|
|
)
|
|
)
|
|
|
|
|
|
def key(dx: int, dy: int, label_ltop: str = '', label_lbottom: str = '', label_rtop: str = '', label_rbottom: str = '',
|
|
label_center: str = '') -> OpenSCADObject:
|
|
"""
|
|
Generates a single key
|
|
|
|
:param dx: width in mm
|
|
:param dy: length in mm
|
|
:param label: List of labels (top, bottom, right, middle)
|
|
:return:
|
|
"""
|
|
def get_size(s: str) -> float:
|
|
if s in ['⇧', '⏎']:
|
|
return 5
|
|
elif s == '⌫':
|
|
return 3.5
|
|
elif s in ['Esc', 'Ctrl', 'Alt', 'Fn']:
|
|
return 3
|
|
else:
|
|
return 4
|
|
|
|
thickness = config.getfloat('key', 'thickness', fallback=1)
|
|
font = config.get('key', 'font', fallback='DejaVu Sans:style=Bold')
|
|
|
|
return difference()(
|
|
linear_extrude(height=thickness)(
|
|
roundrect(width=dx, height=dy, radius=0.5)
|
|
),
|
|
|
|
translate(v=[1, 5.5, 0.8])(
|
|
linear_extrude(height=0.2)(
|
|
text(text=label_ltop, size=3, font=font, halign='left', valign='top')
|
|
)
|
|
),
|
|
translate(v=[1, 0.5, 0.8])(
|
|
linear_extrude(height=0.2)(
|
|
text(text=label_lbottom, size=3, font=font, halign='left', valign='bottom')
|
|
)
|
|
),
|
|
translate(v=[8.5, 5.5, 0.8])(
|
|
linear_extrude(height=0.2)(
|
|
text(text=label_rtop, size=3, font=font, halign='right', valign='top')
|
|
)
|
|
),
|
|
translate(v=[8.5, -1.5 if label_rbottom == '`' else 0.5, 0.8])(
|
|
linear_extrude(height=0.2)(
|
|
text(text=label_rbottom, size=3, font=font, halign='right', valign='bottom')
|
|
)
|
|
),
|
|
translate(v=[4.5, 3, 0.8])(
|
|
linear_extrude(height=0.2)(
|
|
text(text=label_center, size=get_size(label_center), font=font, halign='center', valign='center')
|
|
)
|
|
)
|
|
)
|
|
|
|
|
|
def keymat() -> OpenSCADObject:
|
|
"""
|
|
Generates keymat
|
|
|
|
mat:
|
|
+---------------------------------------------+
|
|
| margin |
|
|
| +-----+ +-----+ +-----+ |
|
|
|margin| |padding| | ... | |margin|
|
|
| +-----+ +-----+ +-----+ |
|
|
| padding |
|
|
| +-----+ +-----+ +-----+ |
|
|
| | | | | | | |
|
|
| +-----+ +-----+ +-----+ |
|
|
| ... |
|
|
| +-----+ +-----+ +-----+ |
|
|
| | | | | | | |
|
|
| +-----+ +-----+ +-----+ |
|
|
| margin |
|
|
+---------------------------------------------+
|
|
\ thickness
|
|
key:
|
|
+-----+
|
|
| | dy
|
|
+-----+
|
|
dx \ thickness
|
|
|
|
:return:
|
|
"""
|
|
thickness = config.getfloat('keymat', 'thickness', fallback=0.5)
|
|
cols = 12
|
|
rows = 5
|
|
key_dx = 9
|
|
key_dy = 6
|
|
padding = 1
|
|
margin = 2
|
|
spacebar_x = 5 # nth key from the left
|
|
spacebar_length = 2 # length in keys
|
|
|
|
keys = []
|
|
for row in range(rows):
|
|
for col in range(cols):
|
|
keys.append(
|
|
translate([
|
|
col * padding + col * key_dx + margin,
|
|
row * padding + row * key_dy + margin,
|
|
thickness
|
|
])(
|
|
key(
|
|
dx=key_dx,
|
|
dy=key_dy,
|
|
label_ltop=layout[row][col][0],
|
|
label_lbottom=layout[row][col][1],
|
|
label_rtop=layout[row][col][2],
|
|
label_rbottom=layout[row][col][3],
|
|
label_center=layout[row][col][4]
|
|
)
|
|
)
|
|
)
|
|
|
|
return color(Black)(
|
|
union()(
|
|
# mat
|
|
cube([
|
|
cols * key_dx + (cols - 1) * padding + 2 * margin,
|
|
rows * key_dy + (rows - 1) * padding + 2 * margin,
|
|
thickness
|
|
]),
|
|
# keys
|
|
*keys,
|
|
# spacebar
|
|
translate([spacebar_x * key_dx + spacebar_x * padding + margin, margin, thickness])(
|
|
key(dx=key_dx * spacebar_length + (spacebar_length - 1) * padding, dy=key_dy)
|
|
)
|
|
)
|
|
)
|
|
|
|
|
|
def pcb() -> OpenSCADObject:
|
|
rows = 5
|
|
columns = 12
|
|
hspacing = 4
|
|
vspacing = 3
|
|
width = 6
|
|
height = 4
|
|
thickness = 0.78
|
|
switches = []
|
|
|
|
for row in range(rows):
|
|
for col in range(columns):
|
|
switches.append(
|
|
translate([3.5 + (hspacing + width) * col, 2 + (vspacing + height) * row, 0.6])(
|
|
color(White)(
|
|
linear_extrude(height=thickness)(
|
|
# 6 +----+ 5
|
|
# / \
|
|
# 7 + + 4 | 2mm
|
|
# 8 + + 3 |
|
|
# \ 4mm /
|
|
# 1 +----+ 2
|
|
polygon(points=[(1, 0), (5, 0), (6, 1), (6, 3), (5, 4), (1, 4), (0, 3), (0, 1)]),
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
return union()(
|
|
color(Green)(cube([123, 49, 0.6])),
|
|
# ATMEGA32U4
|
|
translate([13, 36, 0.6])(
|
|
color(Black)(cube([7, 7, 1]))
|
|
),
|
|
# Crystal
|
|
translate([23, 35.75, 0.6])(
|
|
color(Black)(cube([2.5, 2, 0.55]))
|
|
),
|
|
*switches
|
|
)
|
|
|
|
|
|
def top_plate() -> OpenSCADObject:
|
|
thickness = config.getfloat('keymat', 'thickness', fallback=1)
|
|
w = config.getfloat('phone', 'width')
|
|
h = config.getfloat('phone', 'height')
|
|
r = config.getfloat('phone', 'cornerradius')
|
|
|
|
return color(Aluminum)(
|
|
difference()(
|
|
#cube([123, 49, 0.5]),
|
|
linear_extrude(height=thickness)(
|
|
roundrect(width=w, height=h, radius=r)
|
|
),
|
|
translate([0, 0, -1 * thickness])(keymat())
|
|
)
|
|
)
|
|
|
|
|
|
def internals() -> OpenSCADObject:
|
|
return color(Cyan)(cube([1, 1, 1]))
|
|
|
|
|
|
def mkbd65() -> OpenSCADObject:
|
|
fr4 = 0.4 # pcb thickness
|
|
switch = 0.78 # switch height
|
|
keymat_thickness = config.getfloat('keymat', 'thickness', fallback=1)
|
|
|
|
return union()(
|
|
internals(),
|
|
pcb(),
|
|
color(Black)(translate([0, -1, fr4 + switch])(keymat())),
|
|
color(Aluminum)(translate([0, -1, fr4 + switch + keymat_thickness])(top_plate()))
|
|
)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
config = ConfigParser()
|
|
|
|
if '-h' in sys.argv or '--help' in sys.argv or 2 > len(sys.argv) > 3:
|
|
sys.exit(__doc__)
|
|
|
|
if len(sys.argv) == 3:
|
|
layoutfile = sys.argv[2]
|
|
else:
|
|
layoutfile = 'en_us-intl'
|
|
|
|
try:
|
|
with open(sys.argv[1]) as fd:
|
|
config.read_file(fd)
|
|
layout = import_module(f'layout_{ layoutfile }').LAYOUT
|
|
file_header = '$fn = 16;'
|
|
scad_render_to_file(
|
|
keymat(),
|
|
filepath='./mkbd65_keymat.scad',
|
|
file_header=file_header,
|
|
)
|
|
# TODO: generate mkbd65_pcb.scad
|
|
scad_render_to_file(
|
|
pcb(),
|
|
filepath='./mkbd65_pcb.scad',
|
|
file_header=file_header,
|
|
)
|
|
# TODO: generate mkbd65_top_plate.scad
|
|
scad_render_to_file(
|
|
top_plate(),
|
|
filepath='./mkbd65_top_plate.scad',
|
|
file_header=file_header,
|
|
)
|
|
# TODO: generate mkbd65_internals.scad
|
|
scad_render_to_file(
|
|
internals(),
|
|
filepath='./mkbd65_internals.scad',
|
|
file_header=file_header,
|
|
)
|
|
# TODO: generate mkbd65.scad
|
|
scad_render_to_file(
|
|
mkbd65(),
|
|
filepath='./mkbd65.scad',
|
|
file_header=file_header,
|
|
)
|
|
except FileNotFoundError:
|
|
sys.exit(f'The config file { sys.argv[1] } could not be found.')
|
|
except ModuleNotFoundError:
|
|
sys.exit(f'The layout file `layout_{ layoutfile }.py` could not be found.')
|