Metadata-Version: 2.4
Name: pythonscad-gridfinity
Version: 0.2.3
Summary: Gridfinity baseplates and bins for PythonSCAD
Project-URL: Homepage, https://github.com/nomike/pythonscad-gridfinity
Project-URL: Documentation, https://github.com/nomike/pythonscad-gridfinity#readme
Project-URL: Repository, https://github.com/nomike/pythonscad-gridfinity
Project-URL: Issues, https://github.com/nomike/pythonscad-gridfinity/issues
Project-URL: Changelog, https://github.com/nomike/pythonscad-gridfinity/blob/main/CHANGELOG.md
Author-email: nomike <nomike@nomike.com>
License-Expression: BSD-3-Clause
License-File: LICENSE
Keywords: 3d-printing,cad,gridfinity,openscad,pythonscad
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Manufacturing
Classifier: License :: OSI Approved :: BSD License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Multimedia :: Graphics :: 3D Modeling
Classifier: Topic :: Scientific/Engineering
Requires-Python: >=3.10
Requires-Dist: pythonscad>=1.1.0
Description-Content-Type: text/markdown

# pythonscad-gridfinity

[![CI](https://github.com/nomike/pythonscad-gridfinity/actions/workflows/ci.yml/badge.svg)](https://github.com/nomike/pythonscad-gridfinity/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/pythonscad-gridfinity)](https://pypi.org/project/pythonscad-gridfinity/)
[![Python](https://img.shields.io/pypi/pyversions/pythonscad-gridfinity)](https://pypi.org/project/pythonscad-gridfinity/)
[![License: BSD 3-Clause](https://img.shields.io/badge/License-BSD_3--Clause-blue.svg)](LICENSE)

A Python library for generating [Gridfinity](https://gridfinity.xyz/)-compatible
baseplates and bins using [PythonSCAD](https://pythonscad.org/).

## Gallery

These previews are generated by the deterministic
`scripts/render_gallery_*.py` models using PythonSCAD.

```console
pythonscad --trust-python --backend=Manifold \
  -o baseplate.stl scripts/render_gallery_baseplate.py
```

| Baseplate | Standard bin | Vase-mode bin |
| --- | --- | --- |
| ![Weighted 2×2 baseplate](docs/gallery/baseplate.png) | ![2×1 bin with compartments](docs/gallery/bin.png) | ![2×1 vase bin](docs/gallery/vase.png) |

## Dependencies

- **Runtime:** [PythonSCAD](https://pythonscad.org/) **1.1.0 or newer**
  (`pythonscad>=1.1.0` on PyPI)
- **Python:** 3.10+

The library imports `gridfinity` in PythonSCAD scripts. Geometry is built with
PythonSCAD's `pythonscad` module (`from pythonscad import *` in examples).

## Installation

```console
pip install pythonscad-gridfinity
```

For development:

```console
git clone https://github.com/nomike/pythonscad-gridfinity.git
cd pythonscad-gridfinity
uv sync --all-groups
```

When opening examples from a clone without installing, add `src/` to the path
(see `examples/basic_baseplate.py`).

### Use it as a project dependency

Put this in `requirements.txt`:

```text
pythonscad-gridfinity
```

Or add it to another project's `pyproject.toml`:

```toml
[project]
dependencies = [
  "pythonscad-gridfinity",
]
```

## Usage

```python
from pythonscad import *
from gridfinity import GridfinityBaseplate, GridfinityBin, HoleOptions

bp = GridfinityBaseplate(2, 2, style="weighted",
                         hole_options=HoleOptions(magnet_hole=True))
bp.render().color("SteelBlue").show()

b = GridfinityBin(2, 1, 3, div_x=2, scoop=1.0)
b.render().color("Tomato").show()
```

Export without the GUI:

```console
pythonscad --trust-python -o baseplate.stl my_script.py
```

## What is this?

[Gridfinity](https://www.youtube.com/watch?v=ra_9zU-mnl8) is a modular storage
system created by [Zack Freedman](https://www.youtube.com/c/ZackFreedman). It
uses a standardised grid of interlocking baseplates and bins to organise tools,
parts, and other small items.

[PythonSCAD](https://pythonscad.org/) is a fork of OpenSCAD that adds a native
Python API for programmatic 3D modelling. This library lets you generate
Gridfinity objects directly from Python scripts inside PythonSCAD.

## Features

### Baseplates

- Five baseplate styles: thin, weighted, skeletonized, screw-together, and
  screw-together-minimal.
- Configurable magnet holes (6 mm x 2 mm) with optional crush ribs, chamfer,
  and supportless printing.
- Gridfinity Refined side-insert magnet holes.
- M3 screw holes with countersink or counterbore options.
- Fit-to-drawer mode with configurable padding alignment.
- Screw-together channels for joining multiple baseplates.

### Bins

- Configurable grid size (X, Y) and height (in Gridfinity units, internal mm,
  or external mm).
- Equal compartments via `div_x` / `div_y` dividers.
- Custom compartment layouts with arbitrary placement and per-compartment
  scoop / tab control via the `Compartment` class.
- Finger scoops (adjustable weight 0--1).
- Label tabs (full, auto, left, center, right, or none).
- Stacking lip (normal, reduced, or none).
- Lite bins with hollow shell bases for faster printing and less material.
- Half-grid bins with 21 mm base units (half the standard 42 mm).
- Cylindrical cutouts for tool holders (chamfered cylinder holes).
- Z-snap to round height to the nearest 7 mm increment.
- Only-corners hole placement to save print time.
- Compartment depth override.
- Tab placement control (everywhere or top-left only).
- Gridfinity Refined thumbscrew holes (M15 x 1.5 compatible) for secure
  baseplate attachment.
- Scoop chamfer for easier part removal.
- Solid bin option with configurable fill ratio.
- Bottom magnet/screw holes (same options as baseplates).
- Interior edge fillets using PythonSCAD's native `.fillet()`.

### Spiral / vase-mode bins

- Bins designed for spiral (vase) mode printing with single-wall extrusions.
- Configurable nozzle width and layer height.
- Single-wall dividers.
- Magic slice for slicer compatibility.
- Base cross pattern for baseplate attachment.
- Scoop chamfer on the front wall for easy part removal.
- Lip pinch for added structural strength.
- Front inset for reinforcement at the scoop area.

### Hole options

- Standard magnet holes
- Crush-rib press-fit magnet holes
- Chamfered holes for easy magnet insertion
- Supportless (bridged) holes for printing without supports
- Gridfinity Refined friction-fit holes
- Underside screw mounting (countersink / counterbore)

### Architecture

- All Gridfinity standard dimensions are encoded in a single `GridfinitySpec`
  class, making it easy to reference or override measurements.
- Clean Python API using `dataclasses`, operator overloading (`|`, `-`), and
  PythonSCAD convenience methods (`.up()`, `.rotz()`, etc.).

## Installation (legacy clone layout)

When not using `pip install`, add the `src/` directory from a clone:

```python
import sys
import os
from pythonscad import *

sys.path.insert(0, os.path.join("/path/to/pythonscad-gridfinity", "src"))

from gridfinity import GridfinityBaseplate, HoleOptions
```

If your script lives inside the repository (e.g. in `examples/`), use
`modelpath()` for a relative path:

```python
import sys
import os
from pythonscad import *

root = os.path.dirname(os.path.dirname(os.path.abspath(modelpath())))
sys.path.insert(0, os.path.join(root, "src"))

from gridfinity import GridfinityBaseplate, HoleOptions
```

## Quick start

### Thin baseplate (1 x 1)

```python
from pythonscad import *
from gridfinity import GridfinityBaseplate

bp = GridfinityBaseplate(1, 1, style="thin")
```

### Weighted baseplate with magnet holes (4 x 3)

```python
from gridfinity import GridfinityBaseplate, HoleOptions

bp = GridfinityBaseplate(
    4, 3,
    style="weighted",
    hole_options=HoleOptions(magnet_hole=True, crush_ribs=True, chamfer=True),
)
bp.render().color("SteelBlue").show()
```

### Skeletonized baseplate (2 x 2)

```python
from gridfinity import GridfinityBaseplate, HoleOptions

bp = GridfinityBaseplate(
    2, 2,
    style="skeleton",
    hole_options=HoleOptions(magnet_hole=True),
)
bp.render().show()
```

### Simple bin (2 x 1, 3U)

```python
from gridfinity import GridfinityBin

b = GridfinityBin(2, 1, 3)
b.render().color("SteelBlue").show()
```

### Bin with compartments, scoops, and tabs (3 x 2, 6U)

```python
from gridfinity import GridfinityBin, HoleOptions

b = GridfinityBin(
    3, 2, 6,
    div_x=3, div_y=2,
    scoop=1.0,
    tab_style="auto",
    hole_options=HoleOptions(magnet_hole=True),
)
b.render().color("Tomato").show()
```

### Custom compartment layout (3 x 2, 6U)

```python
from gridfinity import GridfinityBin, Compartment, HoleOptions

b = GridfinityBin(
    3, 2, 6,
    compartments=[
        Compartment(0, 0, 2, 2, scoop=1.0, tab_style="left"),
        Compartment(2, 0, 1, 1, scoop=0.5, tab_style="right"),
        Compartment(2, 1, 1, 1, scoop=0.0, tab_style="none"),
    ],
    hole_options=HoleOptions(magnet_hole=True),
)
b.render().color("CadetBlue").show()
```

### Lite bin with hollow base (2 x 2, 6U)

```python
from gridfinity import GridfinityBin, HoleOptions

b = GridfinityBin(
    2, 2, 6,
    div_x=2, div_y=2,
    lite=True,
    base_thickness=1.0,
    hole_options=HoleOptions(magnet_hole=True),
)
b.render().color("LightCoral").show()
```

### Spiral/vase-mode bin (2 x 1, 6U)

```python
from gridfinity import GridfinityVaseBin

v = GridfinityVaseBin(2, 1, 6, n_divx=2, nozzle=0.4)
v.render().color("Tomato").show()
```

## Baseplate styles

| Style | Description |
| --- | --- |
| `"thin"` | Minimal baseplate with just the lip profile. Thinnest option. |
| `"weighted"` | Thick bottom (6.4 mm extra) with rectangular weight cutouts for stability. |
| `"skeleton"` | Thick bottom hollowed out, keeping solid material only around hole positions. |
| `"screw_together"` | Thick bottom with horizontal screw channels between cells for joining baseplates. |
| `"screw_together_minimal"` | Screw channels combined with the thin lip profile. |

## Hole option details

The `HoleOptions` dataclass controls magnet and screw hole features:

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `magnet_hole` | `bool` | `False` | Hole for a 6 mm x 2 mm magnet. |
| `screw_hole` | `bool` | `False` | M3 screw hole beneath the magnet hole. |
| `crush_ribs` | `bool` | `False` | Sinusoidal crush ribs for press-fit magnets. |
| `chamfer` | `bool` | `True` | 45-degree chamfer around the hole opening. |
| `supportless` | `bool` | `False` | Bridging layers so the hole prints without supports. |
| `refined_hole` | `bool` | `False` | Gridfinity Refined side-insert style. Mutually exclusive with `magnet_hole`. |

## Bin options

### Tab styles

| Style | Description |
| --- | --- |
| `"full"` | Full-width tab across the entire compartment back wall. |
| `"auto"` | Left-aligned for the leftmost column, right for rightmost, center otherwise. |
| `"left"` | Tab left-aligned on the back wall. |
| `"center"` | Tab centered on the back wall. |
| `"right"` | Tab right-aligned on the back wall. |
| `"none"` | No label tab. |

Tabs are automatically disabled when the bin is shorter than 3 height units.

### Lip styles

| Style | Description |
| --- | --- |
| `"normal"` | Standard stacking lip for stacking bins. |
| `"reduced"` | No lip, height is reduced accordingly. |
| `"none"` | No lip, but total height is preserved. |
| `"subtractive"` | Lip zone is subtracted from the bin top (for lite bins). Height is reduced. |

### Height modes

| Mode | Interpretation of `height_u` |
| --- | --- |
| `"units"` | Gridfinity height units (1U = 7 mm above the base). Default. |
| `"mm_internal"` | Interior cavity height in millimetres. |
| `"mm_external"` | Total external height in millimetres. |

## API reference

### `GridfinitySpec`

All Gridfinity standard dimensions as class-level constants. Key values:

- `GRID_SIZE = 42.0` -- one grid unit in mm
- `BASEPLATE_HEIGHT = 5.0` -- minimum baseplate height
- `MAGNET_HOLE_RADIUS = 3.25` -- for 6 mm magnets
- `SCREW_HOLE_RADIUS = 1.5` -- M3 screw

See [`spec.py`](src/gridfinity/spec.py) for the full list.

### `GridfinityBaseplate`

```python
GridfinityBaseplate(
    grid_x, grid_y,
    spec=None,              # GridfinitySpec instance (default: standard)
    style="thin",           # One of: thin, weighted, skeleton, screw_together, screw_together_minimal
    hole_options=None,      # HoleOptions instance
    screw_style="none",     # Underside mounting: none, countersink, counterbore
    min_size_mm=(0, 0),     # Minimum size for fit-to-drawer
    fit_offset=(0, 0),      # Padding alignment (-1..1 per axis)
    screw_diameter=3.35,    # Screw-together channel diameter
    screw_head_diameter=5.0,
    screw_spacing=0.5,
    n_screws=1,             # Screws per grid edge (1-3)
)
```

Call `.render()` to get a PythonSCAD 3D object, then `.show()` or `.export()`.

### `GridfinityBin`

```python
GridfinityBin(
    grid_x, grid_y, height_u,
    spec=None,              # GridfinitySpec instance (default: standard)
    div_x=1,                # Compartments along X (1 = no dividers)
    div_y=1,                # Compartments along Y (1 = no dividers)
    scoop=1.0,              # Scoop weight (0.0 = off, 1.0 = full)
    tab_style="auto",       # One of: full, auto, left, center, right, none
    lip_style="normal",     # One of: normal, reduced, none
    hole_options=None,       # HoleOptions instance for bottom holes
    height_mode="units",    # One of: units, mm_internal, mm_external
    solid=False,            # Fill the interior (no compartments)
    solid_ratio=1.0,        # Fill fraction when solid (0.0--1.0)
    compartments=None,      # List of Compartment objects (overrides div_x/div_y)
    lite=False,             # Hollow shell base for faster printing
    base_thickness=1.0,     # Bottom thickness in mm (lite bins only)
    half_grid=False,        # Use 21 mm (half-size) bases; implies only_corners holes
    cut_cylinders=False,    # Cylindrical cutouts instead of compartments
    cylinder_diameter=10.0, # Diameter of cylindrical cutouts (mm)
    cylinder_chamfer=0.5,   # Chamfer radius around top rim (mm)
    enable_zsnap=False,     # Snap height to nearest 7 mm increment
    only_corners=False,     # Holes at corners only (saves print time)
    depth=0,                # Override compartment depth in mm (0 = full)
    place_tab="everywhere", # "everywhere" or "top_left"
    enable_thumbscrew=False,# M15x1.5 thumbscrew hole in each base unit
    scoop_chamfer=False,    # 45-degree chamfer at top of scoop
)
```

Call `.render()` to get a PythonSCAD 3D object, then `.show()` or `.export()`.

### `GridfinityVaseBin`

```python
GridfinityVaseBin(
    grid_x, grid_y, height_u,
    spec=None,              # GridfinitySpec instance (default: standard)
    nozzle=0.6,             # Nozzle width in mm (walls = 2 * nozzle)
    layer_height=0.35,      # Slicer layer height in mm
    bottom_layers=3,        # Number of solid bottom layers
    n_divx=1,               # Number of X compartments (single-wall dividers)
    enable_lip=True,        # Include stacking lip
    enable_holes=True,      # Magnet holes in the base
    enable_zsnap=False,     # Snap height to nearest 7 mm increment
    enable_scoop_chamfer=True,  # Front wall chamfer for easy part removal
    enable_pinch=True,      # Pinch lip for structural strength
    enable_front_inset=True,# Front reinforcement when scoop is present
)
```

Call `.render()` to get a PythonSCAD 3D object. **Print in spiral/vase mode.**

### `Compartment`

Defines a single compartment in a custom layout. Positions and sizes are in
fractional grid units relative to the bin's `grid_x` / `grid_y`.

```python
Compartment(
    x,                      # Grid X position of left edge
    y,                      # Grid Y position of front edge
    w,                      # Grid width (X)
    h,                      # Grid depth (Y)
    scoop=None,             # Scoop weight 0.0--1.0 (None inherits from bin)
    tab_style=None,         # Tab style (None inherits from bin)
)
```

### `HoleOptions`

See the [Hole option details](#hole-option-details) table above.

### Helper functions

The library also exposes lower-level building blocks in case you want to
compose custom objects:

- `block_base_hole(options, spec)` -- single combined magnet/screw hole
- `hole_pattern(obj, spec)` -- place an object at the four hole positions
- `refined_hole(spec)` -- Gridfinity Refined magnet hole geometry
- `cut_chamfered_cylinder(radius, depth, chamfer_radius, cut_lip)` --
  chamfered cylindrical cutout for tool holders

## Project structure

```text
pythonscad-gridfinity/
  src/gridfinity/
    __init__.py       # Package exports
    spec.py           # GridfinitySpec (all standard dimensions)
    helpers.py        # Utility functions (rounded_square, pattern_grid, etc.)
    holes.py          # HoleOptions and hole geometry builders
    baseplate.py      # GridfinityBaseplate class
    bin.py            # GridfinityBin class
    vase.py           # GridfinityVaseBin class (spiral/vase mode)
  examples/
    basic_baseplate.py
    basic_bin.py
    vase_bin.py
  scripts/
    render_gallery_*.py
  tests/
  pyproject.toml
  LICENSE
  README.md
```

## Development

See [CONTRIBUTING.md](CONTRIBUTING.md) for setup and checks,
[VERSIONING.md](VERSIONING.md) for releases, and
[PUBLISHING.md](PUBLISHING.md) for GitHub and PyPI trusted publishing.

## Running from the command line

You can render and export baseplates without opening the PythonSCAD GUI:

```bash
pythonscad --trust-python -o baseplate.stl my_baseplate_script.py
```

The `--trust-python` flag is required to enable the Python interpreter.

## Acknowledgements

This library would not exist without the following projects and people:

- **[Zack Freedman](https://www.youtube.com/c/ZackFreedman)** for designing the
  [Gridfinity](https://www.youtube.com/watch?v=ra_9zU-mnl8) modular storage
  system and releasing it to the community.

- **[gridfinity-rebuilt-openscad](https://github.com/kennetek/gridfinity-rebuilt-openscad)**
  by [kennetek](https://github.com/kennetek) -- the definitive OpenSCAD
  implementation of the Gridfinity standard. This library's grid dimensions,
  base/lip profile geometry, stacking lip cross-section, baseplate styles
  (thin, weighted, skeleton, screw-together), bin construction (wall heights,
  compartment dividers, finger scoops, label tabs), and hole specifications
  (magnet, screw, crush-rib, supportless, and Gridfinity Refined) are all
  derived from gridfinity-rebuilt-openscad.

- **[cq-gridfinity](https://github.com/michaelgale/cq-gridfinity)** by
  [Michael Gale](https://github.com/michaelgale) -- a CadQuery/Python
  implementation of Gridfinity. Used as an additional reference for
  cross-checking geometry calculations and for informing the Python-oriented
  API design of this library.

- **[PythonSCAD](https://pythonscad.org/)** for making Python-based 3D
  modelling possible with a native Python API on top of the OpenSCAD engine.

## License

[BSD 3-Clause](LICENSE)
