diff --git a/.github/workflows/ccpp_mac_arm_rc.yml b/.github/workflows/ccpp_mac_arm_rc.yml index 5b68de5f80c..0fbba7b9c21 100644 --- a/.github/workflows/ccpp_mac_arm_rc.yml +++ b/.github/workflows/ccpp_mac_arm_rc.yml @@ -16,6 +16,11 @@ jobs: ref: 'rc' - name: build deps & slicer run: ./BuildMacOS.sh -ia + - name: Upload artifact + uses: actions/upload-artifact@v1.0.0 + with: + name: rc_arm_macos.dmg + path: build/${{ github.event.repository.name }}.dmg - name: Upload artifact uses: actions/upload-artifact@v1.0.0 with: diff --git a/.github/workflows/ccpp_win_rc.yml b/.github/workflows/ccpp_win_rc.yml index c94d8ec5538..ea5f0563844 100644 --- a/.github/workflows/ccpp_win_rc.yml +++ b/.github/workflows/ccpp_win_rc.yml @@ -6,8 +6,6 @@ on: - rc jobs: - build_dep: - runs-on: windows-2019 build: runs-on: windows-2019 diff --git a/resources/ui_layout/default/extruder.ui b/resources/ui_layout/default/extruder.ui index f3896c7d60e..e7db8c7e341 100644 --- a/resources/ui_layout/default/extruder.ui +++ b/resources/ui_layout/default/extruder.ui @@ -11,6 +11,7 @@ group:Offsets (for multi-extruder printers) setting:idx:extruder_offset setting:idx:extruder_temperature_offset setting:idx:extruder_fan_offset + extruder_extrusion_multiplier_speed group:Retraction setting:idx:retract_length setting:idx:retract_lift diff --git a/resources/ui_layout/default/print.ui b/resources/ui_layout/default/print.ui index 92d3863a64c..21d38c4b72a 100644 --- a/resources/ui_layout/default/print.ui +++ b/resources/ui_layout/default/print.ui @@ -2,6 +2,7 @@ page:Perimeters & Shell:shell group:Vertical shells setting:width$6:perimeters + setting:width$6:perimeters_hole setting:tags$Simple$Expert$SuSi:script:float:depends$perimeter_spacing$external_perimeter_spacing:label$Wall Thickness:tooltip$Change the perimeter extrusion widths to ensure that there is an exact number of perimeters for this wall thickness value. It won't put the perimeter width below the nozzle diameter, and up to double.\nNote that the value displayed is just a view of the current perimeter thickness, like the info text below. The number of perimeters used to compute this value is one loop, or the custom variable 'wall_thickness_lines' (advanced mode) if defined.\nIf the value is too low, it will revert the widths to the saved value.\nIf the value is set to 0, it will show 0.:s_wall_thickness setting:spiral_vase recommended_thin_wall_thickness_description @@ -134,6 +135,7 @@ group:Filtering setting:resolution_internal setting:model_precision setting:slice_closing_radius + setting:bridge_precision # gcode_resolution group:Modifying slices line:Curve smoothing @@ -159,6 +161,11 @@ group:Modifying slices setting:sidetext_width$5:hole_to_polyhole_threshold setting:hole_to_polyhole_twisted end_line + line:Overhangs cut + setting:overhangs_max_slope + setting:overhangs_bridge_threshold + setting:overhangs_bridge_upper_layers + end_line group:Other setting:slicing_mode setting:clip_multipart_objects @@ -211,7 +218,11 @@ group:sidetext_width$5:Infill angle # setting:fill_angle_template group:sidetext_width$5:Advanced setting:solid_infill_every_layers - setting:solid_infill_below_area + line:Solid infill is area below + setting:label$From region:solid_infill_below_area + setting:label$From the whole layer:solid_infill_below_layer_area + end_line + setting:solid_infill_below_width line:Anchor solid infill by X mm setting:label_width$8:width$5:external_infill_margin setting:label_width$6:width$5:bridged_infill_margin @@ -444,7 +455,10 @@ group:Overlap setting:label_width$7:label$External:external_perimeter_overlap setting:label_width$7:label$Gap Fill:gap_fill_overlap end_line - setting:width$4:solid_infill_overlap + line:Solid infill ovelrap + setting:label$Inside:width$4:solid_infill_overlap + setting:label$Top:width$4:top_solid_infill_overlap + end_line line:Bridge lines density setting:label_width$7:bridge_overlap_min setting:label_width$7:bridge_overlap diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index da87e70e831..7298244ac0c 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -476,6 +476,9 @@ void AppConfig::set_defaults() if (get("show_layer_time_doubleslider").empty()) set("show_layer_time_doubleslider", "0"); + if (get("show_layer_area_doubleslider").empty()) + set("show_layer_area_doubleslider", "0"); + } else { #ifdef _WIN32 if (get("associate_gcode").empty()) diff --git a/src/libslic3r/BridgeDetector.cpp b/src/libslic3r/BridgeDetector.cpp index 43c4bccdaa1..245de23815f 100644 --- a/src/libslic3r/BridgeDetector.cpp +++ b/src/libslic3r/BridgeDetector.cpp @@ -5,29 +5,37 @@ namespace Slic3r { -BridgeDetector::BridgeDetector( - ExPolygon _expolygon, - const ExPolygons &_lower_slices, - coord_t _spacing) : +BridgeDetector::BridgeDetector(ExPolygon _expolygon, + const ExPolygons &_lower_slices, + coord_t _extrusion_spacing, + coord_t _precision, + int layer_idx) + : // The original infill polygon, not inflated. expolygons(expolygons_owned), // All surfaces of the object supporting this region. lower_slices(_lower_slices), - spacing(_spacing) + spacing(_extrusion_spacing), + precision(_precision), + layer_id(layer_idx) { this->expolygons_owned.push_back(std::move(_expolygon)); initialize(); } -BridgeDetector::BridgeDetector( - const ExPolygons &_expolygons, - const ExPolygons &_lower_slices, - coord_t _spacing) : +BridgeDetector::BridgeDetector(const ExPolygons &_expolygons, + const ExPolygons &_lower_slices, + coord_t _extrusion_spacing, + coord_t _precision, + int layer_idx) + : // The original infill polygon, not inflated. expolygons(_expolygons), // All surfaces of the object supporting this region. lower_slices(_lower_slices), - spacing(_spacing) + spacing(_extrusion_spacing), + precision(_precision), + layer_id(layer_idx) { initialize(); } @@ -40,8 +48,22 @@ void BridgeDetector::initialize() this->angle = -1.; // Outset our bridge by an arbitrary amout; we'll use this outer margin for detecting anchors. - Polygons grown = offset(this->expolygons, float(this->spacing)); + Polygons grown = offset(this->expolygons, float(this->spacing * 0.5), ClipperLib::JoinType::jtMiter); + //remove bits that shoudln't be here, but are due to the grow + clip + //get the unsupported out-of-part section (if any) + ExPolygons union_lower_slices = union_safety_offset_ex(this->lower_slices); + ExPolygons part_areas = union_lower_slices; + append(part_areas, this->expolygons); + part_areas = union_safety_offset_ex(part_areas); + ExPolygons out_of_part = diff_ex(grown, part_areas); + // then grow it + out_of_part = offset_ex(out_of_part, float(this->spacing * 0.55), ClipperLib::JoinType::jtMiter); + // remove it from grow + grown = diff(grown, out_of_part); + //finish growing + grown = offset(grown, float(this->spacing * 0.55f), ClipperLib::JoinType::jtMiter, 6); + // Detect possible anchoring edges of this bridging region. // Detect what edges lie on lower slices by turning bridge contour and holes // into polylines and then clipping them with each lower slice's contour. @@ -58,7 +80,7 @@ void BridgeDetector::initialize() // detect anchors as intersection between our bridge expolygon and the lower slices // safety offset required to avoid Clipper from detecting empty intersection while Boost actually found some edges - this->_anchor_regions = intersection_ex(grown, union_safety_offset(this->lower_slices)); + this->_anchor_regions = intersection_ex(grown, union_lower_slices); /* if (0) { @@ -88,6 +110,16 @@ bool BridgeDetector::detect_angle(double bridge_direction_override) we'll use this one to clip our test lines and be sure that their endpoints are inside the anchors and not on their contours leading to false negatives. */ Polygons clip_area = offset(this->expolygons, 0.5f * float(this->spacing)); + // union with offseted anchor before un-offset to get the good clip area with anchor added. + ExPolygons unoffset_clip = offset_ex(this->_anchor_regions, 0.5f * float(this->spacing)); + for (Polygon &poly : clip_area) { + unoffset_clip.emplace_back(poly); + } + unoffset_clip = union_ex(unoffset_clip); + unoffset_clip = offset_ex(unoffset_clip, -0.5f * float(this->spacing)); + // now clip the clip with not-offset merged anchor + expolygons, so it's enlarged only inside the anchor. + clip_area = intersection(unoffset_clip, clip_area); + /* we'll now try several directions using a rudimentary visibility check: bridge in several directions and then sum the length of lines having both @@ -121,7 +153,7 @@ bool BridgeDetector::detect_angle(double bridge_direction_override) } //compute stat on line with anchors, and their lengths. - BridgeDirection& c = candidates[i_angle]; + BridgeDirection& bridge_dir_candidate = candidates[i_angle]; std::vector dist_anchored; { Lines clipped_lines = intersection_ln(lines, clip_area); @@ -129,90 +161,131 @@ bool BridgeDetector::detect_angle(double bridge_direction_override) // this can be called 100 000 time per detect_angle, please optimise const Line &line = clipped_lines[i]; bool good_line = false; + bool fake_bridge = false; coordf_t len = line.length(); - //is anchored? - size_t line_a_anchor_idx = -1; - size_t line_b_anchor_idx = -1; - for (int i = 0; i < _anchor_regions.size(); ++i) { - ExPolygon& poly = this->_anchor_regions[i]; - BoundingBox& polybb = anchor_bb[i]; - if (polybb.contains(line.a) && poly.contains(line.a)) { // using short-circuit evaluation to test boundingbox and only then the other - line_a_anchor_idx = i; - } - if (polybb.contains(line.b) && poly.contains(line.b)) { // using short-circuit evaluation to test boundingbox and only then the other - line_b_anchor_idx = i; - } - if (line_a_anchor_idx < clipped_lines.size() && line_b_anchor_idx < clipped_lines.size()) - break; - } - //check if the anchor search has been successful - if ( (line_a_anchor_idx < clipped_lines.size()) & (line_b_anchor_idx < clipped_lines.size())) { // this 'if' isn't very effective (culls ~ 10% on a benchy) but it's almost free to compute - good_line = true; - //test if it's not a fake bridge - if (line_a_anchor_idx == line_b_anchor_idx) { - good_line = false; - //check that the line go out of the anchor into the briding area - // don't call intersection_ln here, as even if we succeed to limit the number of candidates to ~100, here we can have hundreds of lines, so that means dozen of thousands of calls (or more)! - // add some points (at least the middle) to test, it's quick - Point middle_point = line.midpoint(); - for (int i = 0; i < _anchor_regions.size(); ++i) { - ExPolygon& poly = this->_anchor_regions[i]; - BoundingBox& polybb = anchor_bb[i]; - if (!polybb.contains(middle_point) || !poly.contains(middle_point)) { // using short-circuit evaluation to test boundingbox and only then the other - good_line = true; - break; - } + // check if the line isn't too long + if (good_line && max_bridge_length > 0 && len > max_bridge_length) { + good_line = false; + } else { + // is anchored? + size_t line_a_anchor_idx = -1; + size_t line_b_anchor_idx = -1; + for (int i = 0; i < _anchor_regions.size(); ++i) { + ExPolygon & poly = this->_anchor_regions[i]; + BoundingBox &polybb = anchor_bb[i]; + if (polybb.contains(line.a) && + poly.contains( + line.a)) { // using short-circuit evaluation to test boundingbox and only then the other + line_a_anchor_idx = i; + } + if (polybb.contains(line.b) && + poly.contains( + line.b)) { // using short-circuit evaluation to test boundingbox and only then the other + line_b_anchor_idx = i; } - // if still bad, the line is long enough to warrant two more test point? (1/2000 on a benchy) - if (!good_line && len > this->spacing * 10) { - //now test with to more points - Line middle_line; - middle_line.a = (line.a + middle_point) / 2; - middle_line.b = (line.b + middle_point) / 2; + if (line_a_anchor_idx < clipped_lines.size() && line_b_anchor_idx < clipped_lines.size()) + break; + } + // check if the anchor search has been successful + // note: this 'if' isn't very effective (culls ~ 10% on a benchy) but it's almost free to compute + if ((line_a_anchor_idx < clipped_lines.size()) & (line_b_anchor_idx < clipped_lines.size())) { + good_line = true; + // test if it's not a fake bridge + if (line_a_anchor_idx == line_b_anchor_idx) { + fake_bridge = true; + // check that the line go out of the anchor into the briding area + // don't call intersection_ln here, as even if we succeed to limit the number of + // candidates to ~100, here we can have hundreds of lines, so that means dozen of + // thousands of calls (or more)! add some points (at least the middle) to test, it's quick + Point middle_point = line.midpoint(); for (int i = 0; i < _anchor_regions.size(); ++i) { - ExPolygon& poly = this->_anchor_regions[i]; - BoundingBox& polybb = anchor_bb[i]; - if (!polybb.contains(middle_line.a) || !poly.contains(middle_line.a)) { // using short-circuit evaluation to test boundingbox and only then the other - good_line = true; - break; + ExPolygon & poly = this->_anchor_regions[i]; + BoundingBox &polybb = anchor_bb[i]; + if (!polybb.contains(middle_point) || + !poly.contains(middle_point)) { // using short-circuit evaluation to test + // boundingbox and only then the other + fake_bridge = false; + goto stop_fake_bridge_test; } - if (!polybb.contains(middle_line.b) || !poly.contains(middle_line.b)) { // using short-circuit evaluation to test boundingbox and only then the other - good_line = true; - break; + } + // try with rigth & left + if (fake_bridge && len > this->spacing * 4) { + Vector normal = line.normal(); + normal.normalize(); + normal *= coordf_t(spacing / 2); + Point middle_point_right = line.midpoint() + normal; + Point middle_point_left = line.midpoint() - normal; + for (int i = 0; i < _anchor_regions.size(); ++i) { + ExPolygon & poly = this->_anchor_regions[i]; + BoundingBox &polybb = anchor_bb[i]; + if (!poly.contains(middle_point_right) || !poly.contains(middle_point_left)) { + fake_bridge = false; + goto stop_fake_bridge_test; + } } } - } - // If the line is still bad and is a long one, use the more costly intersection_ln. This case is rare enough to swallow the cost. (1/10000 on a benchy) - if (!good_line && len > this->spacing * 40) { - //now test with intersection_ln - Lines lines = intersection_ln(line, to_polygons(this->_anchor_regions)); - good_line = lines.size() > 1; + // if still bad, the line is long enough to warrant two more test point? (1/2000 on a benchy) + if (fake_bridge && len > this->spacing * 10) { + // now test with to four more points + Vector normal = line.normal(); + normal.normalize(); + normal *= coordf_t(spacing / 2); + Points pts; + pts.push_back((line.a + middle_point) / 2 + normal); + pts.push_back((line.a + middle_point) / 2 - normal); + pts.push_back((line.b + middle_point) / 2 + normal); + pts.push_back((line.b + middle_point) / 2 - normal); + for (int i = 0; i < _anchor_regions.size(); ++i) { + ExPolygon & poly = this->_anchor_regions[i]; + BoundingBox &polybb = anchor_bb[i]; + for (Point &pt : pts) + if (!polybb.contains(pt) || + !poly.contains(pt)) { // using short-circuit evaluation to test + // boundingbox and only then the other + fake_bridge = false; + goto stop_fake_bridge_test; + } + } + } + // If the line is still bad and is a long one, use the more costly intersection_ln. This + // case is rare enough to swallow the cost. (1/10000 on a benchy) + if (fake_bridge && len > this->spacing * 40) { + // now test with intersection_ln + Lines lines = intersection_ln(line, to_polygons(this->_anchor_regions)); + // if < 2, not anchored at both end + fake_bridge = lines.size() < 2; + } } } +stop_fake_bridge_test: ; } - if(good_line) { + if (good_line && fake_bridge) + bridge_dir_candidate.nb_lines_fake_bridge++; + if (good_line) { // This line could be anchored at both side and goes over the void to bridge it in its middle. //store stats - c.total_length_anchored += len; - c.max_length_anchored = std::max(c.max_length_anchored, len); - c.nb_lines_anchored++; + if (!fake_bridge) { + bridge_dir_candidate.total_length_anchored += len; + bridge_dir_candidate.max_length_anchored = std::max(bridge_dir_candidate.max_length_anchored, len); + } + bridge_dir_candidate.nb_lines_anchored++; dist_anchored.push_back(len); } else { // this line could NOT be anchored. - c.total_length_free += len; - c.max_length_free = std::max(c.max_length_free, len); - c.nb_lines_free++; + bridge_dir_candidate.total_length_free += len; + bridge_dir_candidate.max_length_free = std::max(bridge_dir_candidate.max_length_free, len); + bridge_dir_candidate.nb_lines_free++; } } } - if (c.total_length_anchored == 0. || c.nb_lines_anchored == 0) { + if (bridge_dir_candidate.total_length_anchored == 0. || bridge_dir_candidate.nb_lines_anchored == 0) { continue; } else { have_coverage = true; // compute median if (!dist_anchored.empty()) { std::sort(dist_anchored.begin(), dist_anchored.end()); - c.median_length_anchor = dist_anchored[dist_anchored.size() / 2]; + bridge_dir_candidate.median_length_anchor = dist_anchored[dist_anchored.size() / 2]; } @@ -331,11 +404,13 @@ bool BridgeDetector::detect_angle(double bridge_direction_override) for (size_t i = 1; i < candidates.size(); ++ i) if (candidates[i].coverage > candidates[i_best].coverage) i_best = i; + else if (candidates[i].coverage > candidates[i_best].coverage) + i_best = i; this->angle = candidates[i_best].angle; if (this->angle >= PI) this->angle -= PI; - + #ifdef SLIC3R_DEBUG printf(" Optimal infill angle is %d degrees\n", (int)Slic3r::Geometry::rad2deg(this->angle)); #endif @@ -450,51 +525,7 @@ void ExPolygon::get_trapezoids(ExPolygon clone, Polygons* polygons, double angle } */ -// This algorithm may return more trapezoids than necessary -// (i.e. it may break a single trapezoid in several because -// other parts of the object have x coordinates in the middle) -static void get_trapezoids2(const ExPolygon& expoly, Polygons* polygons) -{ - Polygons src_polygons = to_polygons(expoly); - // get all points of this ExPolygon - const Points pp = to_points(src_polygons); - - // build our bounding box - BoundingBox bb(pp); - - // get all x coordinates - std::vector xx; - xx.reserve(pp.size()); - for (Points::const_iterator p = pp.begin(); p != pp.end(); ++p) - xx.push_back(p->x()); - std::sort(xx.begin(), xx.end()); - - // find trapezoids by looping from first to next-to-last coordinate - Polygons rectangle; - rectangle.emplace_back(Polygon()); - for (std::vector::const_iterator x = xx.begin(); x != xx.end()-1; ++x) { - coord_t next_x = *(x + 1); - if (*x != next_x) { - // intersect with rectangle - // append results to return value - rectangle.front() = { { *x, bb.min.y() }, { next_x, bb.min.y() }, { next_x, bb.max.y() }, { *x, bb.max.y() } }; - polygons_append(*polygons, intersection(rectangle, src_polygons)); - } - } -} - -static void get_trapezoids2(const ExPolygon &expoly, Polygons* polygons, double angle) -{ - ExPolygon clone = expoly; - clone.rotate(PI/2 - angle, Point(0,0)); - get_trapezoids2(clone, polygons); - for (Polygon &polygon : *polygons) - polygon.rotate(-(PI/2 - angle), Point(0,0)); -} - - - -void get_trapezoids3_half(const ExPolygon& expoly, Polygons* polygons, float spacing) +void get_lines(const ExPolygon& expoly, std::vector &lines, coord_t spacing, int layer_id, ExPolygons anchorage) { // get all points of this ExPolygon @@ -512,129 +543,111 @@ void get_trapezoids3_half(const ExPolygon& expoly, Polygons* polygons, float spa if (min_x > p->x()) min_x = p->x(); if (max_x < p->x()) max_x = p->x(); } - for (coord_t x = min_x; x < max_x - (coord_t)(spacing / 2); x += (coord_t)spacing) { + for (coord_t x = min_x; x < max_x - (spacing / 2); x += spacing) { xx.push_back(x); } xx.push_back(max_x); //std::sort(xx.begin(), xx.end()); // 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].x() = *x + (coord_t)spacing / 4; - poly[0].y() = bb.min(1); - poly[1].x() = next_x - (coord_t)spacing / 4; - poly[1].y() = bb.min(1); - poly[2].x() = next_x - (coord_t)spacing / 4; - poly[2].y() = bb.max(1); - poly[3].x() = *x + (coord_t)spacing / 4; - poly[3].y() = bb.max(1); - - // intersect with this expolygon - // append results to return value - polygons_append(*polygons, intersection(Polygons{ poly }, to_polygons(expoly))); + coord_t prev_x = xx.front() - SCALED_EPSILON; + for (std::vector::const_iterator x = xx.begin(); x != xx.end(); ++x) { + if (*x == prev_x) continue; + prev_x = *x; + + lines.emplace_back(Point(*x, bb.min(1) - spacing / 2), Point(*x, bb.max(1) + spacing / 2)); + assert(lines.back().a.x() == lines.back().b.x()); + assert(lines.back().a.y() < lines.back().b.y()); } } -Polygons BridgeDetector::coverage(double angle, bool precise) const +Polygons BridgeDetector::coverage(double angle) const { if (angle == -1) angle = this->angle; - Polygons covered; + Polygons covered; + const coord_t covered_offset = this->precision / 2 + SCALED_EPSILON / 2; if (angle != -1) { // Get anchors, convert them to Polygons and rotate them. - Polygons anchors = to_polygons(this->_anchor_regions); - polygons_rotate(anchors, PI / 2.0 - angle); - //same for region which do not need bridging - //Polygons supported_area = diff(this->lower_slices.expolygons, this->_anchor_regions, true); - //polygons_rotate(anchors, PI / 2.0 - angle); + ExPolygons anchors = this->_anchor_regions; + expolygons_rotate(anchors, PI / 2.0 - angle); - for (ExPolygon expolygon : this->expolygons) { + for (ExPolygon unsupported : this->expolygons) { // Clone our expolygon and rotate it so that we work with vertical lines. - expolygon.rotate(PI / 2.0 - angle); + unsupported.rotate(PI / 2.0 - angle); // Outset the bridge expolygon by half the amount we used for detecting anchors; // we'll use this one to generate our trapezoids and be sure that their vertices // are inside the anchors and not on their contours leading to false negatives. - for (ExPolygon &expoly : offset_ex(expolygon, 0.5f * float(this->spacing))) { - // Compute trapezoids according to a vertical orientation - Polygons trapezoids; - if (!precise) get_trapezoids2(expoly, &trapezoids, PI / 2); - else get_trapezoids3_half(expoly, &trapezoids, float(this->spacing)); - for (Polygon &trapezoid : trapezoids) { - size_t n_supported = 0; - if (!precise) { - // not nice, we need a more robust non-numeric check - // imporvment 1: take into account when we go in the supported area. - for (const Line &supported_line : intersection_ln(trapezoid.lines(), anchors)) - if (supported_line.length() >= this->spacing) - ++n_supported; + ExPolygons unsupported_bigger = offset_ex(unsupported, 0.5f * float(this->spacing)); + assert(unsupported_bigger.size() == 1); // growing don't split + ExPolygons small_anchors = intersection_ex(unsupported_bigger.front(), anchors); + unsupported_bigger = small_anchors; + unsupported_bigger.push_back(unsupported); + unsupported_bigger = union_safety_offset_ex(unsupported_bigger); + // now unsupported_bigger is unsupported but with a little extra inside the anchors + // clean it up if needed (remove bits unlinked to 'unsupported' + if (unsupported_bigger.size() > 1) { + double biggest_area = 0; + for (auto it = unsupported_bigger.begin(); it != unsupported_bigger.end(); ++it) { + biggest_area = std::max(biggest_area, it->area()); + } + auto it = unsupported_bigger.begin(); + while (it != unsupported_bigger.end()) { + if (it->area() >= biggest_area - 1) { + ++it; } else { - Polygons intersects = intersection(Polygons{trapezoid}, anchors); - n_supported = intersects.size(); - - if (n_supported >= 2) { - // trim it to not allow to go outside of the intersections - BoundingBox center_bound = intersects[0].bounding_box(); - coord_t min_y = center_bound.center()(1), max_y = center_bound.center()(1); - for (Polygon &poly_bound : intersects) { - center_bound = poly_bound.bounding_box(); - if (min_y > center_bound.center()(1)) min_y = center_bound.center()(1); - if (max_y < center_bound.center()(1)) max_y = center_bound.center()(1); - } - coord_t min_x = trapezoid[0](0), max_x = trapezoid[0](0); - for (Point &p : trapezoid.points) { - if (min_x > p(0)) min_x = p(0); - if (max_x < p(0)) max_x = p(0); - } - //add what get_trapezoids3 has removed (+EPSILON) - min_x -= (this->spacing / 4 + 1); - max_x += (this->spacing / 4 + 1); - coord_t mid_x = (min_x + max_x) / 2; - for (Point &p : trapezoid.points) { - if (p(1) < min_y) p(1) = min_y; - if (p(1) > max_y) p(1) = max_y; - if (p(0) > min_x && p(0) < mid_x) p(0) = min_x; - if (p(0) < max_x && p(0) > mid_x) p(0) = max_x; - } + it = unsupported_bigger.erase(it); + } + } + } + assert(unsupported_bigger.size() == 1); + { + std::vector support_lines; + get_lines(unsupported_bigger.front(), support_lines, this->precision, layer_id, anchors); + std::vector lines_checked; + for (Line &line : support_lines) { + lines_checked.emplace_back(); + // intersection to have the printable lines + Polylines pls = intersection_pl({Polyline{line.a, line.b}}, unsupported_bigger); + for (Polyline &pl : pls) { + // you can't add point with a cut + assert(pl.size() == 2); + // check if the line is anchored + bool has_a = false, has_b = false; + for (ExPolygon anchor : anchors) { + has_a = has_a || anchor.contains(pl.front()); + has_b = has_b || anchor.contains(pl.back()); } + // not both in anchor: bad. discard. + if (!has_a || !has_b) + continue; + lines_checked.back().emplace_back(pl.front(), pl.back()); } - - if (n_supported >= 2) { - //add it - covered.push_back(std::move(trapezoid)); + } + assert(lines_checked.size() == support_lines.size()); + + // create polygons inflated by covered_offset from good lines + for (Lines &lines : lines_checked) { + Polygon p; + for (Line &l : lines) { + covered.emplace_back(Points{Point(l.a.x() - covered_offset, l.a.y() - covered_offset), + Point(l.a.x() - covered_offset, l.b.y() + covered_offset), + Point(l.a.x() + covered_offset, l.b.y() + covered_offset), + Point(l.a.x() + covered_offset, l.a.y() - covered_offset)}); } } } } - // Unite the trapezoids before rotation, as the rotation creates tiny gaps and intersections between the trapezoids - // instead of exact overlaps. + // Unite the polygons created from lines covered = union_(covered); + // unoffset the polygons, so it doesn't expand into un-printable areas + covered = offset(covered, -covered_offset); + // Intersect trapezoids with actual bridge area to remove extra margins and append it to result. - polygons_rotate(covered, -(PI/2.0 - angle)); - //covered = intersection(this->expolygons, covered); -#if 0 - { - my @lines = map @{$_->lines}, @$trapezoids; - $_->rotate(-(PI/2 - $angle), [0,0]) for @lines; - - require "Slic3r/SVG.pm"; - Slic3r::SVG::output( - "coverage_" . rad2deg($angle) . ".svg", - expolygons => [$self->expolygon], - green_expolygons => $self->_anchor_regions, - red_expolygons => $coverage, - lines => \@lines, - ); - } -#endif + polygons_rotate(covered, -(PI / 2.0 - angle)); } return covered; } diff --git a/src/libslic3r/BridgeDetector.hpp b/src/libslic3r/BridgeDetector.hpp index 0416a48cf2b..d6843ec5e39 100644 --- a/src/libslic3r/BridgeDetector.hpp +++ b/src/libslic3r/BridgeDetector.hpp @@ -22,17 +22,24 @@ class BridgeDetector { // Lower slices, all regions. const ExPolygons &lower_slices; // Scaled extrusion width of the infill. - coord_t spacing; + const coord_t spacing; + // precision, number of lines to use. + const coord_t precision; // Angle resolution for the brute force search of the best bridging angle. double resolution; // The final optimal angle. double angle; + // ignore bridges that are longer than that. + coordf_t max_bridge_length; + + + int layer_id = -1; - BridgeDetector(ExPolygon _expolygon, const ExPolygons &_lower_slices, coord_t _extrusion_width); - BridgeDetector(const ExPolygons &_expolygons, const ExPolygons &_lower_slices, coord_t _extrusion_width); + BridgeDetector(ExPolygon _expolygon, const ExPolygons &_lower_slices, coord_t _extrusion_spacing, coord_t _precision, int layer_id); + BridgeDetector(const ExPolygons &_expolygons, const ExPolygons &_lower_slices, coord_t _extrusion_spacing, coord_t _precision, int layer_id); // If bridge_direction_override != 0, then the angle is used instead of auto-detect. bool detect_angle(double bridge_direction_override = 0.); - Polygons coverage(double angle = -1, bool precise = true) const; + Polygons coverage(double angle = -1) const; void unsupported_edges(double angle, Polylines* unsupported) const; Polylines unsupported_edges(double angle = -1) const; @@ -52,9 +59,13 @@ class BridgeDetector { coordf_t total_length_anchored = 0; coordf_t median_length_anchor = 0; coordf_t max_length_anchored = 0; + // number of lines that are anchored at both ends, ie a real bridge uint32_t nb_lines_anchored = 0; + // number of lines that are anchored but never go in an unsupported area (useless bridge, that can create problems with flow) + uint32_t nb_lines_fake_bridge = 0; coordf_t total_length_free = 0; coordf_t max_length_free = 0; + // number of lines that overhangs or floating in the air uint32_t nb_lines_free = 0; }; public: diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 2d339c0a104..0d2149b3b53 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -1169,6 +1169,8 @@ Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::Polygo { return _clipper_pl_open(ClipperLib::ctDifference, ClipperUtils::PolylinesProvider(subject), ClipperUtils::PolygonsProvider(clip)); } Slic3r::Polylines diff_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygon &clip) { return _clipper_pl_open(ClipperLib::ctDifference, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::ExPolygonProvider(clip)); } +Slic3r::Polylines diff_pl(const Slic3r::Polyline &subject, const Slic3r::Polygon &clip) + { return _clipper_pl_open(ClipperLib::ctDifference, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::SinglePathProvider(clip.points)); } Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygon &clip) { return _clipper_pl_open(ClipperLib::ctDifference, ClipperUtils::PolylinesProvider(subject), ClipperUtils::ExPolygonProvider(clip)); } Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygons &clip) diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index 55a566851f5..283458c02a3 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -419,6 +419,7 @@ Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfac Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip); Slic3r::Polylines diff_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygon &clip); +Slic3r::Polylines diff_pl(const Slic3r::Polyline &subject, const Slic3r::Polygon &clip); Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygon &clip); Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygons &clip); Slic3r::Polylines diff_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip); diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 3797c13ebad..e3a5014a02d 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -413,6 +413,7 @@ class ConfigOption { virtual void set(const ConfigOption *option) = 0; virtual int32_t get_int(size_t idx = 0) const { throw BadOptionTypeException("Calling ConfigOption::get_int on a non-int ConfigOption"); } virtual double get_float(size_t idx = 0) const { throw BadOptionTypeException("Calling ConfigOption::get_float on a non-float ConfigOption"); } + virtual bool is_percent(size_t idx = 0) const { return false; } virtual bool get_bool(size_t idx = 0) const { throw BadOptionTypeException("Calling ConfigOption::get_bool on a non-boolean ConfigOption"); } virtual void set_enum_int(int32_t /* val */) { throw BadOptionTypeException("Calling ConfigOption::set_enum_int on a non-enum ConfigOption"); } virtual boost::any get_any(int32_t idx = -1) const { throw BadOptionTypeException("Calling ConfigOption::get_any on a raw ConfigOption"); } @@ -1159,8 +1160,9 @@ class ConfigOptionPercent : public ConfigOptionFloat ConfigOptionPercent& operator= (const ConfigOption *opt) { this->set(opt); return *this; } bool operator==(const ConfigOptionPercent &rhs) const throw() { return this->value == rhs.value; } bool operator< (const ConfigOptionPercent &rhs) const throw() { return this->value < rhs.value; } - + double get_abs_value(double ratio_over) const { return ratio_over * this->value / 100.; } + bool is_percent(size_t idx = 0) const override { return true; } std::string serialize() const override { @@ -1203,6 +1205,7 @@ class ConfigOptionPercentsTempl : public ConfigOptionFloatsTempl bool operator==(const ConfigOptionPercentsTempl &rhs) const throw() { return ConfigOptionFloatsTempl::vectors_equal(this->values, rhs.values); } bool operator< (const ConfigOptionPercentsTempl &rhs) const throw() { return ConfigOptionFloatsTempl::vectors_lower(this->values, rhs.values); } double get_abs_value(size_t i, double ratio_over) const { return this->is_nil(i) ? 0 : ratio_over * this->get_at(i) / 100; } + bool is_percent(size_t idx = 0) const override { return true; } std::string serialize() const override { @@ -1272,6 +1275,7 @@ class ConfigOptionFloatOrPercent : public ConfigOptionPercent double get_abs_value(double ratio_over) const { return this->percent ? (ratio_over * this->value / 100) : this->value; } double get_float(size_t idx = 0) const override { return get_abs_value(1.); } + bool is_percent(size_t idx = 0) const override { return this->percent; } // special case for get/set any: use a FloatOrPercent like for FloatsOrPercents, to have the is_percent boost::any get_any(int32_t idx = 0) const override { return boost::any(FloatOrPercent{value, percent}); } void set_any(boost::any anyval, int32_t idx = -1) override @@ -1357,6 +1361,12 @@ class ConfigOptionFloatsOrPercentsTempl : public ConfigOptionVectoris_nil(idx)) + return false; + return this->get_at(idx).percent; + } static inline bool is_nil(const boost::any &to_check) { bool ok = std::isnan(boost::any_cast(to_check).value) || boost::any_cast(to_check).value == NIL_VALUE().value diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index f1396fcbdcd..c3adb94d1a5 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -221,6 +221,8 @@ inline Polylines to_polylines(ExPolygons &&src) inline Polygons to_polygons(const ExPolygon &src) { + assert(src.contour.is_counter_clockwise()); + assert(src.holes.empty() || src.holes.front().is_clockwise()); Polygons polygons; polygons.reserve(src.holes.size() + 1); polygons.push_back(src.contour); @@ -233,6 +235,8 @@ inline Polygons to_polygons(const ExPolygons &src) Polygons polygons; polygons.reserve(number_polygons(src)); for (const ExPolygon& ex_poly : src) { + assert(ex_poly.contour.is_counter_clockwise()); + assert(ex_poly.holes.empty() || ex_poly.holes.front().is_clockwise()); polygons.push_back(ex_poly.contour); polygons.insert(polygons.end(), ex_poly.holes.begin(), ex_poly.holes.end()); } @@ -241,6 +245,8 @@ inline Polygons to_polygons(const ExPolygons &src) inline ConstPolygonPtrs to_polygon_ptrs(const ExPolygon &src) { + assert(src.contour.is_counter_clockwise()); + assert(src.holes.empty() || src.holes.front().is_clockwise()); ConstPolygonPtrs polygons; polygons.reserve(src.holes.size() + 1); polygons.emplace_back(&src.contour); @@ -254,6 +260,8 @@ inline ConstPolygonPtrs to_polygon_ptrs(const ExPolygons &src) ConstPolygonPtrs polygons; polygons.reserve(number_polygons(src)); for (const ExPolygon &expoly : src) { + assert(expoly.contour.is_counter_clockwise()); + assert(expoly.holes.empty() || expoly.holes.front().is_clockwise()); polygons.emplace_back(&expoly.contour); for (const Polygon &hole : expoly.holes) polygons.emplace_back(&hole); @@ -276,6 +284,8 @@ inline Polygons to_polygons(ExPolygons &&src) Polygons polygons; polygons.reserve(number_polygons(src)); for (ExPolygons::iterator it = src.begin(); it != src.end(); ++it) { + assert(it->contour.is_counter_clockwise()); + assert(it->holes.empty() || it->holes.front().is_clockwise()); polygons.push_back(std::move(it->contour)); std::move(std::begin(it->holes), std::end(it->holes), std::back_inserter(polygons)); it->holes.clear(); @@ -287,8 +297,10 @@ inline ExPolygons to_expolygons(const Polygons &polys) { ExPolygons ex_polys; ex_polys.assign(polys.size(), ExPolygon()); - for (size_t idx = 0; idx < polys.size(); ++idx) + for (size_t idx = 0; idx < polys.size(); ++idx) { + assert(polys[idx].is_counter_clockwise()); ex_polys[idx].contour = polys[idx]; + } return ex_polys; } @@ -296,13 +308,17 @@ inline ExPolygons to_expolygons(Polygons &&polys) { ExPolygons ex_polys; ex_polys.assign(polys.size(), ExPolygon()); - for (size_t idx = 0; idx < polys.size(); ++idx) + for (size_t idx = 0; idx < polys.size(); ++idx) { + assert(polys[idx].is_counter_clockwise()); ex_polys[idx].contour = std::move(polys[idx]); + } return ex_polys; } inline void polygons_append(Polygons &dst, const ExPolygon &src) { + assert(src.contour.is_counter_clockwise()); + assert(src.holes.empty() || src.holes.front().is_clockwise()); dst.reserve(dst.size() + src.holes.size() + 1); dst.push_back(src.contour); dst.insert(dst.end(), src.holes.begin(), src.holes.end()); @@ -312,6 +328,8 @@ inline void polygons_append(Polygons &dst, const ExPolygons &src) { dst.reserve(dst.size() + number_polygons(src)); for (ExPolygons::const_iterator it = src.begin(); it != src.end(); ++ it) { + assert(it->contour.is_counter_clockwise()); + assert(it->holes.empty() || it->holes.front().is_clockwise()); dst.push_back(it->contour); dst.insert(dst.end(), it->holes.begin(), it->holes.end()); } @@ -319,6 +337,8 @@ inline void polygons_append(Polygons &dst, const ExPolygons &src) inline void polygons_append(Polygons &dst, ExPolygon &&src) { + assert(src.contour.is_counter_clockwise()); + assert(src.holes.empty() || src.holes.front().is_clockwise()); dst.reserve(dst.size() + src.holes.size() + 1); dst.push_back(std::move(src.contour)); std::move(std::begin(src.holes), std::end(src.holes), std::back_inserter(dst)); @@ -329,6 +349,8 @@ inline void polygons_append(Polygons &dst, ExPolygons &&src) { dst.reserve(dst.size() + number_polygons(src)); for (ExPolygons::iterator it = src.begin(); it != src.end(); ++ it) { + assert(it->contour.is_counter_clockwise()); + assert(it->holes.empty() || it->holes.front().is_clockwise()); dst.push_back(std::move(it->contour)); std::move(std::begin(it->holes), std::end(it->holes), std::back_inserter(dst)); it->holes.clear(); diff --git a/src/libslic3r/ExtrusionEntityCollection.cpp b/src/libslic3r/ExtrusionEntityCollection.cpp index 66130974f34..d1d6f91dab1 100644 --- a/src/libslic3r/ExtrusionEntityCollection.cpp +++ b/src/libslic3r/ExtrusionEntityCollection.cpp @@ -83,9 +83,49 @@ void ExtrusionEntityCollection::remove(size_t i) void ExtrusionEntityCollection::chained_path_from(const Point &start_near) { - if (this->m_no_sort) - return; - chain_and_reorder_extrusion_entities(this->m_entities, &start_near); + if (this->m_no_sort) { + if (this->m_can_reverse) { + if (m_entities.size() > 1) { + //can't sort myself, ask first and last thign to sort itself so the first point of each are the best ones + if (m_entities.front()->is_collection()) { + assert(dynamic_cast(m_entities.front()) != nullptr); + static_cast(m_entities.front())->chained_path_from(start_near); + } else if (m_entities.front()->can_reverse() && + m_entities.front()->first_point().distance_to_square(start_near) > + m_entities.front()->first_point().distance_to_square(start_near)) { + m_entities.front()->reverse(); + } + if (m_entities.back()->is_collection()) { + assert(dynamic_cast(m_entities.front()) != nullptr); + static_cast(m_entities.back())->chained_path_from(start_near); + } else if (m_entities.back()->can_reverse() && + m_entities.back()->first_point().distance_to_square(start_near) > + m_entities.back()->first_point().distance_to_square(start_near)) { + m_entities.back()->reverse(); + } + //now check if it's better for us to reverse + if (start_near.distance_to_square(this->m_entities.front()->first_point()) > + start_near.distance_to_square(this->m_entities.back()->first_point())) { + // switch entities + this->reverse(); + } + } + // now we are in our good order, update the internals + Point last_point = start_near; + for (ExtrusionEntity *entity : m_entities) { + if (entity->is_collection()) { + assert(dynamic_cast(entity) != nullptr); + static_cast(entity)->chained_path_from(last_point); + } else if (entity->can_reverse() && entity->first_point().distance_to_square(last_point) > + entity->first_point().distance_to_square(last_point)) { + entity->reverse(); + } + last_point = entity->last_point(); + } + } + } else { + chain_and_reorder_extrusion_entities(this->m_entities, &start_near); + } } void ExtrusionEntityCollection::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 913a30cb45c..990d1de7015 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -716,7 +716,8 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: double area = unscaled(unscaled(real_surface)); assert(compute_volume.volume <= area * surface_fill.params.layer_height * 1.001 || f->debug_verify_flow_mult <= 0.8); if(compute_volume.volume > 0) //can fail for thin regions - assert(compute_volume.volume >= area * surface_fill.params.layer_height * 0.999 || f->debug_verify_flow_mult >= 1.3 || area < std::max(1.,surface_fill.params.config->solid_infill_below_area.value)); + assert(compute_volume.volume >= area * surface_fill.params.layer_height * 0.999 || f->debug_verify_flow_mult >= 1.3 + || area < std::max(1.,surface_fill.params.config->solid_infill_below_area.value) || area < std::max(1.,surface_fill.params.config->solid_infill_below_layer_area.value)); } #endif } @@ -934,8 +935,8 @@ void Layer::make_ironing() fill.link_max_length = (coord_t)scale_(3. * fill.get_spacing()); double extrusion_height = ironing_params.height * fill.get_spacing() / nozzle_dmr; //FIXME FLOW decide if it's good - double max_overlap = region_config.get_computed_value("filament_max_overlap", ironing_params.extruder - 1); - double overlap = std::min(max_overlap, region_config.solid_infill_overlap.get_abs_value(1.)); + // note: don't use filament_max_overlap, as it's a top surface + double overlap = region_config.top_solid_infill_overlap.get_abs_value(1.); float extrusion_width = Flow::rounded_rectangle_extrusion_width_from_spacing(float(nozzle_dmr), float(extrusion_height), float(overlap)); double flow_mm3_per_mm = nozzle_dmr * extrusion_height; //Flow flow = Flow::new_from_spacing(float(nozzle_dmr), 0., float(height), 1.f, false); diff --git a/src/libslic3r/Flow.cpp b/src/libslic3r/Flow.cpp index 85ddccc3a0e..e8be7b07bba 100644 --- a/src/libslic3r/Flow.cpp +++ b/src/libslic3r/Flow.cpp @@ -306,7 +306,7 @@ Flow Flow::new_from_config(FlowRole role, const DynamicConfig& print_config, flo } else if (role == frTopSolidInfill) { config_width = print_config.opt("top_infill_extrusion_width"); config_spacing = print_config.opt("top_infill_extrusion_spacing"); - overlap = (float)print_config.get_abs_value("solid_infill_overlap", 1.); + overlap = (float)print_config.get_abs_value("top_solid_infill_overlap", 1.); } else { throw Slic3r::InvalidArgument("Unknown role"); } @@ -322,7 +322,8 @@ Flow Flow::new_from_config(FlowRole role, const DynamicConfig& print_config, flo // Get the configured nozzle_diameter for the extruder associated to the flow role requested. // Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will follback to zero'th element, so everything is all right. - return Flow::new_from_config_width(role, config_width, config_spacing, nozzle_diameter, layer_height, std::min(overlap, filament_max_overlap)); + return Flow::new_from_config_width(role, config_width, config_spacing, nozzle_diameter, layer_height, + std::min(role == frTopSolidInfill ? 1.f : overlap, filament_max_overlap)); //bridge ? (float)m_config.bridge_flow_ratio.get_abs_value(1) : 0.0f); } diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index f0cfb77b1a6..1679482463f 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2255,20 +2255,17 @@ void GCode::process_layers( [this, &fan_mover = this->m_fan_mover, &config = this->config(), &writer = this->m_writer](std::string in)->std::string { CNumericLocalesSetter locales_setter; - if (config.fan_speedup_time.value != 0 || config.fan_kickstart.value > 0) { - if (fan_mover.get() == nullptr) - fan_mover.reset(new Slic3r::FanMover( - writer, - std::abs((float)config.fan_speedup_time.value), - config.fan_speedup_time.value > 0, - config.use_relative_e_distances.value, - config.fan_speedup_overhangs.value, - (float)config.fan_kickstart.value)); - //flush as it's a whole layer - this->m_throw_if_canceled(); - return fan_mover->process_gcode(in, true); - } - return in; + if (fan_mover.get() == nullptr) + fan_mover.reset(new Slic3r::FanMover( + writer, + std::abs((float)config.fan_speedup_time.value), + config.fan_speedup_time.value > 0, + config.use_relative_e_distances.value, + config.fan_speedup_overhangs.value, + (float)config.fan_kickstart.value)); + //flush as it's a whole layer + this->m_throw_if_canceled(); + return fan_mover->process_gcode(in, true); }); // It registers a handler that sets locales to "C" before any TBB thread starts participating in tbb::parallel_pipeline. @@ -2365,21 +2362,17 @@ void GCode::process_layers( const auto fan_mover = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, [this, &fan_mover = this->m_fan_mover, &config = this->config(), &writer = this->m_writer](std::string in)->std::string { - - if (config.fan_speedup_time.value != 0 || config.fan_kickstart.value > 0) { - if (fan_mover.get() == nullptr) - fan_mover.reset(new Slic3r::FanMover( - writer, - std::abs((float)config.fan_speedup_time.value), - config.fan_speedup_time.value > 0, - config.use_relative_e_distances.value, - config.fan_speedup_overhangs.value, - (float)config.fan_kickstart.value)); - this->m_throw_if_canceled(); - //flush as it's a whole layer - return fan_mover->process_gcode(in, true); - } - return in; + if (fan_mover.get() == nullptr) + fan_mover.reset(new Slic3r::FanMover( + writer, + std::abs((float)config.fan_speedup_time.value), + config.fan_speedup_time.value > 0, + config.use_relative_e_distances.value, + config.fan_speedup_overhangs.value, + (float)config.fan_kickstart.value)); + this->m_throw_if_canceled(); + //flush as it's a whole layer + return fan_mover->process_gcode(in, true); }); // It registers a handler that sets locales to "C" before any TBB thread starts participating in tbb::parallel_pipeline. @@ -2958,9 +2951,9 @@ LayerResult GCode::process_layer( return result; // Extract 1st object_layer and support_layer of this set of layers with an equal print_z. - coordf_t print_z = layer.print_z; - bool first_layer = layer.id() == 0; - uint16_t first_extruder_id = layer_tools.extruders.front(); + coordf_t print_z = layer.print_z; + bool first_layer = layer.id() == 0; + uint16_t first_extruder_id = layer_tools.extruders.front(); // Initialize config with the 1st object to be printed at this layer. m_config.apply(print.default_region_config(), true); @@ -3444,7 +3437,10 @@ LayerResult GCode::process_layer( m_layer = layer_to_print.support_layer; m_object_layer_over_raft = false; if (m_config.print_temperature > 0) - gcode += m_writer.set_temperature(m_config.print_temperature.value, false, m_writer.tool()->id()); + if (m_layer != nullptr && m_layer->bottom_z() < EPSILON && m_config.print_first_layer_temperature.value > 0) + gcode += m_writer.set_temperature(m_config.print_first_layer_temperature.value, false, m_writer.tool()->id()); + else + gcode += m_writer.set_temperature(m_config.print_temperature.value, false, m_writer.tool()->id()); else if (m_layer != nullptr && m_layer->bottom_z() < EPSILON && m_config.first_layer_temperature.get_at(m_writer.tool()->id()) > 0) gcode += m_writer.set_temperature(m_config.first_layer_temperature.get_at(m_writer.tool()->id()), false, m_writer.tool()->id()); else if (m_config.temperature.get_at(m_writer.tool()->id()) > 0) // don't set it if disabled @@ -3632,6 +3628,16 @@ LayerResult GCode::process_layer( file.write(gcode); #endif + // set area used in this layer + double layer_area = 0; + for (const LayerToPrint &print_layer : layers) { + assert(print_layer.layer()); + if (print_layer.layer()) + for (auto poly : print_layer.layer()->lslices) layer_area += poly.area(); + } + layer_area = unscaled(unscaled(layer_area)); + status_monitor.stats().layer_area_stats.emplace_back(print_z, layer_area); + BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z << log_memory_info(); @@ -3896,11 +3902,7 @@ std::string GCode::extrude_loop_vase(const ExtrusionLoop &original_loop, const s } // calculate extrusion length per distance unit - double e_per_mm_per_height = (path->mm3_per_mm / this->m_layer->height) - * m_writer.tool()->e_per_mm3() - * this->config().print_extrusion_multiplier.get_abs_value(1); - if (m_writer.extrusion_axis().empty()) - e_per_mm_per_height = 0; + double e_per_mm_per_height = compute_e_per_mm(path->mm3_per_mm); //extrude { std::string comment = m_config.gcode_comments ? description : ""; @@ -4890,10 +4892,7 @@ std::string GCode::extrude_multi_path3D(const ExtrusionMultiPath3D &multipath3D, gcode += this->_before_extrude(path, description, speed); // calculate extrusion length per distance unit - double e_per_mm = path.mm3_per_mm - * m_writer.tool()->e_per_mm3() - * this->config().print_extrusion_multiplier.get_abs_value(1); - if (m_writer.extrusion_axis().empty()) e_per_mm = 0; + double e_per_mm = compute_e_per_mm(path.mm3_per_mm); double path_length = 0.; { std::string comment = m_config.gcode_comments ? description : ""; @@ -5007,10 +5006,7 @@ std::string GCode::extrude_path_3D(const ExtrusionPath3D &path, const std::strin std::string gcode = this->_before_extrude(path, description, speed); // calculate extrusion length per distance unit - double e_per_mm = path.mm3_per_mm - * m_writer.tool()->e_per_mm3() - * this->config().print_extrusion_multiplier.get_abs_value(1); - if (m_writer.extrusion_axis().empty()) e_per_mm = 0; + double e_per_mm = compute_e_per_mm(path.mm3_per_mm); double path_length = 0.; { std::string comment = m_config.gcode_comments ? description : ""; @@ -5049,7 +5045,10 @@ std::string GCode::extrude_perimeters(const Print &print, const std::vectorconfig()); m_seam_placer.external_perimeters_first = m_region->config().external_perimeters_first.value; if (m_config.print_temperature > 0) - gcode += m_writer.set_temperature(m_config.print_temperature.value, false, m_writer.tool()->id()); + if (m_layer != nullptr && m_layer->bottom_z() < EPSILON && m_config.print_first_layer_temperature.value > 0) + gcode += m_writer.set_temperature(m_config.print_first_layer_temperature.value, false, m_writer.tool()->id()); + else + gcode += m_writer.set_temperature(m_config.print_temperature.value, false, m_writer.tool()->id()); else if (m_layer != nullptr && m_layer->bottom_z() < EPSILON && m_config.first_layer_temperature.get_at(m_writer.tool()->id()) > 0) gcode += m_writer.set_temperature(m_config.first_layer_temperature.get_at(m_writer.tool()->id()), false, m_writer.tool()->id()); else if (m_config.temperature.get_at(m_writer.tool()->id()) > 0) { // don't set it if disabled @@ -5077,7 +5076,10 @@ std::string GCode::extrude_infill(const Print& print, const std::vectorconfig()); m_writer.apply_print_region_config(m_region->config()); if (m_config.print_temperature > 0) - gcode += m_writer.set_temperature(m_config.print_temperature.value, false, m_writer.tool()->id()); + if (m_layer != nullptr && m_layer->bottom_z() < EPSILON && m_config.print_first_layer_temperature.value > 0) + gcode += m_writer.set_temperature(m_config.print_first_layer_temperature.value, false, m_writer.tool()->id()); + else + gcode += m_writer.set_temperature(m_config.print_temperature.value, false, m_writer.tool()->id()); else if (m_layer != nullptr && m_layer->bottom_z() < EPSILON && m_config.first_layer_temperature.get_at(m_writer.tool()->id()) > 0) gcode += m_writer.set_temperature(m_config.first_layer_temperature.get_at(m_writer.tool()->id()), false, m_writer.tool()->id()); else if (m_config.temperature.get_at(m_writer.tool()->id()) > 0) // don't set it if disabled @@ -5103,7 +5105,10 @@ std::string GCode::extrude_ironing(const Print& print, const std::vectorconfig()); m_writer.apply_print_region_config(m_region->config()); if (m_config.print_temperature > 0) - gcode += m_writer.set_temperature(m_config.print_temperature.value, false, m_writer.tool()->id()); + if (m_layer != nullptr && m_layer->bottom_z() < EPSILON && m_config.print_first_layer_temperature.value > 0) + gcode += m_writer.set_temperature(m_config.print_first_layer_temperature.value, false, m_writer.tool()->id()); + else + gcode += m_writer.set_temperature(m_config.print_temperature.value, false, m_writer.tool()->id()); else if (m_layer != nullptr && m_layer->bottom_z() < EPSILON && m_config.first_layer_temperature.get_at(m_writer.tool()->id()) > 0) gcode += m_writer.set_temperature(m_config.first_layer_temperature.get_at(m_writer.tool()->id()), false, m_writer.tool()->id()); else if (m_config.temperature.get_at(m_writer.tool()->id()) > 0) @@ -5333,11 +5338,48 @@ void GCode::_extrude_line_cut_corner(std::string& gcode_str, const Line& line, c comment); } - //relance + // relance last_pos = line.a; } } +double GCode::compute_e_per_mm(double path_mm3_per_mm) { + // no e if no extrusion axis + if (m_writer.extrusion_axis().empty()) + return 0; + // compute + double e_per_mm = path_mm3_per_mm + * m_writer.tool()->e_per_mm3() // inside is the filament_extrusion_multiplier + * this->config().print_extrusion_multiplier.get_abs_value(1); + // extrusion mult per speed + std::string str = this->config().extruder_extrusion_multiplier_speed.get_at(this->m_writer.tool()->id()); + if (str.size() > 2 && !(str.at(0) == '0' && str.at(1) == ' ')) { + assert(e_per_mm > 0); + double current_speed = this->writer().get_speed(); + std::vector extrusion_mult; + std::stringstream stream{str}; + double parsed = 0.f; + while (stream >> parsed) + extrusion_mult.push_back(parsed); + int idx_before = int(current_speed/10); + if (idx_before >= extrusion_mult.size() - 1) { + // last or after the last + e_per_mm *= extrusion_mult.back(); + } else { + assert(idx_before + 1 < extrusion_mult.size()); + float percent_before = 1 - (current_speed/10 - idx_before); + double mult = extrusion_mult[idx_before] * percent_before; + mult += extrusion_mult[idx_before+1] * (1-percent_before); + e_per_mm *= (mult / 2); + } + assert(e_per_mm > 0); + } + // first layer mult + if (this->m_layer->bottom_z() < EPSILON) + e_per_mm *= this->config().first_layer_flow_ratio.get_abs_value(1); + return e_per_mm; +} + std::string GCode::_extrude(const ExtrusionPath &path, const std::string &description, double speed) { std::string descr = description.empty() ? ExtrusionEntity::role_to_string(path.role()) : description; @@ -5352,11 +5394,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string &descri }; // calculate extrusion length per distance unit - double e_per_mm = path.mm3_per_mm - * m_writer.tool()->e_per_mm3() - * this->config().print_extrusion_multiplier.get_abs_value(1); - if (m_layer->bottom_z() < EPSILON) e_per_mm *= this->config().first_layer_flow_ratio.get_abs_value(1); - if (m_writer.extrusion_axis().empty()) e_per_mm = 0; + double e_per_mm = compute_e_per_mm(path.mm3_per_mm); path.polyline.ensure_fitting_result_valid(); if (path.polyline.lines().size() > 0) { std::string comment = m_config.gcode_comments ? descr : ""; @@ -6002,24 +6040,24 @@ Polyline GCode::travel_to(std::string &gcode, const Point &point, ExtrusionRole && !m_avoid_crossing_perimeters.disabled_once() && m_avoid_crossing_perimeters.is_init() && !(m_config.avoid_crossing_not_first_layer && this->on_first_layer()); - + // check / compute avoid_crossing_perimeters - bool will_cross_perimeter = this->can_cross_perimeter(travel, can_avoid_cross_peri); - - // if a retraction would be needed (with a low min_dist threshold), try to use avoid_crossing_perimeters to plan a - // multi-hop travel path inside the configuration space - if (will_cross_perimeter && this->needs_retraction(travel, role, scale_d(EXTRUDER_CONFIG_WITH_DEFAULT(nozzle_diameter, 0.4)) * 3) - && can_avoid_cross_peri) { - this->m_throw_if_canceled(); - travel = m_avoid_crossing_perimeters.travel_to(*this, point, &could_be_wipe_disabled); + bool may_need_avoid_crossing = can_avoid_cross_peri && this->needs_retraction(travel, role, scale_d(EXTRUDER_CONFIG_WITH_DEFAULT(nozzle_diameter, 0.4)) * 3); + + if (may_need_avoid_crossing) { + // if a retraction would be needed (with a low min_dist threshold), try to use avoid_crossing_perimeters to + // plan a multi-hop travel path inside the configuration space + if (this->can_cross_perimeter(travel, can_avoid_cross_peri)) { + this->m_throw_if_canceled(); + travel = m_avoid_crossing_perimeters.travel_to(*this, point, &could_be_wipe_disabled); + } } - if(can_avoid_cross_peri) - will_cross_perimeter = this->can_cross_perimeter(travel, false); // check whether a straight travel move would need retraction bool needs_retraction = this->needs_retraction(travel, role); - if (m_config.only_retract_when_crossing_perimeters && !(m_config.enforce_retract_first_layer && m_layer_index == 0)) - needs_retraction = needs_retraction && will_cross_perimeter; + if (m_config.only_retract_when_crossing_perimeters && + !(m_config.enforce_retract_first_layer && m_layer_index == 0)) + needs_retraction = needs_retraction && can_avoid_cross_peri && this->can_cross_perimeter(travel, false); // Re-allow avoid_crossing_perimeters for the next travel moves m_avoid_crossing_perimeters.reset_once_modifiers(); @@ -6031,10 +6069,39 @@ Polyline GCode::travel_to(std::string &gcode, const Point &point, ExtrusionRole // m_wipe.reset_path(); //} else { //check if it cross hull - auto result = diff_pl(Polylines{ travel }, to_polygons(m_layer->lslices)); - if (result.empty()) { - m_wipe.reset_path(); + + //TODO: add bbox cache & checks like for can_cross_perimeter + bool has_intersect = false; + for (const ExPolygon &expoly : m_layer->lslices) { + // first, check if it's inside the contour (still, it can go over holes) + Polylines diff_result = diff_pl(travel, expoly.contour); + if (diff_result.size() == 1 && diff_result.front() == travel) + // not inside/cross this contour, try another one. + continue; + if (!diff_result.empty()) { + //it's crossing this contour! + has_intersect = true; + } else { + // it's inside this contour, does it cross a hole? + Line travel_line; + Point whatever; + for (size_t idx_travel = travel.size() - 1; idx_travel > 0; --idx_travel) { + travel_line.a = travel.points[idx_travel]; + travel_line.b = travel.points[idx_travel - 1]; + for (const Polygon &hole : expoly.holes) { + if (hole.first_intersection(travel_line, &whatever) || + Line(hole.first_point(), hole.last_point()).intersection(travel_line, &whatever)) { + has_intersect = true; + break; + } + } + } } + break; + } + if (!has_intersect) { + m_wipe.reset_path(); + } //} } @@ -6198,46 +6265,83 @@ bool GCode::needs_retraction(const Polyline& travel, ExtrusionRole role /*=erNon bool GCode::can_cross_perimeter(const Polyline& travel, bool offset) { - if(m_layer != nullptr) - if ( ( (m_config.only_retract_when_crossing_perimeters && !(m_config.enforce_retract_first_layer && m_layer_index == 0)) && m_config.fill_density.value > 0) || m_config.avoid_crossing_perimeters) - { - //test && m_layer->any_internal_region_slice_contains(travel) - // Skip retraction if travel is contained in an internal slice *and* - // internal infill is enabled (so that stringing is entirely not visible). - //note: any_internal_region_slice_contains() is potentionally very slow, it shall test for the bounding boxes first. - //bool inside = false; - //BoundingBox bbtravel(travel.points); - //for (const BoundingBox &bbox : m_layer->lslices_bboxes) { - // inside = bbox.overlap(bbtravel); - // if(inside) break; - //} - ////have to do a bit more work to be sure - //if (inside) { - //contained inside at least one bb - //construct m_layer_slices_offseted if needed + if (m_layer != nullptr) + if (((m_config.only_retract_when_crossing_perimeters && + !(m_config.enforce_retract_first_layer && m_layer_index == 0)) && + m_config.fill_density.value > 0) || + m_config.avoid_crossing_perimeters) { + // FROM 2.7 if (m_layer_slices_offseted.layer != m_layer) { - m_layer_slices_offseted.layer = m_layer; + m_layer_slices_offseted.layer = m_layer; m_layer_slices_offseted.diameter = scale_t(EXTRUDER_CONFIG_WITH_DEFAULT(nozzle_diameter, 0.4)); - m_layer_slices_offseted.slices = m_layer->lslices; - m_layer_slices_offseted.slices_offsetted = offset_ex(m_layer->lslices, -m_layer_slices_offseted.diameter * 1.5f); - //remove top surfaces - for (const LayerRegion* reg : m_layer->regions()) { + ExPolygons slices = m_layer->lslices; + ExPolygons slices_offsetted = offset_ex(m_layer->lslices, -m_layer_slices_offseted.diameter * 1.5f); + // remove top surfaces + for (const LayerRegion *reg : m_layer->regions()) { m_throw_if_canceled(); - m_layer_slices_offseted.slices_offsetted = diff_ex(m_layer_slices_offseted.slices_offsetted, to_expolygons(reg->fill_surfaces.filter_by_type_flag(SurfaceType::stPosTop))); - m_layer_slices_offseted.slices = diff_ex(m_layer_slices_offseted.slices, to_expolygons(reg->fill_surfaces.filter_by_type_flag(SurfaceType::stPosTop))); + slices_offsetted = diff_ex(slices_offsetted, to_expolygons(reg->fill_surfaces.filter_by_type_flag(SurfaceType::stPosTop))); + slices = diff_ex(slices, to_expolygons(reg->fill_surfaces.filter_by_type_flag(SurfaceType::stPosTop))); + } + // create bb for speeding things up. + m_layer_slices_offseted.slices.clear(); + for (ExPolygon &ex : slices) { + BoundingBox bb{ex.contour.points}; + // simplify as much as possible + for (ExPolygon &ex_simpl : ex.simplify(m_layer_slices_offseted.diameter)) { + m_layer_slices_offseted.slices.emplace_back(std::move(ex_simpl), std::move(bb)); + } + } + m_layer_slices_offseted.slices_offsetted.clear(); + for (ExPolygon &ex : slices_offsetted) { + BoundingBox bb{ex.contour.points}; + for (ExPolygon &ex_simpl : ex.simplify(m_layer_slices_offseted.diameter)) { + m_layer_slices_offseted.slices_offsetted.emplace_back(std::move(ex_simpl), std::move(bb)); + } } - } // test if a expoly contains the entire travel - for (const ExPolygon &poly : + for (const std::pair &expoly_2_bb : offset ? m_layer_slices_offseted.slices_offsetted : m_layer_slices_offseted.slices) { - m_throw_if_canceled(); - if (poly.contains(travel)) { - return false; + // first check if it's roughtly inside the bb, to reject quickly. + if (travel.size() > 1 && expoly_2_bb.second.contains(travel.front()) && + expoly_2_bb.second.contains(travel.back()) && + expoly_2_bb.second.contains(travel.points[travel.size() / 2])) { + // first, check if it's inside the contour (still, it can go over holes) + Polylines diff_result = diff_pl(travel, expoly_2_bb.first.contour); + if (diff_result.size() == 1 && diff_result.front() == travel) + //if (!diff_pl(travel, expoly_2_bb.first.contour).empty()) + continue; + //second, check if it's crossing this contour + if (!diff_result.empty()) { + //has_intersect = true; + return true; + } + // third, check if it's going over a hole + // TODO: kdtree to get the ones interesting + //bool has_intersect = false; + Line travel_line; + Point whatever; + for (const Polygon &hole : expoly_2_bb.first.holes) { + m_throw_if_canceled(); + for (size_t idx_travel = travel.size() - 1; idx_travel > 0; --idx_travel) { + travel_line.a = travel.points[idx_travel]; + travel_line.b = travel.points[idx_travel - 1]; + if (hole.first_intersection(travel_line, &whatever) || + Line(hole.first_point(), hole.last_point()).intersection(travel_line, &whatever)) { + //has_intersect = true; + //break; + return true; + } + } + //if (has_intersect) + // break; + } + // if inside contour and does not inersect hole -> inside expoly, you don't need to avoid. + //if (!has_intersect) + return false; } } - //} - } + } // retract if only_retract_when_crossing_perimeters is disabled or doesn't apply return true; diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index ee1527d0120..00ee268902e 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -444,8 +444,8 @@ class GCode : ExtrusionVisitorConst { // For crossing perimeter retraction detection (contain the layer & nozzle widdth used to construct it) // !!!! not thread-safe !!!! if threaded per layer, please store it in the thread. struct SliceOffsetted { - ExPolygons slices; - ExPolygons slices_offsetted; + std::vector> slices; + std::vector> slices_offsetted; const Layer* layer; coord_t diameter; } m_layer_slices_offseted{ {},{},nullptr, 0}; @@ -507,6 +507,7 @@ class GCode : ExtrusionVisitorConst { std::function m_throw_if_canceled = [](){}; + double compute_e_per_mm(double path_mm3_per_mm); std::string _extrude(const ExtrusionPath &path, const std::string &description, double speed = -1); void _extrude_line(std::string& gcode_str, const Line& line, const double e_per_mm, const std::string& comment); void _extrude_line_cut_corner(std::string& gcode_str, const Line& line, const double e_per_mm, const std::string& comment, Point& last_pos, const double path_width); diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 472ed3e5dab..a5b60fd891d 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -49,6 +49,7 @@ namespace Slic3r { std::vector> moves_times; std::vector> roles_times; std::vector layers_times; + std::vector layers_areas; void reset() { time = 0.0f; diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index becd1881226..6341a12b13b 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -391,7 +391,9 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly BridgeDetector bd( initial, lower_layer->lslices, - this->flow(frInfill).scaled_width() + this->bridging_flow(frInfill).scaled_spacing(), + scale_t(this->layer()->object()->print()->config().bridge_precision.get_abs_value(this->bridging_flow(frInfill).spacing())), + this->layer()->id() ); #ifdef SLIC3R_DEBUG printf("Processing bridge at layer %zu:\n", this->layer()->id()); @@ -517,13 +519,54 @@ void LayerRegion::prepare_fill_surfaces() } // turn too small internal regions into solid regions according to the user setting - if (! spiral_vase && this->region().config().fill_density.value > 0) { + if (!spiral_vase && this->region().config().fill_density.value > 0) { + // apply solid_infill_below_area // scaling an area requires two calls! double min_area = scale_(scale_(this->region().config().solid_infill_below_area.value)); - for (Surfaces::iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) { + for (Surfaces::iterator surface = this->fill_surfaces.surfaces.begin(); + surface != this->fill_surfaces.surfaces.end(); ++surface) { if (surface->has_fill_sparse() && surface->has_pos_internal() && surface->area() <= min_area) surface->surface_type = stPosInternal | stDensSolid; } + // also Apply solid_infill_below_width + double spacing = this->flow(frSolidInfill).spacing(); + coordf_t scaled_spacing = scale_d(spacing); + coordf_t min_half_width = scale_d(this->region().config().solid_infill_below_width.get_abs_value(spacing)) / 2; + if (min_half_width > 0) { + Surfaces srfs_to_add; + for (Surfaces::iterator surface = this->fill_surfaces.surfaces.begin(); + surface != this->fill_surfaces.surfaces.end(); ++surface) { + if (surface->has_fill_sparse() && surface->has_pos_internal()) { + // try to collapse the surface + // grow it a bit more to have an easy time to intersect + ExPolygons results = offset2_ex({surface->expolygon}, -min_half_width - SCALED_EPSILON, + min_half_width + SCALED_EPSILON + + std::min(scaled_spacing / 5, min_half_width / 5)); + // TODO: find a way to have both intersect & cut + ExPolygons cut = diff_ex(ExPolygons{surface->expolygon}, results); + ExPolygons intersect = intersection_ex(ExPolygons{surface->expolygon}, results); + if (intersect.size() == 1 && cut.empty()) + continue; + if (!intersect.empty()) { + //not possible ot have multiple intersect no cut from a single expoly. + assert(!cut.empty()); + surface->expolygon = std::move(intersect[0]); + for (int i = 1; i < intersect.size(); i++) { + srfs_to_add.emplace_back(*surface, std::move(intersect[i])); + } + for (ExPolygon& expoly : cut) { + srfs_to_add.emplace_back(*surface, std::move(expoly)); + srfs_to_add.back().surface_type = stPosInternal | stDensSolid; + } + } else { + //no intersec => all in solid + assert(cut.size() == 1); + surface->surface_type = stPosInternal | stDensSolid; + } + } + } + append(this->fill_surfaces.surfaces, std::move(srfs_to_add)); + } } #ifdef SLIC3R_DEBUG_SLICE_PROCESSING diff --git a/src/libslic3r/MultiPoint.cpp b/src/libslic3r/MultiPoint.cpp index 37c6a8df2de..eac91ece652 100644 --- a/src/libslic3r/MultiPoint.cpp +++ b/src/libslic3r/MultiPoint.cpp @@ -132,7 +132,11 @@ bool MultiPoint::first_intersection(const Line& line, Point* intersection) const { bool found = false; double dmin = 0.; - for (const Line &l : this->lines()) { + Line l; + //for (const Line &l : this->lines()) { + for (size_t idx = 1; idx < points.size(); ++idx) { + l.a = points[idx-1]; + l.b = points[idx]; Point ip; if (l.intersection(line, &ip)) { if (! found) { diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 18e9c1ea03f..6c090953849 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -263,13 +263,13 @@ ProcessSurfaceResult PerimeterGenerator::process_arachne(int& loop_number, const const Polygons last_p = to_polygons(last); Arachne::WallToolPaths wallToolPaths(last_p, this->get_ext_perimeter_spacing(), this->get_ext_perimeter_width(), - this->get_perimeter_spacing(), this->get_perimeter_width(), loop_number + 1, coord_t(0), + this->get_perimeter_spacing(), this->get_perimeter_width(), loop_number, coord_t(0), this->layer->height, *this->object_config, *this->print_config); std::vector perimeters = wallToolPaths.getToolPaths(); #if _DEBUG - for (auto periemter : perimeters) { - for (Arachne::ExtrusionLine &extrusion : periemter) { + for (auto perimeter : perimeters) { + for (Arachne::ExtrusionLine &extrusion : perimeter) { if (extrusion.isZeroLength()) continue; for (Slic3r::Arachne::ExtrusionJunction &junction : extrusion.junctions) { @@ -324,7 +324,7 @@ ProcessSurfaceResult PerimeterGenerator::process_arachne(int& loop_number, const perimeters.insert(perimeters.begin(), out_shell.begin(), out_shell.end()); } - loop_number = int(perimeters.size()) - 1; + loop_number = int(perimeters.size()); #ifdef ARACHNE_DEBUG { @@ -490,10 +490,7 @@ ProcessSurfaceResult PerimeterGenerator::process_arachne(int& loop_number, const if (offset_ex(infill_contour, -float(spacing / 2.)).empty()) infill_contour.clear(); // Infill region is too small, so let's filter it out. - result.inner_perimeter = (loop_number < 0) ? infill_contour : - (loop_number == 0) - ? offset_ex(infill_contour, ext_perimeter_spacing / 2) - : offset_ex(infill_contour, perimeter_spacing / 2); + result.inner_perimeter = infill_contour; return result; } @@ -683,7 +680,7 @@ void PerimeterGenerator::process() // internal flow which is unrelated. <- i don't undertand, so revert to ext_perimeter_spacing2 //const coord_t min_spacing = (coord_t)( perimeter_spacing * (1 - 0.05/*INSET_OVERLAP_TOLERANCE*/) ); //const coord_t ext_min_spacing = (coord_t)( ext_perimeter_spacing2 * (1 - 0.05/*INSET_OVERLAP_TOLERANCE*/) ); - // now the tolerance is built in thin_periemter settings + // now the tolerance is built in thin_perimeter settings // prepare grown lower layer slices for overhang detection if (this->lower_slices != NULL && (this->config->overhangs_width.value > 0 || this->config->overhangs_width_speed.value > 0)) { @@ -765,26 +762,41 @@ void PerimeterGenerator::process() int surface_idx = 0; const int extra_odd_perimeter = (config->extra_perimeters_odd_layers && layer->id() % 2 == 1 ? 1 : 0); for (const Surface& surface : all_surfaces) { - // detect how many perimeters must be generated for this island - int loop_number = this->config->perimeters + surface.extra_perimeters - 1 + extra_odd_perimeter; // 0-indexed loops surface_idx++; - if (print_config->spiral_vase) { - if (layer->id() >= config->bottom_solid_layers) { - loop_number = 0; + // detect how many perimeters must be generated for this island + int nb_loop_contour = this->config->perimeters; + if (nb_loop_contour > 0) + nb_loop_contour += extra_odd_perimeter + surface.extra_perimeters; + int nb_loop_holes = this->config->perimeters_hole; + if (nb_loop_holes > 0) + nb_loop_holes += extra_odd_perimeter + surface.extra_perimeters; + + if (nb_loop_contour < 0) + nb_loop_contour = std::max(0, nb_loop_holes); + if (nb_loop_holes < 0) + nb_loop_holes = std::max(0, nb_loop_contour); + + if (print_config->spiral_vase) { + if (layer->id() >= config->bottom_solid_layers) { + nb_loop_contour = 1; + nb_loop_holes = 0; + } } - } - if ((layer->id() == 0 && this->config->only_one_perimeter_first_layer) || (this->config->only_one_perimeter_top && loop_number > 0 && this->upper_slices == NULL)) { - loop_number = 0; + if ((layer->id() == 0 && this->config->only_one_perimeter_first_layer) || + (this->config->only_one_perimeter_top && this->upper_slices == NULL)) { + nb_loop_contour = std::min(nb_loop_contour, 1); + nb_loop_holes = std::min(nb_loop_holes, 1); } ProcessSurfaceResult surface_process_result; //core generation - if (use_arachne) { - surface_process_result = process_arachne(loop_number, surface); + if (use_arachne && nb_loop_holes == nb_loop_contour) { + surface_process_result = process_arachne(nb_loop_contour, surface); + nb_loop_holes = nb_loop_contour; // nb_loop_contour is in/out } else { - surface_process_result = process_classic(loop_number, surface); + surface_process_result = process_classic(nb_loop_contour, nb_loop_holes, surface); } this->throw_if_canceled(); @@ -793,17 +805,9 @@ void PerimeterGenerator::process() // we offset by half the perimeter spacing (to get to the actual infill boundary) // and then we offset back and forth by half the infill spacing to only consider the // non-collapsing regions - coord_t inset = 0; coord_t infill_peri_overlap = 0; // only apply infill overlap if we actually have one perimeter - if (loop_number >= 0) { - // half infill / perimeter - inset = (loop_number == 0) ? - // one loop - this->get_ext_perimeter_spacing() / 2 : - // two or more loops? - this->get_perimeter_spacing() / 2; - //infill_peri_overlap = scale_t(this->config->get_abs_value("infill_overlap", unscale(perimeter_spacing + solid_infill_spacing) / 2)); + if (nb_loop_contour > 0 || nb_loop_holes > 0) { //give the overlap size to let the infill do his overlap //add overlap if at least one perimeter coordf_t perimeter_spacing_for_encroach = 0; @@ -818,37 +822,31 @@ void PerimeterGenerator::process() infill_peri_overlap = scale_t(this->config->get_abs_value("infill_overlap", perimeter_spacing_for_encroach)); } - //remove gapfill from last - ExPolygons last_no_gaps = (surface_process_result.gap_srf.empty()) ? surface_process_result.inner_perimeter : diff_ex(surface_process_result.inner_perimeter, surface_process_result.gap_srf); - // simplify infill contours according to resolution Polygons not_filled_p; - for (ExPolygon& ex : last_no_gaps) + for (const ExPolygon& ex : surface_process_result.inner_perimeter) ex.simplify_p(scale_t(std::max(this->print_config->resolution.value, print_config->resolution_internal / 4)), ¬_filled_p); ExPolygons not_filled_exp = union_ex(not_filled_p); // collapse too narrow infill areas coord_t min_perimeter_infill_spacing = (coord_t)(this->get_solid_infill_spacing() * (1. - INSET_OVERLAP_TOLERANCE)); ExPolygons infill_exp; + infill_exp = offset2_ex(not_filled_exp, + double(- min_perimeter_infill_spacing / 2 + infill_peri_overlap - this->get_infill_gap()), + double(min_perimeter_infill_spacing / 2)); //special branch if gap : don't inset away from gaps! - if (surface_process_result.gap_srf.empty()) { + ExPolygons gap_fill_exps; + if (!surface_process_result.gap_srf.empty()) { + //not_filled_exp = union_ex(not_filled_p); infill_exp = offset2_ex(not_filled_exp, - double(-inset - min_perimeter_infill_spacing / 2 + infill_peri_overlap - this->get_infill_gap()), + double(- min_perimeter_infill_spacing / 2 + infill_peri_overlap - this->get_infill_gap()), double(min_perimeter_infill_spacing / 2)); - } else { - //store the infill_exp but not offseted, it will be used as a clip to remove the gapfill portion - const ExPolygons infill_exp_no_gap = offset2_ex(not_filled_exp, - double(-inset - min_perimeter_infill_spacing / 2 + infill_peri_overlap - this->get_infill_gap()), - double(inset + min_perimeter_infill_spacing / 2 - infill_peri_overlap + this->get_infill_gap())); - //redo the same as not_filled_exp but with last instead of last_no_gaps + //remove gaps surfaces not_filled_p.clear(); - for (ExPolygon& ex : surface_process_result.inner_perimeter) + for (ExPolygon& ex : surface_process_result.gap_srf) ex.simplify_p(scale_t(std::max(this->print_config->resolution.value, print_config->resolution_internal / 4)), ¬_filled_p); - not_filled_exp = union_ex(not_filled_p); - infill_exp = offset2_ex(not_filled_exp, - double(-inset - min_perimeter_infill_spacing / 2 + infill_peri_overlap - this->get_infill_gap()), - double(min_perimeter_infill_spacing / 2)); - // intersect(growth(surface_process_result.inner_perimeter-gap) , surface_process_result.inner_perimeter), so you have the (surface_process_result.inner_perimeter - small gap) but without voids betweeng gap & surface_process_result.inner_perimeter - infill_exp = intersection_ex(infill_exp, infill_exp_no_gap); + gap_fill_exps = union_ex(not_filled_p); + gap_fill_exps = offset_ex(gap_fill_exps, -infill_peri_overlap); + infill_exp = diff_ex(infill_exp, gap_fill_exps); } this->throw_if_canceled(); @@ -865,12 +863,15 @@ void PerimeterGenerator::process() if (min_perimeter_infill_spacing / 2 > infill_peri_overlap) polyWithoutOverlap = offset2_ex( not_filled_exp, - double(-inset - infill_gap - min_perimeter_infill_spacing / 2 + infill_peri_overlap), + double(- infill_gap - min_perimeter_infill_spacing / 2 + infill_peri_overlap), double(min_perimeter_infill_spacing / 2 - infill_peri_overlap)); else polyWithoutOverlap = offset_ex( not_filled_exp, - double(-inset - this->get_infill_gap())); + double(- this->get_infill_gap())); + if (!gap_fill_exps.empty()) { + polyWithoutOverlap = diff_ex(polyWithoutOverlap, gap_fill_exps); + } if (!surface_process_result.top_fills.empty()) { polyWithoutOverlap = union_ex(polyWithoutOverlap, top_infill_exp); } @@ -920,11 +921,13 @@ void PerimeterGenerator::processs_no_bridge(Surfaces& all_surfaces) { //a detector per island ExPolygons bridgeable; for (ExPolygon unsupported : unsupported_filtered) { - BridgeDetector detector{ unsupported, + BridgeDetector detector( unsupported, lower_island.expolygons, - perimeter_spacing }; + this->overhang_flow.scaled_spacing(), + scale_t(this->print_config->bridge_precision.get_abs_value(this->overhang_flow.spacing())), + this->layer->id()); if (detector.detect_angle(Geometry::deg2rad(this->config->bridge_angle.value))) - expolygons_append(bridgeable, union_ex(detector.coverage(-1, true))); + expolygons_append(bridgeable, union_ex(detector.coverage())); } if (!bridgeable.empty()) { //check if we get everything or just the bridgeable area @@ -1103,7 +1106,243 @@ void PerimeterGenerator::processs_no_bridge(Surfaces& all_surfaces) { } } -ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const Surface& surface) +Polygons get_contours(const ExPolygons &expolys) { + Polygons polys; + for (const ExPolygon &expoly : expolys) { + assert(expoly.contour.is_counter_clockwise()); + polys.push_back(expoly.contour); + } + return polys; +} + +Polygons as_contour(const Polygons &holes) { + Polygons out; + for (const Polygon &hole : holes) { + assert(hole.is_clockwise()); + out.push_back(hole); + out.back().make_counter_clockwise(); + } + return out; +} + +Polygons get_holes_as_contour(const ExPolygon &expoly) { + Polygons polys; + for(const Polygon hole : expoly.holes){ + assert(hole.is_clockwise()); + polys.push_back(hole); + polys.back().make_counter_clockwise(); + } + return polys; +} + +Polygons get_holes_as_contour(const ExPolygons &expolys) { + Polygons polys; + for(const ExPolygon &expoly : expolys) + for(const Polygon hole : expoly.holes){ + assert(hole.is_clockwise()); + polys.push_back(hole); + polys.back().make_counter_clockwise(); + } + return polys; +} + +// expolygon representing the perimeter path +struct ExPolygonAsynch +{ + enum ExPolygonAsynchType { + epatGrowHole, + epatShrinkContour + }; + ExPolygonAsynchType type; + ExPolygon expoly; + // shrink the contour by this value to get the end of the spacing (should be negative, to shrink from centerline or edge) + coordf_t offset_contour_inner; + // shrink the contour by this value to get the external shell (the spacing position) (can be negative to grow from centreline, and be positive to shrink from surface polygon) + coordf_t offset_contour_outer; + // grow the holes by this value to get the end of the spacing (should be negative, to grow from centerline or edge) + coordf_t offset_holes_inner; + // grow the holes by this value to get the external shell (the spacing position) (should be the same value as offset_contour_outer) + coordf_t offset_holes_outer; + +}; + +// next_onion can be partially filled +void grow_holes_only(std::vector &unmoveable_contours, + ExPolygons & next_onion, + coordf_t spacing, + coordf_t overlap_spacing, + bool round_peri, + float min_round_spacing = 3.f) +{ + assert(spacing > 0); + assert(overlap_spacing >= 0); + Polygons new_contours; + for (size_t idx_unmoveable = 0; idx_unmoveable < unmoveable_contours.size(); ++idx_unmoveable) { + ExPolygonAsynch & unmoveable_contour = unmoveable_contours[idx_unmoveable]; + assert(unmoveable_contour.type == ExPolygonAsynch::ExPolygonAsynchType::epatGrowHole); + ExPolygon &expoly = unmoveable_contour.expoly; + assert(unmoveable_contour.offset_holes_inner <= 0); + // grow fake contours, can now have fake holes and/or less fake contours. + Polygons ok_holes = offset(get_holes_as_contour(expoly), + -unmoveable_contour.offset_holes_inner + spacing / 2 + overlap_spacing, + (round_peri ? ClipperLib::JoinType::jtRound : + ClipperLib::JoinType::jtMiter), + (round_peri ? min_round_spacing : 3)); + for (size_t i = 0; i < ok_holes.size(); ++i) { + if (ok_holes[i].is_clockwise()) { + // hole, it's a new peri, move it. + new_contours.push_back(std::move(ok_holes[i])); + ok_holes.erase(ok_holes.begin() + i); + new_contours.back().make_counter_clockwise(); + i--; + } + } + ok_holes = union_(ok_holes); + for (const Polygon &p : ok_holes) assert(p.is_counter_clockwise()); + //shrink contour, can now be multiple contour. + coordf_t computed_offset = unmoveable_contour.offset_contour_inner; + computed_offset -= spacing / 2; + computed_offset -= overlap_spacing; + Polygons ex_contour_offset = offset(Polygons{expoly.contour}, computed_offset); + bool ex_contour_offset_now_fake_hole = false; + for (size_t idx_hole = 0; idx_hole < ok_holes.size(); ++idx_hole) { + const Polygon &hole = ok_holes[idx_hole]; + assert(hole.is_counter_clockwise()); + // Check if it can fuse with contour + // TODO: bounding box for quicker cut search + auto it_contour_candidate_for_fuse = ex_contour_offset.begin(); + Polygons fused_contour; + while (it_contour_candidate_for_fuse != ex_contour_offset.end()) { + ExPolygons result = diff_ex(Polygons{*it_contour_candidate_for_fuse}, Polygons{hole}); + // Only two options here, it can fuse and then there is 1 or more contour, no holes. + // Or it don't touch the contour and so nothing happen. (the hole can be inside or outside) + // SO, we can check it it slip or if the contour has been modified + if (result.size() > 1 || (result.size() == 1 && result.front().contour != *it_contour_candidate_for_fuse)) { + for (ExPolygon &expoly : result) assert(expoly.holes.empty()); + // now use this one. + append(fused_contour, to_polygons(result)); + ex_contour_offset_now_fake_hole = true; + // remove from useful holes + ok_holes.erase(ok_holes.begin() + idx_hole); + idx_hole--; + it_contour_candidate_for_fuse = ex_contour_offset.erase(it_contour_candidate_for_fuse); + } else { + ++it_contour_candidate_for_fuse; + } + } + if(!fused_contour.empty()) + append(ex_contour_offset, std::move(fused_contour)); + } + // if moved from unmoveable_contours to growing_contours, then move the expoly + if (ex_contour_offset_now_fake_hole) { + // add useful holes to the contours, and push them + if (overlap_spacing != 0) + append(next_onion, offset_ex(diff_ex(ex_contour_offset, ok_holes), overlap_spacing)); + else + append(next_onion, diff_ex(ex_contour_offset, ok_holes)); + // remove from unmoveable + unmoveable_contours.erase(unmoveable_contours.begin() + idx_unmoveable); + idx_unmoveable--; + } else { + // update holes + // shrink to centerline + if (overlap_spacing != 0) + ok_holes = offset(ok_holes, -overlap_spacing); + /*test*/ for (const Polygon &p : ok_holes) assert(p.is_counter_clockwise()); + ExPolygons new_unmoveable = diff_ex(Polygons{unmoveable_contour.expoly.contour}, ok_holes); + // check if size is good. It's not possible to split the peri: it isn't srhunk, and holes intersect are alreedy detected (not unmoveable anymore) + assert(new_unmoveable.size() <= 1); + if (new_unmoveable.empty()) { + // remove from unmoveable + unmoveable_contours.erase(unmoveable_contours.begin() + idx_unmoveable); + idx_unmoveable--; + } else if(new_unmoveable.size() == 1){ + // update + unmoveable_contour.expoly = new_unmoveable.front(); + unmoveable_contour.offset_holes_inner = -spacing / 2; + unmoveable_contour.offset_holes_outer = spacing / 2; + } else { + assert(false); + //add all + for(ExPolygon & new_expoly : new_unmoveable) + unmoveable_contours.push_back({unmoveable_contour.type, new_expoly, unmoveable_contour.offset_contour_inner, + unmoveable_contour.offset_contour_outer, -spacing / 2, + spacing / 2}); + // remove from unmoveable + unmoveable_contours.erase(unmoveable_contours.begin() + idx_unmoveable); + idx_unmoveable--; + } + } + } +} + + +// next_onion can be partially filled +void grow_contour_only(std::vector &unmoveable_holes, coordf_t spacing, coordf_t overlap_spacing, bool round_peri, float min_round_spacing = 3.f) { + assert(spacing > 0); + assert(overlap_spacing >= 0); + Polygons new_contours; + // mutable size to allow insert at the same time. + size_t unmoveable_holes_size = unmoveable_holes.size(); + for (size_t idx_unmoveable = 0; idx_unmoveable < unmoveable_holes_size; ++idx_unmoveable) { + ExPolygonAsynch & unmoveable_hole = unmoveable_holes[idx_unmoveable]; + assert(unmoveable_hole.type == ExPolygonAsynch::ExPolygonAsynchType::epatShrinkContour); + ExPolygon &expoly = unmoveable_hole.expoly; + // shrink contour, can now have more contours. + assert(unmoveable_hole.offset_contour_inner <=0); + Polygons ok_contours = offset(expoly.contour, unmoveable_hole.offset_contour_inner - spacing/2 - overlap_spacing, + (round_peri ? ClipperLib::JoinType::jtRound : + ClipperLib::JoinType::jtMiter), + (round_peri ? min_round_spacing : 3)); + //we shrunk -> new peri can appear, holes can disapear, but there is already none. + for (const Polygon &p : ok_contours) assert(p.is_counter_clockwise()); + //grow holes to right size + assert(-unmoveable_hole.offset_holes_inner + spacing/2 - overlap_spacing > 0); + Polygons original_holes = get_holes_as_contour(expoly); + Polygons offsetted_holes = offset(original_holes, -unmoveable_hole.offset_holes_inner + spacing/2 + overlap_spacing); + // remove fake periemter, i don't want them. + for (size_t i = 0; i < offsetted_holes.size(); ++i) { + if (offsetted_holes[i].is_clockwise()) { + offsetted_holes.erase(offsetted_holes.begin() + i); + new_contours.back().make_counter_clockwise(); + i--; + } + } + offsetted_holes = union_(offsetted_holes); + for (const Polygon &p : offsetted_holes) assert(p.is_counter_clockwise()); + + for (Polygon simple_contour : ok_contours) { + // remove holes + ExPolygons test_expoly = diff_ex(Polygons{simple_contour}, offsetted_holes); + if (overlap_spacing != 0) { + test_expoly = offset_ex(test_expoly, overlap_spacing); + } + if (test_expoly.size() == 1) { + // no merge, then i can use the right hole size + ExPolygons new_unmoveable_hole = diff_ex(Polygons{test_expoly[0].contour}, original_holes); + // diff with smaller holes, so it has to be only one contour. + assert(new_unmoveable_hole.size() == 1); + expoly = new_unmoveable_hole[0]; + unmoveable_hole.offset_contour_inner = -spacing / 2; + unmoveable_hole.offset_contour_outer = spacing / 2; + } else { + // a hole cut it, or clear it. + for (ExPolygon &new_expoly : test_expoly) { + ExPolygons new_unmoveable_holes = diff_ex(Polygons{new_expoly.contour}, original_holes); + for(ExPolygon & new_unmoveable_hole : new_unmoveable_holes) + unmoveable_holes.push_back({unmoveable_hole.type, new_unmoveable_hole, -spacing / 2, spacing / 2, + unmoveable_hole.offset_holes_inner, unmoveable_hole.offset_holes_outer}); + } + unmoveable_holes.erase(unmoveable_holes.begin() + idx_unmoveable); + idx_unmoveable--; + unmoveable_holes_size--; + } + } + //we shrink periemter, so it doesn't create holes, so we don't have anythign to add to next_onion. + } +} + +ProcessSurfaceResult PerimeterGenerator::process_classic(int& contour_count, int& holes_count, const Surface& surface) { ProcessSurfaceResult results; ExPolygons gaps; @@ -1114,7 +1353,11 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const ExPolygons last = union_ex(surface.expolygon.simplify_p((resolution < SCALED_EPSILON ? SCALED_EPSILON : resolution))); double last_area = -1; - if (loop_number >= 0) { + // list of Expolygons where contour or holes aren't growing. + std::vector last_asynch; + bool last_asynch_initialized = false; + + if (contour_count > 0 || holes_count > 0) { //increase surface for milling_post-process if (this->mill_extra_size > SCALED_EPSILON) { @@ -1149,11 +1392,13 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const const ExPolygonCollection lower_island(diff_ex(last, overhangs_unsupported)); ExPolygons bridgeable; for (ExPolygon unsupported : overhangs_unsupported) { - BridgeDetector detector{ unsupported, + BridgeDetector detector( unsupported, lower_island.expolygons, - perimeter_spacing }; + this->overhang_flow.scaled_spacing(), + scale_t(this->print_config->bridge_precision.get_abs_value(this->overhang_flow.spacing())), + this->layer->id()); if (detector.detect_angle(Geometry::deg2rad(this->config->bridge_angle.value))) - expolygons_append(bridgeable, union_ex(detector.coverage(-1, true))); + expolygons_append(bridgeable, union_ex(detector.coverage())); } if (!bridgeable.empty()) { //simplify to avoid most of artefacts from printing lines. @@ -1184,9 +1429,9 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const } } - // In case no perimeters are to be generated, loop_number will equal to -1. - std::vector contours(loop_number + 1); // depth => loops - std::vector holes(loop_number + 1); // depth => loops + // In case no perimeters are to be generated, contour_count / holes_count will equal to 0. + std::vector contours(contour_count); // depth => loops + std::vector holes(holes_count); // depth => loops ThickPolylines thin_walls_thickpolys; ExPolygons no_last_gapfill; // we loop one time more than needed in order to find gaps after the last perimeter was applied @@ -1218,48 +1463,68 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const } // Calculate next onion shell of perimeters. - //this variable stored the next onion + // this variable stored the next onion ExPolygons next_onion; + // like next_onion, but with all polygons, even ones that didn't grow and so won't be added as periemter + ExPolygons area_used; + ExPolygons* all_next_onion = &next_onion; + if (perimeter_idx == 0) { // compute next onion // the minimum thickness of a single loop is: // ext_width/2 + ext_spacing/2 + spacing/2 + width/2 - if (thin_perimeter > 0.98) { - next_onion = offset_ex( - last, - -(float)(ext_perimeter_width / 2), - ClipperLib::JoinType::jtMiter, - 3); - } else if (thin_perimeter > 0.01) { - next_onion = offset2_ex( - last, - -(float)(ext_perimeter_width / 2 + (1 - thin_perimeter) * ext_perimeter_spacing / 2 - 1), - +(float)((1 - thin_perimeter) * ext_perimeter_spacing / 2 - 1), - ClipperLib::JoinType::jtMiter, - 3); + coordf_t good_spacing = ext_perimeter_width / 2; + coordf_t overlap_spacing = (1 - thin_perimeter) * ext_perimeter_spacing / 2; + if (holes_count == 0 || contour_count == 0) { + + if (holes_count == 0) { + for (ExPolygon &expoly : last) { + last_asynch.push_back(ExPolygonAsynch{ExPolygonAsynch::ExPolygonAsynchType::epatShrinkContour, expoly, + // inner_offset outer_offset (go spacing limit) + -coordf_t(this->get_perimeter_width() - get_perimeter_spacing())/2, -coordf_t(this->get_perimeter_width() - get_perimeter_spacing())/2, + -coordf_t(this->get_perimeter_width() - get_perimeter_spacing())/2, -coordf_t(this->get_perimeter_width() - get_perimeter_spacing())/2}); + } + last_asynch_initialized = true; + grow_contour_only(last_asynch, get_perimeter_spacing(), 0 /*no overlap for external*/, false /*no round peri for external*/); + } else { + for (ExPolygon &expoly : last) { + last_asynch.push_back(ExPolygonAsynch{ExPolygonAsynch::ExPolygonAsynchType::epatGrowHole, expoly, + // inner_offset outer_offset (go spacing limit) + -coordf_t(this->get_perimeter_width() - get_perimeter_spacing())/2, -coordf_t(this->get_perimeter_width() - get_perimeter_spacing())/2, + -coordf_t(this->get_perimeter_width() - get_perimeter_spacing())/2, -coordf_t(this->get_perimeter_width() - get_perimeter_spacing())/2}); + } + last_asynch_initialized = true; + grow_holes_only(last_asynch, next_onion, get_perimeter_spacing(), 0 /*no overlap for external*/, false /*no round peri for external*/); + } } else { - next_onion = offset2_ex( - last, - -(float)(ext_perimeter_width / 2 + ext_perimeter_spacing / 2 - 1), - +(float)(ext_perimeter_spacing / 2 + 1), - ClipperLib::JoinType::jtMiter, - 3); + if (thin_perimeter > 0.98) { + next_onion = offset_ex(last, -(float) (ext_perimeter_width / 2), + ClipperLib::JoinType::jtMiter, 3); + } else { + coordf_t good_spacing = ext_perimeter_width / 2; + coordf_t overlap_spacing = (1 - thin_perimeter) * ext_perimeter_spacing / 2; + next_onion = offset2_ex(last, -(float) (good_spacing + overlap_spacing - 1), + +(float) (overlap_spacing + 1), ClipperLib::JoinType::jtMiter, 3); + } + if (thin_perimeter < 0.7) { + // offset2_ex can create artifacts, if too big. see superslicer#2428 + next_onion = intersection_ex(next_onion, offset_ex(last, -(float) (ext_perimeter_width / 2), + ClipperLib::JoinType::jtMiter, 3)); + } } - if (thin_perimeter < 0.7) { - //offset2_ex can create artifacts, if too big. see superslicer#2428 - next_onion = intersection_ex(next_onion, - offset_ex( - last, - -(float)(ext_perimeter_width / 2), - ClipperLib::JoinType::jtMiter, - 3)); + + bool special_area = contour_count == 0 || holes_count == 0; + if (special_area && (this->config->thin_walls || m_spiral_vase)) { + area_used = next_onion; + for(auto& expolycontainer : last_asynch) + area_used.push_back(expolycontainer.expoly); + all_next_onion = &area_used; } - - // look for thin walls if (this->config->thin_walls) { + // detect edge case where a curve can be split in multiple small chunks. - if (allow_perimeter_anti_hysteresis) { + if (allow_perimeter_anti_hysteresis && !special_area) { std::vector divs = { 2.1f, 1.9f, 2.2f, 1.75f, 1.5f }; //don't go too far, it's not possible to print thin wall after that size_t idx_div = 0; while (next_onion.size() > last.size() && idx_div < divs.size()) { @@ -1280,7 +1545,7 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const // (actually, something larger than that still may exist due to mitering or other causes) coord_t min_width = scale_t(this->config->thin_walls_min_width.get_abs_value(this->ext_perimeter_flow.nozzle_diameter())); - ExPolygons no_thin_zone = offset_ex(next_onion, double(ext_perimeter_width / 2), jtSquare); + ExPolygons no_thin_zone = offset_ex(*all_next_onion, double(ext_perimeter_width / 2), jtSquare); // medial axis requires non-overlapping geometry ExPolygons thin_zones = diff_ex(last, no_thin_zone, ApplySafetyOffset::Yes); //don't use offset2_ex, because we don't want to merge the zones that have been separated. @@ -1328,7 +1593,7 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const // use perimeters to extrude area that can't be printed by thin walls // it's a bit like re-add thin area into perimeter area. // it can over-extrude a bit, but it's for a better good. - { + if(!special_area) { if (thin_perimeter > 0.98) next_onion = union_ex(next_onion, offset_ex(diff_ex(last, thins, ApplySafetyOffset::Yes), -(float)(ext_perimeter_width / 2), @@ -1355,9 +1620,10 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const next_onion = intersection_ex(next_onion_temp, last); } } - if (m_spiral_vase && next_onion.size() > 1) { + if (m_spiral_vase && all_next_onion->size() > 1) { + assert(contour_count > 0); // Remove all but the largest area polygon. - keep_largest_contour_only(next_onion); + keep_largest_contour_only(*all_next_onion); } } else { //FIXME Is this offset correct if the line width of the inner perimeters differs @@ -1366,7 +1632,7 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const if (thin_perimeter <= 0.98) { // This path will ensure, that the perimeters do not overfill, as in // prusa3d/Slic3r GH #32, but with the cost of rounding the perimeters - // excessively, creating gaps, which then need to be filled in by the not very + // excessively, creating gaps, which then need to be filled in by the not very // reliable gap fill algorithm. // Also the offset2(perimeter, -x, x) may sometimes lead to a perimeter, which is larger than // the original. @@ -1377,22 +1643,20 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const (round_peri ? min_round_spacing : 3)); if (allow_perimeter_anti_hysteresis) { // now try with different min spacing if we fear some hysteresis - //TODO, do that for each polygon from last, instead to do for all of them in one go. + // TODO, do that for each polygon from last, instead to do for all of them in one go. ExPolygons no_thin_onion = offset_ex(last, double(-good_spacing)); if (last_area < 0) { last_area = 0; - for (const ExPolygon& expoly : last) { - last_area += expoly.area(); - } + for (const ExPolygon &expoly : last) { last_area += expoly.area(); } } double new_area = 0; - for (const ExPolygon& expoly : next_onion) { - new_area += expoly.area(); - } + for (const ExPolygon &expoly : next_onion) { new_area += expoly.area(); } - std::vector divs{ 1.8f, 1.6f }; //don't over-extrude, so don't use divider >2 - size_t idx_div = 0; - while ((next_onion.size() > no_thin_onion.size() || (new_area != 0 && last_area > new_area * 100)) && idx_div < divs.size()) { + std::vector divs{1.8f, 1.6f}; // don't over-extrude, so don't use divider >2 + size_t idx_div = 0; + while ((next_onion.size() > no_thin_onion.size() || + (new_area != 0 && last_area > new_area * 100)) && + idx_div < divs.size()) { float div = divs[idx_div]; //use a sightly bigger spacing to try to drastically improve the split, that can lead to very thick gapfill ExPolygons next_onion_secondTry = offset2_ex( @@ -1405,9 +1669,7 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const } else if (next_onion.size() > next_onion_secondTry.size() || last_area > new_area * 100) { // don't get it if it's too small double area_new = 0; - for (const ExPolygon& expoly : next_onion_secondTry) { - area_new += expoly.area(); - } + for (const ExPolygon &expoly : next_onion_secondTry) { area_new += expoly.area(); } if (last_area > area_new * 100 || new_area == 0) { next_onion = next_onion_secondTry; } @@ -1423,14 +1685,42 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const (round_peri ? ClipperLib::JoinType::jtRound : ClipperLib::JoinType::jtMiter), (round_peri ? min_round_spacing : 3)); } + + std::vector *touse; + std::vector copy; + if (perimeter_idx < std::max(contour_count, holes_count)) { + touse = &last_asynch; + } else { + // for gap fill only: use a copy + copy = last_asynch; + touse = © + } + if (contour_count > perimeter_idx && holes_count <= perimeter_idx) { + grow_contour_only(*touse, good_spacing, (1 - thin_perimeter) * perimeter_spacing / 2, + round_peri, min_round_spacing); + } + if (holes_count > perimeter_idx && contour_count <= perimeter_idx) { + grow_holes_only(*touse, next_onion, good_spacing, + (1 - thin_perimeter) * perimeter_spacing / 2, round_peri, min_round_spacing); + } + + bool special_area = contour_count == 0 || holes_count == 0; + if (special_area && (this->config->thin_walls || m_spiral_vase)) { + area_used = next_onion; + for (auto &expolycontainer : *touse) area_used.push_back(expolycontainer.expoly); + all_next_onion = &area_used; + } + + // look for gaps if (this->config->gap_fill_enabled.value //check if we are going to have an other perimeter - && (perimeter_idx <= loop_number || has_overhang || next_onion.empty() || (this->config->gap_fill_last.value && perimeter_idx == loop_number + 1))) { + && (perimeter_idx < std::max(contour_count, holes_count) || has_overhang || all_next_onion->empty() || + (this->config->gap_fill_last.value && perimeter_idx == std::max(contour_count, holes_count)))) { // not using safety offset here would "detect" very narrow gaps // (but still long enough to escape the area threshold) that gap fill // won't be able to fill but we'd still remove from infill area - no_last_gapfill = offset_ex(next_onion, 0.5f * good_spacing + 10, + no_last_gapfill = offset_ex(*all_next_onion, 0.5f * good_spacing + 10, (round_peri ? ClipperLib::JoinType::jtRound : ClipperLib::JoinType::jtMiter), (round_peri ? min_round_spacing : 3)); if (perimeter_idx == 1) { @@ -1455,15 +1745,29 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const // svg.Close(); //} - if (next_onion.empty()) { + if (next_onion.empty() && last_asynch.empty()) { // Store the number of loops actually generated. - loop_number = perimeter_idx - 1; + if (perimeter_idx < contour_count) { + assert(contours.size() == contour_count); + for(size_t i = perimeter_idx; i loop_number) { + } else if (perimeter_idx >= std::max(contour_count, holes_count)) { if (has_overhang) { - loop_number++; + contour_count++; + holes_count++; //TODO: only increase the ones that are needed (or just use 2.7) contours.emplace_back(); holes.emplace_back(); } else { @@ -1471,34 +1775,90 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const break; } } + if (contour_count <= perimeter_idx && !next_onion.empty()) { + assert(contour_count <= perimeter_idx); + assert(holes_count > perimeter_idx); + //assert(contours.size() == perimeter_idx); + contour_count = perimeter_idx + 1; + while (contours.size() < contour_count) { + contours.emplace_back(); + } + } + + assert(contours.size() == contour_count); + assert(holes.size() == holes_count); - // fuzzify + // fuzzify params const bool fuzzify_contours = this->config->fuzzy_skin != FuzzySkinType::None && perimeter_idx == 0 && this->layer->id() > 0; const bool fuzzify_holes = this->config->fuzzy_skin == FuzzySkinType::Shell && perimeter_idx == 0 && this->layer->id() > 0 ; const bool fuzzify_all = this->config->fuzzy_skin == FuzzySkinType::All && this->layer->id() > 0 ; + + //push last_asynch or next_onion into contours & holes + if (!last_asynch.empty()) { + // we already put the last hole, now add contours. + for (auto &exp : last_asynch) { + if (exp.type == ExPolygonAsynch::epatShrinkContour) { + assert(next_onion.empty()); + assert(exp.expoly.contour.is_counter_clockwise()); + if (exp.expoly.contour.length() > SCALED_EPSILON) // TODO: atleastLength + contours[perimeter_idx].emplace_back(exp.expoly.contour, perimeter_idx, true, + has_steep_overhang, fuzzify_contours || fuzzify_all); + } else { + // we already put the last contour, now add holes + // contours from hole collapse is added via next_onion + assert(exp.type == ExPolygonAsynch::epatGrowHole); + for (auto &hole : exp.expoly.holes) { + assert(hole.is_clockwise()); + if(hole.length() > SCALED_EPSILON) // TODO: atleastLength + holes[perimeter_idx].emplace_back(hole, perimeter_idx, false, has_steep_overhang, + fuzzify_contours || fuzzify_all); + } + } + } + } for (const ExPolygon& expolygon : next_onion) { //TODO: add width here to allow variable width (if we want to extrude a sightly bigger perimeter, see thin wall) - contours[perimeter_idx].emplace_back(expolygon.contour, perimeter_idx, true, has_steep_overhang, fuzzify_contours || fuzzify_all); - if (!expolygon.holes.empty()) { + if(contour_count > perimeter_idx && expolygon.contour.length() > SCALED_EPSILON) // TODO: atleastLength + contours[perimeter_idx].emplace_back(expolygon.contour, perimeter_idx, true, has_steep_overhang, fuzzify_contours || fuzzify_all); + if (!expolygon.holes.empty() && holes_count > perimeter_idx) { holes[perimeter_idx].reserve(holes[perimeter_idx].size() + expolygon.holes.size()); for (const Polygon& hole : expolygon.holes) - holes[perimeter_idx].emplace_back(hole, perimeter_idx, false, has_steep_overhang, fuzzify_holes || fuzzify_all); + if(hole.length() > SCALED_EPSILON) // TODO: atleastLength + holes[perimeter_idx].emplace_back(hole, perimeter_idx, false, has_steep_overhang, fuzzify_holes || fuzzify_all); } } //simplify the loop to avoid artifacts when shrinking almost-0 segments resolution = get_resolution(perimeter_idx + 1, false, &surface); last.clear(); - for(ExPolygon& exp : next_onion) + for (ExPolygon &exp : next_onion) exp.simplify((resolution < SCALED_EPSILON ? SCALED_EPSILON : resolution), &last); // store surface for top infill if only_one_perimeter_top - if (perimeter_idx == 0 && (config->only_one_perimeter_top && this->upper_slices != NULL)) { + if (perimeter_idx == 0 && (config->only_one_perimeter_top && this->upper_slices != NULL) + && contour_count > 1 && holes_count > 1) { ExPolygons next; split_top_surfaces(this->lower_slices, this->upper_slices, last, results.top_fills, next, results.fill_clip); last = next; } + + //if next turn we are in asynch mode, move from last to last_asynch + if ( !last_asynch_initialized && ( + (holes_count == perimeter_idx + 1 && contour_count > perimeter_idx + 1) || + (contour_count == perimeter_idx + 1 && holes_count > perimeter_idx + 1))) { + coordf_t last_spacing = perimeter_idx == 0 ? get_ext_perimeter_spacing() / 2 : + get_perimeter_spacing() / 2; + // populate last_asynch from last + for (ExPolygon &expoly : last) { + last_asynch.push_back( + {holes_count == perimeter_idx + 1 ? ExPolygonAsynch::ExPolygonAsynchType::epatShrinkContour : + ExPolygonAsynch::ExPolygonAsynchType::epatGrowHole, + std::move(expoly), -last_spacing, last_spacing, -last_spacing, last_spacing}); + } + last.clear(); + last_asynch_initialized = true; + } } // fuzzify @@ -1506,7 +1866,8 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const // check for extracting extra perimeters from gapfill if (!gaps.empty()) { // if needed, add it to the first empty contour list - const size_t contours_size = loop_number + 1; + const size_t contours_size = contour_count; + assert(contours.size() == contour_count); //first, find loops and try to extract a perimeter from them. for (size_t gap_idx = 0; gap_idx < gaps.size(); gap_idx++) { ExPolygon& expoly = gaps[gap_idx]; @@ -1517,11 +1878,12 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const if (contour_expolygon.size() == 1 && !contour_expolygon.front().holes.empty()) { //OK // update list & variable to let the new perimeter be taken into account - loop_number = contours_size; + contour_count = contours_size + 1; if (contours_size >= contours.size()) { contours.emplace_back(); holes.emplace_back(); } + assert(contours.size() == contour_count); //Add the new perimeter contours[contours_size].emplace_back(contour_expolygon.front().contour, contours_size, true, has_steep_overhang, fuzzify_gapfill); //create the new gapfills @@ -1538,15 +1900,17 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const } } } - + assert(contours.size() == contour_count); + assert(holes.size() == holes_count); // nest loops: holes first - for (int d = 0; d <= loop_number; ++d) { + for (int d = 0; d < holes_count; ++d) { PerimeterGeneratorLoops& holes_d = holes[d]; // loop through all holes having depth == d for (int hole_idx = 0; hole_idx < (int)holes_d.size(); ++hole_idx) { const PerimeterGeneratorLoop& loop = holes_d[hole_idx]; + assert(loop.polygon.length() > SCALED_EPSILON); // find the hole loop that contains this one, if any - for (int t = d + 1; t <= loop_number; ++t) { + for (int t = d + 1; t < holes_count; ++t) { for (int j = 0; j < (int)holes[t].size(); ++j) { PerimeterGeneratorLoop& candidate_parent = holes[t][j]; if (candidate_parent.polygon.contains(loop.polygon.first_point())) { @@ -1558,7 +1922,7 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const } } // if no hole contains this hole, find the contour loop that contains it - for (int t = loop_number; t >= 0; --t) { + for (int t = contours.size() - 1; t >= 0; --t) { for (int j = 0; j < (int)contours[t].size(); ++j) { PerimeterGeneratorLoop& candidate_parent = contours[t][j]; if (candidate_parent.polygon.contains(loop.polygon.first_point())) { @@ -1569,15 +1933,22 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const } } } + // no perimeter, then add the hole like a perimeter. + while(d >= contours.size()) + contours.emplace_back(); + contours[d].push_back(loop); + holes_d.erase(holes_d.begin() + hole_idx); + --hole_idx; NEXT_LOOP:; } } // nest contour loops - for (int d = loop_number; d >= 1; --d) { + for (int d = contours.size() - 1; d >= 1; --d) { PerimeterGeneratorLoops& contours_d = contours[d]; // loop through all contours having depth == d for (int contour_idx = 0; contour_idx < (int)contours_d.size(); ++contour_idx) { const PerimeterGeneratorLoop& loop = contours_d[contour_idx]; + assert(loop.polygon.length() > SCALED_EPSILON); // find the contour loop that contains it for (int t = d - 1; t >= 0; --t) { for (size_t j = 0; j < contours[t].size(); ++j) { @@ -1590,45 +1961,62 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const } } } + //can't find one, put in front + if (contours.front().empty()) { + contours.front().push_back(loop); + } else { + contours.front().front().children.push_back(loop); + } + contours_d.erase(contours_d.begin() + contour_idx); + --contour_idx; NEXT_CONTOUR:; } } + //remove all empty perimeters + while(contours.size() > 1 && contours.front().empty()) + contours.erase(contours.begin()); + // fuse all unfused // at this point, all loops should be in contours[0] (= contours.front() ) + // or no perimeters nor holes have been generated, too small area. + + assert(contours.empty() || contours.front().size() >= 1); // collection of loops to add into loops ExtrusionEntityCollection peri_entities; - if (config->perimeter_loop.value) { - //onlyone_perimeter = >fusion all perimeterLoops - for (PerimeterGeneratorLoop& loop : contours.front()) { - ExtrusionLoop extr_loop = this->_traverse_and_join_loops(loop, get_all_Childs(loop), loop.polygon.points.front()); - //ExtrusionLoop extr_loop = this->_traverse_and_join_loops_old(loop, loop.polygon.points.front(), true); - if (extr_loop.paths.back().polyline.back() != extr_loop.paths.front().polyline.front()) { - extr_loop.paths.back().polyline.append(extr_loop.paths.front().polyline.front()); - assert(false); + if (!contours.empty()) { + if (config->perimeter_loop.value) { + // onlyone_perimeter = >fusion all perimeterLoops + for (PerimeterGeneratorLoop &loop : contours.front()) { + ExtrusionLoop extr_loop = this->_traverse_and_join_loops(loop, get_all_Childs(loop), + loop.polygon.points.front()); + // ExtrusionLoop extr_loop = this->_traverse_and_join_loops_old(loop, loop.polygon.points.front(), true); + if (extr_loop.paths.back().polyline.back() != extr_loop.paths.front().polyline.front()) { + extr_loop.paths.back().polyline.append(extr_loop.paths.front().polyline.front()); + assert(false); + } + peri_entities.append(extr_loop); } - peri_entities.append(extr_loop); - } - // append thin walls - if (!thin_walls_thickpolys.empty()) { + // append thin walls + if (!thin_walls_thickpolys.empty()) { + if (this->object_config->thin_walls_merge) { + _merge_thin_walls(peri_entities, thin_walls_thickpolys); + } else { + peri_entities.append( + Geometry::thin_variable_width(thin_walls_thickpolys, erThinWall, this->ext_perimeter_flow, + std::max(ext_perimeter_width / 4, + scale_t(this->print_config->resolution)), + false)); + } + thin_walls_thickpolys.clear(); + } + } else { if (this->object_config->thin_walls_merge) { + ThickPolylines no_thin_walls; + peri_entities = this->_traverse_loops(contours.front(), no_thin_walls); _merge_thin_walls(peri_entities, thin_walls_thickpolys); } else { - peri_entities.append(Geometry::thin_variable_width( - thin_walls_thickpolys, - erThinWall, - this->ext_perimeter_flow, - std::max(ext_perimeter_width / 4, scale_t(this->print_config->resolution)), - false)); + peri_entities = this->_traverse_loops(contours.front(), thin_walls_thickpolys); } - thin_walls_thickpolys.clear(); - } - } else { - if (this->object_config->thin_walls_merge) { - ThickPolylines no_thin_walls; - peri_entities = this->_traverse_loops(contours.front(), no_thin_walls); - _merge_thin_walls(peri_entities, thin_walls_thickpolys); - } else { - peri_entities = this->_traverse_loops(contours.front(), thin_walls_thickpolys); } } #if _DEBUG @@ -1785,7 +2173,46 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const } } - results.inner_perimeter = last; + if (contour_count == 0 && holes_count == 0) { + // for the infill shell, move it a little bit inside so the extrusion tip don't go over the sides. + results.inner_perimeter = offset_ex(last, -(this->get_perimeter_width() - get_perimeter_spacing()) / 2); + } else { + coordf_t last_spacing = std::max(contour_count, holes_count) == 1 ? + get_ext_perimeter_spacing() / 2 : + get_perimeter_spacing() / 2; + results.inner_perimeter = offset_ex(last, -last_spacing); + if (!last_asynch.empty()) { + // merge with last_async + for (auto &exp : last_asynch) { + if (exp.offset_contour_inner == exp.offset_holes_inner) { + append(results.inner_perimeter, offset_ex(exp.expoly, exp.offset_contour_inner)); + } else { + // offset contour & holes separatly + // first holes: + assert(exp.offset_holes_inner <= 0); + Polygons holes = offset(get_holes_as_contour(exp.expoly), -exp.offset_holes_inner); + // we are growing (fake) perimeter, so it can creates holes. + for (size_t i = 0; i < holes.size(); ++i) { + Polygon &fakeperi = holes[i]; + if (fakeperi.is_clockwise()) { + // put real perimeters in results.inner_perimeter + fakeperi.make_counter_clockwise(); + results.inner_perimeter.push_back(ExPolygon(fakeperi)); + holes.erase(holes.begin() + i); + i--; + } + } + // now shrink perimeter + Polygons perimeters = offset(exp.expoly.contour, exp.offset_contour_inner); + // as it shrink, it can creates more perimeter, not a big deal. + for (auto &p : perimeters) assert(p.is_counter_clockwise()); + + // now diff and add + append(results.inner_perimeter, diff_ex(perimeters, holes)); + } + } + } + } return results; } @@ -3222,7 +3649,7 @@ void PerimeterGenerator::_merge_thin_walls(ExtrusionEntityCollection &extrusions entity->visit(*this); } }; - //max dist to branch: ~half external periemeter width + //max dist to branch: ~half external perimeter width coord_t max_width = this->ext_perimeter_flow.scaled_width(); SearchBestPoint searcher; ThickPolylines not_added; diff --git a/src/libslic3r/PerimeterGenerator.hpp b/src/libslic3r/PerimeterGenerator.hpp index b0d36ca9a7b..b19cdc49dbd 100644 --- a/src/libslic3r/PerimeterGenerator.hpp +++ b/src/libslic3r/PerimeterGenerator.hpp @@ -80,7 +80,7 @@ class PerimeterGenerator { Layer *layer; Flow perimeter_flow; Flow ext_perimeter_flow; - Flow overhang_flow; + Flow overhang_flow; // ie bridging flow Flow solid_infill_flow; const PrintRegionConfig *config; const PrintObjectConfig *object_config; @@ -153,7 +153,7 @@ class PerimeterGenerator { ExPolygons unmillable; coord_t mill_extra_size; - ProcessSurfaceResult process_classic(int& loop_number, const Surface& surface); + ProcessSurfaceResult process_classic(int& contour_count, int& holes_count, const Surface& surface); ProcessSurfaceResult process_arachne(int& loop_number, const Surface& surface); void processs_no_bridge(Surfaces& all_surfaces); diff --git a/src/libslic3r/Polygon.cpp b/src/libslic3r/Polygon.cpp index 8badf41ea72..c10dcb55a6b 100644 --- a/src/libslic3r/Polygon.cpp +++ b/src/libslic3r/Polygon.cpp @@ -195,21 +195,42 @@ Points Polygon::concave_points(double angle) const return points; } +std::vector Polygon::concave_points_idx(double angle) const +{ + std::vector points_idx; + angle = 2. * PI - angle + EPSILON; + + // check whether first point forms a concave angle + if (this->points.front().ccw_angle(this->points.back(), *(this->points.begin() + 1)) <= angle) + points_idx.push_back(0); + + // check whether points 1..(n-1) form concave angles + for (size_t idx = 1; idx != this->points.size() - 1; ++idx) + if (points[idx].ccw_angle(points[idx - 1], points[idx + 1]) <= angle) + points_idx.push_back(idx); + + // check whether last point forms a concave angle + if (this->points.back().ccw_angle(*(this->points.end() - 2), this->points.front()) <= angle) + points_idx.push_back(this->points.size() - 1); + + return points_idx; +} + // find all convex vertices (i.e. having an internal angle smaller than the supplied angle) // (external = right side, thus we consider ccw orientation) Points Polygon::convex_points(double angle) const { Points points; - angle = 2*PI - angle - EPSILON; + angle = 2 * PI - angle - EPSILON; // check whether first point forms a convex angle if (this->points.front().ccw_angle(this->points.back(), *(this->points.begin()+1)) >= angle) points.push_back(this->points.front()); // check whether points 1..(n-1) form convex angles - for (Points::const_iterator p = this->points.begin()+1; p != this->points.end()-1; ++p) { - if (p->ccw_angle(*(p-1), *(p+1)) >= angle) points.push_back(*p); - } + for (Points::const_iterator p = this->points.begin() + 1; p != this->points.end() - 1; ++p) + if (p->ccw_angle(*(p - 1), *(p + 1)) >= angle) + points.push_back(*p); // check whether last point forms a convex angle if (this->points.back().ccw_angle(*(this->points.end()-2), this->points.front()) >= angle) @@ -218,6 +239,28 @@ Points Polygon::convex_points(double angle) const return points; } + +std::vector Polygon::convex_points_idx(double angle) const +{ + std::vector points_idx; + angle = 2. * PI - angle - EPSILON; + + // check whether first point forms a convex angle + if (this->points.front().ccw_angle(this->points.back(), *(this->points.begin() + 1)) >= angle) + points_idx.push_back(0); + + // check whether points 1..(n-1) form convex angles + for (size_t idx = 1; idx != this->points.size() - 1; ++idx) + if (points[idx].ccw_angle(points[idx - 1], points[idx + 1]) >= angle) + points_idx.push_back(idx); + + // check whether last point forms a convex angle + if (this->points.back().ccw_angle(*(this->points.end() - 2), this->points.front()) >= angle) + points_idx.push_back(this->points.size() - 1); + + return points_idx; +} + // Projection of a point onto the polygon. Point Polygon::point_projection(const Point &point) const { diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index b4127d6adae..137c4f69115 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -69,6 +69,8 @@ class Polygon : public MultiPoint Point centroid() const; Points concave_points(double angle = PI) const; Points convex_points(double angle = PI) const; + std::vector concave_points_idx(double angle = PI) const; + std::vector convex_points_idx(double angle = PI) const; // Projection of a point onto the polygon. Point point_projection(const Point &point) const; std::vector parameter_by_length() const; diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index 8666eaa4b1e..06ffb8ea164 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -81,8 +81,8 @@ class Polyline : public MultiPoint { bool is_closed() const { return this->points.front() == this->points.back(); } }; -inline bool operator==(const Polyline &lhs, const Polyline &rhs) { return lhs.points == rhs.points; } -inline bool operator!=(const Polyline &lhs, const Polyline &rhs) { return lhs.points != rhs.points; } +//inline bool operator==(const Polyline &lhs, const Polyline &rhs) { return lhs.points == rhs.points; } +//inline bool operator!=(const Polyline &lhs, const Polyline &rhs) { return lhs.points != rhs.points; } // Don't use this class in production code, it is used exclusively by the Perl binding for unit tests! #ifdef PERL_UCHAR_MIN diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index ca667080cfa..78750f24bfc 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -449,7 +449,10 @@ void Preset::set_visible_from_appconfig(const AppConfig &app_config) static std::vector s_Preset_print_options { "layer_height", - "first_layer_height", "perimeters", "spiral_vase", + "first_layer_height", + "perimeters", + "perimeters_hole", + "spiral_vase", "slice_closing_radius", "slicing_mode", "top_solid_layers", @@ -472,6 +475,9 @@ static std::vector s_Preset_print_options { "thin_perimeters", "thin_perimeters_all", "overhangs_speed", "overhangs_speed_enforce", + "overhangs_max_slope", + "overhangs_bridge_threshold", + "overhangs_bridge_upper_layers", "overhangs_width", "overhangs_width_speed", "overhangs_reverse", @@ -515,6 +521,8 @@ static std::vector s_Preset_print_options { "fill_angle_template", "bridge_angle", "solid_infill_below_area", + "solid_infill_below_layer_area", + "solid_infill_below_width", "only_retract_when_crossing_perimeters", "enforce_retract_first_layer", "infill_first", "avoid_crossing_perimeters_max_detour", @@ -659,6 +667,7 @@ static std::vector s_Preset_print_options { "bridge_flow_ratio", "bridge_type", "solid_infill_overlap", + "top_solid_infill_overlap", "infill_anchor", "infill_anchor_max", "clip_multipart_objects", @@ -703,12 +712,14 @@ static std::vector s_Preset_print_options { "model_precision", "resolution", "resolution_internal", + "bridge_precision", "gcode_resolution", //TODO what to do with it? "curve_smoothing_precision", "curve_smoothing_cutoff_dist", "curve_smoothing_angle_convex", "curve_smoothing_angle_concave", "print_extrusion_multiplier", + "print_first_layer_temperature", "print_retract_length", "print_temperature", "print_retract_lift", @@ -1543,7 +1554,9 @@ inline t_config_option_keys deep_diff(const ConfigBase &config_this, const Confi && (ignore_phony || !(this_opt->is_phony() && other_opt->is_phony())) && ((*this_opt != *other_opt) || (this_opt->is_phony() != other_opt->is_phony()))) { - if (opt_key == "bed_shape" || opt_key == "compatible_prints" || opt_key == "compatible_printers" || opt_key == "filament_ramming_parameters" || opt_key == "gcode_substitutions") { + //if (opt_key == "bed_shape" || opt_key == "compatible_prints" || opt_key == "compatible_printers" || + // opt_key == "filament_ramming_parameters" || opt_key == "gcode_substitutions") { + if (this_opt->is_vector() && !(static_cast(this_opt)->is_extruder_size())) { // Scalar variable, or a vector variable, which is independent from number of extruders, // thus the vector is presented to the user as a single input. // Merill: these are 'button' special settings. diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index dd21018f5c6..ea70c4f0382 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -93,6 +93,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver& /* ne "extruder_clearance_height", "extruder_clearance_radius", "extruder_colour", + "extruder_extrusion_multiplier_speed", "extruder_offset", "extruder_fan_offset" "extruder_temperature_offset", @@ -247,7 +248,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver& /* ne ) { steps.emplace_back(psSkirtBrim); } else if ( - opt_key == "filament_shrink" + opt_key == "bridge_precision" + || opt_key == "filament_shrink" || opt_key == "first_layer_height" || opt_key == "nozzle_diameter" || opt_key == "model_precision" diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 96a3804ff5e..d446bba0fd6 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -392,8 +392,10 @@ class PrintObject : public PrintObjectBaseWithState filament_stats; + std::vector> layer_area_stats; // print_z to area // Config with the filled in print statistics. DynamicConfig config() const; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index c9abe3b6d24..c30aa24a8e5 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -816,6 +816,16 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvancedE | comSuSi; def->set_default_value(new ConfigOptionPercent(100)); + def = this->add("bridge_precision", coFloatOrPercent); + def->label = L("Bridge precision"); + def->category = OptionCategory::slicing; + def->tooltip = L("This is the precision of the bridge detection. If you put it too low, the bridge detection will be very inneficient." + "\nCan be a % of the bridge spacing."); + def->sidetext = L("mm or %"); + def->min = 0; + def->mode = comAdvancedE | comSuSi; + def->set_default_value(new ConfigOptionFloatOrPercent(25, true)); + def = this->add("bridge_overlap_min", coPercent); def->label = L("Min"); def->full_label = L("Min bridge density"); @@ -1747,6 +1757,14 @@ void PrintConfigDef::init_fff_params() def->is_vector_extruder = true; def->set_default_value(new ConfigOptionStrings{ "" }); + def = this->add("extruder_extrusion_multiplier_speed", coStrings); + def->label = L("Extrusion multipler"); + def->tooltip = L("This string is edited by a Dialog and contains extusion multiplier for different speeds."); + def->mode = comExpert | comSuSi; + def->is_vector_extruder = true; + def->set_default_value(new ConfigOptionStrings { "0 1 1 1 1 1 1 1 1 1 1 1|" + " 10 1. 20 1. 30 1. 40 1. 60 1. 80 1. 120 1. 160 1. 240 1. 320 1. 480 1. 640 1. 960 1. 1280 1." }); + def = this->add("extruder_offset", coPoints); def->label = L("Extruder offset"); def->category = OptionCategory::extruders; @@ -2193,7 +2211,8 @@ void PrintConfigDef::init_fff_params() def = this->add("filament_max_overlap", coPercents); def->label = L("Max line overlap"); def->tooltip = L("This setting will ensure that all 'overlap' are not higher than this value." - " This is useful for filaments that are too viscous, as the line can't flow under the previous one."); + " This is useful for filaments that are too viscous, as the line can't flow under the previous one." + "\nNote: top solid infill lines are excluded, to prevent visual defects."); def->sidetext = L("%"); def->ratio_over = ""; def->min = 0; @@ -3099,7 +3118,7 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back(L("Connected to outer perimeters")); def->enum_labels.push_back(L("Not connected")); def->mode = comExpert | comSuSi; - def->set_default_value(new ConfigOptionEnum(icConnected)); + def->set_default_value(new ConfigOptionEnum(icNotConnected)); def = this->add("infill_connection_solid", coEnum); def->label = L("Connection of solid infill lines"); @@ -3978,6 +3997,42 @@ void PrintConfigDef::init_fff_params() def->is_vector_extruder = true; def->set_default_value(new ConfigOptionInts{ -1 }); + def = this->add("overhangs_max_slope", coFloatOrPercent); + def->label = L("Overhangs max slope"); + def->full_label = L("Overhangs max slope"); + def->category = OptionCategory::slicing; + def->tooltip = L("Maximum slope for overhangs. if at each layer, the overhangs hangs by more than this value, then the geometry will be cut." + " It doesn't cut into detected bridgeable areas." + "\nCan be a % of the highest nozzle diameter." + "\nSet to 0 to disable."); + def->sidetext = L("mm or %"); + def->ratio_over = "nozzle_diameter"; + def->min = 0; + def->mode = comExpert | comSuSi; + def->set_default_value(new ConfigOptionFloatOrPercent(0, false)); + + def = this->add("overhangs_bridge_threshold", coFloat); + def->label = L("Bridge max length"); + def->category = OptionCategory::slicing; + def->tooltip = L("Maximum distance for bridges. If the distance is over that, it will be considered as overhangs for 'overhangs_max_slope'." + "\nSet to -1 to accept all distances." + "\nSet to 0 to ignore bridges."); + def->sidetext = L("mm"); + def->min = -1; + def->mode = comExpert | comSuSi; + def->set_default_value(new ConfigOptionFloat(-1)); + + def = this->add("overhangs_bridge_upper_layers", coInt); + def->label = L("Consider upper bridges"); + def->category = OptionCategory::slicing; + def->tooltip = L("Don't put overhangs if the area will filled in next layer by bridges." + "\nSet to -1 to accept all upper layers." + "\nSet to 0 to only consider our layer bridges."); + def->sidetext = L("layers"); + def->min = -1; + def->mode = comExpert | comSuSi; + def->set_default_value(new ConfigOptionInt(0)); + def = this->add("overhangs_speed", coFloatOrPercent); def->label = L("Overhangs"); def->full_label = L("Overhangs speed"); @@ -4269,8 +4324,10 @@ void PrintConfigDef::init_fff_params() def->label = L("Perimeters"); def->full_label = L("Perimeters count"); def->category = OptionCategory::perimeter; - def->tooltip = L("This option sets the number of perimeters to generate for each layer. " - "Note that Slic3r may increase this number automatically when it detects " + def->tooltip = L("This option sets the number of perimeters to generate for each layer." + "\nIf perimeters_hole is activated, then this number is only for contour periemters." + "Note that if a contour perimeter encounter a hole, it will go around like a hole perimeter." + "\nNote that Slic3r may increase this number automatically when it detects " "sloping surfaces which benefit from a higher number of perimeters " "if the Extra Perimeters option is enabled."); def->sidetext = L("(minimum)."); @@ -4280,6 +4337,21 @@ void PrintConfigDef::init_fff_params() def->mode = comSimpleAE | comPrusa; def->set_default_value(new ConfigOptionInt(3)); + def = this->add("perimeters_hole", coInt); + def->label = L("Max perimeter count for holes"); + def->category = OptionCategory::perimeter; + def->tooltip = L("This option sets the number of perimeters to have over holes." + " Note that if a hole-perimeter fuse with the contour, then it will go around like a contour perimeter.." + "\nSet to -1 to deactivate, then holes will have the same number of perimeters as contour." + "\nNote that Slic3r may increase this number automatically when it detects " + "sloping surfaces which benefit from a higher number of perimeters " + "if the Extra Perimeters option is enabled."); + def->sidetext = L("(minimum)."); + def->min = -1; + def->max = 10000; + def->mode = comAdvancedE | comSuSi; + def->set_default_value(new ConfigOptionInt(-1)); + def = this->add("post_process", coStrings); def->label = L("Post-processing scripts"); def->category = OptionCategory::customgcode; @@ -4963,11 +5035,32 @@ void PrintConfigDef::init_fff_params() def->mode = comExpert | comPrusa; def->set_default_value(new ConfigOptionFloat(70)); + def = this->add("solid_infill_below_layer_area", coFloat); + def->label = L("Solid infill layer threshold area"); + def->category = OptionCategory::infill; + def->tooltip = L("Force solid infill for the whole layer when the combined area of all objects that are printed at the same layer is smaller than this value."); + def->sidetext = L("mm²"); + def->min = 0; + def->mode = comExpert | comSuSi; + def->set_default_value(new ConfigOptionFloat(0)); + + def = this->add("solid_infill_below_width", coFloatOrPercent); + def->label = L("Solid infill threshold width"); + def->category = OptionCategory::infill; + def->tooltip = L("Force solid infill for parts of regions having a smaller width than the specified threshold." + "\nCan be a % of the current solid infill spacing." + "\nSet 0 to disable"); + def->sidetext = L("mm or %"); + def->min = 0; + def->mode = comExpert | comSuSi; + def->set_default_value(new ConfigOptionFloatOrPercent(0, false)); + def = this->add("solid_infill_overlap", coPercent); - def->label = L("Solid fill overlap"); + def->label = L("Solid infill overlap"); def->category = OptionCategory::width; def->tooltip = L("This setting allows you to reduce the overlap between the lines of the solid fill, to reduce the % filled if you see overextrusion signs on solid areas." - " Note that you should be sure that your flow (filament extrusion multiplier) is well calibrated and your filament max overlap is set before thinking to modify this."); + " Note that you should be sure that your flow (filament extrusion multiplier) is well calibrated and your filament max overlap is set before thinking to modify this." + "\nNote: top surfaces are still extruded with 100% overlap to prevent gaps."); def->sidetext = L("%"); def->min = 0; def->max = 100; @@ -5703,6 +5796,15 @@ void PrintConfigDef::init_fff_params() def->category = OptionCategory::filament; def->tooltip = L("Override the temperature of the extruder. Avoid making too many changes, it won't stop for cooling/heating. 0 to disable. May only work on Height range modifiers."); def->mode = comExpert | comSuSi; + def->min = 0; + def->set_default_value(new ConfigOptionInt(0)); + + def = this->add("print_first_layer_temperature", coInt); + def->label = L("Temperature"); + def->category = OptionCategory::filament; + def->tooltip = L("Override the temperature of the extruder (for the first layer). Avoid making too many changes, it won't stop for cooling/heating. 0 to disable (using print_temperature if defined). May only work on Height range modifiers."); + def->mode = comExpert | comSuSi; + def->min = 0; def->set_default_value(new ConfigOptionInt(0)); def = this->add("print_retract_lift", coFloat); @@ -5941,6 +6043,19 @@ void PrintConfigDef::init_fff_params() def->mode = comExpert | comSuSi; def->set_default_value(new ConfigOptionFloatOrPercent(0,false)); + def = this->add("top_solid_infill_overlap", coPercent); + def->label = L("Top solid infill overlap"); + def->category = OptionCategory::width; + def->tooltip = L("This setting allows you to reduce the overlap between the lines of the top solid fill, to reduce the % filled if you see overextrusion signs on solid areas." + "\nNote that you should be sure that your flow (filament extrusion multiplier) is well calibrated and your filament max overlap is set before thinking to modify this." + "\nAlso, lowering it below 100% may create visible gaps in the top surfaces" + "\nSet overlap setting is the only one that can't be reduced by the filament's max overlap."); + def->sidetext = L("%"); + def->min = 0; + def->max = 100; + def->mode = comExpert | comSuSi; + def->set_default_value(new ConfigOptionPercent(100)); + def = this->add("top_solid_infill_speed", coFloatOrPercent); def->label = L("Top solid"); def->full_label = L("Top solid speed"); @@ -6554,6 +6669,7 @@ void PrintConfigDef::init_extruder_option_keys() "default_filament_profile", "deretract_speed", "extruder_colour", + "extruder_extrusion_multiplier_speed", "extruder_fan_offset", "extruder_offset", "extruder_temperature_offset", @@ -7831,6 +7947,38 @@ std::map PrintConfigDef::from_prusa(t_config_option_key if (value == "PNG") output["thumbnails_tag_format"] = "0"; } + + if ("thumbnails" == opt_key) { + //check if their format is inside the size + if (value.find('/') != std::string::npos) { + std::vector sizes; + boost::split(sizes, value, boost::is_any_of(","), boost::token_compress_off); + value = ""; + std::string coma = ""; + size_t added = 0; + for (std::string &size : sizes) { + size_t pos = size.find('/'); + assert(pos != std::string::npos); + if (pos != std::string::npos) { + assert(size.find('/', pos + 1) == std::string::npos); + value = value + coma + size.substr(0, pos); + } else { + value = value + coma + size; + } + coma = ","; + added++; + if (added >= 2) + break; + } + //if less than 2: add 0X0 until two. + while (added < 2) { + value = value + coma + "0x0"; + coma = ","; + added++; + } + // format (the first) is still set by prusa, no need to parse it. + } + } // ---- custom gcode: ---- @@ -7858,6 +8006,11 @@ std::map PrintConfigDef::from_prusa(t_config_option_key return output; } +//keys that needs to go through from_prusa before beeing deserialized. +std::unordered_set prusa_import_to_review_keys = +{ + "thumbnails" +}; template void _convert_from_prusa(CONFIG_CLASS& conf, const DynamicPrintConfig& global_config, bool with_phony) { @@ -7933,7 +8086,7 @@ void _deserialize_maybe_from_prusa(const std::maphas(opt_key)) { + if (!def->has(opt_key) || (check_prusa && prusa_import_to_review_keys.find(opt_key) != prusa_import_to_review_keys.end())) { unknown_keys.emplace(key, value); } else { config.set_deserialize(opt_key, opt_value, config_substitutions); @@ -8069,6 +8222,7 @@ std::unordered_set prusa_export_to_remove_keys = { "bridge_fill_pattern", "bridge_internal_acceleration", "bridge_internal_fan_speed", +"bridge_precision", "bridge_overlap", "bridge_overlap_min", "bridge_speed_internal", @@ -8105,6 +8259,7 @@ std::unordered_set prusa_export_to_remove_keys = { "external_perimeters_vase", "extra_perimeters_odd_layers", "extra_perimeters_overhangs", +"extruder_extrusion_multiplier_speed", "extruder_fan_offset", "extruder_temperature_offset", "extrusion_spacing", @@ -8160,6 +8315,7 @@ std::unordered_set prusa_export_to_remove_keys = { "gcode_precision_e", "gcode_precision_xyz", "hole_size_compensation", +"hole_size_compensations_curve", "hole_size_threshold", "hole_to_polyhole_threshold", "hole_to_polyhole_twisted", @@ -8202,6 +8358,9 @@ std::unordered_set prusa_export_to_remove_keys = { "over_bridge_flow_ratio", "overhangs_acceleration", "overhangs_fan_speed", +"overhangs_max_slope", +"overhangs_bridge_threshold", +"overhangs_bridge_upper_layers", "overhangs_reverse_threshold", "overhangs_reverse", "overhangs_speed_enforce", @@ -8217,6 +8376,7 @@ std::unordered_set prusa_export_to_remove_keys = { "perimeter_reverse", "perimeter_round_corners", "print_extrusion_multiplier", +"print_first_layer_temperature", "print_custom_variables", "print_retract_length", "print_retract_lift", @@ -8255,6 +8415,8 @@ std::unordered_set prusa_export_to_remove_keys = { "solid_infill_fan_speed", "solid_infill_overlap", "start_gcode_manual", +"solid_infill_below_layer_area", +"solid_infill_below_thickness", "support_material_angle_height", "support_material_acceleration", "support_material_contact_distance_type", @@ -8287,6 +8449,7 @@ std::unordered_set prusa_export_to_remove_keys = { "top_fan_speed", "top_infill_extrusion_spacing", "top_solid_infill_acceleration", +"top_solid_infill_overlap", "travel_acceleration", "travel_deceleration_use_target", "travel_speed_z", @@ -8486,6 +8649,26 @@ std::map PrintConfigDef::to_prusa(t_config_option_key& new_entries["fan_always_on"] = "1"; } } + + if ("thumbnails" == opt_key) { + // add format to thumbnails + const ConfigOptionEnum *format_opt = all_conf.option>("thumbnails_format"); + std::string format = format_opt->serialize(); + std::vector sizes; + boost::split(sizes, value, boost::is_any_of(","), boost::token_compress_off); + value = ""; + std::string coma = ""; + for (std::string &size : sizes) { + //if first or second dimension is 0: ignore. + size_t test1 = size.find("0x"); + size_t test2 = size.find("x0"); + if (size.find("0x") == 0 || size.find("x0") + 2 == size.size()) + continue; + assert(size.find('/') == std::string::npos); + value = value + coma + size + std::string("/") + format; + coma = ","; + } + } // ---- custom gcode: ---- static const std::vector> custom_gcode_replace = @@ -8877,7 +9060,9 @@ std::set DynamicPrintConfig::value_changed(const t_co return { this }; return {}; } - if (opt_key == "filament_max_overlap" || opt_key == "perimeter_overlap" || opt_key == "external_perimeter_overlap" || opt_key == "solid_infill_overlap") { + if (opt_key == "filament_max_overlap" || opt_key == "perimeter_overlap" || + opt_key == "external_perimeter_overlap" || opt_key == "solid_infill_overlap" || + opt_key == "top_solid_infill_overlap") { for (auto conf : config_collection) { if (conf->option("extrusion_width")) if (!conf->update_phony(config_collection).empty()) @@ -8998,7 +9183,7 @@ std::set DynamicPrintConfig::value_changed(const t_co } } if (opt_key == "top_infill_extrusion_spacing") { - const ConfigOptionPercent* solid_infill_overlap_option = find_option("solid_infill_overlap", this, config_collection); + const ConfigOptionPercent* top_solid_infill_overlap_option = find_option("top_solid_infill_overlap", this, config_collection); ConfigOptionFloatOrPercent* width_option = this->option("top_infill_extrusion_width"); if (width_option) { width_option->set_phony(true); @@ -9006,7 +9191,7 @@ std::set DynamicPrintConfig::value_changed(const t_co if (spacing_value == 0) width_option->value = 0; else { - float spacing_ratio = (std::min(flow.spacing_ratio(), float(solid_infill_overlap_option->get_abs_value(1)))); + float spacing_ratio = (std::min(flow.spacing_ratio(), float(top_solid_infill_overlap_option->get_abs_value(1)))); flow = flow.with_width(spacing_option->get_abs_value(max_nozzle_diameter) + layer_height_option->value * (1. - 0.25 * PI) * spacing_ratio); width_option->value = (spacing_option->percent) ? std::round(100 * flow.width() / max_nozzle_diameter) : (std::round(flow.width() * 10000) / 10000); } @@ -9156,7 +9341,7 @@ std::set DynamicPrintConfig::value_changed(const t_co } } if (opt_key == "top_infill_extrusion_width") { - const ConfigOptionPercent* solid_infill_overlap_option = find_option("solid_infill_overlap", this, config_collection); + const ConfigOptionPercent* top_solid_infill_overlap_option = find_option("top_solid_infill_overlap", this, config_collection); spacing_option = this->option("top_infill_extrusion_spacing"); if (width_option) { width_option->set_phony(false); @@ -9167,7 +9352,7 @@ std::set DynamicPrintConfig::value_changed(const t_co Flow flow = Flow::new_from_config_width(FlowRole::frTopSolidInfill, width_option->value == 0 ? *default_width_option : *width_option, *spacing_option, max_nozzle_diameter, layer_height_option->value, - std::min(overlap_ratio, float(solid_infill_overlap_option->get_abs_value(1.))), 0); + std::min(overlap_ratio, float(top_solid_infill_overlap_option->get_abs_value(1.))), 0); if (flow.width() < flow.height()) flow = flow.with_height(flow.width()); spacing_option->value = (width_option->percent) ? std::round(100 * flow.spacing() / max_nozzle_diameter) : (std::round(flow.spacing() * 10000) / 10000); } diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index d08278c77e1..f2eba2b63db 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -909,6 +909,9 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, milling_speed)) ((ConfigOptionFloatOrPercent, min_width_top_surface)) // Detect bridging perimeters + ((ConfigOptionFloatOrPercent, overhangs_max_slope)) + ((ConfigOptionFloat, overhangs_bridge_threshold)) + ((ConfigOptionInt, overhangs_bridge_upper_layers)) ((ConfigOptionFloatOrPercent, overhangs_speed)) ((ConfigOptionInt, overhangs_speed_enforce)) ((ConfigOptionFloatOrPercent, overhangs_width)) @@ -928,6 +931,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloatOrPercent, perimeter_speed)) // Total number of perimeters. ((ConfigOptionInt, perimeters)) + ((ConfigOptionInt, perimeters_hole)) ((ConfigOptionPercent, print_extrusion_multiplier)) ((ConfigOptionFloat, print_retract_length)) ((ConfigOptionFloat, print_retract_lift)) @@ -936,6 +940,8 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloatOrPercent, small_perimeter_max_length)) ((ConfigOptionEnum, solid_fill_pattern)) ((ConfigOptionFloat, solid_infill_below_area)) + ((ConfigOptionFloat, solid_infill_below_layer_area)) + ((ConfigOptionFloatOrPercent, solid_infill_below_width)) ((ConfigOptionInt, solid_infill_extruder)) ((ConfigOptionFloatOrPercent, solid_infill_extrusion_width)) ((ConfigOptionFloatOrPercent, solid_infill_extrusion_spacing)) @@ -944,6 +950,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloatOrPercent, solid_infill_speed)) ((ConfigOptionPercent, solid_infill_overlap)) ((ConfigOptionInt, solid_over_perimeters)) + ((ConfigOptionInt, print_first_layer_temperature)) ((ConfigOptionInt, print_temperature)) ((ConfigOptionPercent, thin_perimeters)) ((ConfigOptionPercent, thin_perimeters_all)) @@ -956,6 +963,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloatOrPercent, top_infill_extrusion_spacing)) ((ConfigOptionInt, top_solid_layers)) ((ConfigOptionFloat, top_solid_min_thickness)) + ((ConfigOptionPercent, top_solid_infill_overlap)) ((ConfigOptionFloatOrPercent, top_solid_infill_speed)) ((ConfigOptionBool, wipe_into_infill)) @@ -1024,6 +1032,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionString, end_gcode)) ((ConfigOptionStrings, end_filament_gcode)) ((ConfigOptionFloat, extra_loading_move)) + ((ConfigOptionStrings, extruder_extrusion_multiplier_speed)) ((ConfigOptionPercents, extruder_fan_offset)) ((ConfigOptionFloats, extruder_temperature_offset)) ((ConfigOptionString, extrusion_axis)) @@ -1166,6 +1175,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionFloatOrPercent, bridge_internal_acceleration)) ((ConfigOptionInts, bridge_fan_speed)) ((ConfigOptionInts, bridge_internal_fan_speed)) + ((ConfigOptionFloatOrPercent, bridge_precision)) ((ConfigOptionFloatOrPercent, brim_acceleration)) ((ConfigOptionInts, chamber_temperature)) ((ConfigOptionBool, complete_objects)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 7db8108f277..a065cf00480 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -116,146 +116,6 @@ namespace Slic3r { return out; } - - - Polygons create_polyholes(const Point center, const coord_t radius, const coord_t nozzle_diameter, bool multiple) - { - // n = max(round(2 * d), 3); // for 0.4mm nozzle - size_t nb_edges = (int)std::max(3, (int)std::round(4.0 * unscaled(radius) * 0.4 / unscaled(nozzle_diameter))); - // cylinder(h = h, r = d / cos (180 / n), $fn = n); - //create x polyholes by rotation if multiple - int nb_polyhole = 1; - float rotation = 0; - if (multiple) { - nb_polyhole = 5; - rotation = 2 * float(PI) / (nb_edges * nb_polyhole); - } - Polygons list; - for (int i_poly = 0; i_poly < nb_polyhole; i_poly++) - list.emplace_back(); - for (int i_poly = 0; i_poly < nb_polyhole; i_poly++) { - Polygon& pts = (((i_poly % 2) == 0) ? list[i_poly / 2] : list[(nb_polyhole + 1) / 2 + i_poly / 2]); - const float new_radius = radius / float(std::cos(PI / nb_edges)); - for (size_t i_edge = 0; i_edge < nb_edges; ++i_edge) { - float angle = rotation * i_poly + (float(PI) * 2 * (float)i_edge) / nb_edges; - pts.points.emplace_back(center.x() + new_radius * cos(angle), center.y() + new_radius * sin(angle)); - } - pts.make_clockwise(); - } - //alternate - return list; - } - - void PrintObject::_transform_hole_to_polyholes() - { - // get all circular holes for each layer - // the id is center-diameter-extruderid - //the tuple is Point center; float diameter_max; int extruder_id; coord_t max_variation; bool twist; - std::vector, Polygon*>>> layerid2center; - for (size_t i = 0; i < this->m_layers.size(); i++) layerid2center.emplace_back(); - tbb::parallel_for( - tbb::blocked_range(0, m_layers.size()), - [this, &layerid2center](const tbb::blocked_range& range) { - for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { - m_print->throw_if_canceled(); - Layer* layer = m_layers[layer_idx]; - for (size_t region_idx = 0; region_idx < layer->m_regions.size(); ++region_idx) - { - if (layer->m_regions[region_idx]->region().config().hole_to_polyhole) { - for (Surface& surf : layer->m_regions[region_idx]->m_slices.surfaces) { - for (Polygon& hole : surf.expolygon.holes) { - //test if convex (as it's clockwise bc it's a hole, we have to do the opposite) - if (hole.convex_points().empty() && hole.points.size() > 8) { - // Computing circle center - Point center = hole.centroid(); - double diameter_min = std::numeric_limits::max(), diameter_max = 0; - double diameter_sum = 0; - for (int i = 0; i < hole.points.size(); ++i) { - double dist = hole.points[i].distance_to(center); - diameter_min = std::min(diameter_min, dist); - diameter_max = std::max(diameter_max, dist); - diameter_sum += dist; - } - //also use center of lines to check it's not a rectangle - double diameter_line_min = std::numeric_limits::max(), diameter_line_max = 0; - Lines hole_lines = hole.lines(); - for (Line l : hole_lines) { - Point midline = (l.a + l.b) / 2; - double dist = center.distance_to(midline); - diameter_line_min = std::min(diameter_line_min, dist); - diameter_line_max = std::max(diameter_line_max, dist); - } - - - // SCALED_EPSILON was a bit too harsh. Now using a config, as some may want some harsh setting and some don't. - coord_t max_variation = std::max(SCALED_EPSILON, scale_(this->m_layers[layer_idx]->m_regions[region_idx]->region().config().hole_to_polyhole_threshold.get_abs_value(unscaled(diameter_sum / hole.points.size())))); - bool twist = this->m_layers[layer_idx]->m_regions[region_idx]->region().config().hole_to_polyhole_twisted.value; - if (diameter_max - diameter_min < max_variation * 2 && diameter_line_max - diameter_line_min < max_variation * 2) { - layerid2center[layer_idx].emplace_back( - std::tuple{center, diameter_max, layer->m_regions[region_idx]->region().config().perimeter_extruder.value, max_variation, twist}, & hole); - } - } - } - } - } - } - // for layer->slices, it will be also replaced later. - } - }); - //sort holes per center-diameter - std::map, std::vector>> id2layerz2hole; - - //search & find hole that span at least X layers - const size_t min_nb_layers = 2; - for (size_t layer_idx = 0; layer_idx < this->m_layers.size(); ++layer_idx) { - for (size_t hole_idx = 0; hole_idx < layerid2center[layer_idx].size(); ++hole_idx) { - //get all other same polygons - std::tuple& id = layerid2center[layer_idx][hole_idx].first; - float max_z = layers()[layer_idx]->print_z; - std::vector> holes; - holes.emplace_back(layerid2center[layer_idx][hole_idx].second, layer_idx); - for (size_t search_layer_idx = layer_idx + 1; search_layer_idx < this->m_layers.size(); ++search_layer_idx) { - if (layers()[search_layer_idx]->print_z - layers()[search_layer_idx]->height - max_z > EPSILON) break; - //search an other polygon with same id - for (size_t search_hole_idx = 0; search_hole_idx < layerid2center[search_layer_idx].size(); ++search_hole_idx) { - std::tuple& search_id = layerid2center[search_layer_idx][search_hole_idx].first; - if (std::get<2>(id) == std::get<2>(search_id) - && std::get<0>(id).distance_to(std::get<0>(search_id)) < std::get<3>(id) - && std::abs(std::get<1>(id) - std::get<1>(search_id)) < std::get<3>(id) - ) { - max_z = layers()[search_layer_idx]->print_z; - holes.emplace_back(layerid2center[search_layer_idx][search_hole_idx].second, search_layer_idx); - layerid2center[search_layer_idx].erase(layerid2center[search_layer_idx].begin() + search_hole_idx); - search_hole_idx--; - break; - } - } - } - //check if strait hole or first layer hole (cause of first layer compensation) - if (holes.size() >= min_nb_layers || (holes.size() == 1 && holes[0].second == 0)) { - id2layerz2hole.emplace(std::move(id), std::move(holes)); - } - } - } - //create a polyhole per id and replace holes points by it. - for (auto entry : id2layerz2hole) { - Polygons polyholes = create_polyholes(std::get<0>(entry.first), std::get<1>(entry.first), scale_(print()->config().nozzle_diameter.get_at(std::get<2>(entry.first) - 1)), std::get<4>(entry.first)); - for (auto& poly_to_replace : entry.second) { - Polygon polyhole = polyholes[poly_to_replace.second % polyholes.size()]; - //search the clone in layers->slices - for (ExPolygon& explo_slice : m_layers[poly_to_replace.second]->lslices) { - for (Polygon& poly_slice : explo_slice.holes) { - if (poly_slice.points == poly_to_replace.first->points) { - poly_slice.points = polyhole.points; - } - } - } - // copy - poly_to_replace.first->points = polyhole.points; - } - } - } - // 1) Merges typed region slices into stInternal type. // 2) Increases an "extra perimeters" counter at region slices where needed. // 3) Generates perimeters, gap fills and fill regions (fill regions of type stInternal). @@ -428,6 +288,9 @@ namespace Slic3r { m_print->throw_if_canceled(); } + // solid_infill_below_area has just beeing applied at the end of prepare_fill_surfaces() + apply_solid_infill_below_layer_area(); + // this will detect bridges and reverse bridges // and rearrange top/bottom/internal surfaces // It produces enlarged overlapping bridging areas. @@ -896,12 +759,14 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "raft_interface_layer_height" || opt_key == "raft_layers" || opt_key == "raft_layer_height" - || opt_key == "slice_closing_radius" || opt_key == "clip_multipart_objects" || opt_key == "first_layer_size_compensation" || opt_key == "first_layer_size_compensation_layers" || opt_key == "elephant_foot_min_width" || opt_key == "dont_support_bridges" + || opt_key == "overhangs_max_slope" + || opt_key == "overhangs_bridge_threshold" + || opt_key == "overhangs_bridge_upper_layers" || opt_key == "slice_closing_radius" || opt_key == "slicing_mode" || opt_key == "support_material_contact_distance_type" @@ -977,6 +842,8 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "infill_only_where_needed" || opt_key == "ironing_type" || opt_key == "solid_infill_below_area" + || opt_key == "solid_infill_below_layer_area" + || opt_key == "solid_infill_below_width" || opt_key == "solid_infill_extruder" || opt_key == "solid_infill_every_layers" || opt_key == "solid_over_perimeters" @@ -1042,6 +909,7 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "no_perimeter_unsupported_algo" || opt_key == "filament_max_overlap" || opt_key == "perimeters" + || opt_key == "perimeters_hole" || opt_key == "perimeter_overlap" || opt_key == "solid_infill_extrusion_change_odd_layers" || opt_key == "solid_infill_extrusion_spacing" @@ -1780,6 +1648,44 @@ bool PrintObject::invalidate_state_by_config_options( // Mark the object to have the region slices classified (typed, which also means they are split based on whether they are supported, bridging, top layers etc.) m_typed_slices = true; } + + void PrintObject::apply_solid_infill_below_layer_area() + { + // compute the total layer surface for the bed, for solid_infill_below_layer_area + for (auto *my_layer : this->m_layers) { + bool exists = false; + for (auto *region : my_layer->m_regions) { + exists |= region->region().config().solid_infill_below_layer_area.value > 0; + } + if (!exists) + return; + double total_area = 0; + if (this->print()->config().complete_objects.value) { + // sequential printing: only consider myself + for (const ExPolygon &slice : my_layer->lslices) { total_area += slice.area(); } + } else { + // parallel printing: get all objects + for (const PrintObject *object : this->print()->objects()) { + for (auto *layer : object->m_layers) { + if (std::abs(layer->print_z - my_layer->print_z) < EPSILON) { + for (const ExPolygon &slice : layer->lslices) { total_area += slice.area(); } + } + } + } + } + // is it low enough to apply solid_infill_below_layer_area? + for (auto *region : my_layer->m_regions) { + if (!this->print()->config().spiral_vase.value && region->region().config().fill_density.value > 0) { + double min_area = scale_d(scale_d(region->region().config().solid_infill_below_layer_area.value)); + for (Surfaces::iterator surface = region->fill_surfaces.surfaces.begin(); + surface != region->fill_surfaces.surfaces.end(); ++surface) { + if (surface->has_fill_sparse() && surface->has_pos_internal() && total_area <= min_area) + surface->surface_type = stPosInternal | stDensSolid; + } + } + } + } + } void PrintObject::process_external_surfaces() { diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index a20fdce557e..aefb502ed25 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -1,3 +1,4 @@ +#include "BridgeDetector.hpp" #include "ElephantFootCompensation.hpp" #include "I18N.hpp" #include "Layer.hpp" @@ -544,6 +545,8 @@ void PrintObject::slice() //create polyholes this->_transform_hole_to_polyholes(); + this->_min_overhang_threshold(); + // Update bounding boxes, back up raw slices of complex models. tbb::parallel_for( tbb::blocked_range(0, m_layers.size()), @@ -563,6 +566,346 @@ void PrintObject::slice() this->set_done(posSlice); } +// modify the polygon so it doesn't have any angle spiker than 90° +// used by _min_overhang_threshold +void only_convex_90(Polygon &poly) { + const bool ccw = poly.is_counter_clockwise(); + std::vector concave = ccw ? poly.concave_points_idx( 3 * PI/2 + EPSILON) : poly.convex_points_idx(PI/2 - EPSILON); + while (!concave.empty()) { + assert(std::is_sorted(concave.begin(), concave.end())); + Points new_pts; + bool previous_modified = false; + for (size_t idx = 0; idx < poly.points.size(); ++idx) { + if (previous_modified || std::find(concave.begin(), concave.end(), idx) == concave.end()) { + // convex: keep + new_pts.push_back(poly.points[idx]); + previous_modified = false; + } else { + previous_modified = true; + // concave: create new points to have a 90° angle + // get smallest side + Point small_side_point = idx == 0 ? poly.back() : poly[idx - 1]; + Point big_side_point = idx == poly.size() - 1 ? poly.front() : poly[idx + 1]; + if (poly[idx].distance_to_square(small_side_point) > poly[idx].distance_to_square(big_side_point)) { + big_side_point = idx == 0 ? poly.back() : poly[idx - 1]; + small_side_point = idx == poly.size() - 1 ? poly.front() : poly[idx + 1]; + } + // then get the distance to move in the big side + Point previous_point = ccw? (idx == 0 ? poly.back() : poly[idx - 1]) : (idx == poly.size() - 1 ? poly.front() : poly[idx + 1]); + Point next_point = ccw? (idx == poly.size() - 1 ? poly.front() : poly[idx + 1]) : (idx == 0 ? poly.back() : poly[idx - 1]); + double angle = poly[idx].ccw_angle(previous_point, next_point); + assert(angle < PI/2 && angle > 0); + coordf_t dist_to_move = std::cos(angle) * poly[idx].distance_to(small_side_point) + SCALED_EPSILON / 2; + // if distance to move too big, just deleted point (don't add it) + if (dist_to_move < poly[idx].distance_to(big_side_point)) { + Line l(poly[idx], big_side_point); + l.extend_start(-dist_to_move); + new_pts.push_back(l.a); + } + } + } + poly.points = new_pts; + concave = ccw ? poly.concave_points_idx( 3 * PI/2 + EPSILON) : poly.convex_points_idx(PI/2 - EPSILON); + } + +} + +void PrintObject::_min_overhang_threshold() { + bool has_enlargment = false; + + coord_t max_nz_diam = 0; + for (int16_t extr_id : this->object_extruders()) { + max_nz_diam = std::max(max_nz_diam, scale_t(print()->config().nozzle_diameter.get_at(extr_id))); + } + if (max_nz_diam == 0) + max_nz_diam = scale_t(0.4); + + for (size_t region_idx = 0; region_idx < this->num_printing_regions(); ++region_idx) { + coord_t enlargement = scale_t(this->printing_region(region_idx).config().overhangs_max_slope.get_abs_value(unscaled(max_nz_diam))); + if (enlargement > 0) { + has_enlargment = true; + break; + } + } + if (!has_enlargment) + return; + + for (size_t layer_idx = 1; layer_idx < this->layers().size(); layer_idx++) { + // get supported area + Layer* my_layer = this->get_layer(layer_idx); + Layer* lower_layer = this->get_layer(layer_idx - 1); + assert(lower_layer == my_layer->lower_layer); + ExPolygons supported_area = intersection_ex(my_layer->lslices, lower_layer->lslices); + ExPolygons bridged_area; + ExPolygons bridged_other_layers_area; + + // get bridgeable area + for (size_t region_idx = 0; region_idx < my_layer->m_regions.size(); ++region_idx) { + LayerRegion* lregion = my_layer->get_region(region_idx); + if (lregion->region().config().overhangs_bridge_threshold.value != 0) { + Surfaces & my_surfaces = lregion->m_slices.surfaces; + ExPolygons unsupported = to_expolygons(my_surfaces); + unsupported = diff_ex(unsupported, lower_layer->lslices, ApplySafetyOffset::Yes); + Flow bridgeFlow = lregion->bridging_flow(FlowRole::frSolidInfill); + + if (!unsupported.empty()) { + ExPolygons unsupported_filtered; + // remove small overhangs + unsupported_filtered = offset2_ex(unsupported, double(-max_nz_diam), double(max_nz_diam)); + for (const ExPolygon &to_bridge : unsupported_filtered) { + BridgeDetector detector(to_bridge, + lower_layer->lslices, + bridgeFlow.scaled_spacing(), + scale_t(this->print()->config().bridge_precision.get_abs_value(bridgeFlow.spacing())), + layer_idx); + detector.max_bridge_length = scale_d(std::max(0., lregion->region().config().overhangs_bridge_threshold.value)); + if (detector.detect_angle(0)) + append(bridged_area, union_ex(detector.coverage())); + } + // then, check other layers + size_t max_layer_idx = lregion->region().config().overhangs_bridge_upper_layers.value; + if (max_layer_idx < 0) // -1 -> all layers + max_layer_idx = this->layers().size(); + if (max_layer_idx > 0) { // 0 -> don't check other layers + max_layer_idx += layer_idx; + max_layer_idx = std::min(max_layer_idx, this->layers().size()); + // compute the area still unsupported + ExPolygons still_unsupported = diff_ex(unsupported, bridged_area); + still_unsupported = offset2_ex(still_unsupported, double(-bridgeFlow.scaled_spacing()/2), double(bridgeFlow.scaled_spacing()/2)); + // compute the support (without the enlarged part,as we don't know yet where it will be) + ExPolygons previous_supported = supported_area; + append(previous_supported, bridged_area); + previous_supported = union_safety_offset_ex(previous_supported); + for (size_t other_layer_bridge_idx = layer_idx + 1; other_layer_bridge_idx < max_layer_idx; other_layer_bridge_idx++) { + // remove new voids + still_unsupported = intersection_ex(still_unsupported, this->get_layer(other_layer_bridge_idx)->lslices); + //compute bridges + ExPolygons new_bridged_area; + for (size_t other_region_idx = 0; other_region_idx < my_layer->m_regions.size(); ++other_region_idx) { + LayerRegion *other_lregion = my_layer->get_region(other_region_idx); + if (other_lregion->region().config().overhangs_bridge_threshold.value != 0 && other_lregion->region().config().overhangs_max_slope > 0) { + coord_t enlargement = scale_t(my_layer->get_region(region_idx)->region().config().overhangs_max_slope.get_abs_value(unscaled(max_nz_diam))); + enlargement = std::max(enlargement, max_nz_diam); + Surfaces &my_surfaces = other_lregion->m_slices.surfaces; + for (const ExPolygon &to_bridge : intersection_ex(still_unsupported, to_expolygons(my_surfaces))) { + //collapse too small area + if(offset(to_bridge, -enlargement).empty()) + continue; + + BridgeDetector detector(to_bridge, previous_supported, bridgeFlow.scaled_spacing(), + scale_t(this->print()->config().bridge_precision.get_abs_value(bridgeFlow.spacing())), + other_layer_bridge_idx); + detector.layer_id = other_layer_bridge_idx; + detector.max_bridge_length = scale_d(std::max(0., other_lregion->region().config().overhangs_bridge_threshold.value)); + if (detector.detect_angle(0)) { + append(new_bridged_area, union_ex(detector.coverage())); + } + } + } + // FIXME: if overhangs_bridge_upper_layers goes from 2+ to 0, detect that you can't go higher inside the region. + } + if (!new_bridged_area.empty()) { + append(bridged_other_layers_area, new_bridged_area); + // update the area still unsupported + still_unsupported = diff_ex(still_unsupported, new_bridged_area); + still_unsupported = offset2_ex(still_unsupported, + double(-bridgeFlow.scaled_spacing()/2), double(bridgeFlow.scaled_spacing()/2)); + } + // update support area from this layer + if (other_layer_bridge_idx + 1 < max_layer_idx) { + previous_supported = diff_ex(this->get_layer(other_layer_bridge_idx)->lslices, still_unsupported); + } + } + } + } + } + + // enlarge supported area & intersect it with full area + //also modify region surfaces + ExPolygons modified; + //std::map enlargement_2_support_area; + for (size_t region_idx = 0; region_idx < my_layer->m_regions.size(); ++region_idx) { + // TODO: fuse region with same enlargement + coord_t enlargement = scale_t(my_layer->get_region(region_idx)->region().config().overhangs_max_slope.get_abs_value(unscaled(max_nz_diam))); + if (enlargement > 0) { + ExPolygons enlarged_support = offset_ex(supported_area, double(enlargement)); + enlarged_support = diff_ex(enlarged_support, bridged_other_layers_area); + append(enlarged_support, supported_area); + // put bridgeable into supported area (bridges are not enlarged) + append(enlarged_support, bridged_area); + ExPolygons new_enlarged_support = union_safety_offset_ex(enlarged_support); + // if possible, be sure to not have concave points in unsupported area + for (ExPolygon &expoly : new_enlarged_support) { + only_convex_90(expoly.contour); + //same with holes (convex as they are in reverse order) + for (Polygon &hole : expoly.holes) { + only_convex_90(hole); + } + } + enlarged_support = intersection_ex(new_enlarged_support, enlarged_support); + // modify geometry + Surfaces to_add; + Surfaces &my_surfaces = my_layer->m_regions[region_idx]->m_slices.surfaces; + for (size_t surf_idx = 0; surf_idx < my_surfaces.size(); surf_idx++) { + ExPolygons polys = intersection_ex(my_surfaces[surf_idx].expolygon, enlarged_support); + if (polys.empty()) { + my_surfaces.erase(my_surfaces.begin() + surf_idx); + surf_idx--; + } else { + my_surfaces[surf_idx].expolygon = polys[0]; + for (size_t i = 1; i < polys.size(); i++) { + to_add.emplace_back(my_surfaces[surf_idx], polys[i]); + } + } + } + append(my_surfaces, std::move(to_add)); + append(modified, union_ex(enlarged_support)); + } + } + //also lslices + my_layer->lslices = intersection_ex(my_layer->lslices, union_ex(modified), ApplySafetyOffset::Yes); + } + } +} + +Polygons create_polyholes(const Point center, const coord_t radius, const coord_t nozzle_diameter, bool multiple) +{ + // n = max(round(2 * d), 3); // for 0.4mm nozzle + size_t nb_edges = (int)std::max(3, (int)std::round(4.0 * unscaled(radius) * 0.4 / unscaled(nozzle_diameter))); + // cylinder(h = h, r = d / cos (180 / n), $fn = n); + //create x polyholes by rotation if multiple + int nb_polyhole = 1; + float rotation = 0; + if (multiple) { + nb_polyhole = 5; + rotation = 2 * float(PI) / (nb_edges * nb_polyhole); + } + Polygons list; + for (int i_poly = 0; i_poly < nb_polyhole; i_poly++) + list.emplace_back(); + for (int i_poly = 0; i_poly < nb_polyhole; i_poly++) { + Polygon& pts = (((i_poly % 2) == 0) ? list[i_poly / 2] : list[(nb_polyhole + 1) / 2 + i_poly / 2]); + const float new_radius = radius / float(std::cos(PI / nb_edges)); + for (size_t i_edge = 0; i_edge < nb_edges; ++i_edge) { + float angle = rotation * i_poly + (float(PI) * 2 * (float)i_edge) / nb_edges; + pts.points.emplace_back(center.x() + new_radius * cos(angle), center.y() + new_radius * sin(angle)); + } + pts.make_clockwise(); + } + //alternate + return list; +} + +void PrintObject::_transform_hole_to_polyholes() +{ + // get all circular holes for each layer + // the id is center-diameter-extruderid + //the tuple is Point center; float diameter_max; int extruder_id; coord_t max_variation; bool twist; + std::vector, Polygon*>>> layerid2center; + for (size_t i = 0; i < this->m_layers.size(); i++) layerid2center.emplace_back(); + tbb::parallel_for( + tbb::blocked_range(0, m_layers.size()), + [this, &layerid2center](const tbb::blocked_range& range) { + for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { + m_print->throw_if_canceled(); + Layer* layer = m_layers[layer_idx]; + for (size_t region_idx = 0; region_idx < layer->m_regions.size(); ++region_idx) + { + if (layer->m_regions[region_idx]->region().config().hole_to_polyhole) { + for (Surface& surf : layer->m_regions[region_idx]->m_slices.surfaces) { + for (Polygon& hole : surf.expolygon.holes) { + //test if convex (as it's clockwise bc it's a hole, we have to do the opposite) + if (hole.convex_points().empty() && hole.points.size() > 8) { + // Computing circle center + Point center = hole.centroid(); + double diameter_min = std::numeric_limits::max(), diameter_max = 0; + double diameter_sum = 0; + for (int i = 0; i < hole.points.size(); ++i) { + double dist = hole.points[i].distance_to(center); + diameter_min = std::min(diameter_min, dist); + diameter_max = std::max(diameter_max, dist); + diameter_sum += dist; + } + //also use center of lines to check it's not a rectangle + double diameter_line_min = std::numeric_limits::max(), diameter_line_max = 0; + Lines hole_lines = hole.lines(); + for (Line l : hole_lines) { + Point midline = (l.a + l.b) / 2; + double dist = center.distance_to(midline); + diameter_line_min = std::min(diameter_line_min, dist); + diameter_line_max = std::max(diameter_line_max, dist); + } + + + // SCALED_EPSILON was a bit too harsh. Now using a config, as some may want some harsh setting and some don't. + coord_t max_variation = std::max(SCALED_EPSILON, scale_(this->m_layers[layer_idx]->m_regions[region_idx]->region().config().hole_to_polyhole_threshold.get_abs_value(unscaled(diameter_sum / hole.points.size())))); + bool twist = this->m_layers[layer_idx]->m_regions[region_idx]->region().config().hole_to_polyhole_twisted.value; + if (diameter_max - diameter_min < max_variation * 2 && diameter_line_max - diameter_line_min < max_variation * 2) { + layerid2center[layer_idx].emplace_back( + std::tuple{center, diameter_max, layer->m_regions[region_idx]->region().config().perimeter_extruder.value, max_variation, twist}, & hole); + } + } + } + } + } + } + // for layer->slices, it will be also replaced later. + } + }); + //sort holes per center-diameter + std::map, std::vector>> id2layerz2hole; + + //search & find hole that span at least X layers + const size_t min_nb_layers = 2; + for (size_t layer_idx = 0; layer_idx < this->m_layers.size(); ++layer_idx) { + for (size_t hole_idx = 0; hole_idx < layerid2center[layer_idx].size(); ++hole_idx) { + //get all other same polygons + std::tuple& id = layerid2center[layer_idx][hole_idx].first; + float max_z = layers()[layer_idx]->print_z; + std::vector> holes; + holes.emplace_back(layerid2center[layer_idx][hole_idx].second, layer_idx); + for (size_t search_layer_idx = layer_idx + 1; search_layer_idx < this->m_layers.size(); ++search_layer_idx) { + if (layers()[search_layer_idx]->print_z - layers()[search_layer_idx]->height - max_z > EPSILON) break; + //search an other polygon with same id + for (size_t search_hole_idx = 0; search_hole_idx < layerid2center[search_layer_idx].size(); ++search_hole_idx) { + std::tuple& search_id = layerid2center[search_layer_idx][search_hole_idx].first; + if (std::get<2>(id) == std::get<2>(search_id) + && std::get<0>(id).distance_to(std::get<0>(search_id)) < std::get<3>(id) + && std::abs(std::get<1>(id) - std::get<1>(search_id)) < std::get<3>(id) + ) { + max_z = layers()[search_layer_idx]->print_z; + holes.emplace_back(layerid2center[search_layer_idx][search_hole_idx].second, search_layer_idx); + layerid2center[search_layer_idx].erase(layerid2center[search_layer_idx].begin() + search_hole_idx); + search_hole_idx--; + break; + } + } + } + //check if strait hole or first layer hole (cause of first layer compensation) + if (holes.size() >= min_nb_layers || (holes.size() == 1 && holes[0].second == 0)) { + id2layerz2hole.emplace(std::move(id), std::move(holes)); + } + } + } + //create a polyhole per id and replace holes points by it. + for (auto entry : id2layerz2hole) { + Polygons polyholes = create_polyholes(std::get<0>(entry.first), std::get<1>(entry.first), scale_(print()->config().nozzle_diameter.get_at(std::get<2>(entry.first) - 1)), std::get<4>(entry.first)); + for (auto& poly_to_replace : entry.second) { + Polygon polyhole = polyholes[poly_to_replace.second % polyholes.size()]; + //search the clone in layers->slices + for (ExPolygon& explo_slice : m_layers[poly_to_replace.second]->lslices) { + for (Polygon& poly_slice : explo_slice.holes) { + if (poly_slice.points == poly_to_replace.first->points) { + poly_slice.points = polyhole.points; + } + } + } + // copy + poly_to_replace.first->points = polyhole.points; + } + } +} + template static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCancel throw_on_cancel) { diff --git a/src/libslic3r/PrintRegion.cpp b/src/libslic3r/PrintRegion.cpp index 03fd40de5f8..22c95a6daeb 100644 --- a/src/libslic3r/PrintRegion.cpp +++ b/src/libslic3r/PrintRegion.cpp @@ -75,7 +75,7 @@ Flow PrintRegion::flow(const PrintObject &object, FlowRole role, double layer_he } else if (role == frTopSolidInfill) { config_width = m_config.top_infill_extrusion_width; config_spacing = m_config.top_infill_extrusion_spacing; - overlap = this->config().solid_infill_overlap.get_abs_value(1); + overlap = this->config().top_solid_infill_overlap.get_abs_value(1); } else { throw Slic3r::InvalidArgument("Unknown role"); } diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index c11f860c59b..81241b45238 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -95,6 +95,8 @@ set(SLIC3R_GUI_SOURCES GUI/GLTexture.cpp GUI/GLToolbar.hpp GUI/GLToolbar.cpp + GUI/GraphDialog.hpp + GUI/GraphDialog.cpp GUI/GCodeViewer.hpp GUI/GCodeViewer.cpp GUI/Preferences.cpp diff --git a/src/slic3r/GUI/CalibrationRetractionDialog.cpp b/src/slic3r/GUI/CalibrationRetractionDialog.cpp index 44ea22a2d20..d4ed5b2f792 100644 --- a/src/slic3r/GUI/CalibrationRetractionDialog.cpp +++ b/src/slic3r/GUI/CalibrationRetractionDialog.cpp @@ -146,6 +146,7 @@ void CalibrationRetractionDialog::create_geometry(wxCommandEvent& event_args) { double retraction_start = 0; std::string str = temp_start->GetValue().ToStdString(); int temp = int((2 + filament_config->option("temperature")->get_at(0)) / 5) * 5; + int first_layer_temp = filament_config->option("first_layer_temperature")->get_at(0); if (str.find_first_not_of("0123456789") == std::string::npos) temp = std::atoi(str.c_str()); @@ -239,6 +240,7 @@ void CalibrationRetractionDialog::create_geometry(wxCommandEvent& event_args) { current_obj->config.set_key_value("layer_height", new ConfigOptionFloat(nozzle_diameter / 2.)); //temp current_obj->config.set_key_value("print_temperature", new ConfigOptionInt(int(temp - temp_decr * i))); + current_obj->config.set_key_value("print_first_layer_temperature", new ConfigOptionInt(first_layer_temp)); //set retraction override const int mytemp = temp - temp_decr * i; diff --git a/src/slic3r/GUI/CalibrationTempDialog.cpp b/src/slic3r/GUI/CalibrationTempDialog.cpp index b76abb26ac9..49ec3c35c40 100644 --- a/src/slic3r/GUI/CalibrationTempDialog.cpp +++ b/src/slic3r/GUI/CalibrationTempDialog.cpp @@ -71,6 +71,7 @@ void CalibrationTempDialog::create_geometry(wxCommandEvent& event_args) { // -- get temps const ConfigOptionInts* temperature_config = filament_config->option("temperature"); + const int first_layer_temperature = filament_config->option("temperature")->get_at(0); assert(temperature_config->values.size() >= 1); long nb_items_up = 1; if (!nb_up->GetValue().ToLong(&nb_items_up)) { @@ -160,6 +161,7 @@ void CalibrationTempDialog::create_geometry(wxCommandEvent& event_args) { double firstChangeHeight = print_config->get_abs_value("first_layer_height", nozzle_diameter); //model.custom_gcode_per_print_z.gcodes.emplace_back(CustomGCode::Item{ firstChangeHeight + nozzle_diameter/2, CustomGCode::Type::Custom, -1, "", "M104 S" + std::to_string(temperature) + " ; ground floor temp tower set" }); model.objects[objs_idx[0]]->config.set_key_value("print_temperature", new ConfigOptionInt(temperature)); + model.objects[objs_idx[0]]->config.set_key_value("print_first_layer_temperature", new ConfigOptionInt(first_layer_temperature)); for (int16_t i = 1; i < nb_items; i++) { model.custom_gcode_per_print_z.gcodes.emplace_back(CustomGCode::Item{ (i * 10 * xyzScale), CustomGCode::Type::Custom , -1, "", "M104 S" + std::to_string(temperature - i * step_temp) + " ; floor " + std::to_string(i) + " of the temp tower set" }); //str_layer_gcode += "\n{ elsif layer_z >= " + std::to_string(i * 10 * xyzScale) + " and layer_z <= " + std::to_string((1 + i * 10) * xyzScale) + " }\nM104 S" + std::to_string(temperature - (int8_t)nb_delta * 5 + i * 5); diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 741a97146d9..cd9ee58b3f5 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -344,8 +344,10 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) toggle_field(el, have_perimeters); bool has_spiral_vase = have_perimeters && config->opt_bool("spiral_vase"); - - bool have_arachne = have_perimeters && config->opt_enum("perimeter_generator") == PerimeterGeneratorType::Arachne; + + bool have_arachne = have_perimeters && (config->opt_int("perimeters") == config->opt_int("perimeters_hole") || config->opt_int("perimeters_hole") < 0); + toggle_field("perimeter_generator", have_arachne); + have_arachne = have_arachne && config->opt_enum("perimeter_generator") == PerimeterGeneratorType::Arachne; for (auto el : { "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle", "wall_distribution_count", "min_feature_size", "min_bead_width", "aaa" }) toggle_field(el, have_arachne); @@ -396,7 +398,8 @@ 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_aligned_z", "fill_pattern", "infill_connection", "infill_every_layers", "infill_only_where_needed", - "solid_infill_every_layers", "solid_infill_below_area", "infill_extruder", "infill_anchor_max" }) + "solid_infill_every_layers", "solid_infill_below_area", "solid_infill_below_layer_area", "solid_infill_below_width", + "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_connection")->value != InfillConnection::icNotConnected; diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index bc347621b42..35409771824 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -449,7 +449,7 @@ void Control::SetLayersTimes(const std::vector& layers_times, float total m_layers_times.push_back(total_time); Refresh(); Update(); -} + } } void Control::SetLayersTimes(const std::vector& layers_times) @@ -460,6 +460,22 @@ void Control::SetLayersTimes(const std::vector& layers_times) m_layers_times[i] += m_layers_times[i - 1]; } +void Control::SetLayersAreas(const std::vector& layers_areas) +{ + m_layers_areas.clear(); + m_layers_areas.reserve(layers_areas.size()); + for(float area : layers_areas) + m_layers_areas.push_back(area); + if (m_layers_values.size() == m_layers_areas.size() + 1) + m_layers_areas.insert(m_layers_areas.begin(), 0.); + if (m_is_wipe_tower && m_values.size() != m_layers_areas.size()) { + // When whipe tower is used to the end of print, there is one layer which is not marked in layers_times + // So, add this value from the total print time value + for (size_t i = m_layers_areas.size(); i < m_layers_values.size(); i++) + m_layers_areas.push_back(0.); + } +} + void Control::SetDrawMode(bool is_sla_print, bool is_sequential_print) { m_draw_mode = is_sla_print ? dmSlaPrint : @@ -579,7 +595,7 @@ bool Control::is_wipe_tower_layer(int tick) const return false; if (tick == 0 || (tick == (int)m_values.size() - 1 && m_values[tick] > m_values[tick - 1])) return false; - if ((m_values[tick - 1] == m_values[tick + 1] && m_values[tick] < m_values[tick + 1]) || + if (m_values.size() > tick + 1 && (m_values[tick - 1] == m_values[tick + 1] && m_values[tick] < m_values[tick + 1]) || (tick > 0 && m_values[tick] < m_values[tick - 1]) ) // if there is just one wiping on the layer return true; @@ -796,6 +812,7 @@ wxString Control::get_label(int tick, LabelType label_type/* = ltHeightWithLayer size_t layer_number = m_is_wipe_tower ? get_layer_number(value, label_type) /**/ + 1 : (m_values.empty() ? value : value /* + 1 */ ); bool show_lheight = GUI::wxGetApp().app_config->get("show_layer_height_doubleslider") == "1"; bool show_ltime = GUI::wxGetApp().app_config->get("show_layer_time_doubleslider") == "1"; + bool show_larea = GUI::wxGetApp().app_config->get("show_layer_area_doubleslider") == "1"; int nb_lines = 2; // to move things down if the slider is on top wxString comma = "\n"; if (show_lheight) { @@ -827,6 +844,19 @@ wxString Control::get_label(int tick, LabelType label_type/* = ltHeightWithLayer } } } + if (show_larea && !m_layers_areas.empty()) { + if (m_layers_areas.size() +1 >= m_values.size()) { + size_t layer_idx_time = layer_number; + if (m_values.size() > m_layers_areas.size()) { + layer_idx_time--; + } + if (layer_idx_time < m_layers_areas.size()) { + nb_lines++; + str = str + comma + wxString::Format("%.*f", m_layers_areas[layer_idx_time] < 1 ? 3 : m_layers_areas[layer_idx_time] < 10 ? 2 : m_layers_areas[layer_idx_time] < 100 ? 1 : 0, m_layers_areas[layer_idx_time]); + comma = "\n"; + } + } + } int nb_step_down = layer_number - m_values.size() + nb_lines - 1; while (nb_step_down > 0) { str = "\n" + str; diff --git a/src/slic3r/GUI/DoubleSlider.hpp b/src/slic3r/GUI/DoubleSlider.hpp index 10c1a050076..616f86221cd 100644 --- a/src/slic3r/GUI/DoubleSlider.hpp +++ b/src/slic3r/GUI/DoubleSlider.hpp @@ -249,6 +249,7 @@ class Control : public wxControl void SetTicksValues(const Info &custom_gcode_per_print_z); void SetLayersTimes(const std::vector& layers_times, float total_time); void SetLayersTimes(const std::vector& layers_times); + void SetLayersAreas(const std::vector& layers_areas); void SetDrawMode(bool is_sla_print, bool is_sequential_print); void SetDrawMode(DrawMode mode) { m_draw_mode = mode; } @@ -424,6 +425,7 @@ class Control : public wxControl std::vector m_values; TickCodeInfo m_ticks; std::vector m_layers_times; + std::vector m_layers_areas; std::vector m_layers_values; std::vector m_extruder_colors; std::string m_print_obj_idxs; diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index ded6e93b6e7..9d54ac891e5 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -745,6 +745,29 @@ void Preview::update_layers_slider(const std::vector& layers_z, bool kee auto print_mode_stat = m_gcode_result->print_statistics.modes.front(); m_layers_slider->SetLayersTimes(print_mode_stat.layers_times, print_mode_stat.time); } + { + // create area array + //area not computed for sla_print_technology //TODO + if (!sla_print_technology){ + const std::vector> &layerz_to_area = plater->fff_print().print_statistics().layer_area_stats; + std::vector areas; + for(auto [z, area] : layerz_to_area) + areas.push_back(area); + m_layers_slider->SetLayersAreas(areas); + //auto objects = plater->fff_print().objects(); + //for (auto object : objects) { + // for (auto layer : object->layers()) { + // assert(layer->print_z > 100); + // coord_t layer_z = 100*(coord_t(layer->print_z + 50)/100); + // int32_t area = layerz_to_area[layer_z]; + // for (auto poly : layer->lslices) { + // area += poly.area(); + // } + // layerz_to_area[layer_z] = area; + // } + //} + } + } // Suggest the auto color change, if model looks like sign if (m_layers_slider->IsNewPrint()) diff --git a/src/slic3r/GUI/GraphDialog.cpp b/src/slic3r/GUI/GraphDialog.cpp new file mode 100644 index 00000000000..c8a895e1a65 --- /dev/null +++ b/src/slic3r/GUI/GraphDialog.cpp @@ -0,0 +1,288 @@ +#include +#include +#include "GraphDialog.hpp" +#include "BitmapCache.hpp" +#include "GUI.hpp" +#include "I18N.hpp" +#include "GUI_App.hpp" +#include "MsgDialog.hpp" + +#include +#include + +namespace Slic3r { namespace GUI { + +int scale(const int val) { return val * Slic3r::GUI::wxGetApp().em_unit(); } +#ifdef __WXGTK3__ +int ITEM_WIDTH() { return scale(10); } +#else +int ITEM_WIDTH() { return scale(6); } +#endif + +static void update_ui(wxWindow *window) { Slic3r::GUI::wxGetApp().UpdateDarkUI(window); } + +GraphDialog::GraphDialog(wxWindow *parent, const std::string ¶meters) + : wxDialog(parent, + wxID_ANY, + _(L("Extrusion multiplier per extrusion speed")), + wxDefaultPosition, + wxDefaultSize, + wxDEFAULT_DIALOG_STYLE /* | wxRESIZE_BORDER*/) +{ + update_ui(this); + m_panel_graph = new GraphPanel(this, parameters); + + // Not found another way of getting the background colours of GraphDialog, GraphPanel and Chart correct than + // setting them all explicitely. Reading the parent colour yielded colour that didn't really match it, no + // wxSYS_COLOUR_... matched colour used for the dialog. Same issue (and "solution") here : + // https://forums.wxwidgets.org/viewtopic.php?f=1&t=39608 Whoever can fix this, feel free to do so. +#ifndef _WIN32 + this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_FRAMEBK)); + m_panel_graph->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_FRAMEBK)); +#endif + m_panel_graph->Show(true); + this->Show(); + + auto main_sizer = new wxBoxSizer(wxVERTICAL); + main_sizer->Add(m_panel_graph, 1, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 5); + main_sizer->Add(CreateButtonSizer(wxOK | wxCANCEL), 0, wxALIGN_CENTER_HORIZONTAL | wxTOP | wxBOTTOM, 10); + SetSizer(main_sizer); + main_sizer->SetSizeHints(this); + + update_ui(static_cast(this->FindWindowById(wxID_OK, this))); + update_ui(static_cast(this->FindWindowById(wxID_CANCEL, this))); + + this->Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent &e) { + EndModal(wxCANCEL); + }); + + this->Bind( + wxEVT_BUTTON, + [this](wxCommandEvent &) { + m_output_data = m_panel_graph->get_parameters(); + EndModal(wxID_OK); + }, + wxID_OK); + this->Show(); + // Slic3r::GUI::MessageDialog dlg(this, _(L("Graph .")), _(L("Warning")), wxOK | wxICON_EXCLAMATION); + // dlg.ShowModal(); +} + +#ifdef _WIN32 +#define style wxSP_ARROW_KEYS | wxBORDER_SIMPLE +#else +#define style wxSP_ARROW_KEYS +#endif + + + +GraphPanel::GraphPanel(wxWindow *parent, const std::string ¶meters) + : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize /*,wxPoint(50,50), wxSize(800,350),wxBORDER_RAISED*/) +{ + update_ui(this); + auto sizer_chart = new wxBoxSizer(wxVERTICAL); + + std::stringstream stream{parameters}; + //stream >> m_graph_line_width_multiplicator >> m_graph_step_multiplicator; + int Graph_speed_size = 0; + float dummy = 0.f; + float min = 2.f; + float max = 0.f; + while (stream >> dummy) { + ++Graph_speed_size; + if (dummy > 0 && dummy <= 2) { + min = std::min(min, dummy); + max = std::max(max, dummy); + } + } + stream.clear(); + stream.get(); + + if (min >= max) { + max = 1.2f; + min = 0.9f; + } else { + min = int(min * 10 - 1 + EPSILON) / 10.f; + max = int(1.9f + max * 10 - EPSILON) / 10.f; + } + + std::vector> buttons; + float x = 0.f; + float y = 0.f; + while (stream >> x >> y) buttons.push_back(std::make_pair(x, y)); + + m_chart = new Chart(this, wxRect(scale(1), scale(1), scale(64), scale(36)), buttons, scale(1)); + m_chart->set_manual_points_manipulation(true); + m_chart->set_xy_range(0, min, Graph_speed_size * 10.f, max); + m_chart->set_x_label(_L("Print speed") + " ("+_L("mm/s")+")", 1.f); + m_chart->set_y_label(_L("Extrusion multiplier"), 0.001f); + m_chart->set_no_point_label(_L("No compensation")); +#ifdef _WIN32 + update_ui(m_chart); +#else + m_chart->SetBackgroundColour(parent->GetBackgroundColour()); // see comment in GraphDialog constructor +#endif + sizer_chart->Add(new wxStaticText(this, wxID_ANY, + _L("Choose the extrusion multipler value for multiple speeds.\nYou can add/remove points with a right clic."))); + sizer_chart->Add(m_chart, 0, wxALL, 5); + + m_last_speed = Graph_speed_size * 10; + m_widget_speed = new wxSpinCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(ITEM_WIDTH(), -1), + style | wxTE_PROCESS_ENTER, 10, 2000, m_last_speed); + // note: wxTE_PROCESS_ENTER allow the wxSpinCtrl to receive wxEVT_TEXT_ENTER events + + + m_widget_min_flow = new wxSpinCtrlDouble(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(ITEM_WIDTH(), -1), + style, 0.1, 2, min, 0.1f); + m_widget_max_flow = new wxSpinCtrlDouble(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(ITEM_WIDTH(), -1), + style, 0.1, 2, max, 0.1f); + +#ifdef _WIN32 + update_ui(m_widget_speed); + update_ui(m_widget_min_flow); + update_ui(m_widget_max_flow); +#endif + // line for speed max & reset + wxBoxSizer *size_line = new wxBoxSizer(wxHORIZONTAL); + size_line->Add(new wxStaticText(this, wxID_ANY, wxString(_L("Graph max speed") + " :")), 0, wxALIGN_CENTER_VERTICAL); + size_line->Add(m_widget_speed); + size_line->AddSpacer(80); + wxButton *bt_reset = new wxButton(this, wxID_ANY, _L("Reset")); + bt_reset->SetToolTip(_L("Reset all values to 1. Also reset all points to defaults.")); + size_line->Add(bt_reset); + sizer_chart->Add(size_line); + + //line for y min & max + size_line = new wxBoxSizer(wxHORIZONTAL); + size_line->Add(new wxStaticText(this, wxID_ANY, wxString(_L("Minimum flow") + " :")), 0, wxALIGN_CENTER_VERTICAL); + size_line->Add(m_widget_min_flow); + size_line->AddSpacer(20); + size_line->Add(new wxStaticText(this, wxID_ANY, wxString(_L("Maximum flow") + " :")), 0, wxALIGN_CENTER_VERTICAL); + size_line->Add(m_widget_max_flow); + sizer_chart->Add(size_line); + + sizer_chart->SetSizeHints(this); + SetSizer(sizer_chart); + + bt_reset->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) { + std::vector> buttons;// = m_chart->get_buttons(); + //for (std::pair &button : buttons) { + // button.second = 1.f; + //} + //buttons.emplace_back(5,1.f); + buttons.emplace_back(10,1.f); + //buttons.emplace_back(15,1.f); + buttons.emplace_back(20,1.f); + buttons.emplace_back(30,1.f); + buttons.emplace_back(40,1.f); + buttons.emplace_back(60,1.f); + buttons.emplace_back(80,1.f); + buttons.emplace_back(120,1.f); + buttons.emplace_back(160,1.f); + buttons.emplace_back(240,1.f); + buttons.emplace_back(480,1.f); + buttons.emplace_back(640,1.f); + buttons.emplace_back(960,1.f); + buttons.emplace_back(1280,1.f); + m_chart->set_buttons(buttons); + })); + + m_widget_speed->Bind(wxEVT_TEXT_ENTER, [this](wxCommandEvent &) { + int old_speed = m_last_speed; + m_last_speed = 10 * ((m_widget_speed->GetValue() + 5) / 10); + m_last_speed = std::min(std::max(m_last_speed, 20), 2000); + m_widget_speed->SetValue(m_last_speed); + if (old_speed < m_last_speed) { + if (old_speed < 1000 && m_last_speed >= 1000) + m_chart->set_x_label(_L("Print speed") + " (" + _L("mm/s") + ")", 100.f); + else if (old_speed < 100 && m_last_speed >= 100) + m_chart->set_x_label(_L("Print speed") + " (" + _L("mm/s") + ")", 10.f); + } else { + if (old_speed >= 100 && m_last_speed < 100) + m_chart->set_x_label(_L("Print speed") + " (" + _L("mm/s") + ")", 1.f); + else if (old_speed >= 1000 && m_last_speed < 1000) + m_chart->set_x_label(_L("Print speed") + " (" + _L("mm/s") + ")", 10.f); + } + m_chart->set_xy_range(0, -1, m_last_speed, -1); + }); + m_widget_speed->Bind(wxEVT_SPINCTRL, [this](wxSpinEvent &evt) { + assert(evt.GetInt() != m_last_speed); + int incr = 10; + if (m_last_speed >= 60) + incr = 20; + if (m_last_speed >= 100) + incr = 50; + if (m_last_speed >= 300) + incr = 100; + if (m_last_speed >= 600) + incr = 200; + if (m_last_speed >= 1000) + incr = 500; + if (evt.GetInt() > m_last_speed) { + if (m_last_speed < 100 && m_last_speed + incr >= 100) + m_chart->set_x_label(_L("Print speed") + " (" + _L("mm/s") + ")", 10.f); + else if (m_last_speed < 1000 && m_last_speed + incr >= 1000) + m_chart->set_x_label(_L("Print speed") + " (" + _L("mm/s") + ")", 100.f); + m_last_speed += incr; + } else { + if (m_last_speed >= 100 && m_last_speed - incr < 100) + m_chart->set_x_label(_L("Print speed") + " (" + _L("mm/s") + ")", 1.f); + else if (m_last_speed >= 1000 && m_last_speed - incr < 1000) + m_chart->set_x_label(_L("Print speed") + " (" + _L("mm/s") + ")", 10.f); + m_last_speed -= incr; + } + m_last_speed = std::min(std::max(m_last_speed, 20), 2000); + m_widget_speed->SetValue(m_last_speed); + m_chart->set_xy_range(0, 0, m_last_speed, -1); + }); + // thses don't work, i don't know why. + //m_widget_speed->Bind(wxEVT_SPIN_UP, [this](wxSpinEvent &evt) { + // std::cout<<"up"; + // }); + //m_widget_speed->Bind(wxEVT_SPIN_DOWN, [this](wxSpinEvent &evt) { + // std::cout<<"down"; + // }); + + m_widget_min_flow->Bind(wxEVT_TEXT, [this](wxCommandEvent &) { + m_chart->set_xy_range(-1, m_widget_min_flow->GetValue(), -1, m_widget_max_flow->GetValue()); + }); + m_widget_min_flow->Bind(wxEVT_CHAR, [](wxKeyEvent &) {}); // do nothing - prevents the user to change the value + m_widget_max_flow->Bind(wxEVT_TEXT, [this](wxCommandEvent &) { + m_chart->set_xy_range(-1, m_widget_min_flow->GetValue(), -1, m_widget_max_flow->GetValue()); + }); + m_widget_max_flow->Bind(wxEVT_CHAR, [](wxKeyEvent &) {}); // do nothing - prevents the user to change the value + Bind(EVT_WIPE_TOWER_CHART_CHANGED, [this](wxCommandEvent &) { + int nb_samples = m_chart->get_speed(10.f).size(); + m_last_speed = 10 * nb_samples; + m_widget_speed->SetValue(m_last_speed); + }); + Refresh(true); // erase background +} + +std::string GraphPanel::get_parameters() +{ + std::vector flow_rates = m_chart->get_speed(10.f); + std::vector> buttons = m_chart->get_buttons(); + + //write string + std::stringstream stream; + //stream << m_graph_line_width_multiplicator << " " << m_graph_step_multiplicator; + //if all are at 1, then set the first to 0 so it's "disabled" + bool disabled = true; + for (const float &flow_rate : flow_rates) + if (flow_rate != 1.) + disabled = false; + for (size_t i = 0; i < flow_rates.size() ; i++) { + const float &flow_rate = flow_rates[i]; + if (0 == i) { + stream << (disabled ? 0.f : flow_rate); + } else { + stream << " " << flow_rate; + } + } + stream << "|"; + for (const auto &button : buttons) stream << " " << button.first << " " << button.second; + return stream.str(); +} + +}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/GraphDialog.hpp b/src/slic3r/GUI/GraphDialog.hpp new file mode 100644 index 00000000000..eb20f4a93fc --- /dev/null +++ b/src/slic3r/GUI/GraphDialog.hpp @@ -0,0 +1,40 @@ +#ifndef _GRAPH_DIALOG_H_ +#define _GRAPH_DIALOG_H_ + +#include +#include +#include +#include +#include + +#include "RammingChart.hpp" + +namespace Slic3r { namespace GUI { + +class GraphPanel : public wxPanel +{ +public: + GraphPanel(wxWindow *parent, const std::string &data); + std::string get_parameters(); + +private: + Chart * m_chart = nullptr; + wxSpinCtrl * m_widget_speed = nullptr; + wxSpinCtrlDouble *m_widget_min_flow = nullptr; + wxSpinCtrlDouble *m_widget_max_flow = nullptr; + int m_last_speed = 120; +}; + +class GraphDialog : public wxDialog +{ +public: + GraphDialog(wxWindow *parent, const std::string ¶meters); + std::string get_parameters() { return m_output_data; } + +private: + GraphPanel *m_panel_graph = nullptr; + std::string m_output_data; +}; + +}} // namespace Slic3r::GUI +#endif // _GRAPH_DIALOG_H_ \ No newline at end of file diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index f165f092ff4..facaefc6734 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -1035,6 +1035,7 @@ bool OptionsGroup::launch_browser(const std::string& path_end) static const std::vector option_without_field = { "bed_shape", "filament_ramming_parameters", + "extruder_extrusion_multiplier_speed", "gcode_substitutions", "compatible_prints", "compatible_printers" diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index c3715877fff..e6c0f16fe47 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -656,17 +656,24 @@ void PreferencesDialog::build(size_t selected_tab) def.label = L("Show layer height on the scroll bar"); def.type = coBool; - def.tooltip = L("Add the layer height (first number in parentheses) next to a widget of the layer double-scrollbar."); + def.tooltip = L("Add the layer height (first number after the layer z position) next to a widget of the layer double-scrollbar."); def.set_default_value(new ConfigOptionBool{ app_config->get("show_layer_height_doubleslider") == "1" }); option = Option(def, "show_layer_height_doubleslider"); m_optgroups_gui.back()->append_single_option_line(option); def.label = L("Show layer time on the scroll bar"); def.type = coBool; - def.tooltip = L("Add the layer height (before the layer count in parentheses) next to a widget of the layer double-scrollbar."); + def.tooltip = L("Add the layer time (after the layer height, or if it's hidden after the layer z position) next to a widget of the layer double-scrollbar."); def.set_default_value(new ConfigOptionBool{ app_config->get("show_layer_time_doubleslider") == "1" }); option = Option(def, "show_layer_time_doubleslider"); m_optgroups_gui.back()->append_single_option_line(option); + + def.label = L("Show layer area on the scroll bar"); + def.type = coBool; + def.tooltip = L("Add the layer area (the number just below the layer id) next to a widget of the layer double-scrollbar."); + def.set_default_value(new ConfigOptionBool{ app_config->get("show_layer_area_doubleslider") == "1" }); + option = Option(def, "show_layer_area_doubleslider"); + m_optgroups_gui.back()->append_single_option_line(option); } diff --git a/src/slic3r/GUI/RammingChart.cpp b/src/slic3r/GUI/RammingChart.cpp index 244d83a9b87..f906809609f 100644 --- a/src/slic3r/GUI/RammingChart.cpp +++ b/src/slic3r/GUI/RammingChart.cpp @@ -8,7 +8,6 @@ wxDEFINE_EVENT(EVT_WIPE_TOWER_CHART_CHANGED, wxCommandEvent); - void Chart::draw() { wxAutoBufferedPaintDC dc(this); // unbuffered DC caused flickering on win @@ -26,7 +25,8 @@ void Chart::draw() { dc.DrawRectangle(m_rect); if (visible_area.m_width < 0.499) { - dc.DrawText(_(L("NO RAMMING AT ALL")),wxPoint(m_rect.GetLeft()+m_rect.GetWidth()/2-legend_side,m_rect.GetBottom()-m_rect.GetHeight()/2)); + dc.DrawText(m_no_point_legend, wxPoint(m_rect.GetLeft() + m_rect.GetWidth() / 2 - legend_side, + m_rect.GetBottom() - m_rect.GetHeight() / 2)); return; } @@ -35,19 +35,19 @@ void Chart::draw() { for (unsigned int i=0;iget_pos().m_x); + float pos_y = float(m_dragged->get_pos().m_y); + // show on bottom right + // TODO: compute legend height instead of '3 * scale_unit' + wxPoint ptx = math_to_screen(wxPoint2DDouble(visible_area.m_x + visible_area.m_width, visible_area.m_y)); + wxPoint pty = math_to_screen(wxPoint2DDouble(visible_area.m_x + visible_area.m_width, visible_area.m_y)); + ptx.x -= 1*legend_side; + ptx.y -= 1*legend_side; + pty.x -= 1*legend_side; + pty.y -= 0.5*legend_side; + dc.DrawText(wxString().Format(wxT("x: %.3f"), pos_x), ptx); + dc.DrawText(wxString().Format(wxT("y: %.3f"), pos_y), pty); + } } void Chart::mouse_right_button_clicked(wxMouseEvent& event) { - if (!manual_points_manipulation) + if (!m_manual_points_manipulation) return; wxPoint point = event.GetPosition(); int button_index = which_button_is_clicked(point); - if (button_index != -1 && m_buttons.size()>2) { - m_buttons.erase(m_buttons.begin()+button_index); + if (button_index != -1 && m_buttons.size() > 2) { + m_buttons.erase(m_buttons.begin() + button_index); + recalculate_line(); + } else { + // create a new point + wxPoint point = event.GetPosition(); + if (!m_rect.Contains(point)) // the click is outside the chart + return; + wxPoint2DDouble dblpoint = screen_to_math(point); + // trunc by precision + dblpoint.m_x = int((dblpoint.m_x + this->m_x_legend_incr / 2) / this->m_x_legend_incr) * this->m_x_legend_incr; + dblpoint.m_y = int((dblpoint.m_y + this->m_y_legend_incr / 2) / this->m_y_legend_incr) * this->m_y_legend_incr; + //check it doesn't exist + for (const ButtonToDrag &bt : m_buttons) + if(bt.get_pos().m_x == dblpoint.m_x) + return; + m_buttons.push_back(dblpoint); + std::sort(m_buttons.begin(), m_buttons.end()); recalculate_line(); } } - - void Chart::mouse_clicked(wxMouseEvent& event) { wxPoint point = event.GetPosition(); int button_index = which_button_is_clicked(point); if ( button_index != -1) { m_dragged = &m_buttons[button_index]; - m_previous_mouse = point; + m_previous_mouse = point; + Refresh(); } } - - - + void Chart::mouse_moved(wxMouseEvent& event) { if (!event.Dragging() || !m_dragged) return; wxPoint pos = event.GetPosition(); @@ -129,18 +178,17 @@ void Chart::mouse_moved(wxMouseEvent& event) { if (!(rect.Contains(pos))) { // the mouse left chart area mouse_left_window(event); return; - } + } int delta_x = pos.x - m_previous_mouse.x; int delta_y = pos.y - m_previous_mouse.y; - m_dragged->move(fixed_x?0:double(delta_x)/m_rect.GetWidth() * visible_area.m_width,-double(delta_y)/m_rect.GetHeight() * visible_area.m_height); + m_dragged->move(fixed_x ? 0 : double(delta_x) / m_rect.GetWidth() * visible_area.m_width, + -double(delta_y) / m_rect.GetHeight() * visible_area.m_height); m_previous_mouse = pos; recalculate_line(); } - - void Chart::mouse_double_clicked(wxMouseEvent& event) { - if (!manual_points_manipulation) + if (!m_manual_points_manipulation) return; wxPoint point = event.GetPosition(); if (!m_rect.Contains(point)) // the click is outside the chart @@ -151,8 +199,23 @@ void Chart::mouse_double_clicked(wxMouseEvent& event) { return; } - - +void Chart::mouse_left_window(wxMouseEvent &e) +{ + mouse_released(e); +} +void Chart::mouse_released(wxMouseEvent &) +{ + if (m_dragged != nullptr) { + if (!fixed_x) { + float m_x = int((m_dragged->get_pos().m_x + this->m_x_legend_incr / 2) / this->m_x_legend_incr) * this->m_x_legend_incr; + m_dragged->move(m_x - m_dragged->get_pos().m_x, 0); + } + float m_y = int((m_dragged->get_pos().m_y + this->m_y_legend_incr / 2) / this->m_y_legend_incr) * this->m_y_legend_incr; + m_dragged->move(0, m_y - m_dragged->get_pos().m_y); + m_dragged = nullptr; + recalculate_line(); + } +} void Chart::recalculate_line() { m_line_to_draw.clear(); @@ -243,6 +306,15 @@ void Chart::recalculate_line() { m_line_to_draw.push_back(math_to_screen(wxPoint2DDouble(x_math,m_buttons[i].get_pos().m_y)).y); } + m_line_to_draw.back() = std::max(m_line_to_draw.back(), m_rect.GetTop()-1); + m_line_to_draw.back() = std::min(m_line_to_draw.back(), m_rect.GetBottom()-1); + m_total_volume += (m_rect.GetBottom() - m_line_to_draw.back()) * (visible_area.m_width / m_rect.GetWidth()) * (visible_area.m_height / m_rect.GetHeight()); + } + } else if (points.size() == 1) { + + for (int x=m_rect.GetLeft(); x<=m_rect.GetRight() ; ++x) { + float x_math = screen_to_math(wxPoint(x,0)).m_x; + m_line_to_draw.push_back(math_to_screen(wxPoint2DDouble(x_math,m_buttons[0].get_pos().m_y)).y); m_line_to_draw.back() = std::max(m_line_to_draw.back(), m_rect.GetTop()-1); m_line_to_draw.back() = std::min(m_line_to_draw.back(), m_rect.GetBottom()-1); m_total_volume += (m_rect.GetBottom() - m_line_to_draw.back()) * (visible_area.m_width / m_rect.GetWidth()) * (visible_area.m_height / m_rect.GetHeight()); @@ -253,9 +325,7 @@ void Chart::recalculate_line() { Refresh(); } - - -std::vector Chart::get_ramming_speed(float sampling) const { +std::vector Chart::get_speed(float sampling) const { std::vector speeds_out; const int number_of_samples = std::round( visible_area.m_width / sampling); @@ -270,16 +340,21 @@ std::vector Chart::get_ramming_speed(float sampling) const { return speeds_out; } - std::vector> Chart::get_buttons() const { std::vector> buttons_out; for (const auto& button : m_buttons) buttons_out.push_back(std::make_pair(float(button.get_pos().m_x),float(button.get_pos().m_y))); return buttons_out; } - - - + + +void Chart::set_buttons(std::vector> new_buttons) { + m_buttons.clear(); + for (std::pair &new_button : new_buttons) { + m_buttons.emplace_back(wxPoint2DDouble(new_button.first, new_button.second)); + } + recalculate_line(); +} BEGIN_EVENT_TABLE(Chart, wxWindow) EVT_MOTION(Chart::mouse_moved) diff --git a/src/slic3r/GUI/RammingChart.hpp b/src/slic3r/GUI/RammingChart.hpp index f62546b1d53..5193db98185 100644 --- a/src/slic3r/GUI/RammingChart.hpp +++ b/src/slic3r/GUI/RammingChart.hpp @@ -13,30 +13,40 @@ wxDECLARE_EVENT(EVT_WIPE_TOWER_CHART_CHANGED, wxCommandEvent); class Chart : public wxWindow { public: - Chart(wxWindow* parent, wxRect rect,const std::vector>& initial_buttons,int ramming_speed_size, float sampling, int scale_unit=10) : + Chart(wxWindow* parent, wxRect rect,const std::vector>& initial_buttons, int scale_unit=10) : wxWindow(parent,wxID_ANY,rect.GetTopLeft(),rect.GetSize()), scale_unit(scale_unit), legend_side(5*scale_unit) { SetBackgroundStyle(wxBG_STYLE_PAINT); m_rect = wxRect(wxPoint(legend_side,0),rect.GetSize()-wxSize(legend_side,legend_side)); - visible_area = wxRect2DDouble(0.0, 0.0, sampling*ramming_speed_size, 20.); + visible_area = wxRect2DDouble(0.0, 0.0, 20., 20.); m_buttons.clear(); if (initial_buttons.size()>0) for (const auto& pair : initial_buttons) m_buttons.push_back(wxPoint2DDouble(pair.first,pair.second)); recalculate_line(); } - void set_xy_range(float x,float y) { - x = int(x/0.5) * 0.5; - if (x>=0) visible_area.SetRight(x); - if (y>=0) visible_area.SetBottom(y); + void set_xy_range(float min_x, float min_y, float max_x, float max_y) { + if (min_x >= 0 && max_x > min_x) { + visible_area.SetLeft(min_x); + visible_area.SetRight(max_x); + } + if (min_y >= 0 && max_y > min_y) { + visible_area.SetTop(min_y); + visible_area.SetBottom(max_y); + } recalculate_line(); } + void set_manual_points_manipulation(bool manip) { m_manual_points_manipulation = manip; } + void set_x_label(wxString &label, float incr = 0.1f) { m_x_legend = label; m_x_legend_incr = incr; } + void set_y_label(wxString &label, float incr = 0.1f) { m_y_legend = label; m_y_legend_incr = incr; } + void set_no_point_label(wxString &label) { m_no_point_legend = label; } float get_volume() const { return m_total_volume; } - float get_time() const { return visible_area.m_width; } + float get_max_x() const { return visible_area.m_width; } - std::vector get_ramming_speed(float sampling) const; //returns sampled ramming speed - std::vector> get_buttons() const; // returns buttons position + std::vector get_speed(float sampling) const; //returns sampled ramming speed + std::vector> get_buttons() const; // returns buttons position + void set_buttons(std::vector>); void draw(); @@ -44,8 +54,8 @@ class Chart : public wxWindow { void mouse_right_button_clicked(wxMouseEvent& event); void mouse_moved(wxMouseEvent& event); void mouse_double_clicked(wxMouseEvent& event); - void mouse_left_window(wxMouseEvent&) { m_dragged = nullptr; } - void mouse_released(wxMouseEvent&) { m_dragged = nullptr; } + void mouse_left_window(wxMouseEvent &); + void mouse_released(wxMouseEvent &); void paint_event(wxPaintEvent&) { draw(); } DECLARE_EVENT_TABLE() @@ -55,11 +65,16 @@ class Chart : public wxWindow { private: static const bool fixed_x = true; static const bool splines = true; - static const bool manual_points_manipulation = false; static const int side = 10; // side of draggable button const int scale_unit; int legend_side; + wxString m_x_legend; + wxString m_y_legend; + float m_x_legend_incr = 0.1f; + float m_y_legend_incr = 1.f; + wxString m_no_point_legend; + bool m_manual_points_manipulation = false; class ButtonToDrag { public: diff --git a/src/slic3r/GUI/Search.cpp b/src/slic3r/GUI/Search.cpp index f801e4a6c36..85a6ede914d 100644 --- a/src/slic3r/GUI/Search.cpp +++ b/src/slic3r/GUI/Search.cpp @@ -152,6 +152,8 @@ void OptionsSearcher::append_options(DynamicPrintConfig* config, Preset::Type ty const ConfigDef* defs = config->def(); auto emplace_option = [this, type](const std::string grp_key, const int16_t idx) { + assert(groups_and_categories.find(grp_key) == groups_and_categories.end() + || !groups_and_categories[grp_key].empty()); for (const GroupAndCategory& gc : groups_and_categories[grp_key]) { if (gc.group.IsEmpty() || gc.category.IsEmpty()) return; @@ -159,6 +161,7 @@ void OptionsSearcher::append_options(DynamicPrintConfig* config, Preset::Type ty Option option = create_option(gc.gui_opt.opt_key, idx, type, gc); if (!option.label.empty()) { options.push_back(std::move(option)); + sorted = false; } //wxString suffix; @@ -508,6 +511,7 @@ void OptionsSearcher::check_and_update(PrinterTechnology pt_in, ConfigOptionMode return; options.clear(); + sorted = false; printer_technology = pt_in; current_tags = tags_in; @@ -573,6 +577,18 @@ const Option& OptionsSearcher::get_option(const std::string& opt_key, Preset::Ty size_t pos_hash = opt_key.find('#'); if (pos_hash == std::string::npos) { auto it = std::lower_bound(options.begin(), options.end(), Option({ boost::nowide::widen(opt_key), type, idx })); +#ifdef _DEBUG + if (options[it - options.begin()].opt_key_with_idx() != opt_key) { + std::wstring wopt_key = boost::nowide::widen(opt_key); + for (const Option &opt : options) { + if (opt.key == wopt_key) { + if (opt.type == type) { + std::cout << "found\n"; + } + } + } + } +#endif assert(it != options.end()); return options[it - options.begin()]; } else { @@ -580,6 +596,17 @@ const Option& OptionsSearcher::get_option(const std::string& opt_key, Preset::Ty std::string opt_idx = opt_key.substr(pos_hash + 1); idx = atoi(opt_idx.c_str()); auto it = std::lower_bound(options.begin(), options.end(), Option({ boost::nowide::widen(raw_opt_key), type, idx })); +#ifdef _DEBUG + if (options[it - options.begin()].opt_key_with_idx() != opt_key) { + for (const Option &opt : options) { + if (opt.opt_key_with_idx() == opt_key) { + if (opt.type == type) { + std::cout << "found\n"; + } + } + } + } +#endif assert(it != options.end()); return options[it - options.begin()]; } diff --git a/src/slic3r/GUI/Search.hpp b/src/slic3r/GUI/Search.hpp index c836379214d..a4a2e8ec7f0 100644 --- a/src/slic3r/GUI/Search.hpp +++ b/src/slic3r/GUI/Search.hpp @@ -106,6 +106,7 @@ class OptionsSearcher ConfigOptionMode current_tags {comNone}; std::vector