Blog · Nesting · 1 Oct 2026 · 3 min read

How to nest cut parts on steel sheets with Python, from DXF profiles

Our nesting for 12000 x 2500 plates is a short Python program, not a commercial package. Profiles become polygons, polygons become rasters, and an FFT finds every free position at once. The method, the yield it gave against bounding boxes, and the bugs that showed 107% yield or overlapping parts with no error.

Short answer: turn each closed profile into a polygon (Shapely), rasterise it at 5 mm, dilate it by half the gap, and for each orientation compute the overlap with the sheet at every position in one shot with an FFT convolution (SciPy). Among the free positions, take the one that extends the sheet least. Use float64. On 10 mm plate it used 20.9% less sheet length than nesting bounding boxes.

Where the profiles come from

From the delivery booklet cells, not from the drawings: there the geometry is already 1:1 with one part per cell. Taking profiles from the drawings got 28 of 115 profiles wrong, 6 of them by picking the isometric view.

Marking lines (engraved codes) are drawn on the nest but kept out of the collision.

The method

  1. Each closed contour becomes a polygon (shapely.ops.polygonize, unary_union for parts with several faces).
  2. The polygon is rasterised into a boolean mask and dilated by half the gap (scipy.ndimage.binary_dilation).
  3. Orientations: 0°, 90°, 180°, 270°, plus the angle of the minimum bounding rectangle.
  4. For each orientation, fftconvolve gives the overlap at every position at once.
  5. Free positions are those with overlap below 0.5. Choose the one that extends the used sheet length least; on a tie, the lowest.
lim = max(mw, min(W, frontier + mw + 2))
conv = fftconvolve(occupied[:, :lim], m[::-1, ::-1].astype(np.float64), mode="valid")
free = np.argwhere(conv < 0.5)
if free.size == 0:
    continue
# least extension of the sheet, then lowest: plain bottom-left on a
# 12000 x 2500 sheet fills by rows and leaves a ragged frontier
order = np.lexsort((free[:, 0], free[:, 1] + mw))
j, i = free[order[0]]
cost = (int(i) + mw, int(j))
if best is None or cost < best[0]:
    best = (cost, angle, m, int(i), int(j))

Sheet 12000 × 2500, 10 mm border not cuttable, 10 mm minimum gap between parts. The usable height is 2470: 2500 minus 10 border per side, minus 5 dilation per side.

Yield, measured

10 mm plate on 12000 × 2500 sheets, total sheet length used:

Method Length Saving
Bounding boxes 33.9 m —
Minimum rectangles 31.3 m 7.8%
Paired twins 29.6 m 12.7%
True shape, 5 mm raster 26.8 m 20.9% (197 s)

Count metres, not sheets: the last sheet is almost always half empty. If sheets are already ordered, the leftover is an offcut back to stock.

Bugs that looked like results

Bug Symptom Fix
FFT in float32 Yield 107% float64
One sample per raster row Parts overlap 3 samples per row
binary_dilation on an unpadded array Dilation cut at the edge Pad first
Full gap as dilation radius True shape lost to bounding boxes Half the gap per part
Bottom-left fill Ragged frontier on a long sheet Least extension of the sheet
Cap on parts per sheet raised 339 parts on one sheet at 65% yield: overlapping, no error The cap is a bug guard, not a tuning knob
Engraved text rotated twice ezdxf 1.4.4 transform() already rotates. The PNG cannot show it Check: output rotation = source + part angle
Micro-gap in a contour (0.1 mm) Falls back to bounding box Close small gaps before polygonising

Acceptance checks

Do and don't

Do

Don't

Questions people ask

Why not a commercial nesting program? Use one if you have it. This shows an AI agent can build a working nest for plate when the job needs it, and the checks are the same either way.

Does the gap include the kerf? Our code has a 10 mm minimum gap and no separate kerf value. Set the gap your cutting process needs.