feat: Snuggle — 3D voxel-aware genetic bed nesting#1
Closed
thereprocase wants to merge 92 commits into
Closed
Conversation
- deps/wxWidgets/wxWidgets.cmake: Add GIT_TAG v3.3.2 to track the correct branch; remove -DwxUSE_UNICODE=ON (unicode-only in 3.3, option removed) - src/CMakeLists.txt: Bump find_package minimum version from 3.0/3.1 to 3.3; remove SLIC3R_WX_STABLE conditional (3.0 no longer supported) - CMakeLists.txt: Remove SLIC3R_WX_STABLE option definition - scripts/flatpak/com.orcaslicer.OrcaSlicer.yml: Update wxWidgets source URL to v3.3.2 branch archive; remove sha256 (placeholder TODO); remove -DwxUSE_UNICODE=ON
Since we now target wxWidgets 3.3, the custom DPI change event workaround (DpiChangedEvent, EVT_DPI_CHANGED_SLICER, register_win32_dpi_event) is dead code. wxWidgets 3.1.3+ provides native wxEVT_DPI_CHANGED / wxDPIChangedEvent which is already wired up in the "true" branch of the version guards. Removes: - DpiChangedEvent struct and EVT_DPI_CHANGED_SLICER declaration/definition - register_win32_dpi_event() function and its call site - All associated #if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) guards
Remove scale_win_font() and scale_controls_fonts() functions along with the #if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) guard in rescale(). Since wx >= 3.1.3 is now guaranteed, this code could never execute and the functions had no other callers.
wxWidgets 3.1+ accepts const argv arrays (const wchar_t* const* and const char* const*) in wxExecute(), making the const_casts unnecessary. Remove all 14 const_cast<char**>/const_cast<wchar_t**> wrappers around wxExecute calls and their associated FIXME comments across GUI.cpp, NotificationManager.cpp, and Downloader.cpp.
wxWidgets 3.3 requires macOS 10.11+, making the 10.9.5-specific crash workaround in OpenGLManager impossible to trigger. Remove: - OSInfo struct and s_os_info static member from the header - OS version recording in init_glcontext() - Conditional wxGLContext deletion in the destructor (now always deletes) - Unused #include <wx/platinfo.h> The MacDarkMode.hpp include is retained as mac_max_scaling_factor() is still used by GLInfo::get_max_tex_size().
Since we now target wxWidgets 3.3, all wxCHECK_VERSION(3,1,N) checks are always true. Remove the guards keeping only the true branches: - I18N.hpp: Remove version guard around _wxGetTranslation_ctx macro - ExtraRenderers.hpp, GUI_App.hpp: Simplify SUPPORTS_MARKUP to check only wxUSE_MARKUP (version check always true) - ConfigWizard.cpp: Remove manual wxArrayInt comparison fallback - SendSystemInfoDialog.cpp: Simplify display scaling guard to _WIN32 only - GUI_Utils.cpp: Remove IsDark() fallback using luma approximation - wxinit.h: Remove legacy wxEVT_BUTTON and wxEVT_HTML_LINK_CLICKED compat macros (these event names exist natively in wx 3.3)
wxWidgets 3.2+ asserts on invalid sizer flag combinations where wxEXPAND (which fills the entire space in the secondary direction) is combined with wxALIGN_* flags (which are meaningless when expanding). Remove the conflicting wxALIGN_* flags from all 112 occurrences across 21 files, keeping wxEXPAND and any non-conflicting flags intact.
wxTRANSPARENT_WINDOW is removed in wxWidgets 3.3. Remove all 3 occurrences in MainFrame.cpp: - ResizeEdgePanel constructor: already uses wxBG_STYLE_TRANSPARENT via SetBackgroundStyle(), so the flag was redundant - slice_panel and print_panel: drop the style parameter entirely (defaults to 0)
In wxWidgets 3.3, wxWindow::Raise() no longer implies Show(). Add explicit Show() before Raise() in two event handlers that activate the main frame from another instance (load model, start download), and swap the Show/Raise order in bring_instance_forward() so Show() precedes Raise().
…s.h includes - Wrap GetToolTipCtrl() call in GUI_App.cpp with #if wxVERSION_NUMBER < 3300 guard, as this API may not be accessible in wxWidgets 3.3. The dark tooltip theming is cosmetic and non-critical. - Add explicit #include <wx/utils.h> to 7 source files that use functions from that header (wxGetMousePosition, wxLaunchDefaultBrowser, wxGetDisplaySize, wxBell) but relied on transitive includes. This preempts breakage from wxWidgets 3.3 reducing transitive includes. Files with wx/utils.h added: BBLTopbar.cpp, CreatePresetsDialog.cpp, CameraPopup.cpp, GLCanvas3D.cpp, GCodeViewer.cpp, GUI_ObjectList.cpp, FilamentMapPanel.cpp. Skipped BindDialog.cpp and FilamentPickerDialog.cpp as they already include wx/wx.h which provides wx/utils.h transitively. Part of wxWidgets 3.1.5 -> 3.3.2 upgrade.
In wxWidgets 3.3, wxBitmapComboBoxBase::OnAddBitmap changed its parameter from const wxBitmap& to const wxBitmapBundle&, and m_bitmaps was replaced by m_bitmapbundles. Update OnAddBitmap signature and OnDrawItem to use wxBitmapBundle, extracting wxBitmap via GetBitmap(GetDefaultSize()) where needed.
In wx 3.3 with wxUSE_STD_CONTAINERS=ON, wxString is backed by
std::wstring, so direct concatenation of const char[] with
std::wstring or wxUniCharRef fails. Fix by splitting compound
concatenations into separate += operations on wxString, or by
wrapping the left operand in wxString() to use its operator+.
Files fixed:
- AuxiliaryDataViewModel.cpp: split "\\" + wxString/wstring chains
- AboutDialog.cpp: split std::string("\n") + wxUniCharRef
- Auxiliary.cpp: wrap dir.wstring() in wxString(), split "/" + wstring
wxWidgets 3.3 handles OpenGL discovery natively via imported targets (OpenGL::GL, OpenGL::OpenGL). The override was corrupting wx-config output with malformed "-framework OpenGL" entries, causing FindwxWidgets.cmake to fail.
wxComboPopup no longer inherits from wxObject in wx 3.3, so wxDynamicCast (which casts through wxObject) fails. Use dynamic_cast directly instead.
Same pattern as earlier fixes: const char[] + std::wstring fails in wx 3.3 where wxUSE_STD_CONTAINERS=ON. Wrap with wxString().
…ing ctor - PhysicalPrinterDialog: disambiguate set_values() call with explicit std::vector<std::string> (wxArrayString now also matches initializer list) - Preferences: use ToStdString() instead of mb_str() for std::string comparison - Plater: use wxString::FromUTF8() for wxArrayString constructor argument
- Plater: use Add() instead of wxArrayString(size_t, wxString) ctor - Search: change sep from std::wstring to wxString for concatenation - SendMultiMachinePage: replace wxList::Node* with compatibility_iterator (Node type removed in wx 3.3 with wxUSE_STD_CONTAINERS=ON)
wxWidgets 3.3 bundles its own NanoSVG in bmpsvg.cpp, conflicting with OrcaSlicer's bundled copy which includes the nsvgRasterizeXY extension. Set wxUSE_NANOSVG=OFF in deps cmake to use OrcaSlicer's version only.
wxWidgets 3.3 cmake install doesn't include private headers. OrcaSlicer uses some private headers for accessibility support. Add a post-install step to copy the private headers directory.
On Linux/GTK, CheckBox, RadioBox, and SwitchButton set their size to exactly the bitmap size (18x18 or 16x16), but GTK's internal CSS padding requires additional space, resulting in negative content width warnings. Use GetBestSize() on GTK to account for theme padding.
…TEXT (context)' failed
Merged evolved algorithm from standalone bench testing into the GPU branch's nester, preserving all branch-specific features (rotation cache, GPU evaluator interface, post-GA compaction jiggle). New from evolved GA: - 4 seed strategies: center, line, grid, bottom-left - Order-aware crossover: fitter parent bias (70/30 inheritance) - Push-apart mutation (13%): moves parts away from nearest neighbor - Adaptive mutation scale: 1x baseline, up to 3x when stagnant - Stagnation detection: early exit after 45 gens without improvement - Aspect ratio fitness term: penalizes elongated bounding boxes - Proximity fitness term: rewards close-but-not-colliding pairs Preserved from branch: - Rotation cache (360-bin quantized) - GPU CollisionEvaluator batch interface - Post-GA compaction jiggle toward centroid - compute_origin_positions for delta writeback - bed_margin_mm, w_clustering config fields Bench results: 30 parts in 4.9s, 38.9% bed usage, zero collisions. Independently validated with exact Moller-Trumbore triangle intersection. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…fications UI improvements (per UX spec): - Renamed to "Snuggle 3D Arrangement" with teal header - Rich tooltips on all Snuggle controls explaining what each does - "Standard rotation disabled" note when Snuggle is active - Sequential printing warning in footer when detected - Footer shows "voxel collision detection" instead of GPU claim ArrangeJob improvements: - Overflow fallback: unarranged items go to default arranger with Snuggle-placed items as fixed obstacles (multi-plate support) - Snuggle-specific completion messages with part count: "Snuggle complete — 5 parts arranged" "Snuggle placed 3 of 5 parts. Some couldn't fit." Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Sauron: coordinates clean, format strings correct, settings persist OK. Frodo fixes: - Sequential print warning: orange text instead of grey (was invisible) - Rotation note: "Snuggle controls rotation" instead of "disabled" (instructional, not cautionary) - Visual hierarchy: warnings use orange, info uses grey Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Newly implemented (was MISSING):
- Single-part centering: 1 part = center on bed instantly, no GA
- Fallback for 50+ parts: bail to default arranger with warning
- Sequential printing hard block: bail to default (toolhead safety)
- Oversized parts flagged: pre-flight check marks off-plate immediately
- Voxel fail fallback: failed parts marked off-plate, overflow handles them
- Bed utilization % in completion toast: "Bed usage: 47%"
- Detailed overflow message: "placed X of Y, some overflow to next plate"
Upgraded from PARTIAL to DONE:
- Overflow: items marked off-plate trigger ArrangeJob fallback
- Seq print: UI warning + actual hard block in SnuggleArrange
Already confirmed DONE by Sauron (17 of 30):
- Zero collisions, bed containment, cancel, non-blocking UI,
distinct button, rotation locked, spacing default, remember choice,
empty skipped, other plates safe, bed full overflow, arrange selected,
detailed overflow msg, locked obstacles
Deferred (complex GUI work, not blocking):
- Animated part transition (requires render pipeline changes)
- Wipe tower lock icon (requires 3D scene overlay)
- Split dropdown button (requires toolbar refactor)
- Cached redo (requires undo system extension)
Undo/redo: confirmed DONE via Orca's existing take_snapshot("Arrange")
in Plater.cpp — atomic undo is free.
Final scorecard: 26/30 DONE, 0 PARTIAL, 4 deferred (GUI polish)
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…i-plate params New user-facing controls: - Rotation dropdown: Locked / 90 / 45 / 15 / 5 / 1 degree snaps relative to each part's starting orientation - Quality slider + text input: population = quality x 64, tooltip explains the mapping - Collapsible "Debug / Advanced" section: - Timeout (5-300s, default 40s) - Max parts per plate (2-500, default 200) - Multi-plate overflow toggle Backend changes: - ArrangeParams: snuggle_max_parts, snuggle_timeout_s, snuggle_rotation_step, snuggle_multi_plate - NesterConfig: rotation_step_rad for quantized rotation - snap_rotation(): quantizes angle to nearest step relative to each part's initial_zrot (their starting position) - Max parts limit now configurable (was hardcoded 50) - Timeout now configurable (was derived from quality only) All settings persist in app_config, load on startup, reset on Reset. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Bug 1 (WRONG): Reset now persists max_parts, timeout, rotation_step, multi_plate to app config. Was only resetting in-memory values. Bug 2 (COSMETIC): Quality clamped to [1,10] on config load. Prevents population_size=0 from hand-edited config files. Bug 3 (WRONG): Invalid rotation_step from config now snaps to nearest valid option (0,1,5,15,45,90). Prevents UI showing "Locked" while nester actually uses a non-standard step value. Bug 4 (WRONG): snuggle_lock_rotation is now derived FROM rotation_step on load (step=0 -> locked=true). Legacy "snuggle_lock_rotation" config key no longer loaded independently. Dropdown is source of truth. Bug 5 (WRONG): Single-part centering preserves user's Z rotation instead of zeroing it. Was destructively resetting orientation. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
B6 (UB/CRASH): randf(lo, hi) with lo > hi when part > half bed. Fix: clamp hi >= lo + 0.1 before calling randf. B2 (OOM): Rotation cache built 360 copies per part even when locked. 200 parts = 72K grids = potential OOM. Fix: When lock_rotation=true, build only 1 cache entry per part. B5 (WRONG): Silent fallback (too many parts, seq print) left items with stale bed_idx. Overflow detection skipped standard arranger. Fix: Set all items bed_idx=-1 before returning from fallback. B4 (WRONG): compact_toward_center micro-rotations violated rotation step constraint. User set 90deg, got 90.05deg after compaction. Fix: Re-snap all rotations after compaction. B1 (WRONG): Single-part centering placed instance origin at bed center, ignoring polygon offset. Asymmetric meshes appeared off-center. Fix: Account for polygon centroid when computing center position. B8 (COSMETIC): Progress callback scale was items-based, not 0-100%. Fix: Use gen * 100 / max_gen for percentage. B9 (perf): Oversized parts still voxelized despite being pre-flagged. Already marked bed_idx=-1, nester handles gracefully. Left as-is (performance waste, not correctness bug). Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Bug P2-1 (WRONG): generations_run always reported max_generations regardless of early exit, timeout, or cancel. Now tracks actual gen. Bug P2-2 (PERF): Compaction binary search created redundant rotated_copy calls inside inner loop (216K+ calls for 30 parts). Fix: Pre-compute rotated copies for collision partners once per part per sweep, reuse for all 12 binary search iterations. Bug P2-3 (WRONG/OOM): lock_rotation cache allocated 360 bins but filled only 1, leaving 359 empty grids. GPU evaluator could read empty bin and report false-feasible (zero collisions). Fix: Fill all 360 bins with the same rotated copy using vector resize(N, value). Safe and minimal memory (shared copies). Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
U1: seed_bottom_left randf reversed range (part > half bed) U2: seed_greedy_grid division by zero on empty parts U3: Missing bed_idx=-1 when model instance not found U4: rotated_copy negative dx/dy cast to size_t (silent voxel loss) U5: population_size=0 causes UB in tournament_select False positives triaged out: fitness div-by-zero (bed validated at entry), mutate parts[i] bounds (sizes always match), double bed_origin (verified correct by Sauron x3), evaluate_batch exception (CPU doesn't throw), compaction timeout (12-iter binary search is bounded). Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Wave 3: randomize_placement hi_x/hi_y now subtracts part_margin on right/top side (was only subtracting lo, allowing parts to exceed bed) Wave 4: Wildcard mutation randf(wild_margin, bed-wild_margin) guarded against reversed range when part > half bed size. False positives triaged: PopItemWidth (Orca convention — 22 pushes, 2 pops across file, window end resets stack), nudge_range negative (config is always positive), index bounds (validated upstream), get_rotated null (private, only called from guarded path). Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
When bed_margin_mm > bed_width/2 (tiny beds like 120mm A1 Mini with large gap settings), std::clamp(val, margin, bed-margin) had min > max which is undefined behavior per C++17. Added safe_clamp() that returns midpoint when min > max. Replaced all 7 std::clamp calls with bed_margin bounds in the nester. Wave 5: CLEAN (1 of 3) Wave 6: 1 bug fixed (this), 4 CLEAN Running total: 3 waves needed with 0 bugs. Currently at 1 of 3. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Wave 7b: Greedy accept now skips parts with empty grids (failed voxelization, bed_idx already -1). Previously called rotated_copy on empty grids which could produce garbage results. Wave 7c: rotated_copy now checks both lower AND upper bounds on destination indices (dx < rnx, dy < rny). Previously only checked dx >= 0, dy >= 0 — edge voxels at the exact boundary could write out of bounds (silently dropped by set() but indicates logic error). False positives triaged: crossover stale fitness (re-evaluated next gen), reset rotation=0 (intended safe default), params vs settings mismatch (Orca's existing pattern — non-user fields computed from printer config, not stored in ArrangeSettings). Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
padding_mm, max_parts, and timeout_s now clamped on load to match UI slider ranges. Prevents hand-edited config from producing out-of-range values (negative max_parts, zero timeout, etc.). Wave 8: 1 real fix (load clamps), 2 false positives triaged (compact parts bounds — sizes always match; parts[0] — guarded by n<2) Clean wave count: 0 of 3 (reset by this fix) Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
snuggle_rotation_step now clamped to [0,90] on config load for symmetry with all other numeric settings. UI already snaps to valid values, but load path was unclamped. Wave 9: 1 minor fix, 2 false positives (voxelizer indices validated upstream, GPU margin is documented TODO) Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Replaced quality slider with direct numeric controls: - Population (16-1024, default 64) — GA candidates per generation - Generations (10-500, default 30) — evolution cycles - Resolution (0.5-5.0mm, default 2.0mm) — voxel size with slider+textbox - Timeout (2-300s, default 5s) — per-plate time budget Defaults from Legolas performance sweep: - pop=64 gen=30 at 2mm: 5-10 parts in ~1-2 seconds, collision-free - Rotation step default: 15 degrees (was locked) - Compact: OFF by default (GA fitness handles packing, compact causes bugs with rotation snapping) UI layout: - Top level: Enable, Spacing slider, Rotation dropdown, Resolution slider - Collapsible "Effort / Advanced": Population, Generations, Timeout, Max parts, Multi-plate, Post-GA compaction All settings load/save/reset with clamps. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Changed snuggle_padding_mm default from 5.0 to 1.0 mm. Impact (256mm bed, pop=64 gen=30, 2mm voxels): - 5mm gap: wall at 17 parts - 1mm gap: wall at 30 parts, 693ms, 49% bed utilization The 5mm default was eating ~90mm of bed space in gaps alone. 1mm is sufficient for FDM — conservative outward voxelization already provides sub-voxel clearance margin. From Legolas v3 (509 runs): the feasibility wall is geometric (bed area vs part footprint), not algorithmic. Reducing gap is the single biggest lever for fitting more parts. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Seers found: GPU shader was already adaptive-voxel-safe (reads per-grid voxel_size from GridMeta). Real bug was GPU ignoring bed_margin in OOB checks — shader checked against [0, bed_w] while CPU checked [margin, bed_w-margin]. Fixed by adding u_bed_margin uniform to shader. Test harness updates: - GPU enabled (SLIC3R_GUI + GLEW linked) - Multi-restart (--restarts N, keeps best of N runs) - Adaptive voxel (--detail N, per-part resolution from AABB diagonal) - Gap default changed to 1mm Status: adaptive+GPU works for 10 parts. 20 parts shows oob_count disagreement (under investigation — margin coordination issue). Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Added guards to GPU evaluator upload_grids: - Query GL_MAX_SHADER_STORAGE_BLOCK_SIZE before upload - Check total_voxel_bytes < UINT32_MAX (shader uses uint32 offsets) - Fall back to CPU with warning if limits exceeded Bug narrowed: adaptive voxels + rotation (rot=15) on GPU = broken. Adaptive + locked rotation (rot=0) on GPU = works perfectly. Uniform voxels + rotation on GPU = works perfectly. The issue is specifically: mixed voxel_size parts × rotated grids. Ents + Sonnet probe confirmed: not a buffer SIZE issue (data is small enough). Root cause is in rotation cache metadata when parts have different voxel_sizes and rotation produces variable-sized grids. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Remove undefined 'quality' variable in log line (Sauron: compile error) - Make GpuCollisionEvaluator::available_ std::atomic<bool> (Aragorn: data race) - Add index bounds check in voxelize_indexed_mesh (Aragorn: OOB read) - Replace 6 live rotated_copy() calls in compaction with cached_rotated() (Legolas: 7.2s wasted allocation at 1mm voxels, exceeded compact timeout) - Fix full-fallback finish message showing "Snuggle complete — 0 parts" instead of generic "Arranging done." (Frodo: misleading UX) Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Owner
Author
|
Not merging to main yet — keeping work on feature/snuggle-uiux. |
thereprocase
added a commit
that referenced
this pull request
Mar 31, 2026
Ledger fixes (Sauron #1): - W-001: clean up dangling brace in SnuggleArrange (cosmetic, already safe) - W-003 CRITICAL: GPU cleanup() now releases DC/HWND on all failure paths - W-006 HIGH: voxel_size<=0 guard in world_to_grid + collision_count - W-007 HIGH: multimap instance lookup replaces O(N²) linear scan - W-010: fmod replaces while-loop rotation normalization (DoS fix) - W-027: shared snuggle_constants.hpp for ROT_CACHE_BINS - W-028: PI_F/TWO_PI_F constants replace 9 bare literals UX fixes (Sauron OrcaSlicer#2, from Frodo's supervision): - Part gap slider min matches backend (1mm) - Duplicate compact checkbox removed - 4 missing tooltips added (Part gap, Timeout, Max parts, Multi-plate) - Progress messages humanized ("Optimizing... 42%") - Footer text clarified - Fallback notification added - Label references corrected Reviewed by: Gandalf (ledger walkthrough), Frodo (final approval) Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
thereprocase
added a commit
that referenced
this pull request
Apr 1, 2026
Critical: translation writeback computed bbox center instead of polygon origin (0,0). Parts would land at wrong positions offset by (bbox_center - origin). Fixed to track where the poly's local origin falls in bed coordinates after placement. High: removed dead code (6 unused variables from earlier approach). High: config key now uses technology postfix (_fff, _sla, _seq_print) matching the established pattern for multi-tech persistence. Medium: guard negative pixel offsets in collides/stamp to prevent undefined behavior from exclude polygons extending past bed edge. Medium: replaced goto-based instance matching with bool tracking and added warning log when no ModelInstance match is found. Fixes: Sauron #1 (critical), OrcaSlicer#2 (high), OrcaSlicer#3 (high), OrcaSlicer#5 (medium), Uruk-Hai UI #1 (high), OrcaSlicer#2 (high), OrcaSlicer#3 (medium), Uruk-Hai ArrangeJob OrcaSlicer#2 (high) Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
thereprocase
added a commit
that referenced
this pull request
Apr 5, 2026
Critical: translation writeback computed bbox center instead of polygon origin (0,0). Parts would land at wrong positions offset by (bbox_center - origin). Fixed to track where the poly's local origin falls in bed coordinates after placement. High: removed dead code (6 unused variables from earlier approach). High: config key now uses technology postfix (_fff, _sla, _seq_print) matching the established pattern for multi-tech persistence. Medium: guard negative pixel offsets in collides/stamp to prevent undefined behavior from exclude polygons extending past bed edge. Medium: replaced goto-based instance matching with bool tracking and added warning log when no ModelInstance match is found. Fixes: Sauron #1 (critical), OrcaSlicer#2 (high), OrcaSlicer#3 (high), OrcaSlicer#5 (medium), Uruk-Hai UI #1 (high), OrcaSlicer#2 (high), OrcaSlicer#3 (medium), Uruk-Hai ArrangeJob OrcaSlicer#2 (high) Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
thereprocase
added a commit
that referenced
this pull request
Apr 11, 2026
User priority #1: minimize print-head travel. Travel cost between two points in a rectangle is upper-bounded by the diagonal, so minimizing diag^2 is a direct proxy for worst-case travel across the cluster. Area delta doesn't distinguish a 100x100 cluster (diag^2=20000) from a 400x25 cluster (diag^2=160625) — both have area 10000 — even though the 400x25 layout forces 2.8x longer diagonal moves for the same total footprint. Diag^2 correctly identifies the square as tighter. Implementation: replace the area-delta primary with diag^2-delta in both the main placement score_at and the consolidation migration score_dst. Same data inputs (cluster bbox corners + candidate bbox corners), different metric. Interlock detection still works because a rotated shape that fits inside the current bbox has delta=0 in both area and diag^2. Not expected to change tetromino tests much (tetrominoes are tight interlock-dominated, scoring mostly hits the delta=0 case). Should favor more square cluster shapes on realistic mixes — tall elongated strips will lose to compact clumps of equal area. Removed the now-dead cbb_area variable at line 607. Pairs with the priority ordering saved to memory/feedback_arrange_priorities.md.
thereprocase
added a commit
that referenced
this pull request
Apr 11, 2026
Primary stays delta(cluster_bbox_area). New secondary is delta(cluster_bbox_perimeter). Tertiary is the existing dist-to-anchor + tall-bias. score_at now returns std::tuple<int64_t, int64_t, double> and std::tuple's operator< does lexicographic comparison directly. Rationale: when multiple candidate positions produce the same area growth, the current tiebreaker was dist-to-anchor (positional). Now the first tiebreak is perimeter growth, which prefers positions that keep the cluster more square. Square clusters have shorter perimeters which directly tracks worst-case print-head XY travel — user priority #1. For non-tied-area cases, behavior is unchanged (primary still wins). For tied-area cases, the secondary now drives selection toward square clusters instead of arbitrary off-grid positions. Earlier this session I tried diag² as a REPLACEMENT for the area primary and that broke a pre-existing sub-pixel test fragility. This version is lower-risk because ties on area are rare enough that the positional shift is smaller. The 4-equal-squares case is already solved by corner-anchor-for-all; this commit is additional insurance for mixed-item cases where area ties happen at coarse-scan boundaries. Tuple type change touches: score_at, CoarseBest::score, refine_score, plus new #include <tuple>.
thereprocase
added a commit
that referenced
this pull request
Apr 11, 2026
Critical: translation writeback computed bbox center instead of polygon origin (0,0). Parts would land at wrong positions offset by (bbox_center - origin). Fixed to track where the poly's local origin falls in bed coordinates after placement. High: removed dead code (6 unused variables from earlier approach). High: config key now uses technology postfix (_fff, _sla, _seq_print) matching the established pattern for multi-tech persistence. Medium: guard negative pixel offsets in collides/stamp to prevent undefined behavior from exclude polygons extending past bed edge. Medium: replaced goto-based instance matching with bool tracking and added warning log when no ModelInstance match is found. Fixes: Sauron #1 (critical), OrcaSlicer#2 (high), OrcaSlicer#3 (high), OrcaSlicer#5 (medium), Uruk-Hai UI #1 (high), OrcaSlicer#2 (high), OrcaSlicer#3 (medium), Uruk-Hai ArrangeJob OrcaSlicer#2 (high) Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
thereprocase
added a commit
that referenced
this pull request
Apr 11, 2026
User priority #1: minimize print-head travel. Travel cost between two points in a rectangle is upper-bounded by the diagonal, so minimizing diag^2 is a direct proxy for worst-case travel across the cluster. Area delta doesn't distinguish a 100x100 cluster (diag^2=20000) from a 400x25 cluster (diag^2=160625) — both have area 10000 — even though the 400x25 layout forces 2.8x longer diagonal moves for the same total footprint. Diag^2 correctly identifies the square as tighter. Implementation: replace the area-delta primary with diag^2-delta in both the main placement score_at and the consolidation migration score_dst. Same data inputs (cluster bbox corners + candidate bbox corners), different metric. Interlock detection still works because a rotated shape that fits inside the current bbox has delta=0 in both area and diag^2. Not expected to change tetromino tests much (tetrominoes are tight interlock-dominated, scoring mostly hits the delta=0 case). Should favor more square cluster shapes on realistic mixes — tall elongated strips will lose to compact clumps of equal area. Removed the now-dead cbb_area variable at line 607. Pairs with the priority ordering saved to memory/feedback_arrange_priorities.md.
thereprocase
added a commit
that referenced
this pull request
Apr 11, 2026
Primary stays delta(cluster_bbox_area). New secondary is delta(cluster_bbox_perimeter). Tertiary is the existing dist-to-anchor + tall-bias. score_at now returns std::tuple<int64_t, int64_t, double> and std::tuple's operator< does lexicographic comparison directly. Rationale: when multiple candidate positions produce the same area growth, the current tiebreaker was dist-to-anchor (positional). Now the first tiebreak is perimeter growth, which prefers positions that keep the cluster more square. Square clusters have shorter perimeters which directly tracks worst-case print-head XY travel — user priority #1. For non-tied-area cases, behavior is unchanged (primary still wins). For tied-area cases, the secondary now drives selection toward square clusters instead of arbitrary off-grid positions. Earlier this session I tried diag² as a REPLACEMENT for the area primary and that broke a pre-existing sub-pixel test fragility. This version is lower-risk because ties on area are rare enough that the positional shift is smaller. The 4-equal-squares case is already solved by corner-anchor-for-all; this commit is additional insurance for mixed-item cases where area ties happen at coarse-scan boundaries. Tuple type change touches: score_at, CoarseBest::score, refine_score, plus new #include <tuple>.
thereprocase
added a commit
that referenced
this pull request
Apr 12, 2026
Two fixes from visual testing with the GUI-wired snapshot: 1. island.bbox now expanded by inflation padding so locate's clamp accounts for the full bitmap footprint, not just raw polygon extent. Fixes parts hanging past bed edges on high-count plates. 2. Pre-rotate polygon ONCE per rotation, not per candidate position. Eliminates N_candidates rotate() calls per item — the #1 hot-path cost per Legolas's S3.3 review. Applied to both coarse scan and refine pass. Also avoids full ExPolygon clone per candidate; instead translates pre-extracted contour points with emplace_back. 34 cases / 318 assertions green. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Snuggle is a new arrange algorithm that uses 3D voxelized mesh geometry (not just 2D convex hulls) to pack parts tightly on the print bed using GPU-accelerated evolutionary optimization.
New files (3,110 lines of Snuggle code)
Modified files
Code review
Full War Council review completed (Sauron/Gandalf/Frodo at Opus, Aragorn/Legolas at Sonnet, 10 waves of Uruk-Hai adversarial bug hunting). All blocking issues resolved. Coordinate chain triple-verified.
Also includes
Test plan