Welcome back! We're in the home stretch now. Well, home-ish stretch. If you need catching up: The series introduction introduced us to the world of Ergogen, Part 1 created the points that define out layout, and Part 2 created some useful outlines for our keyboard. In Part 3 we created the basis of our keyboard's PCB, defined the footprints for our parts, and connected them all together with nets. If you're familiar with KiCAD, you can probably run off and trace the routes on your own at this point. Slap some rubber feet on the bottom of the keyboard and call it a day!
In the interest of completeness however, I'd like to take a side trip over to the last few Ergogen topics I haven't touched on just yet. Chief among these is the "Cases" section. Ergogen has functionality to export outlines as 3D objects with a depth component. The web implementation of Ergogen doesn't support exporting these files just yet however, which gives us an excuse to dabble in the world of a locally installed, offline Ergogen.
Even if you don't own a 3D printer and aren't interested in creating a case for your keyboard, running a local copy of Ergogen allows you to import custom footprints from outside of the main Ergogen GitHub repository. This chapter will use that functionality to import a mounting hole footprint, but the same theory could apply to a different microcontroller, toggle switch, or rotary encoder. Part 4 still has something for everyone.
Commanding the Line
Installing Ergogen locally on your computer is thankfully a fairly straightforward process. Ergogen is written in the popular cross-platform NodeJS scripting language. As the name implies, Node leverages the Javascript, making it easily adaptable to Windows machines, Mac laptops, or Linux setups. As we saw earlier, it's also what makes it easy to create a web implementation for Ergogen.
Node install instructions vary over time and by platform. Your best guess is to mosey on over to NodeJS.org and follow their instructions there. If you're on a Mac or Linux system, you may also want to look into the Node Version Manager application if you haven't already. nvm can save you some versioning headaches in the long run if you start using Node more regularly.
Installing Node nets you more than just the scripting environment. You also get the Node Package Manager. Rather than having to fiddle with a bunch of application files and install scripts, npm lets you easily download and install Node packages from npmjs.com. These packages can be installed in one or two ways: Locally or Globally.
A local package install works like most software. It creates a folder in a specified location on your hard drive, and to use that package you need to navigate to that folder. This is useful for programs that have very isolated contexts, or apps that you may install multiple instances of.
In Ergogen's case, we don't want a single isolated instance of the tool. We want to be able to run it inside of any of our future keyboard project folders like a standard command line utility. This means we should perform a global install for Ergogen. This installs Ergogen to a Node system folder, and allows us to call the utility from anywhere on the command line.
Enough command line background, let's get to it! Assuming your NodeJS install is up and running, open a command line and run:
npm i -g ergogen
npm
calls the Node Package Manager, i
is short for install, -g
is the global flag, and ergogen
is the name of the package we want to install. Easy peasy.
Now that we've got Ergogen installed, go ahead and do whatever it is you usually do when starting a new coding project. In my case, I'm creating a new folder inside of ~/pcb
on my system called /tutorial
. This is where my Ergogen config will live, and where all the outputs will be placed. I'm also running a quick git init
to create a local git repository in case I accidentally screw something up and want to roll back to an earlier state. Finally, create a file called config.yaml
inside of your project folder and copy the contents of your ergogen.ceoloide.com config into it.
Now's the moment of truth. Let's run Ergogen!
ergogen config.yaml
ergogen
is the name of our command, and config.yaml
is the name of your config file. If everything goes smoothly, Ergogen will create an outputs
folder and put our finished files inside of it. If you'd like to use an outputs folder with a different name, you can use the -o output_folder_name
option to specify your own.
With that, we should be back to where we were with the web UI. We've got all of our .dxf outline files, as well as the .kicad_pcb file.
So far running things locally hasn't gotten us anything astounding yet. The web UI is useful for generating points and outlines, while the local instance can make constantly reloading new KiCAD files a bit easier. There are a few tricks up the local install's sleeve however. Let's first take a look at loading external footprints.
Expanding The Footprint Clan
Wow this section header is going to date me.Ergogen comes with a useful assortment of component footprints. At some point however you'll most likely want to add something a bit custom to your keyboard. In the old Ergogen v3 era you would need to fork Ergogen, add your additional footprints, and then run your new custom fork locally.
Thankfully in Ergogen v4, there's now a dedicated method for referencing external footprints. As of the time of writing this guide, this technique only works on local instances of Ergogen. The code's already there to handle bundled .zip footprint collections however, so it's only a matter of time until the web instance gets updated as well.
For this external footprint example, I'm going to use MvEerd's M2 mounting hole footprint. Creating your own custom footprint is a bit outside of the scope of this tutorial, but they're written in fairly straightforward javascript files. If you need to make a small tweak to an existing footprint, it shouldn't be too difficult to fumble your way through it. There are a lot of community footprints at this point, so poke around Github to see what else is out there, or hit up the Ergogen Discord.
Go ahead and download the M2 mounting hole footprint. ("M2" is metric shorthand for a screw with a 2mm diameter.) If you haven't downloaded individual files from GitHub before, you'll need to right click on the Raw button and download it to your computer. Make sure it keeps the mountinghole.js
name. Some browsers like adding .txt onto the end of .js.
Create a new folder inside of your project folder called footprints
. Place mountinghole.js
inside of it.
This screenshot shows the current structure of our project file. There's a few things to note here when working with external footprints. Ergogen has historically allowed you to use any file name for your .yaml config. If you named your config tutorial.yaml
, all you would need to do is call ergogen tutorial.yaml
for Ergogen to parse the file.
That's not the case here. When working with external footprints, it's required that you use the filename config.yaml
for your Ergogen config. Secondly, we need to change how we call Ergogen. Instead of the usual ergogen config.yaml
command, we need to pass our entire folder to Ergogen.
This can be accomplished easily enough. But first, let's add an example mounting hole to our config.yaml
file.
units: ...
points: ...
outlines: ...
pcbs:
tutorial:
outlines:
main:
outline:
board
footprints:
choc_hotswap: ...
diode: ...
promicro: ...
oled: ...
reset: ...
holeTopLeft:
what: mountinghole
where:
ref: [matrix_outer_num]
This should be pretty standard at this point. holeTopLeft:
is the name of our footprint. what: mountinghole
indicates that we'd like to use mountinghole.js for the footprint. where.ref: [matrix_outer_num]
places the footprint smack dab in the middle of the top left key.
The trick here is that mountinghole.js isn't in the Ergogen GitHub repository. It's part of our local project folder. Thankfully we don't need to use any special syntax as long as we place the footprint files in the correct location. Without further ado, let's fire up Ergogen and provide it with our project folder.
ergogen .
That's a bit of an anticlimax. On command lines, a single dot is shorthand for "the current folder that I'm in. By calling Ergogen this way, we're telling it that it needs to look at both the config.yaml file and all of the .js files inside of the ./footprints
folder.
There's our mounting hole! You need to make sure all your ducks are in a row, but working with external footprints is a huge step up in Ergogen v4. I cannot stress enough that you need to call Ergogen with ergogen .
instead of ergogen config.yaml
There's a ton of examples and documentation online for v3 with the config file explicitly called out. Don't get tripped up!
Mounting Holes
Mounting holes are a convenient way of attaching a case to a PCB. Our custom footprint is honestly a bit overkill. We could have just created a few circle outlines and drilled them out of our PCB as edge cuts. (You'll see this from time to time if you go poking around Github.) But now that we've got a properly drilled mounting hole, let's go ahead and add a few more.
units: ...
points: ...
outlines: ...
pcbs:
tutorial:
outlines:
main:
outline:
board
footprints:
choc_hotswap: ...
diode: ...
promicro: ...
oled: ...
reset: ...
holeTopLeft:
what: mountinghole
where:
ref: [matrix_outer_num]
holeTopInnerLeft:
what: mountinghole
where:
ref: [matrix_index_num]
holeBottomInnerLeft:
what: mountinghole
where:
ref: [matrix_index_mod]
holeTopRight:
what: mountinghole
where:
ref: [mirror_matrix_outer_num]
holeBottomRight:
what: mountinghole
where:
ref: [mirror_matrix_outer_bottom]
holeTopInnerRight:
what: mountinghole
where:
ref: [mirror_matrix_index_num]
holeBottomInnerRight:
what: mountinghole
where:
ref: [mirror_matrix_index_mod]
Things are looking good so far, but we've got the classic issue of having placed out footprints directly inside of one of our key switches. Let's add some shifts to move these mounting holes over.
units: ...
points: ...
outlines: ...
pcbs:
tutorial:
outlines:
main:
outline:
board
footprints:
choc_hotswap: ...
diode: ...
promicro: ...
oled: ...
reset: ...
holeTopLeft:
what: mountinghole
where:
ref: [matrix_outer_num]
shift: [0.5kx, -0.3ky]
holeBottomLeft:
what: mountinghole
where:
ref: [matrix_outer_bottom]
shift: [0.5kx, -0.3ky]
holeTopInnerLeft:
what: mountinghole
where:
ref: [matrix_index_num]
shift: [0.5kx, -0.4ky]
holeBottomInnerLeft:
what: mountinghole
where:
ref: [matrix_index_mod]
shift: [0.5kx, -0.35ky]
holeTopRight:
what: mountinghole
where:
ref: [mirror_matrix_outer_num]
shift: [0.5kx, -0.3ky]
holeBottomRight:
what: mountinghole
where:
ref: [mirror_matrix_outer_bottom]
shift: [0.5kx, -0.3ky]
holeTopInnerRight:
what: mountinghole
where:
ref: [mirror_matrix_index_num]
shift: [0.5kx, -0.4ky]
holeBottomInnerRight:
what: mountinghole
where:
ref: [mirror_matrix_index_mod]
shift: [0.5kx, -0.35ky]
There we go! Our mounting holes have been nudged over between the keys, and shifted down slightly so that they're tucked away between the diodes. This design might be a little tight if you're using through hole diodes, but it should work fine for smd diodes.
That wraps up our last big change to the PCB. The important thing to note here is that we can use these same where:
values when constructing our PCB to know exactly where each of these mounting holes go.
Making A Case
3D printing is an art form of its own. People familiar with CAD tools most likely have some idea of the type of case they'd like to design. This section isn't going to be an exhaustive treatise on keyboard case design. We're going to quickly slap something together in Ergogen to cover the bottom of our keyboard and give it a bit of color.
Let's define a simple 3D model now.
units: ...
points: ...
outlines:
raw: ...
keys: ...
board: ...
combo: ...
pcbs: ...
cases:
bottom:
- name: board
extrude: 1
After everything we just went through with the PCB, cases are surprisingly straightforward. The cases:
section is similar to the outlines:
section of our Ergogen config file. Instead of creating flat .dxf files, it creates 3D files with depth.
The bottom:
entry is a name we gave this particular 3D model. - name: board
refers to the outline we defined earlier. Finally, extrude: 1
tells Ergogen to create a 3D model that is 1mm tall.
Ergogen v3 previously exported cases files in the .stl lingua franca format of 3D printing. Due to some technical issues with the .stl file creation library, Ergogen v4 exports cases as .jscad files. If you'd like to convert these into .stl files, you can use sites like openjscad.xyz or their NPM package. We're already working on the command line, so let's leverage their command line package.
npx @jscad/cli@1 output/cases/bottom.jscad -of stla -o bottom.stl
This line uses the Node Package Manager's npx
command to run JSCAD as an executable. It's a whole topic worth looking into on its own, but the short story is that the above command will convert a .jscad into an .stl files for us.
If we want to run both Ergogen and JSCAD with one terminal command, we can string them together with:
ergogen . && npx @jscad/cli@1 output/cases/bottom.jscad -of stla
This will only match the one part name of bottom.jscad
however. If we'd like to get real fancy, the following command will process your Ergogen files and then convert every .jscad file in your outputs folder. Sometimes it's hard to beat a good old fashioned for loop. (The output name is implicitly understood to be the same as the input name here.)
ergogen . && for i in output/cases/*.jscad; do npx @jscad/cli@1 "$i" -of stla; done
After all that, we've got a 1mm thick version of our bottom outline. Hurray?
Let's see if we can add some walls to our case design. First up, we need an outline of our board that's a few mm larger than the actual PCB design.
units:
# Proxy Spacing Variables
kx: cx
ky: cy
# Padding Variables
px: kx + 4
py: ky + 4
# Double Padding Variables
dpx: kx + 8
dpy: ky + 8
points: ...
outlines:
raw: ...
keys: ...
board: ...
xlBoard:
- what: polygon
operation: stack
points:
- ref: matrix_outer_num
shift: [-0.5dpx,0.5dpy]
- ref: matrix_ring_num
shift: [-0.5dpx,0.5dpy]
- ref: matrix_middle_num
shift: [-0.5dpx,0.5dpy]
- ref: matrix_middle_num
shift: [0.5dpx,0.5dpy]
- ref: matrix_inner_num
shift: [0.5dpx,0.5dpy]
- ref: matrix_inner_top
shift: [0.5dpx,0.5dpy]
- ref: mirror_matrix_inner_top
shift: [0.5dpx,0.5dpy]
- ref: mirror_matrix_inner_num
shift: [0.5dpx,0.5dpy]
- ref: mirror_matrix_middle_num
shift: [0.5dpx,0.5dpy]
- ref: mirror_matrix_middle_num
shift: [-0.5dpx,0.5dpy]
- ref: mirror_matrix_ring_num
shift: [-0.5dpx,0.5dpy]
- ref: mirror_matrix_outer_num
shift: [-0.5dpx,0.5dpy]
- ref: mirror_matrix_outer_bottom
shift: [-0.5dpx,-0.5dpy]
- ref: mirror_matrix_ring_mod
shift: [-0.5dpx,-0.5dpy]
- ref: mirror_thumbs_layer_cluster
shift: [-0.5dpx,-0.5dpy]
- ref: mirror_thumbs_space_cluster
shift: [-0.5dpy,-0.5dpx]
- ref: mirror_thumbs_space_cluster
shift: [0.5dpy,-0.5dpx]
- ref: thumbs_space_cluster
shift: [0.5dpy,-0.5dpx]
- ref: thumbs_space_cluster
shift: [-0.5dpy,-0.5dpx]
- ref: thumbs_layer_cluster
shift: [-0.5dpx,-0.5dpy]
- ref: matrix_ring_mod
shift: [-0.5dpx,-0.5dpy]
- ref: matrix_outer_bottom
shift: [-0.5dpx,-0.5dpy]
fillet: 2
combo: ...
pcbs: ...
cases:
bottom:
- name: board
extrude: 1
xlBottom:
- name: xlBoard
extrude: 1
Our good buddy units is back! Our original board outline used the px
and py
padding variables to make a board that was 2mm wider than it absolutely needed to be. dpx
and dpy
are adding another 2px onto the outside of our new xlBoard
outline. Just as before we were able to create a 1mm case file called xlBottom
.
...okay. So we have a slightly bigger bottom board. Does that get us anything? Well, cases can be added and subtracted to just like we did with the combo
outline in Part 2. If we subtracted board
from xlBoard
, we'd start getting something that resembles a wall.
cases:
bottom:
- name: board
extrude: 1
xlBottom:
- name: xlBoard
extrude: 1
_outerWall:
- name: xlBoard
extrude: 4
_innerWall:
- name: board
extrude: 4
wall:
- what: case
name: _outerWall
operation: add
- what: case
name: _innerWall
operation: subtract
Now that's starting to look like a case! We even got to introduce a bit more syntactic flare to our config. Prefixing an outline or case with an underscore will signal to Ergogen that it doesn't need to export a particular asset to our outputs files. It can still be referenced by other cases however, which is exactly what we did here.
_outerWall
and _innerWall
were 4mm repeats of what we've seen before. wall
is a bit new however. Instead of creating a new case object, we combined two together. The what:
property defaults to an outline when working with cases, so we had to be explicit here that we wanted to refer to one of our earlier case files. name:
tells Ergogen which case files we'd like to reference. operation:
meanwhile tells Ergogen what we'd like to do to those particular case file. add
is the default implicit option, but in this case we wanted to use operation: subtract
to remove the inner option of our larger outline.
cases:
bottom:
- name: board
extrude: 1
xlBottom:
- name: xlBoard
extrude: 1
_outerWall:
- name: xlBoard
extrude: 4
_innerWall:
- name: board
extrude: 4
wall:
- what: case
name: _outerWall
operation: add
- what: case
name: _innerWall
operation: subtract
case:
- what: case
name: xlBottom
operation: add
- what: case
name: wall
operation: add
One more quick addition, and now we've got a nice stable base with some sturdy looking walls. This case
piece should make a good basis for the rest of our build. Now we just need to address those mounting holes.
units:
# Proxy Spacing Variables
kx: cx
ky: cy
# Padding Variables
px: kx + 4
py: ky + 4
# Double Padding Variables
dpx: kx + 8
dpy: ky + 8
# Defaults to M2 Screws
screwSize: 1
points: ...
outlines:
raw: ...
keys: ...
board: ...
xlBoard: ...
combo: ...
mounting:
- what: circle
radius: screwSize
where:
ref: [matrix_outer_num]
shift: [0.5kx, -0.3ky]
- what: circle
radius: screwSize
where:
ref: [matrix_outer_bottom]
shift: [0.5kx, -0.3ky]
- what: circle
radius: screwSize
where:
ref: [matrix_index_num]
shift: [0.5kx, -0.4ky]
- what: circle
radius: screwSize
where:
ref: [matrix_index_mod]
shift: [0.5kx, -0.35ky]
- what: circle
radius: screwSize
where:
ref: [mirror_matrix_outer_num]
shift: [0.5kx, -0.3ky]
- what: circle
radius: screwSize
where:
ref: [mirror_matrix_outer_bottom]
shift: [0.5kx, -0.3ky]
- what: circle
radius: screwSize
where:
ref: [mirror_matrix_index_num]
shift: [0.5kx, -0.4ky]
- what: circle
radius: screwSize
where:
ref: [mirror_matrix_index_mod]
shift: [0.5kx, -0.35ky]
pcbs: ...
cases:
bottom: ...
xlBottom: ...
_outerWall: ...
_innerWall: ...
wall: ...
_holes:
- name: mounting
extrude: 4
case:
- what: case
name: xlBottom
operation: add
- what: case
name: _holes
operation: add
- what: case
name: wall
operation: add
Another giant code block, but most of this should start to look a little familiar. To start with I added a new unit called screwSize: 1
to use as the radius of our screws. I then created a new outline called mounting:
which reused the where.ref:
and where.shift:
values from our mounting holes on the PCB. (This mounting
outline is a good example of including multiple shapes within a single outline by the way.) Finally, I created a new case called _holes
and added it to our final case
part.
You could keep going with this approach and create a basic plastic standoff design to slot onto the PCB, but we can get a much sturdier design by introducing a few extra pieces of hardware.
Threaded screw inserts are small pieces of metal with a screw hole in the middle. They're designed to let you add a sturdy mounting point on wooden or plastic components. They're 3.2mm wide, and 3mm tall. All you need to do to add them to your design is create a hole that's 3mm in diameter and 3mm tall. Then you use a soldering iron to warm up the screw insert and melt the last 0.2mm of plastic. Once they're in place, you just need a standard M2 laptop-style flathead screw to hold the keyboard in place.
To add support for these threaded screw inserts to our design, we need to define a second set of slightly larger mounting holes, and then subtract the inner holes from the outside holes like we did for the wall part. The outer walls will be 5mm thick, with a 3mm hollow inside.
Incidentally, these threaded screw inserts are why I had been using 4mm for some of the heights in the case section. 1mm for the base of the case plus 3mm for the standoff height is 4mm. You could get a little thinner without the standoffs, but the hotswap sockets are already 2mm thick. This design is only making the case 2mm thicker than the bare PCB itself.
Double-checking all the number just now made me realize I had undershot the wall height. The walls currently come up to the bottom of the PCB, but I want it to be flush with the top. We need to add the 1.6mm height of the PCB to the walls. Let's build our threaded screw insert standoffs and fix those heights.
units:
# Proxy Spacing Variables
kx: cx
ky: cy
# Padding Variables
px: kx + 4
py: ky + 4
# Double Padding Variables
dpx: kx + 8
dpy: ky + 8
# M2 Screw Inserts
screwSize: 1.5
standoffSize: 2.5
points: ...
outlines:
raw: ...
keys: ...
board: ...
xlBoard: ...
combo: ...
mounting
standoff:
- what: circle
radius: standoffSize
where:
ref: [matrix_outer_num]
shift: [0.5kx, -0.3ky]
- what: circle
radius: standoffSize
where:
ref: [matrix_outer_bottom]
shift: [0.5kx, -0.3ky]
- what: circle
radius: standoffSize
where:
ref: [matrix_index_num]
shift: [0.5kx, -0.4ky]
- what: circle
radius: standoffSize
where:
ref: [matrix_index_mod]
shift: [0.5kx, -0.35ky]
- what: circle
radius: standoffSize
where:
ref: [mirror_matrix_outer_num]
shift: [0.5kx, -0.3ky]
- what: circle
radius: standoffSize
where:
ref: [mirror_matrix_outer_bottom]
shift: [0.5kx, -0.3ky]
- what: circle
radius: standoffSize
where:
ref: [mirror_matrix_index_num]
shift: [0.5kx, -0.4ky]
- what: circle
radius: standoffSize
where:
ref: [mirror_matrix_index_mod]
shift: [0.5kx, -0.35ky]
pcbs: ...
cases:
bottom: ...
xlBottom: ...
_outerWall:
- name: xlBoard
extrude: 5.6
_innerWall:
- name: board
extrude: 5.6
wall: ...
_holes:
- name: mounting
extrude: 4
_standoffs:
- name: standoff
extrude: 4
case:
- what: case
name: _standoffs
operation: add
- what: case
name: _holes
operation: subtract
- what: case
name: xlBottom
operation: add
- what: case
name: wall
operation: add
The threaded screw insert standoffs are looking great! With that, our case is finished! We've got slightly more than a minimum viable case without ever having to leave Ergogen.
If you've got something a bit more complicated in mind, it may be worth pivoting to CAD software at some point. There are beginner friendly options like Autodesk's TinkerCAD software, or more high end suites like Autodesk Fusion 360 for creating impressive beveled edges. Even if your end product isn't created from Ergogen alone, the .dxf and .stl files generated by it provide great starting points. For a more focused guide on keyboard case design theory, check out Sadek Baroudi excellent KBD News article on the topic.
It's worth noting that if you're building a unibody keyboard like the one we designed in this tutorial, there's a good chance your keyboard will be larger than your 3D printer's print area. This case design is 273mm wide, while my printer bed is only 200mm wide. You'll probably need to split your case into at least two pieces. Each half of this design has four well-spaced standoffs, so we should be fine here.
End of Part 4
units:
# Proxy Spacing Variables
kx: cx
ky: cy
# Padding Variables
px: kx + 4
py: ky + 4
# Double Padding Variables
dpx: kx + 8
dpy: ky + 8
# Defaults to M2 Screws
screwSize: 1.5
standoffSize: 2.5
points:
zones:
# The primary 6x4 key matrix, plus 3 modifiers.
matrix:
# Position in center of KiCAD workspace.
anchor:
shift: [100, -100]
# Choc spacing
key:
padding: 1ky
spread: 1kx
columns:
# Hide the first two mods and the last mod.
# Provide a Sofle-like column stagger.
outer:
rows.mod.skip: true
key.column_net: P14
pinky:
rows.mod.skip: true
key.column_net: P16
ring:
key:
stagger: 5
column_net: P10
rows.mod.column_net: P16
middle:
key:
stagger: 2.5
column_net: P7
rows.mod.column_net: P10
index:
key:
stagger: -2.5
column_net: P8
rows.mod.column_net: P7
inner:
rows.mod.skip: true
key:
stagger: -2.5
column_net: P9
rows:
# Four main rows, one partial row.
mod:
row_net: P15
mirror.row_net: P6
bottom:
row_net: P18
mirror.row_net: P5
home:
row_net: P19
mirror.row_net: P4
top:
row_net: P20
mirror.row_net: P0
num:
row_net: P21
mirror.row_net: P1
# Thumb cluster for Layer and Space keys.
thumbs:
# Choc spacing
key:
padding: 1ky
spread: 1kx
# Place thumbs where the inner mod would go.
anchor:
ref: matrix_inner_mod
shift: [2, -2]
columns:
# Fan thumbs out by -15 degrees.
layer:
key:
splay: -15
column_net: P8
# Spacebar uses a 1.5 wide key.
space:
key:
width: 1.5kx
splay: 75
shift: [2.5,-3.25]
column_net: P9
rows:
# Thumbs only have one row.
cluster:
row_net: P15
mirror.row_net: P6
# Mirror keyboard halves with a moderate rotation.
rotate: -20
mirror: &mirror
ref: matrix_inner_num
distance: 2kx
outlines:
# Pure key outline.
raw:
- what: rectangle
where: true
size: [px, py]
# Key outlines with 0.5mm removed to show key overlaps.
keys:
- what: rectangle
where: true
size: [kx-0.5,ky-0.5]
# PCB board outline.
board:
- what: polygon
operation: stack
points:
- ref: matrix_outer_num
shift: [-0.5px,0.5py]
- ref: matrix_ring_num
shift: [-0.5px,0.5py]
- ref: matrix_middle_num
shift: [-0.5px,0.5py]
- ref: matrix_middle_num
shift: [0.5px,0.5py]
- ref: matrix_inner_num
shift: [0.5px,0.5py]
- ref: matrix_inner_top
shift: [0.5px,0.5py]
- ref: mirror_matrix_inner_top
shift: [0.5px,0.5py]
- ref: mirror_matrix_inner_num
shift: [0.5px,0.5py]
- ref: mirror_matrix_middle_num
shift: [0.5px,0.5py]
- ref: mirror_matrix_middle_num
shift: [-0.5px,0.5py]
- ref: mirror_matrix_ring_num
shift: [-0.5px,0.5py]
- ref: mirror_matrix_outer_num
shift: [-0.5px,0.5py]
- ref: mirror_matrix_outer_bottom
shift: [-0.5px,-0.5py]
- ref: mirror_matrix_ring_mod
shift: [-0.5px,-0.5py]
- ref: mirror_thumbs_layer_cluster
shift: [-0.5px,-0.5py]
- ref: mirror_thumbs_space_cluster
shift: [-0.5py,-0.5px]
- ref: mirror_thumbs_space_cluster
shift: [0.5py,-0.5px]
- ref: thumbs_space_cluster
shift: [0.5py,-0.5px]
- ref: thumbs_space_cluster
shift: [-0.5py,-0.5px]
- ref: thumbs_layer_cluster
shift: [-0.5px,-0.5py]
- ref: matrix_ring_mod
shift: [-0.5px,-0.5py]
- ref: matrix_outer_bottom
shift: [-0.5px,-0.5py]
fillet: 2
# Extra Large PCB board outline.
xlBoard:
- what: polygon
operation: stack
points:
- ref: matrix_outer_num
shift: [-0.5dpx,0.5dpy]
- ref: matrix_ring_num
shift: [-0.5dpx,0.5dpy]
- ref: matrix_middle_num
shift: [-0.5dpx,0.5dpy]
- ref: matrix_middle_num
shift: [0.5dpx,0.5dpy]
- ref: matrix_inner_num
shift: [0.5dpx,0.5dpy]
- ref: matrix_inner_top
shift: [0.5dpx,0.5dpy]
- ref: mirror_matrix_inner_top
shift: [0.5dpx,0.5dpy]
- ref: mirror_matrix_inner_num
shift: [0.5dpx,0.5dpy]
- ref: mirror_matrix_middle_num
shift: [0.5dpx,0.5dpy]
- ref: mirror_matrix_middle_num
shift: [-0.5dpx,0.5dpy]
- ref: mirror_matrix_ring_num
shift: [-0.5dpx,0.5dpy]
- ref: mirror_matrix_outer_num
shift: [-0.5dpx,0.5dpy]
- ref: mirror_matrix_outer_bottom
shift: [-0.5dpx,-0.5dpy]
- ref: mirror_matrix_ring_mod
shift: [-0.5dpx,-0.5dpy]
- ref: mirror_thumbs_layer_cluster
shift: [-0.5dpx,-0.5dpy]
- ref: mirror_thumbs_space_cluster
shift: [-0.5dpy,-0.5dpx]
- ref: mirror_thumbs_space_cluster
shift: [0.5dpy,-0.5dpx]
- ref: thumbs_space_cluster
shift: [0.5dpy,-0.5dpx]
- ref: thumbs_space_cluster
shift: [-0.5dpy,-0.5dpx]
- ref: thumbs_layer_cluster
shift: [-0.5dpx,-0.5dpy]
- ref: matrix_ring_mod
shift: [-0.5dpx,-0.5dpy]
- ref: matrix_outer_bottom
shift: [-0.5dpx,-0.5dpy]
fillet: 2
# Combination preview showing outline and keys.
combo:
- name: board
- operation: subtract
name: keys
mounting:
- what: circle
radius: screwSize
where:
ref: [matrix_outer_num]
shift: [0.5kx, -0.3ky]
- what: circle
radius: screwSize
where:
ref: [matrix_outer_bottom]
shift: [0.5kx, -0.3ky]
- what: circle
radius: screwSize
where:
ref: [matrix_index_num]
shift: [0.5kx, -0.4ky]
- what: circle
radius: screwSize
where:
ref: [matrix_index_mod]
shift: [0.5kx, -0.35ky]
- what: circle
radius: screwSize
where:
ref: [mirror_matrix_outer_num]
shift: [0.5kx, -0.3ky]
- what: circle
radius: screwSize
where:
ref: [mirror_matrix_outer_bottom]
shift: [0.5kx, -0.3ky]
- what: circle
radius: screwSize
where:
ref: [mirror_matrix_index_num]
shift: [0.5kx, -0.4ky]
- what: circle
radius: screwSize
where:
ref: [mirror_matrix_index_mod]
shift: [0.5kx, -0.35ky]
standoff:
- what: circle
radius: standoffSize
where:
ref: [matrix_outer_num]
shift: [0.5kx, -0.3ky]
- what: circle
radius: standoffSize
where:
ref: [matrix_outer_bottom]
shift: [0.5kx, -0.3ky]
- what: circle
radius: standoffSize
where:
ref: [matrix_index_num]
shift: [0.5kx, -0.4ky]
- what: circle
radius: standoffSize
where:
ref: [matrix_index_mod]
shift: [0.5kx, -0.35ky]
- what: circle
radius: standoffSize
where:
ref: [mirror_matrix_outer_num]
shift: [0.5kx, -0.3ky]
- what: circle
radius: standoffSize
where:
ref: [mirror_matrix_outer_bottom]
shift: [0.5kx, -0.3ky]
- what: circle
radius: standoffSize
where:
ref: [mirror_matrix_index_num]
shift: [0.5kx, -0.4ky]
- what: circle
radius: standoffSize
where:
ref: [mirror_matrix_index_mod]
shift: [0.5kx, -0.35ky]
pcbs:
tutorial:
outlines:
main:
outline:
board
footprints:
# Hotswap Choc keys.
choc_hotswap:
what: choc
where: true
params:
keycaps: true
reverse: false
hotswap: true
from: "{{column_net}}"
to: "{{colrow}}"
# Through Hole or SMD Diodes
diode:
what: diode
where: true
params:
from: "{{colrow}}"
to: "{{row_net}}"
adjust:
shift: [0, -5]
# Face Down Arduino Pro Micro
promicro:
what: promicro
params:
orientation: "down"
where:
ref.aggregate.parts: [matrix_inner_home, mirror_matrix_inner_home]
shift: [0,0]
rotate: -90
# OLED Screen
oled:
what: oled
params:
side: "F"
SDA: P2
SCL: P3
where:
ref.aggregate.parts: [matrix_inner_home, mirror_matrix_inner_home]
shift: [-6,-19]
rotate: 90
# Four Pin Reset Button
reset:
what: button
params:
from: GND
to: RST
where:
ref.aggregate.parts: [matrix_index_mod, mirror_matrix_index_mod]
shift: [0, -1]
rotate: -90
# Mounting Holes
holeTopLeft:
what: mountinghole
where:
ref: [matrix_outer_num]
shift: [0.5kx, -0.3ky]
holeBottomLeft:
what: mountinghole
where:
ref: [matrix_outer_bottom]
shift: [0.5kx, -0.3ky]
holeTopInnerLeft:
what: mountinghole
where:
ref: [matrix_index_num]
shift: [0.5kx, -0.4ky]
holeBottomInnerLeft:
what: mountinghole
where:
ref: [matrix_index_mod]
shift: [0.5kx, -0.35ky]
holeTopRight:
what: mountinghole
where:
ref: [mirror_matrix_outer_num]
shift: [0.5kx, -0.3ky]
holeBottomRight:
what: mountinghole
where:
ref: [mirror_matrix_outer_bottom]
shift: [0.5kx, -0.3ky]
holeTopInnerRight:
what: mountinghole
where:
ref: [mirror_matrix_index_num]
shift: [0.5kx, -0.4ky]
holeBottomInnerRight:
what: mountinghole
where:
ref: [mirror_matrix_index_mod]
shift: [0.5kx, -0.35ky]
cases:
bottom:
- name: board
extrude: 1
xlBottom:
- name: xlBoard
extrude: 1
_outerWall:
- name: xlBoard
extrude: 5.6
_innerWall:
- name: board
extrude: 5.6
wall:
- what: case
name: _outerWall
operation: add
- what: case
name: _innerWall
operation: subtract
_holes:
- name: mounting
extrude: 4
_standoffs:
- name: standoff
extrude: 4
case:
- what: case
name: _standoffs
operation: add
- what: case
name: _holes
operation: subtract
- what: case
name: xlBottom
operation: add
- what: case
name: wall
operation: add
That's it for Part 4! We configured Ergogen to run locally on the command line, imported some custom external footprints, and created a basic case file. Not bad for less than 500 lines of configuration.
With that, we're also done with Ergogen in general. We've walked through all four of the main sections of an Ergogen config, and have created an unrouted PCB and case file for our new keyboard. For the final chapter, I'll give a brief overview of the non-Ergogen steps required to build a custom keyboard. We need to route our PCB, export the circuit board files to a PCB fab, create a firmware for the keyboard, and actually build the dang thing. Time to wrap this up with Let's Design A Keyboard With Ergogen v4: KiCAD, Firmwares, & Assembly (Finale)!