diff --git a/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp b/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp index ab5f7678f..fff5f73c4 100644 --- a/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp +++ b/src/libnest2d/include/libnest2d/geometry_traits_nfp.hpp @@ -19,6 +19,14 @@ inline bool _vsort(const TPoint& v1, const TPoint& v2) return y1 == y2 ? x1 < x2 : y1 < y2; } +template> +inline bool _vsort_max_x(const TPoint& v1, const TPoint& v2) +{ + Unit x1 = getX(v1), x2 = getX(v2), y1 = getY(v1), y2 = getY(v2); + return y1 == y2 ? x1 > x2 : y1 < y2; +} + + template> inline void buildPolygon(const EdgeList& edgelist, RawShape& rpoly, @@ -166,6 +174,22 @@ TPoint rightmostUpVertex(const RawShape& sh) return it == shapelike::cend(sh) ? TPoint() : *it; } +/** + * Get the vertex of the polygon that is at the lowest values (bottom) in the Y + * axis and if there are more than one vertices on the same Y coordinate then + * the result will be the leftmost (with the lowest X coordinate). + */ +template +TPoint leftmostBottomVertex(const RawShape& sh) +{ + + // find min x and min y vertex + auto it = std::min_element(shapelike::cbegin(sh), shapelike::cend(sh), + __nfp::_vsort); + + return it == shapelike::cend(sh) ? TPoint() : *it; +} + /** * A method to get a vertex from a polygon that always maintains a relative * position to the coordinate system: It is always the rightmost top vertex. @@ -227,7 +251,7 @@ template inline NfpResult best_object_pos; + // scaled bed shrink in x and y direction + TPoint bed_shrink; + /** * @brief A function object representing the fitting function in the * placement optimization process. (Optional) @@ -168,9 +171,9 @@ template class EdgeCache { void createCache(const RawShape& sh) { { // For the contour - auto first = shapelike::cbegin(sh); - auto next = std::next(first); - auto endit = shapelike::cend(sh); + auto first = sl::cbegin(sh); + auto endit = sl::cend(sh); + auto next = first == endit ? endit : std::next(first); contour_.distances.reserve(shapelike::contourVertexCount(sh)); @@ -182,12 +185,12 @@ template class EdgeCache { } for(auto& h : shapelike::holes(sh)) { // For the holes - auto first = h.begin(); - auto next = std::next(first); - auto endit = h.end(); + auto first = sl::cbegin(h); + auto endit = sl::cend(h); + auto next = first == endit ? endit :std::next(first); ContourCache hc; - hc.distances.reserve(endit - first); + hc.distances.reserve(sl::contourVertexCount(h)); while(next != endit) { hc.emap.emplace_back(*(first++), *(next++)); @@ -217,7 +220,6 @@ template class EdgeCache { contour_.corners.reserve(N / S + 1); contour_.corners.emplace_back(0.0); auto N_1 = N-1; - contour_.corners.emplace_back(0.0); for(size_t i = 0; i < N_1; i += S) { contour_.corners.emplace_back( contour_.distances.at(i) / contour_.full_distance); @@ -349,12 +351,18 @@ inline void correctNfpPosition(nfp::NfpResult& nfp, // rightmost upper vertex of the nfp. No proof provided other than Jonas // Lindmark's reasoning about the reference vertex of nfp in his thesis // ("No fit polygon problem" - section 2.1.9) - +#if 0 auto touch_sh = stationary.rightmostTopVertex(); auto touch_other = orbiter.leftmostBottomVertex(); auto dtouch = touch_sh - touch_other; auto top_other = orbiter.rightmostTopVertex() + dtouch; auto dnfp = top_other - nfp.second; // nfp.second is the nfp reference point +#else + // move the nfp so that its leftmost bottom vertex touches that of the stationary + auto touch_sh = stationary.leftmostBottomVertex(); + auto touch_nfp = nfp::leftmostBottomVertex(nfp.first); + auto dnfp = touch_sh - touch_nfp; +#endif shapelike::translate(nfp.first, dnfp); } @@ -363,11 +371,17 @@ inline void correctNfpPosition(nfp::NfpResult& nfp, const RawShape& stationary, const _Item& orbiter) { +#if 0 auto touch_sh = nfp::rightmostUpVertex(stationary); auto touch_other = orbiter.leftmostBottomVertex(); auto dtouch = touch_sh - touch_other; auto top_other = orbiter.rightmostTopVertex() + dtouch; auto dnfp = top_other - nfp.second; +#else + auto touch_sh = nfp::leftmostBottomVertex(stationary); + auto touch_nfp = nfp::leftmostBottomVertex(nfp.first); + auto dnfp = touch_sh - touch_nfp; +#endif shapelike::translate(nfp.first, dnfp); } @@ -609,7 +623,8 @@ private: }); RawShape innerNfp = nfpInnerRectBed(bed, trsh.transformedShape()).first; - return nfp::subtract({innerNfp}, nfps); + Shapes finalNFP = nfp::subtract({ innerNfp }, nfps); + return finalNFP; } Shapes calcnfp(const RawShape &sliding, const Shapes &stationarys, const Box &bed, Lvl) @@ -731,6 +746,7 @@ private: bool first_object = std::all_of(items_.begin(), items_.end(), [&](const Item &rawShape) { return rawShape.is_virt_object && !rawShape.is_wipe_tower; }); // item won't overlap with virtual objects if it's inside or touches NFP + // @return 1 if current item overlaps with virtual objects, 0 otherwise auto overlapWithVirtObject = [&]() -> double { if (items_.empty()) return 0; nfps = calcnfp(item, binbb, Lvl()); @@ -747,19 +763,28 @@ private: auto best_rot = item.rotation(); best_overfit = overfit(item.transformedShape(), bin_) + overlapWithVirtObject(); - for(auto rot : config_.rotations) { - item.translation(initial_tr); - item.rotation(initial_rot + rot); - setInitialPosition(item); - double of = 0.; - if ((of = overfit(item.transformedShape(), bin_)) + overlapWithVirtObject() < best_overfit) { - best_overfit = of; - best_tr = item.translation(); - best_rot = item.rotation(); + // try normal inflation first, then 0 inflation if not fit. See STUDIO-5566. + // Note for by-object printing, bed is expanded by -config_.bed_shrink.x(). + Coord inflation_back = item.inflation(); + Coord inflations[2]={inflation_back, std::abs(config_.bed_shrink.x())}; + for (size_t i = 0; i < 2; i++) { + item.inflation(inflations[i]); + for (auto rot : config_.rotations) { + item.translation(initial_tr); + item.rotation(initial_rot + rot); + setInitialPosition(item); + double of = overfit(item.transformedShape(), bin_); + if (of + overlapWithVirtObject() < best_overfit) { + best_overfit = of; + best_tr = item.translation(); + best_rot = item.rotation(); + } } + can_pack = best_overfit <= 0; + if(can_pack) break; } - - can_pack = best_overfit <= 0; + item.inflation(inflation_back); + if (can_pack) global_score = 0.2; item.rotation(best_rot); @@ -967,14 +992,20 @@ private: svgwriter.setSize(binbb2); svgwriter.conf_.x0 = binbb.width(); svgwriter.conf_.y0 = -binbb.height()/2; // origin is top left corner + svgwriter.add_comment("bed"); svgwriter.writeShape(box2RawShape(binbb), "none", "black"); + svgwriter.add_comment("nfps"); for (int i = 0; i < nfps.size(); i++) svgwriter.writeShape(nfps[i], "none", "blue"); - for (int i = 0; i < items_.size(); i++) + for (int i = 0; i < items_.size(); i++) { + svgwriter.add_comment(items_[i].get().name); svgwriter.writeItem(items_[i], "none", "black"); + } + svgwriter.add_comment("merged_pile_"); for (int i = 0; i < merged_pile_.size(); i++) svgwriter.writeShape(merged_pile_[i], "none", "yellow"); - svgwriter.writeItem(item, "none", "red", 2); + svgwriter.add_comment("current item"); + svgwriter.writeItem(item, "red", "red", 2); std::stringstream ss; ss.setf(std::ios::fixed | std::ios::showpoint); @@ -987,7 +1018,7 @@ private: ss << "items.size=" << items_.size() << "-merged_pile.size=" << merged_pile_.size(); svgwriter.draw_text(20, 40, ss.str(), "blue", 20); - svgwriter.save(boost::filesystem::path("SVG")/ ("nfpplacer_" + std::to_string(plate_id) + "_" + ss.str() + "_" + item.name + ".svg")); + svgwriter.save(boost::filesystem::path("C:/Users/arthur.tang/AppData/Roaming/BambuStudioInternal/SVG")/ ("nfpplacer_" + std::to_string(plate_id) + "_" + ss.str() + "_" + item.name + ".svg")); #endif if(can_pack) { @@ -1115,27 +1146,32 @@ private: // BBS make sure the item won't clash with excluded regions // do we have wipe tower after arranging? + size_t n_objs = 0; std::set extruders; for (const Item& item : items_) { - if (!item.is_virt_object) { extruders.insert(item.extrude_ids.begin(), item.extrude_ids.end()); } + if (!item.is_virt_object) { + extruders.insert(item.extrude_ids.begin(), item.extrude_ids.end()); + n_objs++; + } } bool need_wipe_tower = extruders.size() > 1; std::vector objs,excludes; - for (const Item &item : items_) { - if (item.isFixed()) continue; - objs.push_back(item.transformedShape()); + for (Item &item : items_) { + if (item.isFixed()) { + excludes.push_back(item.transformedShape()); + } + else { + // better center a single large object without any inflation + if (n_objs == 1) + item.inflation(0); + objs.push_back(item.transformedShape()); + } } if (objs.empty()) return; { // find a best position inside NFP of fixed items (excluded regions), so the center of pile is cloest to bed center RawShape objs_convex_hull = sl::convexHull(objs); - for (const Item &item : items_) { - if (item.isFixed()) { - excludes.push_back(item.transformedShape()); - } - } - auto nfps = calcnfp(objs_convex_hull, excludes, bbin, Lvl()); if (nfps.empty()) { return; diff --git a/src/libnest2d/include/libnest2d/selections/firstfit.hpp b/src/libnest2d/include/libnest2d/selections/firstfit.hpp index bb7f4b45c..47442850a 100644 --- a/src/libnest2d/include/libnest2d/selections/firstfit.hpp +++ b/src/libnest2d/include/libnest2d/selections/firstfit.hpp @@ -182,7 +182,7 @@ public: if(!was_packed){ if (this->unfitindicator_ && !placers.empty()) - this->unfitindicator_(it->get().name + ", height=" +std::to_string(it->get().height) + this->unfitindicator_(it->get().name + " not fit! height=" +std::to_string(it->get().height) + " ,plate_id=" + std::to_string(j-1) + ", score=" + std::to_string(score) + ", best_bed_id=" + std::to_string(best_bed_id) diff --git a/src/libnest2d/tools/svgtools.hpp b/src/libnest2d/tools/svgtools.hpp index 2bf090b32..4497b2b3e 100644 --- a/src/libnest2d/tools/svgtools.hpp +++ b/src/libnest2d/tools/svgtools.hpp @@ -153,6 +153,12 @@ public: }; } + void add_comment(const std::string comment) + { + if (svg_layers_.empty()) addLayer(); + currentLayer() += "\n"; + } + private: std::string& currentLayer() { return svg_layers_.back(); } diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index 6b52b843a..637b47837 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -119,13 +119,6 @@ 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); - } }); } @@ -296,6 +289,8 @@ void fill_config(PConf& pcfg, const ArrangeParams ¶ms) { else pcfg.rotations = {0.}; + pcfg.bed_shrink = { scale_(params.bed_shrink_x), scale_(params.bed_shrink_y) }; + // The accuracy of optimization. // Goes from 0.0 to 1.0 and scales performance as well pcfg.accuracy = params.accuracy; diff --git a/src/libslic3r/Arrange.hpp b/src/libslic3r/Arrange.hpp index fd5342041..eb3b4de78 100644 --- a/src/libslic3r/Arrange.hpp +++ b/src/libslic3r/Arrange.hpp @@ -126,8 +126,8 @@ struct ArrangeParams { bool avoid_extrusion_cali_region = true; bool is_seq_print = false; bool align_to_y_axis = false; - float bed_shrink_x = 1; - float bed_shrink_y = 1; + float bed_shrink_x = 0.1; + float bed_shrink_y = 0.1; float brim_skirt_distance = 0; float clearance_height_to_rod = 0; float clearance_height_to_lid = 0; diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp index f422c334f..f42a8a197 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.cpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp @@ -710,6 +710,12 @@ void ArrangeJob::finalize() { } m_plater->get_notification_manager()->close_notification_of_type(NotificationType::ArrangeOngoing); + // unlock the plates we just locked + for (int i : m_uncompatible_plates) { + PartPlate* plate = plate_list.get_plate(i); + if (plate) plate->lock(false); + } + //BBS: reload all objects due to arrange if (only_on_partplate) { plate_list.rebuild_plates_after_arrangement(!only_on_partplate, true, current_plate_index); @@ -718,10 +724,6 @@ void ArrangeJob::finalize() { plate_list.rebuild_plates_after_arrangement(!only_on_partplate, true); } - // unlock the plates we just locked - for (int i : m_uncompatible_plates) - plate_list.get_plate(i)->lock(false); - // BBS: update slice context and gcode result. m_plater->update_slicing_context_to_current_partplate();