U-Nesting
A high-performance 2D/3D spatial optimization engine for nesting and bin packing problems.
Features
- 2D Nesting: Optimal placement of irregular polygons on sheets
- 3D Bin Packing: Efficient box placement in containers
- Multiple Algorithms: BLF, NFP-guided, Genetic Algorithm, BRKGA, Simulated Annealing
- High Performance: Written in Rust with Python bindings
- Type Hints: Full type annotation support
Installation
pip install u-nesting
Quick Start
2D Nesting
import u_nesting
# Define polygons to nest
geometries = [
{
"id": "part1",
"polygon": [[0, 0], [100, 0], [100, 50], [0, 50]],
"quantity": 5,
"rotations": [0, 90, 180, 270]
},
{
"id": "triangle",
"polygon": [[0, 0], [80, 0], [40, 60]],
"quantity": 3
}
]
# Define sheet boundary
boundary = {"width": 500, "height": 300}
# Configure solver
config = {
"strategy": "nfp", # Options: blf, nfp, ga, brkga, sa, gdrr, alns
"spacing": 2.0, # Gap between parts
"time_limit_ms": 30000 # 30 second timeout
}
# Solve
result = u_nesting.solve_2d(geometries, boundary, config)
print(f"Utilization: {result['utilization']:.1%}")
print(f"Placed: {len(result['placements'])} items")
for p in result['placements']:
print(f" {p['geometry_id']}[{p['instance']}]: ({p['position'][0]:.1f}, {p['position'][1]:.1f})")
3D Bin Packing
import u_nesting
# Define boxes to pack
geometries = [
{
"id": "small",
"dimensions": [20, 20, 20],
"quantity": 10
},
{
"id": "large",
"dimensions": [40, 30, 25],
"quantity": 5,
"mass": 2.5 # Optional weight
}
]
# Define container
boundary = {
"dimensions": [200, 150, 100],
"max_mass": 50.0, # Optional mass limit
"gravity": True, # Stack from bottom
"stability": True # Require stable placement
}
# Configure solver
config = {
"strategy": "ep", # Extreme Point heuristic
"time_limit_ms": 10000
}
# Solve
result = u_nesting.solve_3d(geometries, boundary, config)
print(f"Utilization: {result['utilization']:.1%}")
print(f"Containers used: {result['boundaries_used']}")
API Reference
solve_2d(geometries, boundary, config=None) -> dict
Solve a 2D nesting problem.
Parameters:
Nothing a request asks for is raised as an exception: every refusal comes back in the result dict, with
successFalse, a stablecodeand the values behind it indetails-- the same codes and paths the C and WebAssembly bindings give. Branch oncode, not on theerrortext.
- Keys are read strictly: an unrecognized key in
geometries,boundaryorconfig, or a value of the wrong type -- a count given as a fraction (2.5) included -- ismalformed_inputwithdetailsparameterthe path of the field (config.stratgy,geometries[0].quantity). A typo can never silently fall back to a default.- A number that is not finite (
float("nan"),math.inf) isvalue_not_finite:parameteris the path of the value (config.spacing), or of the list it is in with its position asindex(geometries[0].polygon[2],index1 for the y coordinate), andidnames the geometry. A value JSON cannot hold (a set, a non-string key) ismalformed_input.- A value outside its range (a negative one included) is
parameter_out_of_range: a geometry'squantitybelow 1 hasparametergeometries[i].quantityand the geometry'sid; aconfigsetting (population_sizebelow 2,seedbelow 0, ...) hasparameterconfig.population_size; a boundary side, dimension ormax_massof 0 or below names it (boundary.width,boundary.dimensions[i],boundary.max_mass, withexclusive_min0).- An empty
geometrieslist isempty_input(parametergeometries); a strategy the dimension does not offer isinvalid_option(parameterconfig.strategy); a boundary given as both a rectangle and a polygon, or half of a rectangle, isinvalid_boundary.
geometries: List of geometry definitionsid(str): Unique identifierpolygon(list): Vertices as [[x, y], ...]quantity(int): Number of copies (default: 1)rotations(list): Allowed rotation angles in degrees — at least one; omit it for a fixed orientation (an empty list is refused)allow_flip(bool): Allow horizontal flipholes(list): Interior holes as list of polygons
boundary: Sheet definitionwidth,height(float): Rectangle dimensions, ORpolygon(list): Custom boundary shape
config: Solver configuration (optional)strategy(str): "blf", "nfp", "ga", "brkga", "sa", "gdrr", "alns"spacing(float): Gap between geometriesmargin(float): Gap from boundarytime_limit_ms(int): Timeout in milliseconds, at least 0 (0 = no limit; default 30000)target_utilization(float):ga,brkga,sastop once every part is placed at this utilization or more, in [0, 1]population_size(int): GA/BRKGA population, at least 2, run as given. Omitted, each search uses the size it is tuned for: 30 in 2Dmax_generations(int): GA/BRKGA generation budget, at least 0, run as given. Omitted, 50 in 2D- A setting only some strategies read (
target_utilization,population_size,max_generations,crossover_rate,mutation_rate,seed) is refused asinvalid_optionunder a strategy that does not read it, not ignored crossover_rate/mutation_rate(float): GA crossover and mutation probabilities, in [0, 1] (default 0.85 / 0.05)seed(int): RNG seed ofga,brkga,sa,gdrr,alnsandhybridexact. The same problem with the same seed returns the same layout when the run ends on its own budget rather than on the clock (a 2D search keeps a time budget of its own, 10-15 s, whentime_limit_msis 0)multi_sheet(bool): Distribute overflow across multiple sheets (default:False). WhenTrue, parts that do not fit on one sheet spill onto extra sheets instead of becoming unplaced;boundaries_usedreports the sheet count and each placement'sboundary_indexselects its sheet with sheet-local coordinates.
Returns: Dictionary with:
success(bool): Whether solve succeededplacements(list): Placement results (instance-level)utilization(float): Area utilization ratioboundaries_used(int): Number of sheets used (>1 only whenmulti_sheet=True)total_requested(int): Σ of every geometry's quantity (instance-level total). Unplaced instance count =total_requested - len(placements)unplaced(list): Deduplicated IDs of items that couldn't be placed (not per-instance, solen(unplaced)under-reports the failed-instance count)unplaced_count(int): Instance-level count of unplaced instancesall_placed(bool): Whether every requested instance was placed -- prefer it tosuccess, which only says the solve was not refusedused_bounding_box(list):[width, height]of the placed parts' footprint (2D;[0, 0]for 3D)used_utilization(float): Part area overused_bounding_box(2D;0for 3D)computation_time_ms(int): Solve timeerror(str | None): Readable text when the request was refusedcode(str | None): Stable reason for that refusal (invalid_geometry,duplicate_id,parameter_out_of_range, ...)details(dict | None): The values behindcode(id,first,index,parameter,got, ...). A range gives each bounded side asmin/maxwhen the bound itself is accepted, orexclusive_min/exclusive_maxwhen it is not
solve_3d(geometries, boundary, config=None) -> dict
Solve a 3D bin packing problem.
Parameters:
geometries: List of box definitionsid(str): Unique identifierdimensions(list): [width, depth, height]quantity(int): Number of copiesmass(float): Weight, at least 0 (optional)orientation(str):"any","upright"or"fixed"(optional)
boundary: Container definitiondimensions(list): [width, depth, height], each above 0max_mass(float): Weight limit, above 0 (optional)gravity(bool): Enable gravity constraintstability(bool): Enable stability constraint
config: Same as solve_2d butmulti_sheet, which is refused (invalid_option,config.multi_sheet: one container, no sheets), and:strategy: "blf", "ep", "ga", "brkga", "sa"population_size/max_generationsomitted: 100 / 500 forga, 50 / 100 forbrkga
Returns: Same structure as solve_2d
Strategy Selection Guide
| Strategy | Speed | Quality | Best For |
|---|---|---|---|
blf |
Fast | Good | Large instances, quick results |
nfp |
Medium | Better | 2D with complex shapes |
ep |
Fast | Good | 3D bin packing |
ga |
Slow | Best | Small instances, max quality |
brkga |
Slow | Best | Complex constraints |
sa |
Medium | Better | Balanced speed/quality |
gdrr |
Medium | Better | 2D, ruin-and-recreate on dense layouts |
alns |
Medium | Better | 2D, adaptive neighborhood search |
Requirements
- Python 3.8+
- No additional dependencies
License
MIT License - see LICENSE for details.
Links
Metadata
Release files for u-nesting 0.19.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| u_nesting-0.19.0-cp38-abi3-win_amd64.whl | CPython 3.8 | abi3 | Windows x86-64 | Details |
| u_nesting-0.19.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.8 | abi3 | Linux glibc 2.17+ x86-64 | Details |
| u_nesting-0.19.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | CPython 3.8 | abi3 | Linux glibc 2.17+ ARM64 | Details |
| u_nesting-0.19.0-cp38-abi3-macosx_11_0_arm64.whl | CPython 3.8 | abi3 | macOS 11.0+ ARM64 | Details |
| u_nesting-0.19.0-cp38-abi3-macosx_10_12_x86_64.whl | CPython 3.8 | abi3 | macOS 10.12+ x86-64 | Details |
Total release size: 4.2 MB
Release files / u_nesting-0.19.0-cp38-abi3-win_amd64.whl
| Download URL | u_nesting-0.19.0-cp38-abi3-win_amd64.whl |
|---|---|
| Size | 830.4 kB |
| Tags | CPython 3.8 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
b5c24449344644554c7313b91a3a19110f45e0b17475e8be087a5b38f9b4005e
|
|
BLAKE2b-256 checksum How to use checksums |
5333ed0d147ecffd1934bd7cac1b80ede66b91a797de75fe9c5af8ead02b71b9
|
| 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 Oct 13, 2026.
Transparency logRelease files / u_nesting-0.19.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | u_nesting-0.19.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 883.8 kB |
| Tags | CPython 3.8 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
3c906b5abbef6fc866adcb40862c3ea2852be6d6cd959d28fc0025e39791825e
|
|
BLAKE2b-256 checksum How to use checksums |
eeb9fc79ec70d017e044c2fbd6d8350f776f63756cd8d591aba9a26d5ee09874
|
| 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 Oct 13, 2026.
Transparency logRelease files / u_nesting-0.19.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | u_nesting-0.19.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 811.7 kB |
| Tags | CPython 3.8 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
591b90beb9bc684825bb425bc408e2fc917d90b877c16e6c9d12afe669fa9408
|
|
BLAKE2b-256 checksum How to use checksums |
5dc46bdc8b988f5f92154fef25203bc3bcc4cd59815affb52064953a0438fdb3
|
| 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 Oct 13, 2026.
Transparency logRelease files / u_nesting-0.19.0-cp38-abi3-macosx_11_0_arm64.whl
| Download URL | u_nesting-0.19.0-cp38-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 786.1 kB |
| Tags | CPython 3.8 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
93ddf65352b17ddc9beea2832e89a806d5ed5fa8c020e37c5530f0adb8ba8aad
|
|
BLAKE2b-256 checksum How to use checksums |
c840eb0c5a736bc90ce795e790498c6c98e06b6f088ee7f227d2b482029ad0c9
|
| 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 Oct 13, 2026.
Transparency logRelease files / u_nesting-0.19.0-cp38-abi3-macosx_10_12_x86_64.whl
| Download URL | u_nesting-0.19.0-cp38-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 857.1 kB |
| Tags | CPython 3.8 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
9634e60f11994e227510e911f0c780a762f264fd6221fde2458816836a808161
|
|
BLAKE2b-256 checksum How to use checksums |
51ada03f0f89a23813e6978fd7e7c415db8bf362edd003b9ba9998aeeb863541
|
| 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 Oct 13, 2026.
Transparency log