Naming rules
How LYNX names modules, parts and signals across firmware, electronics, Blender sources, tools and documentation. The names live in the files listed under one authoritative mapping. The generated module lookup maps every module's full name and code to its files.
Full names for discovery; compact names for repeated work
Someone encountering LYNX for the first time should be able to identify a module, find its PCB project and open its Blender source without learning abbreviations. Use full names in navigation, headings, module directories, source filenames, standalone CAD parts and distributed exports. Introduce the compact code beside the full name; use the code in commands, internal references and constrained labels.
Consistency means the same concepts, word order and relationships across domains. Formatting may follow the language or application. Do not expand every local variable into a complete hardware description when its enclosing type or module already supplies that context.
Put the side first
For a side-specific item, use this order:
Side → module family → variant → part or signal.
| Full display name | Compact code | Descriptive filename stem |
|---|---|---|
| Left finger module, 21 keys | left-F21 | left-finger-module-21-keys |
| Right finger module, 20 keys + scroll wheel | right-F20W | right-finger-module-20-keys-scroll-wheel |
Use left and right from the user's hand perspective, regardless of the current
CAD view or PCB face. Keep these words spelled out in module codes and filenames.
Within a parent explicitly named Left hand unit, a child may display simply
“Finger module, 21 keys.” A standalone file or exported part needs the side.
Every module is made for both hands, so a user can build each hand from any available
module per slot. A variant is a hand-independent design (F21: “Finger module, 21 keys”);
finger and thumb modules exist as mirrored left and right builds, and the side names the
build: left-F21, its mirror right-F21. The Assembly catalogues the modules that exist
and records which of them one device is fitted with, not which variants a hand can take.
Shared definitions and sources that genuinely serve both sides have no side
prefix: bottom modules fit either hand as they are, so “Bottom module, mouse sensor
PMW3389” is code BM-PMW3389, stem bottom-module-mouse-sensor-pmw3389. A part
number keeps its maker's hyphen in the name and drops it from the stem: “…mouse sensor
ADNS-5050” has stem bottom-module-mouse-sensor-adns5050. If a bottom
part becomes side-specific, prefix that part with its side.
Compact module nomenclature
Fmeans finger module;Tmeans thumb module;Bmeans bottom module.- A number following
ForTcounts ordinary keys. Wmeans a scroll wheel.F20Whas 20 ordinary keys plus a scroll wheel; its wheel click is additional to those 20 keys.- Use uppercase feature letters consistently:
F20W, neverF20w. BMmeans bottom mouse module, followed by the sensor model. Define additional codes when actual variants need them; do not invent a complete vocabulary for hypothetical hardware. The gyro bottom module isBG(“Bottom module, gyro”, stembottom-module-gyro): one gyro, so no sensor model in the code until a second one exists.- Side, module variant and hardware revision are separate attributes. A key count is not a revision. Preserve revision and manufacturing provenance when renaming files or preparing releases.
Tables distinguish ordinary keys, wheel clicks and total switch inputs: the right finger module's matrix carries 21 switch inputs, its wheel click included.
Filenames, PCB projects and Blender parts
Use lowercase hyphen-separated descriptive stems for module paths, filenames and standalone Blender module objects/collections. Keep normal application extensions. Use the same module stem across PCB files, Blender parts and exported artifacts.
electronics/boards/left-thumb-module-11-keys/
left-thumb-module-11-keys.kicad_pro
left-thumb-module-11-keys.kicad_sch
left-thumb-module-11-keys.kicad_pcb
mechanical/blender/thumb-module.blend
left-thumb-module-11-keys-cover
right-thumb-module-11-keys-cover
thumb-module.blend and finger-module.blend each contain both sides: one shared
source per slot. Do not duplicate Blender sources just to obtain one file per variant.
A module's navigation entry identifies both the source file and the object/collection
to open inside it.
Use explicit part suffixes such as -cover, -board, and -board-placement; a
standalone export would be left-thumb-module-11-keys-cover.stl. The printed parts a cover
takes are placed per module as -keycaps-placement (one -keycap-<key> per board key, a1),
-led-plugs-placement (one -led-plug-<designator> per LED, LED1) and, over a joystick,
-stick-placement (-stick-dome, -stick-cap); each instances the module file's
<source>-print stand-in of the part (Key-Cap-print). The screws a part takes are placed per
joint as -screws-placement (one -screw-<n> per hole, numbered as its cutter or Lid-Screw-N-Pos;
-washers-placement, -washer-<n> under the gyro's), each instancing references.blend's drawing
(References-Enclosure-Screw). These placements and the
-board-placement sit in the part set's imports set, <set>-imports (left-finger-module-imports;
a module fitted to no slot has its own set, named by its stem:
left-thumb-module-4-keys-joystick-imports). A bottom module is placed in the imports of the lid
that holds it, its placement side first (left-lid-mouse-imports:
left-bottom-module-mouse-sensor-pmw3389-refs-placement). Other side-specific
parts and containers follow the same order (left-casing-body, right-finger-module).
Shared helpers keep contextual names such as Finger-Key-Grid or Lid-Screw-1-Pos
where they remain unambiguous. Blender datablocks may be shared or globally named, so
a collection parent alone is not proof that a short name is safe; check every lookup
before shortening it.
Electrical signals describe their function
A signal is named <SLOT>_<FUNCTION>[_<INDEX>] from the hand unit microcontroller's
perspective: FINGER_MATRIX_ROW_1, FINGER_MATRIX_COLUMN_6,
FINGER_SCROLL_ENCODER_A, THUMB_LED_DATA. Prefer row/column names because “input”
or “output” alone does not explain the function or its viewpoint. The compact alias
(FI1, FO6, WA, LED_DATA) stays on the space-constrained silkscreen and in the
frozen sources, mapped to the canonical name in wiring-harness.toml
and the generated pinout.
The shared signal definition has no side prefix. Both hand units implement the same interface vocabulary on their own circuits. A wiring reference supplies the instance context, for example:
Left finger module, 21 keys → connector J1, pin 7 → matrix row 1 → GPIO38.
If a future assembly schematic includes both hands in one net namespace, scope or qualify the instances so identically named local signals are not accidentally joined. Reusing an interface name does not mean the two hands share an electrical net.
Slot, matrix axis/index, electrical direction and GPIO are explicit metadata. Direction is relative to the hand unit microcontroller: matrix rows are inputs with pull-ups; columns are driven outputs. Do not infer these facts by parsing letters in a name.
Keep these addresses distinct:
- Connector pin number, such as J1 pin 7.
- Human-facing matrix row/column number, starting at 1.
- Firmware matrix index, starting at 0.
- GPIO number, such as GPIO38.
- Physical key identity and key binding.
An electrical row is not necessarily a visually straight row of keys.
Keep standard electrical names such as GND and 3V3, and standard component
designators such as J1, SW1, D1 and LED1. Add descriptive fields where useful.
Code and control identities
Use the same domain vocabulary in Rust, Python, shell and structured data, with
their normal formatting conventions. For example, FINGER_MATRIX_ROW_1 is a
net name; finger_matrix_rows is a descriptive Rust binding; rows is sufficient
inside FingerPins. A signal group does not need a variable for each named net
when an ordered array already expresses its meaning correctly.
Physical control paths such as right.fingers.index.button_2 and compact labels
such as Ri2 already provide side-first identity and have consumers in the keymap
and Vial. Do not replace control identity with a module filename, matrix address or
assigned key.
Read finger, fingers, F and Finger by meaning and scope; do not globally
replace substrings. The physical slot, installed variant, PCB, Blender part and Rust
module are related concepts, not interchangeable names.
Firmware feature names such as fk, fkw and tk select capabilities and are
not module identifiers; the command help explains them. Renaming a feature or a CLI
word is its own decision, with its callers and compatibility.
Setup roles
A setup is one or two cats, with or without a tower. Its devices are the Left Cat, the Right Cat and the Tower (physical devices; left and right are the user's hands). What a device does in the setup is its role, a firmware concept, chosen at build time:
- The Coordinator owns the setup's Input Engine (keymap, layers, pointer,
settings) and its USB connection to the computer. Exactly one per setup: the
tower when there is one, otherwise a cat (the cat feature
coordinator). - A Satellite captures input and sends it to the Coordinator over ESP-NOW. Zero or more per setup. Plugging a Satellite into USB powers it; it does not make it the Coordinator.
DeviceRole (LeftCat, RightCat, Tower) stays the wire identity of a device;
Coordinator and Satellite are not wire roles. Battery presence is independent of
the role: a cat without a battery runs from its USB cable (to the computer, or a
USB power supply for a Satellite) and so fits no power switch, and its lid has no
holder for either. The two ends of the ESP-NOW link are the Satellite's (cat::link) and
the Coordinator's (coordinator_link). On the wire and in the provisioned records
the Coordinator end keeps its historical name, tower (target_tower,
CatLinkRecord::tower_mac, TowerPairingRecord): a Coordinator cat answers as
the tower of its pair.
A setup manifest writes one setup down: its cats, its tower, each cat's
modules and battery in build.toml's choice words, and which
device is the Coordinator. ./lx setup plan resolves it into each device's
build (examples).
The firmware is described by responsibilities, used as names in code where they
own something: capture (scan controls and sensors), link (exchange input
and state between devices; ESP-NOW), input engine (input_engine: keymaps,
layers, keyboard and pointer processing), usb (HID and Vial), feedback
(layer, lock, battery and connection state on LEDs or a screen), power,
configuration (topology, boards, modules, roles) and storage (keymaps,
settings, provisioning records). They are responsibilities, not one crate each;
existing names (drivers, slots, protocol, supervisor) stay.
One authoritative mapping
- LYNXware.toml owns physical identity and module metadata. A variant
(the compact variant code,
F21) is described once in[variant.*]; each module built of it is one[[module]]entry (itsside, or none for a shared variant, and itslegacycode), which keeps the module's identity whichever slot it is fitted to, or none. A slot names only thevariantit fits; assembly.py derives the code, full name and stem. Derive display names and stems; do not author competing copies. - electronics/wiring-harness.toml owns the connector contract and signal vocabulary, checked against the board netlists and firmware GPIO assignments.
- mechanical/associations.toml owns links from Blender
objects to their source files, modules and firmware. It names a module by
module(the module code) and purchased hardware bypart.
Keep those ownership boundaries; do not duplicate the whole mapping in each file. Resolve a new schema ambiguity together with its readers.
Legacy names
The renames ran as one-shot scripts, removed after use; the commits keep them:
- 2026-09-24, Blender: node groups localized, names normalized, the part files split (commit 5105493, with its rename maps).
- 2026-09-25, side-first names: board directories and files renamed to the stem, project,
title, sheet and connector-signal tokens rewritten, every KiCad UUID kept; the Blender
datablocks renamed by
rename_map_sides.json(commit 341456c holds scripts and map). - 2026-09-26, Blender flattening: each file's entry collection dissolved into its top level,
left-lid-bodysplit intoleft-lid-plain-bodyandleft-lid-bowl-body; every object's evaluated geometry compared before and after withtools/blender/inventory_ids.py, and the milestones hold the files before it. - 2026-10-06, the bottom module in its lid: the bowl gave way to a lid per kind of bottom
module, and bottom-module.blend dissolved into lid.blend.
Lid-Board-Bowl-OriginbecameLid-Board-Pos;left-lid-bowl-body/-bowl-skates-body(and their sets and right twins)left-lid-gyro-body/left-lid-mouse-body; the bases (bottom-module-mouse-sensor-pmw3389-base,Bottom-Gyro-Base) gave way to the lids'Lid-Bottom-Module-Holderand their modules' cuts;bottom-module-mouse-sensor-pmw3389-refs-placementisleft-/right-…in the mouse lid's imports. The renames were compared through their map withinventory_ids.py(commit 341456c) before anything was reshaped.
The names from before remain only where they must:
| Where | Why |
|---|---|
Each Assembly [[module]] entry's legacy (F21-left) | Tools still accept it. It names the frozen electronics/baseline/<legacy>/ source and seeds every KiCad UUID of the board, so it never changes. |
wiring-harness.toml alias, the boards' silkscreen, nets.json keys | The compact names on the boards and in the frozen sources. |
electronics/baseline/** and the private EasyEDA archives | The EasyEDA sources as archived. |
Compatibility aliases resolve to one canonical record, never to duplicate boards or source files.