Generate 3D-printable keycaps with text legends and Kailh Choc stems.
Note: This project has been vibe coded with Claude (Anthropic's AI assistant). The code works, but don't expect enterprise-grade polish.
The included configuration is for a 3x5 split keyboard layout with symbols based on the author's ZMK keymap. You'll likely want to customize
config.tomlfor your own layout and preferences.The Subliminal Contradiction STEP files use Cherry MX keycap dimensions (not Choc v1 size) with **Kailh Choc stems ** - larger keycap profile on low-profile switches.
- What It Does
- Acknowledgements
- Requirements
- Setup
- Usage
- uv Commands Reference
- Configuration
- Preparing STEP Files with FreeCAD
- Using Your Own STEP Files
- Tips
- Printing Tips
- License
Takes STEP files of keycap shells (from FreeCAD) and adds:
- Text legends - Characters carved into the keycap top surface (supports primary, secondary, and tertiary legends)
- Kailh Choc stems - Low-profile switch mount geometry
Outputs 3MF files with separate bodies (cap body, legend, stem) that slicers recognize as distinct objects, making multi-material or multi-color printing easy - just assign different filaments to each body in your slicer.
This project uses keycap shells from the Subliminal Contradiction sculpted keycap set by pseudoku.
- GitHub: pseudoku/Subliminal-Contradiction
- Store: If you want professionally cast versions of SC profile keycaps, check out Asymplex
⚠️ License Note: The STEP files inassets/are licensed under CC BY-NC 4.0 (NonCommercial). You may NOT use the keycap designs or generated 3MF files for commercial purposes. See LICENSE for details.
- Python 3.12+
- mise (optional, for tool management)
# Install mise if you don't have it
curl https://mise.run | sh
# Install project tools (uv, watchexec)
mise install
# Create virtual environment and install dependencies
uv sync# Install uv
curl -LsSf https://astral.sh/uv/install.sh | sh
# Create virtual environment and install dependencies
uv syncuv run main.pyOutput files are saved to results/ as 3MF files.
uv run fonts.pyThis lists all fonts available to build123d for legend text. Useful for finding Nerd Fonts or other installed fonts.
watchexec -e py -- uv run main.pyThe ocp_vscode library (included as a dependency) has a standalone viewer mode. To visualize all generated shapes for
debugging, replace show with show_all in main.py:
from ocp_vscode import show_all
# ... at the end of the script:
show_all()This opens a 3D viewer window showing all parts, useful for verifying legend placement and geometry.
To debug specific rows without processing everything, edit ONLY_ROWS in main.py:
ONLY_ROWS = None # Process all rows
ONLY_ROWS = ["row_2"] # Process only row_2
ONLY_ROWS = ["thumb_mid", "thumb_corners"] # Process only thumb keysmise run run # Generate keycaps (equivalent to `uv run main.py`)
mise run run_ocp # Launch OCP-VSCode standalone vieweruv sync # Install/update dependencies from pyproject.toml
uv add <package> # Add a new dependency
uv remove <package> # Remove a dependency
uv run <script> # Run a Python script in the virtual environment
uv lock # Update the lock fileEdit config.toml to configure legends and settings. No Python knowledge required!
[settings]
font = "Rajdhani" # Default font for legends
primary_font_size = 8 # Main character size (mm)
secondary_font_size = 6 # Symbol size (mm)
tertiary_font_size = 5 # Third character size (mm)
legend_gap = 0.0 # Gap between primary and secondary
vertical_shift = 0.0 # Shift legend block up/down
tertiary_x_offset = -5.0 # Tertiary position (negative = left)[step_files.row_2]
path = "assets/1u_row_2.step"
rotation = 0 # Optional, degrees
has_stem = true # Optional, skip stem generation if STEP already has stem[[legends.row_2]]
primary = "q"
secondary = "`"
mirror_x = false # Optional, for reachy keys
primary_font = "Rajdhani" # Optional override
secondary_font = "FantasqueSansM Nerd Font Propo" # Optional override
tertiary = "1" # Optional third character
tertiary_font = "Rajdhani" # Optional overrideThe STEP files in this project were prepared using FreeCAD 1.0 / 1.2 dev version.
If you have a mesh (STL/OBJ) of a keycap and need to convert it to STEP:
- Import mesh - Use the Mesh workbench to import your file
- Repair mesh - Use mesh repair tools to fix holes and ensure validity
- Decimate - Lower the triangle count to reduce complexity
- Convert to shape - In Part workbench, use "Part > Create shape from mesh"
- Convert to solid - Use "Part > Convert to solid"
- Simplify - Use "Edit > Copy > Simplify" to reduce geometry complexity
- Validate - Use Part workbench check tools to ensure boolean operations will work
- Clean up - Use Part and Draft workbenches to cut or clean the solid as needed
- Export - Export as STEP file
This project is designed to work with the STEP files in assets/, which are sculpted keycap shells from the "Subliminal
Contradiction" keycap set.
It should be generic enough to work with other keycap STEP files, but:
- The STEP file should be a solid keycap body (with or without stem)
- The largest bottom face is used for stem plane positioning
- Legend placement is automatic - it finds the lowest point on the top surface near the center (works for concave, convex, and flat keycaps)
- If your STEP file already includes a stem, set
has_stem = trueto skip stem generation - You may need to adjust rotation in config
- Font sizes and positioning may need tuning
The author can't provide much support for custom STEP files - you're on your own for debugging CAD geometry issues. Good luck!
Some fonts work better than others for keycap legends. Fonts with clean, simple geometry produce better results. Recommended fonts to try:
- DIN 1451 - Clean industrial look
- Open Cherry - Designed for keycaps
Use uv run fonts.py to list all available fonts on your system.
Sometimes certain symbols will break the 3MF output or cause meshing errors. If this happens:
- Try a different font size - Slightly larger or smaller sizes can fix geometry issues
- Use a Nerd Font symbol - Replace problematic characters with Nerd Font icons (e.g.,
\uf069instead of*) - Simplify the glyph - Some ornate characters have geometry that doesn't mesh well
- Material: PLA works well
- Orientation: 45° angle on the side recommended
- Spacing: getting some space around each key gives the print more travel time and can help with pieces detaching from the bed
- Supports: Required
- Post-processing: Stems may need light filing for fit (they print tight)
- Blank keycaps: If you want keycaps without visible legends, simply assign the same filament/color to both the cap body and legend in your slicer
Dual-licensed:
- Code (Python, config, docs): MIT License
- STEP files in
assets/: CC BY-NC 4.0 (NonCommercial)
See LICENSE for full details.


