cad/generate_scad.py
Ricardo Band 07fb52fd73 fooo
2022-02-05 18:33:03 +01:00

322 lines
9.5 KiB
Python
Executable file

#!/usr/bin/env python3
"""
mkbd65 openscad generator
Usage:
python3 generate_scad.py <oneplus_8t.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 BlackPaint, 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(BlackPaint)(
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(BlackPaint)(cube([7, 7, 1]))
),
# Crystal
translate([23, 35.75, 0.6])(
color(BlackPaint)(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(BlackPaint)(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.')