diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index 50b20bacd1..8e5e57021c 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -122,8 +122,8 @@ void update_arrange_params(ArrangeParams& params, const DynamicPrintConfig* prin void update_selected_items_inflation(ArrangePolygons& selected, const DynamicPrintConfig* print_cfg, ArrangeParams& params) { // do not inflate brim_width. Objects are allowed to have overlapped brim. - Points bedpts = get_shrink_bedpts(print_cfg, params); - BoundingBox bedbb = Polygon(bedpts).bounding_box(); + // A brim reaching the printable area's edge is fine, so measure against the full bed. + BoundingBox bedbb = Polygon(get_bed_shape(*print_cfg)).bounding_box(); // set obj distance for auto seq_print if (params.is_seq_print) { bool all_objects_are_short = std::all_of(selected.begin(), selected.end(), [&](ArrangePolygon& ap) { return ap.height < params.nozzle_height; }); @@ -144,14 +144,30 @@ void update_selected_items_inflation(ArrangePolygons& selected, const DynamicPri // 3. otherwise, use each object's own brim width ap.inflation = params.min_obj_distance != 0 ? params.min_obj_distance / 2 : plate_has_tree_support ? scaled(brim_max / 2) : scaled(ap.brim_width); - BoundingBox apbb = ap.poly.contour.bounding_box(); - auto diffx = bedbb.size().x() - apbb.size().x() - 5; - auto diffy = bedbb.size().y() - apbb.size().y() - 5; - if (diffx > 0 && diffy > 0) { - auto min_diff = std::min(diffx, diffy); - ap.inflation = std::min(min_diff / 2, ap.inflation); - } + if (params.min_obj_distance != 0) + return; // an explicit object distance is the user's call, not ours to shrink + // An item cannot be larger than the printable area, inflation included. Inflating + // an almost bed filling item would leave it too big to ever be placed. + BoundingBox apbb = ap.poly.contour.bounding_box(); + auto diffx = bedbb.size().x() - apbb.size().x(); + auto diffy = bedbb.size().y() - apbb.size().y(); + ap.inflation = std::max(0, std::min({ ap.inflation, diffx / 2, diffy / 2 })); }); + + // Give the bed margin, which the arrange bed keeps from the printable area, back + // where the largest item needs it. The skirt keeps its room: pushing a skirt off + // the bed is worse than saying the item does not fit. + if (!selected.empty()) { + coord_t max_width = 0, max_height = 0; + for (const ArrangePolygon &ap : selected) { + BoundingBox apbb = ap.poly.contour.bounding_box(); + max_width = std::max(max_width, apbb.size().x() + 2 * ap.inflation); + max_height = std::max(max_height, apbb.size().y() + 2 * ap.inflation); + } + auto margin = [](coord_t bed, coord_t item) { return std::max(0., unscale(bed - item) / 2.); }; + params.bed_shrink_x = std::min(params.bed_shrink_x, std::max(params.brim_skirt_distance, margin(bedbb.size().x(), max_width))); + params.bed_shrink_y = std::min(params.bed_shrink_y, std::max(params.brim_skirt_distance, margin(bedbb.size().y(), max_height))); + } } void update_unselected_items_inflation(ArrangePolygons& unselected, const DynamicPrintConfig* print_cfg, const ArrangeParams& params) diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp index 1ba99bf1b1..3cdbd5b275 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.cpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp @@ -771,6 +771,17 @@ void ArrangeJob::finalize(bool canceled, std::exception_ptr &eptr) { if (canceled || eptr) return; + // Nothing could be placed: leave the plates and the instances exactly as they were. + // Rebuilding the plates would only re-home the instances by their bounds, which moves + // a too large item to whichever plate it happens to reach over. + const bool nothing_arranged = !m_selected.empty() && + std::all_of(m_selected.begin(), m_selected.end(), [](const ArrangePolygon &ap) { return !ap.is_arranged(); }); + if (nothing_arranged) { + m_plater->get_notification_manager()->close_notification_of_type(NotificationType::ArrangeOngoing); + m_plater->m_arrange_running.store(false); + return; + } + // Unprintable items go to the last virtual bed int beds = 0; @@ -870,12 +881,13 @@ void ArrangeJob::finalize(bool canceled, std::exception_ptr &eptr) { } m_plater->get_notification_manager()->close_notification_of_type(NotificationType::ArrangeOngoing); - //BBS: reload all objects due to arrange + //BBS: reload all objects due to arrange. Plates are never recycled here: arranging + // must not remove a plate the user created, even when the arrange left it empty. if (only_on_partplate) { - plate_list.rebuild_plates_after_arrangement(!only_on_partplate, true, current_plate_index); + plate_list.rebuild_plates_after_arrangement(false, true, current_plate_index); } else { - plate_list.rebuild_plates_after_arrangement(!only_on_partplate, true); + plate_list.rebuild_plates_after_arrangement(false, true); } // unlock the plates we just locked diff --git a/src/slic3r/GUI/Jobs/FillBedJob.cpp b/src/slic3r/GUI/Jobs/FillBedJob.cpp index 4b4936e4ef..91981852dd 100644 --- a/src/slic3r/GUI/Jobs/FillBedJob.cpp +++ b/src/slic3r/GUI/Jobs/FillBedJob.cpp @@ -248,7 +248,6 @@ void FillBedJob::process(Ctl &ctl) if (m_object_idx == -1 || m_selected.empty()) return; update_arrange_params(params, m_plater->config(), m_selected); - m_bedpts = get_shrink_bedpts(m_plater->config(), params); auto &partplate_list = m_plater->get_partplate_list(); auto &print = wxGetApp().plater()->get_partplate_list().get_current_fff_print(); @@ -260,6 +259,8 @@ void FillBedJob::process(Ctl &ctl) update_selected_items_inflation(m_selected, m_plater->config(), params); update_unselected_items_inflation(m_unselected, m_plater->config(), params); + // After the inflation, which may give back part of the bed margin. + m_bedpts = get_shrink_bedpts(m_plater->config(), params); bool do_stop = false; params.stopcondition = [&ctl, &do_stop]() { diff --git a/src/slic3r/GUI/PartPlate.cpp b/src/slic3r/GUI/PartPlate.cpp index ae0c0c46db..5d8ccd1a15 100644 --- a/src/slic3r/GUI/PartPlate.cpp +++ b/src/slic3r/GUI/PartPlate.cpp @@ -7048,19 +7048,14 @@ void PartPlateList::postprocess_arrange_polygon(arrangement::ArrangePolygon& arr { BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": bed_idx %1%, selected %2%, translation(x) %3%, (y) %4%") % arrange_polygon.bed_idx % selected % unscale(arrange_polygon.translation(X)) % unscale(arrange_polygon.translation(Y)); + // UNARRANGED means the item could not be placed, so it has no bed to map onto and + // must be left where the user had it. Moving it to a virtual bed here would shift + // it off the plate, because no such plate is created for it afterwards. + if (arrange_polygon.bed_idx == arrangement::UNARRANGED) + return; + if ((selected) || (arrange_polygon.bed_idx != PartPlateList::MAX_PLATES_COUNT)) { - if (arrange_polygon.bed_idx == -1) - { - // outarea for large object - arrange_polygon.bed_idx = m_plate_list.size(); - BoundingBox apbox = get_extents(arrange_polygon.transformed_poly()); // the item may have been rotated - auto apbox_size = apbox.size(); - - arrange_polygon.translation(X) = 0.5 * apbox_size[0]; - arrange_polygon.translation(Y) = scaled(static_cast(m_plate_depth)) - 0.5 * apbox_size[1]; - } - arrange_polygon.row = arrange_polygon.bed_idx / m_plate_cols; arrange_polygon.col = arrange_polygon.bed_idx % m_plate_cols; arrange_polygon.translation(X) += scaled(plate_stride_x() * arrange_polygon.col); diff --git a/tests/libslic3r/test_arrange.cpp b/tests/libslic3r/test_arrange.cpp index 030dd9e4b6..9d3164050e 100644 --- a/tests/libslic3r/test_arrange.cpp +++ b/tests/libslic3r/test_arrange.cpp @@ -220,11 +220,39 @@ TEST_CASE("An item larger than the bed cannot be placed", "[Arrange]") ArrangePolygons items; items.emplace_back(make_square(scaled(20.))); items.emplace_back(make_square(scaled(400.))); // far bigger than the bed + // An item that cannot be placed keeps the position it came in with, so the + // caller can leave it where the user had it. + items[1].translation = {scaled(10.), scaled(20.)}; arrange(items, bed(200, 200), quiet_params(scaled(1.))); REQUIRE(items[0].bed_idx == 0); REQUIRE(items[1].bed_idx == UNARRANGED); + REQUIRE(items[1].translation.x() == scaled(10.)); + REQUIRE(items[1].translation.y() == scaled(20.)); +} + +TEST_CASE("An item filling the printable area is arranged", "[Arrange]") +{ + // The arrange bed keeps a margin from the printable area and the item is widened by + // its brim. Neither may keep an item that fits the printable area from being placed. + DynamicPrintConfig cfg = bed_config(); // 200 x 200 + ArrangeParams p = quiet_params(); + + ArrangePolygons items = squares(1, 197.); + items.front().brim_width = 1.; + update_arrange_params(p, &cfg, items); + update_selected_items_inflation(items, &cfg, p); + arrange(items, get_shrink_bedpts(&cfg, p), p); + REQUIRE(items.front().bed_idx == 0); + + // Larger than the printable area is still refused. + ArrangeParams p_large = quiet_params(); + ArrangePolygons large = squares(1, 201.); + update_arrange_params(p_large, &cfg, large); + update_selected_items_inflation(large, &cfg, p_large); + arrange(large, get_shrink_bedpts(&cfg, p_large), p_large); + REQUIRE(large.front().bed_idx == UNARRANGED); } TEST_CASE("Items overflowing one bed spill onto virtual beds", "[Arrange]")