This commit is contained in:
Ricardo Band 2022-02-05 18:33:03 +01:00
commit 07fb52fd73
7 changed files with 159 additions and 84 deletions

View file

@ -4,7 +4,7 @@
mkbd65 openscad generator
Usage:
python3 generate_scad.py <config.ini> [<layout>]
python3 generate_scad.py <oneplus_8t.ini> [<layout>]
Options:
-h --help Show this screen.

View file

@ -1,14 +0,0 @@
[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

View file

@ -3,9 +3,10 @@ All sizes are in millimeters.
"""
from typing import Tuple
from solid import OpenSCADObject, cube, linear_extrude, text, translate
from solid import OpenSCADObject, cube, linear_extrude, text, translate, scale, rotate, cylinder, union, intersection, \
color
from util import round_rect, get_label_size, FILAMENT_LAYER, LAYOUTS
from util import round_rect, get_label_size, FILAMENT_LAYER, LAYOUTS, forward
def key(
@ -33,10 +34,31 @@ def key(
: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)(
dome_height = 2
return union()(
translate(v=(0, height / 2, 2))(
scale(v=(1, 1, dome_height / height))(
rotate((0, 90, 0))(
cylinder(h=width, d=height)
)
) * translate(v=(width / 2, height / 2, 0))(
scale((1, 1, dome_height / width))(
rotate((90, 0, 0))(
cylinder(h=height, d=width)
)
)
)
) * forward(v=2)(
linear_extrude(height=dome_height / 2)(
round_rect(width=width, height=height, radius=corner_radius)
)
),
linear_extrude(height=2)(
round_rect(width=width, height=height, radius=corner_radius)
)
) - translate(v=(width / 2, height / 2, thickness + dome_height / 2 - dome_height * 2 * FILAMENT_LAYER))(
linear_extrude(height=dome_height * 2 * FILAMENT_LAYER)(
text(text=label, size=get_label_size(label), font=font, halign='center', valign='center')
)
)
@ -51,6 +73,7 @@ def key_mat(
key_radius: float,
key_thickness: float,
layout: str,
variant: str,
font: str
) -> OpenSCADObject:
"""
@ -74,6 +97,7 @@ def key_mat(
+---------------------------------------------+
\\ thickness
:param variant:
: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.
@ -106,49 +130,49 @@ def key_mat(
for x in range(keys_h):
for y in range(keys_v):
if (x == keys_h - 1 and y in [0, 2, 3]) \
if (x == keys_h - 1 and (y in [0, 2, 3] and layout.startswith("ansi") or y in [0, 2] and layout == "iso")) \
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":
elif x == keys_h - 1 and y == 1 and layout in ["ansi_big_enter", "iso"]:
# big enter
mat += translate(v=(x, y, thickness))(key(
mat += translate(v=(padding_h + x * (key_width + padding_h), padding_v + (keys_v - y - 2) * (key_height + padding_v), 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]
label=LAYOUTS[f"{layout}_{variant}"][y][x]
))
elif x == keys_h - 2 and (y in [0, 3]) or (y == 2 and layout == "ansi"):
elif x == keys_h - 2 and (y == 0 or (y == 2 and layout == "ansi") or (y == 3 and layout != "iso")):
# wide key
mat += translate(v=(x, y, thickness))(key(
mat += translate(v=(padding_h + x * (key_width + padding_h), padding_v + (keys_v - y - 1) * (key_height + padding_v), 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]
label=LAYOUTS[f"{layout}_{variant}"][y][x]
))
elif x == spacebar_start and y == keys_v - 1:
# space bar
mat += translate(v=(x, y, thickness))(key(
mat += translate(v=(padding_h + x * (key_width + padding_h), padding_v + (keys_v - y - 1) * (key_height + padding_v), 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]
label=LAYOUTS[f"{layout}_{variant}"][y][x]
))
else:
# normal key
mat += translate(v=(x, y, thickness))(key(
mat += translate(v=(padding_h + x * (key_width + padding_h), padding_v + (keys_v - y - 1) * (key_height + padding_v), thickness))(key(
width=key_width,
height=key_height,
corner_radius=key_radius,
thickness=key_thickness,
font=font,
label=LAYOUTS[layout][y][x]
label=LAYOUTS[f"{layout}_{variant}"][y][x]
))
return mat
return color("DarkSlateGray")(mat)

View file

@ -3,42 +3,53 @@
All sizes are in millimeters.
"""
import sys
from subprocess import run
from configparser import ConfigParser
import click
from solid import scad_render_to_file
from key_mat import key_mat
from key_mat import key_mat, key
from phone import phone
def main(device: str = "oneplus_8t", layout: str = "ansi"):
@click.command()
@click.option("-l", "--layout", "layout", default="ansi", show_default=True, help="Keyboard layout", type=click.Choice(["ansi", "ansi_big_enter", "iso"], case_sensitive=False))
@click.option("-v", "--variant", "variant", default="en", show_default=True, help="Keyboard layout variant", type=click.Choice(["en", "de"], case_sensitive=False))
@click.option("-s", "--stl", "stl", is_flag=True, help="Generate STL files")
@click.option("-3", "--3mf", "emf", is_flag=True, help="Generate 3MF files")
@click.argument("config_file")
def cli(layout, variant, stl, emf, config_file):
config = ConfigParser()
config.read("config.ini")
if not config.has_section(device):
sys.exit(f"{device} not found in config.ini")
config.read(config_file)
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")
width=config.getfloat("phone", "width"),
height=config.getfloat("phone", "height"),
thickness=config.getfloat("phone", "thickness"),
corner_radius=config.getfloat("phone", "corner_radius"),
side_radius=config.getfloat("phone", "side_radius")
)],
["key", key(
width=config.getfloat("key_mat", "key_width", fallback=9),
height=config.getfloat("key_mat", "key_height", fallback=6),
thickness=config.getfloat("key_mat", "key_thickness", fallback=2),
corner_radius=config.getfloat("key_mat", "key_corner_radius", fallback=1),
label="G",
font=config.get("key_mat", "font", fallback="DejaVu Sans:style=Bold")
)],
["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")
margin=config.getfloat("key_mat", "margin", fallback=1),
padding=config.getfloat("key_mat", "padding", fallback=1),
thickness=config.getfloat("key_mat", "thickness", fallback=0.5),
key_width=config.getfloat("key_mat", "key_width", fallback=9),
key_height=config.getfloat("key_mat", "key_height", fallback=6),
key_radius=config.getfloat("key_mat", "key_corner_radius", fallback=1),
key_thickness=config.getfloat("key_mat", "key_thickness", fallback=2),
layout=layout,
variant=variant,
font=config.get("key_mat", "font", fallback="DejaVu Sans:style=Bold")
)]
]
@ -50,19 +61,16 @@ def main(device: str = "oneplus_8t", layout: str = "ansi"):
file_header=f'$fn = 64;',
include_orig_code=False
)
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"])
if stl:
print(f"Generate STL for component {component[0]}...")
run(["openscad", "-o", f"mkbd65_{component[0]}.stl", f"mkbd65_{component[0]}.scad"])
if emf:
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 or len(sys.argv) < 2:
print("Usage:")
print(" ./main.py <PHONE> [LAYOUT]\n")
print("Layout can be ANSI or ISO.")
else:
main(device=sys.argv[1])
cli()

20
mkbd65/oneplus_8t.ini Normal file
View file

@ -0,0 +1,20 @@
# oneplus_8t
[phone]
width = 160.7
height = 74.1
thickness = 8.4
corner_radius = 10
side_radius = 5
[key_mat]
margin = 1
padding = 1
thickness = 0.5
key_width = 9
key_height = 6
key_corner_radius = 1
key_thickness = 2
font = DejaVu Sans:style=Bold
#font = Liberation Mono:style=Bold
#font = Noto Sans:style=Bold

View file

@ -1,22 +1,36 @@
from solid import polygon, circle, translate
from solid import polygon, circle, translate, OpenSCADObject
FILAMENT_LAYER = 0.1
LAYOUTS = {
"ansi": [
["Esc", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "Bksp", None],
"ansi_en": [
["Esc", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "⌫", 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"]
["A", "S", "D", "F", "G", "H", "J", "K", "L", ";", "'", "↵", None],
["Z", "X", "C", "V", "B", "N", "M", ",", ".", "/", "▲", "⬆", None],
["Ctrl", "Fn", "⬆", "Alt", "———", None, None, "-", "=", "◀", "▼", "▶", "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"]
"ansi_big_enter_en": [
["Esc", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "⌫", None],
["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "[", "]", "↵"],
["A", "S", "D", "F", "G", "H", "J", "K", "L", ";", "'", "\\\\", None],
["Z", "X", "C", "V", "B", "N", "M", ",", ".", "/", "▲", "⬆", None],
["Ctrl", "Fn", "⬆", "Alt", "———", None, None, "-", "=", "◀", "▼", "▶", "Ctrl"]
],
"iso_en": [
["Esc", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "⌫", None],
["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "[", "]", "↵"],
["A", "S", "D", "F", "G", "H", "J", "K", "L", ";", "'", "#", None],
["Z", "X", "C", "V", "B", "N", "M", ",", ".", "/", "▲", "\\\\", "⬆"],
["Ctrl", "Fn", "⬆", "Alt", "———", None, None, "-", "=", "◀", "▼", "▶", "Ctrl"]
],
"iso_de": [
["Esc", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "⌫", None],
["Q", "W", "E", "R", "T", "Z", "U", "I", "O", "P", "Ü", "+", "↵"],
["A", "S", "D", "F", "G", "H", "J", "K", "L", "Ö", "Ä", "#", None],
["Y", "X", "C", "V", "B", "N", "M", ",", ".", "/", "▲", "<", "⬆"],
["Ctrl", "Fn", "⬆", "Alt", "———", None, None, "ẞ", "´", "◀", "▼", "▶", "Ctrl"]
]
}
@ -28,17 +42,15 @@ def get_label_size(label: str) -> float:
:param label:
:return:
"""
if label in ['⇧', '⏎']:
return 5
elif label == '⌫':
return 3.5
elif label in ['Esc', 'Ctrl', 'Alt', 'Fn']:
if label in ["Esc", "Alt", "Fn", "Ä", "Ü", "Ö"]:
return 3
elif label == "Ctrl":
return 2.5
else:
return 4
return 3.5
def round_rect(width: float | int, height: float | int, radius: float | int):
def round_rect(width: float | int, height: float | int, radius: float | int) -> OpenSCADObject:
"""
Rectangle with rounded corners.
@ -69,3 +81,27 @@ def round_rect(width: float | int, height: float | int, radius: float | int):
+ 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))
def up(v: float) -> translate:
return translate(v=(0, v, 0))
def down(v: float) -> translate:
return translate(v=(0, v * -1, 0))
def left(v: float) -> translate:
return translate(v=(v * -1, 0, 0))
def right(v: float) -> translate:
return translate(v=(v, 0, 0))
def forward(v: float) -> translate:
return translate(v=(0, 0, v))
def backward(v: float) -> translate:
return translate(v=(0, 0, v * -1))

View file

@ -1 +1,2 @@
solidpython
click