pip-hinge
A parametric print-in-place piano hinge in build123d, designed for clamshell cases.
Start with four main inputs; the parameter table covers the remaining fit and geometry controls.
from pip_hinge import Knuckle, PinStyle, PrintInPlaceHinge
hinge = PrintInPlaceHinge(
case_h = 10, # case wall height (mm)
hinge_length = 60, # total hinge length along the axis (mm)
stations = 6, # alternating tab count (even, ≥ 2)
knuckle = Knuckle.FULL, # FULL = "bump on top", no ramp needed
)
base_leaf, lid_leaf = hinge.cylinder_side, hinge.pin_side
The default pin has 45° conical tips. Choose PinStyle.ROUNDED or the
experimental PinStyle.BRIDGED with the pin_style argument when needed.
PrintInPlaceHinge is a build123d Compound with two labelled children,
one per leaf:
cylinder_side— the leaf whose knuckle tabs carry the bores;pin_side— the leaf with the end caps and the captured pin.
(Internally these are abbreviated cs and ps.) Each child is a Compound,
because a bare leaf can be several solids — see mounting_flat below.
make_hinge(HingeParams(...)) builds the same thing from a reusable
parameter value.
Where the hinge comes out
The hinge is built flat-open, in print orientation, ready to fuse into a case whose walls stand on the bed:
- bed at Z = 0, wall top at Z =
case_h; - hinge axis along Y through X = 0, Z =
case_h + pivot_z_offset; cylinder_sidereaches X = +hinge.leaf_width,pin_sideX = −hinge.leaf_width— those outer faces are where the two case back walls go;- Y spans ±
hinge_length / 2.
So for a base whose back wall's outer face is at X = x0, place the leaves (build123d children are relative to their parent, so move the leaves themselves, not the hinge):
from build123d import Pos
at = Pos(x0 - hinge.leaf_width, y_centre, 0)
base = base + at * hinge.cylinder_side
lid = lid + at * hinge.pin_side
Joints
cylinder_side.joints["pivot"] is a RevoluteJoint on the hinge axis and
pin_side.joints["pivot"] a matching RigidJoint, so the hinge can be swung
in an assembly view (0° = flat-open as printed, 180° = closed):
hinge.cylinder_side.joints["pivot"].connect_to(hinge.pin_side.joints["pivot"], angle=180)
Each leaf also has a "mount" RigidJoint at the bottom centre of its outer
face (X = ±leaf_width, Y = 0, Z = 0), axes aligned with the hinge frame. To
attach the hinge to a case wall with joints instead of Pos:
from build123d import Location, RigidJoint
RigidJoint("hinge", base, Location((x0, y_centre, 0))) # back-wall outer face, bottom centre
base.joints["hinge"].connect_to(hinge.cylinder_side.joints["mount"])
hinge.cylinder_side.joints["pivot"].connect_to(hinge.pin_side.joints["pivot"], angle=0)
base = base + hinge.cylinder_side # leaves are now in place
lid = lid + hinge.pin_side
In context: a flat-open clamshell with HALF knuckle
Built by examples/clamshell.py — case_h = 10mm,
80 × 50 mm footprint, 60 mm hinge with Knuckle.HALF, plus four 6 × 3 mm
corner magnet pockets to latch the case shut. Both halves print as one
piece in the orientation shown. The example also emits a bare HALF/FULL
variant (no magnets) for reference.
Quick start
Install version 0.3.0 from PyPI
with uv add "pip-hinge>=0.3.0". Earlier releases do not include PinStyle
or FitProfile. In a checkout, install it for development with:
uv venv
uv pip install -e .
Then in your build123d code:
from pip_hinge import Knuckle, PrintInPlaceHinge
hinge = PrintInPlaceHinge(case_h=10, hinge_length=60, knuckle=Knuckle.FULL)
base = my_base + hinge.cylinder_side
lid = my_lid + hinge.pin_side
To run the included examples from a checkout:
uv run python examples/clamshell.py # writes clamshell_{full,half,small,magnets}.{step,stl}
uv run python examples/hinge_only.py # writes the bare hinge_{full,half}.{step,stl}
uv run python examples/conical_test_print.py --fit tighter # two-size print plate with tighter fit
Geometry at a glance
Cross-section (Panel A) shows the spatial parameters: case_h (wall
height), pivot_z_offset (extra lift), mounting_flat (flat past the
disc edge), plus the derived Po/Ro/T/W and the pin/bore inset.
Top view (Panel B) shows hinge_length, stations, derived
station pitch (hinge_length / stations), and clasp_clearance between tabs.
Knuckle sizes
knuckle |
knuckle diameter | ramp | gap between case walls (flat-open) |
|---|---|---|---|
Knuckle.FULL |
2 × (case_h + pivot_z_offset) |
none — rests on bed | 2 × (case_h + pivot_z_offset) + 2 × mounting_flat |
Knuckle.HALF |
case_h + pivot_z_offset |
45° self-supporting teardrop | case_h + pivot_z_offset + 2 × mounting_flat |
Knuckle.SMALL |
max((case_h + pivot_z_offset) / 2, 5 mm) |
~25° from vertical (smaller knuckle → naturally steeper) | max((case_h + pivot_z_offset) / 2, 5 mm) + 2 × mounting_flat |
The knuckle diameter Po is sized to the lifted axis height
case_h + pivot_z_offset, and the flat-open gap between the case walls is
always Po + 2 × mounting_flat (= 2 × hinge.leaf_width). For case_h = 10
with the defaults that is 21.4 mm (FULL), 11.2 mm (HALF) and 6.1 mm (SMALL).
See docs/clamshell-integration.md for mounting, orientation, multi-hinge layouts, and the closed-vs-open view.
Parameters
The four primary inputs:
| Parameter | Default | Meaning |
|---|---|---|
case_h |
(required) | Height in mm of the case wall each leaf attaches to |
hinge_length |
(required) | Total hinge length in mm along the rotation axis (Y) |
stations |
6 | Even count of tab positions along Y, at least 2. Each end cap occupies half a position; 4, 6, 8, … are supported |
knuckle |
Knuckle.FULL |
FULL, HALF, or SMALL — see the option table below |
stations counts alternating tab positions, not pin pairs or bored tabs.
For example, four stations make two bored cylinder-side tabs, one full
pin-side middle tab, and two half-width pin-side end caps. Any even count of
at least two is accepted; the usable count also depends on tab width.
Fit and pin options:
| Parameter | Default | Meaning |
|---|---|---|
pin_style |
PinStyle.CONICAL |
CONICAL: 45° tapered pin tips in matching tapered bores; ROUNDED: original hemispherical tips in round bores; BRIDGED: one continuous cylindrical pin in a teardrop-roof bore. Both revised bores released in one small clamshell print; the bridged hinge rocked noticeably |
knuckle_wall |
None |
Radial material thickness around the bore at its widest round section. None gives CONICAL at least 1.0 mm of modeled wall and grows the pin where station spacing permits. BRIDGED preserves at least 1.0 mm above its higher roof. Check the actual perimeter count in your slicer; set a value in mm to override |
mounting_flat |
0.5 | Width in mm of the flat leaf strip beyond the knuckle where the case wall joins. At or below the resolved pivot_clearance the bare leaf can contain separate solids that join when fused to the wall |
pivot_clearance |
None |
Difference in mm between bore and pin diameters at the straight shank. The gap there is half this value; CONICAL also keeps that gap normal to its sloped tip. None selects 0.6 mm with STANDARD or 0.4 mm for small conical hinges with TIGHT |
pivot_z_offset |
0.2 | Lift of the hinge axis above the wall top. When closed, the lid then rests 2 × pivot_z_offset above the base instead of meeting it on a zero-tolerance plane, so a high spot along the seam can't spring the front of the case open. Only the knuckle is raised — the leaves stay flush with the wall top. 0 disables it; must be less than the knuckle radius |
clasp_clearance |
None |
Gap in mm along Y between neighbouring tab faces. None selects the fit profile's value; an explicit value overrides it |
fit_profile |
FitProfile.STANDARD |
STANDARD retains 0.3 mm radial and axial gaps. TIGHT gives small conical hinges the narrower gaps that worked in the earlier cylindrical-bore and the small shaped-bore trial |
Tighter fit for small conical hinges
The two-size conical test plate printed and released with the standard 0.3 mm gaps, but both lids rocked around the pin and the smaller case also slid along the hinge axis. The tighter trial printed well on that printer. Both printed plates used the earlier cylindrical bore. The new shaped-bore trial also released and felt good on that printer. Select the tighter gap values with:
from pip_hinge import FitProfile, Knuckle, PrintInPlaceHinge
hinge = PrintInPlaceHinge(
case_h=6, hinge_length=24, stations=4, knuckle=Knuckle.SMALL,
fit_profile=FitProfile.TIGHT,
)
| Profile and geometry | Radial pin gap | Axial tab gap |
|---|---|---|
STANDARD (all hinges) |
0.3 mm | 0.3 mm |
TIGHT: conical SMALL, barrel diameter ≤ 5.5 mm, station pitch ≤ 6 mm |
0.2 mm | 0.2 mm |
TIGHT: conical SMALL, barrel diameter ≤ 5.5 mm, station pitch > 6 mm |
0.2 mm | 0.3 mm |
TIGHT: other geometry |
0.3 mm | 0.3 mm |
Station pitch is hinge_length / stations. The straight-shank radial gap and
conical-tip normal gap are half of pivot_clearance, so 0.2 mm means
pivot_clearance=0.4. Passing
pivot_clearance or clasp_clearance explicitly overrides that part of the
profile. Fit still depends on the printer, material, and slicer settings;
try a small test print before committing to a large case. The successful
prints reported here were made on one Bambu Lab P1S. Material, nozzle
diameter, line width, layer height, and slicer profile were not recorded.
These results do not establish separate clearance recommendations for 0.4 mm
and 0.6 mm nozzles; use the small Python trial
to check a different setup.
Pin shapes and print tradeoffs
CONICAL uses short 45° pin tips and matching tapered bores. The small
shaped-bore trial released and felt good in one print. ROUNDED preserves the older
hemispherical tips and cylindrical bores. BRIDGED runs one continuous pin
through the whole hinge, which can resist pulling the leaves apart along the
axis, but its internal spans must print unsupported.
BRIDGED makes the printer span each bored tab with an unsupported pin.
The unsupported distance is approximately hinge_length / stations + clasp_clearance: 5.3 mm for a 20 mm hinge with 4 stations, or 10.3 mm at
the 60 mm, 6-station defaults. The default pivot_clearance leaves only
0.3 mm of radial space between the printed pin and bore. Sagging plastic
can use up that space and fuse the hinge, even when the CAD solids are
separate. A tester reported that the earlier round bore roof settled onto
the pin and fused. The revised 45° teardrop roof released in one small
clamshell print, but that hinge was very loose and rocked around its pin.
In that size, the pointed roof leaves about 0.74 mm of modeled space above
the pin at its peak, even though the gap at the round sides is 0.3 mm.
The conical hinge from the same print felt much better. Check in the slicer
that the unsupported pin spans use bridge settings. Treat BRIDGED as an
experimental option when a continuous pin is needed; do not assume its
printed fit will match the conical hinge.
ROUNDED preserves the older pin and bore sizing. On a large barrel with
closely spaced stations, neighbouring rounded tips can meet inside a bore;
the library warns because the joined span may sag or fuse during printing.
The default CONICAL profile keeps an axial gap between its tips.
For 4 or more stations, the two middle tab types have the same width,
hinge_length / stations − clasp_clearance. Each end cap has half that width.
For example, a 20 mm hinge with 4 stations and the default gap has two
4.7 mm cylinder-side tabs, one 4.7 mm pin-side middle tab, and two 2.35 mm
end caps. The pin shank and tips extend into the bores beyond the middle
tab; that visible pin is wider than the tab itself.
The shank is the constant-diameter part of a pin between its pin-side tab
and its tapered or rounded tip. Three advanced settings change the shanks of
the CONICAL and ROUNDED pins. pin_cyl_extra adds to the station width to
give the middle shank length; it protrudes
(pin_cyl_extra + clasp_clearance) / 2 past each
middle tab face. pin_end_offset sets how far an end shank protrudes past its
end cap face; its reach into the next bored tab is that value minus
clasp_clearance. pin_short_cyl_factor × (hinge_length / stations) is
the total end shank length, including the portion buried in the cap. These
settings do not affect the continuous BRIDGED pin. That pin runs the full
hinge_length; a shorter bridged pin is not currently parameterized.
Geometry changes in 0.3
The default pin is now conical, with a matching tapered bore. It can use a wider bore than the original
rounded pin when station spacing leaves room for separate 45° tips; on
shorter hinges it uses a smaller bore to prevent those tips from joining.
PinStyle.ROUNDED retains the original pin tip profile and cylindrical bore.
The STANDARD tab gap is 0.3 mm measured between faces, with equal-width
middle tabs on both sides. Re-export existing designs and check the pin and
tab fit before relying on an older printed part.
Validation
PrintInPlaceHinge / make_hinge() raise ValueError for hard geometric problems:
- non-positive
case_h,hinge_length, ormounting_flat stations < 2or odd- negative
pivot_z_offset, or one not smaller than the knuckle radius - bore Ø ≤
pivot_clearance(knuckle too small for the pivot clearance) knuckle_walloutside the knuckle radius- invalid
pin_styleor clearances that leave no tab or pin material
And warns (warnings.warn) when:
- tab width
hinge_length / stations − clasp_clearancedrops below ~3 mm (too thin for FDM)
Printing
Lay flat on the bed with the hinge axis along Y (parallel to bed). Start with 0.2 mm layers and the part-cooling fan on. If adhesion needs a brim, keep it clear of the hinge gaps. After printing, gently flex the leaves to break the clearance gaps free.
For a first fit check of the current conical bore, use the small shaped-bore trial. The paired Python example generates the conical and bridged cases used in the printed comparison and accepts gap overrides for printer calibration. The earlier two-size plate documents the cylindrical-bore prints and their fit results.
- FULL knuckle body rests on the bed without supports. The optional
BRIDGEDpin still requires an unsupported bridge through each bore. - HALF knuckle body has a self-supporting underside on valid sizes: it
meets the disc tangentially at 45° and runs to the bed as a teardrop.
The optional
BRIDGEDpin still needs a bridge test.
How this was built
The build123d code, API design discussions, station-count generalisation, self-supporting ramp, clamshell example, and documentation in this repository were produced through a paired design session with Claude Code (Anthropic's Claude Opus 4.7). I drove the design decisions — what the API should look like, which knuckle geometries to support, what trade-offs to accept — and Claude wrote the code, generated the diagrams, ran the verifications, and opened the PRs. The conversation is the source of truth for why the code looks the way it does; the commit history reflects the steps.
The original FreeCAD geometry from r0berts provided the dimensional starting point. The current pin profiles and fit defaults also include changes described above. The Claude collaboration is on the build123d port and the case-designer-facing API built on top of it.
Provenance
This is a port of "Parametric print-in-place hinge. FreeCAD." by r0berts on Printables, licensed CC BY 4.0.
The original is a spreadsheet-driven FreeCAD model. This repository:
- Translates the FreeCAD geometry into build123d Python via fcd2b123d.
- Reparameterises around four case-designer-facing inputs (
case_h,hinge_length,stations,knuckle) with the original dimensional relationships derived under the hood. - Generalises the comb pattern (hardcoded 6 stations in the original) to
any even number of stations ≥ 2, adds optional
Knuckle.HALFandKnuckle.SMALLmodes, and offers three pin profiles.
Per the CC BY 4.0 terms: design and dimensional relationships are r0berts'; modifications are the build123d port, the four-input API, the configurable station count, knuckle sizes, pin profiles, and fit defaults. The original model's printer settings and extent of physical testing are not documented in this repository.
License
This work is licensed under Creative Commons Attribution 4.0 International (CC BY 4.0), matching the upstream Printables source. See LICENSE.
When using or redistributing, please credit:
- r0berts — original FreeCAD design (Printables)
- Paul Fremantle (pzfreo) — build123d port, four-input parameterisation, station generalisation, and ramp option
Metadata
Release files for pip-hinge 0.3.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 | Provenance | |
|---|---|---|---|---|
| pip_hinge-0.3.0.tar.gz | 382.0 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pip_hinge-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 402.7 kB
Release files / pip_hinge-0.3.0.tar.gz
| Download URL | pip_hinge-0.3.0.tar.gz |
|---|---|
| Size | 382.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
35983177d5ee61f67fe05611a2d25d59adc9824b2e035fac55ead568cfd6cfa9
|
|
BLAKE2b-256 checksum How to use checksums |
971cab54ad388213045a593b862b2640233d06f86583feb70c5f48fe6a811c7a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Oct 1, 2026.
Transparency logRelease files / pip_hinge-0.3.0-py3-none-any.whl
| Download URL | pip_hinge-0.3.0-py3-none-any.whl |
|---|---|
| Size | 20.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
362c44940fca3c38d51c552469503b2179cc154486f23cdbb6b9da9e1db946e1
|
|
BLAKE2b-256 checksum How to use checksums |
e6d5cc5efd9550fb053410fa9b8370a7374d280aaaa065e77841961f257db970
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Oct 1, 2026.
Transparency logSecurity for pip-hinge 0.3.0
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.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for pip_hinge-0.3.0.tar.gz
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 Oct 1, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.