The saved calculation 3b1e5ea9-1f25-4dff-808a-58efaae7111f contains
16 circles Ø340 mm and 16 circles Ø223 mm. Both stored contours have 128
vertices. The sheet is 3050×2030 mm with a 10 mm gap and 10 mm edge margin.
No calculation or source file was modified for the investigation.
| Workload | Before | After, same server |
|---|---|---|
| Saved 32-circle case | Persisted run failed at 17/32 pieces after 60.17 s; a 20 s cProfile run reached 11/32 | 32/32 on one sheet; 0.98 s solver, 1.20 s subprocess plus exact validation |
| Saved Acceptance 300, 600×387 mm rectangles | Existing accepted result: 12 sheets; old runtime not recorded | 300/300 on 12 sheets; 2.20 s solver, 2.54 s subprocess plus exact validation |
The old solver generated the Cartesian product of X and Y coordinates sampled
from every placed contour. Candidate counts grew rapidly as pieces accumulated.
Each candidate reparsed GeoJSON to a new Shapely polygon, rotated and
normalized it again, then often computed a polygon intersection before a
distance check. In the 20-second baseline profile, it called
placed_shape/GeoJSON conversion 20,624 times and polygon intersection 24,008
times; 11.7 million Python calls were recorded. The 128-vertex circles made
that repeated work expensive, but the candidate explosion was the root cause.
The solver now caches each part’s exact 0°/90° polygons once and keeps
incremental two-dimensional anchors rather than an X×Y grid. Cached bounding
boxes select possible colliders before any exact polygon predicate. For larger
sheets of placements, one STRtree is built per part attempt and reused across
its candidates. Exact contour distance/intersection remains authoritative;
the persisted layout is validated again from full polygons. Newly generated
circles use an adaptive polygon count, with 64 vertices for Ø340 mm and about
0.25 mm maximum chord deviation. Older saved geometry remains unchanged.
The worker includes timing fields for geometry preparation, candidate
generation, collision checks, polygon distance, polygon intersection,
transformations, each piece, and total solver time. The backend logs
NESTING_TIMINGS with the slowest five pieces and exact layout-validation
time. The progress callback still advances only when a real piece is
processed. MAX_NEST_SECONDS=180 is a configurable killable-worker safety
ceiling, not an expected runtime.
Run pytest -q backend/tests/test_nesting_performance.py to check 32 old-style
circles, 300 rectangles, mixed primitives, an interlocking L-shaped contour,
and worker timing output. Full backend and frontend build checks remain
required before deployment. After updating backend code on a systemd host,
run systemctl restart signage-estimator-backend, verify
curl http://127.0.0.1:8000/api/health, and inspect
journalctl -u signage-estimator-backend -n 100 for timing records. The
current sandbox cannot perform that host restart.