Skip to main content

pip-hinge

A parametric print-in-place piano hinge in build123d, designed for clamshell cases.

Four inputs:

from pip_hinge import Knuckle, 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

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_side reaches X = +hinge.leaf_width, pin_side X = −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):

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:

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

clamshell with HALF knuckle and corner magnet pockets, flat-open print orientation

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.

Parameter reference

parameters guide

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 clasp_width, and clasp_clearance between meshing tabs.

The two knuckle options

knuckle options

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.

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:

  1. Translates the FreeCAD geometry into build123d Python via fcd2b123d.
  2. Reparameterises around four case-designer-facing inputs (case_h, hinge_length, stations, knuckle) with the original dimensional relationships derived under the hood.
  3. Generalises the comb pattern (hardcoded 6 stations in the original) to any even number of stations ≥ 2, and adds an optional Knuckle.HALF mode with a self-supporting teardrop knuckle for cases where a smaller knuckle is wanted.

Per the CC BY 4.0 terms: design and dimensional relationships are r0berts'; modifications are the build123d port, the four-input API, and the configurable station count and ramp.

Quick start

Install the latest release from PyPI:

uv add pip-hinge

Use uv pip install pip-hinge instead when installing directly into an environment rather than adding it to a project.

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

Or to play with it locally:

git clone https://github.com/pzfreo/pip-hinge.git && cd pip-hinge
uv pip install -e .                          # editable install
python examples/clamshell.py                 # writes clamshell_{full,half,small,magnets}.{step,stl}
python examples/hinge_only.py                # writes the bare hinge_{full,half}.{step,stl}

Parameters

The four primary inputs:

Parameter Default Meaning
case_h (required) Case wall height; the hinge's "scale" reference
hinge_length (required) Total hinge length along the axis (Y)
stations 6 Number of alternating cylinder-side / pin-side tabs (even, ≥ 2)
knuckle Knuckle.FULL FULL, HALF, or SMALL — see the option table below

Four small tuneables:

Parameter Default Meaning
mounting_flat 0.5 Flat width past the disc edge for case-wall fusion. Below pivot_clearance (= 0.6 mm) the bare hinge fragments into multiple solids — fine when fused into a case, see docs
pivot_clearance 0.6 Radial pin/bore gap (FDM tolerance)
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 Axial gap between meshing tabs. None auto-scales with knuckle diameter Po: 0.2 mm at Po ≤ 5 mm (matches r0berts' original), linear up to 0.4 mm at Po ≥ 10 mm. Pass an explicit value to override

Plus three pin-engagement constants from the original FreeCAD source (pin_cyl_extra, pin_end_offset, pin_short_cyl_factor) — leave at defaults unless deliberately tuning the pin/bore feel.

Validation

PrintInPlaceHinge / make_hinge() raise ValueError for hard geometric problems:

  • non-positive case_h, hinge_length, or mounting_flat
  • stations < 2 or odd
  • negative pivot_z_offset, or one not smaller than the knuckle radius
  • bore Ø ≤ pivot_clearance (knuckle too small for the pivot clearance)

And warns (warnings.warn) when:

  • clasp_width = hinge_length / stations drops below ~3 mm (too thin for FDM)

Printing

Lay flat on the bed with the hinge axis along Y (parallel to bed). 0.2 mm layers, fan on, brim recommended. After printing, gently flex the leaves to break the clearance gaps free.

  • FULL prints without any supports at any knuckle size — the knuckle rests on the bed.
  • HALF prints without supports at any case_h: the meshing-side underside meets the knuckle tangentially at 45° and runs to the bed as a self-supporting teardrop, so the disc's downward arc is never left hanging.

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 is unchanged in its dimensional relationships — it was reparameterised, not redesigned. The Claude collaboration is on the build123d port and the case-designer-facing API built on top of it.

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

Release files for pip-hinge 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pip-hinge 0.2.0
File Size Uploaded
pip_hinge-0.2.0.tar.gz 364.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pip-hinge 0.2.0
File Interpreter ABI Platform
pip_hinge-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 379.4 kB

Release files / pip_hinge-0.2.0.tar.gz

Download URL pip_hinge-0.2.0.tar.gz
Size 364.5 kB
Tags Source
SHA-256 checksum
How to use checksums
34c7c31c0d8446512e27de592bb9def55a508cd38fe382ee22a6514b856e8004
BLAKE2b-256 checksum
How to use checksums
38e31d383f79e81d108a7a65923ba561517f8a1b04b8772bea247b95b1a57351
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 Sep 27, 2026.

Transparency log

Release files / pip_hinge-0.2.0-py3-none-any.whl

Download URL pip_hinge-0.2.0-py3-none-any.whl
Size 14.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8f173c14fd7fef1f1534222105de4c5aa9da6aeb513006e7cd0913f1ab6898fb
BLAKE2b-256 checksum
How to use checksums
9d9893bb55a11dbd1d7b4aee81a21e931e25b19057a4541ffe6df7f02df5f092
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 Sep 27, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page