Methodology for custom tuning a concave, column-staggered, split keyboard with thumb clusters (AKA Dactyl). Centered on an OpenSCAD library for designing all sorts of ortholinear-ish keyboards supporting a variety of keycaps including Cherry profile. Additionally supports trackpoints, and 5-way navigation switches.
The goal of this project is to be a flexible API for generating ortholinear-ish (column-staggered) keyboards, and particularly split, concave keyboards often called Dactyls. You can start with my preferred keyboard and modify it to your needs, or use the API to build your own 'from scratch' without the pain of re-writing the details you don't care about.
This project starts with the assumption that one-size does not fit all, and so a keyboard user will want to tune their dactyl to achieve the greatest comfort possible. In this process, a number of design-print-test iterations will be required and while the process of 3D printing can be a significant time delay, beyond a certain point its not possible to spend extra time designing and avoid the need to print and test. Given this, it makes sense to optimize the modeling software for faster iteration time at both the design and print level, and to expose both easy configuration for bootstrapping and more fine-grained control for tuning. This way you can Try All (the) Dactyls!
I also aim to document an easy approach to handwiring that both simplifies the initial process and allows easy reuse of the electronics as you switch from case to case. You can find a brief summary here.
These are abbreviated descriptions of what lives where. See the linked files for detailed documentation comments.
Keys - the most basic building blocks of a keyboard (you are unlikely to need these directly)
key_mount()
is the basic switch holder. there are also various bounding boxes around the sides of the keywell used to stitch keywells together into a switch plate, and to generate sidewalls for a case when desired.keycap()
model manages caching of pre-rendered .stls when possible and by default hides the caps when rendering a keyboard for printing. position_flat()
orients a keycap()
, or key_mount()
with the origin at the center of the top of the keycap. effective_rows()
produces the data structure used to map row/column indexes to keycap profile rows used by a given layout, such as a keyboard without number row that uses only R2, R3, R4.Columns - the main unit of organization for our designs, one or more columns per finger
layout_columns(rows, cols, homerow, homecol, ..., keys, wells, walls, ..., params)
is the main module exposed to the user for creating a keyboard. It creates a connected plate of cols
key columns with rows
rows. The homerow
and homecol
give the coordinates of the key that is centered on the origin. This is mostly important for curved placements. The boolean keys
can be used to disable display of keycaps in previews, wells
controls generation of the switch plate, and walls
controls the creation of walls, which are independent of the switch plate. The booleans leftwall
, rightwall
, topwall
and bottomwall
control a separate way to generate simple, regular walls that are reasonable for flat keyboards with a shallow tent and tilt. A wall_matrix
may be specified instead to give per-key control of the wall sides and distance (including multiple articulation points). The params struct is generated by the function below.layout_placement_params()
produces a pseudo-struct containing all the user supplied configuration parameters, for convenience. layout_placement()
is the low level module all the higher level APIs use to uniformly place child modules at row, column coordinates, using the placement_params. The functions create_flat_placement()
and create_circular_placement()
create structs passed to layout_placement_params()
for row or column alignment. The flat placement take an [x, y] pair (or vector of pairs, or vector of vector of pairs) specifying distance in millimeters. The circular placement takes a [chord, radius, angle] triplet (or vectors thereof) specifying the relationship between two points on a circle, using any two of: the the shortest-line distance between the points (a chord), the radius of the circle, or the angle between the two points from the center of the circle. normalize_chord()
calculates the missing parameter from the other two, but should be automatically called anywhere a chord is accepted to sanitize the input. If a single value is provided to these functions, the spacing between successive rows or columns will be uniform. If a vector of values are provided, the distance used for each successive row or column relative to the previous can be tweaked. using a vector of vectors allows it to vary both per-row and per-column. missing rows, or columns, will be substituted with the last specified value. The optional_vector_index()
automates this lookup-with-substitution process. optional_index()
is the version used for scalars, like rows
.Accessories
wrist_rest_base()
is the model for @crystalhand's holder for a silicone palmrest. this file invokes it and adds mounting dimples for rubber feet and a flush mount for a bar-shaped cup magnet. (TODO add @kennykaye style rest)install_trackpoint(row, col, ... displacement, params, ... use_shield ...)
places a mount for a trackpoint so that the stem rises through the intersection between keys nearer to the homerow
and homecol
of the key specified by row
and col
. displacement
can be used to tweak this positioning without having to modify the params
used for the rest of the switch plate. h1
and h2
can be used to move the arms of the trackpoint mount so they contact the switch plate and don't collide with the switches. use_shield
enables a cylindrical covering to protect the trackpoint stem extension, if the trackpoint is not fully encircled by keys.Keyboards
screw_mounting()
module and associated screw_mounting_params()
function for adding screw-attached support struts between switch plate and base plateTo view the keyboard assembled, run openscad assembly.scad
. This is how I normally make edits. Within assembly.scad the base plate is generated in debug mode which just shows the strut mount placement, so that strut heights can be manually adjusted, and so that renders are faster. To see the full keyboard, find the line base_plate(debug=true);
and change to false
.
For printing, the assembly/fingers.scad
, assembly/plate.scad
and assembly/thumb.scad
models can be used to obtain separate parts. trying to print the whole assembly as is would be unlikely to succeed. I like to use the commandline to invoke openscad
for renders, like the following: openscad -q --hardwarnings --render -o things/plate.stl assembly/plate.scad
.
To validate that changes to the library code have not introduced regressions, run make test
prior to editing (or after a git stash
), which will generate reference .stls for later comparison. Run make test
again after editing to see if the tests still produce identical .stls. New tests should be added when new positioning functions, keycap profiles, or other features are introduced. The tester models should also be useful to those who might like to print a smaller model to consider the feel of a given column shape without wasting time and material printing a full keyboard.
Right now testing is a bit fiddly. Edit testers/wrapper.py
to point SCAD_EXE
at a nightly build of openscad, which has the sort-stl feature. Even with that, .stl files aren't always consistent. The alternative approach also has issues. Blog post incoming :)
Why not just use a flat keycap profile and save yourself a lot of work?
Using a flat profile doesn't actually solve the problem. For example, when you do cylindrical placement of the switch holders, rather than the keycap top surfaces, the radius does not correspond to measurements of the fingers and their movement. The radius is longer because the height of the switch and keycap can be a sizable percentage of the finger length. Rather than calculating this offset manually, it makes sense for the software to make the compensation for you.
I'm not aware of a cylindrical topped (rather than spherical) flat MX profile, which I prefer.
Sculpted profiles tend to have angled sides, and top surfaces that are noticeably smaller than a full 1u footprint, which lets you pack them a bit more tightly if you aren't looking for a full 19.05 mm spacing between keys. This also seems particularly important for the spherical key placement i use, because its more at risk of keycap collisions than cylindrical. However, I haven't yet investigated the spherical placement with a flat profile, so maybe it's less of a concern than it seems.
Many colorways are limited to specific (sculpted) profiles. I started this project after being completely blown away by the GMK Copper renders, for example.
Flexibility: my design can accommodate flat profiles. It may even give the most pleasing results that way. It can also accommodate sculpted profiles. It could even be used to add a sculpted effect to a flat profile (or change the sculpt of a sculpted profile). To each their own, as they say.
Cherry profile R3 has a lower vertical height that DSA or XDA. Obviously chocs are a superior choice for low profile switches, but I don't like linear switches and I love my Kailh Speed Copper switches, so Cherry profile helps me shave off some height.
mxstem()
to incorporate switch typeinnerdia
, outerdia
and thickness
spacer()
from assembly.scad level use
keywell()
to ???switch_holder()
??? to reflect that keywell is generally accepted to mean concavity