diff --git a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp index 6cdaadd257..3554892b37 100644 --- a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp +++ b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp @@ -479,13 +479,18 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer; - // Norming factor for the optimization function - const double norm_; - public: using Pile = nfp::Shapes; +private: + + // Norming factor for the optimization function + const double norm_; + Pile merged_pile_; + +public: + inline explicit _NofitPolyPlacer(const BinType& bin): Base(bin), norm_(std::sqrt(sl::area(bin))) @@ -616,135 +621,9 @@ private: template Shapes calcnfp(const Item &trsh, Level) { // Function for arbitrary level of nfp implementation - using namespace nfp; - Shapes nfps; - - auto& orb = trsh.transformedShape(); - bool orbconvex = trsh.isContourConvex(); - - for(Item& sh : items_) { - nfp::NfpResult subnfp; - auto& stat = sh.transformedShape(); - - if(sh.isContourConvex() && orbconvex) - subnfp = nfp::noFitPolygon(stat, orb); - else if(orbconvex) - subnfp = nfp::noFitPolygon(stat, orb); - else - subnfp = nfp::noFitPolygon(stat, orb); - - correctNfpPosition(subnfp, sh, trsh); - - nfps = nfp::merge(nfps, subnfp.first); - } - - return nfps; - } - - // Very much experimental - void repack(Item& item, PackResult& result) { - - if((sl::area(bin_) - this->filledArea()) >= item.area()) { - auto prev_func = config_.object_function; - - unsigned iter = 0; - ItemGroup backup_rf = items_; - std::vector backup_cpy; - for(Item& itm : items_) backup_cpy.emplace_back(itm); - - auto ofn = [this, &item, &result, &iter, &backup_cpy, &backup_rf] - (double ratio) - { - auto& bin = bin_; - iter++; - config_.object_function = [bin, ratio]( - nfp::Shapes& pile, - const Item& item, - const ItemGroup& /*remaining*/) - { - pile.emplace_back(item.transformedShape()); - auto ch = sl::convexHull(pile); - auto pbb = sl::boundingBox(pile); - pile.pop_back(); - - double parea = 0.5*(sl::area(ch) + sl::area(pbb)); - - double pile_area = std::accumulate( - pile.begin(), pile.end(), item.area(), - [](double sum, const RawShape& sh){ - return sum + sl::area(sh); - }); - - // The pack ratio -- how much is the convex hull occupied - double pack_rate = (pile_area)/parea; - - // ratio of waste - double waste = 1.0 - pack_rate; - - // Score is the square root of waste. This will extend the - // range of good (lower) values and shrink the range of bad - // (larger) values. - auto wscore = std::sqrt(waste); - - - auto ibb = item.boundingBox(); - auto bbb = sl::boundingBox(bin); - auto c = ibb.center(); - double norm = 0.5*pl::distance(bbb.minCorner(), - bbb.maxCorner()); - - double dscore = pl::distance(c, pbb.center()) / norm; - - return ratio*wscore + (1.0 - ratio) * dscore; - }; - - auto bb = sl::boundingBox(bin); - double norm = bb.width() + bb.height(); - - auto items = items_; - clearItems(); - auto it = items.begin(); - while(auto pr = _trypack(*it++)) { - this->accept(pr); if(it == items.end()) break; - } - - auto count_diff = items.size() - items_.size(); - double score = count_diff; - - if(count_diff == 0) { - result = _trypack(item); - - if(result) { - std::cout << "Success" << std::endl; - score = 0.0; - } else { - score += result.overfit() / norm; - } - } else { - result = PackResult(); - items_ = backup_rf; - for(unsigned i = 0; i < items_.size(); i++) { - items_[i].get() = backup_cpy[i]; - } - } - - std::cout << iter << " repack result: " << score << " " - << ratio << " " << count_diff << std::endl; - - return score; - }; - - opt::StopCriteria stopcr; - stopcr.max_iterations = 30; - stopcr.stop_score = 1e-20; - opt::TOptimizer solver(stopcr); - solver.optimize_min(ofn, opt::initvals(0.5), - opt::bound(0.0, 1.0)); - - // optimize - config_.object_function = prev_func; - } + // TODO: implement + return {}; } struct Optimum { @@ -798,6 +677,50 @@ private: Radians final_rot = initial_rot; Shapes nfps; + auto& bin = bin_; + double norm = norm_; + auto pbb = sl::boundingBox(merged_pile_); + auto binbb = sl::boundingBox(bin); + + // This is the kernel part of the object function that is + // customizable by the library client + std::function _objfunc; + if(config_.object_function) _objfunc = config_.object_function; + else { + + // Inside check has to be strict if no alignment was enabled + std::function ins_check; + if(config_.alignment == Config::Alignment::DONT_ALIGN) + ins_check = [&binbb, norm](const Box& fullbb) { + double ret = 0; + if(!sl::isInside(fullbb, binbb)) + ret += norm; + return ret; + }; + else + ins_check = [&bin](const Box& fullbb) { + double miss = overfit(fullbb, bin); + miss = miss > 0? miss : 0; + return std::pow(miss, 2); + }; + + _objfunc = [norm, binbb, pbb, ins_check](const Item& item) + { + auto ibb = item.boundingBox(); + auto fullbb = sl::boundingBox(pbb, ibb); + + double score = pl::distance(ibb.center(), + binbb.center()); + score /= norm; + + score += ins_check(fullbb); + + return score; + }; + } + + Pile merged_pile = merged_pile_; + for(auto rot : config_.rotations) { item.translation(initial_tr); @@ -822,57 +745,6 @@ private: ecache.back().accuracy(config_.accuracy); } - Shapes pile; - pile.reserve(items_.size()+1); - // double pile_area = 0; - for(Item& mitem : items_) { - pile.emplace_back(mitem.transformedShape()); - // pile_area += mitem.area(); - } - - auto merged_pile = nfp::merge(pile); - auto& bin = bin_; - double norm = norm_; - auto pbb = sl::boundingBox(merged_pile); - auto binbb = sl::boundingBox(bin); - - // This is the kernel part of the object function that is - // customizable by the library client - std::function _objfunc; - if(config_.object_function) _objfunc = config_.object_function; - else { - - // Inside check has to be strict if no alignment was enabled - std::function ins_check; - if(config_.alignment == Config::Alignment::DONT_ALIGN) - ins_check = [&binbb, norm](const Box& fullbb) { - double ret = 0; - if(!sl::isInside(fullbb, binbb)) - ret += norm; - return ret; - }; - else - ins_check = [&bin](const Box& fullbb) { - double miss = overfit(fullbb, bin); - miss = miss > 0? miss : 0; - return std::pow(miss, 2); - }; - - _objfunc = [norm, binbb, pbb, ins_check](const Item& item) - { - auto ibb = item.boundingBox(); - auto fullbb = sl::boundingBox(pbb, ibb); - - double score = pl::distance(ibb.center(), - binbb.center()); - score /= norm; - - score += ins_check(fullbb); - - return score; - }; - } - // Our object function for placement auto rawobjfunc = [_objfunc, iv, startpos] (Vertex v, Item& itm) @@ -1045,6 +917,7 @@ private: if(can_pack) { ret = PackResult(item); + merged_pile_ = nfp::merge(merged_pile_, item.transformedShape()); } else { ret = PackResult(best_overfit); } diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index e4a5c7b42f..1036844d70 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -309,7 +309,7 @@ protected: public: AutoArranger(const TBin & bin, const ArrangeParams ¶ms, - std::function progressind, + std::function progressind, std::function stopcond) : m_pck(bin, params.min_obj_distance) , m_bin(bin) @@ -348,7 +348,9 @@ public: m_pconf.object_function = get_objfn(); - if (progressind) m_pck.progressIndicator(progressind); + if (progressind) m_pck.progressIndicator([this, &progressind](unsigned rem) { + progressind(rem, m_pck.lastResult().size() - 1); + }); if (stopcond) m_pck.stopCondition(stopcond); m_pck.configure(m_pconf); @@ -462,7 +464,7 @@ void _arrange( std::vector & excludes, const BinT & bin, const ArrangeParams ¶ms, - std::function progressfn, + std::function progressfn, std::function stopfn) { // Integer ceiling the min distance from the bed perimeters diff --git a/src/libslic3r/Arrange.hpp b/src/libslic3r/Arrange.hpp index 65c3984d55..69511e7ec8 100644 --- a/src/libslic3r/Arrange.hpp +++ b/src/libslic3r/Arrange.hpp @@ -74,7 +74,7 @@ struct ArrangeParams { /// The accuracy of optimization. /// Goes from 0.0 to 1.0 and scales performance as well - float accuracy = 0.65f; + float accuracy = 1.f; /// Allow parallel execution. bool parallel = true; @@ -83,7 +83,8 @@ struct ArrangeParams { /// Progress indicator callback called when an object gets packed. /// The unsigned argument is the number of items remaining to pack. - std::function progressind; + /// Second is the current bed idx being filled. + std::function progressind; /// A predicate returning true if abort is needed. std::function stopcondition; diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 16d985e9c9..59872ad66b 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -1069,7 +1069,7 @@ Polygons variable_offset_inner(const ExPolygon &expoly, const std::vector 0.); @@ -1113,7 +1113,7 @@ for (const std::vector& ds : deltas) ClipperLib::Paths holes; holes.reserve(expoly.holes.size()); for (const Polygon& hole : expoly.holes) - append(holes, fix_after_inner_offset(mittered_offset_path_scaled(hole, deltas[1 + &hole - expoly.holes.data()], miter_limit), ClipperLib::pftPositive, true)); + append(holes, fix_after_inner_offset(mittered_offset_path_scaled(hole.points, deltas[1 + &hole - expoly.holes.data()], miter_limit), ClipperLib::pftPositive, true)); #ifndef NDEBUG for (auto &c : holes) assert(ClipperLib::Area(c) > 0.); @@ -1157,7 +1157,7 @@ for (const std::vector& ds : deltas) ClipperLib::Paths holes; holes.reserve(expoly.holes.size()); for (const Polygon& hole : expoly.holes) - append(holes, fix_after_inner_offset(mittered_offset_path_scaled(hole, deltas[1 + &hole - expoly.holes.data()], miter_limit), ClipperLib::pftPositive, true)); + append(holes, fix_after_inner_offset(mittered_offset_path_scaled(hole.points, deltas[1 + &hole - expoly.holes.data()], miter_limit), ClipperLib::pftPositive, true)); #ifndef NDEBUG for (auto &c : holes) assert(ClipperLib::Area(c) > 0.); @@ -1205,7 +1205,7 @@ ExPolygons variable_offset_inner_ex(const ExPolygon &expoly, const std::vector 0.); diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp index 5bdd5055ec..989cfd4424 100644 --- a/src/libslic3r/ExPolygon.cpp +++ b/src/libslic3r/ExPolygon.cpp @@ -350,23 +350,10 @@ void ExPolygon::get_trapezoids2(Polygons* polygons) const // find trapezoids by looping from first to next-to-last coordinate for (std::vector::const_iterator x = xx.begin(); x != xx.end()-1; ++x) { coord_t next_x = *(x + 1); - if (*x == next_x) continue; - - // build rectangle - Polygon poly; - poly.points.resize(4); - poly[0](0) = *x; - poly[0](1) = bb.min(1); - poly[1](0) = next_x; - poly[1](1) = bb.min(1); - poly[2](0) = next_x; - poly[2](1) = bb.max(1); - poly[3](0) = *x; - poly[3](1) = bb.max(1); - - // intersect with this expolygon - // append results to return value - polygons_append(*polygons, intersection(poly, to_polygons(*this))); + if (*x != next_x) + // intersect with rectangle + // append results to return value + polygons_append(*polygons, intersection({ { { *x, bb.min.y() }, { next_x, bb.min.y() }, { next_x, bb.max.y() }, { *x, bb.max.y() } } }, to_polygons(*this))); } } diff --git a/src/libslic3r/ExtrusionEntity.cpp b/src/libslic3r/ExtrusionEntity.cpp index 6516713ce6..390d107f27 100644 --- a/src/libslic3r/ExtrusionEntity.cpp +++ b/src/libslic3r/ExtrusionEntity.cpp @@ -14,12 +14,12 @@ namespace Slic3r { void ExtrusionPath::intersect_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const { - this->_inflate_collection(intersection_pl(this->polyline, (Polygons)collection), retval); + this->_inflate_collection(intersection_pl((Polylines)polyline, to_polygons(collection.expolygons)), retval); } void ExtrusionPath::subtract_expolygons(const ExPolygonCollection &collection, ExtrusionEntityCollection* retval) const { - this->_inflate_collection(diff_pl(this->polyline, (Polygons)collection), retval); + this->_inflate_collection(diff_pl((Polylines)this->polyline, to_polygons(collection.expolygons)), retval); } void ExtrusionPath::clip_end(double distance) diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 90cd5699f3..6dbfa18fae 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -37,7 +37,8 @@ struct SurfaceFillParams bool dont_adjust = false; // Length of the infill anchor along the perimeter line. // 1000mm is roughly the maximum length line that fits into a 32bit coord_t. - float anchor_length = 1000.f; + float anchor_length = 1000.f; + float anchor_length_max = 1000.f; // width, height of extrusion, nozzle diameter, is bridge // For the output, for fill generator. @@ -68,6 +69,7 @@ struct SurfaceFillParams RETURN_COMPARE_NON_EQUAL(density); RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, dont_adjust); RETURN_COMPARE_NON_EQUAL(anchor_length); + RETURN_COMPARE_NON_EQUAL(anchor_length_max); RETURN_COMPARE_NON_EQUAL(flow.width); RETURN_COMPARE_NON_EQUAL(flow.height); RETURN_COMPARE_NON_EQUAL(flow.nozzle_diameter); @@ -85,7 +87,8 @@ struct SurfaceFillParams this->angle == rhs.angle && this->density == rhs.density && this->dont_adjust == rhs.dont_adjust && - this->anchor_length == rhs.anchor_length && + this->anchor_length == rhs.anchor_length && + this->anchor_length_max == rhs.anchor_length_max && this->flow == rhs.flow && this->extrusion_role == rhs.extrusion_role; } @@ -171,8 +174,12 @@ std::vector group_fills(const Layer &layer) // Anchor a sparse infill to inner perimeters with the following anchor length: params.anchor_length = float(region_config.infill_anchor); if (region_config.infill_anchor.percent) - params.anchor_length *= 0.01 * params.spacing; + params.anchor_length = float(params.anchor_length * 0.01 * params.spacing); + params.anchor_length_max = float(region_config.infill_anchor_max); + if (region_config.infill_anchor_max.percent) + params.anchor_length_max = float(params.anchor_length_max * 0.01 * params.spacing); } + params.anchor_length = std::min(params.anchor_length, params.anchor_length_max); auto it_params = set_surface_params.find(params); if (it_params == set_surface_params.end()) @@ -376,9 +383,10 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: // apply half spacing using this flow's own spacing and generate infill FillParams params; - params.density = float(0.01 * surface_fill.params.density); - params.dont_adjust = surface_fill.params.dont_adjust; // false - params.anchor_length = surface_fill.params.anchor_length; + params.density = float(0.01 * surface_fill.params.density); + params.dont_adjust = surface_fill.params.dont_adjust; // false + params.anchor_length = surface_fill.params.anchor_length; + params.anchor_length_max = surface_fill.params.anchor_length_max; for (ExPolygon &expoly : surface_fill.expolygons) { // Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon. diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index 3b62976261..0865c36938 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -667,9 +667,26 @@ static inline rtree_segment_t mk_rtree_seg(const Line &l) { // Create a hook based on hook_line and append it to the begin or end of the polyline in the intersection static void add_hook( const Intersection &intersection, const double scaled_offset, - const int hook_length, double scaled_trim_distance, + const coordf_t hook_length, double scaled_trim_distance, const rtree_t &rtree, const Lines &lines_src) { + if (hook_length < SCALED_EPSILON) + // Ignore open hooks. + return; + +#ifndef NDEBUG + { + const Vec2d v = (intersection.closest_line->b - intersection.closest_line->a).cast(); + const Vec2d va = (intersection.intersect_point - intersection.closest_line->a).cast(); + const double l2 = v.squaredNorm(); // avoid a sqrt + assert(l2 > 0.); + const double t = va.dot(v) / l2; + assert(t > 0. && t < 1.); + const double d = (t * v - va).norm(); + assert(d < 1000.); + } +#endif // NDEBUG + // Trim the hook start by the infill line it will connect to. Point hook_start; bool intersection_found = intersection.intersect_line->intersection( @@ -700,7 +717,7 @@ static void add_hook( const std::vector> &hook_intersections, bool self_intersection, const std::optional &self_intersection_line, const Point &self_intersection_point) { // No hook is longer than hook_length, there shouldn't be any intersection closer than that. - auto max_length = double(hook_length); + auto max_length = hook_length; auto update_max_length = [&max_length](double d) { if (d < max_length) max_length = d; @@ -757,15 +774,32 @@ static void add_hook( } } -static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &boundary, const double spacing, const int hook_length) +#ifndef NDEBUG +bool validate_intersection_t_joint(const Intersection &intersection) +{ + const Vec2d v = (intersection.closest_line->b - intersection.closest_line->a).cast(); + const Vec2d va = (intersection.intersect_point - intersection.closest_line->a).cast(); + const double l2 = v.squaredNorm(); // avoid a sqrt + assert(l2 > 0.); + const double t = va.dot(v); + assert(t > SCALED_EPSILON && t < l2 - SCALED_EPSILON); + const double d = ((t / l2) * v - va).norm(); + assert(d < 1000.); + return true; +} +bool validate_intersections(const std::vector &intersections) +{ + for (const Intersection& intersection : intersections) + assert(validate_intersection_t_joint(intersection)); + return true; +} +#endif // NDEBUG + +static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &boundary, const double spacing, const coordf_t hook_length, const coordf_t hook_length_max) { rtree_t rtree; size_t poly_idx = 0; - Lines lines_src; - lines_src.reserve(lines.size()); - std::transform(lines.begin(), lines.end(), std::back_inserter(lines_src), [](const Line& l) { return Polyline{ l.a, l.b }; }); - // 19% overlap, slightly lower than the allowed overlap in Fill::connect_infill() const float scaled_offset = float(scale_(spacing) * 0.81); // 25% overlap @@ -814,16 +848,19 @@ static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &b } return std::make_pair(static_cast(nullptr), false); }; - auto collinear_front = collinear_segment(poly.points.front(), poly.points.back(), &poly); + auto collinear_front = collinear_segment(poly.points.front(), poly.points.back(), &poly); + auto collinear_back = collinear_segment(poly.points.back(), poly.points.front(), &poly); + assert(! collinear_front.first || ! collinear_back.first || collinear_front.first != collinear_back.first); if (collinear_front.first) { Polyline &other = *collinear_front.first; + assert(&other != &poly); poly.points.front() = collinear_front.second ? other.points.back() : other.points.front(); other.points.clear(); } - auto collinear_back = collinear_segment(poly.points.back(), poly.points.front(), &poly); if (collinear_back.first) { - Polyline &other = *collinear_front.first; - poly.points.back() = collinear_front.second ? other.points.back() : other.points.front(); + Polyline &other = *collinear_back.first; + assert(&other != &poly); + poly.points.back() = collinear_back.second ? other.points.back() : other.points.front(); other.points.clear(); } } @@ -831,6 +868,12 @@ static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &b } } + // Convert input polylines to lines_src after the colinear segments were merged. + Lines lines_src; + lines_src.reserve(lines.size()); + std::transform(lines.begin(), lines.end(), std::back_inserter(lines_src), [](const Polyline &pl) { + return pl.empty() ? Line(Point(0, 0), Point(0, 0)) : Line(pl.points.front(), pl.points.back()); }); + sort_remove_duplicates(lines_touching_at_endpoints); std::vector intersections; @@ -854,23 +897,38 @@ static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &b // Find the nearest line from the start point of the line. std::optional tjoint_front, tjoint_back; { - auto has_tjoint = [&closest, line_idx, &rtree, &lines](const Point &pt) { - auto filter_itself = [line_idx](const auto &item) { return item.second != line_idx; }; + auto has_tjoint = [&closest, line_idx, &rtree, &lines, &lines_src](const Point &pt) { + auto filter_t_joint = [line_idx, &lines_src, pt](const auto &item) { + if (item.second != line_idx) { + // Verify that the point projects onto the line. + const Line &line = lines_src[item.second]; + const Vec2d v = (line.b - line.a).cast(); + const Vec2d va = (pt - line.a).cast(); + const double l2 = v.squaredNorm(); // avoid a sqrt + if (l2 > 0.) { + const double t = va.dot(v); + return t > SCALED_EPSILON && t < l2 - SCALED_EPSILON; + } + } + return false; + }; closest.clear(); - rtree.query(bgi::nearest(mk_rtree_point(pt), 1) && bgi::satisfies(filter_itself), std::back_inserter(closest)); - const Polyline &pl = lines[closest.front().second]; + rtree.query(bgi::nearest(mk_rtree_point(pt), 1) && bgi::satisfies(filter_t_joint), std::back_inserter(closest)); std::optional out; - if (pl.points.empty()) { - // The closest infill line was already dropped as it was too short. - // Such an infill line should not make a T-joint anyways. -#if 0 // #ifndef NDEBUG - const auto &seg = closest.front().first; - struct Linef { Vec2d a; Vec2d b; }; - Linef l { { bg::get<0, 0>(seg), bg::get<0, 1>(seg) }, { bg::get<1, 0>(seg), bg::get<1, 1>(seg) } }; - assert(line_alg::distance_to_squared(l, Vec2d(pt.cast())) > 1000 * 1000); -#endif // NDEBUG - } else if (((Line)pl).distance_to_squared(pt) <= 1000 * 1000) - out = closest.front().second; + if (! closest.empty()) { + const Polyline &pl = lines[closest.front().second]; + if (pl.points.empty()) { + // The closest infill line was already dropped as it was too short. + // Such an infill line should not make a T-joint anyways. + #if 0 // #ifndef NDEBUG + const auto &seg = closest.front().first; + struct Linef { Vec2d a; Vec2d b; }; + Linef l { { bg::get<0, 0>(seg), bg::get<0, 1>(seg) }, { bg::get<1, 0>(seg), bg::get<1, 1>(seg) } }; + assert(line_alg::distance_to_squared(l, Vec2d(pt.cast())) > 1000 * 1000); + #endif // NDEBUG + } else if (((Line)pl).distance_to_squared(pt) <= 1000 * 1000) + out = closest.front().second; + } return out; }; // Refuse to create a T-joint if the infill lines touch at their ends. @@ -912,12 +970,16 @@ static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &b // A shorter line than spacing could produce a degenerate polyline. line.points.clear(); } else if (anchor) { - if (tjoint_front) + if (tjoint_front) { // T-joint of line's front point with the 'closest' line. intersections.emplace_back(lines_src[*tjoint_front], lines_src[line_idx], &line, front_point, true); - if (tjoint_back) + assert(validate_intersection_t_joint(intersections.back())); + } + if (tjoint_back) { // T-joint of line's back point with the 'closest' line. intersections.emplace_back(lines_src[*tjoint_back], lines_src[line_idx], &line, back_point, false); + assert(validate_intersection_t_joint(intersections.back())); + } } else { if (tjoint_front) // T joint at the front at a 60 degree angle, the line is very short. @@ -940,6 +1002,7 @@ static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &b ++ it; } } + assert(validate_intersections(intersections)); #ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT static int iRun = 0; @@ -1106,7 +1169,7 @@ static Polylines connect_lines_using_hooks(Polylines &&lines, const ExPolygon &b } Points &first_points = first_i.intersect_pl->points; Points &second_points = nearest_i.intersect_pl->points; - could_connect &= (nearest_i_point - first_i_point).cast().squaredNorm() <= Slic3r::sqr(3. * hook_length); + could_connect &= (nearest_i_point - first_i_point).cast().squaredNorm() <= Slic3r::sqr(hook_length_max); if (could_connect) { // Both intersections are so close that their polylines can be connected. // Verify that no other infill line intersects this anchor line. @@ -1219,7 +1282,7 @@ bool has_no_collinear_lines(const Polylines &polylines) const Point* operator()(const LineEnd &pt) const { return &pt.point(); } }; typedef ClosestPointInRadiusLookup ClosestPointLookupType; - ClosestPointLookupType closest_end_point_lookup(1001. * sqrt(2.)); + ClosestPointLookupType closest_end_point_lookup(coord_t(1001. * sqrt(2.))); for (const Polyline& pl : polylines) { // assert(pl.points.size() == 2); auto line_start = LineEnd(&pl, true); @@ -1321,9 +1384,10 @@ void Filler::_fill_surface_single( } #endif /* ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT */ - const auto hook_length = coord_t(std::min(scale_(this->spacing * 5), scale_(params.anchor_length))); + const auto hook_length = coordf_t(std::min(std::numeric_limits::max(), scale_(params.anchor_length))); + const auto hook_length_max = coordf_t(std::min(std::numeric_limits::max(), scale_(params.anchor_length_max))); - Polylines all_polylines_with_hooks = all_polylines.size() > 1 ? connect_lines_using_hooks(std::move(all_polylines), expolygon, this->spacing, hook_length) : std::move(all_polylines); + Polylines all_polylines_with_hooks = all_polylines.size() > 1 ? connect_lines_using_hooks(std::move(all_polylines), expolygon, this->spacing, hook_length, hook_length_max) : std::move(all_polylines); #ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT { diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index 326c1bd42c..370b2f85a5 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -200,10 +200,10 @@ struct ContourIntersectionPoint { // Could extrude a complete segment from this to this->prev_on_contour. bool could_connect_prev() const throw() - { return ! this->consumed && this->prev_on_contour && ! this->prev_on_contour->consumed && ! this->prev_trimmed && ! this->prev_on_contour->next_trimmed; } + { return ! this->consumed && this->prev_on_contour != this && ! this->prev_on_contour->consumed && ! this->prev_trimmed && ! this->prev_on_contour->next_trimmed; } // Could extrude a complete segment from this to this->next_on_contour. bool could_connect_next() const throw() - { return ! this->consumed && this->next_on_contour && ! this->next_on_contour->consumed && ! this->next_trimmed && ! this->next_on_contour->prev_trimmed; } + { return ! this->consumed && this->next_on_contour != this && ! this->next_on_contour->consumed && ! this->next_trimmed && ! this->next_on_contour->prev_trimmed; } }; // Distance from param1 to param2 when going counter-clockwise. @@ -390,7 +390,12 @@ static void take(Polyline &pl1, const Polyline &pl2, const Points &contour, size static void take(Polyline &pl1, const Polyline &pl2, const Points &contour, ContourIntersectionPoint *cp_start, ContourIntersectionPoint *cp_end, bool clockwise) { + assert(cp_start->prev_on_contour != nullptr); + assert(cp_start->next_on_contour != nullptr); + assert(cp_end ->prev_on_contour != nullptr); + assert(cp_end ->next_on_contour != nullptr); assert(cp_start != cp_end); + take(pl1, pl2, contour, cp_start->point_idx, cp_end->point_idx, clockwise); // Mark the contour segments in between cp_start and cp_end as consumed. @@ -410,7 +415,12 @@ static void take_limited( ContourIntersectionPoint *cp_start, ContourIntersectionPoint *cp_end, bool clockwise, float take_max_length, float line_half_width) { #ifndef NDEBUG - assert(cp_start != cp_end); + // This is a valid case, where a single infill line connect to two different contours (outer contour + hole or two holes). +// assert(cp_start != cp_end); + assert(cp_start->prev_on_contour != nullptr); + assert(cp_start->next_on_contour != nullptr); + assert(cp_end ->prev_on_contour != nullptr); + assert(cp_end ->next_on_contour != nullptr); assert(pl1.size() >= 2); assert(contour.size() + 1 == params.size()); #endif /* NDEBUG */ @@ -438,8 +448,18 @@ static void take_limited( float length = params.back(); float length_to_go = take_max_length; cp_start->consumed = true; - if (clockwise) { + if (cp_start == cp_end) { + length_to_go = std::max(0.f, std::min(length_to_go, length - line_half_width)); + length_to_go = std::min(length_to_go, clockwise ? cp_start->contour_not_taken_length_prev : cp_start->contour_not_taken_length_next); + cp_start->consume_prev(); + cp_start->consume_next(); + if (length_to_go > SCALED_EPSILON) + clockwise ? + take_cw_limited (pl1, contour, params, cp_start->point_idx, cp_start->point_idx, length_to_go) : + take_ccw_limited(pl1, contour, params, cp_start->point_idx, cp_start->point_idx, length_to_go); + } else if (clockwise) { // Going clockwise from cp_start to cp_end. + assert(cp_start != cp_end); for (ContourIntersectionPoint *cp = cp_start; cp != cp_end; cp = cp->prev_on_contour) { // Length of the segment from cp to cp->prev_on_contour. float l = closed_contour_distance_cw(cp->param, cp->prev_on_contour->param, length); @@ -461,6 +481,7 @@ static void take_limited( } } } else { + assert(cp_start != cp_end); for (ContourIntersectionPoint *cp = cp_start; cp != cp_end; cp = cp->next_on_contour) { float l = closed_contour_distance_ccw(cp->param, cp->next_on_contour->param, length); length_to_go = std::min(length_to_go, cp->contour_not_taken_length_next); @@ -869,6 +890,10 @@ void mark_boundary_segments_touching_infill( for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++ it_contour_and_segment) { // End points of the line segment and their vector. auto segment = this->grid.segment(*it_contour_and_segment); + std::vector &intersections = boundary_intersections[it_contour_and_segment->first]; + if (intersections.empty()) + // There is no infil line touching this contour, thus effort will be saved to calculate overlap with other infill lines. + continue; const Vec2d seg_pt1 = segment.first.cast(); const Vec2d seg_pt2 = segment.second.cast(); std::pair interval; @@ -892,20 +917,23 @@ void mark_boundary_segments_touching_infill( const float param_overlap1 = param_seg_pt1 + interval.first; const float param_overlap2 = param_seg_pt1 + interval.second; // 2) Find the ContourIntersectionPoints before param_overlap1 and after param_overlap2. - std::vector &intersections = boundary_intersections[it_contour_and_segment->first]; // Find the span of ContourIntersectionPoints, that is trimmed by the interval (param_overlap1, param_overlap2). ContourIntersectionPoint *ip_low, *ip_high; - { + if (intersections.size() == 1) { + // Only a single infill line touches this contour. + ip_low = ip_high = intersections.front(); + } else { + assert(intersections.size() > 1); auto it_low = Slic3r::lower_bound_by_predicate(intersections.begin(), intersections.end(), [param_overlap1](const ContourIntersectionPoint *l) { return l->param < param_overlap1; }); auto it_high = Slic3r::lower_bound_by_predicate(intersections.begin(), intersections.end(), [param_overlap2](const ContourIntersectionPoint *l) { return l->param < param_overlap2; }); ip_low = it_low == intersections.end() ? intersections.front() : *it_low; ip_high = it_high == intersections.end() ? intersections.front() : *it_high; if (ip_low->param != param_overlap1) ip_low = ip_low->prev_on_contour; + assert(ip_low != ip_high); + // Verify that the interval (param_overlap1, param_overlap2) is inside the interval (ip_low->param, ip_high->param). + assert(cyclic_interval_inside_interval(ip_low->param, ip_high->param, param_overlap1, param_overlap2, contour_length)); } - assert(ip_low != ip_high); - // Verify that the interval (param_overlap1, param_overlap2) is inside the interval (ip_low->param, ip_high->param). - assert(cyclic_interval_inside_interval(ip_low->param, ip_high->param, param_overlap1, param_overlap2, contour_length)); assert(validate_boundary_intersections(boundary_intersections)); // Mark all ContourIntersectionPoints between ip_low and ip_high as consumed. if (ip_low->next_on_contour != ip_high) @@ -1068,8 +1096,11 @@ void Fill::connect_infill(Polylines &&infill_ordered, const Polygons &boundary_s void Fill::connect_infill(Polylines &&infill_ordered, const std::vector &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms) { assert(! infill_ordered.empty()); - assert(params.anchor_length >= 0.01f); - const auto anchor_length = float(scale_(params.anchor_length)); + assert(params.anchor_length >= 0.f); + assert(params.anchor_length_max >= 0.01f); + assert(params.anchor_length_max >= params.anchor_length); + const auto anchor_length = float(scale_(params.anchor_length)); + const auto anchor_length_max = float(scale_(params.anchor_length_max)); #if 0 append(polylines_out, infill_ordered); @@ -1097,7 +1128,7 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vectornext_on_contour = pfirst; pfirst->prev_on_contour = pprev; } @@ -1170,10 +1201,15 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vectorparam = contour_params[ip->point_idx]; // and measure distance to the previous and next intersection point. const float contour_length = contour_params.back(); - for (ContourIntersectionPoint *ip : contour_intersection_points) { - ip->contour_not_taken_length_prev = closed_contour_distance_ccw(ip->prev_on_contour->param, ip->param, contour_length); - ip->contour_not_taken_length_next = closed_contour_distance_ccw(ip->param, ip->next_on_contour->param, contour_length); - } + for (ContourIntersectionPoint *ip : contour_intersection_points) + if (ip->next_on_contour == ip) { + assert(ip->prev_on_contour == ip); + ip->contour_not_taken_length_prev = ip->contour_not_taken_length_next = contour_length; + } else { + assert(ip->prev_on_contour != ip); + ip->contour_not_taken_length_prev = closed_contour_distance_ccw(ip->prev_on_contour->param, ip->param, contour_length); + ip->contour_not_taken_length_next = closed_contour_distance_ccw(ip->param, ip->next_on_contour->param, contour_length); + } } assert(boundary.size() == boundary_src.size()); @@ -1277,7 +1313,7 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vectorcontour_idx], cp1, cp2, connection_cost.reversed); @@ -1299,10 +1335,11 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector arches; arches.reserve(map_infill_end_point_to_boundary.size()); for (ContourIntersectionPoint &cp : map_infill_end_point_to_boundary) - if (! cp.contour_idx != boundary_idx_unconnected && cp.next_on_contour != &cp && cp.could_connect_next()) + if (cp.contour_idx != boundary_idx_unconnected && cp.next_on_contour != &cp && cp.could_connect_next()) arches.push_back({ &cp, path_length_along_contour_ccw(&cp, cp.next_on_contour, boundary_params[cp.contour_idx].back()) }); std::sort(arches.begin(), arches.end(), [](const auto &l, const auto &r) { return l.arc_length < r.arc_length; }); + //FIXME improve the Traveling Salesman problem with 2-opt and 3-opt local optimization. for (Arc &arc : arches) if (! arc.intersection->consumed && ! arc.intersection->next_on_contour->consumed) { // Indices of the polylines to be connected by a perimeter segment. @@ -1315,7 +1352,7 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vectorpoint_idx] == polyline1.points.front() || contour[cp1->point_idx] == polyline1.points.back()); if (contour[cp1->point_idx] == polyline1.points.front()) @@ -1333,7 +1370,7 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector SCALED_EPSILON) { // Move along the perimeter, but don't take the whole arc. take_limited(polyline1, contour, contour_params, cp1, cp2, false, anchor_length, line_half_width); take_limited(polyline2, contour, contour_params, cp2, cp1, true, anchor_length, line_half_width); @@ -1360,7 +1397,7 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector::max() || l > anchor_length * 2.5) + if (l == std::numeric_limits::max() || l > anchor_length_max) break; // Take the complete contour. bool reversed = l == lprev; @@ -1392,7 +1429,7 @@ void Fill::connect_infill(Polylines &&infill_ordered, const std::vector SCALED_EPSILON) { // Which to take? One could optimize for: // 1) Shortest path // 2) Hook length diff --git a/src/libslic3r/Fill/FillBase.hpp b/src/libslic3r/Fill/FillBase.hpp index 4ebad7a8d9..c09b70bca0 100644 --- a/src/libslic3r/Fill/FillBase.hpp +++ b/src/libslic3r/Fill/FillBase.hpp @@ -34,14 +34,15 @@ struct FillParams { bool full_infill() const { return density > 0.9999f; } // Don't connect the fill lines around the inner perimeter. - bool dont_connect() const { return anchor_length < 0.05f; } + bool dont_connect() const { return anchor_length_max < 0.05f; } // Fill density, fraction in <0, 1> float density { 0.f }; // Length of an infill anchor along the perimeter. // 1000mm is roughly the maximum length line that fits into a 32bit coord_t. - float anchor_length { 1000.f }; + float anchor_length { 1000.f }; + float anchor_length_max { 1000.f }; // Don't adjust spacing to fill the space evenly. bool dont_adjust { true }; diff --git a/src/libslic3r/Fill/FillConcentric.cpp b/src/libslic3r/Fill/FillConcentric.cpp index 5870381783..87bddeb610 100644 --- a/src/libslic3r/Fill/FillConcentric.cpp +++ b/src/libslic3r/Fill/FillConcentric.cpp @@ -39,7 +39,7 @@ void FillConcentric::_fill_surface_single( size_t iPathFirst = polylines_out.size(); Point last_pos(0, 0); for (const Polygon &loop : loops) { - polylines_out.push_back(loop.split_at_index(last_pos.nearest_point_index(loop))); + polylines_out.push_back(loop.split_at_index(last_pos.nearest_point_index(loop.points))); last_pos = polylines_out.back().last_point(); } diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp index 230b04de5d..fcea7e14e0 100644 --- a/src/libslic3r/ModelArrange.cpp +++ b/src/libslic3r/ModelArrange.cpp @@ -49,7 +49,7 @@ Slic3r::arrangement::ArrangePolygon get_arrange_poly(const Model &model) std::copy(pts.begin(), pts.end(), std::back_inserter(apts)); } - apts = Geometry::convex_hull(apts); + apts = std::move(Geometry::convex_hull(apts).points); return ap; } diff --git a/src/libslic3r/MotionPlanner.cpp b/src/libslic3r/MotionPlanner.cpp index ae50df8f49..a8ce590862 100644 --- a/src/libslic3r/MotionPlanner.cpp +++ b/src/libslic3r/MotionPlanner.cpp @@ -264,7 +264,7 @@ Point MotionPlannerEnv::nearest_env_point(const Point &from, const Point &to) co for (const ExPolygon &ex : m_env.expolygons) { for (const Polygon &hole : ex.holes) if (hole.contains(from)) - pp = hole; + pp = hole.points; if (! pp.empty()) break; } diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp index 9ff91b5022..fa6dbabb9d 100644 --- a/src/libslic3r/MultiPoint.hpp +++ b/src/libslic3r/MultiPoint.hpp @@ -17,8 +17,6 @@ class MultiPoint public: Points points; - operator Points() const { return this->points; } - MultiPoint() {} MultiPoint(const MultiPoint &other) : points(other.points) {} MultiPoint(MultiPoint &&other) : points(std::move(other.points)) {} diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 3cd91dafe4..e6261ebd1b 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -158,7 +158,7 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime // get non-overhang paths by intersecting this loop with the grown lower slices extrusion_paths_append( paths, - intersection_pl(loop.polygon, perimeter_generator.lower_slices_polygons()), + intersection_pl((Polygons)loop.polygon, perimeter_generator.lower_slices_polygons()), role, is_external ? perimeter_generator.ext_mm3_per_mm() : perimeter_generator.mm3_per_mm(), is_external ? perimeter_generator.ext_perimeter_flow.width : perimeter_generator.perimeter_flow.width, @@ -169,7 +169,7 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime // the loop centerline and original lower slices is >= half nozzle diameter extrusion_paths_append( paths, - diff_pl(loop.polygon, perimeter_generator.lower_slices_polygons()), + diff_pl((Polygons)loop.polygon, perimeter_generator.lower_slices_polygons()), erOverhangPerimeter, perimeter_generator.mm3_per_mm_overhang(), perimeter_generator.overhang_flow.width, diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index 8479a0bd69..b550ae7d7a 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -16,8 +16,8 @@ typedef std::vector Polygons; class Polygon : public MultiPoint { public: - operator Polygons() const { Polygons pp; pp.push_back(*this); return pp; } - operator Polyline() const { return this->split_at_first_point(); } + explicit operator Polygons() const { Polygons pp; pp.push_back(*this); return pp; } + explicit operator Polyline() const { return this->split_at_first_point(); } Point& operator[](Points::size_type idx) { return this->points[idx]; } const Point& operator[](Points::size_type idx) const { return this->points[idx]; } diff --git a/src/libslic3r/Polyline.cpp b/src/libslic3r/Polyline.cpp index d24788c7bc..a6be642994 100644 --- a/src/libslic3r/Polyline.cpp +++ b/src/libslic3r/Polyline.cpp @@ -200,7 +200,7 @@ BoundingBox get_extents(const Polylines &polylines) if (! polylines.empty()) { bb = polylines.front().bounding_box(); for (size_t i = 1; i < polylines.size(); ++ i) - bb.merge(polylines[i]); + bb.merge(polylines[i].points); } return bb; } diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index 2a53a13f9f..ef1da9afb9 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -60,8 +60,8 @@ public: } } - operator Polylines() const; - operator Line() const; + explicit operator Polylines() const; + explicit operator Line() const; const Point& last_point() const override { return this->points.back(); } const Point& leftmost_point() const; diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 0b13745e13..c7e0c50409 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -427,7 +427,7 @@ const std::vector& Preset::print_options() "infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder", "ooze_prevention", "standby_temperature_delta", "interface_shells", "extrusion_width", "first_layer_extrusion_width", "perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width", - "top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "infill_anchor", "bridge_flow_ratio", "clip_multipart_objects", + "top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "infill_anchor", "infill_anchor_max", "bridge_flow_ratio", "clip_multipart_objects", "elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_bridging", "single_extruder_multi_material_priming", "wipe_tower_no_sparse_layers", "compatible_printers", "compatible_printers_condition", "inherits" diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 4e4c90fb2f..a8d4b2ea05 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1220,9 +1220,9 @@ static inline bool sequential_print_horizontal_clearance_valid(const Print &prin // instance.shift is a position of a centered object, while model object may not be centered. // Conver the shift from the PrintObject's coordinates into ModelObject's coordinates by removing the centering offset. convex_hull.translate(instance.shift - print_object->center_offset()); - if (! intersection(convex_hulls_other, convex_hull).empty()) + if (! intersection(convex_hulls_other, (Polygons)convex_hull).empty()) return false; - polygons_append(convex_hulls_other, convex_hull); + convex_hulls_other.emplace_back(std::move(convex_hull)); } } return true; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 629838a0b7..97b62bf56f 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1064,11 +1064,15 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionInt(1)); - def = this->add("infill_anchor", coFloatOrPercent); + auto def_infill_anchor_min = def = this->add("infill_anchor", coFloatOrPercent); def->label = L("Length of the infill anchor"); def->category = L("Advanced"); def->tooltip = L("Connect an infill line to an internal perimeter with a short segment of an additional perimeter. " - "If expressed as percentage (example: 15%) it is calculated over infill extrusion width."); + "If expressed as percentage (example: 15%) it is calculated over infill extrusion width. " + "PrusaSlicer tries to connect two close infill lines to a short perimeter segment. If no such perimeter segment " + "shorter than infill_anchor_max is found, the infill line is connected to a perimeter segment at just one side " + "and the length of the perimeter segment taken is limited to this parameter, but no longer than anchor_length_max. " + "Set this parameter to zero to disable anchoring perimeters connected to a single infill line."); def->sidetext = L("mm or %"); def->ratio_over = "infill_extrusion_width"; def->gui_type = "f_enum_open"; @@ -1078,15 +1082,36 @@ void PrintConfigDef::init_fff_params() def->enum_values.push_back("5"); def->enum_values.push_back("10"); def->enum_values.push_back("1000"); - def->enum_labels.push_back(L("0 (not anchored)")); + def->enum_labels.push_back(L("0 (no open anchors)")); def->enum_labels.push_back("1 mm"); def->enum_labels.push_back("2 mm"); def->enum_labels.push_back("5 mm"); def->enum_labels.push_back("10 mm"); def->enum_labels.push_back(L("1000 (unlimited)")); def->mode = comAdvanced; -// def->set_default_value(new ConfigOptionFloatOrPercent(300, true)); - def->set_default_value(new ConfigOptionFloatOrPercent(1000, false)); + def->set_default_value(new ConfigOptionFloatOrPercent(600, true)); + + def = this->add("infill_anchor_max", coFloatOrPercent); + def->label = L("Maximum length of the infill anchor"); + def->category = def_infill_anchor_min->category; + def->tooltip = L("Connect an infill line to an internal perimeter with a short segment of an additional perimeter. " + "If expressed as percentage (example: 15%) it is calculated over infill extrusion width. " + "PrusaSlicer tries to connect two close infill lines to a short perimeter segment. If no such perimeter segment " + "shorter than this parameter is found, the infill line is connected to a perimeter segment at just one side " + "and the length of the perimeter segment taken is limited to infill_anchor, but no longer than this parameter. " + "Set this parameter to zero to disable anchoring."); + def->sidetext = def_infill_anchor_min->sidetext; + def->ratio_over = def_infill_anchor_min->ratio_over; + def->gui_type = def_infill_anchor_min->gui_type; + def->enum_values = def_infill_anchor_min->enum_values; + def->enum_labels.push_back(L("0 (not anchored)")); + def->enum_labels.push_back("1 mm"); + def->enum_labels.push_back("2 mm"); + def->enum_labels.push_back("5 mm"); + def->enum_labels.push_back("10 mm"); + def->enum_labels.push_back(L("1000 (unlimited)")); + def->mode = def_infill_anchor_min->mode; + def->set_default_value(new ConfigOptionFloatOrPercent(50, false)); def = this->add("infill_extruder", coInt); def->label = L("Infill extruder"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 5ca04a341f..aa7b159d03 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -532,6 +532,7 @@ public: ConfigOptionEnum fill_pattern; ConfigOptionFloat gap_fill_speed; ConfigOptionFloatOrPercent infill_anchor; + ConfigOptionFloatOrPercent infill_anchor_max; ConfigOptionInt infill_extruder; ConfigOptionFloatOrPercent infill_extrusion_width; ConfigOptionInt infill_every_layers; @@ -584,6 +585,7 @@ protected: OPT_PTR(fill_pattern); OPT_PTR(gap_fill_speed); OPT_PTR(infill_anchor); + OPT_PTR(infill_anchor_max); OPT_PTR(infill_extruder); OPT_PTR(infill_extrusion_width); OPT_PTR(infill_every_layers); diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 3937f4fede..c70542a261 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -591,6 +591,7 @@ bool PrintObject::invalidate_state_by_config_options(const std::vectorcontour : it_contact_expoly->holes[i_contour - 1]; const Point *seg_current_pt = nullptr; coordf_t seg_current_t = 0.; - if (! intersection_pl(contour.split_at_first_point(), overhang_with_margin).empty()) { + if (! intersection_pl((Polylines)contour.split_at_first_point(), overhang_with_margin).empty()) { // The contour is below the overhang at least to some extent. //FIXME ideally one would place the circles below the overhang only. // Walk around the contour and place circles so their centers are not closer than circle_distance from each other. diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index c69746afe7..738fdd7c07 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -1,9 +1,9 @@ #ifndef _prusaslicer_technologies_h_ #define _prusaslicer_technologies_h_ -//============ +//============= // debug techs -//============ +//============= // Shows camera target in the 3D scene #define ENABLE_SHOW_CAMERA_TARGET 0 @@ -23,20 +23,24 @@ #define DISABLE_INSTANCES_SYNCH 0 // Use wxDataViewRender instead of wxDataViewCustomRenderer #define ENABLE_NONCUSTOM_DATA_VIEW_RENDERING 0 +// Enable G-Code viewer statistics imgui dialog +#define ENABLE_GCODE_VIEWER_STATISTICS 0 +// Enable G-Code viewer comparison between toolpaths height and width detected from gcode and calculated at gcode generation +#define ENABLE_GCODE_VIEWER_DATA_CHECKING 0 -//================ +//================= // 2.2.0.rc1 techs -//================ +//================= #define ENABLE_2_2_0_RC1 1 // Enable hack to remove crash when closing on OSX 10.9.5 #define ENABLE_HACK_CLOSING_ON_OSX_10_9_5 (1 && ENABLE_2_2_0_RC1) -//=================== +//==================== // 2.3.0.alpha1 techs -//=================== +//==================== #define ENABLE_2_3_0_ALPHA1 1 // Enable rendering of objects using environment map @@ -51,27 +55,22 @@ // Enable built-in DPI changed event handler of wxWidgets 3.1.3 #define ENABLE_WX_3_1_3_DPI_CHANGED_EVENT (1 && ENABLE_2_3_0_ALPHA1) -// Enable G-Code viewer -#define ENABLE_GCODE_VIEWER (1 && ENABLE_2_3_0_ALPHA1) -#define ENABLE_GCODE_VIEWER_STATISTICS (0 && ENABLE_GCODE_VIEWER) -#define ENABLE_GCODE_VIEWER_DATA_CHECKING (0 && ENABLE_GCODE_VIEWER) - -//=================== +//==================== // 2.3.0.alpha3 techs -//=================== +//==================== #define ENABLE_2_3_0_ALPHA3 1 #define ENABLE_CTRL_M_ON_WINDOWS (0 && ENABLE_2_3_0_ALPHA3) -//=================== +//==================== // 2.3.0.alpha4 techs -//=================== +//==================== #define ENABLE_2_3_0_ALPHA4 1 -#define ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS (1 && ENABLE_GCODE_VIEWER && ENABLE_2_3_0_ALPHA4) -#define ENABLE_SHOW_OPTION_POINT_LAYERS (1 && ENABLE_GCODE_VIEWER && ENABLE_2_3_0_ALPHA4) +#define ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS (1 && ENABLE_2_3_0_ALPHA4) +#define ENABLE_SHOW_OPTION_POINT_LAYERS (1 && ENABLE_2_3_0_ALPHA4) //=================== @@ -79,7 +78,8 @@ //=================== #define ENABLE_2_3_0_BETA1 1 -#define ENABLE_SHOW_WIPE_MOVES (1 && ENABLE_GCODE_VIEWER && ENABLE_2_3_0_BETA1) +#define ENABLE_SHOW_WIPE_MOVES (1 && ENABLE_2_3_0_BETA1) #define ENABLE_DRAG_AND_DROP_FIX (1 && ENABLE_2_3_0_BETA1) + #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/GUI/2DBed.cpp b/src/slic3r/GUI/2DBed.cpp index debd104ef6..ea6720356f 100644 --- a/src/slic3r/GUI/2DBed.cpp +++ b/src/slic3r/GUI/2DBed.cpp @@ -87,7 +87,7 @@ void Bed_2D::repaint(const std::vector& shape) for (auto y = bb.min(1) - fmod(bb.min(1), step) + step; y < bb.max(1); y += step) { polylines.push_back(Polyline::new_scale({ Vec2d(bb.min(0), y), Vec2d(bb.max(0), y) })); } - polylines = intersection_pl(polylines, bed_polygon); + polylines = intersection_pl(polylines, (Polygons)bed_polygon); dc.SetPen(wxPen(wxColour(230, 230, 230), 1, wxPENSTYLE_SOLID)); for (auto pl : polylines) diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 845dc1c0bd..899a013690 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -237,8 +237,11 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) bool have_infill = config->option("fill_density")->value > 0; // infill_extruder uses the same logic as in Print::extruders() for (auto el : { "fill_pattern", "infill_every_layers", "infill_only_where_needed", - "solid_infill_every_layers", "solid_infill_below_area", "infill_extruder" }) + "solid_infill_every_layers", "solid_infill_below_area", "infill_extruder", "infill_anchor_max" }) toggle_field(el, have_infill); + // Only allow configuration of open anchors if the anchoring is enabled. + bool has_infill_anchors = have_infill && config->option("infill_anchor_max")->value > 0; + toggle_field("infill_anchor", has_infill_anchors); bool has_spiral_vase = config->opt_bool("spiral_vase"); bool has_top_solid_infill = config->opt_int("top_solid_layers") > 0; diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index eaf75c2543..f4838e7b88 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -569,7 +569,7 @@ void Control::draw_tick_on_mouse_position(wxDC& dc) if (m_extra_style & wxSL_VALUE_LABEL) { wxColour old_clr = dc.GetTextForeground(); - dc.SetTextForeground(LIGHT_GREY_PEN.GetColour()); + dc.SetTextForeground(GREY_PEN.GetColour()); draw_tick_text(dc, pos, tick, ltEstimatedTime, false); dc.SetTextForeground(old_clr); } @@ -893,6 +893,11 @@ void Control::Ruler::update(wxWindow* win, const std::vector& values, do int DPI = GUI::get_dpi_for_window(win); int pixels_per_sm = lround((double)(DPI) * 5.0/25.4); + if (lround(scroll_step) > pixels_per_sm) { + long_step = -1.0; + return; + } + int pow = -2; int step = 0; auto end_it = count == 1 ? values.end() : values.begin() + lround(values.size() / count); @@ -907,15 +912,15 @@ void Control::Ruler::update(wxWindow* win, const std::vector& values, do break; int tick = val_it - values.begin(); - if (lround(tick * scroll_step) > pixels_per_sm) { - step = istep; + // find next tick with istep + val *= 2; + val_it = std::lower_bound(values.begin(), end_it, val - epsilon()); + // count of short ticks between ticks + int short_ticks_cnt = val_it == values.end() ? tick : val_it - values.begin() - tick; - // find next tick with istep - val *= 2; - val_it = std::lower_bound(values.begin(), end_it, val - epsilon()); - // count of short ticks between ticks - int short_ticks_cnt = val_it == values.end() ? tick : val_it - values.begin() - tick; - // there couldn't be more then 10 short ticks between thicks + if (lround(short_ticks_cnt * scroll_step) > pixels_per_sm) { + step = istep; + // there couldn't be more then 10 short ticks between ticks short_step = 0.1 * short_ticks_cnt; break; } @@ -931,71 +936,77 @@ void Control::Ruler::update(wxWindow* win, const std::vector& values, do void Control::draw_ruler(wxDC& dc) { m_ruler.update(this->GetParent(), m_values, get_scroll_step()); - if (!m_ruler.is_ok()) - return; int height, width; get_size(&width, &height); - const wxCoord mid = is_horizontal() ? 0.5 * height : 0.5 * width; + const wxCoord mid = is_horizontal() ? 0.5 * height : 0.5 * width; - auto draw_short_ticks = [this, mid](wxDC& dc, double& current_tick, int max_tick) { - while (current_tick < max_tick) { - wxCoord pos = get_position_from_value(lround(current_tick)); - draw_ticks_pair(dc, pos, mid, 2); - current_tick += m_ruler.short_step; - if (current_tick > m_max_value) - break; - } - }; - - dc.SetPen(LIGHT_GREY_PEN); + dc.SetPen(GREY_PEN); wxColour old_clr = dc.GetTextForeground(); - dc.SetTextForeground(LIGHT_GREY_PEN.GetColour()); + dc.SetTextForeground(GREY_PEN.GetColour()); - double short_tick; - int tick = 0; - double value = 0.0; - int sequence = 0; - - while (tick <= m_max_value) { - value += m_ruler.long_step; - if (value > m_values.back() && sequence < m_ruler.count) { - value = m_ruler.long_step; - for (tick; tick < m_values.size(); tick++) - if (m_values[tick] < value) - break; - // short ticks from the last tick to the end of current sequence - draw_short_ticks(dc, short_tick, tick); - sequence++; + if (m_ruler.long_step < 0) + for (int tick = 1; tick < m_values.size(); tick++) { + wxCoord pos = get_position_from_value(tick); + draw_ticks_pair(dc, pos, mid, 5); + draw_tick_text(dc, wxPoint(mid, pos), tick); } - short_tick = tick; + else { + auto draw_short_ticks = [this, mid](wxDC& dc, double& current_tick, int max_tick) { + while (current_tick < max_tick) { + wxCoord pos = get_position_from_value(lround(current_tick)); + draw_ticks_pair(dc, pos, mid, 2); + current_tick += m_ruler.short_step; + if (current_tick > m_max_value) + break; + } + }; - for (tick; tick < m_values.size(); tick++) { - if (m_values[tick] == value) - break; - if (m_values[tick] > value) { - if (tick > 0) - tick--; + double short_tick; + int tick = 0; + double value = 0.0; + int sequence = 0; + + while (tick <= m_max_value) { + value += m_ruler.long_step; + if (value > m_values.back() && sequence < m_ruler.count) { + value = m_ruler.long_step; + for (tick; tick < m_values.size(); tick++) + if (m_values[tick] < value) + break; + // short ticks from the last tick to the end of current sequence + draw_short_ticks(dc, short_tick, tick); + sequence++; + } + short_tick = tick; + + for (tick; tick < m_values.size(); tick++) { + if (m_values[tick] == value) + break; + if (m_values[tick] > value) { + if (tick > 0) + tick--; + break; + } + } + if (tick > m_max_value) break; + + wxCoord pos = get_position_from_value(tick); + draw_ticks_pair(dc, pos, mid, 5); + draw_tick_text(dc, wxPoint(mid, pos), tick); + + draw_short_ticks(dc, short_tick, tick); + + if (value == m_values.back() && sequence < m_ruler.count) { + value = 0.0; + sequence++; + tick++; } } - if (tick > m_max_value) - break; - - wxCoord pos = get_position_from_value(tick); - draw_ticks_pair(dc, pos, mid, 5); - draw_tick_text(dc, wxPoint(mid, pos), tick); - - draw_short_ticks(dc, short_tick, tick); - - if (value == m_values.back() && sequence < m_ruler.count) { - value = 0.0; - sequence++; - tick++; - } + // short ticks from the last tick to the end + draw_short_ticks(dc, short_tick, m_max_value); } - // short ticks from the last tick to the end - draw_short_ticks(dc, short_tick, m_max_value); dc.SetTextForeground(old_clr); } diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index a92a988444..a0176ad4a4 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -862,7 +862,19 @@ void Choice::BUILD() { #endif // temp->Bind(wxEVT_TEXT, ([this](wxCommandEvent e) { on_change_field(); }), temp->GetId()); - temp->Bind(wxEVT_COMBOBOX, ([this](wxCommandEvent e) { on_change_field(); }), temp->GetId()); + temp->Bind(wxEVT_COMBOBOX_DROPDOWN, [this](wxCommandEvent&) { m_is_dropped = true; }); + temp->Bind(wxEVT_COMBOBOX_CLOSEUP, [this](wxCommandEvent&) { m_is_dropped = false; }); + + temp->Bind(wxEVT_COMBOBOX, ([this, temp](wxCommandEvent evt) { + if (m_suppress_scroll) { + if (!m_is_dropped) { + temp->SetSelection(m_last_selected); + return; + } + m_last_selected = evt.GetSelection(); + } + on_change_field(); + }), temp->GetId()); if (m_is_editable) { temp->Bind(wxEVT_KILL_FOCUS, ([this](wxEvent& e) { @@ -876,8 +888,7 @@ void Choice::BUILD() { if (is_defined_input_value(window, m_opt.type)) { if (fabs(old_val - boost::any_cast(get_value())) <= 0.0001) return; - else - on_change_field(); + on_change_field(); } else on_kill_focus(); @@ -887,6 +898,13 @@ void Choice::BUILD() { temp->SetToolTip(get_tooltip_text(temp->GetValue())); } +void Choice::suppress_scroll() +{ + m_suppress_scroll = true; + choice_ctrl* ctrl = dynamic_cast(window); + m_last_selected = ctrl->GetSelection(); +} + void Choice::set_selection() { /* To prevent earlier control updating under OSX set m_disable_change_event to true @@ -901,6 +919,7 @@ void Choice::set_selection() case coEnum:{ int id_value = m_opt.get_default_value>()->value; //!! field->SetSelection(id_value); + if (m_suppress_scroll) m_last_selected = id_value; break; } case coFloat: @@ -934,6 +953,8 @@ void Choice::set_selection() ++idx; } idx == m_opt.enum_values.size() ? field->SetValue(text_value) : field->SetSelection(idx); + + if (m_suppress_scroll && idx < m_opt.enum_values.size()) m_last_selected = idx; } } @@ -953,6 +974,7 @@ void Choice::set_value(const std::string& value, bool change_event) //! Redunda idx == m_opt.enum_values.size() ? field->SetValue(value) : field->SetSelection(idx); + if (m_suppress_scroll && idx < m_opt.enum_values.size()) m_last_selected = idx; m_disable_change_event = false; } @@ -990,6 +1012,7 @@ void Choice::set_value(const boost::any& value, bool change_event) } else field->SetSelection(idx); + if (m_suppress_scroll && idx < m_opt.enum_values.size()) m_last_selected = idx; break; } case coEnum: { @@ -1020,6 +1043,7 @@ void Choice::set_value(const boost::any& value, bool change_event) val = 0; } field->SetSelection(val); + if (m_suppress_scroll) m_last_selected = val; break; } default: @@ -1179,6 +1203,7 @@ void Choice::msw_rescale() idx == m_opt.enum_values.size() ? field->SetValue(selection) : field->SetSelection(idx); + if (m_suppress_scroll && idx < m_opt.enum_values.size()) m_last_selected = idx; #else auto size = wxSize(def_width_wider() * m_em_unit, wxDefaultCoord); if (m_opt.height >= 0) size.SetHeight(m_opt.height * m_em_unit); diff --git a/src/slic3r/GUI/Field.hpp b/src/slic3r/GUI/Field.hpp index b4bcf9f33b..6cadb607d1 100644 --- a/src/slic3r/GUI/Field.hpp +++ b/src/slic3r/GUI/Field.hpp @@ -385,7 +385,10 @@ public: /* Under OSX: wxBitmapComboBox->GetWindowStyle() returns some weard value, * so let use a flag, which has TRUE value for a control without wxCB_READONLY style */ - bool m_is_editable { false }; + bool m_is_editable { false }; + bool m_is_dropped { false }; + bool m_suppress_scroll { false }; + int m_last_selected { wxNOT_FOUND }; void set_selection(); void set_value(const std::string& value, bool change_event = false); @@ -399,6 +402,8 @@ public: void enable() override ;//{ dynamic_cast(window)->Enable(); }; void disable() override;//{ dynamic_cast(window)->Disable(); }; wxWindow* getWindow() override { return window; } + + void suppress_scroll(); }; class ColourPicker : public Field { diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 2ae852dbd0..e55c5fa8c6 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3892,7 +3892,7 @@ bool GLCanvas3D::_render_arrange_menu(float pos_x) settings_changed = true; } - if (imgui->checkbox(_(L("Enable rotations")), settings.enable_rotation)) { + if (imgui->checkbox(_(L("Enable rotations (slow)")), settings.enable_rotation)) { m_arrange_settings.enable_rotation = settings.enable_rotation; settings_changed = true; } diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp index 7a8c213651..7af0b47037 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.cpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp @@ -158,14 +158,14 @@ void ArrangeJob::process() params.stopcondition = [this]() { return was_canceled(); }; try { - params.progressind = [this, count](unsigned st) { + params.progressind = [this, count](unsigned st, unsigned) { st += m_unprintable.size(); if (st > 0) update_status(int(count - st), arrangestr); }; arrangement::arrange(m_selected, m_unselected, bedpts, params); - params.progressind = [this, count](unsigned st) { + params.progressind = [this, count](unsigned st, unsigned) { if (st > 0) update_status(int(count - st), arrangestr); }; diff --git a/src/slic3r/GUI/Jobs/FillBedJob.cpp b/src/slic3r/GUI/Jobs/FillBedJob.cpp index 0605280060..9c959ff166 100644 --- a/src/slic3r/GUI/Jobs/FillBedJob.cpp +++ b/src/slic3r/GUI/Jobs/FillBedJob.cpp @@ -90,9 +90,13 @@ void FillBedJob::process() params.min_obj_distance = scaled(settings.distance); params.allow_rotations = settings.enable_rotation; - params.stopcondition = [this]() { return was_canceled(); }; + unsigned curr_bed = 0; + params.stopcondition = [this, &curr_bed]() { + return was_canceled() || curr_bed > 0; + }; - params.progressind = [this](unsigned st) { + params.progressind = [this, &curr_bed](unsigned st, unsigned bed) { + curr_bed = bed; if (st > 0) update_status(int(m_status_range - st), _(L("Filling bed"))); }; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 5df1e5e179..11c2448b70 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -439,6 +439,9 @@ FreqChangedParams::FreqChangedParams(wxWindow* parent) : m_og->activate(); + Choice* choice = dynamic_cast(m_og->get_field("support")); + choice->suppress_scroll(); + // Frequently changed parameters for SLA_technology m_og_sla = std::make_shared(parent, ""); m_og_sla->hide_labels(); diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 95e86ff7e0..578c9d3973 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -256,8 +256,8 @@ void PreferencesDialog::build() create_settings_mode_widget(); } - if (is_editor) { #if ENABLE_ENVIRONMENT_MAP + if (is_editor) { m_optgroup_render = std::make_shared(this, _L("Render")); m_optgroup_render->label_width = 40; m_optgroup_render->m_on_change = [this](t_config_option_key opt_key, boost::any value) { @@ -272,8 +272,8 @@ void PreferencesDialog::build() m_optgroup_render->append_single_option_line(option); m_optgroup_render->activate(); -#endif // ENABLE_ENVIRONMENT_MAP } +#endif // ENABLE_ENVIRONMENT_MAP auto sizer = new wxBoxSizer(wxVERTICAL); sizer->Add(m_optgroup_general->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10); @@ -293,13 +293,13 @@ void PreferencesDialog::build() SetSizer(sizer); sizer->SetSizeHints(this); + this->CenterOnParent(); } void PreferencesDialog::accept() { - if (m_values.find("no_defaults") != m_values.end()) { + if (m_values.find("no_defaults") != m_values.end()) warning_catcher(this, wxString::Format(_L("You need to restart %s to make the changes effective."), SLIC3R_APP_NAME)); - } auto app_config = get_app_config(); @@ -308,9 +308,9 @@ void PreferencesDialog::accept() m_seq_top_layer_only_changed = app_config->get("seq_top_layer_only") != it->second; m_settings_layout_changed = false; - for (const std::string& key : {"old_settings_layout_mode", - "new_settings_layout_mode", - "dlg_settings_layout_mode" }) + for (const std::string& key : { "old_settings_layout_mode", + "new_settings_layout_mode", + "dlg_settings_layout_mode" }) { auto it = m_values.find(key); if (it != m_values.end() && app_config->get(key) != it->second) { @@ -319,8 +319,7 @@ void PreferencesDialog::accept() } } - for (const std::string& key : {"default_action_on_close_application", "default_action_on_select_preset"}) - { + for (const std::string& key : {"default_action_on_close_application", "default_action_on_select_preset"}) { auto it = m_values.find(key); if (it != m_values.end() && it->second != "none" && app_config->get(key) != "none") m_values.erase(it); // we shouldn't change value, if some of those parameters was selected, and then deselected @@ -421,9 +420,9 @@ void PreferencesDialog::create_icon_size_slider() void PreferencesDialog::create_settings_mode_widget() { - wxString choices[] = { _L("Old regular layout with the tab bar"), - _L("New layout, access via settings button in the top menu"), - _L("Settings in non-modal window") }; + wxString choices[] = { _L("Old regular layout with the tab bar"), + _L("New layout, access via settings button in the top menu"), + _L("Settings in non-modal window") }; auto app_config = get_app_config(); int selection = app_config->get("old_settings_layout_mode") == "1" ? 0 : @@ -432,14 +431,13 @@ void PreferencesDialog::create_settings_mode_widget() wxWindow* parent = m_optgroup_gui->ctrl_parent(); - m_layout_mode_box = new wxRadioBox(parent, wxID_ANY, _L("Layout Options"), wxDefaultPosition, wxDefaultSize, WXSIZEOF(choices), choices, - 3, wxRA_SPECIFY_ROWS); + m_layout_mode_box = new wxRadioBox(parent, wxID_ANY, _L("Layout Options"), wxDefaultPosition, wxDefaultSize, + WXSIZEOF(choices), choices, 3, wxRA_SPECIFY_ROWS); m_layout_mode_box->SetFont(wxGetApp().normal_font()); m_layout_mode_box->SetSelection(selection); m_layout_mode_box->Bind(wxEVT_RADIOBOX, [this](wxCommandEvent& e) { int selection = e.GetSelection(); - m_values["old_settings_layout_mode"] = boost::any_cast(selection == 0) ? "1" : "0"; m_values["new_settings_layout_mode"] = boost::any_cast(selection == 1) ? "1" : "0"; m_values["dlg_settings_layout_mode"] = boost::any_cast(selection == 2) ? "1" : "0"; @@ -447,7 +445,6 @@ void PreferencesDialog::create_settings_mode_widget() auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(m_layout_mode_box, 1, wxALIGN_CENTER_VERTICAL); - m_optgroup_gui->sizer->Add(sizer, 0, wxEXPAND); } diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 5acf71feff..78559b9375 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -103,6 +103,8 @@ PresetComboBox::PresetComboBox(wxWindow* parent, Preset::Type preset_type, const // parameters for an icon's drawing fill_width_height(); + Bind(wxEVT_COMBOBOX_DROPDOWN, [this](wxCommandEvent& evt) { m_suppress_change = false; }); + Bind(wxEVT_COMBOBOX_CLOSEUP, [this](wxCommandEvent& evt) { m_suppress_change = true ; }); Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) { // see https://github.com/prusa3d/PrusaSlicer/issues/3889 @@ -147,6 +149,15 @@ bool PresetComboBox::set_printer_technology(PrinterTechnology pt) return false; } +bool PresetComboBox::check_event_for_suppress_change(wxCommandEvent& evt) +{ + if (m_suppress_change) { + evt.StopPropagation(); + SetSelection(m_last_selected); + } + return m_suppress_change; +} + void PresetComboBox::invalidate_selection() { m_last_selected = INT_MAX; // this value means that no one item is selected @@ -534,6 +545,8 @@ PlaterPresetComboBox::PlaterPresetComboBox(wxWindow *parent, Preset::Type preset PresetComboBox(parent, preset_type, wxSize(15 * wxGetApp().em_unit(), -1)) { Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &evt) { + if (check_event_for_suppress_change(evt)) + return; auto selected_item = evt.GetSelection(); auto marker = reinterpret_cast(this->GetClientData(selected_item)); @@ -871,6 +884,8 @@ TabPresetComboBox::TabPresetComboBox(wxWindow* parent, Preset::Type preset_type) PresetComboBox(parent, preset_type, wxSize(35 * wxGetApp().em_unit(), -1)) { Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) { + if (check_event_for_suppress_change(evt)) + return; // see https://github.com/prusa3d/PrusaSlicer/issues/3889 // Under OSX: in case of use of a same names written in different case (like "ENDER" and "Ender") // m_presets_choice->GetSelection() will return first item, because search in PopupListCtrl is case-insensitive. diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index 2967ff2633..0bd4f036f6 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -86,6 +86,7 @@ protected: int m_last_selected; int m_em_unit; + bool m_suppress_change { true }; // parameters for an icon's drawing int icon_height; @@ -98,6 +99,7 @@ protected: PrinterTechnology printer_technology {ptAny}; + bool check_event_for_suppress_change(wxCommandEvent& evt); void invalidate_selection(); void validate_selection(bool predicate = false); void update_selection(); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 940b4eeb2e..469a48527d 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1423,6 +1423,7 @@ void TabPrint::build() optgroup->append_single_option_line("fill_density", category_path + "fill-density"); optgroup->append_single_option_line("fill_pattern", category_path + "fill-pattern"); optgroup->append_single_option_line("infill_anchor", category_path + "fill-pattern"); + optgroup->append_single_option_line("infill_anchor_max", category_path + "fill-pattern"); optgroup->append_single_option_line("top_fill_pattern", category_path + "top-fill-pattern"); optgroup->append_single_option_line("bottom_fill_pattern", category_path + "bottom-fill-pattern");