From b9f7148080a5d7540d4b7ae691025a7210d5b282 Mon Sep 17 00:00:00 2001 From: Clifford Garwood Date: Mon, 6 Apr 2026 00:52:37 -0400 Subject: [PATCH] fix: constrain auto-arrange to iXex primary zone when parallel mode is active Adds std::optional m_ixex_primary_zone_box to PartPlate, populated by calc_ixex_zones() alongside the existing secondary/collision zone geometry. The new ixex_primary_zone() getter calls ensure_ixex_zones() so callers always get fresh data. ArrangeJob::process() replaces the full-bed bedpts with the primary zone corners when the getter returns a value, so auto-arrange no longer drops objects into the bed center. Co-Authored-By: Claude Sonnet 4.6 --- src/slic3r/GUI/Jobs/ArrangeJob.cpp | 13 +++++++++++++ src/slic3r/GUI/PartPlate.cpp | 2 ++ src/slic3r/GUI/PartPlate.hpp | 7 ++++++- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp index 2e43f0271e..dded97556a 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.cpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp @@ -540,6 +540,19 @@ void ArrangeJob::process(Ctl &ctl) Points bedpts = get_shrink_bedpts(m_plater->config(),params); + // When an iXex parallel mode is active, constrain auto-arrange to the primary zone only. + if (PartPlate* curr_plate = partplate_list.get_curr_plate()) { + if (auto pz = curr_plate->ixex_primary_zone()) { + BoundingBox scaled_pz = scaled(*pz); + bedpts = { + { scaled_pz.min.x(), scaled_pz.min.y() }, + { scaled_pz.max.x(), scaled_pz.min.y() }, + { scaled_pz.max.x(), scaled_pz.max.y() }, + { scaled_pz.min.x(), scaled_pz.max.y() }, + }; + } + } + bool enable_wrapping = global_config.option("enable_wrapping_detection")->value; partplate_list.preprocess_exclude_areas(params.excluded_regions, enable_wrapping, 1, scale_(1)); diff --git a/src/slic3r/GUI/PartPlate.cpp b/src/slic3r/GUI/PartPlate.cpp index 09db0bb072..f48e12cb76 100644 --- a/src/slic3r/GUI/PartPlate.cpp +++ b/src/slic3r/GUI/PartPlate.cpp @@ -465,6 +465,7 @@ void PartPlate::calc_ixex_zones() m_ixex_collision_zones.clear(); m_ixex_collision_overlay.clear(); m_ixex_margin_overlay.clear(); + m_ixex_primary_zone_box = std::nullopt; if (!wxGetApp().preset_bundle) return; @@ -689,6 +690,7 @@ void PartPlate::calc_ixex_zones() double pz_x1 = has_col_sep ? x_min + (pri_col + 1) * zone_w : x_max; double pz_y0 = has_row_sep ? y_min + pri_row * zone_h : y_min; double pz_y1 = has_row_sep ? y_min + (pri_row + 1) * zone_h : y_max; + m_ixex_primary_zone_box = BoundingBoxf(Vec2d(pz_x0, pz_y0), Vec2d(pz_x1, pz_y1)); // Helper: build a BoundingBoxf3 strip, clip to bed, add to members auto add_strip = [&](double sx0, double sx1, double sy0, double sy1) { diff --git a/src/slic3r/GUI/PartPlate.hpp b/src/slic3r/GUI/PartPlate.hpp index 2bb575afca..23d63da7e0 100644 --- a/src/slic3r/GUI/PartPlate.hpp +++ b/src/slic3r/GUI/PartPlate.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -137,10 +138,11 @@ private: GLModel m_gridlines_bolder; std::vector m_ixex_copy_zones; // blue tint (Wong #56B4E9) std::vector m_ixex_mirror_zones; // orange tint (Wong #E69F00) -std::vector m_ixex_secondary_zone_boxes; // secondary (copy/mirror) zone rects — objects blocked here + std::vector m_ixex_secondary_zone_boxes; // secondary (copy/mirror) zone rects — objects blocked here std::vector m_ixex_collision_zones; // carriage danger strips inside primary zone (mm) std::vector m_ixex_collision_overlay; // red-orange rendered fill for danger strips std::vector m_ixex_margin_overlay; // amber advisory bands just inside collision strips + std::optional m_ixex_primary_zone_box; // primary zone extents in mm; empty when iXex is off/primary-only std::string m_ixex_zones_mode_cache{ "\x01" }; // cached mode+topology key; sentinel = unbuilt GLModel m_height_limit_common; GLModel m_height_limit_bottom; @@ -444,6 +446,9 @@ public: } // Returns true if any instance on this plate overlaps an iXex secondary or collision zone. bool has_ixex_placement_violations(); + // Returns the primary-zone bounding box in mm when an iXex parallel mode is active. + // Empty (nullopt) when iXex is off or the mode is "primary" (full-bed). + std::optional ixex_primary_zone() { ensure_ixex_zones(); return m_ixex_primary_zone_box; } void update_slice_ready_status(bool ready_slice) { m_ready_for_slice = ready_slice;