diff --git a/generate_scad.py b/generate_scad.py index 3a19aed..6057e47 100755 --- a/generate_scad.py +++ b/generate_scad.py @@ -4,7 +4,7 @@ mkbd65 openscad generator Usage: - python3 generate_scad.py [] + python3 generate_scad.py [] Options: -h --help Show this screen. diff --git a/mkbd65/config.ini b/mkbd65/config.ini deleted file mode 100644 index 9d7ba32..0000000 --- a/mkbd65/config.ini +++ /dev/null @@ -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 diff --git a/mkbd65/key_mat.py b/mkbd65/key_mat.py index 2df8061..3890e45 100644 --- a/mkbd65/key_mat.py +++ b/mkbd65/key_mat.py @@ -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) diff --git a/mkbd65/main.py b/mkbd65/main.py index d61ae3b..1b9cba5 100755 --- a/mkbd65/main.py +++ b/mkbd65/main.py @@ -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 [LAYOUT]\n") - print("Layout can be ANSI or ISO.") - else: - main(device=sys.argv[1]) + cli() diff --git a/mkbd65/oneplus_8t.ini b/mkbd65/oneplus_8t.ini new file mode 100644 index 0000000..fe6f856 --- /dev/null +++ b/mkbd65/oneplus_8t.ini @@ -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 diff --git a/mkbd65/util.py b/mkbd65/util.py index fa586bf..80b52ac 100644 --- a/mkbd65/util.py +++ b/mkbd65/util.py @@ -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)) diff --git a/requirements.txt b/requirements.txt index 7b89a88..cf22434 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ solidpython +click \ No newline at end of file