diff --git a/resources/images/param_crosszag.svg b/resources/images/param_crosszag.svg new file mode 100644 index 0000000000..0a21257317 --- /dev/null +++ b/resources/images/param_crosszag.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/images/param_lockedzag.svg b/resources/images/param_lockedzag.svg new file mode 100644 index 0000000000..55e87074ff --- /dev/null +++ b/resources/images/param_lockedzag.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/images/param_zigzag.svg b/resources/images/param_zigzag.svg new file mode 100644 index 0000000000..f762e99c9a --- /dev/null +++ b/resources/images/param_zigzag.svg @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/profiles/BBL.json b/resources/profiles/BBL.json index 10383cb01e..9ac95694b3 100644 --- a/resources/profiles/BBL.json +++ b/resources/profiles/BBL.json @@ -1,7 +1,7 @@ { "name": "Bambulab", "url": "http://www.bambulab.com/Parameters/vendor/BBL.json", - "version": "01.10.00.36", + "version": "02.00.00.53", "force_update": "0", "description": "the initial version of BBL configurations", "machine_model_list": [ diff --git a/resources/profiles/BBL/process/fdm_process_common.json b/resources/profiles/BBL/process/fdm_process_common.json index 9b99ec1b28..8a9c65ad17 100644 --- a/resources/profiles/BBL/process/fdm_process_common.json +++ b/resources/profiles/BBL/process/fdm_process_common.json @@ -71,5 +71,8 @@ "compatible_printers": [], "smooth_coefficient": "80", "overhang_totally_speed": "19", - "scarf_angle_threshold": "155" + "scarf_angle_threshold": "155", + "infill_shift_step": "0.4", + "infill_rotate_step": "0", + "symmetric_infill_y_axis": "false" } \ No newline at end of file diff --git a/src/libslic3r/BoundingBox.cpp b/src/libslic3r/BoundingBox.cpp index f2a258797b..a2a510b64c 100644 --- a/src/libslic3r/BoundingBox.cpp +++ b/src/libslic3r/BoundingBox.cpp @@ -49,6 +49,13 @@ BoundingBox BoundingBox::rotated(double angle, const Point ¢er) const return out; } +BoundingBox BoundingBox::scaled(double factor) const +{ + BoundingBox out(*this); + out.scale(factor); + return out; +} + template void BoundingBoxBase::scale(double factor) { diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp index 9c305bd9ad..d6baa27012 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -222,7 +222,9 @@ public: BoundingBox(const Point &pmin, const Point &pmax) : BoundingBoxBase(pmin, pmax) {} BoundingBox(const Points &points) : BoundingBoxBase(points) {} - BoundingBox inflated(coordf_t delta) const throw() { BoundingBox out(*this); out.offset(delta); return out; } + BoundingBox inflated(coordf_t delta) const noexcept { BoundingBox out(*this); out.offset(delta); return out; } + + BoundingBox scaled(double factor) const; friend BoundingBox get_extents_rotated(const Points &points, double angle); }; diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 3ce062ac39..d5332688ab 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -657,6 +657,11 @@ public: { if (! append) this->values.clear(); + + if (str.empty()) { + this->values.push_back(0); + return true; + } std::istringstream is(str); std::string item_str; while (std::getline(is, item_str, ',')) { @@ -675,6 +680,13 @@ public: } return true; } + static bool validate_string(const std::string &str) + { + // should only have number and commas + return std::all_of(str.begin(), str.end(), [](char c) { + return std::isdigit(c) || c == ','|| std::isspace(c); + }); + } ConfigOptionFloatsTempl& operator=(const ConfigOption *opt) { diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp index f2dbf7389a..185e925084 100644 --- a/src/libslic3r/ExPolygon.cpp +++ b/src/libslic3r/ExPolygon.cpp @@ -1,544 +1,551 @@ -#include "BoundingBox.hpp" -#include "ExPolygon.hpp" -#include "Exception.hpp" -#include "Geometry/MedialAxis.hpp" -#include "Polygon.hpp" -#include "Line.hpp" -#include "ClipperUtils.hpp" -#include "SVG.hpp" -#include -#include -#include - -namespace Slic3r { - -void ExPolygon::scale(double factor) -{ - contour.scale(factor); - for (Polygon &hole : holes) - hole.scale(factor); -} - -void ExPolygon::scale(double factor_x, double factor_y) -{ - contour.scale(factor_x, factor_y); - for (Polygon &hole : holes) - hole.scale(factor_x, factor_y); -} - -void ExPolygon::translate(const Point &p) -{ - contour.translate(p); - for (Polygon &hole : holes) - hole.translate(p); -} - -void ExPolygon::rotate(double angle) -{ - contour.rotate(angle); - for (Polygon &hole : holes) - hole.rotate(angle); -} - -void ExPolygon::rotate(double angle, const Point ¢er) -{ - contour.rotate(angle, center); - for (Polygon &hole : holes) - hole.rotate(angle, center); -} - -double ExPolygon::area() const -{ - double a = this->contour.area(); - for (const Polygon &hole : holes) - a -= - hole.area(); // holes have negative area - return a; -} - -bool ExPolygon::is_valid() const -{ - if (!this->contour.is_valid() || !this->contour.is_counter_clockwise()) return false; - for (Polygons::const_iterator it = this->holes.begin(); it != this->holes.end(); ++it) { - if (!(*it).is_valid() || (*it).is_counter_clockwise()) return false; - } - return true; -} - -void ExPolygon::douglas_peucker(double tolerance) -{ - this->contour.douglas_peucker(tolerance); - for (Polygon &poly : this->holes) - poly.douglas_peucker(tolerance); -} - -bool ExPolygon::contains(const Line &line) const -{ - return this->contains(Polyline(line.a, line.b)); -} - -bool ExPolygon::contains(const Polyline &polyline) const -{ - BoundingBox bbox1 = get_extents(*this); - BoundingBox bbox2 = get_extents(polyline); - bbox2.inflated(1); - if (!bbox1.overlap(bbox2)) - return false; - - return diff_pl(polyline, *this).empty(); -} - -bool ExPolygon::contains(const Polylines &polylines) const -{ - #if 0 - BoundingBox bbox = get_extents(polylines); - bbox.merge(get_extents(*this)); - SVG svg(debug_out_path("ExPolygon_contains.svg"), bbox); - svg.draw(*this); - svg.draw_outline(*this); - svg.draw(polylines, "blue"); - #endif - Polylines pl_out = diff_pl(polylines, *this); - #if 0 - svg.draw(pl_out, "red"); - #endif - return pl_out.empty(); -} - -bool ExPolygon::contains(const Point &point, bool border_result /* = true */) const -{ - if (! Slic3r::contains(contour, point, border_result)) - // Outside the outer contour, not on the contour boundary. - return false; - for (const Polygon &hole : this->holes) - if (Slic3r::contains(hole, point, ! border_result)) - // Inside a hole, not on the hole boundary. - return false; - return true; -} - -bool ExPolygon::on_boundary(const Point &point, double eps) const -{ - if (this->contour.on_boundary(point, eps)) - return true; - for (const Polygon &hole : this->holes) - if (hole.on_boundary(point, eps)) - return true; - return false; -} - -// Projection of a point onto the polygon. -Point ExPolygon::point_projection(const Point &point) const -{ - if (this->holes.empty()) { - return this->contour.point_projection(point); - } else { - double dist_min2 = std::numeric_limits::max(); - Point closest_pt_min; - for (size_t i = 0; i < this->num_contours(); ++ i) { - Point closest_pt = this->contour_or_hole(i).point_projection(point); - double d2 = (closest_pt - point).cast().squaredNorm(); - if (d2 < dist_min2) { - dist_min2 = d2; - closest_pt_min = closest_pt; - } - } - return closest_pt_min; - } -} - -bool ExPolygon::overlaps(const ExPolygon &other) const -{ - if (this->empty() || other.empty()) - return false; - - #if 0 - BoundingBox bbox = get_extents(other); - bbox.merge(get_extents(*this)); - static int iRun = 0; - SVG svg(debug_out_path("ExPolygon_overlaps-%d.svg", iRun ++), bbox); - svg.draw(*this); - svg.draw_outline(*this); - svg.draw_outline(other, "blue"); - #endif - - Polylines pl_out = intersection_pl(to_polylines(other), *this); - - #if 0 - svg.draw(pl_out, "red"); - #endif - - // See unit test SCENARIO("Clipper diff with polyline", "[Clipper]") - // for in which case the intersection_pl produces any intersection. - return ! pl_out.empty() || - // If *this is completely inside other, then pl_out is empty, but the expolygons overlap. Test for that situation. - other.contains(this->contour.points.front()); -} - -bool overlaps(const ExPolygons& expolys1, const ExPolygons& expolys2) -{ - for (const ExPolygon& expoly1 : expolys1) { - for (const ExPolygon& expoly2 : expolys2) { - if (expoly1.overlaps(expoly2)) - return true; - } - } - return false; -} - -bool overlaps(const ExPolygons& expolys, const ExPolygon& expoly) -{ - for (const ExPolygon& el : expolys) { - if (el.overlaps(expoly)) - return true; - } - return false; -} - -Point projection_onto(const ExPolygons& polygons, const Point& from) -{ - Point projected_pt; - double min_dist = std::numeric_limits::max(); - - for (const auto& poly : polygons) { - for (int i = 0; i < poly.num_contours(); i++) { - Point p = from.projection_onto(poly.contour_or_hole(i)); - double dist = (from - p).cast().squaredNorm(); - if (dist < min_dist) { - projected_pt = p; - min_dist = dist; - } - } - } - - return projected_pt; -} - -void ExPolygon::simplify_p(double tolerance, Polygons* polygons) const -{ - Polygons pp = this->simplify_p(tolerance); - polygons->insert(polygons->end(), pp.begin(), pp.end()); -} - -Polygons ExPolygon::simplify_p(double tolerance) const -{ - Polygons pp; - pp.reserve(this->holes.size() + 1); - // contour - { - Polygon p = this->contour; - p.points.push_back(p.points.front()); - p.points = MultiPoint::_douglas_peucker(p.points, tolerance); - p.points.pop_back(); - pp.emplace_back(std::move(p)); - } - // holes - for (Polygon p : this->holes) { - p.points.push_back(p.points.front()); - p.points = MultiPoint::_douglas_peucker(p.points, tolerance); - p.points.pop_back(); - pp.emplace_back(std::move(p)); - } - return simplify_polygons(pp); -} - -ExPolygons ExPolygon::simplify(double tolerance) const -{ - return union_ex(this->simplify_p(tolerance)); -} - -void ExPolygon::simplify(double tolerance, ExPolygons* expolygons) const -{ - append(*expolygons, this->simplify(tolerance)); -} - -void ExPolygon::medial_axis(double min_width, double max_width, ThickPolylines* polylines) const -{ - // init helper object - Slic3r::Geometry::MedialAxis ma(min_width, max_width, *this); - - // compute the Voronoi diagram and extract medial axis polylines - ThickPolylines pp; - ma.build(&pp); - - /* - SVG svg("medial_axis.svg"); - svg.draw(*this); - svg.draw(pp); - svg.Close(); - */ - - /* Find the maximum width returned; we're going to use this for validating and - filtering the output segments. */ - double max_w = 0; - for (ThickPolylines::const_iterator it = pp.begin(); it != pp.end(); ++it) - max_w = fmaxf(max_w, *std::max_element(it->width.begin(), it->width.end())); - - /* Loop through all returned polylines in order to extend their endpoints to the - expolygon boundaries */ - bool removed = false; - for (size_t i = 0; i < pp.size(); ++i) { - ThickPolyline& polyline = pp[i]; - - // extend initial and final segments of each polyline if they're actual endpoints - /* We assign new endpoints to temporary variables because in case of a single-line - polyline, after we extend the start point it will be caught by the intersection() - call, so we keep the inner point until we perform the second intersection() as well */ - Point new_front = polyline.points.front(); - Point new_back = polyline.points.back(); - if (polyline.endpoints.first && !this->on_boundary(new_front, SCALED_EPSILON)) { - Vec2d p1 = polyline.points.front().cast(); - Vec2d p2 = polyline.points[1].cast(); - // prevent the line from touching on the other side, otherwise intersection() might return that solution - if (polyline.points.size() == 2) - p2 = (p1 + p2) * 0.5; - // Extend the start of the segment. - p1 -= (p2 - p1).normalized() * max_width; - this->contour.intersection(Line(p1.cast(), p2.cast()), &new_front); - } - if (polyline.endpoints.second && !this->on_boundary(new_back, SCALED_EPSILON)) { - Vec2d p1 = (polyline.points.end() - 2)->cast(); - Vec2d p2 = polyline.points.back().cast(); - // prevent the line from touching on the other side, otherwise intersection() might return that solution - if (polyline.points.size() == 2) - p1 = (p1 + p2) * 0.5; - // Extend the start of the segment. - p2 += (p2 - p1).normalized() * max_width; - this->contour.intersection(Line(p1.cast(), p2.cast()), &new_back); - } - polyline.points.front() = new_front; - polyline.points.back() = new_back; - - /* remove too short polylines - (we can't do this check before endpoints extension and clipping because we don't - know how long will the endpoints be extended since it depends on polygon thickness - which is variable - extension will be <= max_width/2 on each side) */ - if ((polyline.endpoints.first || polyline.endpoints.second) - && polyline.length() < max_w*2) { - pp.erase(pp.begin() + i); - --i; - removed = true; - continue; - } - } - - /* If we removed any short polylines we now try to connect consecutive polylines - in order to allow loop detection. Note that this algorithm is greedier than - MedialAxis::process_edge_neighbors() as it will connect random pairs of - polylines even when more than two start from the same point. This has no - drawbacks since we optimize later using nearest-neighbor which would do the - same, but should we use a more sophisticated optimization algorithm we should - not connect polylines when more than two meet. */ - if (removed) { - for (size_t i = 0; i < pp.size(); ++i) { - ThickPolyline& polyline = pp[i]; - if (polyline.endpoints.first && polyline.endpoints.second) continue; // optimization - - // find another polyline starting here - for (size_t j = i+1; j < pp.size(); ++j) { - ThickPolyline& other = pp[j]; - if (polyline.last_point() == other.last_point()) { - other.reverse(); - } else if (polyline.first_point() == other.last_point()) { - polyline.reverse(); - other.reverse(); - } else if (polyline.first_point() == other.first_point()) { - polyline.reverse(); - } else if (polyline.last_point() != other.first_point()) { - continue; - } - - polyline.points.insert(polyline.points.end(), other.points.begin() + 1, other.points.end()); - polyline.width.insert(polyline.width.end(), other.width.begin(), other.width.end()); - polyline.endpoints.second = other.endpoints.second; - assert(polyline.width.size() == polyline.points.size()*2 - 2); - - pp.erase(pp.begin() + j); - j = i; // restart search from i+1 - } - } - } - - polylines->insert(polylines->end(), pp.begin(), pp.end()); -} - -void ExPolygon::medial_axis(double min_width, double max_width, Polylines* polylines) const -{ - ThickPolylines tp; - this->medial_axis(min_width, max_width, &tp); - polylines->reserve(polylines->size() + tp.size()); - for (auto &pl : tp) - polylines->emplace_back(pl.points); -} - -Lines ExPolygon::lines() const -{ - Lines lines = this->contour.lines(); - for (Polygons::const_iterator h = this->holes.begin(); h != this->holes.end(); ++h) { - Lines hole_lines = h->lines(); - lines.insert(lines.end(), hole_lines.begin(), hole_lines.end()); - } - return lines; -} - -// Do expolygons match? If they match, they must have the same topology, -// however their contours may be rotated. -bool expolygons_match(const ExPolygon &l, const ExPolygon &r) -{ - if (l.holes.size() != r.holes.size() || ! polygons_match(l.contour, r.contour)) - return false; - for (size_t hole_idx = 0; hole_idx < l.holes.size(); ++ hole_idx) - if (! polygons_match(l.holes[hole_idx], r.holes[hole_idx])) - return false; - return true; -} - -BoundingBox get_extents(const ExPolygon &expolygon) -{ - return get_extents(expolygon.contour); -} - -BoundingBox get_extents(const ExPolygons &expolygons) -{ - BoundingBox bbox; - if (! expolygons.empty()) { - for (size_t i = 0; i < expolygons.size(); ++ i) - if (! expolygons[i].contour.points.empty()) - bbox.merge(get_extents(expolygons[i])); - } - return bbox; -} - -BoundingBox get_extents_rotated(const ExPolygon &expolygon, double angle) -{ - return get_extents_rotated(expolygon.contour, angle); -} - -BoundingBox get_extents_rotated(const ExPolygons &expolygons, double angle) -{ - BoundingBox bbox; - if (! expolygons.empty()) { - bbox = get_extents_rotated(expolygons.front().contour, angle); - for (size_t i = 1; i < expolygons.size(); ++ i) - bbox.merge(get_extents_rotated(expolygons[i].contour, angle)); - } - return bbox; -} - -extern std::vector get_extents_vector(const ExPolygons &polygons) -{ - std::vector out; - out.reserve(polygons.size()); - for (ExPolygons::const_iterator it = polygons.begin(); it != polygons.end(); ++ it) - out.push_back(get_extents(*it)); - return out; -} - -bool has_duplicate_points(const ExPolygon &expoly) -{ -#if 1 - // Check globally. - size_t cnt = expoly.contour.points.size(); - for (const Polygon &hole : expoly.holes) - cnt += hole.points.size(); - Points allpts; - allpts.reserve(cnt); - allpts.insert(allpts.begin(), expoly.contour.points.begin(), expoly.contour.points.end()); - for (const Polygon &hole : expoly.holes) - allpts.insert(allpts.end(), hole.points.begin(), hole.points.end()); - return has_duplicate_points(std::move(allpts)); -#else - // Check per contour. - if (has_duplicate_points(expoly.contour)) - return true; - for (const Polygon &hole : expoly.holes) - if (has_duplicate_points(hole)) - return true; - return false; -#endif -} - -bool has_duplicate_points(const ExPolygons &expolys) -{ -#if 1 - // Check globally. - Points allpts; - allpts.reserve(count_points(expolys)); - for (const ExPolygon &expoly : expolys) { - allpts.insert(allpts.begin(), expoly.contour.points.begin(), expoly.contour.points.end()); - for (const Polygon &hole : expoly.holes) - allpts.insert(allpts.end(), hole.points.begin(), hole.points.end()); - } - return has_duplicate_points(std::move(allpts)); -#else - // Check per contour. - for (const ExPolygon &expoly : expolys) - if (has_duplicate_points(expoly)) - return true; - return false; -#endif -} - -bool remove_same_neighbor(ExPolygons &expolygons) -{ - if (expolygons.empty()) - return false; - bool remove_from_holes = false; - bool remove_from_contour = false; - for (ExPolygon &expoly : expolygons) { - remove_from_contour |= remove_same_neighbor(expoly.contour); - remove_from_holes |= remove_same_neighbor(expoly.holes); - } - // Removing of expolygons without contour - if (remove_from_contour) - expolygons.erase(std::remove_if(expolygons.begin(), expolygons.end(), - [](const ExPolygon &p) { return p.contour.points.size() <= 2; }), - expolygons.end()); - return remove_from_holes || remove_from_contour; -} - -bool remove_sticks(ExPolygon &poly) -{ - return remove_sticks(poly.contour) || remove_sticks(poly.holes); -} - -bool remove_small_and_small_holes(ExPolygons &expolygons, double min_area) -{ - bool modified = false; - size_t free_idx = 0; - for (size_t expoly_idx = 0; expoly_idx < expolygons.size(); ++expoly_idx) { - if (std::abs(expolygons[expoly_idx].area()) >= min_area) { - // Expolygon is big enough, so also check all its holes - modified |= remove_small(expolygons[expoly_idx].holes, min_area); - if (free_idx < expoly_idx) { - std::swap(expolygons[expoly_idx].contour, expolygons[free_idx].contour); - std::swap(expolygons[expoly_idx].holes, expolygons[free_idx].holes); - } - ++free_idx; - } else - modified = true; - } - if (free_idx < expolygons.size()) - expolygons.erase(expolygons.begin() + free_idx, expolygons.end()); - return modified; -} - -void keep_largest_contour_only(ExPolygons &polygons) -{ - if (polygons.size() > 1) { - double max_area = 0.; - ExPolygon* max_area_polygon = nullptr; - for (ExPolygon& p : polygons) { - double a = p.contour.area(); - if (a > max_area) { - max_area = a; - max_area_polygon = &p; - } - } - assert(max_area_polygon != nullptr); - ExPolygon p(std::move(*max_area_polygon)); - polygons.clear(); - polygons.emplace_back(std::move(p)); - } -} - -} // namespace Slic3r +#include "BoundingBox.hpp" +#include "ExPolygon.hpp" +#include "Exception.hpp" +#include "Geometry/MedialAxis.hpp" +#include "Polygon.hpp" +#include "Line.hpp" +#include "ClipperUtils.hpp" +#include "SVG.hpp" +#include +#include +#include + +namespace Slic3r { + +void ExPolygon::scale(double factor) +{ + contour.scale(factor); + for (Polygon &hole : holes) + hole.scale(factor); +} + +void ExPolygon::scale(double factor_x, double factor_y) +{ + contour.scale(factor_x, factor_y); + for (Polygon &hole : holes) + hole.scale(factor_x, factor_y); +} + +void ExPolygon::translate(const Point &p) +{ + contour.translate(p); + for (Polygon &hole : holes) + hole.translate(p); +} + +void ExPolygon::rotate(double angle) +{ + contour.rotate(angle); + for (Polygon &hole : holes) + hole.rotate(angle); +} + +void ExPolygon::rotate(double angle, const Point ¢er) +{ + contour.rotate(angle, center); + for (Polygon &hole : holes) + hole.rotate(angle, center); +} + +double ExPolygon::area() const +{ + double a = this->contour.area(); + for (const Polygon &hole : holes) + a -= - hole.area(); // holes have negative area + return a; +} + +bool ExPolygon::is_valid() const +{ + if (!this->contour.is_valid() || !this->contour.is_counter_clockwise()) return false; + for (Polygons::const_iterator it = this->holes.begin(); it != this->holes.end(); ++it) { + if (!(*it).is_valid() || (*it).is_counter_clockwise()) return false; + } + return true; +} + +void ExPolygon::douglas_peucker(double tolerance) +{ + this->contour.douglas_peucker(tolerance); + for (Polygon &poly : this->holes) + poly.douglas_peucker(tolerance); +} + +bool ExPolygon::contains(const Line &line) const +{ + return this->contains(Polyline(line.a, line.b)); +} + +bool ExPolygon::contains(const Polyline &polyline) const +{ + BoundingBox bbox1 = get_extents(*this); + BoundingBox bbox2 = get_extents(polyline); + bbox2.inflated(1); + if (!bbox1.overlap(bbox2)) + return false; + + return diff_pl(polyline, *this).empty(); +} + +bool ExPolygon::contains(const Polylines &polylines) const +{ + #if 0 + BoundingBox bbox = get_extents(polylines); + bbox.merge(get_extents(*this)); + SVG svg(debug_out_path("ExPolygon_contains.svg"), bbox); + svg.draw(*this); + svg.draw_outline(*this); + svg.draw(polylines, "blue"); + #endif + Polylines pl_out = diff_pl(polylines, *this); + #if 0 + svg.draw(pl_out, "red"); + #endif + return pl_out.empty(); +} + +bool ExPolygon::contains(const Point &point, bool border_result /* = true */) const +{ + if (! Slic3r::contains(contour, point, border_result)) + // Outside the outer contour, not on the contour boundary. + return false; + for (const Polygon &hole : this->holes) + if (Slic3r::contains(hole, point, ! border_result)) + // Inside a hole, not on the hole boundary. + return false; + return true; +} + +bool ExPolygon::on_boundary(const Point &point, double eps) const +{ + if (this->contour.on_boundary(point, eps)) + return true; + for (const Polygon &hole : this->holes) + if (hole.on_boundary(point, eps)) + return true; + return false; +} + +// Projection of a point onto the polygon. +Point ExPolygon::point_projection(const Point &point) const +{ + if (this->holes.empty()) { + return this->contour.point_projection(point); + } else { + double dist_min2 = std::numeric_limits::max(); + Point closest_pt_min; + for (size_t i = 0; i < this->num_contours(); ++ i) { + Point closest_pt = this->contour_or_hole(i).point_projection(point); + double d2 = (closest_pt - point).cast().squaredNorm(); + if (d2 < dist_min2) { + dist_min2 = d2; + closest_pt_min = closest_pt; + } + } + return closest_pt_min; + } +} + +void ExPolygon::symmetric_y(const coord_t &y_axis) +{ + this->contour.symmetric_y(y_axis); + for (Polygon &hole : holes) + hole.symmetric_y(y_axis); +} + +bool ExPolygon::overlaps(const ExPolygon &other) const +{ + if (this->empty() || other.empty()) + return false; + + #if 0 + BoundingBox bbox = get_extents(other); + bbox.merge(get_extents(*this)); + static int iRun = 0; + SVG svg(debug_out_path("ExPolygon_overlaps-%d.svg", iRun ++), bbox); + svg.draw(*this); + svg.draw_outline(*this); + svg.draw_outline(other, "blue"); + #endif + + Polylines pl_out = intersection_pl(to_polylines(other), *this); + + #if 0 + svg.draw(pl_out, "red"); + #endif + + // See unit test SCENARIO("Clipper diff with polyline", "[Clipper]") + // for in which case the intersection_pl produces any intersection. + return ! pl_out.empty() || + // If *this is completely inside other, then pl_out is empty, but the expolygons overlap. Test for that situation. + other.contains(this->contour.points.front()); +} + +bool overlaps(const ExPolygons& expolys1, const ExPolygons& expolys2) +{ + for (const ExPolygon& expoly1 : expolys1) { + for (const ExPolygon& expoly2 : expolys2) { + if (expoly1.overlaps(expoly2)) + return true; + } + } + return false; +} + +bool overlaps(const ExPolygons& expolys, const ExPolygon& expoly) +{ + for (const ExPolygon& el : expolys) { + if (el.overlaps(expoly)) + return true; + } + return false; +} + +Point projection_onto(const ExPolygons& polygons, const Point& from) +{ + Point projected_pt; + double min_dist = std::numeric_limits::max(); + + for (const auto& poly : polygons) { + for (int i = 0; i < poly.num_contours(); i++) { + Point p = from.projection_onto(poly.contour_or_hole(i)); + double dist = (from - p).cast().squaredNorm(); + if (dist < min_dist) { + projected_pt = p; + min_dist = dist; + } + } + } + + return projected_pt; +} + +void ExPolygon::simplify_p(double tolerance, Polygons* polygons) const +{ + Polygons pp = this->simplify_p(tolerance); + polygons->insert(polygons->end(), pp.begin(), pp.end()); +} + +Polygons ExPolygon::simplify_p(double tolerance) const +{ + Polygons pp; + pp.reserve(this->holes.size() + 1); + // contour + { + Polygon p = this->contour; + p.points.push_back(p.points.front()); + p.points = MultiPoint::_douglas_peucker(p.points, tolerance); + p.points.pop_back(); + pp.emplace_back(std::move(p)); + } + // holes + for (Polygon p : this->holes) { + p.points.push_back(p.points.front()); + p.points = MultiPoint::_douglas_peucker(p.points, tolerance); + p.points.pop_back(); + pp.emplace_back(std::move(p)); + } + return simplify_polygons(pp); +} + +ExPolygons ExPolygon::simplify(double tolerance) const +{ + return union_ex(this->simplify_p(tolerance)); +} + +void ExPolygon::simplify(double tolerance, ExPolygons* expolygons) const +{ + append(*expolygons, this->simplify(tolerance)); +} + +void ExPolygon::medial_axis(double min_width, double max_width, ThickPolylines* polylines) const +{ + // init helper object + Slic3r::Geometry::MedialAxis ma(min_width, max_width, *this); + + // compute the Voronoi diagram and extract medial axis polylines + ThickPolylines pp; + ma.build(&pp); + + /* + SVG svg("medial_axis.svg"); + svg.draw(*this); + svg.draw(pp); + svg.Close(); + */ + + /* Find the maximum width returned; we're going to use this for validating and + filtering the output segments. */ + double max_w = 0; + for (ThickPolylines::const_iterator it = pp.begin(); it != pp.end(); ++it) + max_w = fmaxf(max_w, *std::max_element(it->width.begin(), it->width.end())); + + /* Loop through all returned polylines in order to extend their endpoints to the + expolygon boundaries */ + bool removed = false; + for (size_t i = 0; i < pp.size(); ++i) { + ThickPolyline& polyline = pp[i]; + + // extend initial and final segments of each polyline if they're actual endpoints + /* We assign new endpoints to temporary variables because in case of a single-line + polyline, after we extend the start point it will be caught by the intersection() + call, so we keep the inner point until we perform the second intersection() as well */ + Point new_front = polyline.points.front(); + Point new_back = polyline.points.back(); + if (polyline.endpoints.first && !this->on_boundary(new_front, SCALED_EPSILON)) { + Vec2d p1 = polyline.points.front().cast(); + Vec2d p2 = polyline.points[1].cast(); + // prevent the line from touching on the other side, otherwise intersection() might return that solution + if (polyline.points.size() == 2) + p2 = (p1 + p2) * 0.5; + // Extend the start of the segment. + p1 -= (p2 - p1).normalized() * max_width; + this->contour.intersection(Line(p1.cast(), p2.cast()), &new_front); + } + if (polyline.endpoints.second && !this->on_boundary(new_back, SCALED_EPSILON)) { + Vec2d p1 = (polyline.points.end() - 2)->cast(); + Vec2d p2 = polyline.points.back().cast(); + // prevent the line from touching on the other side, otherwise intersection() might return that solution + if (polyline.points.size() == 2) + p1 = (p1 + p2) * 0.5; + // Extend the start of the segment. + p2 += (p2 - p1).normalized() * max_width; + this->contour.intersection(Line(p1.cast(), p2.cast()), &new_back); + } + polyline.points.front() = new_front; + polyline.points.back() = new_back; + + /* remove too short polylines + (we can't do this check before endpoints extension and clipping because we don't + know how long will the endpoints be extended since it depends on polygon thickness + which is variable - extension will be <= max_width/2 on each side) */ + if ((polyline.endpoints.first || polyline.endpoints.second) + && polyline.length() < max_w*2) { + pp.erase(pp.begin() + i); + --i; + removed = true; + continue; + } + } + + /* If we removed any short polylines we now try to connect consecutive polylines + in order to allow loop detection. Note that this algorithm is greedier than + MedialAxis::process_edge_neighbors() as it will connect random pairs of + polylines even when more than two start from the same point. This has no + drawbacks since we optimize later using nearest-neighbor which would do the + same, but should we use a more sophisticated optimization algorithm we should + not connect polylines when more than two meet. */ + if (removed) { + for (size_t i = 0; i < pp.size(); ++i) { + ThickPolyline& polyline = pp[i]; + if (polyline.endpoints.first && polyline.endpoints.second) continue; // optimization + + // find another polyline starting here + for (size_t j = i+1; j < pp.size(); ++j) { + ThickPolyline& other = pp[j]; + if (polyline.last_point() == other.last_point()) { + other.reverse(); + } else if (polyline.first_point() == other.last_point()) { + polyline.reverse(); + other.reverse(); + } else if (polyline.first_point() == other.first_point()) { + polyline.reverse(); + } else if (polyline.last_point() != other.first_point()) { + continue; + } + + polyline.points.insert(polyline.points.end(), other.points.begin() + 1, other.points.end()); + polyline.width.insert(polyline.width.end(), other.width.begin(), other.width.end()); + polyline.endpoints.second = other.endpoints.second; + assert(polyline.width.size() == polyline.points.size()*2 - 2); + + pp.erase(pp.begin() + j); + j = i; // restart search from i+1 + } + } + } + + polylines->insert(polylines->end(), pp.begin(), pp.end()); +} + +void ExPolygon::medial_axis(double min_width, double max_width, Polylines* polylines) const +{ + ThickPolylines tp; + this->medial_axis(min_width, max_width, &tp); + polylines->reserve(polylines->size() + tp.size()); + for (auto &pl : tp) + polylines->emplace_back(pl.points); +} + +Lines ExPolygon::lines() const +{ + Lines lines = this->contour.lines(); + for (Polygons::const_iterator h = this->holes.begin(); h != this->holes.end(); ++h) { + Lines hole_lines = h->lines(); + lines.insert(lines.end(), hole_lines.begin(), hole_lines.end()); + } + return lines; +} + +// Do expolygons match? If they match, they must have the same topology, +// however their contours may be rotated. +bool expolygons_match(const ExPolygon &l, const ExPolygon &r) +{ + if (l.holes.size() != r.holes.size() || ! polygons_match(l.contour, r.contour)) + return false; + for (size_t hole_idx = 0; hole_idx < l.holes.size(); ++ hole_idx) + if (! polygons_match(l.holes[hole_idx], r.holes[hole_idx])) + return false; + return true; +} + +BoundingBox get_extents(const ExPolygon &expolygon) +{ + return get_extents(expolygon.contour); +} + +BoundingBox get_extents(const ExPolygons &expolygons) +{ + BoundingBox bbox; + if (! expolygons.empty()) { + for (size_t i = 0; i < expolygons.size(); ++ i) + if (! expolygons[i].contour.points.empty()) + bbox.merge(get_extents(expolygons[i])); + } + return bbox; +} + +BoundingBox get_extents_rotated(const ExPolygon &expolygon, double angle) +{ + return get_extents_rotated(expolygon.contour, angle); +} + +BoundingBox get_extents_rotated(const ExPolygons &expolygons, double angle) +{ + BoundingBox bbox; + if (! expolygons.empty()) { + bbox = get_extents_rotated(expolygons.front().contour, angle); + for (size_t i = 1; i < expolygons.size(); ++ i) + bbox.merge(get_extents_rotated(expolygons[i].contour, angle)); + } + return bbox; +} + +extern std::vector get_extents_vector(const ExPolygons &polygons) +{ + std::vector out; + out.reserve(polygons.size()); + for (ExPolygons::const_iterator it = polygons.begin(); it != polygons.end(); ++ it) + out.push_back(get_extents(*it)); + return out; +} + +bool has_duplicate_points(const ExPolygon &expoly) +{ +#if 1 + // Check globally. + size_t cnt = expoly.contour.points.size(); + for (const Polygon &hole : expoly.holes) + cnt += hole.points.size(); + Points allpts; + allpts.reserve(cnt); + allpts.insert(allpts.begin(), expoly.contour.points.begin(), expoly.contour.points.end()); + for (const Polygon &hole : expoly.holes) + allpts.insert(allpts.end(), hole.points.begin(), hole.points.end()); + return has_duplicate_points(std::move(allpts)); +#else + // Check per contour. + if (has_duplicate_points(expoly.contour)) + return true; + for (const Polygon &hole : expoly.holes) + if (has_duplicate_points(hole)) + return true; + return false; +#endif +} + +bool has_duplicate_points(const ExPolygons &expolys) +{ +#if 1 + // Check globally. + Points allpts; + allpts.reserve(count_points(expolys)); + for (const ExPolygon &expoly : expolys) { + allpts.insert(allpts.begin(), expoly.contour.points.begin(), expoly.contour.points.end()); + for (const Polygon &hole : expoly.holes) + allpts.insert(allpts.end(), hole.points.begin(), hole.points.end()); + } + return has_duplicate_points(std::move(allpts)); +#else + // Check per contour. + for (const ExPolygon &expoly : expolys) + if (has_duplicate_points(expoly)) + return true; + return false; +#endif +} + +bool remove_same_neighbor(ExPolygons &expolygons) +{ + if (expolygons.empty()) + return false; + bool remove_from_holes = false; + bool remove_from_contour = false; + for (ExPolygon &expoly : expolygons) { + remove_from_contour |= remove_same_neighbor(expoly.contour); + remove_from_holes |= remove_same_neighbor(expoly.holes); + } + // Removing of expolygons without contour + if (remove_from_contour) + expolygons.erase(std::remove_if(expolygons.begin(), expolygons.end(), + [](const ExPolygon &p) { return p.contour.points.size() <= 2; }), + expolygons.end()); + return remove_from_holes || remove_from_contour; +} + +bool remove_sticks(ExPolygon &poly) +{ + return remove_sticks(poly.contour) || remove_sticks(poly.holes); +} + +bool remove_small_and_small_holes(ExPolygons &expolygons, double min_area) +{ + bool modified = false; + size_t free_idx = 0; + for (size_t expoly_idx = 0; expoly_idx < expolygons.size(); ++expoly_idx) { + if (std::abs(expolygons[expoly_idx].area()) >= min_area) { + // Expolygon is big enough, so also check all its holes + modified |= remove_small(expolygons[expoly_idx].holes, min_area); + if (free_idx < expoly_idx) { + std::swap(expolygons[expoly_idx].contour, expolygons[free_idx].contour); + std::swap(expolygons[expoly_idx].holes, expolygons[free_idx].holes); + } + ++free_idx; + } else + modified = true; + } + if (free_idx < expolygons.size()) + expolygons.erase(expolygons.begin() + free_idx, expolygons.end()); + return modified; +} + +void keep_largest_contour_only(ExPolygons &polygons) +{ + if (polygons.size() > 1) { + double max_area = 0.; + ExPolygon* max_area_polygon = nullptr; + for (ExPolygon& p : polygons) { + double a = p.contour.area(); + if (a > max_area) { + max_area = a; + max_area_polygon = &p; + } + } + assert(max_area_polygon != nullptr); + ExPolygon p(std::move(*max_area_polygon)); + polygons.clear(); + polygons.emplace_back(std::move(p)); + } +} + +} // namespace Slic3r diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index 187ef27a11..87a1146d41 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -56,7 +56,7 @@ public: bool on_boundary(const Point &point, double eps) const; // Projection of a point onto the polygon. Point point_projection(const Point &point) const; - + void symmetric_y(const coord_t &y_axis); // Does this expolygon overlap another expolygon? // Either the ExPolygons intersect, or one is fully inside the other, // and it is not inside a hole of the other expolygon. diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index a3d8b45323..47726328c4 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -34,7 +34,6 @@ struct SurfaceFillParams coordf_t overlap = 0.; // Angle as provided by the region config, in radians. float angle = 0.f; - bool rotate_angle = true; // Is bridging used for this fill? Bridging parameters may be used even if this->flow.bridge() is not set. bool bridge; // Non-negative for a bridge. @@ -68,6 +67,9 @@ struct SurfaceFillParams // Params for lattice infill angles float lattice_angle_1 = 0.f; float lattice_angle_2 = 0.f; + float infill_lock_depth = 0; + float skin_infill_depth = 0; + bool symmetric_infill_y_axis = false; // Params for 2D honeycomb float infill_overhang_angle = 60.f; @@ -85,7 +87,6 @@ struct SurfaceFillParams RETURN_COMPARE_NON_EQUAL(spacing); RETURN_COMPARE_NON_EQUAL(overlap); RETURN_COMPARE_NON_EQUAL(angle); - RETURN_COMPARE_NON_EQUAL(rotate_angle); RETURN_COMPARE_NON_EQUAL(density); // RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, dont_adjust); RETURN_COMPARE_NON_EQUAL(anchor_length); @@ -100,33 +101,36 @@ struct SurfaceFillParams RETURN_COMPARE_NON_EQUAL(solid_infill_speed); RETURN_COMPARE_NON_EQUAL(lattice_angle_1); RETURN_COMPARE_NON_EQUAL(lattice_angle_2); - RETURN_COMPARE_NON_EQUAL(infill_overhang_angle); + RETURN_COMPARE_NON_EQUAL(symmetric_infill_y_axis); + RETURN_COMPARE_NON_EQUAL(infill_lock_depth); + RETURN_COMPARE_NON_EQUAL(skin_infill_depth); RETURN_COMPARE_NON_EQUAL(infill_overhang_angle); return false; } - bool operator==(const SurfaceFillParams &rhs) const { - return this->extruder == rhs.extruder && - this->pattern == rhs.pattern && - this->spacing == rhs.spacing && - this->overlap == rhs.overlap && - this->angle == rhs.angle && - this->rotate_angle == rhs.rotate_angle && - this->bridge == rhs.bridge && - this->bridge_angle == rhs.bridge_angle && - this->density == rhs.density && -// this->dont_adjust == rhs.dont_adjust && - this->anchor_length == rhs.anchor_length && - this->anchor_length_max == rhs.anchor_length_max && - this->flow == rhs.flow && - this->extrusion_role == rhs.extrusion_role && - this->sparse_infill_speed == rhs.sparse_infill_speed && - this->top_surface_speed == rhs.top_surface_speed && - this->solid_infill_speed == rhs.solid_infill_speed && - this->lattice_angle_1 == rhs.lattice_angle_1 && - this->lattice_angle_2 == rhs.lattice_angle_2 && + bool operator==(const SurfaceFillParams &rhs) const { + return this->extruder == rhs.extruder && + this->pattern == rhs.pattern && + this->spacing == rhs.spacing && + this->overlap == rhs.overlap && + this->angle == rhs.angle && + this->bridge == rhs.bridge && + this->bridge_angle == rhs.bridge_angle && + this->density == rhs.density && +// this->dont_adjust == rhs.dont_adjust && + this->anchor_length == rhs.anchor_length && + this->anchor_length_max == rhs.anchor_length_max && + this->flow == rhs.flow && + this->extrusion_role == rhs.extrusion_role && + this->sparse_infill_speed == rhs.sparse_infill_speed && + this->top_surface_speed == rhs.top_surface_speed && + this->solid_infill_speed == rhs.solid_infill_speed && + this->lattice_angle_1 == rhs.lattice_angle_1 && + this->lattice_angle_2 == rhs.lattice_angle_2 && + this->infill_lock_depth == rhs.infill_lock_depth && + this->skin_infill_depth == rhs.skin_infill_depth && this->infill_overhang_angle == rhs.infill_overhang_angle; - } + } }; struct SurfaceFill { @@ -602,16 +606,34 @@ void split_solid_surface(size_t layer_id, const SurfaceFill &fill, ExPolygons &n #endif } -std::vector group_fills(const Layer &layer) +std::vector group_fills(const Layer &layer, LockRegionParam &lock_param) { std::vector surface_fills; - // Fill in a map of a region & surface to SurfaceFillParams. std::set set_surface_params; std::vector> region_to_surface_params(layer.regions().size(), std::vector()); SurfaceFillParams params; bool has_internal_voids = false; const PrintObjectConfig& object_config = layer.object()->config(); + + auto append_flow_param = [](std::map &flow_params, Flow flow, const ExPolygon &exp) { + auto it = flow_params.find(flow); + if (it == flow_params.end()) + flow_params.insert({flow, {exp}}); + else + it->second.push_back(exp); + it++; + }; + + auto append_density_param = [](std::map &density_params, float density, const ExPolygon &exp) { + auto it = density_params.find(density); + if (it == density_params.end()) + density_params.insert({density, {exp}}); + else + it->second.push_back(exp); + it++; + }; + for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id) { const LayerRegion &layerm = *layer.regions()[region_id]; region_to_surface_params[region_id].assign(layerm.fill_surfaces.size(), nullptr); @@ -628,8 +650,19 @@ std::vector group_fills(const Layer &layer) params.lattice_angle_1 = region_config.lattice_angle_1; params.lattice_angle_2 = region_config.lattice_angle_2; params.infill_overhang_angle = region_config.infill_overhang_angle; + if (params.pattern == ipLockedZag) { + params.infill_lock_depth = scale_(region_config.infill_lock_depth); + params.skin_infill_depth = scale_(region_config.skin_infill_depth); + } + if (params.pattern == ipCrossZag || params.pattern == ipLockedZag) { + params.symmetric_infill_y_axis = region_config.symmetric_infill_y_axis; + } else if (params.pattern == ipZigZag) { - if (surface.is_solid()) { + + params.symmetric_infill_y_axis = region_config.symmetric_infill_y_axis; + } + + if (surface.is_solid()) { params.density = 100.f; //FIXME for non-thick bridges, shall we allow a bottom surface pattern? if (surface.is_solid_infill()) @@ -667,10 +700,8 @@ std::vector group_fills(const Layer &layer) params.bridge_angle = float(surface.bridge_angle); if (params.extrusion_role == erInternalInfill) { params.angle = float(Geometry::deg2rad(region_config.infill_direction.value)); - params.rotate_angle = (params.pattern == ipRectilinear || params.pattern == ipLine); } else { params.angle = float(Geometry::deg2rad(region_config.solid_infill_direction.value)); - params.rotate_angle = region_config.rotate_solid_infill_direction; } // Calculate the actual flow we'll be using for this infill. @@ -709,7 +740,28 @@ std::vector group_fills(const Layer &layer) params.anchor_length = std::min(params.anchor_length, params.anchor_length_max); } - auto it_params = set_surface_params.find(params); + //get locked region param + if (params.pattern == ipLockedZag){ + const PrintObject *object = layerm.layer()->object(); + auto nozzle_diameter = float(object->print()->config().nozzle_diameter.get_at(layerm.region().extruder(extrusion_role) - 1)); + Flow skin_flow = params.bridge ? params.flow : Flow::new_from_config_width(extrusion_role, region_config.skin_infill_line_width, nozzle_diameter, float((surface.thickness == -1) ? layer.height : surface.thickness)); + //add skin flow + append_flow_param(lock_param.skin_flow_params, skin_flow, surface.expolygon); + + Flow skeleton_flow = params.bridge ? params.flow : Flow::new_from_config_width(extrusion_role, region_config.skeleton_infill_line_width, nozzle_diameter, float((surface.thickness == -1) ? layer.height : surface.thickness)) ; + // add skeleton flow + append_flow_param(lock_param.skeleton_flow_params, skeleton_flow, surface.expolygon); + + // add skin density + append_density_param(lock_param.skin_density_params, float(0.01 * region_config.skin_infill_density), surface.expolygon); + + // add skin density + append_density_param(lock_param.skeleton_density_params, float(0.01 * region_config.skeleton_infill_density), surface.expolygon); + + } + + auto it_params = set_surface_params.find(params); + if (it_params == set_surface_params.end()) it_params = set_surface_params.insert(it_params, params); region_to_surface_params[region_id][&surface - &layerm.fill_surfaces.surfaces.front()] = &(*it_params); @@ -829,7 +881,6 @@ std::vector group_fills(const Layer &layer) params.density = 100.f; params.extrusion_role = erSolidInfill; params.angle = float(Geometry::deg2rad(layerm.region().config().solid_infill_direction.value)); - params.rotate_angle = layerm.region().config().rotate_solid_infill_direction; // calculate the actual flow we'll be using for this infill params.flow = layerm.flow(frSolidInfill); params.spacing = params.flow.spacing(); @@ -914,8 +965,8 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: #ifdef SLIC3R_DEBUG_SLICE_PROCESSING // this->export_region_fill_surfaces_to_svg_debug("10_fill-initial"); #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - - std::vector surface_fills = group_fills(*this); + LockRegionParam lock_param; + std::vector surface_fills = group_fills(*this, lock_param); const Slic3r::BoundingBox bbox = this->object()->bounding_box(); const auto resolution = this->object()->print()->config().resolution.value; @@ -933,14 +984,22 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: f->layer_id = this->id(); f->z = this->print_z; f->angle = surface_fill.params.angle; - f->rotate_angle = surface_fill.params.rotate_angle; f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree; f->print_config = &this->object()->print()->config(); f->print_object_config = &this->object()->config(); - - if (surface_fill.params.pattern == ipLightning) + f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree; + if (surface_fill.params.pattern == ipConcentricInternal) { + FillConcentricInternal *fill_concentric = dynamic_cast(f.get()); + assert(fill_concentric != nullptr); + fill_concentric->print_config = &this->object()->print()->config(); + fill_concentric->print_object_config = &this->object()->config(); + } else if (surface_fill.params.pattern == ipConcentric) { + FillConcentric *fill_concentric = dynamic_cast(f.get()); + assert(fill_concentric != nullptr); + fill_concentric->print_config = &this->object()->print()->config(); + fill_concentric->print_object_config = &this->object()->config(); + } else if (surface_fill.params.pattern == ipLightning) dynamic_cast(f.get())->generator = lightning_generator; - // calculate flow spacing for infill pattern generation bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.bridge; double link_max_length = 0.; @@ -979,11 +1038,41 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: params.extrusion_role = surface_fill.params.extrusion_role; params.using_internal_flow = using_internal_flow; params.no_extrusion_overlap = surface_fill.params.overlap; - params.config = &layerm->region().config(); + auto ®ion_config = layerm->region().config(); + + ConfigOptionFloats rotate_angles; + rotate_angles.deserialize( surface_fill.params.extrusion_role == erInternalInfill ? region_config.sparse_infill_rotate_template.value : region_config.solid_infill_rotate_template.value); + auto rotate_angle_idx = f->layer_id % rotate_angles.size(); + f->rotate_angle = Geometry::deg2rad(rotate_angles.values[rotate_angle_idx]); + + params.config = ®ion_config; + params.pattern = surface_fill.params.pattern; + if( surface_fill.params.pattern == ipLockedZag ) { + params.locked_zag = true; + params.infill_lock_depth = surface_fill.params.infill_lock_depth; + params.skin_infill_depth = surface_fill.params.skin_infill_depth; + f->set_lock_region_param(lock_param); + } + if (surface_fill.params.pattern == ipCrossZag || surface_fill.params.pattern == ipLockedZag) { + if (f->layer_id % 2 == 0) { + params.horiz_move -= scale_(region_config.infill_shift_step) * (f->layer_id / 2); + } else { + params.horiz_move += scale_(region_config.infill_shift_step) * (f->layer_id / 2); + } + + params.symmetric_infill_y_axis = surface_fill.params.symmetric_infill_y_axis; + + } if (surface_fill.params.pattern == ipGrid) params.can_reverse = false; for (ExPolygon& expoly : surface_fill.expolygons) { - f->no_overlap_expolygons = intersection_ex(surface_fill.no_overlap_expolygons, ExPolygons() = {expoly}, ApplySafetyOffset::Yes); + + f->no_overlap_expolygons = intersection_ex(surface_fill.no_overlap_expolygons, ExPolygons() = {expoly}, ApplySafetyOffset::Yes); + if (params.symmetric_infill_y_axis) { + params.symmetric_y_axis = f->extended_object_bounding_box().center().x(); + expoly.symmetric_y(params.symmetric_y_axis); + } + // Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon. f->spacing = surface_fill.params.spacing; surface_fill.surface.expolygon = std::move(expoly); @@ -1023,9 +1112,10 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator) const { - std::vector surface_fills = group_fills(*this); - const Slic3r::BoundingBox bbox = this->object()->bounding_box(); - const auto resolution = this->object()->print()->config().resolution.value; + LockRegionParam skin_inner_param; + std::vector surface_fills = group_fills(*this, skin_inner_param); + const Slic3r::BoundingBox bbox = this->object()->bounding_box(); + const auto resolution = this->object()->print()->config().resolution.value; Polylines sparse_infill_polylines{}; @@ -1059,7 +1149,10 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Oc case ipTpmsD: case ipHilbertCurve: case ipArchimedeanChords: - case ipOctagramSpiral: break; + case ipOctagramSpiral: + case ipZigZag: + case ipCrossZag: + case ipLockedZag: break; } // Create the filler object. diff --git a/src/libslic3r/Fill/Fill3DHoneycomb.hpp b/src/libslic3r/Fill/Fill3DHoneycomb.hpp index f7a27cf4f9..ccb37a36a0 100644 --- a/src/libslic3r/Fill/Fill3DHoneycomb.hpp +++ b/src/libslic3r/Fill/Fill3DHoneycomb.hpp @@ -15,6 +15,10 @@ public: Fill* clone() const override { return new Fill3DHoneycomb(*this); }; ~Fill3DHoneycomb() override {} + // require bridge flow since most of this pattern hangs in air + bool use_bridge_flow() const override { return true; } + bool is_self_crossing() override { return false; } + protected: void _fill_surface_single( const FillParams ¶ms, diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp index 0578cc3e1b..0132ffaa6c 100644 --- a/src/libslic3r/Fill/FillAdaptive.hpp +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -71,6 +71,7 @@ protected: // may not be optimal as the internal infill lines may get extruded before the long infill // lines to which the short infill lines are supposed to anchor. bool no_sort() const override { return false; } + bool is_self_crossing() override { return true; } }; } // namespace FillAdaptive diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index 3b2f5bcc5c..9f5a54a0d9 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -67,6 +67,9 @@ Fill* Fill::new_from_type(const InfillPattern type) // BBS: for bottom and top surface only // Orca: Replace BBS implementation with Prusa implementation case ipMonotonicLine: return new FillMonotonicLines(); + case ipZigZag: return new FillZigZag(); + case ipCrossZag: return new FillCrossZag(); + case ipLockedZag: return new FillLockedZag(); default: throw Slic3r::InvalidArgument("unknown type"); } } @@ -243,7 +246,7 @@ void Fill::_create_gap_fill(const Surface* surface, const FillParams& params, Ex // Calculate a new spacing to fill width with possibly integer number of lines, // the first and last line being centered at the interval ends. -// This function possibly increases the spacing, never decreases, +// This function possibly increases the spacing, never decreases, // and for a narrow width the increase in spacing may become severe, // therefore the adjustment is limited to 20% increase. coord_t Fill::_adjust_solid_spacing(const coord_t width, const coord_t distance) @@ -252,8 +255,8 @@ coord_t Fill::_adjust_solid_spacing(const coord_t width, const coord_t distance) assert(distance > 0); // floor(width / distance) const auto number_of_intervals = coord_t((width - EPSILON) / distance); - coord_t distance_new = (number_of_intervals == 0) ? - distance : + coord_t distance_new = (number_of_intervals == 0) ? + distance : coord_t((width - EPSILON) / number_of_intervals); const coordf_t factor = coordf_t(distance_new) / coordf_t(distance); assert(factor > 1. - 1e-5); @@ -279,8 +282,8 @@ std::pair Fill::_infill_direction(const Surface *surface) const // Bounding box is the bounding box of a perl object Slic3r::Print::Object (c++ object Slic3r::PrintObject) // The bounding box is only undefined in unit tests. - Point out_shift = empty(this->bounding_box) ? - surface->expolygon.contour.bounding_box().center() : + Point out_shift = empty(this->bounding_box) ? + surface->expolygon.contour.bounding_box().center() : this->bounding_box.center(); #if 0 @@ -354,10 +357,10 @@ struct ContourIntersectionPoint { bool could_take_next() const throw() { return ! this->consumed && this->contour_not_taken_length_next > SCALED_EPSILON; } // Could extrude a complete segment from this to this->prev_on_contour. - bool could_connect_prev() const throw() + bool could_connect_prev() const throw() { return ! this->consumed && this->prev_on_contour != this && ! this->prev_on_contour->consumed && ! this->prev_trimmed && ! this->prev_on_contour->next_trimmed; } // Could extrude a complete segment from this to this->next_on_contour. - bool could_connect_next() const throw() + bool could_connect_next() const throw() { return ! this->consumed && this->next_on_contour != this && ! this->next_on_contour->consumed && ! this->next_trimmed && ! this->next_on_contour->prev_trimmed; } }; @@ -566,7 +569,7 @@ static void take(Polyline &pl1, const Polyline &pl2, const Points &contour, Cont } static void take_limited( - Polyline &pl1, const Points &contour, const std::vector ¶ms, + Polyline &pl1, const Points &contour, const std::vector ¶ms, ContourIntersectionPoint *cp_start, ContourIntersectionPoint *cp_end, bool clockwise, double take_max_length, double line_half_width) { #ifndef NDEBUG @@ -730,8 +733,8 @@ static inline SegmentPoint clip_end_segment_and_point(const Points &polyline, do // Calculate intersection of a line with a thick segment. // Returns Eucledian parameters of the line / thick segment overlap. static inline bool line_rounded_thick_segment_collision( - const Vec2d &line_a, const Vec2d &line_b, - const Vec2d &segment_a, const Vec2d &segment_b, const double offset, + const Vec2d &line_a, const Vec2d &line_b, + const Vec2d &segment_a, const Vec2d &segment_b, const double offset, std::pair &out_interval) { const Vec2d line_v0 = line_b - line_a; @@ -794,8 +797,8 @@ static inline bool line_rounded_thick_segment_collision( std::pair interval; if (Geometry::liang_barsky_line_clipping_interval( Vec2d(line_p0.dot(dir_x), line_p0.dot(dir_y)), - Vec2d(line_v0.dot(dir_x), line_v0.dot(dir_y)), - BoundingBoxf(Vec2d(0., - offset), Vec2d(segment_l, offset)), + Vec2d(line_v0.dot(dir_x), line_v0.dot(dir_y)), + BoundingBoxf(Vec2d(0., - offset), Vec2d(segment_l, offset)), interval)) extend_interval(interval.first, interval.second); } else @@ -1155,7 +1158,7 @@ void mark_boundary_segments_touching_infill( // Clip the infill polyline by the Eucledian distance along the polyline. SegmentPoint start_point = clip_start_segment_and_point(polyline.points, clip_distance); SegmentPoint end_point = clip_end_segment_and_point(polyline.points, clip_distance); - if (start_point.valid() && end_point.valid() && + if (start_point.valid() && end_point.valid() && (start_point.idx_segment < end_point.idx_segment || (start_point.idx_segment == end_point.idx_segment && start_point.t < end_point.t))) { // The clipped polyline is non-empty. #ifdef INFILL_DEBUG_OUTPUT @@ -1295,21 +1298,21 @@ struct BoundaryInfillGraph }; static Direction dir(const Point &p1, const Point &p2) { - return p1.x() == p2.x() ? + return p1.x() == p2.x() ? (p1.y() < p2.y() ? Up : Down) : (p1.x() < p2.x() ? Right : Left); } const Direction dir_prev(const ContourIntersectionPoint &cp) const { assert(cp.prev_on_contour); - return cp.could_take_prev() ? + return cp.could_take_prev() ? dir(this->point(cp), this->point(*cp.prev_on_contour)) : Taken; } const Direction dir_next(const ContourIntersectionPoint &cp) const { assert(cp.next_on_contour); - return cp.could_take_next() ? + return cp.could_take_next() ? dir(this->point(cp), this->point(*cp.next_on_contour)) : Taken; } @@ -1367,7 +1370,7 @@ static inline void mark_boundary_segments_overlapping_infill( assert(interval.first == 0.); double len_out = closed_contour_distance_ccw(contour_params[cp.point_idx], contour_params[i], contour_params.back()) + interval.second; if (len_out < cp.contour_not_taken_length_next) { - // Leaving the infill line region before exiting cp.contour_not_taken_length_next, + // Leaving the infill line region before exiting cp.contour_not_taken_length_next, // thus at least some of the contour is outside and we will extrude this segment. inside = false; break; @@ -1399,7 +1402,7 @@ static inline void mark_boundary_segments_overlapping_infill( assert(interval.first == 0.); double len_out = closed_contour_distance_cw(contour_params[cp.point_idx], contour_params[i], contour_params.back()) + interval.second; if (len_out < cp.contour_not_taken_length_prev) { - // Leaving the infill line region before exiting cp.contour_not_taken_length_next, + // Leaving the infill line region before exiting cp.contour_not_taken_length_next, // thus at least some of the contour is outside and we will extrude this segment. inside = false; break; @@ -1496,7 +1499,7 @@ BoundaryInfillGraph create_boundary_infill_graph(const Polylines &infill_ordered ContourIntersectionPoint *pthis = &out.map_infill_end_point_to_boundary[it->second]; if (pprev) { pprev->next_on_contour = pthis; - pthis->prev_on_contour = pprev; + pthis->prev_on_contour = pprev; } else pfirst = pthis; contour_intersection_points.emplace_back(pthis); @@ -1521,7 +1524,7 @@ BoundaryInfillGraph create_boundary_infill_graph(const Polylines &infill_ordered ip->param = contour_params[ip->point_idx]; // and measure distance to the previous and next intersection point. const double contour_length = contour_params.back(); - for (ContourIntersectionPoint *ip : contour_intersection_points) + for (ContourIntersectionPoint *ip : contour_intersection_points) if (ip->next_on_contour == ip) { assert(ip->prev_on_contour == ip); ip->contour_not_taken_length_prev = ip->contour_not_taken_length_next = contour_length; @@ -1556,6 +1559,18 @@ BoundaryInfillGraph create_boundary_infill_graph(const Polylines &infill_ordered return out; } +// The extended bounding box of the whole object that covers any rotation of every layer. +BoundingBox Fill::extended_object_bounding_box() const +{ + BoundingBox out = bounding_box; + out.merge(Point(out.min.y(), out.min.x())); + out.merge(Point(out.max.y(), out.max.x())); + + // The bounding box is scaled by sqrt(2.) to ensure that the bounding box + // covers any possible rotations. + return out.scaled(sqrt(2.)); +} + void Fill::connect_infill(Polylines &&infill_ordered, const std::vector &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms) { assert(! infill_ordered.empty()); @@ -1927,14 +1942,14 @@ static inline void base_support_extend_infill_lines(Polylines &infill, BoundaryI // The contour is supposed to enter the "forbidden" zone outside of the (left, right) band at tbegin and also at tend. static inline void emit_loops_in_band( // Vertical band, which will trim the contour between tbegin and tend. - coord_t left, + coord_t left, coord_t right, // Contour and its parametrization. const Points &contour, const std::vector &contour_params, // Span of the parameters of an arch to trim with the vertical band. double tbegin, - double tend, + double tend, // Minimum arch length to put into polylines_out. Shorter arches are not necessary to support a dense support infill. double min_length, Polylines &polylines_out) @@ -1990,13 +2005,13 @@ static inline void emit_loops_in_band( }; enum InOutBand { - Entering, + Entering, Leaving, }; class State { public: - State(coord_t left, coord_t right, double min_length, Polylines &polylines_out) : + State(coord_t left, coord_t right, double min_length, Polylines &polylines_out) : m_left(left), m_right(right), m_min_length(min_length), m_polylines_out(polylines_out) {} void add_inner_point(const Point* p) @@ -2297,7 +2312,7 @@ void Fill::connect_base_support(Polylines &&infill_ordered, const std::vector SCALED_EPSILON && + if (cp.contour_not_taken_length_prev > SCALED_EPSILON && (cost_prev.self_loop ? cost_prev.cost > cap_cost : cost_prev.cost > cost_veryhigh)) { @@ -2643,7 +2658,7 @@ void Fill::connect_base_support(Polylines &&infill_ordered, const std::vector SCALED_EPSILON && + if (cp.contour_not_taken_length_next > SCALED_EPSILON && (cost_next.self_loop ? cost_next.cost > cap_cost : cost_next.cost > cost_veryhigh)) { diff --git a/src/libslic3r/Fill/FillBase.hpp b/src/libslic3r/Fill/FillBase.hpp index 62eeb9000e..4348ba5127 100644 --- a/src/libslic3r/Fill/FillBase.hpp +++ b/src/libslic3r/Fill/FillBase.hpp @@ -36,6 +36,15 @@ public: InfillFailedException() : Slic3r::RuntimeError("Infill failed") {} }; +struct LockRegionParam +{ + LockRegionParam() {} + std::map skin_density_params; + std::map skeleton_density_params; + std::map skin_flow_params; + std::map skeleton_flow_params; +}; + struct FillParams { bool full_infill() const { return density > 0.9999f; } @@ -72,6 +81,7 @@ struct FillParams // For 2D lattice coordf_t lattice_angle_1 { 0.f }; coordf_t lattice_angle_2 { 0.f }; + InfillPattern pattern{ ipRectilinear }; // For 2D Honeycomb float infill_overhang_angle { 60 }; @@ -85,6 +95,13 @@ struct FillParams const PrintRegionConfig* config{ nullptr }; bool dont_sort{ false }; // do not sort the lines, just simply connect them bool can_reverse{true}; + + float horiz_move{0.0}; //move infill to get cross zag pattern + bool symmetric_infill_y_axis{false}; + coord_t symmetric_y_axis{0}; + bool locked_zag{false}; + float infill_lock_depth{0.0}; + float skin_infill_depth{0.0}; }; static_assert(IsTriviallyCopyable::value, "FillParams class is not POD (and it should be - see constructor)."); @@ -102,7 +119,7 @@ public: // in radians, ccw, 0 = East float angle; // Orca: enable angle shifting for layer change - bool rotate_angle{ true }; + float rotate_angle{ M_PI/180.0 }; // In scaled coordinates. Maximum lenght of a perimeter segment connecting two infill lines. // Used by the FillRectilinear2, FillGrid2, FillTriangles, FillStars and FillCubic. // If left to zero, the links will not be limited. @@ -135,20 +152,25 @@ public: static bool use_bridge_flow(const InfillPattern type); void set_bounding_box(const Slic3r::BoundingBox &bbox) { bounding_box = bbox; } - + BoundingBox extended_object_bounding_box() const; // Use bridge flow for the fill? virtual bool use_bridge_flow() const { return false; } // Do not sort the fill lines to optimize the print head path? virtual bool no_sort() const { return false; } + virtual bool is_self_crossing() = 0; + + // Return true if infill has a consistent pattern between layers. + virtual bool has_consistent_pattern() const { return false; } + // Perform the fill. virtual Polylines fill_surface(const Surface *surface, const FillParams ¶ms); virtual ThickPolylines fill_surface_arachne(const Surface* surface, const FillParams& params); - + virtual void set_lock_region_param(const LockRegionParam &lock_param){}; // BBS: this method is used to fill the ExtrusionEntityCollection. // It call fill_surface by default - virtual void fill_surface_extrusion(const Surface* surface, const FillParams& params, ExtrusionEntitiesPtr& out); + virtual void fill_surface_extrusion(const Surface *surface, const FillParams ¶ms, ExtrusionEntitiesPtr &out); protected: Fill() : @@ -159,7 +181,7 @@ protected: overlap(0.), // Initial angle is undefined. angle(FLT_MAX), - rotate_angle(true), + rotate_angle(M_PI/180.0), link_max_length(0), loop_clipping(0), // The initial bounding box is empty, therefore undefined. @@ -168,11 +190,11 @@ protected: // The expolygon may be modified by the method to avoid a copy. virtual void _fill_surface_single( - const FillParams & /* params */, + const FillParams & /* params */, unsigned int /* thickness_layers */, - const std::pair & /* direction */, + const std::pair & /* direction */, ExPolygon /* expolygon */, - Polylines & /* polylines_out */) {}; + Polylines & /* polylines_out */) {} // Used for concentric infill to generate ThickPolylines using Arachne. virtual void _fill_surface_single(const FillParams& params, @@ -181,7 +203,7 @@ protected: ExPolygon expolygon, ThickPolylines& thick_polylines_out) {} - virtual float _layer_angle(size_t idx) const { return (rotate_angle && (idx & 1)) ? float(M_PI/2.) : 0; } + virtual float _layer_angle(size_t idx) const { return rotate_angle; } virtual std::pair _infill_direction(const Surface *surface) const; diff --git a/src/libslic3r/Fill/FillConcentric.hpp b/src/libslic3r/Fill/FillConcentric.hpp index 476c9b8757..56040d848d 100644 --- a/src/libslic3r/Fill/FillConcentric.hpp +++ b/src/libslic3r/Fill/FillConcentric.hpp @@ -9,6 +9,7 @@ class FillConcentric : public Fill { public: ~FillConcentric() override = default; + bool is_self_crossing() override { return false; } protected: Fill* clone() const override { return new FillConcentric(*this); }; diff --git a/src/libslic3r/Fill/FillConcentricInternal.hpp b/src/libslic3r/Fill/FillConcentricInternal.hpp index e362d72b81..2c93eb5ee9 100644 --- a/src/libslic3r/Fill/FillConcentricInternal.hpp +++ b/src/libslic3r/Fill/FillConcentricInternal.hpp @@ -10,6 +10,7 @@ class FillConcentricInternal : public Fill public: ~FillConcentricInternal() override = default; void fill_surface_extrusion(const Surface *surface, const FillParams ¶ms, ExtrusionEntitiesPtr &out) override; + bool is_self_crossing() override { return false; } protected: Fill* clone() const override { return new FillConcentricInternal(*this); }; diff --git a/src/libslic3r/Fill/FillCrossHatch.hpp b/src/libslic3r/Fill/FillCrossHatch.hpp index 859d5bd8b5..34c46116eb 100644 --- a/src/libslic3r/Fill/FillCrossHatch.hpp +++ b/src/libslic3r/Fill/FillCrossHatch.hpp @@ -14,6 +14,7 @@ class FillCrossHatch : public Fill public: Fill *clone() const override { return new FillCrossHatch(*this); }; ~FillCrossHatch() override {} + bool is_self_crossing() override { return false; } protected: void _fill_surface_single( diff --git a/src/libslic3r/Fill/FillGyroid.hpp b/src/libslic3r/Fill/FillGyroid.hpp index ac66dfca98..751a7c2570 100644 --- a/src/libslic3r/Fill/FillGyroid.hpp +++ b/src/libslic3r/Fill/FillGyroid.hpp @@ -15,6 +15,7 @@ public: // require bridge flow since most of this pattern hangs in air bool use_bridge_flow() const override { return false; } + bool is_self_crossing() override { return false; } // Correction applied to regular infill angle to maximize printing // speed in default configuration (degrees) diff --git a/src/libslic3r/Fill/FillHoneycomb.hpp b/src/libslic3r/Fill/FillHoneycomb.hpp index 707e976fd9..55be423854 100644 --- a/src/libslic3r/Fill/FillHoneycomb.hpp +++ b/src/libslic3r/Fill/FillHoneycomb.hpp @@ -13,6 +13,7 @@ class FillHoneycomb : public Fill { public: ~FillHoneycomb() override {} + bool is_self_crossing() override { return false; } protected: Fill* clone() const override { return new FillHoneycomb(*this); }; diff --git a/src/libslic3r/Fill/FillLightning.hpp b/src/libslic3r/Fill/FillLightning.hpp index 6e672783a1..74aa159b0b 100644 --- a/src/libslic3r/Fill/FillLightning.hpp +++ b/src/libslic3r/Fill/FillLightning.hpp @@ -20,6 +20,7 @@ class Filler : public Slic3r::Fill { public: ~Filler() override = default; + bool is_self_crossing() override { return false; } Generator *generator { nullptr }; protected: diff --git a/src/libslic3r/Fill/FillLine.hpp b/src/libslic3r/Fill/FillLine.hpp index 9bf2b97e09..4ad9ca49a4 100644 --- a/src/libslic3r/Fill/FillLine.hpp +++ b/src/libslic3r/Fill/FillLine.hpp @@ -14,6 +14,7 @@ class FillLine : public Fill public: Fill* clone() const override { return new FillLine(*this); }; ~FillLine() override = default; + bool is_self_crossing() override { return false; } protected: void _fill_surface_single( diff --git a/src/libslic3r/Fill/FillPlanePath.hpp b/src/libslic3r/Fill/FillPlanePath.hpp index f05f3209aa..1371e8506a 100644 --- a/src/libslic3r/Fill/FillPlanePath.hpp +++ b/src/libslic3r/Fill/FillPlanePath.hpp @@ -37,6 +37,7 @@ class FillPlanePath : public Fill { public: ~FillPlanePath() override = default; + bool is_self_crossing() override { return false; } protected: void _fill_surface_single( diff --git a/src/libslic3r/Fill/FillRectilinear.cpp b/src/libslic3r/Fill/FillRectilinear.cpp index 6948cfd799..261ae3c046 100644 --- a/src/libslic3r/Fill/FillRectilinear.cpp +++ b/src/libslic3r/Fill/FillRectilinear.cpp @@ -987,7 +987,6 @@ static std::vector slice_region_by_vertical_lines(con throw; } #endif //INFILL_DEBUG_OUTPUT - return segs; } @@ -1352,8 +1351,11 @@ static SegmentIntersection& end_of_vertical_run(SegmentedIntersectionLine &il, S return const_cast(end_of_vertical_run(std::as_const(il), std::as_const(start))); } -static void traverse_graph_generate_polylines( - const ExPolygonWithOffset& poly_with_offset, const FillParams& params, const coord_t link_max_length, std::vector& segs, Polylines& polylines_out) +static void traverse_graph_generate_polylines(const ExPolygonWithOffset &poly_with_offset, + const FillParams ¶ms, + std::vector &segs, + const bool consistent_pattern, + Polylines &polylines_out) { // For each outer only chords, measure their maximum distance to the bow of the outer contour. // Mark an outer only chord as consumed, if the distance is low. @@ -1387,34 +1389,28 @@ static void traverse_graph_generate_polylines( pointLast = polylines_out.back().points.back(); for (;;) { if (i_intersection == -1) { - // The path has been interrupted. Find a next starting point, closest to the previous extruder position. - coordf_t dist2min = std::numeric_limits().max(); - for (int i_vline2 = 0; i_vline2 < int(segs.size()); ++ i_vline2) { + // The path has been interrupted. Find a next starting point. + for (int i_vline2 = 0; i_vline2 < int(segs.size()); ++i_vline2) { const SegmentedIntersectionLine &vline = segs[i_vline2]; - if (! vline.intersections.empty()) { + if (!vline.intersections.empty()) { assert(vline.intersections.size() > 1); // Even number of intersections with the loops. assert((vline.intersections.size() & 1) == 0); assert(vline.intersections.front().type == SegmentIntersection::OUTER_LOW); - for (int i = 0; i < int(vline.intersections.size()); ++ i) { - const SegmentIntersection& intrsctn = vline.intersections[i]; + + // For infill that needs to be consistent between layers (like Zig Zag), + // we are switching between forward and backward passes based on the line index. + const bool forward_pass = !consistent_pattern || (i_vline2 % 2 == 0); + for (int i = 0; i < int(vline.intersections.size()); ++i) { + const int intrsctn_idx = forward_pass ? i : int(vline.intersections.size()) - i - 1; + const SegmentIntersection &intrsctn = vline.intersections[intrsctn_idx]; if (intrsctn.is_outer()) { - assert(intrsctn.is_low() || i > 0); - bool consumed = intrsctn.is_low() ? - intrsctn.consumed_vertical_up : - vline.intersections[i - 1].consumed_vertical_up; - if (! consumed) { - coordf_t dist2 = sqr(coordf_t(pointLast(0) - vline.pos)) + sqr(coordf_t(pointLast(1) - intrsctn.pos())); - if (dist2 < dist2min) { - dist2min = dist2; - i_vline = i_vline2; - i_intersection = i; - //FIXME We are taking the first left point always. Verify, that the caller chains the paths - // by a shortest distance, while reversing the paths if needed. - //if (polylines_out.empty()) - // Initial state, take the first line, which is the first from the left. - goto found; - } + assert(intrsctn.is_low() || intrsctn_idx > 0); + const bool consumed = intrsctn.is_low() ? intrsctn.consumed_vertical_up : vline.intersections[intrsctn_idx - 1].consumed_vertical_up; + if (!consumed) { + i_vline = i_vline2; + i_intersection = intrsctn_idx; + goto found; } } } @@ -1487,9 +1483,13 @@ static void traverse_graph_generate_polylines( // 1) Find possible connection points on the previous / next vertical line. int i_prev = it->left_horizontal(); int i_next = it->right_horizontal(); - bool intersection_prev_valid = intersection_on_prev_vertical_line_valid(segs, i_vline, i_intersection); + + // To ensure pattern consistency between layers for Zig Zag infill, we always + // try to connect to the next vertical line and never to the previous vertical line. + bool intersection_prev_valid = intersection_on_prev_vertical_line_valid(segs, i_vline, i_intersection) && !consistent_pattern; bool intersection_next_valid = intersection_on_next_vertical_line_valid(segs, i_vline, i_intersection); bool intersection_horizontal_valid = intersection_prev_valid || intersection_next_valid; + // Mark both the left and right connecting segment as consumed, because one cannot go to this intersection point as it has been consumed. if (i_prev != -1) segs[i_vline - 1].intersections[i_prev].consumed_perimeter_right = true; @@ -2737,6 +2737,17 @@ static void polylines_from_paths(const std::vector &path, c } } +// The extended bounding box of the whole object that covers any rotation of every layer. +BoundingBox FillRectilinear::extended_object_bounding_box() const { + BoundingBox out = this->bounding_box; + out.merge(Point(out.min.y(), out.min.x())); + out.merge(Point(out.max.y(), out.max.x())); + + // The bounding box is scaled by sqrt(2.) to ensure that the bounding box + // covers any possible rotations. + return out.scaled(sqrt(2.)); +} + bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillParams ¶ms, float angleBase, float pattern_shift, Polylines &polylines_out) { // At the end, only the new polylines will be rotated back. @@ -2749,6 +2760,8 @@ bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillPa // Rotate polygons so that we can work with vertical lines here std::pair rotate_vector = this->_infill_direction(surface); + if (params.locked_zag) + rotate_vector.first += float(M_PI/2.); rotate_vector.first += angleBase; assert(params.density > 0.0001f && params.density <= 1.f); @@ -2766,11 +2779,14 @@ bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillPa return true; } - BoundingBox bounding_box = poly_with_offset.bounding_box_src(); + // For infill that needs to be consistent between layers (like Zig Zag), + // we use bounding box of whole object to match vertical lines between layers. + BoundingBox bounding_box_src = poly_with_offset.bounding_box_src(); + BoundingBox bounding_box = this->has_consistent_pattern() ? this->extended_object_bounding_box() : bounding_box_src; // define flow spacing according to requested density if (params.full_infill() && !params.dont_adjust) { - line_spacing = this->_adjust_solid_spacing(bounding_box.size()(0), line_spacing); + line_spacing = this->_adjust_solid_spacing(bounding_box_src.size().x(), line_spacing); this->spacing = unscale(line_spacing); } else { // extend bounding box so that our pattern will be aligned with other layers @@ -2792,6 +2808,13 @@ bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillPa if (params.full_infill()) x0 += (line_spacing + coord_t(SCALED_EPSILON)) / 2; + int gap_line = params.horiz_move / line_spacing; + if (gap_line % 2 == 0) { + x0 += params.horiz_move - gap_line * line_spacing; + } else { + x0 += params.horiz_move - (gap_line - 1) * line_spacing; + n_vlines += 1; + } #ifdef SLIC3R_DEBUG static int iRun = 0; BoundingBox bbox_svg = poly_with_offset.bounding_box_outer(); @@ -2803,7 +2826,6 @@ bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillPa } iRun ++; #endif /* SLIC3R_DEBUG */ - std::vector segs = slice_region_by_vertical_lines(poly_with_offset, n_vlines, x0, line_spacing); // Connect by horizontal / vertical links, classify the links based on link_max_length as too long. connect_segment_intersections_by_contours(poly_with_offset, segs, params, link_max_length); @@ -2848,8 +2870,9 @@ bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillPa std::vector path = chain_monotonic_regions(regions, poly_with_offset, segs, rng); polylines_from_paths(path, poly_with_offset, segs, polylines_out); } - } else - traverse_graph_generate_polylines(poly_with_offset, params, this->link_max_length, segs, polylines_out); + } else { + traverse_graph_generate_polylines(poly_with_offset, params, segs, this->has_consistent_pattern(), polylines_out); + } #ifdef SLIC3R_DEBUG { @@ -2876,6 +2899,11 @@ bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillPa //FIXME rather simplify the paths to avoid very short edges? //assert(! it->has_duplicate_points()); it->remove_duplicate_points(); + + //get origin direction infill + if (params.symmetric_infill_y_axis) { + it->symmetric_y(params.symmetric_y_axis); + } } #ifdef SLIC3R_DEBUG @@ -2884,6 +2912,8 @@ bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillPa assert(! polyline.has_duplicate_points()); #endif /* SLIC3R_DEBUG */ + + return true; } @@ -2963,7 +2993,8 @@ bool FillRectilinear::fill_surface_by_multilines(const Surface *surface, FillPar Polylines FillRectilinear::fill_surface(const Surface *surface, const FillParams ¶ms) { Polylines polylines_out; - if (params.full_infill()) { + // Orca Todo: fow now don't use fill_surface_by_multilines for zipzag infill + if (params.full_infill() || params.pattern == ipCrossZag || params.pattern == ipZigZag || params.pattern == ipLockedZag) { if (!fill_surface_by_lines(surface, params, 0.f, 0.f, polylines_out)) BOOST_LOG_TRIVIAL(error) << "FillRectilinear::fill_surface() fill_surface_by_lines() failed to fill a region."; } else { @@ -3409,5 +3440,119 @@ void FillMonotonicLineWGapFill::fill_surface_by_lines(const Surface* surface, co } }*/ +void FillLockedZag::fill_surface_locked_zag (const Surface * surface, + const FillParams & params, + std::vector> &multi_width_polyline) +{ + // merge different part exps + // diff skin flow + Polylines skin_lines; + Polylines skeloton_lines; + double offset_threshold = params.skin_infill_depth; + double overlap_threshold = params.infill_lock_depth; + Surface cross_surface = *surface; + Surface zig_surface = *surface; + // inner exps + // inner union exps + ExPolygons zig_expas = offset_ex({surface->expolygon}, -offset_threshold); + ExPolygons cross_expas = diff_ex(surface->expolygon, zig_expas); + + bool zig_get = false; + FillParams zig_params = params; + zig_params.horiz_move = 0; + // generate skeleton for diff density + auto generate_for_different_flow = [&multi_width_polyline](const std::map &flow_params, const Polylines &polylines) { + auto it = flow_params.begin(); + while (it != flow_params.end()) { + ExPolygons region_exp = union_safety_offset_ex(it->second); + + Polylines polys = intersection_pl(polylines, region_exp); + multi_width_polyline.emplace_back(polys, it->first); + it++; + } + }; + + auto it = this->lock_param.skeleton_density_params.begin(); + while (it != this->lock_param.skeleton_density_params.end()) { + ExPolygons region_exp = union_safety_offset_ex(it->second); + ExPolygons exps = intersection_ex(region_exp, zig_expas); + zig_params.density = it->first; + exps = intersection_ex(offset_ex(exps, overlap_threshold), surface->expolygon); + for (ExPolygon &exp : exps) { + zig_surface.expolygon = exp; + + Polylines zig_polylines_out = this->fill_surface(&zig_surface, zig_params); + skeloton_lines.insert(skeloton_lines.end(), zig_polylines_out.begin(), zig_polylines_out.end()); + } + it++; + } + + // set skeleton flow + generate_for_different_flow(this->lock_param.skeleton_flow_params, skeloton_lines); + + // skin exps + bool cross_get = false; + FillParams cross_params = params; + cross_params.locked_zag = false; + auto skin_density = this->lock_param.skin_density_params.begin(); + while (skin_density != this->lock_param.skin_density_params.end()) { + ExPolygons region_exp = union_safety_offset_ex(skin_density->second); + ExPolygons exps = intersection_ex(region_exp, cross_expas); + cross_params.density = skin_density->first; + for (ExPolygon &exp : exps) { + cross_surface.expolygon = exp; + Polylines cross_polylines_out = this->fill_surface(&cross_surface, cross_params); + skin_lines.insert(skin_lines.end(), cross_polylines_out.begin(), cross_polylines_out.end()); + } + skin_density++; + } + + generate_for_different_flow(this->lock_param.skin_flow_params, skin_lines); +} + +void FillLockedZag::fill_surface_extrusion(const Surface *surface, const FillParams ¶ms, ExtrusionEntitiesPtr &out) +{ + Polylines polylines; + ThickPolylines thick_polylines; + std::vector> multi_width_polyline; + try { + this->fill_surface_locked_zag(surface, params, multi_width_polyline); + } + catch (InfillFailedException&) {} + + if (!thick_polylines.empty() || !multi_width_polyline.empty()) { + // Save into layer. + ExtrusionEntityCollection* eec = nullptr; + out.push_back(eec = new ExtrusionEntityCollection()); + // Only concentric fills are not sorted. + eec->no_sort = this->no_sort(); + size_t idx = eec->entities.size(); + { + for (std::pair &poly_with_flow: multi_width_polyline) { + // calculate actual flow from spacing (which might have been adjusted by the infill + // pattern generator) + double flow_mm3_per_mm = poly_with_flow.second.mm3_per_mm(); + double flow_width = poly_with_flow.second.width(); + if (params.using_internal_flow) { + // if we used the internal flow we're not doing a solid infill + // so we can safely ignore the slight variation that might have + // been applied to f->spacing + } else { + Flow new_flow = poly_with_flow.second.with_spacing(this->spacing); + flow_mm3_per_mm = new_flow.mm3_per_mm(); + flow_width = new_flow.width(); + } + extrusion_entities_append_paths( + eec->entities, std::move(poly_with_flow.first), + params.extrusion_role, + flow_mm3_per_mm, float(flow_width), poly_with_flow.second.height()); + } + } + if (!params.can_reverse) { + for (size_t i = idx; i < eec->entities.size(); i++) + eec->entities[i]->set_reverse(); + } + } +} } // namespace Slic3r diff --git a/src/libslic3r/Fill/FillRectilinear.hpp b/src/libslic3r/Fill/FillRectilinear.hpp index 2dbcad3e90..28b20ef1f2 100644 --- a/src/libslic3r/Fill/FillRectilinear.hpp +++ b/src/libslic3r/Fill/FillRectilinear.hpp @@ -16,6 +16,7 @@ public: Fill* clone() const override { return new FillRectilinear(*this); } ~FillRectilinear() override = default; Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override; + bool is_self_crossing() override { return false; } protected: // Fill by single directional lines, interconnect the lines along perimeters. @@ -28,6 +29,9 @@ protected: float pattern_shift; }; bool fill_surface_by_multilines(const Surface *surface, FillParams params, const std::initializer_list &sweep_params, Polylines &polylines_out); + + // The extended bounding box of the whole object that covers any rotation of every layer. + BoundingBox extended_object_bounding_box() const; }; class FillAlignedRectilinear : public FillRectilinear @@ -65,6 +69,7 @@ public: Fill* clone() const override { return new FillGrid(*this); } ~FillGrid() override = default; Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override; + bool is_self_crossing() override { return true; } protected: // The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill. @@ -89,6 +94,7 @@ public: Fill* clone() const override { return new FillTriangles(*this); } ~FillTriangles() override = default; Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override; + bool is_self_crossing() override { return true; } protected: // The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill. @@ -101,6 +107,7 @@ public: Fill* clone() const override { return new FillStars(*this); } ~FillStars() override = default; Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override; + bool is_self_crossing() override { return true; } protected: // The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill. @@ -113,6 +120,7 @@ public: Fill* clone() const override { return new FillCubic(*this); } ~FillCubic() override = default; Polylines fill_surface(const Surface *surface, const FillParams ¶ms) override; + bool is_self_crossing() override { return true; } protected: // The grid fill will keep the angle constant between the layers, see the implementation of Slic3r::Fill. @@ -170,6 +178,7 @@ public: public: ~FillMonotonicLineWGapFill() override = default; void fill_surface_extrusion(const Surface *surface, const FillParams ¶ms, ExtrusionEntitiesPtr &out) override; + bool is_self_crossing() override { return false; } protected: Fill* clone() const override { return new FillMonotonicLineWGapFill(*this); }; @@ -179,9 +188,43 @@ private: void fill_surface_by_lines(const Surface* surface, const FillParams& params, Polylines& polylines_out); };*/ -Points sample_grid_pattern(const ExPolygon& expolygon, coord_t spacing, const BoundingBox& global_bounding_box); -Points sample_grid_pattern(const ExPolygons& expolygons, coord_t spacing, const BoundingBox& global_bounding_box); -Points sample_grid_pattern(const Polygons& polygons, coord_t spacing, const BoundingBox& global_bounding_box); +class FillZigZag : public FillRectilinear +{ +public: + Fill* clone() const override { return new FillZigZag(*this); } + ~FillZigZag() override = default; + + bool has_consistent_pattern() const override { return true; } +}; + +class FillCrossZag : public FillRectilinear +{ +public: + Fill *clone() const override { return new FillCrossZag(*this); } + ~FillCrossZag() override = default; + + bool has_consistent_pattern() const override { return true; } +}; + +class FillLockedZag : public FillRectilinear +{ +public: + Fill *clone() const override { return new FillLockedZag(*this); } + ~FillLockedZag() override = default; + LockRegionParam lock_param; + + void fill_surface_extrusion(const Surface *surface, const FillParams ¶ms, ExtrusionEntitiesPtr &out) override; + + bool has_consistent_pattern() const override { return true; } + void set_lock_region_param(const LockRegionParam &lock_param) override { this->lock_param = lock_param;}; + void fill_surface_locked_zag(const Surface * surface, + const FillParams & params, + std::vector> &multi_width_polyline); +}; + +Points sample_grid_pattern(const ExPolygon &expolygon, coord_t spacing, const BoundingBox &global_bounding_box); +Points sample_grid_pattern(const ExPolygons &expolygons, coord_t spacing, const BoundingBox &global_bounding_box); +Points sample_grid_pattern(const Polygons &polygons, coord_t spacing, const BoundingBox &global_bounding_box); } // namespace Slic3r diff --git a/src/libslic3r/Fill/FillTpmsD.hpp b/src/libslic3r/Fill/FillTpmsD.hpp index 73b55e1e1c..5e8accc19c 100644 --- a/src/libslic3r/Fill/FillTpmsD.hpp +++ b/src/libslic3r/Fill/FillTpmsD.hpp @@ -25,20 +25,20 @@ public: // speed in default configuration (degrees) static constexpr float CorrectionAngle = -45.; + void _fill_surface_single(const FillParams& params, + unsigned int thickness_layers, + const std::pair& direction, + ExPolygon expolygon, + Polylines& polylines_out) override; + + bool is_self_crossing() override { return false; } + // Density adjustment to have a good %of weight. static constexpr double DensityAdjust = 2.1; // Gyroid upper resolution tolerance (mm^-2) static constexpr double PatternTolerance = 0.1; - -protected: - void _fill_surface_single( - const FillParams ¶ms, - unsigned int thickness_layers, - const std::pair &direction, - ExPolygon expolygon, - Polylines &polylines_out) override; }; } // namespace Slic3r diff --git a/src/libslic3r/Flow.hpp b/src/libslic3r/Flow.hpp index c7954158f5..79cb1b324d 100644 --- a/src/libslic3r/Flow.hpp +++ b/src/libslic3r/Flow.hpp @@ -82,6 +82,13 @@ public: bool operator==(const Flow &rhs) const { return m_width == rhs.m_width && m_height == rhs.m_height && m_nozzle_diameter == rhs.m_nozzle_diameter && m_bridge == rhs.m_bridge; } + bool operator!=(const Flow &rhs) const{ + return m_width != rhs.m_width || m_height != rhs.m_height || m_nozzle_diameter != rhs.m_nozzle_diameter || m_bridge != rhs.m_bridge; + } + + bool operator <(const Flow &rhs) const { + return this->mm3_per_mm() < rhs.mm3_per_mm(); + } Flow with_width (float width) const { assert(! m_bridge); return Flow(width, m_height, rounded_rectangle_extrusion_spacing(width, m_height), m_nozzle_diameter, m_bridge); diff --git a/src/libslic3r/MultiPoint.cpp b/src/libslic3r/MultiPoint.cpp index f027e93a07..fbf898234e 100644 --- a/src/libslic3r/MultiPoint.cpp +++ b/src/libslic3r/MultiPoint.cpp @@ -507,4 +507,11 @@ BoundingBox get_extents_rotated(const MultiPoint &mp, double angle) return get_extents_rotated(mp.points, angle); } +void MultiPoint::symmetric_y(const coord_t &x_axis) +{ + for (Point &pt : points) { + pt(0) = 2 * x_axis - pt(0); + } +} + } diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp index db51a6a87a..0dfb98f4b4 100644 --- a/src/libslic3r/MultiPoint.hpp +++ b/src/libslic3r/MultiPoint.hpp @@ -94,7 +94,7 @@ public: bool intersection(const Line& line, Point* intersection) const; bool first_intersection(const Line& line, Point* intersection) const; bool intersections(const Line &line, Points *intersections) const; - + void symmetric_y(const coord_t &y_axis); static Points _douglas_peucker(const Points &points, const double tolerance); static Points visivalingam(const Points& pts, const double tolerance); static Points concave_hull_2d(const Points& pts, const double tolerence); diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index b640815ec7..3812601bb5 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -786,7 +786,7 @@ static std::vector s_Preset_print_options { "top_shell_layers", "top_shell_thickness", "bottom_shell_layers", "bottom_shell_thickness", "extra_perimeters_on_overhangs", "ensure_vertical_shell_thickness", "reduce_crossing_wall", "detect_thin_wall", "detect_overhang_wall", "overhang_reverse", "overhang_reverse_threshold","overhang_reverse_internal_only", "wall_direction", "seam_position", "staggered_inner_seams", "wall_sequence", "is_infill_first", "sparse_infill_density", "sparse_infill_pattern", "lattice_angle_1", "lattice_angle_2", "infill_overhang_angle", "top_surface_pattern", "bottom_surface_pattern", - "infill_direction", "solid_infill_direction", "rotate_solid_infill_direction", "counterbore_hole_bridging", + "infill_direction", "solid_infill_direction", "counterbore_hole_bridging","infill_shift_step", "sparse_infill_rotate_template", "solid_infill_rotate_template", "symmetric_infill_y_axis","skeleton_infill_density", "infill_lock_depth", "skin_infill_depth", "skin_infill_density", "minimum_sparse_infill_area", "reduce_infill_retraction","internal_solid_infill_pattern","gap_fill_target", "ironing_type", "ironing_pattern", "ironing_flow", "ironing_speed", "ironing_spacing", "ironing_angle", "ironing_inset", "support_ironing", "support_ironing_pattern", "support_ironing_flow", "support_ironing_spacing", @@ -806,8 +806,9 @@ static std::vector s_Preset_print_options { "support_top_z_distance", "support_on_build_plate_only","support_critical_regions_only", "bridge_no_support", "thick_bridges", "thick_internal_bridges","dont_filter_internal_bridges","enable_extra_bridge_layer", "max_bridge_length", "print_sequence", "print_order", "support_remove_small_overhang", "filename_format", "wall_filament", "support_bottom_z_distance", "sparse_infill_filament", "solid_infill_filament", "support_filament", "support_interface_filament","support_interface_not_for_body", - "ooze_prevention", "standby_temperature_delta", "preheat_time","preheat_steps", "interface_shells", "line_width", "initial_layer_line_width", - "inner_wall_line_width", "outer_wall_line_width", "sparse_infill_line_width", "internal_solid_infill_line_width", + "ooze_prevention", "standby_temperature_delta", "preheat_time","preheat_steps", "interface_shells", "line_width", "initial_layer_line_width", "inner_wall_line_width", + "outer_wall_line_width", "sparse_infill_line_width", "internal_solid_infill_line_width", + "skin_infill_line_width","skeleton_infill_line_width", "top_surface_line_width", "support_line_width", "infill_wall_overlap","top_bottom_infill_wall_overlap", "bridge_flow", "internal_bridge_flow", "elefant_foot_compensation", "elefant_foot_compensation_layers", "xy_contour_compensation", "xy_hole_compensation", "resolution", "enable_prime_tower", "prime_tower_width", "prime_tower_brim_width", "prime_volume", diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 1bb7834927..ae2b420049 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1387,7 +1387,7 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* if (!validate_extrusion_width(object->config(), "support_line_width", layer_height, err_msg)) return {err_msg, object, "support_line_width"}; } - for (const char *opt_key : { "inner_wall_line_width", "outer_wall_line_width", "sparse_infill_line_width", "internal_solid_infill_line_width", "top_surface_line_width" }) + for (const char *opt_key : { "inner_wall_line_width", "outer_wall_line_width", "sparse_infill_line_width", "internal_solid_infill_line_width", "top_surface_line_width","skin_infill_line_width" ,"skeleton_infill_line_width"}) for (const PrintRegion ®ion : object->all_regions()) if (!validate_extrusion_width(region.config(), opt_key, layer_height, err_msg)) return {err_msg, object, opt_key}; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index f0b82cbe36..d3eb2174e0 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -161,6 +161,9 @@ static t_config_enum_values s_keys_map_InfillPattern { { "lightning", ipLightning }, { "crosshatch", ipCrossHatch}, { "quartercubic", ipQuarterCubic}, + { "zigzag", ipZigZag }, + { "crosszag", ipCrossZag }, + { "lockedzag", ipLockedZag } }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(InfillPattern) @@ -2348,13 +2351,6 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(45)); - def = this->add("rotate_solid_infill_direction", coBool); - def->label = L("Rotate solid infill direction"); - def->category = L("Strength"); - def->tooltip = L("Rotate the solid infill direction by 90° for each layer."); - def->mode = comAdvanced; - def->set_default_value(new ConfigOptionBool(true)); - def = this->add("sparse_infill_density", coPercent); def->label = L("Sparse infill density"); def->category = L("Strength"); @@ -2392,6 +2388,9 @@ void PrintConfigDef::init_fff_params() def->enum_values.push_back("lightning"); def->enum_values.push_back("crosshatch"); def->enum_values.push_back("quartercubic"); + def->enum_values.push_back("zigzag"); + def->enum_values.push_back("crosszag"); + def->enum_values.push_back("lockedzag"); def->enum_labels.push_back(L("Concentric")); def->enum_labels.push_back(L("Rectilinear")); def->enum_labels.push_back(L("Grid")); @@ -2414,6 +2413,9 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back(L("Lightning")); def->enum_labels.push_back(L("Cross Hatch")); def->enum_labels.push_back(L("Quarter Cubic")); + def->enum_labels.push_back(L("Zig Zag")); + def->enum_labels.push_back(L("Cross Zag")); + def->enum_labels.push_back(L("Locked Zag")); def->set_default_value(new ConfigOptionEnum(ipCrossHatch)); def = this->add("lattice_angle_1", coFloat); @@ -3100,6 +3102,113 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(false)); + def = this->add("infill_shift_step", coFloat); + def->label = L("Infill shift step"); + def->category = L("Strength"); + def->tooltip = L("This parameter adds a slight displacement to each layer of infill to create a cross texture."); + def->sidetext = L("mm"); + def->min = 0; + def->max = 10; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.4)); + + //Orca + def = this->add("sparse_infill_rotate_template", coString); + def->label = L("Sparse infill rotatation template"); + def->category = L("Strength"); + def->tooltip = L("This parameter adds a rotation of sparse infill direction to each layer according to the specified template. " + "The template is a comma-separated list of angles in degrees, e.g. '0,90'. " + "The first angle is applied to the first layer, the second angle to the second layer, and so on. " + "If there are more layers than angles, the angles will be repeated. Note that not all all sparse infill patterns support rotation."); + def->sidetext = L("°"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionString("0,90")); + + //Orca + def = this->add("solid_infill_rotate_template", coString); + def->label = L("Solid infill rotatation template"); + def->category = L("Strength"); + def->tooltip = L("This parameter adds a rotation of solid infill direction to each layer according to the specified template. " + "The template is a comma-separated list of angles in degrees, e.g. '0,90'. " + "The first angle is applied to the first layer, the second angle to the second layer, and so on. " + "If there are more layers than angles, the angles will be repeated. Note that not all all solid infill patterns support rotation."); + def->sidetext = L("°"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionString("0,90")); + + + def = this->add("skeleton_infill_density", coPercent); + def->label = L("Skeleton infill density"); + def->category = L("Strength"); + def->tooltip = L("The remaining part of the model contour after removing a certain depth from the surface is called the skeleton. This parameter is used to adjust the density of this section." + "When two regions have the same sparse infill settings but different skeleton densities, their skeleton areas will develop overlapping sections." + "default is as same as infill density."); + def->sidetext = "%"; + def->min = 0; + def->max = 100; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionPercent(25)); + + def = this->add("skin_infill_density", coPercent); + def->label = L("Skin infill density"); + def->category = L("Strength"); + def->tooltip = L("The portion of the model's outer surface within a certain depth range is called the skin. This parameter is used to adjust the density of this section." + "When two regions have the same sparse infill settings but different skin densities, This area will not be split into two separate regions." + "default is as same as infill density."); + def->sidetext = "%"; + def->min = 0; + def->max = 100; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionPercent(25)); + + def = this->add("skin_infill_depth", coFloat); + def->label = L("Skin infill depth"); + def->category = L("Strength"); + def->tooltip = L("The parameter sets the depth of skin."); + def->sidetext = L("mm"); + def->min = 0; + def->max = 100; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(2.0)); + + def = this->add("infill_lock_depth", coFloat); + def->label = L("Infill lock depth"); + def->category = L("Strength"); + def->tooltip = L("The parameter sets the overlapping depth between the interior and skin."); + def->sidetext = L("mm"); + def->min = 0; + def->max = 100; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(1.0)); + + def = this->add("skin_infill_line_width", coFloatOrPercent); + def->label = L("Skin line width"); + def->category = L("Strength"); + def->tooltip = L("Adjust the line width of the selected skin paths."); + def->sidetext = L("mm"); + def->ratio_over = "nozzle_diameter"; + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloatOrPercent(0.4, false)); + + def = this->add("skeleton_infill_line_width", coFloatOrPercent); + def->label = L("Skeleton line width"); + def->category = L("Strength"); + def->tooltip = L("Adjust the line width of the selected skeleton paths."); + def->sidetext = L("mm"); + def->ratio_over = "nozzle_diameter"; + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloatOrPercent(0.4, false)); + + def = this->add("symmetric_infill_y_axis", coBool); + def->label = L("Symmetric infill y axis"); + def->category = L("Strength"); + def->tooltip = L("If the model has two parts that are symmetric about the y-axis," + " and you want these parts to have symmetric textures, please click this option on one of the parts."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + // Orca: max layer height for combined infill def = this->add("infill_combination_max_layer_height", coFloatOrPercent); def->label = L("Infill combination - Max layer height"); @@ -6657,33 +6766,31 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va else if (value == "0"){ value = "ensure_moderate"; } - } - else if (opt_key == "sparse_infill_anchor") { + } else if (opt_key == "rotate_solid_infill_direction") { + opt_key = "solid_infill_rotate_template"; + if (value == "1") { + value = "0,90"; + } else if (value == "0") { + value = "0"; + } + } else if (opt_key == "sparse_infill_anchor") { opt_key = "infill_anchor"; - } - else if (opt_key == "sparse_infill_anchor_max") { + } else if (opt_key == "sparse_infill_anchor_max") { opt_key = "infill_anchor_max"; - } - else if (opt_key == "chamber_temperatures") { + } else if (opt_key == "chamber_temperatures") { opt_key = "chamber_temperature"; - } - else if (opt_key == "thumbnail_size") { + } else if (opt_key == "thumbnail_size") { opt_key = "thumbnails"; - } - else if (opt_key == "top_one_wall_type" && value != "none") { + } else if (opt_key == "top_one_wall_type" && value != "none") { opt_key = "only_one_wall_top"; value = "1"; - } - else if (opt_key == "initial_layer_flow_ratio") { + } else if (opt_key == "initial_layer_flow_ratio") { opt_key = "bottom_solid_infill_flow_ratio"; - } - else if(opt_key == "ironing_direction") { + } else if (opt_key == "ironing_direction") { opt_key = "ironing_angle"; - } - else if(opt_key == "counterbole_hole_bridging") { + } else if (opt_key == "counterbole_hole_bridging") { opt_key = "counterbore_hole_bridging"; - } - else if (opt_key == "draft_shield" && value == "limited") { + } else if (opt_key == "draft_shield" && value == "limited") { value = "disabled"; } @@ -7304,7 +7411,9 @@ std::map validate(const FullPrintConfig &cfg, bool und "internal_solid_infill_line_width", "top_surface_line_width", "support_line_width", - "initial_layer_line_width" }; + "initial_layer_line_width", + "skin_infill_line_width", + "skeleton_infill_line_width"}; for (size_t i = 0; i < sizeof(widths) / sizeof(widths[i]); ++ i) { std::string key(widths[i]); if (cfg.get_abs_value(key, max_nozzle_diameter) > 2.5 * max_nozzle_diameter) { diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index ffd97c0ac9..b4ce02309b 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -60,7 +60,7 @@ enum AuthorizationType { enum InfillPattern : int { ipConcentric, ipRectilinear, ipGrid, ip2DLattice, ipLine, ipCubic, ipTriangles, ipStars, ipGyroid, ipTpmsD, ipHoneycomb, ipAdaptiveCubic, ipMonotonic, ipMonotonicLine, ipAlignedRectilinear, ip2DHoneycomb, ip3DHoneycomb, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipSupportCubic, ipSupportBase, ipConcentricInternal, - ipLightning, ipCrossHatch, ipQuarterCubic, + ipLightning, ipCrossHatch, ipQuarterCubic, ipZigZag, ipCrossZag, ipLockedZag, ipCount, }; @@ -946,7 +946,10 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, outer_wall_speed)) ((ConfigOptionFloat, infill_direction)) ((ConfigOptionFloat, solid_infill_direction)) - ((ConfigOptionBool, rotate_solid_infill_direction)) + ((ConfigOptionString, solid_infill_rotate_template)) + ((ConfigOptionBool, symmetric_infill_y_axis)) + ((ConfigOptionFloat, infill_shift_step)) + ((ConfigOptionString, sparse_infill_rotate_template)) ((ConfigOptionPercent, sparse_infill_density)) ((ConfigOptionEnum, sparse_infill_pattern)) ((ConfigOptionFloat, lattice_angle_1)) @@ -966,7 +969,12 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionPercent, infill_wall_overlap)) ((ConfigOptionPercent, top_bottom_infill_wall_overlap)) ((ConfigOptionFloat, sparse_infill_speed)) - //BBS + ((ConfigOptionPercent, skeleton_infill_density)) + ((ConfigOptionPercent, skin_infill_density)) + ((ConfigOptionFloat, infill_lock_depth)) + ((ConfigOptionFloat, skin_infill_depth)) + ((ConfigOptionFloatOrPercent, skin_infill_line_width)) + ((ConfigOptionFloatOrPercent, skeleton_infill_line_width)) ((ConfigOptionBool, infill_combination)) // Orca: ((ConfigOptionFloatOrPercent, infill_combination_max_layer_height)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 5db480347e..8b121f0bd9 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1073,9 +1073,10 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "sparse_infill_filament" || opt_key == "solid_infill_filament" || opt_key == "sparse_infill_line_width" + || opt_key == "skin_infill_line_width" + || opt_key == "skeleton_infill_line_width" || opt_key == "infill_direction" || opt_key == "solid_infill_direction" - || opt_key == "rotate_solid_infill_direction" || opt_key == "ensure_vertical_shell_thickness" || opt_key == "bridge_angle" || opt_key == "internal_bridge_angle" // ORCA: Internal bridge angle override @@ -1097,7 +1098,15 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "lattice_angle_2" || opt_key == "infill_overhang_angle") { steps.emplace_back(posInfill); - } else if (opt_key == "sparse_infill_pattern") { + } else if (opt_key == "sparse_infill_pattern" + || opt_key == "symmetric_infill_y_axis" + || opt_key == "infill_shift_step" + || opt_key == "sparse_infill_rotate_template" + || opt_key == "solid_infill_rotate_template" + || opt_key == "skeleton_infill_density" + || opt_key == "skin_infill_density" + || opt_key == "infill_lock_depth" + || opt_key == "skin_infill_depth") { steps.emplace_back(posPrepareInfill); } else if (opt_key == "sparse_infill_density") { // One likely wants to reslice only when switching between zero infill to simulate boolean difference (subtracting volumes), diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index dc73eb9454..134538137d 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -472,6 +472,20 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con apply(config, &new_conf); is_msg_dlg_already_exist = false; } + + // layer_height shouldn't be equal to zero + float skin_depth = config->opt_float("skin_infill_depth"); + if (config->opt_float("infill_lock_depth") > skin_depth) { + const wxString msg_text = _(L("lock depth should smaller than skin depth.\nReset to 50% of skin depth")); + MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxOK); + DynamicPrintConfig new_conf = *config; + is_msg_dlg_already_exist = true; + dialog.ShowModal(); + new_conf.set_key_value("infill_lock_depth", new ConfigOptionFloat(skin_depth / 2)); + apply(config, &new_conf); + is_msg_dlg_already_exist = false; + } + } void ConfigManipulation::apply_null_fff_config(DynamicPrintConfig *config, std::vector const &keys, std::map const &configs) @@ -526,7 +540,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co bool have_infill = config->option("sparse_infill_density")->value > 0; // sparse_infill_filament uses the same logic as in Print::extruders() for (auto el : { "sparse_infill_pattern", "infill_combination", - "minimum_sparse_infill_area", "sparse_infill_filament", "infill_anchor_max"}) + "minimum_sparse_infill_area", "sparse_infill_filament", "infill_anchor_max","infill_shift_step","sparse_infill_rotate_template","symmetric_infill_y_axis"}) toggle_line(el, have_infill); bool have_combined_infill = config->opt_bool("infill_combination") && have_infill; @@ -539,6 +553,19 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co bool has_infill_anchors = have_infill && config->option("infill_anchor_max")->value > 0 && infill_anchor; toggle_field("infill_anchor", has_infill_anchors); + //cross zag + bool is_cross_zag = config->option>("sparse_infill_pattern")->value == InfillPattern::ipCrossZag; + bool is_locked_zig = config->option>("sparse_infill_pattern")->value == InfillPattern::ipLockedZag; + + toggle_line("infill_shift_step", is_cross_zag || is_locked_zig); + + for (auto el : { "skeleton_infill_density", "skin_infill_density", "infill_lock_depth", "skin_infill_depth","skin_infill_line_width", "skeleton_infill_line_width" }) + toggle_line(el, is_locked_zig); + + bool is_zig_zag = config->option>("sparse_infill_pattern")->value == InfillPattern::ipZigZag; + + toggle_line("symmetric_infill_y_axis", is_zig_zag || is_cross_zag || is_locked_zig); + bool has_spiral_vase = config->opt_bool("spiral_mode"); toggle_line("spiral_mode_smooth", has_spiral_vase); toggle_line("spiral_mode_max_xy_smoothing", has_spiral_vase && config->opt_bool("spiral_mode_smooth")); @@ -552,7 +579,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co for (auto el : { "infill_direction", "sparse_infill_line_width", "sparse_infill_speed", "bridge_speed", "internal_bridge_speed", "bridge_angle", "internal_bridge_angle", - "solid_infill_direction", "rotate_solid_infill_direction", "internal_solid_infill_pattern", "solid_infill_filament", + "solid_infill_direction", "solid_infill_rotate_template", "internal_solid_infill_pattern", "solid_infill_filament", }) toggle_field(el, have_infill || has_solid_infill); @@ -823,6 +850,22 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co for (auto el : { "lattice_angle_1", "lattice_angle_2"}) toggle_line(el, lattice_options); + //Orca: hide rotate template for solid infill if not support + const auto _sparse_infill_pattern = config->option>("sparse_infill_pattern")->value; + bool show_sparse_infill_rotate_template = _sparse_infill_pattern == ipRectilinear || _sparse_infill_pattern == ipLine || + _sparse_infill_pattern == ipZigZag || _sparse_infill_pattern == ipCrossZag || + _sparse_infill_pattern == ipLockedZag; + + toggle_line("sparse_infill_rotate_template", show_sparse_infill_rotate_template); + + //Orca: hide rotate template for solid infill if not support + const auto _solid_infill_pattern = config->option>("internal_solid_infill_pattern")->value; + bool show_solid_infill_rotate_template = _solid_infill_pattern == ipRectilinear || _solid_infill_pattern == ipMonotonic || + _solid_infill_pattern == ipMonotonicLine || _solid_infill_pattern == ipAlignedRectilinear; + + toggle_line("solid_infill_rotate_template", show_solid_infill_rotate_template); + + toggle_line("infill_overhang_angle", config->opt_enum("sparse_infill_pattern") == InfillPattern::ip2DHoneycomb); } diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 7e5f229f1a..d7c9408103 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -430,6 +430,16 @@ void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true str = str_out; set_value(str, true); } + } else if (m_opt.opt_key == "sparse_infill_rotate_template" || m_opt.opt_key == "solid_infill_rotate_template") { + if (!ConfigOptionFloats::validate_string(str.utf8_string())) { + show_error(m_parent, format_wxstr(_L("This parameter expects a comma-delimited list of numbers. E.g, \"0,90\"."))); + wxString old_value(boost::any_cast(m_value)); + this->set_value(old_value, true); // Revert to previous value + } else { + // Valid string, so update m_value with the new string from the control. + m_value = into_u8(str); + } + break; } m_value = into_u8(str); diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 237c8c9604..d5d5de76a9 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -107,7 +107,7 @@ std::map> SettingsFactory::PART_CAT { L("Strength"), {{"wall_loops", "",1},{"top_shell_layers", L("Top Solid Layers"),1},{"top_shell_thickness", L("Top Minimum Shell Thickness"),1}, {"bottom_shell_layers", L("Bottom Solid Layers"),1}, {"bottom_shell_thickness", L("Bottom Minimum Shell Thickness"),1}, {"sparse_infill_density", "",1},{"sparse_infill_pattern", "",1},{"lattice_angle_1", "",1},{"lattice_angle_2", "",1},{"infill_overhang_angle", "",1},{"infill_anchor", "",1},{"infill_anchor_max", "",1},{"top_surface_pattern", "",1},{"bottom_surface_pattern", "",1}, {"internal_solid_infill_pattern", "",1}, - {"infill_combination", "",1}, {"infill_combination_max_layer_height", "",1}, {"infill_wall_overlap", "",1},{"top_bottom_infill_wall_overlap", "",1}, {"solid_infill_direction", "",1}, {"rotate_solid_infill_direction", "",1}, {"infill_direction", "",1}, {"bridge_angle", "",1}, {"internal_bridge_angle", "",1}, {"minimum_sparse_infill_area", "",1} + {"infill_combination", "",1}, {"infill_combination_max_layer_height", "",1}, {"infill_wall_overlap", "",1},{"top_bottom_infill_wall_overlap", "",1}, {"solid_infill_direction", "",1}, {"infill_direction", "",1}, {"bridge_angle", "",1}, {"internal_bridge_angle", "",1}, {"minimum_sparse_infill_area", "",1} }}, { L("Speed"), {{"outer_wall_speed", "",1},{"inner_wall_speed", "",2},{"sparse_infill_speed", "",3},{"top_surface_speed", "",4}, {"internal_solid_infill_speed", "",5}, {"enable_overhang_speed", "",6}, {"overhang_1_4_speed", "",7}, {"overhang_2_4_speed", "",8}, {"overhang_3_4_speed", "",9}, {"overhang_4_4_speed", "",10}, diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index fc7f6c7305..30e1f48d3c 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -9920,7 +9920,6 @@ void adjust_settings_for_flowrate_calib(ModelObjectPtrs& objects, bool linear, i _obj->config.set_key_value("top_solid_infill_flow_ratio", new ConfigOptionFloat(1.0f)); _obj->config.set_key_value("infill_direction", new ConfigOptionFloat(45)); _obj->config.set_key_value("solid_infill_direction", new ConfigOptionFloat(135)); - _obj->config.set_key_value("rotate_solid_infill_direction", new ConfigOptionBool(true)); _obj->config.set_key_value("ironing_type", new ConfigOptionEnum(IroningType::NoIroning)); _obj->config.set_key_value("internal_solid_infill_speed", new ConfigOptionFloat(internal_solid_speed)); _obj->config.set_key_value("top_surface_speed", new ConfigOptionFloat(top_surface_speed)); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 7b645a891f..730a3fda96 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1470,6 +1470,9 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) } } + if (opt_key == "single_extruder_multi_material" || opt_key == "extruders_count" ) + update_wiping_button_visibility(); + if (opt_key == "pellet_flow_coefficient") { @@ -1483,6 +1486,44 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) } + if (opt_key == "single_extruder_multi_material" ){ + const auto bSEMM = m_config->opt_bool("single_extruder_multi_material"); + wxGetApp().sidebar().show_SEMM_buttons(bSEMM); + wxGetApp().get_tab(Preset::TYPE_PRINT)->update(); + } + + if(opt_key == "purge_in_prime_tower") + wxGetApp().get_tab(Preset::TYPE_PRINT)->update(); + + + if (opt_key == "enable_prime_tower") { + auto timelapse_type = m_config->option>("timelapse_type"); + bool timelapse_enabled = timelapse_type->value == TimelapseType::tlSmooth; + if (!boost::any_cast(value) && timelapse_enabled) { + MessageDialog dlg(wxGetApp().plater(), _L("A prime tower is required for smooth timelapse. There may be flaws on the model without prime tower. Are you sure you want to disable prime tower?"), + _L("Warning"), wxICON_WARNING | wxYES | wxNO); + if (dlg.ShowModal() == wxID_NO) { + DynamicPrintConfig new_conf = *m_config; + new_conf.set_key_value("enable_prime_tower", new ConfigOptionBool(true)); + m_config_manipulation.apply(m_config, &new_conf); + } + wxGetApp().plater()->update(); + } + bool is_precise_z_height = m_config->option("precise_z_height")->value; + if (boost::any_cast(value) && is_precise_z_height) { + MessageDialog dlg(wxGetApp().plater(), _L("Enabling both precise Z height and the prime tower may cause the size of prime tower to increase. Do you still want to enable?"), + _L("Warning"), wxICON_WARNING | wxYES | wxNO); + if (dlg.ShowModal() == wxID_NO) { + DynamicPrintConfig new_conf = *m_config; + new_conf.set_key_value("enable_prime_tower", new ConfigOptionBool(false)); + m_config_manipulation.apply(m_config, &new_conf); + } + wxGetApp().plater()->update(); + } + update_wiping_button_visibility(); + } + + if (opt_key == "single_extruder_multi_material" ){ const auto bSEMM = m_config->opt_bool("single_extruder_multi_material"); wxGetApp().sidebar().show_SEMM_buttons(bSEMM); @@ -2165,20 +2206,30 @@ void TabPrint::build() optgroup = page->new_optgroup(L("Infill"), L"param_infill"); optgroup->append_single_option_line("sparse_infill_density", "strength_settings_infill#sparse-infill-density"); optgroup->append_single_option_line("sparse_infill_pattern", "strength_settings_infill#sparse-infill-pattern"); + optgroup->append_single_option_line("infill_direction"); + optgroup->append_single_option_line("sparse_infill_rotate_template"); + optgroup->append_single_option_line("skin_infill_density"); + optgroup->append_single_option_line("skeleton_infill_density"); + optgroup->append_single_option_line("infill_lock_depth"); + optgroup->append_single_option_line("skin_infill_depth"); + optgroup->append_single_option_line("skin_infill_line_width", "parameter/line-width"); + optgroup->append_single_option_line("skeleton_infill_line_width", "parameter/line-width"); + optgroup->append_single_option_line("symmetric_infill_y_axis"); + optgroup->append_single_option_line("infill_shift_step"); + optgroup->append_single_option_line("lattice_angle_1"); optgroup->append_single_option_line("lattice_angle_2"); optgroup->append_single_option_line("infill_overhang_angle"); optgroup->append_single_option_line("infill_anchor_max"); optgroup->append_single_option_line("infill_anchor"); optgroup->append_single_option_line("internal_solid_infill_pattern"); + optgroup->append_single_option_line("solid_infill_direction"); + optgroup->append_single_option_line("solid_infill_rotate_template"); optgroup->append_single_option_line("gap_fill_target"); optgroup->append_single_option_line("filter_out_gap_fill"); optgroup->append_single_option_line("infill_wall_overlap"); optgroup = page->new_optgroup(L("Advanced"), L"param_advanced"); - optgroup->append_single_option_line("infill_direction"); - optgroup->append_single_option_line("solid_infill_direction"); - optgroup->append_single_option_line("rotate_solid_infill_direction"); optgroup->append_single_option_line("bridge_angle"); optgroup->append_single_option_line("internal_bridge_angle"); // ORCA: Internal bridge angle override optgroup->append_single_option_line("minimum_sparse_infill_area");