Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add led / secondary component placement. #2

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ You can then refresh your plugins and it should show up (you can also find a sho

What the dialog looks like:

![image](https://user-images.githubusercontent.com/23428162/175812246-eb44a86b-b6de-445c-b713-ac16aee70f52.png)
![image](https://user-images.githubusercontent.com/26168490/196301764-68277824-57dc-44b8-a1de-a3569c475afc.png)


# Usage
Expand All @@ -29,6 +29,8 @@ The script takes in the `json` file of a KLE, downloaded as shown:

![image](https://user-images.githubusercontent.com/23428162/168476867-7477de1c-a342-41e8-b515-0a1d21b097b8.png)

Place in keyboard-layout.json in the project folder (i.e. "${KIPRJMOD}/keyboard-layout.json") and the script will auto detect it so you don't have to search for the file.

Follow the following KLE guidelines to enable more advanced functionality of the plugin:


Expand Down Expand Up @@ -74,6 +76,14 @@ When you run the script, the rest of the diodes will move the same way:
![image](https://user-images.githubusercontent.com/23428162/175814169-297a9c08-3843-4525-a0dd-9670c7bf7e06.png)


## Led/Other Component Placement

Rudimentary led placement is supported. That is, leds will be placed like the switch, using the top left corner of the footprint as reference.

For the moment seperate numbering is not supported. They require the same numbering as switches, so you will probably need to rename them from D to LED.

Note that this can also place other components, they need not be LEDs.

## Extra switch rotations

At first, all switches will be set to the same rotation as the first key. You can then use extra switch rotations (assign them in label position 10) to further rotate specific switch footprints. This is useful for e.g. specifying certain keys to be north facing, when the rest of the keys on the board are south facing.
Expand Down Expand Up @@ -119,7 +129,7 @@ To mass-annotate symbols, you can use this tool in the schematic editor. If you
## SPECIFIC REFERENCE MODE (and rotated keys)
It's hard to do rotated switches with the board's normal mode (it goes left to right based on the order of switches, `SW1`, `SW2`, and so on). The order of keys in KLE gets mixed up/is hard to interpret once you introduce rotated keys, so my solution/compromise for accurately relating keys on the KLE to footprints on the PCB, is to add a separate mode.

To enable it, check the `Specific Reference Mode` checkbox in the dialog.
To enable it, check the `Specific Reference Mode` checkbox in the dialog.

Furthermore, with this mode, you're required to fill out label 4 with the reference value of the switch footprint that the key corresponds to (e.g. the key that corresponds to `SW1` should have `1` in label position 4, see guidelines above).

Expand Down
74 changes: 55 additions & 19 deletions kle_placer_action.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ def __init__(self, parent, title, caption):

layout_file_picker = wx.FilePickerCtrl(self, -1)
layout_select_box.Add(layout_file_picker, 1, wx.EXPAND|wx.ALL, 5)
default_path = os.path.join(str(os.getenv('KIPRJMOD')), 'keyboard-layout.json')
if os.path.exists(default_path):
layout_file_picker.SetPath(default_path)

# Key format
key_format_box = wx.BoxSizer(wx.HORIZONTAL)
Expand Down Expand Up @@ -51,6 +54,15 @@ def __init__(self, parent, title, caption):
diode_annotation_format = wx.TextCtrl(self, value='D{}')
diode_format_box.Add(diode_annotation_format, 1, wx.EXPAND|wx.ALL, 5)

# Led format
led_format_box = wx.BoxSizer(wx.HORIZONTAL)

ledAnnotationLabel = wx.StaticText(self, -1, "Led Annotation format string:")
led_format_box.Add(ledAnnotationLabel, 1, wx.LEFT|wx.RIGHT|wx.ALIGN_CENTER_VERTICAL, 5)

led_annotation_format = wx.TextCtrl(self, value='LED{}')
led_format_box.Add(led_annotation_format, 1, wx.EXPAND|wx.ALL, 5)

# Diode bool
move_diodes_box = wx.BoxSizer(wx.HORIZONTAL)

Expand Down Expand Up @@ -79,6 +91,7 @@ def __init__(self, parent, title, caption):
box.Add(key_format_box, 0, wx.EXPAND|wx.ALL, 5)
box.Add(stab_format_box, 0, wx.EXPAND|wx.ALL, 5)
box.Add(diode_format_box, 0, wx.EXPAND|wx.ALL, 5)
box.Add(led_format_box, 0, wx.EXPAND|wx.ALL, 5)
box.Add(move_diodes_box, 0, wx.EXPAND|wx.ALL, 5)
box.Add(relative_diode_box, 0, wx.EXPAND|wx.ALL, 5)
box.Add(specific_ref_box, 0, wx.EXPAND|wx.ALL, 5)
Expand All @@ -91,6 +104,7 @@ def __init__(self, parent, title, caption):
self.key_annotation_format = key_annotation_format
self.stabilizer_annotation_format = stabilizer_annotation_format
self.diode_annotation_format = diode_annotation_format
self.led_annotation_format = led_annotation_format
self.move_diodes_bool = move_diodes_bool
self.relative_diode_bool = relative_diode_bool
self.specific_ref_mode = specific_ref_mode
Expand All @@ -107,12 +121,15 @@ def get_stabilizer_annotation_format(self):
def get_diode_annotation_format(self):
return self.diode_annotation_format.GetValue()

def get_led_annotation_format(self):
return self.led_annotation_format.GetValue()

def get_move_diodes_bool(self):
return self.move_diodes_bool.GetValue()

def get_relative_diode_bool(self):
return self.relative_diode_bool.GetValue()

def get_specific_ref_mode_bool(self):
return self.specific_ref_mode.GetValue()

Expand All @@ -123,7 +140,7 @@ def __init__(self, logger, board):

def mm_to_nm(self, v):
return int(v * 1000000)

def nm_to_mm(self, v):
return v / 1000000.0

Expand Down Expand Up @@ -157,16 +174,20 @@ def __init__(self, logger, board, layout):
self.current_diode = 1
self.reference_coordinate = pcbnew.wxPoint(pcbnew.FromMM(25), pcbnew.FromMM(25))

def get_current_key(self, key_format, stabilizer_format):
def get_current_key(self, key_format, stabilizer_format, led_format):
key = self.get_footprint(key_format.format(self.current_key))

# in case of perigoso/keyswitch-kicad-library, stabilizer holes are not part of of switch footprint and needs to be handled
# separately, check if there is stabilizer with id matching current key and return it
# stabilizer will be None if not found
stabilizer = self.get_footprint(stabilizer_format.format(self.current_key), required=False)
led = None
if led_format:
led = self.get_footprint(led_format.format(self.current_key), required=False)

self.current_key += 1

return key, stabilizer
return key, stabilizer, led

# def get_current_diode(self, diode_format):
# diode = self.get_footprint(diode_format.format(self.current_diode))
Expand All @@ -179,7 +200,7 @@ def squish_kbd_multilayout(self):

# This list will replace kbd.keys later
# It is a list with only the keys to be included in the info.json
temp_layout = []
temp_layout = []
# Add non-multilayout keys to the list for now
for key in [k for k in kbd.keys if k not in ml_keys]:
temp_layout.append(key)
Expand Down Expand Up @@ -246,7 +267,7 @@ def squish_kbd_multilayout(self):
else:
# If so, just get the offset from ml_dict
ml_x_offset, ml_y_offset = ml_dict[ml_ndx]["offsets"][ml_val]

# Offset the x and y values
key.x += ml_x_offset
key.y += ml_y_offset
Expand All @@ -261,7 +282,7 @@ def squish_kbd_multilayout(self):
for key in temp_layout:
key.x -= x_offset
key.y -= y_offset

if key.rotation_angle:
key.rotation_x -= x_offset
key.rotation_y -= y_offset
Expand All @@ -272,7 +293,7 @@ def squish_kbd_multilayout(self):
# Sort keys based on the centers of each key (by default it sorts with the top left corner)
sort_keys_kle_placer(self.layout.keys)

def Run(self, key_format, stabilizer_format, diode_format, move_diodes, relative_diode_mode, rotation_mode):
def Run(self, key_format, stabilizer_format, diode_format, led_format, move_diodes, relative_diode_mode, rotation_mode):

### First, check all the multilayouts and squish all the same multilayouts into the same position on top of one another. ###

Expand All @@ -296,7 +317,7 @@ def check(key):
self.layout.keys.sort(key=lambda x:check(x))
first_key_pos = pcbnew.wxPoint((first_key.GetPosition().x) - ((self.key_distance * self.layout.keys[0].x) + (self.key_distance * self.layout.keys[0].width // 2)),
(first_key.GetPosition().y) - ((self.key_distance * self.layout.keys[0].y) + (self.key_distance * self.layout.keys[0].height // 2)))

first_key_rotation = first_key.GetOrientationDegrees()

# Set the origin/reference as the first key
Expand All @@ -316,10 +337,11 @@ def check(key):
diode_offset_x = 0 # mm
diode_offset_y = 0 # mm


if relative_diode_mode:
diode_offset_x = self.nm_to_mm(first_diode.GetPosition().x - first_key.GetPosition().x)
diode_offset_y = self.nm_to_mm(first_diode.GetPosition().y - first_key.GetPosition().y)

first_diode_rotation = first_diode.GetOrientationDegrees()

# Set the default diode rotation to that of the first diode's
Expand All @@ -331,9 +353,9 @@ def check(key):
current_ref = int(key.labels[4]) # Already checked for violations earlier
self.current_key = current_ref

# Get the diode, switch and stabilizer footprints
# Get the diode, switch, stabilizer, and led footprints
diode_footprint = self.get_footprint(diode_format.format(self.current_key), required=False) or None
switch_footprint, stabilizer = self.get_current_key(key_format, stabilizer_format)
switch_footprint, stabilizer, led_footprint = self.get_current_key(key_format, stabilizer_format, led_format)

# Extra individual switch rotations i.e. extra rotation compared to the first switch's rotation e.g. for south/north facing switches
extra_switch_rotation = 0
Expand All @@ -353,13 +375,13 @@ def check(key):
# Calculate position on board
position = pcbnew.wxPoint((self.key_distance * key.x) + (self.key_distance * width // 2),
(self.key_distance * key.y) + (self.key_distance * height // 2)) + self.reference_coordinate

# Move switch footprint
self.set_position(switch_footprint, position)

# Set rotation of switch to the same as the first one, then rotate extra based if needed
switch_footprint.SetOrientationDegrees(default_key_rotation)
if extra_switch_rotation:
switch_footprint.SetOrientationDegrees(default_key_rotation)
if extra_switch_rotation:
self.rotate(switch_footprint, switch_footprint.GetPosition(), extra_switch_rotation)

# Move (and rotate) diode if it exists, and Move Diode is enabled
Expand All @@ -368,6 +390,7 @@ def check(key):
diode_footprint.SetOrientationDegrees(default_diode_rotation)
self.rotate(diode_footprint, switch_footprint.GetPosition(), extra_switch_rotation)


# Move stabilizer if it exists
if stabilizer:
stabilizer.SetOrientationDegrees(0)
Expand All @@ -376,6 +399,16 @@ def check(key):
if flip_stabilizer:
stabilizer.SetOrientationDegrees(180)

# Move led if it exists
if led_footprint:
# led footprint might not have origin at center of switch
led_offset = led_footprint.GetPosition() - led_footprint.GetBoundingBox().GetOrigin()
switch_offset = switch_footprint.GetPosition() - switch_footprint.GetBoundingBox().GetOrigin()
offset = (led_offset - switch_offset)
self.set_position(led_footprint, position + offset)
led_footprint.SetOrientationDegrees(default_key_rotation)
self.rotate(led_footprint, led_footprint.GetPosition() + offset, extra_switch_rotation)

# For angled keys (should only apply when rotation mode is enabled)
if angle != 0:
rotation_reference = pcbnew.wxPoint((self.key_distance * key.rotation_x), (self.key_distance * key.rotation_y)) + self.reference_coordinate
Expand All @@ -387,6 +420,9 @@ def check(key):
if stabilizer:
self.rotate(stabilizer, rotation_reference, angle)

if led_footprint:
self.rotate(led_footprint, rotation_reference, angle)


class KLEPlacerAction(pcbnew.ActionPlugin):
def defaults(self):
Expand Down Expand Up @@ -423,14 +459,14 @@ def Run(self):

dlg = KeyAutoPlaceDialog(pcbFrame, 'Title', 'Caption')
if dlg.ShowModal() == wx.ID_OK:

layout_path = dlg.get_layout_path()
if layout_path:
self.layout = deserialize(json.loads(read_file(layout_path)))

self.logger.info("User layout: {}".format(self.layout))
placer = KeyPlacer(self.logger, self.board, self.layout)
placer.Run(dlg.get_key_annotation_format(), dlg.get_stabilizer_annotation_format(), dlg.get_diode_annotation_format(), dlg.get_move_diodes_bool(), dlg.get_relative_diode_bool(), dlg.get_specific_ref_mode_bool())
placer.Run(dlg.get_key_annotation_format(), dlg.get_stabilizer_annotation_format(), dlg.get_diode_annotation_format(), dlg.get_led_annotation_format(), dlg.get_move_diodes_bool(), dlg.get_relative_diode_bool(), dlg.get_specific_ref_mode_bool())

dlg.Destroy()
logging.shutdown()