Skip to content

Commit

Permalink
feat: completed the new approach of generating PCBs from the master
Browse files Browse the repository at this point in the history
  • Loading branch information
tomsaleeba committed Dec 19, 2019
1 parent 0607def commit 8f09cca
Show file tree
Hide file tree
Showing 8 changed files with 11,545 additions and 14,802 deletions.
48 changes: 44 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,9 @@ more (easily accessible) thumb keys. There are so many candidates
- minimise overall footprint by using internal standoffs (thanks for the idea
[ErgoTravel](https://github.com/jpconstantineau/ErgoTravel))
- use only 1U key caps to minimise size and because stabilisers are annoying
- One PCB design that works as all 3 layers of a sandwich design: base, PCB and
switch plate. Idea taken from
[for-split-keyboard](https://github.com/peej/for-split-keyboard) and
[mitosis](https://www.reddit.com/r/MechanicalKeyboards/comments/66588f/wireless_split_qmk_mitosis/)
- author a single "master" PCB and use a python script to generate the 3
layers of the sandwich case. Makes life much easier to keep them all in
sync.
- support 5-pin/PCB mount switches

## Gerbers
Expand Down Expand Up @@ -71,6 +70,36 @@ manufactured. The listed quantity is to assemble *both halves*.
Note1: we need to construct our own M2 standoffs of the right length as they
don't stock M2x9 female-female standoffs.


# Generating 3 layers of sandwich case
We edit a single PCB file that contains the information required to generate all
3 layer (switch plate, circuitry, base) of a sandwich case keyboard. Keeping it
all in one file ensures that things like mounting hole always stay lined up.

There is some magic in the settings of items on the PCB to indicate what layer
they belong on. Read the code for the specifics but as a general guide:
- we use Eco1.User as the templates for edge cuts that only appear on some
layers
- the width of the drawing lines are slightly different so we can determine
which belongs on each layer
- the thickness of text is slightly different so we can determine which
belongs on each layer

You *must* have Kicad installed in your system. Doing this makes the `pcbnew`
python module available. Make sure you don't have any virtualenv, etc activated
as you need to be using you system-wide python. Check for the module with:

```python
import pcbnew
```

Now you can regenerate the PCBs with:

```bash
python ./generate_pcbs.py
```


# Generating gerbers
Using KiCad 5.1.4, here's the settings to generate gerbers.

Expand Down Expand Up @@ -125,6 +154,17 @@ Sort of. You won't be able to use OLED screens and there's no solder jumpers so
you'll have to jump some wires between the OLED header SDA/SCL pins and the
SLAVE_LED pin/data pin on the unused TRRS jack.

**What happened to a single PCB that works as all 3 layers?**
Originally I tried to make one PCB design that works as all 3 layers of a
sandwich design: base, PCB and switch plate. The idea was taken from
[for-split-keyboard](https://github.com/peej/for-split-keyboard) and
[mitosis](https://www.reddit.com/r/MechanicalKeyboards/comments/66588f/wireless_split_qmk_mitosis/).
I failed because I wanted to have minimal post-manufacture cutting by hand and
JLCPCB kept rejecting my desgins as they didn't have strong enough supports to
hold the PCB together though the whole manufacture process. So now we have
"master" PCB design and use a python script to generate the PCBs for each layer
from that master.

**Do you have keyboard-layout-editor source?**\
Yes. Either [this file](./layout.kle) in this repo or here's a (hopefully up to date)
<a href="http://www.keyboard-layout-editor.com/##@@_x:3&t=#000000%0A%0A#115219;&=E%0A%0A#&_x:9.75&t=#000000%0A%0A#115219%0A#9914a6;&=I%0A%0A*%0A%7D;&@_y:-0.875&x:2&t=#000000%0A%0A#115219;&=W%0A%0A/@&_x:1;&=R%0A%0A$&_x:7.75&t=#000000%0A%0A#115219%0A#9914a6;&=U%0A%0A/&%0A%7B&_x:1&t=#000000%0A%0A#115219;&=O%0A%0A';&@_y:-0.875&x:5;&=T%0A%0A%25&_x:5.75;&=Y%0A%0A%5E;&@_y:-0.625&x:1;&=Q%0A%0A!&_x:13.75;&=P%0A%0A%22;&@_y:-0.625&x:3;&=D%0A%0A3&_x:9.75&t=#000000%0A%0A#115219%0A#9914a6;&=K%0A%0A8%0A);&@_y:-0.875&x:2&t=#000000%0A%0A#115219;&=S%0A%0A2&_x:1;&=F%0A%0A4&_x:7.75&t=#000000%0A%0A#115219%0A#9914a6;&=J%0A%0A7%0A(&_x:1&t=#000000%0A%0A#115219;&=L%0A%0A9;&@_y:-0.875&x:5;&=G%0A%0A5&_x:5.75;&=H%0A%0A6;&@_y:-0.625&x:1;&=A%0A%0A1&_x:13.75;&=/:%0A/;%0A0;&@_y:-0.625&x:3&t=#000000%0A#0b458a%0A#115219;&=C%0A%0A+&_x:9.75&t=#000000%0A%0A#115219%0A#9914a6;&=%3C%0A,%0A%0A%5D;&@_y:-0.875&x:2&t=#000000%0A#0b458a%0A#115219;&=X%0A%0A-&_x:1;&=V%0A%0A/=&_x:7.75&t=#000000%0A%0A#115219%0A#9914a6;&=M%0A%0A~%0A%5B&_x:1&t=#000000;&=%3E%0A.;&@_y:-0.875&x:5&t=#000000%0A%0A#115219;&=B%0A%0A%60&_x:5.75;&=N%0A%0A/:;&@_y:-0.625&x:1&t=#000000%0A#0b458a%0A#115219;&=Z%0A%0A/_&_x:13.75&t=#000000;&=?%0A//;&@_y:-0.625&x:3;&=Del&_x:9.75&t=#000000%0A%0A%0A#9914a6;&=%0A%0A%0A%3E%0A%0A%0A%0A%0A%0A%E2%86%93;&@_y:-0.875&x:2&t=#000000;&=Alt&_x:1;&=GUI%0AEnter&_x:7.75&t=#000000%0A%0A%0A#9914a6;&=%0A%0A%0A%3C%0A%0A%0A%0A%0A%0A%E2%86%90&_x:1&t=#000000&a:7;&=%E2%86%91;&@_y:-0.875&x:5&a:4;&=Shift&=Ctrl&_x:3.75&t=#115219;&=Numb%0AEnter&_t=#000000;&=Space;&@_y:-0.625&x:1;&=Leader&_x:13.75&a:7;&=%E2%86%92;&@_y:-0.375&x:2;&=&_a:4;&=Swap&_t=#000000%0A#0b458a;&=Esc&_t=#ab0202;&=Media&_t=#9914a6;&=Brkt&_x:3.75&t=#000000;&=Tab&=AltTab&=Bspc&=Swap&_a:7;&=">permalink</a>
62 changes: 49 additions & 13 deletions generate_pcbs.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
#!/usr/bin/env python
# Reads the PCB "master", that we edit by hand, and generates 3 output PCBs for
# each of the layers of the sandwich style case. This helps to keep everything
# in sync between the layers. You must have Kicad installed on your system as
# that installs the `pcbnew` module that we use. AFAIK you can't install pcbnew
# via pip.
import sys
import os
import pcbnew
Expand All @@ -12,6 +17,7 @@

ALL_LAYERS_WIDTH = 50000
NOT_TOP_PLATE_WIDTH = 50010
ALL_LAYERS_TEXT_THICKNESS = 150100


def refill_zones(board):
Expand All @@ -21,10 +27,24 @@ def refill_zones(board):
filler.Fill(zones)


def is_logo(module):
return module.GetReference() == 'CHICKEN'


def is_brace(module):
return module.GetReference().startswith('Brace')


def is_brace_not_for_top(module):
return module.GetReference().startswith('BraceNotTop')


def delete_electronics(board):
for module in board.GetModules():
# FIXME don't delete chicken
# FIXME don't delete mount holes
ref = module.GetReference()
is_mounting_hole = ref.startswith('Mount')
if is_mounting_hole or is_logo(module) or is_brace(module):
continue
board.Delete(module)


Expand All @@ -37,18 +57,22 @@ def generate_base_pcb():
board = pcbnew.LoadBoard(master_pcb)
print('Creating base PCB: %s' % base_pcb)
delete_electronics(board)
for module in board.GetModules():
if is_brace(module):
if is_brace_not_for_top(module):
continue
board.Delete(module)
delete_tracks(board)
for d in board.GetDrawings():
# FIXME don't delete name, version, licence
if type(d) is pcbnew.TEXTE_PCB:
if d.GetThickness() == ALL_LAYERS_TEXT_THICKNESS:
continue
if d.GetLayerName() == 'Edge.Cuts':
if type(d) is pcbnew.DRAWSEGMENT:
if d.GetWidth() == NOT_TOP_PLATE_WIDTH or d.GetWidth(
) == ALL_LAYERS_WIDTH:
continue
board.Delete(d)
# FIXME delete template ProMicro edge.cuts
# FIXME delete template choc edge.cuts
# FIXME delete template switch edge.cuts
refill_zones(board)
board.Save(base_pcb)

Expand All @@ -57,17 +81,17 @@ def generate_middle_pcb():
board = pcbnew.LoadBoard(master_pcb)
print('Creating middle PCB: %s' % middle_pcb)
for d in board.GetDrawings():
# FIXME don't delete name, version, licence
if d.GetLayerName() == 'Edge.Cuts':
if type(d) is pcbnew.TEXTE_PCB:
continue
layer_name = d.GetLayerName()
if layer_name == 'F.SilkS' or layer_name == 'B.SilkS':
continue
if layer_name == 'Edge.Cuts':
if type(d) is pcbnew.DRAWSEGMENT:
if d.GetWidth() == NOT_TOP_PLATE_WIDTH or d.GetWidth(
) == ALL_LAYERS_WIDTH:
continue
board.Delete(d)
# FIXME delete template ProMicro edge.cuts
# FIXME delete template choc edge.cuts
# FIXME delete template switch edge.cuts
# FIXME remove reset switch cut
refill_zones(board)
board.Save(middle_pcb)

Expand All @@ -77,9 +101,21 @@ def generate_top_pcb():
edge_cuts_layer_id = board.GetLayerID('Edge.Cuts')
print('Creating top PCB: %s' % top_pcb)
delete_electronics(board)
for module in board.GetModules():
is_brace_for_top = not is_brace_not_for_top(module)
if is_brace(module) and is_brace_for_top:
continue
ref = module.GetReference()
is_mount_for_top = ref.startswith(
'Mount') and not ref.startswith('MountNotTop')
if is_mount_for_top:
continue
board.Delete(module)
delete_tracks(board)
for d in board.GetDrawings():
# FIXME don't delete name, version, licence
if type(d) is pcbnew.TEXTE_PCB:
if d.GetThickness() == ALL_LAYERS_TEXT_THICKNESS:
continue
if d.GetLayerName() == 'Edge.Cuts':
if type(d) is pcbnew.DRAWSEGMENT:
if d.GetWidth() == ALL_LAYERS_WIDTH:
Expand Down
1 change: 1 addition & 0 deletions generated_pcb/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
*.pro
fp-info-cache
*-bak
Loading

0 comments on commit 8f09cca

Please sign in to comment.