openscad generator refactoring

This commit is contained in:
Ricardo Band 2022-02-03 21:07:56 +01:00
commit 0df0f72e1f
5 changed files with 334 additions and 0 deletions

14
mkbd65/config.ini Normal file
View file

@ -0,0 +1,14 @@
[oneplus_8t]
phone_width=160.7
phone_height=74.1
phone_thickness=8.4
phone_corner_radius=10
phone_side_radius=5
key_mat_margin=1
key_mat_padding=1
key_mat_thickness=0.5
key_mat_key_width=9
key_mat_key_height=6
key_mat_key_corner_radius=1
key_mat_key_thickness=2
key_mat_font=DejaVu Sans:style=Bold

154
mkbd65/key_mat.py Normal file
View file

@ -0,0 +1,154 @@
"""
All sizes are in millimeters.
"""
from typing import Tuple
from solid import OpenSCADObject, cube, linear_extrude, text, translate
from util import round_rect, get_label_size, FILAMENT_LAYER, LAYOUTS
def key(
width: float,
height: float,
corner_radius: float,
thickness: float,
font: str,
label: str = ""
) -> OpenSCADObject:
"""
Generates a single key
key:
+-----+
| | height
+-----+
width \\ thickness
:param width: Width in mm
:param height: Height in mm
:param corner_radius: Radius of corners in mm
:param thickness: Thickness in mm
:param label: Text label
:param font: Font to use
:return:
"""
return linear_extrude(height=thickness)(
round_rect(width=width, height=height, radius=corner_radius)
) - translate(v=(width / 2, height / 2, thickness - 2 * FILAMENT_LAYER))(
linear_extrude(height=2 * FILAMENT_LAYER)(
text(text=label, size=get_label_size(label), font=font, halign='center', valign='center')
)
)
def key_mat(
margin: float | Tuple[float, float, float, float],
padding: float | Tuple[float, float],
thickness: float,
key_width: float,
key_height: float,
key_radius: float,
key_thickness: float,
layout: str,
font: str
) -> OpenSCADObject:
"""
Generates elastic mat with fixed layout but variable sizes
mat:
+---------------------------------------------+
| margin |
| +-----+ +-----+ +-----+ |
|margin| |padding| | ... | |margin|
| +-----+ +-----+ +-----+ |
| padding |
| +-----+ +-----+ +-----+ |
| | | | | ... | | |
| +-----+ +-----+ +-----+ |
| ... ... ... |
| +-----+ +-----+ +-----+ |
| | | | | ... | | |
| +-----+ +-----+ +-----+ |
| margin |
+---------------------------------------------+
\\ thickness
:param margin: left, right, top and bottom margin, can be single number to set all at once.
:param padding: Horizontal and vertical padding, can be single number to set both at once.
:param thickness: Thickness of matt.
:param key_width: Width of keys.
:param key_height: Height of keys.
:param key_radius: Radius of key corners.
:param key_thickness: Thickness of keys without matt.
:param layout: ansi or iso layout.
:param font:
:return:
"""
keys_h = 13
keys_v = 5
spacebar_start = 4 # horizontal start key of space bar (0 index)
spacebar_width = 3 # space bar length in keys
margin_l = margin[0] if isinstance(margin, Tuple) else margin
margin_r = margin[1] if isinstance(margin, Tuple) else margin
margin_t = margin[2] if isinstance(margin, Tuple) else margin
margin_b = margin[3] if isinstance(margin, Tuple) else margin
padding_h = padding[0] if isinstance(padding, Tuple) else padding
padding_v = padding[1] if isinstance(padding, Tuple) else padding
mat = cube([
keys_h * key_width + (keys_h - 1) * padding_h + margin_l + margin_r,
keys_v * key_height + (keys_v - 1) * padding_v + margin_t + margin_b,
thickness
])
for x in range(keys_h):
for y in range(keys_v):
if (x == keys_h - 1 and y in [0, 2, 3]) \
or (spacebar_start < x < spacebar_start + spacebar_width and y == keys_v - 1):
# skip for wide keys (backspace, enter, shift and space bar)
continue
elif x == keys_h - 1 and y == 1 and layout == "iso":
# big enter
mat += translate(v=(x, y, thickness))(key(
width=key_width,
height=2 * key_height + padding_v,
corner_radius=key_radius,
thickness=key_thickness,
font=font,
label=LAYOUTS[layout][y][x]
))
elif x == keys_h - 2 and (y in [0, 3]) or (y == 2 and layout == "ansi"):
# wide key
mat += translate(v=(x, y, thickness))(key(
width=2 * key_width + padding_h,
height=key_height,
corner_radius=key_radius,
thickness=key_thickness,
font=font,
label=LAYOUTS[layout][y][x]
))
elif x == spacebar_start and y == keys_v - 1:
# space bar
mat += translate(v=(x, y, thickness))(key(
width=spacebar_width * key_width + (spacebar_width - 1) * padding_h,
height=key_height,
corner_radius=key_radius,
thickness=key_thickness,
font=font,
label=LAYOUTS[layout][y][x]
))
else:
# normal key
mat += translate(v=(x, y, thickness))(key(
width=key_width,
height=key_height,
corner_radius=key_radius,
thickness=key_thickness,
font=font,
label=LAYOUTS[layout][y][x]
))
return mat

66
mkbd65/main.py Normal file
View file

