Skip to content

Commit

Permalink
Add initial version of openscad plate generator
Browse files Browse the repository at this point in the history
  • Loading branch information
adamws committed Sep 4, 2024
1 parent 90b2167 commit 826e6d1
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 0 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,13 @@ The `kbplacer` is used by additional tools available in [tools](tools/README.md)
Example result: <a href="http://www.keyboard-layout-editor.com/##@@_a:7%3B&=&=%3B&@=&=">url</a>
</td>
</tr>
<tr>
<td>
<code>layout2openscad.py</code> - generate plate for <a href="https://openscad.org/">openscad</a></br>
(⚠️ experimental)
</td>
<td><img src="resources/example-openscad-plate.png"/></td>
</tr>
</table>

<!-- TOC --><a name="use-in-python-projects"></a>
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,11 @@ dependencies = [
"kicad-skip==0.2.5",
"pyurlon==0.1.0",
"PyYAML==6.0.1",
"shapely==2.0.6",
"solidpython==1.1.3",
]
[tool.hatch.envs.tools.scripts]
layout2image = "python tools/layout2image.py {args}"
layout2schematic = "python tools/layout2schematic.py {args}"
layout2url = "python tools/layout2url.py {args}"
layout2openscad = "python tools/layout2openscad.py {args}"
Binary file added resources/example-openscad-plate.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions tools/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
- `layout2image.py` - generate KLE style SVG image from keyboard layout
- `layout2schematic.py` - generate KiCad schematic file from keyboard layout
- `layout2url.py` - generate KLE url
- `layout2openscad.py` - generate plate for [openscad](https://openscad.org/) (:warning: experimental)

## How to run

Expand All @@ -14,6 +15,7 @@ To execute, run:
$ hatch run tools:layout2image {args...}
$ hatch run tools:layout2schematic {args...}
$ hatch run tools:layout2url {args...}
$ hatch run tools:layout2openscad {args...}
```

Alternatively, install required dependencies and run as regular python script:
Expand All @@ -22,6 +24,7 @@ Alternatively, install required dependencies and run as regular python script:
python tools/layout2image.py {args...}
python tools/layout2schematic.py {args...}
python tools/layout2url.py {args...}
python tools/layout2openscad.py {args...}
```

## Examples
Expand Down
146 changes: 146 additions & 0 deletions tools/layout2openscad.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import argparse
import json
import shutil
import sys
from enum import Enum
from pathlib import Path

import shapely
from shapely import MultiPoint, affinity, centroid, envelope
from solid import (
OpenSCADObject,
cube,
hole,
linear_extrude,
polygon,
rotate,
scad_render_to_file,
translate,
)

from kbplacer.kle_serial import Keyboard, MatrixAnnotatedKeyboard, get_keyboard

PLATE_THICKNESS = 3
HOLE_SIZE = 14
DISTANCE_1U_X = 19.05
DISTANCE_1U_Y = 19.05


class PlateShape(str, Enum):
ENVELOPE = "envelope" # minimum bounding box that encloses switches
CONVEX_HULL = "convex_hull" # minimum convex geometry that encloses switches


def _get_cube_hole(rotation):
c = cube((HOLE_SIZE, HOLE_SIZE, 2 * PLATE_THICKNESS + 2), center=True)
if rotation == 0:
return c
else:
return rotate(rotation)(c)


def generate(keyboard: Keyboard, shape: PlateShape, margin: float) -> OpenSCADObject:
switches = []
holes = []
if isinstance(keyboard, MatrixAnnotatedKeyboard):
keys = keyboard.keys_in_matrix_order()
else:
keys = keyboard.keys
keys = sorted(keys, key=lambda k: [k.y, k.x])

for k in keys:
if k.decal:
continue
p = shapely.box(k.x, -k.y, k.x + k.width, -k.y - k.height)
p = affinity.rotate(p, -k.rotation_angle, origin=(k.rotation_x, -k.rotation_y))
switches.append(p)

c = centroid(p)

plate_hole = translate(
(
DISTANCE_1U_X * c.x,
DISTANCE_1U_Y * c.y,
0,
)
)(_get_cube_hole(-k.rotation_angle))
holes.append(plate_hole)

points = []
for poly in switches:
for c in poly.exterior.coords:
points.append((c[0] * DISTANCE_1U_X, c[1] * DISTANCE_1U_Y))

if shape == PlateShape.ENVELOPE:
plate_polygon = envelope(MultiPoint(points))
else: # PlateShape.CONVEX_HULL:
plate_polygon = MultiPoint(points).convex_hull

if margin:
plate_polygon = plate_polygon.buffer(margin)

_, coords, _ = shapely.to_ragged_array([plate_polygon])
plate = linear_extrude(height=PLATE_THICKNESS)(polygon(coords))

result = plate - hole()(*holes)
return result


def load_keyboard(layout_path) -> Keyboard:
with open(layout_path, "r", encoding="utf-8") as f:
layout = json.load(f)
_keyboard = get_keyboard(layout)
try:
_keyboard = MatrixAnnotatedKeyboard(_keyboard.meta, _keyboard.keys)
_keyboard.collapse()
except Exception:
pass

return _keyboard


if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Keyboard layout file to OpenSCAD")
parser.add_argument("-in", required=True, help="Layout file")
parser.add_argument("-out", required=True, help="Output path")
parser.add_argument(
"-f",
"--force",
action="store_true",
help="Override output if already exists",
)
parser.add_argument(
"-margin",
type=float,
required=False,
default=0.0,
help="Margin too add (in mm)",
)
parser.add_argument(
"-shape",
type=PlateShape,
choices=list(PlateShape),
required=False,
default=PlateShape.ENVELOPE,
help="Plate shape",
)

args = parser.parse_args()
input_path = getattr(args, "in")
output_path = getattr(args, "out")
force = args.force
margin = args.margin
shape = args.shape

if force:
shutil.rmtree(output_path, ignore_errors=True)
elif Path(output_path).is_file():
print(
f"Output file '{output_path}' already exists, exiting...", file=sys.stderr
)
sys.exit(1)

keyboard = load_keyboard(input_path)
result = generate(keyboard, shape, margin)

scad_render_to_file(result, output_path, file_header="$fn = $preview ? 0 : 100;")

0 comments on commit 826e6d1

Please sign in to comment.