[Media] I wrote a 2D guillotine cutting stock optimizer in Rust for a small furniture shop

205 points nick-linker 23 comments
[Media] I wrote a 2D guillotine cutting stock optimizer in Rust for a small furniture shop

Hey guys, I'm a developer with a math background who previously specialized in combinatorial optimization, and I wanted to share my project I built recently for a small furniture shop: a 2D guillotine cutting stock optimizer.

The specific problem - planning how to cut sheet material (chipboard/MDF panels) into the pieces required for each order, minimizing waste while keeping the cutting as manufacturable as possibble. The cuts must be guillotine cuts, an actual Sliding Table Panel Saw Machine can only make such cuts.

The obvious first move was to look at existing cutting-optimization software, but none of it was a good fit - it was either too closed and rigid to adapt, or expensive enough that it was hard to justify for a shop this size. So I ended up writing it by myself. This project was open-source from the start simply because the real advantage in this field is furniture makers' own craft anyway :-) So by open-sourcing it I'm giving something back to the Rust community for such a great language and ecosystem.

Here are some things that might be interesting aside from the main task:

  • Genetic (evolution) algorithm, generic over the genome representation via a GaDecoder trait, so the GA is written once and shared between two different encodings. There are two decoders: SLAS - one gene per physical piece, and GLAS - one gene per piece type, GLAS scales better and gives more manufacturable cuttings.
  • Objective function is designed to find balanced solutions - ones with good fill rate, but also manufacturable enough for real-world material handling.
  • Piece rotations, Kerf and Margin are supported. The kerf is saw blade width, the margin is a trim strip along the sheet edges.
  • An exact solver for the single-sheet case: a DP over guillotine-cut subsets (GLF from the Andrianova, Mukhtarova and Fazylov paper, the reference in README) that finds the optimal layout for one sheet of given width.
  • A greedy portfolio heuristic (from Jukka Jylanki paper) for instant results when you don't need to wait for the GA to converge.
  • Progress feedback and cancellation: the solver runs in a background thread and streams progress over a channel, so both the CLI and the web UI (Axum + SSE) can show live improvement instead of blocking. This was an interesting task to unify sync and async event interface and I hope I have found a good solution for it.
  • Deterministic even for the multithreaded version: each island (GA thread) gets its own PRNG seeded from its (user-supplied) seed value, and migration between islands happens at a synchronization barrier, so there's no "first thread to finish wins" nondeterminism. Same seeds + same config always reproduce the exact same result, which matters a lot when you're debugging, testing or comparing two parameter sets.
  • CLI JSON interface to plug into a real shop's existing tooling, it gets called from an Excel workbook (VBA) and can export cut plans to AutoCAD.

Why Rust? The GA loop runs millions of genome evaluations per run, so the performance matters a lot. SmallVec cuts heap allocations noticeably, especially in decoders and free-rect list operations. Even with these optimizations, GA can never have enough speed - and implementing it in a high-level language with a fat runtime would be no doubt a showstopper. The ecosystem was also a great help: serde made the JSON boundary for the Excel/VBA integration easy, and chumsky kept the grammar for compact problem format readable instead of fiddling with regexes. axum with tokio made the serve mode easy to implement.

The Rust platform made it possible to keep the whole algorithm development, testing, hypothesis verification etc. under Linux. Only the integration part with Excel and AutoCAD was done under Windows.

I consider the project pretty complete, although a few non-critical things could still be improved. For example, the exact GLF solver is single-threaded, so it has a fairly low ceiling for the size of the problem instance. Also, it only proves optimality for a single sheet — multi-sheet placement is GA/heuristic-only. Still, the GA-based approach is already good enough for daily use.

Repo, with a demo GIF of the GA converging on a layout:

https://github.com/nlinker/guillotine-cutting-2d

What was vibe coded: demos only, the prompt was "Here's the Rust code, build an interactive visualization for it", the other parts were either hand-written, or edited after AI generation and my thorough review.

Happy to answer questions about the guillotine-cut DP, the GA design, or anything else. Feedback ("why didn't you just use X crate/approach?", hehe) is very welcome.