@ -0,0 +1,66 @@
"""
All sizes are in millimeters.
"""
import sys
from subprocess import run
from configparser import ConfigParser
from solid import scad_render_to_file
from key_mat import key_mat
from phone import phone
def main(device: str = "oneplus_8t", layout: str = "ansi"):
config = ConfigParser()
config.read_file("config.ini")
if not config.has_section(device):
sys.exit(f"{device} not found in config.ini")
components = [
["phone", phone(
width=config.getfloat(device, "phone_width"),
height=config.getfloat(device, "phone_height"),
thickness=config.getfloat(device, "phone_thickness"),
corner_radius=config.getfloat(device, "phone_corner_radius"),
side_radius=config.getfloat(device, "phone_side_radius")
)],
["key_mat", key_mat(
margin=config.getfloat(device, "key_mat_margin", fallback=1),
padding=config.getfloat(device, "key_mat_padding", fallback=1),
thickness=config.getfloat(device, "key_mat_thickness", fallback=0.5),
key_width=config.getfloat(device, "key_mat_key_width", fallback=9),
key_height=config.getfloat(device, "key_mat_key_height", fallback=6),
key_radius=config.getfloat(device, "key_mat_key_corner_radius", fallback=1),
key_thickness=config.getfloat(device, "key_mat_key_thickness", fallback=2),
layout=layout.lower(),
font=config.get(device, "key_mat_font", fallback="DejaVu Sans:style=Bold")
)]
]
for component in components:
print(f"Render component {component[0]}...")
scad_render_to_file(
component[1],
filepath=f"mkbd65_{component[0]}.scad",
file_header=f'$fn = 64;',
include_orig_code=True
)
print(f"Generate STL for component {component[0]}...")
run(["openscad", "-o", f"mkbd65_{component[0]}.stl", f"mkbd65_{component[0]}.scad"])
print(f"Generate 3MF for component {component[0]}...")
run(["openscad", "-o", f"mkbd65_{component[0]}.3mf", f"mkbd65_{component[0]}.scad"])
# obj_all = obj_phone + obj_key_mat
# scad_render_to_file(obj_all, filepath="mkbd65.scad", file_header=f'$fn = 64;', include_orig_code=True)
if __name__ == "__main__":
if "-h" in sys.argv or "--help" in sys.argv:
print("Usage:")
print(" ./main.py <PHONE> [LAYOUT]\n")
print("Layout can be ANSI or ISO.")
main(device=sys.argv[1])

29
mkbd65/phone.py Normal file
View file

@ -0,0 +1,29 @@
"""
All sizes are in millimeters.
"""
from solid import OpenSCADObject, linear_extrude
from util import round_rect
def phone(
width: float,
height: float,
thickness: float,
corner_radius: float,
side_radius: float
) -> OpenSCADObject:
"""
Generates rough estimate of the phones shape
:param width: length from top to bottom (landscape)
:param height: length from left to right (landscape)
:param thickness: How thicc is it?
:param corner_radius: Assuming all phones have round corners
:return:
"""
return linear_extrude(height=thickness)(
round_rect(width=width, height=height, radius=corner_radius)
)

71
mkbd65/util.py Normal file
View file

@ -0,0 +1,71 @@
from solid import polygon, circle, translate
FILAMENT_LAYER = 0.1
LAYOUTS = {
"ansi": [
["Esc", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "Bksp", None],
["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "[", "]", "\\"],
["A", "S", "D", "F", "G", "H", "J", "K", "L", ";", "\"", "Enter", None],
["Z", "X", "C", "V", "B", "N", "M", ",", ".", "/", "Up", "Shift", None],
["Ctrl", "Fn", "Shift", "Alt", "Space", None, None, "-", "=", "Left", "Down", "Right", "Ctrl"]
],
"iso": [
["Esc", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "Bksp", None],
["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "[", "]", "Ent"],
["A", "S", "D", "F", "G", "H", "J", "K", "L", ";", "\"", "\\", None],
["Z", "X", "C", "V", "B", "N", "M", ",", ".", "/", "Up", "Shift", None],
["Ctrl", "Fn", "Shift", "Alt", "Space", None, None, "-", "=", "Left", "Down", "Right", "Ctrl"]
]
}
def get_label_size(label: str) -> float:
"""
Get font size depending on label.
:param label:
:return:
"""
if label in ['⇧', '⏎']:
return 5
elif label == '⌫':
return 3.5
elif label in ['Esc', 'Ctrl', 'Alt', 'Fn']:
return 3
else:
return 4
def round_rect(width: float | int, height: float | int, radius: float | int):
"""
Rectangle with rounded corners.
Combines a polygon and four circles to form the round corners.
O/-----\O
/ \
| |
\ /
O\_____/O
:param width: Width of the rectangle
:param height: Height of the rectangle
:param radius: Radius of rectangle corners
:return:
"""
return polygon(points=[
(0, radius),
(0, height - radius),
(radius, height),
(width - radius, height),
(width, height - radius),
(width, radius),
(width - radius, 0),
(radius, 0)]
) \
+ translate(v=(radius, radius, 0))(circle(r=radius)) \
+ translate(v=(radius, height - radius, 0))(circle(r=radius)) \
+ translate(v=(width - radius, height - radius, 0))(circle(r=radius)) \
+ translate(v=(width - radius, radius, 0))(circle(r=radius))