Capistry
A Python package for parametric 3D modeling of keyboard keycaps using build123d.
Table of Contents
- Overview
- Installation
- Quick Start
- Features
- Documentation
- Examples
- License
- Contributing
- Acknowledgments
Overview
- Parametric Design: Create custom keycaps with precise control over dimensions, angles, and styling.
- Shapes: Rectangular, slanted, skewed, and trapezoidal keycap shapes.
- Stems: Compatible with MX and Choc switches.
- Advanced Geometry: Tapers, fillets, and surface modeling.
- Batching: Generate panels of multiple keycaps for 3D printing.
- Exporting: Export to any format supported by build123d, including STL, STEP, DXF, SVG, and more.
- Ergogen: Export
Ergogenconfigurations consisting of keycap positions and outlines. - Extensible: Designed to be extensible - you can create custom stems, cap classes, fillet strategies, and sprue designs by extending the base classes.
Installation
pip install capistry
Requirements
- Python 3.13+
- Dependencies:
- build123d
- rich
- mashumaro
- more-itertools
- attrs
- tzdata
Quick Start
from build123d import *
from capistry import *
import logging
# Initialize logging
init_logger(level=logging.INFO)
# Create a basic rectangular keycap
cap = RectangularCap(
width=18,
length=18,
height=5,
wall=1.25,
roof=1.25,
taper=Taper.uniform(7),
stem=MXStem(center_at=CenterOf.GEOMETRY),
fillet_strategy=FilletUniform()
)
# Export as STL
export_stl(cap.compound, "keycap.stl")
# Create a sprued grid-like panel for 3D printing
panel = Panel(
items=[
PanelItem(cap, quantity=10, mirror=True),
],
sprue=SprueCylinder(),
)
# Export panel as STL
export_stl(panel.compound, "panel.stl")
Features
Keycap Shapes
As of now, all keycap shapes are quadrilaterals, i.e. four-sided polygons.
RectangularCap
Standard rectangular keycap:
cap = RectangularCap(width=18, length=18, height=5)
SlantedCap
An asymmetric keycap where two sides are angled evenly away from their respective orientations. The resulting shape has two orthogonal (90°) corners, one corner at 90 + v° and another at 90 – v°, where v is the slant angle.
cap = SlantedCap(width=18, length=18, height=5, angle=7)
SkewedCap
Keycap with a parallelogram shape:
cap = SkewedCap(width=18, length=18, height=5, skew=5, skew_width=True)
TrapezoidCap
Keycap with a trapezoidal shape:
cap = TrapezoidCap(width=18, length=18, height=5, angle=5)
Stems
Support for different stems:
# MX-style stem
stem = MXStem()
# Choc-style stem
stem = ChocStem()
cap = RectangularCap(stem=stem)
Tapering
Control the slope of keycap sides:
# Uniform taper on all sides
taper = Taper.uniform(7)
# Side-specific tapering
taper = Taper(front=10, back=7, left=4, right=4)
cap = RectangularCap(taper=taper)
Fillet Strategies
Choose how edges are rounded:
# Uniform
strat = FilletUniform()
# Front-to-back, then left-to-right
strat = FilletDepthWidth()
# Left-to-right, then front-to-back
strat = FilletWidthDepth()
# Mid-height edges, then top-perimeter
strat = FilletMiddleTop()
cap = RectangularCap(fillet_strategy=strat)
Surface Modeling
The top face of keycaps can be precisely modeled by defining a Surface. A Surface is represented by a matrix (a 2-dimensional list), specifying the offset values which will be used to model the top face. Optionally, a weights matrix may also be supplied.
surface = Surface(
[
[4, 4, 4, 4],
[2, -1, -1, 2],
[0, -1, -1, 0],
[0, 0, 0, 0],
]
)
cap = TrapezoidCap(surface=surface)
Comparisons
You can compare caps, fillets, stems, and surfaces using the capistry.Comparer class. This is useful for identifying both dimensional differences and property variations.
# Create two keycaps with different parameters
c1 = RectangularCap(width=18, length=18, height=5)
c2 = RectangularCap(width=19*2 - 1, length=18, height=5)
# Create a comparer
comparer = Comparer(c2, c2)
# Show the comparison, outputs a rich table in the console
comparer.show(show_deltas=True)
Panel Generation
Create grid-like panels of keycaps for simpler 3D printing by creating a capistry.Panel:
cap = RectangularCap()
# Create a 4x4 panel of a keycap
panel = Panel(
items=[PanelItem(cap, quantity=16)],
sprue=SprueCylinder(),
cols=4
)
# Export the entire panel
export_stl(panel.compound, "keycap_panel.stl")
Ergogen Export
Export your keycaps in their current locations to an Ergogen configuration file.
# Create two rectangular keycaps
c1 = RectangularCap()
c2 = RectangularCap()
# Position the second keycap to the right of the first
c2.locate(c1.top_right)
# Create an ergogen instance, and write the configuration to a YAML file
ergogen = Ergogen(c1, c2)
ergogen.write_yaml("config.yaml", precision=3)
Documentation
Full API documentation is available and generated using pdoc.
👉 View the full documentation here
Examples
For more detailed examples, see the examples/ directory in the repository.
Development
Building from Source
git clone https://github.com/larssont/capistry.git
cd capistry
uv sync
License
This project is licensed under the Mozilla Public License 2.0 - see the LICENSE.md file for details.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Acknowledgments
- build123d — CAD library powering Capistry
Metadata
Release files for capistry 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| capistry-0.2.0.tar.gz | 36.6 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| capistry-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 36.7 MB
Release files / capistry-0.2.0.tar.gz
| Download URL | capistry-0.2.0.tar.gz |
|---|---|
| Size | 36.6 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5497527236e98affb027c1cc181c24be39bf44929a9f01c724d83123230fac15
|
|
BLAKE2b-256 checksum How to use checksums |
f4b5a56f791b3522ea17adcd6e6edad15d0dc573372b932323d50ff73cfa5cbc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.12.9
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 15, 2025.
Transparency logRelease files / capistry-0.2.0-py3-none-any.whl
| Download URL | capistry-0.2.0-py3-none-any.whl |
|---|---|
| Size | 62.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e046f01b9a3ed673dfa62f227d86862db2028e348eb2b2ae308eaba5a101b22f
|
|
BLAKE2b-256 checksum How to use checksums |
8c20b877f4c7f1cdf85e35363bd7cf094e0a54c731e9d04e7ed5754f0e78352e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.12.9
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 15, 2025.
Transparency log