From 75775bc909973b5cb7dd8a962ff1197e3501f6ab Mon Sep 17 00:00:00 2001 From: supermerill Date: Thu, 7 Feb 2019 10:59:10 +0100 Subject: [PATCH] Thin_wall / medial axis: - reworked thin_variable_width (discretization into segments of constant width) - bugfix taper_ends - add setting thin_walls_overlap to control the perimeter/thin wall overlap --- src/libslic3r/Layer.cpp | 1 + src/libslic3r/MedialAxis.cpp | 124 +++++++++++++++------------ src/libslic3r/MedialAxis.hpp | 2 + src/libslic3r/PerimeterGenerator.cpp | 15 ++-- src/libslic3r/Point.cpp | 13 +++ src/libslic3r/Point.hpp | 1 + src/libslic3r/PrintConfig.cpp | 11 ++- src/libslic3r/PrintConfig.hpp | 2 + src/libslic3r/PrintObject.cpp | 1 + src/slic3r/GUI/Preset.cpp | 1 + src/slic3r/GUI/Tab.cpp | 5 +- 11 files changed, 111 insertions(+), 65 deletions(-) diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index 40f3d9988..42fc9166b 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -111,6 +111,7 @@ void Layer::make_perimeters() && config.serialize("perimeter_extrusion_width").compare(other_config.serialize("perimeter_extrusion_width")) == 0 && config.thin_walls == other_config.thin_walls && config.thin_walls_min_width == other_config.thin_walls_min_width + && config.thin_walls_overlap == other_config.thin_walls_overlap && config.external_perimeters_first == other_config.external_perimeters_first && config.perimeter_loop == other_config.perimeter_loop) { layerms.push_back(other_layerm); diff --git a/src/libslic3r/MedialAxis.cpp b/src/libslic3r/MedialAxis.cpp index 077a94d15..753a7d00a 100644 --- a/src/libslic3r/MedialAxis.cpp +++ b/src/libslic3r/MedialAxis.cpp @@ -343,13 +343,8 @@ add_point_same_percent(ThickPolyline* pattern, ThickPolyline* to_modify) double percent_dist = (percent_length - percent_length_other_before) / (percent_length_other - percent_length_other_before); coordf_t new_width = to_modify->width[idx_other - 1] * (1 - percent_dist); new_width += to_modify->width[idx_other] * (percent_dist); - Point new_point; - new_point.x() = (coord_t)((double)(to_modify->points[idx_other - 1].x()) * (1 - percent_dist)); - new_point.x() += (coord_t)((double)(to_modify->points[idx_other].x()) * (percent_dist)); - new_point.y() = (coord_t)((double)(to_modify->points[idx_other - 1].y()) * (1 - percent_dist)); - new_point.y() += (coord_t)((double)(to_modify->points[idx_other].y()) * (percent_dist)); to_modify->width.insert(to_modify->width.begin() + idx_other, new_width); - to_modify->points.insert(to_modify->points.begin() + idx_other, new_point); + to_modify->points.insert(to_modify->points.begin() + idx_other, to_modify->points[idx_other - 1].interpolate(percent_dist, to_modify->points[idx_other])); } } } @@ -1049,14 +1044,11 @@ MedialAxis::remove_too_thin_extrusion(ThickPolylines& pp) while (polyline.points.size() > 1 && polyline.width.front() < this->min_width && polyline.endpoints.first) { //try to split if possible if (polyline.width[1] > min_width) { - double percent_can_keep = 1 - (min_width - polyline.width[0]) / (polyline.width[1] - polyline.width[0]); - if (polyline.points.front().distance_to(polyline.points[1]) * percent_can_keep > SCALED_RESOLUTION) { + double percent_can_keep = (min_width - polyline.width[0]) / (polyline.width[1] - polyline.width[0]); + if (polyline.points.front().distance_to(polyline.points[1]) * (1-percent_can_keep) > SCALED_RESOLUTION) { //Can split => move the first point and assign a new weight. //the update of endpoints wil be performed in concatThickPolylines - polyline.points.front().x() = polyline.points.front().x() + - (coord_t)((polyline.points[1].x() - polyline.points.front().x()) * (1 - percent_can_keep)); - polyline.points.front().y() = polyline.points.front().y() + - (coord_t)((polyline.points[1].y() - polyline.points.front().y()) * (1 - percent_can_keep)); + polyline.points.front() = polyline.points.front().interpolate(percent_can_keep, polyline.points[1]); polyline.width.front() = min_width; } else { /// almost 0-length, Remove @@ -1073,14 +1065,11 @@ MedialAxis::remove_too_thin_extrusion(ThickPolylines& pp) while (polyline.points.size() > 1 && polyline.width.back() < this->min_width && polyline.endpoints.second) { //try to split if possible if (polyline.width[polyline.points.size() - 2] > min_width) { - double percent_can_keep = 1 - (min_width - polyline.width.back()) / (polyline.width[polyline.points.size() - 2] - polyline.width.back()); - if (polyline.points.back().distance_to(polyline.points[polyline.points.size() - 2]) * percent_can_keep > SCALED_RESOLUTION) { + double percent_can_keep = (min_width - polyline.width.back()) / (polyline.width[polyline.points.size() - 2] - polyline.width.back()); + if (polyline.points.back().distance_to(polyline.points[polyline.points.size() - 2]) * (1 - percent_can_keep) > SCALED_RESOLUTION) { //Can split => move the first point and assign a new weight. //the update of endpoints wil be performed in concatThickPolylines - polyline.points.back().x() = polyline.points.back().x() + - (coord_t)((polyline.points[polyline.points.size() - 2].x() - polyline.points.back().x()) * (1 - percent_can_keep)); - polyline.points.back().y() = polyline.points.back().y() + - (coord_t)((polyline.points[polyline.points.size() - 2].y() - polyline.points.back().y()) * (1 - percent_can_keep)); + polyline.points.back() = polyline.points.back().interpolate(percent_can_keep, polyline.points[polyline.points.size() - 2]); polyline.width.back() = min_width; } else { /// almost 0-length, Remove @@ -1108,6 +1097,7 @@ void MedialAxis::concatenate_polylines_with_crossing(ThickPolylines& pp) { + // concatenate, but even where multiple thickpolyline join, to create nice long strait polylines /* If we removed any short polylines we now try to connect consecutive polylines in order to allow loop detection. Note that this algorithm is greedier than @@ -1155,7 +1145,11 @@ MedialAxis::concatenate_polylines_with_crossing(ThickPolylines& pp) best_dot = other_dot; } } - if (best_candidate != nullptr) { + if (best_candidate != nullptr && best_candidate->size() > 1) { + //intersections may create ever-ertusion because the included circle can be a bit larger. We have to make it short again. + if (polyline.width.back() > polyline.width[polyline.width.size() - 2] && polyline.width.back() > best_candidate->width[1]) { + polyline.width.back() = std::min(polyline.width[polyline.width.size() - 2], best_candidate->width[1]); + } polyline.points.insert(polyline.points.end(), best_candidate->points.begin() + 1, best_candidate->points.end()); polyline.width.insert(polyline.width.end(), best_candidate->width.begin() + 1, best_candidate->width.end()); polyline.endpoints.second = best_candidate->endpoints.second; @@ -1455,7 +1449,6 @@ MedialAxis::build(ThickPolylines* polylines_out) { // svg.draw(pp); // svg.Close(); //} - concatenate_polylines_with_crossing(pp); //{ // stringstream stri; @@ -1518,26 +1511,49 @@ MedialAxis::grow_to_nozzle_diameter(ThickPolylines& pp, const ExPolygons& anchor void MedialAxis::taper_ends(ThickPolylines& pp) { + const coord_t min_size = this->nozzle_diameter * 0.1; + const coordf_t length = std::min(this->anchor_size, (this->nozzle_diameter - min_size) / 2); + if (length <= SCALED_RESOLUTION) return; //ensure the width is not lower than 0.4. for (ThickPolyline& polyline : pp) { - if (polyline.length() < nozzle_diameter * 2) continue; + if (polyline.length() < length * 2.2) continue; if (polyline.endpoints.first) { - polyline.width[0] = min_width; - coord_t current_dist = min_width; + polyline.width[0] = min_size; + coord_t current_dist = min_size; + coord_t last_dist = min_size; for (size_t i = 1; i polyline.width[i]) break; - polyline.width[i] = current_dist; + if (current_dist > length) { + //create new point if not near enough + if (current_dist > polyline.width[i] + SCALED_RESOLUTION) { + coordf_t percent_dist = (polyline.width[i] - polyline.width[i - 1]) / (current_dist - last_dist); + polyline.points.insert(polyline.points.begin() + i, polyline.points[i - 1].interpolate(percent_dist, polyline.points[i])); + polyline.width.insert(polyline.width.begin() + i, polyline.width[i]); + } + break; + } + polyline.width[i] = std::max((coordf_t)min_size, min_size + (polyline.width[i] - min_size) * current_dist / length); + last_dist = current_dist; } } if (polyline.endpoints.second) { - size_t last_idx = polyline.width.size() - 1; - polyline.width[last_idx] = min_width; - coord_t current_dist = min_width; + const size_t back_idx = polyline.width.size() - 1; + polyline.width[back_idx] = min_size; + coord_t current_dist = min_size; + coord_t last_dist = min_size; for (size_t i = 1; i polyline.width[last_idx - i]) break; - polyline.width[last_idx - i] = current_dist; + current_dist += (coord_t)polyline.points[back_idx - i + 1].distance_to(polyline.points[back_idx - i]); + if (current_dist > length) { + //create new point if not near enough + if (current_dist > polyline.width[back_idx - i] + SCALED_RESOLUTION) { + coordf_t percent_dist = (polyline.width[back_idx - i] - polyline.width[back_idx - i + 1]) / (current_dist - last_dist); + polyline.points.insert(polyline.points.begin() + back_idx - i + 1, polyline.points[back_idx - i + 1].interpolate(percent_dist, polyline.points[back_idx - i])); + polyline.width.insert(polyline.width.begin() + back_idx - i + 1, polyline.width[back_idx - i]); + } + break; + } + polyline.width[back_idx - i] = std::max((coordf_t)min_size, min_size + (polyline.width[back_idx - i] - min_size) * current_dist / length); + last_dist = current_dist; } } } @@ -1547,65 +1563,67 @@ ExtrusionEntityCollection thin_variable_width(const ThickPolylines &polylines, E // this value determines granularity of adaptive width, as G-code does not allow // variable extrusion within a single move; this value shall only affect the amount // of segments, and any pruning shall be performed before we apply this tolerance - const double tolerance = scale_(0.05); + const double tolerance = 4*SCALED_RESOLUTION;//scale_(0.05); - int id_line = 0; ExtrusionEntityCollection coll; for (const ThickPolyline &p : polylines) { - id_line++; ExtrusionPaths paths; ExtrusionPath path(role); ThickLines lines = p.thicklines(); for (int i = 0; i < (int)lines.size(); ++i) { - const ThickLine& line = lines[i]; + ThickLine& line = lines[i]; const coordf_t line_len = line.length(); if (line_len < SCALED_EPSILON) continue; double thickness_delta = fabs(line.a_width - line.b_width); - if (thickness_delta > tolerance) { - const uint16_t segments = (uint16_t) std::min(16000.0, ceil(thickness_delta / tolerance)); - const coordf_t seg_len = line_len / segments; + if (thickness_delta > tolerance && ceil(thickness_delta / tolerance) > 2) { + const uint16_t segments = 1+(uint16_t) std::min(16000.0, ceil(thickness_delta / tolerance)); Points pp; std::vector width; { - pp.push_back(line.a); - width.push_back(line.a_width); - for (size_t j = 1; j < segments; ++j) { - pp.push_back(line.point_at(j*seg_len)); - - coordf_t w = line.a_width + (j*seg_len) * (line.b_width - line.a_width) / line_len; - width.push_back(w); - width.push_back(w); + for (size_t j = 0; j < segments; ++j) { + pp.push_back(line.a.interpolate(((double)j) / segments, line.b)); + double percent_width = ((double)j) / (segments-1); + width.push_back(line.a_width*(1 - percent_width) + line.b_width*percent_width); } pp.push_back(line.b); - width.push_back(line.b_width); assert(pp.size() == segments + 1); - assert(width.size() == segments * 2); + assert(width.size() == segments); } // delete this line and insert new ones lines.erase(lines.begin() + i); for (size_t j = 0; j < segments; ++j) { ThickLine new_line(pp[j], pp[j + 1]); - new_line.a_width = width[2 * j]; - new_line.b_width = width[2 * j + 1]; + new_line.a_width = width[j]; + new_line.b_width = width[j]; lines.insert(lines.begin() + i + j, new_line); } + --i; + continue; + } else if (thickness_delta > 0) { + //create a middle point + ThickLine new_line(line.a.interpolate(0.5, line.b), line.b); + new_line.a_width = line.b_width; + new_line.b_width = line.b_width; + line.b = new_line.a; + line.b_width = line.a_width; + lines.insert(lines.begin() + i + 1, new_line); + --i; continue; } - const double w = std::fmax(line.a_width, line.b_width); if (path.polyline.points.empty()) { path.polyline.append(line.a); path.polyline.append(line.b); // Convert from spacing to extrusion width based on the extrusion model // of a square extrusion ended with semi circles. - flow.width = (float)unscaled(w) + flow.height * (1. - 0.25 * PI); + flow.width = (float)unscaled(line.a_width) + flow.height * (1. - 0.25 * PI); #ifdef SLIC3R_DEBUG printf(" filling %f gap\n", flow.width); #endif @@ -1613,7 +1631,7 @@ ExtrusionEntityCollection thin_variable_width(const ThickPolylines &polylines, E path.width = flow.width; path.height = flow.height; } else { - thickness_delta = fabs(scale_(flow.width) - w); + thickness_delta = fabs(flow.scaled_spacing() - line.a_width); if (thickness_delta <= tolerance / 2) { // the width difference between this line and the current flow width is // within the accepted tolerance diff --git a/src/libslic3r/MedialAxis.hpp b/src/libslic3r/MedialAxis.hpp index 9d71207b1..73023a88e 100644 --- a/src/libslic3r/MedialAxis.hpp +++ b/src/libslic3r/MedialAxis.hpp @@ -25,10 +25,12 @@ namespace Slic3r { const coord_t min_width; const coord_t height; coord_t nozzle_diameter; + coord_t anchor_size; bool stop_at_min_width = true; MedialAxis(const ExPolygon &_expolygon, const ExPolygon &_bounds, const coord_t _max_width, const coord_t _min_width, const coord_t _height) : surface(_expolygon), bounds(_bounds), max_width(_max_width), min_width(_min_width), height(_height) { nozzle_diameter = _min_width; + anchor_size = 0; }; void build(ThickPolylines* polylines_out); void build(Polylines* polylines); diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index fee1dbd7b..ef1330e5a 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -228,7 +228,7 @@ void PerimeterGenerator::process() } // Calculate next onion shell of perimeters. - //this variable stored the nexyt onion + //this variable stored the next onion ExPolygons next_onion; if (i == 0) { // compute next onion, without taking care of thin_walls : destroy too thin areas. @@ -240,23 +240,21 @@ void PerimeterGenerator::process() if (this->config->thin_walls) { // the minimum thickness of a single loop is: // ext_width/2 + ext_spacing/2 + spacing/2 + width/2 - next_onion = offset2_ex( last, -(float)(ext_perimeter_width / 2 + ext_min_spacing / 2 - 1), +(float)(ext_min_spacing / 2 - 1)); - // detect edge case where a curve can be split in multiple small chunks. ExPolygons no_thin_onion = offset_ex(last, -(float)(ext_perimeter_width / 2)); float div = 2; while (no_thin_onion.size() > 0 && next_onion.size() > no_thin_onion.size() && no_thin_onion.size() + next_onion.size() > 3) { div += 0.5; - //use a sightly smaller spacing to try to drastically improve the split + //use a sightly smaller spacing to try to drastically improve the split, but with a little bit of over-extrusion ExPolygons next_onion_secondTry = offset2_ex( last, -(float)(ext_perimeter_width / 2 + ext_min_spacing / div - 1), +(float)(ext_min_spacing / div - 1)); - if (next_onion.size() > next_onion_secondTry.size()) { + if (next_onion.size() > next_onion_secondTry.size() * 1.1) { next_onion = next_onion_secondTry; } if (div > 3) break; @@ -281,13 +279,13 @@ void PerimeterGenerator::process() no_thin_zone = diff_ex(last, offset_ex(half_thins, (float)(min_width / 2) - (float) SCALED_EPSILON), true); } // compute a bit of overlap to anchor thin walls inside the print. - ExPolygons thin_zones_extruded; for (ExPolygon &half_thin : half_thins) { //growing back the polygon ExPolygons thin = offset_ex(half_thin, (float)(min_width / 2)); assert(thin.size() == 1); + coord_t overlap = (coord_t)scale_(this->config->thin_walls_overlap.get_abs_value(this->ext_perimeter_flow.nozzle_diameter)); ExPolygons anchor = intersection_ex(offset_ex(half_thin, (float)(min_width / 2) + - (float)(ext_perimeter_width / 2), jtSquare), no_thin_zone, true); + (float)(overlap), jtSquare), no_thin_zone, true); ExPolygons bounds = union_ex(thin, anchor, true); for (ExPolygon &bound : bounds) { if (!intersection_ex(thin[0], bound).empty()) { @@ -298,14 +296,13 @@ void PerimeterGenerator::process() // the maximum thickness of our thin wall area is equal to the minimum thickness of a single loop Slic3r::MedialAxis ma(thin[0], bound, ext_perimeter_width + ext_perimeter_spacing2, min_width, this->layer_height); ma.nozzle_diameter = (coord_t)scale_(this->ext_perimeter_flow.nozzle_diameter); + ma.anchor_size = overlap; ma.build(&thin_walls); - thin_zones_extruded.emplace_back(thin[0]); } break; } } } - next_onion = diff_ex(offset_ex(last, -(float)(ext_perimeter_width / 2)), thin_zones_extruded, true); } } else { //FIXME Is this offset correct if the line width of the inner perimeters differs diff --git a/src/libslic3r/Point.cpp b/src/libslic3r/Point.cpp index 3236231b4..81a685c13 100644 --- a/src/libslic3r/Point.cpp +++ b/src/libslic3r/Point.cpp @@ -209,6 +209,19 @@ Point Point::projection_onto(const Line &line) const return ((line.a - *this).cast().squaredNorm() < (line.b - *this).cast().squaredNorm()) ? line.a : line.b; } +/// This method create a new point on the line defined by this and p2. +/// The new point is place at position defined by |p2-this| * percent, starting from this +/// \param percent the proportion of the segment length to place the point +/// \param p2 the second point, forming a segment with this +/// \return a new point, == this if percent is 0 and == p2 if percent is 1 +Point Point::interpolate(const double percent, const Point &p2) const +{ + Point p_out; + p_out.x() = this->x()*(1 - percent) + p2.x()*(percent); + p_out.y() = this->y()*(1 - percent) + p2.y()*(percent); + return p_out; +} + std::ostream& operator<<(std::ostream &stm, const Vec2d &pointf) { return stm << pointf(0) << "," << pointf(1); diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 7b85ab4e7..94c2770a6 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -121,6 +121,7 @@ public: double ccw_angle(const Point &p1, const Point &p2) const; Point projection_onto(const MultiPoint &poly) const; Point projection_onto(const Line &line) const; + Point interpolate(const double percent, const Point &p) const; double distance_to(const Point &point) const { return (point - *this).cast().norm(); } double distance_to_square(const Point &point) const { diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 915800c17..3b796b0a6 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2426,7 +2426,16 @@ void PrintConfigDef::init_fff_params() def->cli = "thin-walls-min-width=s"; def->mode = comExpert; def->min = 0; - def->default_value = new ConfigOptionFloatOrPercent(33,true); + def->default_value = new ConfigOptionFloatOrPercent(33, true); + + def = this->add("thin_walls_overlap", coFloatOrPercent); + def->label = L("overlap"); + def->category = L("Layers and Perimeters"); + def->tooltip = L("Overlap between the thin wall and the perimeters. Can be a % of the external perimeter width (default 50%)"); + def->cli = "thin-walls-overlap=s"; + def->mode = comExpert; + def->min = 0; + def->default_value = new ConfigOptionFloatOrPercent(50, true); def = this->add("threads", coInt); def->label = L("Threads"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 15ffc1f51..fec64d4e3 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -573,6 +573,7 @@ public: // Detect thin walls. ConfigOptionBool thin_walls; ConfigOptionFloatOrPercent thin_walls_min_width; + ConfigOptionFloatOrPercent thin_walls_overlap; ConfigOptionFloatOrPercent top_infill_extrusion_width; ConfigOptionInt top_solid_layers; ConfigOptionFloatOrPercent top_solid_infill_speed; @@ -629,6 +630,7 @@ protected: OPT_PTR(solid_infill_speed); OPT_PTR(thin_walls); OPT_PTR(thin_walls_min_width); + OPT_PTR(thin_walls_overlap); OPT_PTR(top_infill_extrusion_width); OPT_PTR(top_solid_infill_speed); OPT_PTR(top_solid_layers); diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index bacdd5bb9..64a571181 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -463,6 +463,7 @@ bool PrintObject::invalidate_state_by_config_options(const std::vector& Preset::print_options() , "infill_not_connected" , "first_layer_infill_speed" , "thin_walls_min_width" + , "thin_walls_overlap" }; return s_opts; } diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 03b067f48..87566640a 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -945,6 +945,7 @@ void TabPrint::build() line = { _(L("Thin walls")), "" }; line.append_option(optgroup->get_option("thin_walls")); line.append_option(optgroup->get_option("thin_walls_min_width")); + line.append_option(optgroup->get_option("thin_walls_overlap")); optgroup->append_line(line); optgroup->append_single_option_line("overhangs"); line = { _(L("Avoid unsupported perimeters")), "" }; @@ -1363,11 +1364,11 @@ void TabPrint::update() bool have_perimeters = m_config->opt_int("perimeters") > 0; for (auto el : { "extra_perimeters", "only_one_perimeter_top", "ensure_vertical_shell_thickness", "thin_walls", "overhangs", - "seam_position", "external_perimeters_first", "external_perimeter_extrusion_width", "thin_walls_min_width", + "seam_position", "external_perimeters_first", "external_perimeter_extrusion_width", "perimeter_speed", "small_perimeter_speed", "external_perimeter_speed", "perimeter_loop", "perimeter_loop_seam" }) get_field(el)->toggle(have_perimeters); - get_field("thin_walls_min_width")->toggle(m_config->opt_bool("thin_walls")); + for (auto el : { "thin_walls_min_width", "thin_walls_overlap" }) get_field(el)->toggle(m_config->opt_bool("thin_walls")); get_field("perimeter_loop_seam")->toggle(m_config->opt_bool("perimeter_loop")); bool have_no_perimeter_unsupported = have_perimeters && m_config->opt_bool("no_perimeter_unsupported");