Metadata-Version: 2.5
Name: pip-hinge
Version: 0.2.0
Summary: Parametric print-in-place piano hinge for clamshell cases, in build123d
Project-URL: Homepage, https://github.com/pzfreo/pip-hinge
Project-URL: Issues, https://github.com/pzfreo/pip-hinge/issues
Author-email: Paul Fremantle <pzfreo@gmail.com>
License: Creative Commons Attribution 4.0 International (CC BY 4.0)
        
        This work is a derivative of "Parametric print-in-place hinge. FreeCAD." by
        r0berts, published at:
        
            https://www.printables.com/model/1395662-parametric-print-in-place-hinge-freecad
        
        The original is licensed under CC BY 4.0. This derivative — a build123d Python
        port with full reparameterisation of the sketch geometry — is released under the
        same CC BY 4.0 license.
        
        Copyright:
            Original design © r0berts (Printables: @r0berts_1183620)
            build123d port and parameterisation © 2026 Paul Fremantle (pzfreo)
        
        You are free to:
            Share — copy and redistribute the material in any medium or format
            Adapt — remix, transform, and build upon the material for any purpose,
                    even commercially
        
        Under the following terms:
            Attribution — You must give appropriate credit, provide a link to the
            license, and indicate if changes were made. You may do so in any
            reasonable manner, but not in any way that suggests the licensor
            endorses you or your use.
        
            No additional restrictions — You may not apply legal terms or
            technological measures that legally restrict others from doing anything
            the license permits.
        
        Full legal text:
            https://creativecommons.org/licenses/by/4.0/legalcode
        
        Summary:
            https://creativecommons.org/licenses/by/4.0/
License-File: LICENSE
Requires-Python: <3.14,>=3.10
Requires-Dist: build123d>=0.6
Provides-Extra: test
Requires-Dist: pytest>=7; extra == 'test'
Description-Content-Type: text/markdown

# pip-hinge

A parametric print-in-place piano hinge in [build123d](https://github.com/gumyr/build123d),
designed for clamshell cases.

Four inputs:

```python
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):

```python
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):

```python
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`:

```python
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](docs/diagrams/clamshell_half_preview.png)

Built by [`examples/clamshell.py`](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](docs/diagrams/parameters_guide.png)

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](docs/diagrams/knuckle_options.png)

| `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](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."](https://www.printables.com/model/1395662-parametric-print-in-place-hinge-freecad)**
by **[r0berts](https://www.printables.com/@r0berts_1183620)** on Printables,
licensed [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).

The original is a spreadsheet-driven FreeCAD model. This repository:

1. Translates the FreeCAD geometry into build123d Python via
   [fcd2b123d](https://github.com/pzfreo/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](https://pypi.org/project/pip-hinge/):

```bash
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:

```python
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:

```bash
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](https://claude.com/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)](https://creativecommons.org/licenses/by/4.0/),
matching the upstream Printables source. See [LICENSE](LICENSE).

When using or redistributing, please credit:

- **r0berts** — original FreeCAD design ([Printables](https://www.printables.com/model/1395662-parametric-print-in-place-hinge-freecad))
- **Paul Fremantle** (pzfreo) — build123d port, four-input parameterisation, station generalisation, and ramp option
