Skip to main content

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.

PyPI version Python License: MIT

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. Use Solver() 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 _extra dicts
  • 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
  1. Build — define bins and items as Shapely Polygons via InstanceBuilder
  2. Serialize — convert to PackingSolver JSON format (CCW winding enforced)
  3. Solve — C++ solver finds optimal packing
  4. Parse — solution items returned as Shapely geometries in absolute coordinates

License

MIT — see LICENSE.

Based on PackingSolver by Florian Fontan.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

pyckingsolver-0.1.0-py3-none-win_amd64.whl (2.6 MB view details)

Uploaded Python 3Windows x86-64

pyckingsolver-0.1.0-py3-none-manylinux_2_35_x86_64.whl (4.4 MB view details)

Uploaded Python 3manylinux: glibc 2.35+ x86-64

File details

Details for the file pyckingsolver-0.1.0-py3-none-win_amd64.whl.

File metadata

File hashes

Hashes for pyckingsolver-0.1.0-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 d2031d02e7f2cd459d02aebf4a3b9ef4a9bd3ee006d9f9e292c6d955ecca196b
MD5 1980c80b34327c10e75dade863b0f99d
BLAKE2b-256 da1502f2ef2e4ef8a2038a715ef0a923dc21427c06f166cb0d7ebf0287bf3da6

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyckingsolver-0.1.0-py3-none-win_amd64.whl:

Publisher: build.yml on HamzaYslmn/pyckingsolver

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pyckingsolver-0.1.0-py3-none-manylinux_2_35_x86_64.whl.

File metadata

File hashes

Hashes for pyckingsolver-0.1.0-py3-none-manylinux_2_35_x86_64.whl
Algorithm Hash digest
SHA256 af74a210c474d0af973e91441b60ec545fd929ab89852f0ef0395e323cd6282b
MD5 55556e45829d68f8415b29594265bb16
BLAKE2b-256 8ab0864d7fd4515667b644075526e945f89d8de138ad7b3b75978d0fc91a2826

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page