Shapely-based Python interface for PackingSolver — 2D irregular bin packing & nesting
Project description
pyckingsolver
Shapely-based Python interface for PackingSolver — 2D irregular bin packing & nesting.
Pack irregular shapes into bins — rectangles, circles, arbitrary polygons with holes. Built for CNC laser cutting, sheet metal nesting, fabric cutting, and any 2D packing problem.
Installation
pip install pyckingsolver
Note: The C++ solver binary (
packingsolver_irregular) must be compiled separately from the upstream repository. This package handles instance building, JSON I/O, and solution parsing. UseSolver()to call the binary automatically.
Quick Start
from shapely.geometry import Polygon, Point
from packingsolver import InstanceBuilder, Objective, Solver
# Build an instance
b = InstanceBuilder(Objective.OPEN_DIMENSION_X)
b.add_bin_type_rectangle(500, 300)
# Add items — any Shapely Polygon works
b.add_item_type_rectangle(80, 60, copies=4)
b.add_item_type(Polygon([(0,0), (50,0), (25,40)]), copies=6)
# Polygon with holes (e.g., washer)
washer = Point(0,0).buffer(20).difference(Point(0,0).buffer(10))
b.add_item_type(washer, copies=3)
instance = b.build()
# Solve (requires compiled C++ binary)
solver = Solver(binary="path/to/packingsolver_irregular")
solution = solver.solve(instance, time_limit=30)
print(f"{solution.total_item_count()} items in {solution.total_bins_used()} bins")
# Access placed items as Shapely geometries
for bin in solution.bins:
for item in bin.items:
for shape in item.shapes: # already in absolute coordinates
print(shape.area, shape.bounds)
Features
- Shapely-native — define items and bins as Shapely Polygons
- Any shape — rectangles, circles, arbitrary polygons, shapes with holes
- Solver wrapper — call the C++ binary from Python via
Solver.solve() - Full JSON round-trip — build instances in Python, parse solutions back to Shapely
- Forward-compatible — unknown JSON fields are preserved through
_extradicts - Nesting in holes — items can be placed inside other items' holes
- Rotation & mirroring — specify allowed rotation ranges per item type
Objectives
| Objective | Description |
|---|---|
OPEN_DIMENSION_X |
Minimize strip width (items packed left-to-right) |
OPEN_DIMENSION_Y |
Minimize strip height |
BIN_PACKING |
Minimize number of bins used |
KNAPSACK |
Maximize value of items packed in one bin |
VARIABLE_SIZED_BIN_PACKING |
Minimize cost with multiple bin sizes |
BIN_PACKING_WITH_LEFTOVERS |
Bin packing considering reusable leftovers |
API Reference
InstanceBuilder
Fluent API for constructing packing problems:
b = InstanceBuilder(Objective.OPEN_DIMENSION_X)
# Global settings
b.set_item_item_minimum_spacing(2.0) # mm between items (e.g., laser kerf)
# Bins
b.add_bin_type_rectangle(width, height, copies=1, cost=-1)
b.add_bin_type_circle(radius, resolution=64)
b.add_bin_type(shapely_polygon) # any Shapely Polygon
# Defects (no-go zones in bins)
b.add_defect(bin_type_id=0, shape=defect_polygon)
# Items
b.add_item_type_rectangle(w, h, copies=1, profit=-1)
b.add_item_type(shapely_polygon, copies=1,
allowed_rotations=[(0, 360)], # continuous rotation
allow_mirroring=True)
instance = b.build()
Rotation Control
# Fixed orientation only (default)
b.add_item_type(shape, allowed_rotations=[(0, 0)])
# 90-degree increments
b.add_item_type(shape, allowed_rotations=[(0,0), (90,90), (180,180), (270,270)])
# Free rotation
b.add_item_type(shape, allowed_rotations=[(0, 360)])
Instance I/O
# Save/load JSON (compatible with C++ solver)
instance.to_json("problem.json")
instance = Instance.from_json("problem.json")
# Dict round-trip
d = instance.to_dict()
instance = Instance.from_dict(d)
Solver
solver = Solver(binary="path/to/packingsolver_irregular")
solution = solver.solve(
instance,
time_limit=60, # seconds
verbosity_level=1, # 0=quiet, 1=summary, 2=verbose
output_path="sol.json", # optional: save solution JSON
extra_args=["--flag"], # additional CLI args
)
Solution
solution = Solution.from_json("solution.json")
solution.total_item_count() # total items placed
solution.total_bins_used() # total bins used
solution.all_items() # flat list of SolutionItem
for bin in solution.bins:
for item in bin.items:
item.item_type_id # which item type
item.x, item.y # placement position
item.angle # rotation (degrees)
item.mirror # mirrored?
item.shapes # list[Polygon] — absolute coordinates
Geometry Helpers
from packingsolver import (
shapely_to_polygon_json, # Shapely → solver JSON
json_shape_to_shapely, # solver JSON → Shapely
circle_to_polygon, # circle approximation
elements_to_shapely, # arc/line elements → Shapely
)
Metal Cutting Example
from shapely.geometry import Polygon, Point
from packingsolver import InstanceBuilder, Objective, Solver
b = InstanceBuilder(Objective.OPEN_DIMENSION_X)
b.set_item_item_minimum_spacing(2.0) # 2mm laser kerf
b.add_bin_type_rectangle(1200, 600) # sheet metal
# Mounting plate with bolt holes
plate = Polygon([(0,0),(150,0),(150,100),(0,100)])
for cx, cy in [(25,25), (125,25), (25,75), (125,75)]:
plate = plate.difference(Point(cx, cy).buffer(12, resolution=16))
b.add_item_type(plate, copies=4,
allowed_rotations=[(0,0),(90,90),(180,180),(270,270)])
# Small discs that fit inside bolt holes
b.add_item_type(Point(0,0).buffer(8, resolution=16), copies=8)
# L-bracket
b.add_item_type(
Polygon([(0,0),(80,0),(80,60),(70,60),(70,10),(10,10),(10,60),(0,60)]),
copies=6)
solver = Solver()
solution = solver.solve(b.build(), time_limit=30)
print(f"Packed {solution.total_item_count()} items into {solution.total_bins_used()} bin(s)")
Building the C++ Solver
git clone https://github.com/fontanf/packingsolver
cd packingsolver
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release
# Binary: build/src/irregular/packingsolver_irregular
How It Works
Python (Shapely) → JSON → C++ Solver → JSON → Python (Shapely)
build ↓ ↓ ↓ parse
InstanceBuilder instance optimize solution Solution
.json .json
- Build — define bins and items as Shapely Polygons via
InstanceBuilder - Serialize — convert to PackingSolver JSON format (CCW winding enforced)
- Solve — C++ solver finds optimal packing
- Parse — solution items returned as Shapely geometries in absolute coordinates
License
MIT — see LICENSE.
Based on PackingSolver by Florian Fontan.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyckingsolver-0.1.0-py3-none-win_amd64.whl.
File metadata
- Download URL: pyckingsolver-0.1.0-py3-none-win_amd64.whl
- Upload date:
- Size: 2.6 MB
- Tags: Python 3, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d2031d02e7f2cd459d02aebf4a3b9ef4a9bd3ee006d9f9e292c6d955ecca196b
|
|
| MD5 |
1980c80b34327c10e75dade863b0f99d
|
|
| BLAKE2b-256 |
da1502f2ef2e4ef8a2038a715ef0a923dc21427c06f166cb0d7ebf0287bf3da6
|
Provenance
The following attestation bundles were made for pyckingsolver-0.1.0-py3-none-win_amd64.whl:
Publisher:
build.yml on HamzaYslmn/pyckingsolver
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyckingsolver-0.1.0-py3-none-win_amd64.whl -
Subject digest:
d2031d02e7f2cd459d02aebf4a3b9ef4a9bd3ee006d9f9e292c6d955ecca196b - Sigstore transparency entry: 1119049654
- Sigstore integration time:
-
Permalink:
HamzaYslmn/pyckingsolver@a2da5bedce607e0d99e738031a2709ed3cc76dc6 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/HamzaYslmn
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
build.yml@a2da5bedce607e0d99e738031a2709ed3cc76dc6 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pyckingsolver-0.1.0-py3-none-manylinux_2_35_x86_64.whl.
File metadata
- Download URL: pyckingsolver-0.1.0-py3-none-manylinux_2_35_x86_64.whl
- Upload date:
- Size: 4.4 MB
- Tags: Python 3, manylinux: glibc 2.35+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
af74a210c474d0af973e91441b60ec545fd929ab89852f0ef0395e323cd6282b
|
|
| MD5 |
55556e45829d68f8415b29594265bb16
|
|
| BLAKE2b-256 |
8ab0864d7fd4515667b644075526e945f89d8de138ad7b3b75978d0fc91a2826
|
Provenance
The following attestation bundles were made for pyckingsolver-0.1.0-py3-none-manylinux_2_35_x86_64.whl:
Publisher:
build.yml on HamzaYslmn/pyckingsolver
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyckingsolver-0.1.0-py3-none-manylinux_2_35_x86_64.whl -
Subject digest:
af74a210c474d0af973e91441b60ec545fd929ab89852f0ef0395e323cd6282b - Sigstore transparency entry: 1119049723
- Sigstore integration time:
-
Permalink:
HamzaYslmn/pyckingsolver@a2da5bedce607e0d99e738031a2709ed3cc76dc6 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/HamzaYslmn
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
build.yml@a2da5bedce607e0d99e738031a2709ed3cc76dc6 -
Trigger Event:
push
-
Statement type: