Skip to content

3MF/OrcaSlicer output, placement algorithms (-A), consolidation (-C) - #38

Open
weasel0x00 wants to merge 9 commits into
Rhoban:masterfrom
weasel0x00:feat/output-and-algorithms
Open

weasel0x00 wants to merge 9 commits into
Rhoban:masterfrom
weasel0x00:feat/output-and-algorithms

Conversation

@weasel0x00

Copy link
Copy Markdown

First in a series that splits a large set of CLI/placement improvements into smaller, reviewable PRs (rather than one big request). Each subsequent PR is stacked on this one and will be opened/rebased as this merges.

This PR (9 commits):

  • 3MF output with OrcaSlicer/BambuStudio multi-plate project layout, and weld-vertices fix for manifold meshes
  • Bit-packed Bitmap collision (64-bit mask overlap) for speed
  • Plate-size fit-search geometry caching
  • New placement algorithms under -A: skyline (bottom-left drop), contact (max-contact), pruned (hole-aware pruned brute force)
  • -C consolidation pass (drop a plate by repacking; only kept if it strictly reduces plate count)
  • Also includes a standalone fix: initialize Request::sortMode (was uninitialized)

Follow-ups in the series: CLI ergonomics + fit search (-i) + tall-part centering (-T); simulated-annealing placement (-A anneal) + volume balancing (-B) + shrink fit (-z); and a correctness/security hardening pass.

weasel0x00 and others added 9 commits June 12, 2026 14:23
…g generator

- CLI 3MF export (-m): emits a single OrcaSlicer/BambuStudio multi-plate
  project; each placed part is its own object named after the original part
  file, and each plate is restored as a separate plate in the slicer
  (model_settings.config + project_settings.config + Application tag).
- Fit search (-i ideal, -g step, -N target): grow the plate size from an
  ideal up to the bed size (-W/-H), adding a plate only when parts won't fit,
  always preferring the fewest plates then the smallest size.
- Dependency-free ZIP writer (Zip.{h,cpp}) and 3MF writer (ThreeMF.{h,cpp});
  no new build dependencies (CLI still needs only pthreads).
- gen_plater_conf.py: generate a plater.conf from a directory of STL parts,
  with [a]-prefix "accent parts" and _xN quantity rules.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the byte-at-a-time Bitmap::overlaps scan with a lazily-built,
mutable bit-packed occupancy mask (64 pixels/word) and a shifted-AND
collision test. The mask is invalidated on setPoint and rebuilt on demand,
so the plate mask is rebuilt once per placement and reused across the whole
position sweep; part masks are built once.

Keeps the original scan as overlapsScalar for validation/benchmarking, and
adds bench_overlap (microbenchmark + correctness check).

Microbenchmark (300mm@0.5mm plate, 80mm disk part):
  validation: 50000 checks, 0 mismatches
  empty plate (miss):   71.7x faster
  filled plate (mixed): 45.1x faster

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…RIFY_OVERLAP)

Refactor overlaps() to a single return and add an optional assertion that
cross-checks the bit-packed result against overlapsScalar on every call.
Verified: a full -S solve on real geometry runs clean (no mismatch).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e size

Split Part::load (size-independent geometry: disk read, pixelize, rotate,
trim) from Part::applyPlateSize (cheap per-plate-size rotation filtering).
allBmp owns all rotation bitmaps; bmp is a per-size view. processFit now
loads parts once and binary-searches the smallest feasible plate size
instead of reloading and linearly scanning every step.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Maintain a per-column skyline on each rectangular plate (Plate::colHeight,
updated in place()). placePartSkyline drops each part onto the skyline: the
resting row is computed in O(part width) from the part's per-column bottom
profile, and is overlap-free by construction (everything above the skyline
is empty), so no collision tests are needed in the search at all.

Opt-in via -k; falls back to the brute-force grid for circular plates.

A/B vs brute force:
  plate count: equal on jobs A/B, fewer on job C (2 vs 3) -- never worse
  speed: ~1.5x (-j 0.5) to ~1.9x (-j 0.3) faster, grows with precision

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
sortMode was never set in the constructor, so a run without -S read
indeterminate stack memory and could nondeterministically take the
multiple-sort (time-seeded shuffle) path. Default it to REQUEST_SINGLE_SORT
so single-sort runs are reproducible.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add PLACER_SCORE_CONTACT: rank skyline candidates by how much of the part's
outline touches walls/placed parts (denser packing), gravity as tie-break.
solve() adds contact-scored placers alongside the gravity ones when -K is set,
so the solver keeps whichever packs better (never a regression).

Note: like skyline, this drops onto the top profile and cannot fill interior
holes/cavities -- best for hole-free parts. Opt-in via -K (implies -k).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Same algorithm and result as the default brute force (full collision test,
so holes/cavities are filled identically), but prunes the position search
using score monotonicity: scan y upward per column and stop at the first
feasible cell (the column's lowest-score spot), and skip columns once even
their y=0 score can't beat the current best. Quality-identical to brute
(verified byte-identical output, including hole nesting), ~1.3x faster on
top of the bit-packed collision. Opt-in via -b; works for any plate shape.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Flags:
- Replace -k/-K/-b with a single -A <algorithm>: brute (default) | pruned |
  skyline | contact. Absent -A keeps the original brute force.
- Add -C: post-pass that tries to drop a plate by re-packing all parts into
  one fewer plate (with rearrangement, exploring largest/smallest/shuffled
  orderings), accepted only when it strictly reduces the plate count.

Supporting changes:
- Placer gains setMaxPlates(); place() returns NULL if parts don't fit within
  the cap (used by the consolidation repack). Uncapped behavior unchanged.
- Fix uninitialized Placer::myThread (crashed when a Placer was destroyed
  without ever starting a thread, e.g. the consolidation placers).

Validated: 2/50 random suboptimal jobs dropped a plate via -C (never worse);
all -A values run with/without -C and compose with -m and the fit search.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant