diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 0b6984e7a..ee992cdf1 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -3289,10 +3289,11 @@ void GCode::use(const ExtrusionEntityCollection &collection) { std::string GCode::extrude_path(const ExtrusionPath &path, const std::string &description, double speed) { ExtrusionPath simplifed_path = path; - if (this->config().min_length.value != 0 && !m_last_too_small.empty()) { + const double scaled_min_length = scale_(this->config().min_length.value); + if (scaled_min_length > 0 && !m_last_too_small.empty()) { //descr += " trys fusion " + std::to_string(unscaled(m_last_too_small.last_point().x())) + " , " + std::to_string(unscaled(path.first_point().x())); //ensure that it's a continous thing - if (m_last_too_small.first_point().distance_to_square(path.first_point()) < scale_(this->config().min_length) /*&& m_last_too_small.first_point().distance_to_square(path.first_point()) > EPSILON*/) { + if (m_last_too_small.last_point().distance_to_square(path.first_point()) < scaled_min_length*scaled_min_length /*&& m_last_too_small.first_point().distance_to_square(path.first_point()) > EPSILON*/) { //descr += " ! fusion " + std::to_string(simplifed_path.polyline.points.size()); simplifed_path.height = (m_last_too_small.height * m_last_too_small.length() + simplifed_path.height * simplifed_path.length()) / (m_last_too_small.length() + simplifed_path.length()); simplifed_path.mm3_per_mm = (m_last_too_small.mm3_per_mm * m_last_too_small.length() + simplifed_path.mm3_per_mm * simplifed_path.length()) / (m_last_too_small.length() + simplifed_path.length()); @@ -3302,24 +3303,20 @@ std::string GCode::extrude_path(const ExtrusionPath &path, const std::string &de } m_last_too_small.polyline.points.clear(); } - if (this->config().min_length.value > 0) { - simplifed_path.simplify(scale_(this->config().min_length)); + if (scaled_min_length > 0) { + // it's an alternative to simplifed_path.simplify(scale_(this->config().min_length)); with more enphasis ont he segment length that on the feature detail. + // because tolerance = min_length /10, douglas_peucker will erase more points if angles are shallower than 6° and then the '_plus' will kick in to keep a bit more. + // if angles are all bigger than 6°, then the douglas_peucker will do all the work. + simplifed_path.polyline.points = MultiPoint::_douglas_peucker_plus(simplifed_path.polyline.points, scaled_min_length / 10, scaled_min_length); } //else simplifed_path.simplify(SCALED_RESOLUTION); //should already be simplified - if (this->config().min_length.value != 0 && simplifed_path.length() < scale_(this->config().min_length)) { + if (scaled_min_length > 0 && simplifed_path.length() < scaled_min_length) { m_last_too_small = simplifed_path; return ""; - //"; "+ descr+" .... too small for extrude: "+std::to_string(simplifed_path.length())+" < "+ std::to_string(scale_(this->config().min_length)) - //+ " ; " + std::to_string(unscaled(path.first_point().x())) + " : " + std::to_string(unscaled(path.last_point().x())) - //+" =;=> " + std::to_string(unscaled(simplifed_path.first_point().x())) + " : " + std::to_string(unscaled(simplifed_path.last_point().x())) - //+ "\n"; } std::string gcode = this->_extrude(simplifed_path, description, speed); - //gcode += " ; " + std::to_string(unscaled(path.first_point().x())) + " : " + std::to_string(unscaled(path.last_point().x())); - //gcode += " =;=> " + std::to_string(unscaled(simplifed_path.first_point().x())) + " : " + std::to_string(unscaled(simplifed_path.last_point().x())); - if (m_wipe.enable) { m_wipe.path = std::move(simplifed_path.polyline); m_wipe.path.reverse(); diff --git a/src/libslic3r/MultiPoint.cpp b/src/libslic3r/MultiPoint.cpp index da9d305a8..7fe6d5ece 100644 --- a/src/libslic3r/MultiPoint.cpp +++ b/src/libslic3r/MultiPoint.cpp @@ -249,6 +249,163 @@ std::vector MultiPoint::_douglas_peucker(const std::vector& pts, c return result_pts; } +/// +/// douglas_peucker will keep only points that are more than 'tolerance' out of the current polygon. +/// But when we want to ensure we don't have a segment less than min_length, it's not very usable. +/// This one is more effective: it will keep all points like the douglas_peucker, and also all points +/// in-between that satisfies the min_length, ordered by their tolerance. +/// Note: to have a all 360 points of a circle, then you need 'tolerance <= min_length * (1-cos(1°)) ~= min_length * 0.000155' +/// Note: douglas_peucker is bad for simplifying circles, as it will create uneven segments. +/// +/// +/// +/// +/// +std::vector MultiPoint::_douglas_peucker_plus(const std::vector& pts, const double tolerance, const double min_length) +{ + std::vector result_pts; + std::vector result_idx; + double tolerance_sq = tolerance * tolerance; + if (!pts.empty()) { + const Point* anchor = &pts.front(); + size_t anchor_idx = 0; + const Point* floater = &pts.back(); + size_t floater_idx = pts.size() - 1; + result_pts.reserve(pts.size()); + result_pts.emplace_back(*anchor); + result_idx.reserve(pts.size()); + result_idx.emplace_back(anchor_idx); + if (anchor_idx != floater_idx) { + assert(pts.size() > 1); + std::vector dpStack; + dpStack.reserve(pts.size()); + dpStack.emplace_back(floater_idx); + for (;;) { + double max_dist_sq = 0.0; + size_t furthest_idx = anchor_idx; + // find point furthest from line seg created by (anchor, floater) and note it + for (size_t i = anchor_idx + 1; i < floater_idx; ++i) { + double dist_sq = Line::distance_to_squared(pts[i], *anchor, *floater); + if (dist_sq > max_dist_sq) { + max_dist_sq = dist_sq; + furthest_idx = i; + } + } + // remove point if less than tolerance + if (max_dist_sq <= tolerance_sq) { + result_pts.emplace_back(*floater); + result_idx.emplace_back(floater_idx); + anchor_idx = floater_idx; + anchor = floater; + assert(dpStack.back() == floater_idx); + dpStack.pop_back(); + if (dpStack.empty()) + break; + floater_idx = dpStack.back(); + } else { + floater_idx = furthest_idx; + dpStack.emplace_back(floater_idx); + } + floater = &pts[floater_idx]; + } + } + assert(result_pts.front() == pts.front()); + assert(result_pts.back() == pts.back()); + + //TODO use linked list if needed. + // add other points that are at not less than min_length dist of the other points. + std::vector distances; + for (size_t segment_idx = 0; segment_idx < result_idx.size()-1; segment_idx++) { + distances.clear(); + size_t start_idx = result_idx[segment_idx]; + size_t end_idx = result_idx[segment_idx + 1]; + if (end_idx - start_idx == 1) continue; + //create the list of distances + double sum = 0; + for (size_t i = start_idx; i < end_idx; i++) { + double dist = pts[i].distance_to(pts[i + 1]); + distances.push_back(dist); + sum += dist; + } + if (sum < min_length * 2) continue; + //if there are too many points and dist, then choose a more difficult sections of ~min_length * 2-4, where we will at least one + if (sum > min_length * 4) { + //check what is the last index possible + double current_sum = 0; + size_t last_possible_idx = end_idx; + while (current_sum < min_length * 2) { + last_possible_idx--; + current_sum += distances[last_possible_idx - start_idx]; + } + + //find the new end point + current_sum = 0; + size_t current_idx = start_idx; + while (current_sum < min_length * 4 && current_idx < last_possible_idx){ + current_sum += distances[current_idx - start_idx]; + current_idx ++; + } + + // last check, to see if the points are well distributed enough. + if (current_sum > min_length * 2 && current_idx > start_idx + 1) { + //set new end + sum = current_sum; + end_idx = current_idx; + result_idx.insert(result_idx.begin() + segment_idx + 1, end_idx); + result_pts.insert(result_pts.begin() + segment_idx + 1, pts[end_idx]); + } + } + + Point* start_point = &result_pts[segment_idx]; + Point* end_point = &result_pts[segment_idx + 1]; + + //use at least a point, even if it's not in the middle and sum ~= min_length * 2 + double max_dist_sq = 0.0; + size_t furthest_idx = start_idx + 1; + // find point furthest from line seg created by (anchor, floater) and note it + for (size_t i = start_idx + 1; i < end_idx; ++i) { + double dist_sq = Line::distance_to_squared(pts[i], *start_point, *end_point); + if (dist_sq > max_dist_sq) { + max_dist_sq = dist_sq; + furthest_idx = i; + } + } + + //add this point and skip it + result_idx.insert(result_idx.begin() + segment_idx + 1, furthest_idx); + result_pts.insert(result_pts.begin() + segment_idx + 1, pts[furthest_idx]); + segment_idx++; + } + + +#if 0 + { + static int iRun = 0; + BoundingBox bbox(pts); + BoundingBox bbox2(result_pts); + bbox.merge(bbox2); + //SVG svg(debug_out_path("douglas_peucker_%d.svg", iRun ++).c_str(), bbox); + + std::stringstream stri; + stri << "douglas_peucker_" << (iRun++) << ".svg"; + SVG svg(stri.str()); + if (pts.front() == pts.back()) + svg.draw(Polygon(pts), "black"); + else + svg.draw(Polyline(pts), "black"); + if (result_pts.front() == result_pts.back()) + svg.draw(Polygon(result_pts), "green"); + else + svg.draw(Polyline(result_pts), "green", scale_(0.1)); + svg.Close(); + } +#endif + } + return result_pts; +} + + + // Visivalingam simplification algorithm https://github.com/slic3r/Slic3r/pull/3825 // thanks to @fuchstraumer /* diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp index cb723eb20..d810d5f39 100644 --- a/src/libslic3r/MultiPoint.hpp +++ b/src/libslic3r/MultiPoint.hpp @@ -82,8 +82,9 @@ public: bool intersections(const Line &line, Points *intersections) const; // Projection of a point onto the lines defined by the points. virtual Point point_projection(const Point &point) const; - - static Points _douglas_peucker(const Points &points, const double tolerance); + + static Points _douglas_peucker(const Points& points, const double tolerance); + static Points _douglas_peucker_plus(const Points& points, const double tolerance, const double min_length); static Points visivalingam(const Points& pts, const double& tolerance); }; diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index b65ed1c47..abf223733 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -125,12 +125,13 @@ bool Print::invalidate_state_by_config_options(const std::vectorcategory = OptionCategory::slicing; def->tooltip = L("Minimum detail resolution, used to simplify the input file for speeding up " "the slicing job and reducing memory usage. High-resolution models often carry " - "more detail than printers can render. Set to zero to disable any simplification " + "more details than printers can render. Set to zero to disable any simplification " "and use full resolution from input. " - "\nNote: " SLIC3R_APP_NAME " has an internal resolution of 0.000001mm." + "\nNote: " SLIC3R_APP_NAME " has an internal working resolution of 0.0001mm." "\nInfill & Thin areas are simplified up to 0.0125mm."); def->sidetext = L("mm"); def->min = 0; def->mode = comExpert; - def->set_default_value(new ConfigOptionFloat(0.0125)); + def->set_default_value(new ConfigOptionFloat(0.002)); def = this->add("retract_before_travel", coFloats); def->label = L("Minimum travel after retraction");