diff --git a/src/libslic3r/AABBMesh.cpp b/src/libslic3r/AABBMesh.cpp index 4741632..5c034d8 100644 --- a/src/libslic3r/AABBMesh.cpp +++ b/src/libslic3r/AABBMesh.cpp @@ -1,17 +1,19 @@ #include "AABBMesh.hpp" -#include #include #include +#include +#include -#include +#include "admesh/stl.h" +#include "libslic3r/Point.hpp" #ifdef SLIC3R_HOLE_RAYCASTER #include #endif namespace Slic3r { -//YYY + class AABBMesh::AABBImpl { private: AABBTreeIndirect::Tree3f m_tree; diff --git a/src/libslic3r/AABBMesh.hpp b/src/libslic3r/AABBMesh.hpp index 86ccf10..b10968f 100644 --- a/src/libslic3r/AABBMesh.hpp +++ b/src/libslic3r/AABBMesh.hpp @@ -1,11 +1,16 @@ #ifndef QIDISLICER_AABBMESH_H #define QIDISLICER_AABBMESH_H -#include -#include - #include #include +#include +#include +#include +#include +#include +#include +#include +#include // There is an implementation of a hole-aware raycaster that was eventually // not used in production version. It is now hidden under following define diff --git a/src/libslic3r/AABBTreeLines.hpp b/src/libslic3r/AABBTreeLines.hpp index 9331481..a20eee1 100644 --- a/src/libslic3r/AABBTreeLines.hpp +++ b/src/libslic3r/AABBTreeLines.hpp @@ -119,10 +119,10 @@ inline std::tuple coordinate_aligned_ray_hit_count(size_t template inline void insert_intersections_with_line(std::vector> &result, size_t node_idx, - const TreeType &tree, - const std::vector &lines, - const LineType &line, - const typename TreeType::BoundingBox &line_bb) + const TreeType &tree, + const std::vector &lines, + const LineType &line, + const typename TreeType::BoundingBox &line_bb) { const auto &node = tree.node(node_idx); assert(node.is_valid()); @@ -132,23 +132,62 @@ inline void insert_intersections_with_line(std::vector(result, left_node_idx, tree, lines, line, line_bb); - } + } - if (node_right.bbox.intersects(line_bb)) { + if (node_right.bbox.intersects(line_bb)) { insert_intersections_with_line(result, right_node_idx, tree, lines, line, line_bb); - } + } + //// NOTE: Non recursive implementation - for my case was slower ;-( + // std::vector node_indicies_for_check; // evaluation queue + // size_t approx_size = static_cast(std::ceil(std::sqrt(tree.nodes().size()))); + // node_indicies_for_check.reserve(approx_size); + // do { + // const auto &node = tree.node(node_index); + // assert(node.is_valid()); + // if (node.is_leaf()) { + // VectorType intersection_pt; + // if (line_alg::intersection(line, lines[node.idx], &intersection_pt)) + // result.emplace_back(intersection_pt, node.idx); + // node_index = 0;// clear next node + // } else { + // size_t left_node_idx = node_index * 2 + 1; + // size_t right_node_idx = left_node_idx + 1; + // const auto &node_left = tree.node(left_node_idx); + // const auto &node_right = tree.node(right_node_idx); + // assert(node_left.is_valid()); + // assert(node_right.is_valid()); + + // // Set next node index + // node_index = 0; // clear next node + // if (node_left.bbox.intersects(line_bb)) + // node_index = left_node_idx; + + // if (node_right.bbox.intersects(line_bb)) { + // if (node_index == 0) + // node_index = right_node_idx; + // else + // node_indicies_for_check.push_back(right_node_idx); // store for later evaluation + // } + // } + + // if (node_index == 0 && !node_indicies_for_check.empty()) { + // // no direct next node take one from queue + // node_index = node_indicies_for_check.back(); + // node_indicies_for_check.pop_back(); + // } + //} while (node_index != 0); } } // namespace detail @@ -268,6 +307,7 @@ inline std::vector> get_intersections_with_line(co std::vector> intersections; // result detail::insert_intersections_with_line(intersections, 0, tree, lines, line, line_bb); + if (sorted) { using Floating = typename std::conditional::value, typename LineType::Scalar, double>::type; @@ -283,7 +323,6 @@ inline std::vector> get_intersections_with_line(co intersections[i] = points_with_sq_distance[i].second; } } - return intersections; } diff --git a/src/libslic3r/Algorithm/PathSorting.hpp b/src/libslic3r/Algorithm/PathSorting.hpp index ab44627..3a30507 100644 --- a/src/libslic3r/Algorithm/PathSorting.hpp +++ b/src/libslic3r/Algorithm/PathSorting.hpp @@ -1,9 +1,9 @@ #ifndef SRC_LIBSLIC3R_PATH_SORTING_HPP_ #define SRC_LIBSLIC3R_PATH_SORTING_HPP_ -#include "AABBTreeLines.hpp" -#include "BoundingBox.hpp" -#include "Line.hpp" +#include "libslic3r/AABBTreeLines.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/Line.hpp" #include "ankerl/unordered_dense.h" #include #include @@ -14,8 +14,39 @@ #include #include -namespace Slic3r { -namespace Algorithm { +namespace Slic3r::Algorithm { + +bool is_first_path_touching_second_path(const AABBTreeLines::LinesDistancer &first_distancer, + const AABBTreeLines::LinesDistancer &second_distancer, + const BoundingBox &second_distancer_bbox, + const double touch_distance_threshold) +{ + for (const Line &line : first_distancer.get_lines()) { + if (bbox_point_distance(second_distancer_bbox, line.a) < touch_distance_threshold && second_distancer.distance_from_lines(line.a) < touch_distance_threshold) { + return true; + } + } + + const Point first_distancer_last_pt = first_distancer.get_lines().back().b; + if (bbox_point_distance(second_distancer_bbox, first_distancer_last_pt) && second_distancer.distance_from_lines(first_distancer_last_pt) < touch_distance_threshold) { + return true; + } + + return false; +} + +bool are_paths_touching(const AABBTreeLines::LinesDistancer &first_distancer, const BoundingBox &first_distancer_bbox, + const AABBTreeLines::LinesDistancer &second_distancer, const BoundingBox &second_distancer_bbox, + const double touch_distance_threshold) +{ + if (is_first_path_touching_second_path(first_distancer, second_distancer, second_distancer_bbox, touch_distance_threshold)) { + return true; + } else if (is_first_path_touching_second_path(second_distancer, first_distancer, first_distancer_bbox, touch_distance_threshold)) { + return true; + } + + return false; +} //Sorts the paths such that all paths between begin and last_seed are printed first, in some order. The rest of the paths is sorted // such that the paths that are touching some of the already printed are printed first, sorted secondary by the distance to the last point of the last @@ -24,44 +55,34 @@ namespace Algorithm { // to the second, then they touch. // convert_to_lines is a lambda that should accept the path as argument and return it as Lines vector, in correct order. template -void sort_paths(RandomAccessIterator begin, RandomAccessIterator end, Point start, double touch_limit_distance, ToLines convert_to_lines) +void sort_paths(RandomAccessIterator begin, RandomAccessIterator end, Point start, const double touch_distance_threshold, ToLines convert_to_lines) { - size_t paths_count = std::distance(begin, end); + const size_t paths_count = std::distance(begin, end); if (paths_count <= 1) return; - auto paths_touch = [touch_limit_distance](const AABBTreeLines::LinesDistancer &left, - const AABBTreeLines::LinesDistancer &right) { - for (const Line &l : left.get_lines()) { - if (right.distance_from_lines(l.a) < touch_limit_distance) { - return true; - } - } - if (right.distance_from_lines(left.get_lines().back().b) < touch_limit_distance) { - return true; - } - - for (const Line &l : right.get_lines()) { - if (left.distance_from_lines(l.a) < touch_limit_distance) { - return true; - } - } - if (left.distance_from_lines(right.get_lines().back().b) < touch_limit_distance) { - return true; - } - return false; - }; - std::vector> distancers(paths_count); for (size_t path_idx = 0; path_idx < paths_count; path_idx++) { distancers[path_idx] = AABBTreeLines::LinesDistancer{convert_to_lines(*std::next(begin, path_idx))}; } + BoundingBoxes bboxes; + bboxes.reserve(paths_count); + for (auto tp_it = begin; tp_it != end; ++tp_it) { + bboxes.emplace_back(tp_it->bounding_box()); + } + std::vector> dependencies(paths_count); - for (size_t path_idx = 0; path_idx < paths_count; path_idx++) { - for (size_t next_path_idx = path_idx + 1; next_path_idx < paths_count; next_path_idx++) { - if (paths_touch(distancers[path_idx], distancers[next_path_idx])) { - dependencies[next_path_idx].insert(path_idx); + for (size_t curr_path_idx = 0; curr_path_idx < paths_count; ++curr_path_idx) { + for (size_t next_path_idx = curr_path_idx + 1; next_path_idx < paths_count; ++next_path_idx) { + const BoundingBox &curr_path_bbox = bboxes[curr_path_idx]; + const BoundingBox &next_path_bbox = bboxes[next_path_idx]; + + if (bbox_bbox_distance(curr_path_bbox, next_path_bbox) >= touch_distance_threshold) + continue; + + if (are_paths_touching(distancers[curr_path_idx], curr_path_bbox, distancers[next_path_idx], next_path_bbox, touch_distance_threshold)) { + dependencies[next_path_idx].insert(curr_path_idx); } } } @@ -123,6 +144,6 @@ void sort_paths(RandomAccessIterator begin, RandomAccessIterator end, Point star } } -}} // namespace Slic3r::Algorithm +} // namespace Slic3r::Algorithm -#endif /*SRC_LIBSLIC3R_PATH_SORTING_HPP_*/ \ No newline at end of file +#endif /*SRC_LIBSLIC3R_PATH_SORTING_HPP_*/ diff --git a/src/libslic3r/Algorithm/RegionExpansion.cpp b/src/libslic3r/Algorithm/RegionExpansion.cpp index ee2a5aa..7dba823 100644 --- a/src/libslic3r/Algorithm/RegionExpansion.cpp +++ b/src/libslic3r/Algorithm/RegionExpansion.cpp @@ -4,8 +4,20 @@ #include #include #include - +#include #include +#include +#include +#include +#include +#include + +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/Polyline.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { namespace Algorithm { @@ -258,14 +270,28 @@ std::vector wave_seeds( int iseed = 0; for (const ClipperLib_Z::Path &path : segments) { assert(path.size() >= 2); - const ClipperLib_Z::IntPoint &front = path.front(); - const ClipperLib_Z::IntPoint &back = path.back(); + ClipperLib_Z::IntPoint front = path.front(); + ClipperLib_Z::IntPoint back = path.back(); // Both ends of a seed segment are supposed to be inside a single boundary expolygon. // Thus as long as the seed contour is not closed, it should be open at a boundary point. assert((front == back && front.z() >= idx_boundary_end && front.z() < idx_src_end) || //(front.z() < 0 && back.z() < 0)); // Hope that at least one end of an open polyline is clipped by the boundary, thus an intersection point is created. (front.z() < 0 || back.z() < 0)); + + if (front == back && (front.z() < idx_boundary_end)) { + // This should be a very rare exception. + // See https://github.com/qidi3d/QIDISlicer/issues/12469. + // Segement is open, yet its first point seems to be part of boundary polygon. + // Take the first point with src polygon index. + for (const ClipperLib_Z::IntPoint &point : path) { + if (point.z() >= idx_boundary_end) { + front = point; + back = point; + } + } + } + const Intersection *intersection = nullptr; auto intersection_point_valid = [idx_boundary_end, idx_src_end](const Intersection &is) { return is.first >= 1 && is.first < idx_boundary_end && diff --git a/src/libslic3r/Algorithm/RegionExpansion.hpp b/src/libslic3r/Algorithm/RegionExpansion.hpp index eb99674..667c117 100644 --- a/src/libslic3r/Algorithm/RegionExpansion.hpp +++ b/src/libslic3r/Algorithm/RegionExpansion.hpp @@ -1,10 +1,14 @@ #ifndef SRC_LIBSLIC3R_ALGORITHM_REGION_EXPANSION_HPP_ #define SRC_LIBSLIC3R_ALGORITHM_REGION_EXPANSION_HPP_ -#include #include #include #include +#include +#include +#include +#include +#include namespace Slic3r { namespace Algorithm { diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 4cde5ab..f2d9e9f 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -12,7 +12,6 @@ #include #include -#include #include #include #include diff --git a/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategy.cpp b/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategy.cpp index b57c84d..5ade0c9 100644 --- a/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategy.cpp +++ b/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategy.cpp @@ -1,10 +1,8 @@ //Copyright (c) 2022 Ultimaker B.V. //CuraEngine is released under the terms of the AGPLv3 or higher. -#include - #include "BeadingStrategy.hpp" -#include "Point.hpp" +#include "libslic3r/Point.hpp" namespace Slic3r::Arachne { diff --git a/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp b/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp index 99e3823..0a08f5d 100644 --- a/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp +++ b/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp @@ -4,9 +4,13 @@ #ifndef BEADING_STRATEGY_H #define BEADING_STRATEGY_H +#include #include +#include +#include +#include -#include "../../libslic3r.h" +#include "libslic3r/libslic3r.h" namespace Slic3r::Arachne { diff --git a/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategyFactory.cpp b/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategyFactory.cpp index dd17b35..c066240 100644 --- a/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategyFactory.cpp +++ b/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategyFactory.cpp @@ -3,13 +3,16 @@ #include "BeadingStrategyFactory.hpp" +#include +#include +#include + #include "LimitedBeadingStrategy.hpp" #include "WideningBeadingStrategy.hpp" #include "DistributedBeadingStrategy.hpp" #include "RedistributeBeadingStrategy.hpp" #include "OuterWallInsetBeadingStrategy.hpp" - -#include +#include "libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp" namespace Slic3r::Arachne { diff --git a/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategyFactory.hpp b/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategyFactory.hpp index a586906..e24a85e 100644 --- a/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategyFactory.hpp +++ b/src/libslic3r/Arachne/BeadingStrategy/BeadingStrategyFactory.hpp @@ -4,8 +4,12 @@ #ifndef BEADING_STRATEGY_FACTORY_H #define BEADING_STRATEGY_FACTORY_H +#include +#include + #include "BeadingStrategy.hpp" #include "../../Point.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r::Arachne { diff --git a/src/libslic3r/Arachne/BeadingStrategy/DistributedBeadingStrategy.cpp b/src/libslic3r/Arachne/BeadingStrategy/DistributedBeadingStrategy.cpp index c8a84c4..7234015 100644 --- a/src/libslic3r/Arachne/BeadingStrategy/DistributedBeadingStrategy.cpp +++ b/src/libslic3r/Arachne/BeadingStrategy/DistributedBeadingStrategy.cpp @@ -1,7 +1,12 @@ // Copyright (c) 2022 Ultimaker B.V. // CuraEngine is released under the terms of the AGPLv3 or higher. #include +#include +#include +#include + #include "DistributedBeadingStrategy.hpp" +#include "libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp" namespace Slic3r::Arachne { diff --git a/src/libslic3r/Arachne/BeadingStrategy/DistributedBeadingStrategy.hpp b/src/libslic3r/Arachne/BeadingStrategy/DistributedBeadingStrategy.hpp index 4d65173..991d502 100644 --- a/src/libslic3r/Arachne/BeadingStrategy/DistributedBeadingStrategy.hpp +++ b/src/libslic3r/Arachne/BeadingStrategy/DistributedBeadingStrategy.hpp @@ -5,6 +5,7 @@ #define DISTRIBUTED_BEADING_STRATEGY_H #include "BeadingStrategy.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r::Arachne { diff --git a/src/libslic3r/Arachne/BeadingStrategy/LimitedBeadingStrategy.cpp b/src/libslic3r/Arachne/BeadingStrategy/LimitedBeadingStrategy.cpp index 97d854b..24e04c3 100644 --- a/src/libslic3r/Arachne/BeadingStrategy/LimitedBeadingStrategy.cpp +++ b/src/libslic3r/Arachne/BeadingStrategy/LimitedBeadingStrategy.cpp @@ -1,11 +1,14 @@ //Copyright (c) 2022 Ultimaker B.V. //CuraEngine is released under the terms of the AGPLv3 or higher. -#include #include +#include +#include +#include #include "LimitedBeadingStrategy.hpp" -#include "Point.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp" namespace Slic3r::Arachne { diff --git a/src/libslic3r/Arachne/BeadingStrategy/LimitedBeadingStrategy.hpp b/src/libslic3r/Arachne/BeadingStrategy/LimitedBeadingStrategy.hpp index 33292bc..166057f 100644 --- a/src/libslic3r/Arachne/BeadingStrategy/LimitedBeadingStrategy.hpp +++ b/src/libslic3r/Arachne/BeadingStrategy/LimitedBeadingStrategy.hpp @@ -4,7 +4,10 @@ #ifndef LIMITED_BEADING_STRATEGY_H #define LIMITED_BEADING_STRATEGY_H +#include + #include "BeadingStrategy.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r::Arachne { diff --git a/src/libslic3r/Arachne/BeadingStrategy/OuterWallInsetBeadingStrategy.cpp b/src/libslic3r/Arachne/BeadingStrategy/OuterWallInsetBeadingStrategy.cpp index 1406f7d..ac9edb1 100644 --- a/src/libslic3r/Arachne/BeadingStrategy/OuterWallInsetBeadingStrategy.cpp +++ b/src/libslic3r/Arachne/BeadingStrategy/OuterWallInsetBeadingStrategy.cpp @@ -4,6 +4,9 @@ #include "OuterWallInsetBeadingStrategy.hpp" #include +#include + +#include "libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp" namespace Slic3r::Arachne { diff --git a/src/libslic3r/Arachne/BeadingStrategy/OuterWallInsetBeadingStrategy.hpp b/src/libslic3r/Arachne/BeadingStrategy/OuterWallInsetBeadingStrategy.hpp index 45a700b..f0b4622 100644 --- a/src/libslic3r/Arachne/BeadingStrategy/OuterWallInsetBeadingStrategy.hpp +++ b/src/libslic3r/Arachne/BeadingStrategy/OuterWallInsetBeadingStrategy.hpp @@ -4,7 +4,10 @@ #ifndef OUTER_WALL_INSET_BEADING_STRATEGY_H #define OUTER_WALL_INSET_BEADING_STRATEGY_H +#include + #include "BeadingStrategy.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r::Arachne { diff --git a/src/libslic3r/Arachne/BeadingStrategy/RedistributeBeadingStrategy.cpp b/src/libslic3r/Arachne/BeadingStrategy/RedistributeBeadingStrategy.cpp index 2b4dda0..7307781 100644 --- a/src/libslic3r/Arachne/BeadingStrategy/RedistributeBeadingStrategy.cpp +++ b/src/libslic3r/Arachne/BeadingStrategy/RedistributeBeadingStrategy.cpp @@ -5,6 +5,9 @@ #include #include +#include + +#include "libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp" namespace Slic3r::Arachne { diff --git a/src/libslic3r/Arachne/BeadingStrategy/RedistributeBeadingStrategy.hpp b/src/libslic3r/Arachne/BeadingStrategy/RedistributeBeadingStrategy.hpp index f0fefe2..701aa63 100644 --- a/src/libslic3r/Arachne/BeadingStrategy/RedistributeBeadingStrategy.hpp +++ b/src/libslic3r/Arachne/BeadingStrategy/RedistributeBeadingStrategy.hpp @@ -4,7 +4,10 @@ #ifndef REDISTRIBUTE_DISTRIBUTED_BEADING_STRATEGY_H #define REDISTRIBUTE_DISTRIBUTED_BEADING_STRATEGY_H +#include + #include "BeadingStrategy.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r::Arachne { diff --git a/src/libslic3r/Arachne/BeadingStrategy/WideningBeadingStrategy.cpp b/src/libslic3r/Arachne/BeadingStrategy/WideningBeadingStrategy.cpp index 5345c49..4c6dbb2 100644 --- a/src/libslic3r/Arachne/BeadingStrategy/WideningBeadingStrategy.cpp +++ b/src/libslic3r/Arachne/BeadingStrategy/WideningBeadingStrategy.cpp @@ -3,6 +3,11 @@ #include "WideningBeadingStrategy.hpp" +#include +#include + +#include "libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp" + namespace Slic3r::Arachne { diff --git a/src/libslic3r/Arachne/BeadingStrategy/WideningBeadingStrategy.hpp b/src/libslic3r/Arachne/BeadingStrategy/WideningBeadingStrategy.hpp index 3e799b9..225aeed 100644 --- a/src/libslic3r/Arachne/BeadingStrategy/WideningBeadingStrategy.hpp +++ b/src/libslic3r/Arachne/BeadingStrategy/WideningBeadingStrategy.hpp @@ -4,7 +4,11 @@ #ifndef WIDENING_BEADING_STRATEGY_H #define WIDENING_BEADING_STRATEGY_H +#include +#include + #include "BeadingStrategy.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r::Arachne { diff --git a/src/libslic3r/Arachne/PerimeterOrder.cpp b/src/libslic3r/Arachne/PerimeterOrder.cpp new file mode 100644 index 0000000..554b409 --- /dev/null +++ b/src/libslic3r/Arachne/PerimeterOrder.cpp @@ -0,0 +1,280 @@ +#include +#include +#include + +#include "PerimeterOrder.hpp" +#include "libslic3r/Arachne/utils/ExtrusionJunction.hpp" +#include "libslic3r/Point.hpp" + +namespace Slic3r::Arachne::PerimeterOrder { + +using namespace Arachne; + +static size_t get_extrusion_lines_count(const Perimeters &perimeters) { + size_t extrusion_lines_count = 0; + for (const Perimeter &perimeter : perimeters) + extrusion_lines_count += perimeter.size(); + + return extrusion_lines_count; +} + +static PerimeterExtrusions get_sorted_perimeter_extrusions_by_area(const Perimeters &perimeters) { + PerimeterExtrusions sorted_perimeter_extrusions; + sorted_perimeter_extrusions.reserve(get_extrusion_lines_count(perimeters)); + + for (const Perimeter &perimeter : perimeters) { + for (const ExtrusionLine &extrusion_line : perimeter) { + if (extrusion_line.empty()) + continue; // This shouldn't ever happen. + + const BoundingBox bbox = get_extents(extrusion_line); + // Be aware that Arachne produces contours with clockwise orientation and holes with counterclockwise orientation. + const double area = std::abs(extrusion_line.area()); + const Polygon polygon = extrusion_line.is_closed ? to_polygon(extrusion_line) : Polygon{}; + + sorted_perimeter_extrusions.emplace_back(extrusion_line, area, polygon, bbox); + } + } + + // Open extrusions have an area equal to zero, so sorting based on the area ensures that open extrusions will always be before closed ones. + std::sort(sorted_perimeter_extrusions.begin(), sorted_perimeter_extrusions.end(), + [](const PerimeterExtrusion &l, const PerimeterExtrusion &r) { return l.area < r.area; }); + + return sorted_perimeter_extrusions; +} + +// Functions fill adjacent_perimeter_extrusions field for every PerimeterExtrusion by pointers to PerimeterExtrusions that contain or are inside this PerimeterExtrusion. +static void construct_perimeter_extrusions_adjacency_graph(PerimeterExtrusions &sorted_perimeter_extrusions) { + // Construct a graph (defined using adjacent_perimeter_extrusions field) where two PerimeterExtrusion are adjacent when one is inside the other. + std::vector root_candidates(sorted_perimeter_extrusions.size(), false); + for (PerimeterExtrusion &perimeter_extrusion : sorted_perimeter_extrusions) { + const size_t perimeter_extrusion_idx = &perimeter_extrusion - sorted_perimeter_extrusions.data(); + + if (!perimeter_extrusion.is_closed()) { + root_candidates[perimeter_extrusion_idx] = true; + continue; + } + + for (PerimeterExtrusion &root_candidate : sorted_perimeter_extrusions) { + const size_t root_candidate_idx = &root_candidate - sorted_perimeter_extrusions.data(); + + if (!root_candidates[root_candidate_idx]) + continue; + + if (perimeter_extrusion.bbox.contains(root_candidate.bbox) && perimeter_extrusion.polygon.contains(root_candidate.extrusion.junctions.front().p)) { + perimeter_extrusion.adjacent_perimeter_extrusions.emplace_back(&root_candidate); + root_candidate.adjacent_perimeter_extrusions.emplace_back(&perimeter_extrusion); + root_candidates[root_candidate_idx] = false; + } + } + + root_candidates[perimeter_extrusion_idx] = true; + } +} + +// Perform the depth-first search to assign the nearest external perimeter for every PerimeterExtrusion. +// When some PerimeterExtrusion is achievable from more than one external perimeter, then we choose the +// one that comes from a contour. +static void assign_nearest_external_perimeter(PerimeterExtrusions &sorted_perimeter_extrusions) { + std::stack stack; + for (PerimeterExtrusion &perimeter_extrusion : sorted_perimeter_extrusions) { + if (perimeter_extrusion.is_external_perimeter()) { + perimeter_extrusion.depth = 0; + perimeter_extrusion.nearest_external_perimeter = &perimeter_extrusion; + stack.push(&perimeter_extrusion); + } + } + + while (!stack.empty()) { + PerimeterExtrusion *current_extrusion = stack.top(); + stack.pop(); + + for (PerimeterExtrusion *adjacent_extrusion : current_extrusion->adjacent_perimeter_extrusions) { + const size_t adjacent_extrusion_depth = current_extrusion->depth + 1; + // Update depth when the new depth is smaller or when we can achieve the same depth from a contour. + // This will ensure that the internal perimeter will be extruded before the outer external perimeter + // when there are two external perimeters and one internal. + if (adjacent_extrusion_depth < adjacent_extrusion->depth) { + adjacent_extrusion->nearest_external_perimeter = current_extrusion->nearest_external_perimeter; + adjacent_extrusion->depth = adjacent_extrusion_depth; + stack.push(adjacent_extrusion); + } else if (adjacent_extrusion_depth == adjacent_extrusion->depth && !adjacent_extrusion->nearest_external_perimeter->is_contour() && current_extrusion->is_contour()) { + adjacent_extrusion->nearest_external_perimeter = current_extrusion->nearest_external_perimeter; + stack.push(adjacent_extrusion); + } + } + } +} + +inline Point get_end_position(const ExtrusionLine &extrusion) { + if (extrusion.is_closed) + return extrusion.junctions[0].p; // We ended where we started. + else + return extrusion.junctions.back().p; // Pick the other end from where we started. +} + +// Returns ordered extrusions. +static std::vector ordered_perimeter_extrusions_to_minimize_distances(Point current_position, std::vector extrusions) { + // Ensure that open extrusions will be placed before the closed one. + std::sort(extrusions.begin(), extrusions.end(), + [](const PerimeterExtrusion *l, const PerimeterExtrusion *r) -> bool { return l->is_closed() < r->is_closed(); }); + + std::vector ordered_extrusions; + std::vector already_selected(extrusions.size(), false); + while (ordered_extrusions.size() < extrusions.size()) { + double nearest_distance_sqr = std::numeric_limits::max(); + size_t nearest_extrusion_idx = 0; + bool is_nearest_closed = false; + + for (size_t extrusion_idx = 0; extrusion_idx < extrusions.size(); ++extrusion_idx) { + if (already_selected[extrusion_idx]) + continue; + + const ExtrusionLine &extrusion_line = extrusions[extrusion_idx]->extrusion; + const Point &extrusion_start_position = extrusion_line.junctions.front().p; + const double distance_sqr = (current_position - extrusion_start_position).cast().squaredNorm(); + if (distance_sqr < nearest_distance_sqr) { + if (extrusion_line.is_closed || (!extrusion_line.is_closed && nearest_distance_sqr == std::numeric_limits::max()) || (!extrusion_line.is_closed && !is_nearest_closed)) { + nearest_extrusion_idx = extrusion_idx; + nearest_distance_sqr = distance_sqr; + is_nearest_closed = extrusion_line.is_closed; + } + } + } + + already_selected[nearest_extrusion_idx] = true; + const PerimeterExtrusion *nearest_extrusion = extrusions[nearest_extrusion_idx]; + current_position = get_end_position(nearest_extrusion->extrusion); + ordered_extrusions.emplace_back(nearest_extrusion); + } + + return ordered_extrusions; +} + +struct GroupedPerimeterExtrusions +{ + GroupedPerimeterExtrusions() = delete; + explicit GroupedPerimeterExtrusions(const PerimeterExtrusion *external_perimeter_extrusion) + : external_perimeter_extrusion(external_perimeter_extrusion) {} + + std::vector extrusions; + const PerimeterExtrusion *external_perimeter_extrusion = nullptr; +}; + +// Returns vector of indexes that represent the order of grouped extrusions in grouped_extrusions. +static std::vector order_of_grouped_perimeter_extrusions_to_minimize_distances(Point current_position, std::vector grouped_extrusions) { + // Ensure that holes will be placed before contour and open extrusions before the closed one. + std::sort(grouped_extrusions.begin(), grouped_extrusions.end(), [](const GroupedPerimeterExtrusions &l, const GroupedPerimeterExtrusions &r) -> bool { + return (l.external_perimeter_extrusion->is_contour() < r.external_perimeter_extrusion->is_contour()) || + (l.external_perimeter_extrusion->is_contour() == r.external_perimeter_extrusion->is_contour() && l.external_perimeter_extrusion->is_closed() < r.external_perimeter_extrusion->is_closed()); + }); + + const size_t holes_cnt = std::count_if(grouped_extrusions.begin(), grouped_extrusions.end(), [](const GroupedPerimeterExtrusions &grouped_extrusions) { + return !grouped_extrusions.external_perimeter_extrusion->is_contour(); + }); + + std::vector grouped_extrusions_order; + std::vector already_selected(grouped_extrusions.size(), false); + while (grouped_extrusions_order.size() < grouped_extrusions.size()) { + double nearest_distance_sqr = std::numeric_limits::max(); + size_t nearest_grouped_extrusions_idx = 0; + bool is_nearest_closed = false; + + // First we order all holes and then we start ordering contours. + const size_t grouped_extrusion_end = grouped_extrusions_order.size() < holes_cnt ? holes_cnt: grouped_extrusions.size(); + for (size_t grouped_extrusion_idx = 0; grouped_extrusion_idx < grouped_extrusion_end; ++grouped_extrusion_idx) { + if (already_selected[grouped_extrusion_idx]) + continue; + + const ExtrusionLine &external_perimeter_extrusion_line = grouped_extrusions[grouped_extrusion_idx].external_perimeter_extrusion->extrusion; + const Point &extrusion_start_position = external_perimeter_extrusion_line.junctions.front().p; + const double distance_sqr = (current_position - extrusion_start_position).cast().squaredNorm(); + if (distance_sqr < nearest_distance_sqr) { + if (external_perimeter_extrusion_line.is_closed || (!external_perimeter_extrusion_line.is_closed && nearest_distance_sqr == std::numeric_limits::max()) || (!external_perimeter_extrusion_line.is_closed && !is_nearest_closed)) { + nearest_grouped_extrusions_idx = grouped_extrusion_idx; + nearest_distance_sqr = distance_sqr; + is_nearest_closed = external_perimeter_extrusion_line.is_closed; + } + } + } + + grouped_extrusions_order.emplace_back(nearest_grouped_extrusions_idx); + already_selected[nearest_grouped_extrusions_idx] = true; + const GroupedPerimeterExtrusions &nearest_grouped_extrusions = grouped_extrusions[nearest_grouped_extrusions_idx]; + const ExtrusionLine &last_extrusion_line = nearest_grouped_extrusions.extrusions.back()->extrusion; + current_position = get_end_position(last_extrusion_line); + } + + return grouped_extrusions_order; +} + +static PerimeterExtrusions extract_ordered_perimeter_extrusions(const PerimeterExtrusions &sorted_perimeter_extrusions, const bool external_perimeters_first) { + // Extrusions are ordered inside each group. + std::vector grouped_extrusions; + + std::stack stack; + std::vector visited(sorted_perimeter_extrusions.size(), false); + for (const PerimeterExtrusion &perimeter_extrusion : sorted_perimeter_extrusions) { + if (!perimeter_extrusion.is_external_perimeter()) + continue; + + stack.push(&perimeter_extrusion); + visited.assign(sorted_perimeter_extrusions.size(), false); + + grouped_extrusions.emplace_back(&perimeter_extrusion); + while (!stack.empty()) { + const PerimeterExtrusion *current_extrusion = stack.top(); + const size_t current_extrusion_idx = current_extrusion - sorted_perimeter_extrusions.data(); + + stack.pop(); + visited[current_extrusion_idx] = true; + + if (current_extrusion->nearest_external_perimeter == &perimeter_extrusion) { + grouped_extrusions.back().extrusions.emplace_back(current_extrusion); + } + + std::vector available_candidates; + for (const PerimeterExtrusion *adjacent_extrusion : current_extrusion->adjacent_perimeter_extrusions) { + const size_t adjacent_extrusion_idx = adjacent_extrusion - sorted_perimeter_extrusions.data(); + if (!visited[adjacent_extrusion_idx] && !adjacent_extrusion->is_external_perimeter() && adjacent_extrusion->nearest_external_perimeter == &perimeter_extrusion) { + available_candidates.emplace_back(adjacent_extrusion); + } + } + + if (available_candidates.size() == 1) { + stack.push(available_candidates.front()); + } else if (available_candidates.size() > 1) { + // When there is more than one available candidate, then order candidates to minimize distances between + // candidates and also to minimize the distance from the current_position. + std::vector adjacent_extrusions = ordered_perimeter_extrusions_to_minimize_distances(Point::Zero(), available_candidates); + for (auto extrusion_it = adjacent_extrusions.rbegin(); extrusion_it != adjacent_extrusions.rend(); ++extrusion_it) { + stack.push(*extrusion_it); + } + } + } + + if (!external_perimeters_first) + std::reverse(grouped_extrusions.back().extrusions.begin(), grouped_extrusions.back().extrusions.end()); + } + + const std::vector grouped_extrusion_order = order_of_grouped_perimeter_extrusions_to_minimize_distances(Point::Zero(), grouped_extrusions); + + PerimeterExtrusions ordered_extrusions; + for (size_t order_idx : grouped_extrusion_order) { + for (const PerimeterExtrusion *perimeter_extrusion : grouped_extrusions[order_idx].extrusions) + ordered_extrusions.emplace_back(*perimeter_extrusion); + } + + return ordered_extrusions; +} + +// FIXME: From the point of better patch planning, it should be better to do ordering when we have generated all extrusions (for now, when G-Code is exported). +// FIXME: It would be better to extract the adjacency graph of extrusions from the SkeletalTrapezoidation graph. +PerimeterExtrusions ordered_perimeter_extrusions(const Perimeters &perimeters, const bool external_perimeters_first) { + PerimeterExtrusions sorted_perimeter_extrusions = get_sorted_perimeter_extrusions_by_area(perimeters); + construct_perimeter_extrusions_adjacency_graph(sorted_perimeter_extrusions); + assign_nearest_external_perimeter(sorted_perimeter_extrusions); + return extract_ordered_perimeter_extrusions(sorted_perimeter_extrusions, external_perimeters_first); +} + +} // namespace Slic3r::Arachne::PerimeterOrder diff --git a/src/libslic3r/Arachne/PerimeterOrder.hpp b/src/libslic3r/Arachne/PerimeterOrder.hpp new file mode 100644 index 0000000..20d8a3d --- /dev/null +++ b/src/libslic3r/Arachne/PerimeterOrder.hpp @@ -0,0 +1,54 @@ +#ifndef slic3r_GCode_PerimeterOrder_hpp_ +#define slic3r_GCode_PerimeterOrder_hpp_ + +#include +#include +#include +#include + +#include "libslic3r/Arachne/utils/ExtrusionLine.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/Polygon.hpp" + +namespace Slic3r::Arachne::PerimeterOrder { + +// Data structure stores ExtrusionLine (closed and open) together with additional data. +struct PerimeterExtrusion +{ + explicit PerimeterExtrusion(const Arachne::ExtrusionLine &extrusion, const double area, const Polygon &polygon, const BoundingBox &bbox) + : extrusion(extrusion), area(area), polygon(polygon), bbox(bbox) {} + + Arachne::ExtrusionLine extrusion; + // Absolute value of the area of the polygon. The value is always non-negative, even for holes. + double area = 0; + + // Polygon is non-empty only for closed extrusions. + Polygon polygon; + BoundingBox bbox; + + std::vector adjacent_perimeter_extrusions; + + // How far is this perimeter from the nearest external perimeter. Contour is always preferred over holes. + size_t depth = std::numeric_limits::max(); + PerimeterExtrusion *nearest_external_perimeter = nullptr; + + // Should this extrusion be fuzzyfied during path generation? + bool fuzzify = false; + + // Returns if ExtrusionLine is a contour or a hole. + bool is_contour() const { return extrusion.is_contour(); } + + // Returns if ExtrusionLine is closed or opened. + bool is_closed() const { return extrusion.is_closed; } + + // Returns if ExtrusionLine is an external or an internal perimeter. + bool is_external_perimeter() const { return extrusion.is_external_perimeter(); } +}; + +using PerimeterExtrusions = std::vector; + +PerimeterExtrusions ordered_perimeter_extrusions(const Perimeters &perimeters, bool external_perimeters_first); + +} // namespace Slic3r::Arachne::PerimeterOrder + +#endif // slic3r_GCode_Travels_hpp_ diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp b/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp index 5352681..588afb1 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidation.cpp @@ -3,25 +3,30 @@ #include "SkeletalTrapezoidation.hpp" -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "libslic3r/Geometry/VoronoiUtils.hpp" +#include "ankerl/unordered_dense.h" +#include "libslic3r/Arachne/SkeletalTrapezoidationEdge.hpp" +#include "libslic3r/Arachne/SkeletalTrapezoidationJoint.hpp" +#include "libslic3r/Arachne/utils/ExtrusionJunction.hpp" +#include "libslic3r/Arachne/utils/ExtrusionLine.hpp" -#include "utils/linearAlg2D.hpp" -#include "Utils.hpp" -#include "SVG.hpp" -#include "Geometry/VoronoiVisualUtils.hpp" -#include "Geometry/VoronoiUtilsCgal.hpp" -#include "../EdgeGrid.hpp" +#ifndef NDEBUG + #include "libslic3r/EdgeGrid.hpp" +#endif -#include "Geometry/VoronoiUtils.hpp" #define SKELETAL_TRAPEZOIDATION_BEAD_SEARCH_MAX 1000 //A limit to how long it'll keep searching for adjacent beads. Increasing will re-use beadings more often (saving performance), but search longer for beading (costing performance). - namespace Slic3r::Arachne { @@ -105,7 +110,7 @@ SkeletalTrapezoidation::node_t &SkeletalTrapezoidation::makeNode(const VD::verte } } -void SkeletalTrapezoidation::transferEdge(Point from, Point to, const VD::edge_type &vd_edge, edge_t *&prev_edge, Point &start_source_point, Point &end_source_point, const std::vector &segments) { +void SkeletalTrapezoidation::transferEdge(const Point &from, const Point &to, const VD::edge_type &vd_edge, edge_t *&prev_edge, const Point &start_source_point, const Point &end_source_point, const std::vector &segments) { auto he_edge_it = vd_edge_to_he_edge.find(vd_edge.twin()); if (he_edge_it != vd_edge_to_he_edge.end()) { // Twin segment(s) have already been made @@ -153,8 +158,7 @@ void SkeletalTrapezoidation::transferEdge(Point from, Point to, const VD::edge_t assert(twin->prev->twin); // Back rib assert(twin->prev->twin->prev); // Prev segment along parabola - constexpr bool is_not_next_to_start_or_end = false; // Only ribs at the end of a cell should be skipped - graph.makeRib(prev_edge, start_source_point, end_source_point, is_not_next_to_start_or_end); + graph.makeRib(prev_edge, start_source_point, end_source_point); } assert(prev_edge); } @@ -204,10 +208,8 @@ void SkeletalTrapezoidation::transferEdge(Point from, Point to, const VD::edge_t p0 = p1; v0 = v1; - if (p1_idx < discretized.size() - 1) - { // Rib for last segment gets introduced outside this function! - constexpr bool is_not_next_to_start_or_end = false; // Only ribs at the end of a cell should be skipped - graph.makeRib(prev_edge, start_source_point, end_source_point, is_not_next_to_start_or_end); + if (p1_idx < discretized.size() - 1) { // Rib for last segment gets introduced outside this function! + graph.makeRib(prev_edge, start_source_point, end_source_point); } } assert(prev_edge); @@ -218,6 +220,7 @@ void SkeletalTrapezoidation::transferEdge(Point from, Point to, const VD::edge_t Points SkeletalTrapezoidation::discretize(const VD::edge_type& vd_edge, const std::vector& segments) { assert(Geometry::VoronoiUtils::is_in_range(vd_edge)); + /*Terminology in this function assumes that the edge moves horizontally from left to right. This is not necessarily the case; the edge can go in any direction, but it helps to picture it in a certain direction in your head.*/ @@ -227,7 +230,7 @@ Points SkeletalTrapezoidation::discretize(const VD::edge_type& vd_edge, const st Point start = Geometry::VoronoiUtils::to_point(vd_edge.vertex0()).cast(); Point end = Geometry::VoronoiUtils::to_point(vd_edge.vertex1()).cast(); - + bool point_left = left_cell->contains_point(); bool point_right = right_cell->contains_point(); if ((!point_left && !point_right) || vd_edge.is_secondary()) // Source vert is directly connected to source segment @@ -247,9 +250,9 @@ Points SkeletalTrapezoidation::discretize(const VD::edge_type& vd_edge, const st beadings along the way.*/ Point left_point = Geometry::VoronoiUtils::get_source_point(*left_cell, segments.begin(), segments.end()); Point right_point = Geometry::VoronoiUtils::get_source_point(*right_cell, segments.begin(), segments.end()); - coord_t d = (right_point - left_point).cast().norm(); - Point middle = (left_point + right_point) / 2; - Point x_axis_dir = perp(Point(right_point - left_point)); + coord_t d = (right_point - left_point).cast().norm(); + Point middle = (left_point + right_point) / 2; + Point x_axis_dir = perp(Point(right_point - left_point)); coord_t x_axis_length = x_axis_dir.cast().norm(); const auto projected_x = [x_axis_dir, x_axis_length, middle](Point from) //Project a point on the edge. @@ -326,51 +329,6 @@ Points SkeletalTrapezoidation::discretize(const VD::edge_type& vd_edge, const st } } -bool SkeletalTrapezoidation::computePointCellRange(const VD::cell_type &cell, Point &start_source_point, Point &end_source_point, const VD::edge_type *&starting_vd_edge, const VD::edge_type *&ending_vd_edge, const std::vector &segments) { - if (cell.incident_edge()->is_infinite()) - return false; //Infinite edges only occur outside of the polygon. Don't copy any part of this cell. - - // Check if any point of the cell is inside or outside polygon - // Copy whole cell into graph or not at all - - // If the cell.incident_edge()->vertex0() is far away so much that it doesn't even fit into Vec2i64, then there is no way that it will be inside the input polygon. - if (const VD::vertex_type &vert = *cell.incident_edge()->vertex0(); - vert.x() >= double(std::numeric_limits::max()) || vert.x() <= double(std::numeric_limits::lowest()) || - vert.y() >= double(std::numeric_limits::max()) || vert.y() <= double(std::numeric_limits::lowest())) - return false; // Don't copy any part of this cell - - const Point source_point = Geometry::VoronoiUtils::get_source_point(cell, segments.begin(), segments.end()); - const PolygonsPointIndex source_point_index = Geometry::VoronoiUtils::get_source_point_index(cell, segments.begin(), segments.end()); - Vec2i64 some_point = Geometry::VoronoiUtils::to_point(cell.incident_edge()->vertex0()); - if (some_point == source_point.cast()) - some_point = Geometry::VoronoiUtils::to_point(cell.incident_edge()->vertex1()); - - //Test if the some_point is even inside the polygon. - //The edge leading out of a polygon must have an endpoint that's not in the corner following the contour of the polygon at that vertex. - //So if it's inside the corner formed by the polygon vertex, it's all fine. - //But if it's outside of the corner, it must be a vertex of the Voronoi diagram that goes outside of the polygon towards infinity. - if (!LinearAlg2D::isInsideCorner(source_point_index.prev().p(), source_point_index.p(), source_point_index.next().p(), some_point)) - return false; // Don't copy any part of this cell - - const VD::edge_type* vd_edge = cell.incident_edge(); - do { - assert(vd_edge->is_finite()); - if (Vec2i64 p1 = Geometry::VoronoiUtils::to_point(vd_edge->vertex1()); p1 == source_point.cast()) { - start_source_point = source_point; - end_source_point = source_point; - starting_vd_edge = vd_edge->next(); - ending_vd_edge = vd_edge; - } else { - assert((Geometry::VoronoiUtils::to_point(vd_edge->vertex0()) == source_point.cast() || !vd_edge->is_secondary()) && "point cells must end in the point! They cannot cross the point with an edge, because collinear edges are not allowed in the input."); - } - } - while (vd_edge = vd_edge->next(), vd_edge != cell.incident_edge()); - assert(starting_vd_edge && ending_vd_edge); - assert(starting_vd_edge != ending_vd_edge); - return true; -} - - SkeletalTrapezoidation::SkeletalTrapezoidation(const Polygons& polys, const BeadingStrategy& beading_strategy, double transitioning_angle, coord_t discretization_step_size, coord_t transition_filter_dist, coord_t allowed_filter_deviation, @@ -385,7 +343,6 @@ SkeletalTrapezoidation::SkeletalTrapezoidation(const Polygons& polys, const Bead constructFromPolygons(polys); } - void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys) { #ifdef ARACHNE_DEBUG @@ -432,22 +389,27 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys) if (!cell.incident_edge()) continue; // There is no spoon - Point start_source_point; - Point end_source_point; + Point start_source_point; + Point end_source_point; const VD::edge_type *starting_voronoi_edge = nullptr; const VD::edge_type *ending_voronoi_edge = nullptr; // Compute and store result in above variables if (cell.contains_point()) { - const bool keep_going = computePointCellRange(cell, start_source_point, end_source_point, starting_voronoi_edge, ending_voronoi_edge, segments); - if (!keep_going) + Geometry::PointCellRange cell_range = Geometry::VoronoiUtils::compute_point_cell_range(cell, segments.cbegin(), segments.cend()); + start_source_point = cell_range.source_point; + end_source_point = cell_range.source_point; + starting_voronoi_edge = cell_range.edge_begin; + ending_voronoi_edge = cell_range.edge_end; + + if (!cell_range.is_valid()) continue; } else { assert(cell.contains_segment()); Geometry::SegmentCellRange cell_range = Geometry::VoronoiUtils::compute_segment_cell_range(cell, segments.cbegin(), segments.cend()); assert(cell_range.is_valid()); - start_source_point = cell_range.segment_start_point; - end_source_point = cell_range.segment_end_point; + start_source_point = cell_range.source_segment_start_point; + end_source_point = cell_range.source_segment_end_point; starting_voronoi_edge = cell_range.edge_begin; ending_voronoi_edge = cell_range.edge_end; } @@ -459,30 +421,26 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys) // Copy start to end edge to graph assert(Geometry::VoronoiUtils::is_in_range(*starting_voronoi_edge)); - edge_t* prev_edge = nullptr; + edge_t *prev_edge = nullptr; transferEdge(start_source_point, Geometry::VoronoiUtils::to_point(starting_voronoi_edge->vertex1()).cast(), *starting_voronoi_edge, prev_edge, start_source_point, end_source_point, segments); - node_t* starting_node = vd_node_to_he_node[starting_voronoi_edge->vertex0()]; + node_t *starting_node = vd_node_to_he_node[starting_voronoi_edge->vertex0()]; starting_node->data.distance_to_boundary = 0; - constexpr bool is_next_to_start_or_end = true; - graph.makeRib(prev_edge, start_source_point, end_source_point, is_next_to_start_or_end); + graph.makeRib(prev_edge, start_source_point, end_source_point); for (const VD::edge_type* vd_edge = starting_voronoi_edge->next(); vd_edge != ending_voronoi_edge; vd_edge = vd_edge->next()) { assert(vd_edge->is_finite()); - assert(Geometry::VoronoiUtils::is_in_range(*vd_edge)); Point v1 = Geometry::VoronoiUtils::to_point(vd_edge->vertex0()).cast(); Point v2 = Geometry::VoronoiUtils::to_point(vd_edge->vertex1()).cast(); transferEdge(v1, v2, *vd_edge, prev_edge, start_source_point, end_source_point, segments); - - graph.makeRib(prev_edge, start_source_point, end_source_point, vd_edge->next() == ending_voronoi_edge); + graph.makeRib(prev_edge, start_source_point, end_source_point); } transferEdge(Geometry::VoronoiUtils::to_point(ending_voronoi_edge->vertex0()).cast(), end_source_point, *ending_voronoi_edge, prev_edge, start_source_point, end_source_point, segments); prev_edge->to->data.distance_to_boundary = 0; } - #ifdef ARACHNE_DEBUG assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(voronoi_diagram)); #endif diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp b/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp index 176f2a0..4610b1b 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidation.hpp @@ -5,12 +5,11 @@ #define SKELETAL_TRAPEZOIDATION_H #include - +#include #include // smart pointers #include // pair - -#include - +#include +#include #include "utils/HalfEdgeGraph.hpp" #include "utils/PolygonsSegmentIndex.hpp" @@ -21,6 +20,10 @@ #include "libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp" #include "SkeletalTrapezoidationGraph.hpp" #include "../Geometry/Voronoi.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/libslic3r.h" //#define ARACHNE_DEBUG //#define ARACHNE_DEBUG_VORONOI @@ -178,7 +181,7 @@ protected: * Transfer an edge from the VD to the HE and perform discretization of parabolic edges (and vertex-vertex edges) * \p prev_edge serves as input and output. May be null as input. */ - void transferEdge(Point from, Point to, const VD::edge_type &vd_edge, edge_t *&prev_edge, Point &start_source_point, Point &end_source_point, const std::vector &segments); + void transferEdge(const Point &from, const Point &to, const VD::edge_type &vd_edge, edge_t *&prev_edge, const Point &start_source_point, const Point &end_source_point, const std::vector &segments); /*! * Discretize a Voronoi edge that represents the medial axis of a vertex- @@ -207,32 +210,6 @@ protected: */ Points discretize(const VD::edge_type& segment, const std::vector& segments); - /*! - * Compute the range of line segments that surround a cell of the skeletal - * graph that belongs to a point on the medial axis. - * - * This should only be used on cells that belong to a corner in the skeletal - * graph, e.g. triangular cells, not trapezoid cells. - * - * The resulting line segments is just the first and the last segment. They - * are linked to the neighboring segments, so you can iterate over the - * segments until you reach the last segment. - * \param cell The cell to compute the range of line segments for. - * \param[out] start_source_point The start point of the source segment of - * this cell. - * \param[out] end_source_point The end point of the source segment of this - * cell. - * \param[out] starting_vd_edge The edge of the Voronoi diagram where the - * loop around the cell starts. - * \param[out] ending_vd_edge The edge of the Voronoi diagram where the loop - * around the cell ends. - * \param points All vertices of the input Polygons. - * \param segments All edges of the input Polygons. - * /return Whether the cell is inside of the polygon. If it's outside of the - * polygon we should skip processing it altogether. - */ - static bool computePointCellRange(const VD::cell_type &cell, Point &start_source_point, Point &end_source_point, const VD::edge_type *&starting_vd_edge, const VD::edge_type *&ending_vd_edge, const std::vector &segments); - /*! * For VD cells associated with an input polygon vertex, we need to separate the node at the end and start of the cell into two * That way we can reach both the quad_start and the quad_end from the [incident_edge] of the two new nodes @@ -573,8 +550,6 @@ protected: * Genrate small segments for local maxima where the beading would only result in a single bead */ void generateLocalMaximaSingleBeads(); - - friend bool detect_voronoi_edge_intersecting_input_segment(const VD &voronoi_diagram, const std::vector &segments); }; } // namespace Slic3r::Arachne diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.cpp b/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.cpp index 4629396..f7a30c3 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.cpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.cpp @@ -4,11 +4,16 @@ #include "SkeletalTrapezoidationGraph.hpp" #include - #include +#include +#include +#include +#include -#include "utils/linearAlg2D.hpp" #include "../Line.hpp" +#include "libslic3r/Arachne/SkeletalTrapezoidationEdge.hpp" +#include "libslic3r/Arachne/SkeletalTrapezoidationJoint.hpp" +#include "libslic3r/Point.hpp" namespace Slic3r::Arachne { @@ -314,8 +319,7 @@ void SkeletalTrapezoidationGraph::collapseSmallEdges(coord_t snap_dist) } } -void SkeletalTrapezoidationGraph::makeRib(edge_t*& prev_edge, Point start_source_point, Point end_source_point, bool is_next_to_start_or_end) -{ +void SkeletalTrapezoidationGraph::makeRib(edge_t *&prev_edge, const Point &start_source_point, const Point &end_source_point) { Point p; Line(start_source_point, end_source_point).distance_to_infinite_squared(prev_edge->to->p, &p); coord_t dist = (prev_edge->to->p - p).cast().norm(); diff --git a/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.hpp b/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.hpp index efda323..5baac24 100644 --- a/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.hpp +++ b/src/libslic3r/Arachne/SkeletalTrapezoidationGraph.hpp @@ -5,14 +5,19 @@ #define SKELETAL_TRAPEZOIDATION_GRAPH_H #include +#include #include "utils/HalfEdgeGraph.hpp" #include "SkeletalTrapezoidationEdge.hpp" #include "SkeletalTrapezoidationJoint.hpp" +#include "libslic3r/Arachne/utils/HalfEdge.hpp" +#include "libslic3r/Arachne/utils/HalfEdgeNode.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { class Line; +class Point; }; namespace Slic3r::Arachne @@ -88,7 +93,7 @@ public: */ void collapseSmallEdges(coord_t snap_dist = 5); - void makeRib(edge_t*& prev_edge, Point start_source_point, Point end_source_point, bool is_next_to_start_or_end); + void makeRib(edge_t*& prev_edge, const Point &start_source_point, const Point &end_source_point); /*! * Insert a node into the graph and connect it to the input polygon using ribs diff --git a/src/libslic3r/Arachne/WallToolPaths.cpp b/src/libslic3r/Arachne/WallToolPaths.cpp index 6c5dafd..d0b7b54 100644 --- a/src/libslic3r/Arachne/WallToolPaths.cpp +++ b/src/libslic3r/Arachne/WallToolPaths.cpp @@ -2,20 +2,28 @@ // CuraEngine is released under the terms of the AGPLv3 or higher. #include //For std::partition_copy and std::min_element. +#include +#include +#include +#include +#include #include "WallToolPaths.hpp" - #include "SkeletalTrapezoidation.hpp" -#include "../ClipperUtils.hpp" #include "utils/linearAlg2D.hpp" -#include "EdgeGrid.hpp" #include "utils/SparseLineGrid.hpp" -#include "Geometry.hpp" +#include "libslic3r/Geometry.hpp" #include "utils/PolylineStitcher.hpp" -#include "SVG.hpp" -#include "Utils.hpp" - -#include +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp" +#include "libslic3r/Arachne/BeadingStrategy/BeadingStrategyFactory.hpp" +#include "libslic3r/Arachne/utils/ExtrusionJunction.hpp" +#include "libslic3r/Arachne/utils/ExtrusionLine.hpp" +#include "libslic3r/Arachne/utils/PolygonsPointIndex.hpp" +#include "libslic3r/Flow.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/PrintConfig.hpp" //#define ARACHNE_STITCH_PATCH_DEBUG @@ -758,14 +766,7 @@ bool WallToolPaths::removeEmptyToolPaths(std::vector &toolpa return toolpaths.empty(); } -/*! - * Get the order constraints of the insets when printing walls per region / hole. - * Each returned pair consists of adjacent wall lines where the left has an inset_idx one lower than the right. - * - * Odd walls should always go after their enclosing wall polygons. - * - * \param outer_to_inner Whether the wall polygons with a lower inset_idx should go before those with a higher one. - */ + WallToolPaths::ExtrusionLineSet WallToolPaths::getRegionOrder(const std::vector &input, const bool outer_to_inner) { ExtrusionLineSet order_requirements; diff --git a/src/libslic3r/Arachne/WallToolPaths.hpp b/src/libslic3r/Arachne/WallToolPaths.hpp index 44f3aff..3fd6eb9 100644 --- a/src/libslic3r/Arachne/WallToolPaths.hpp +++ b/src/libslic3r/Arachne/WallToolPaths.hpp @@ -4,14 +4,23 @@ #ifndef CURAENGINE_WALLTOOLPATHS_H #define CURAENGINE_WALLTOOLPATHS_H -#include - #include +#include +#include +#include +#include +#include #include "BeadingStrategy/BeadingStrategyFactory.hpp" #include "utils/ExtrusionLine.hpp" #include "../Polygon.hpp" #include "../PrintConfig.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/libslic3r.h" + +namespace boost { +template struct hash; +} // namespace boost namespace Slic3r::Arachne { @@ -75,14 +84,7 @@ public: static bool removeEmptyToolPaths(std::vector &toolpaths); using ExtrusionLineSet = ankerl::unordered_dense::set, boost::hash>>; - /*! - * Get the order constraints of the insets when printing walls per region / hole. - * Each returned pair consists of adjacent wall lines where the left has an inset_idx one lower than the right. - * - * Odd walls should always go after their enclosing wall polygons. - * - * \param outer_to_inner Whether the wall polygons with a lower inset_idx should go before those with a higher one. - */ + static ExtrusionLineSet getRegionOrder(const std::vector &input, bool outer_to_inner); protected: diff --git a/src/libslic3r/Arachne/utils/ExtrusionJunction.cpp b/src/libslic3r/Arachne/utils/ExtrusionJunction.cpp deleted file mode 100644 index 3cdfa0d..0000000 --- a/src/libslic3r/Arachne/utils/ExtrusionJunction.cpp +++ /dev/null @@ -1,18 +0,0 @@ -//Copyright (c) 2020 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. - -#include "ExtrusionJunction.hpp" - -namespace Slic3r::Arachne -{ - -bool ExtrusionJunction::operator ==(const ExtrusionJunction& other) const -{ - return p == other.p - && w == other.w - && perimeter_index == other.perimeter_index; -} - -ExtrusionJunction::ExtrusionJunction(const Point p, const coord_t w, const coord_t perimeter_index) : p(p), w(w), perimeter_index(perimeter_index) {} - -} diff --git a/src/libslic3r/Arachne/utils/ExtrusionJunction.hpp b/src/libslic3r/Arachne/utils/ExtrusionJunction.hpp index 1465251..49f721b 100644 --- a/src/libslic3r/Arachne/utils/ExtrusionJunction.hpp +++ b/src/libslic3r/Arachne/utils/ExtrusionJunction.hpp @@ -37,9 +37,11 @@ struct ExtrusionJunction */ size_t perimeter_index; - ExtrusionJunction(const Point p, const coord_t w, const coord_t perimeter_index); + ExtrusionJunction(const Point p, const coord_t w, const coord_t perimeter_index) : p(p), w(w), perimeter_index(perimeter_index) {} - bool operator==(const ExtrusionJunction& other) const; + bool operator==(const ExtrusionJunction &other) const { + return p == other.p && w == other.w && perimeter_index == other.perimeter_index; + } }; inline Point operator-(const ExtrusionJunction& a, const ExtrusionJunction& b) diff --git a/src/libslic3r/Arachne/utils/ExtrusionLine.cpp b/src/libslic3r/Arachne/utils/ExtrusionLine.cpp index 8631d9e..c597ac2 100644 --- a/src/libslic3r/Arachne/utils/ExtrusionLine.cpp +++ b/src/libslic3r/Arachne/utils/ExtrusionLine.cpp @@ -2,10 +2,21 @@ //CuraEngine is released under the terms of the AGPLv3 or higher. #include +#include +#include #include "ExtrusionLine.hpp" -#include "linearAlg2D.hpp" #include "../../PerimeterGenerator.hpp" +#include "libslic3r/Arachne/utils/ExtrusionJunction.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/ExtrusionEntity.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/Polyline.hpp" + +namespace Slic3r { +class Flow; +} // namespace Slic3r namespace Slic3r::Arachne { @@ -29,15 +40,6 @@ int64_t ExtrusionLine::getLength() const return len; } -coord_t ExtrusionLine::getMinimalWidth() const -{ - return std::min_element(junctions.cbegin(), junctions.cend(), - [](const ExtrusionJunction& l, const ExtrusionJunction& r) - { - return l.w < r.w; - })->w; -} - void ExtrusionLine::simplify(const int64_t smallest_line_segment_squared, const int64_t allowed_error_distance_squared, const int64_t maximum_extrusion_area_deviation) { const size_t min_path_size = is_closed ? 3 : 2; @@ -236,9 +238,10 @@ bool ExtrusionLine::is_contour() const return poly.is_clockwise(); } -double ExtrusionLine::area() const -{ - assert(this->is_closed); +double ExtrusionLine::area() const { + if (!this->is_closed) + return 0.; + double a = 0.; if (this->junctions.size() >= 3) { Vec2d p1 = this->junctions.back().p.cast(); @@ -248,9 +251,25 @@ double ExtrusionLine::area() const p1 = p2; } } + return 0.5 * a; } +Points to_points(const ExtrusionLine &extrusion_line) { + Points points; + points.reserve(extrusion_line.junctions.size()); + for (const ExtrusionJunction &junction : extrusion_line.junctions) + points.emplace_back(junction.p); + return points; +} + +BoundingBox get_extents(const ExtrusionLine &extrusion_line) { + BoundingBox bbox; + for (const ExtrusionJunction &junction : extrusion_line.junctions) + bbox.merge(junction.p); + return bbox; +} + } // namespace Slic3r::Arachne namespace Slic3r { diff --git a/src/libslic3r/Arachne/utils/ExtrusionLine.hpp b/src/libslic3r/Arachne/utils/ExtrusionLine.hpp index d39e1e0..32bbc96 100644 --- a/src/libslic3r/Arachne/utils/ExtrusionLine.hpp +++ b/src/libslic3r/Arachne/utils/ExtrusionLine.hpp @@ -5,16 +5,29 @@ #ifndef UTILS_EXTRUSION_LINE_H #define UTILS_EXTRUSION_LINE_H +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "ExtrusionJunction.hpp" #include "../../Polyline.hpp" #include "../../Polygon.hpp" #include "../../BoundingBox.hpp" #include "../../ExtrusionEntity.hpp" #include "../../Flow.hpp" -#include "../../../clipper/clipper_z.hpp" +#include "libslic3r/ExtrusionRole.hpp" +#include "libslic3r/Point.hpp" namespace Slic3r { struct ThickPolyline; +class Flow; } namespace Slic3r::Arachne @@ -136,11 +149,6 @@ struct ExtrusionLine return ret; } - /*! - * Get the minimal width of this path - */ - coord_t getMinimalWidth() const; - /*! * Removes vertices of the ExtrusionLines to make sure that they are not too high * resolution. @@ -192,6 +200,8 @@ struct ExtrusionLine bool is_contour() const; double area() const; + + bool is_external_perimeter() const { return this->inset_idx == 0; } }; static inline Slic3r::ThickPolyline to_thick_polyline(const Arachne::ExtrusionLine &line_junctions) @@ -237,6 +247,7 @@ static inline Slic3r::ThickPolyline to_thick_polyline(const ClipperLib_Z::Path & static inline Polygon to_polygon(const ExtrusionLine &line) { Polygon out; + assert(line.is_closed); assert(line.junctions.size() >= 3); assert(line.junctions.front().p == line.junctions.back().p); out.points.reserve(line.junctions.size() - 1); @@ -245,15 +256,11 @@ static inline Polygon to_polygon(const ExtrusionLine &line) return out; } -#if 0 -static BoundingBox get_extents(const ExtrusionLine &extrusion_line) -{ - BoundingBox bbox; - for (const ExtrusionJunction &junction : extrusion_line.junctions) - bbox.merge(junction.p); - return bbox; -} +Points to_points(const ExtrusionLine &extrusion_line); +BoundingBox get_extents(const ExtrusionLine &extrusion_line); + +#if 0 static BoundingBox get_extents(const std::vector &extrusion_lines) { BoundingBox bbox; @@ -272,15 +279,6 @@ static BoundingBox get_extents(const std::vector &extrusi return bbox; } -static Points to_points(const ExtrusionLine &extrusion_line) -{ - Points points; - points.reserve(extrusion_line.junctions.size()); - for (const ExtrusionJunction &junction : extrusion_line.junctions) - points.emplace_back(junction.p); - return points; -} - static std::vector to_points(const std::vector &extrusion_lines) { std::vector points; @@ -293,6 +291,8 @@ static std::vector to_points(const std::vector &e #endif using VariableWidthLines = std::vector; //; } // namespace Slic3r::Arachne diff --git a/src/libslic3r/Arachne/utils/PolygonsPointIndex.hpp b/src/libslic3r/Arachne/utils/PolygonsPointIndex.hpp index 0dadc53..04f017f 100644 --- a/src/libslic3r/Arachne/utils/PolygonsPointIndex.hpp +++ b/src/libslic3r/Arachne/utils/PolygonsPointIndex.hpp @@ -156,7 +156,6 @@ struct PathsPointIndexLocator } }; - }//namespace Slic3r::Arachne namespace std diff --git a/src/libslic3r/Arachne/utils/PolylineStitcher.cpp b/src/libslic3r/Arachne/utils/PolylineStitcher.cpp index 89ec929..0abf63a 100644 --- a/src/libslic3r/Arachne/utils/PolylineStitcher.cpp +++ b/src/libslic3r/Arachne/utils/PolylineStitcher.cpp @@ -2,7 +2,16 @@ //CuraEngine is released under the terms of the AGPLv3 or higher. #include "PolylineStitcher.hpp" + #include "ExtrusionLine.hpp" +#include "libslic3r/Arachne/utils/PolygonsPointIndex.hpp" +#include "libslic3r/Polygon.hpp" + +namespace Slic3r { +namespace Arachne { +struct ExtrusionJunction; +} // namespace Arachne +} // namespace Slic3r namespace Slic3r::Arachne { diff --git a/src/libslic3r/Arachne/utils/PolylineStitcher.hpp b/src/libslic3r/Arachne/utils/PolylineStitcher.hpp index 113761c..7f547f4 100644 --- a/src/libslic3r/Arachne/utils/PolylineStitcher.hpp +++ b/src/libslic3r/Arachne/utils/PolylineStitcher.hpp @@ -4,10 +4,20 @@ #ifndef UTILS_POLYLINE_STITCHER_H #define UTILS_POLYLINE_STITCHER_H +#include +#include +#include +#include +#include +#include +#include +#include + #include "SparsePointGrid.hpp" #include "PolygonsPointIndex.hpp" #include "../../Polygon.hpp" -#include +#include "libslic3r/Point.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r::Arachne { diff --git a/src/libslic3r/Arachne/utils/SparsePointGrid.hpp b/src/libslic3r/Arachne/utils/SparsePointGrid.hpp index 7bb51d7..127380b 100644 --- a/src/libslic3r/Arachne/utils/SparsePointGrid.hpp +++ b/src/libslic3r/Arachne/utils/SparsePointGrid.hpp @@ -39,16 +39,6 @@ public: */ void insert(const Elem &elem); - /*! - * Get just any element that's within a certain radius of a point. - * - * Rather than giving a vector of nearby elements, this function just gives - * a single element, any element, in no particular order. - * \param query_pt The point to query for an object nearby. - * \param radius The radius of what is considered "nearby". - */ - const ElemT *getAnyNearby(const Point &query_pt, coord_t radius); - protected: using GridPoint = typename SparseGrid::GridPoint; @@ -68,22 +58,6 @@ void SparsePointGrid::insert(const Elem &elem) SparseGrid::m_grid.emplace(grid_loc, elem); } -template -const ElemT *SparsePointGrid::getAnyNearby(const Point &query_pt, coord_t radius) -{ - const ElemT *ret = nullptr; - const std::function &process_func = [&ret, query_pt, radius, this](const ElemT &maybe_nearby) { - if (shorter_then(m_locator(maybe_nearby) - query_pt, radius)) { - ret = &maybe_nearby; - return false; - } - return true; - }; - SparseGrid::processNearby(query_pt, radius, process_func); - - return ret; -} - } // namespace Slic3r::Arachne #endif // UTILS_SPARSE_POINT_GRID_H diff --git a/src/libslic3r/Arachne/utils/SquareGrid.cpp b/src/libslic3r/Arachne/utils/SquareGrid.cpp index ae89965..1af9910 100644 --- a/src/libslic3r/Arachne/utils/SquareGrid.cpp +++ b/src/libslic3r/Arachne/utils/SquareGrid.cpp @@ -2,7 +2,10 @@ //CuraEngine is released under the terms of the AGPLv3 or higher. #include "SquareGrid.hpp" -#include "../../Point.hpp" + +#include + +#include "libslic3r/Point.hpp" using namespace Slic3r::Arachne; diff --git a/src/libslic3r/Arachne/utils/SquareGrid.hpp b/src/libslic3r/Arachne/utils/SquareGrid.hpp index 5787e3b..ff45d85 100644 --- a/src/libslic3r/Arachne/utils/SquareGrid.hpp +++ b/src/libslic3r/Arachne/utils/SquareGrid.hpp @@ -4,11 +4,15 @@ #ifndef UTILS_SQUARE_GRID_H #define UTILS_SQUARE_GRID_H -#include "../../Point.hpp" - +#include #include #include #include +#include +#include + +#include "../../Point.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r::Arachne { diff --git a/src/libslic3r/Arachne/utils/linearAlg2D.hpp b/src/libslic3r/Arachne/utils/linearAlg2D.hpp index 304984b..7ece14c 100644 --- a/src/libslic3r/Arachne/utils/linearAlg2D.hpp +++ b/src/libslic3r/Arachne/utils/linearAlg2D.hpp @@ -9,59 +9,6 @@ namespace Slic3r::Arachne::LinearAlg2D { -/*! - * Test whether a point is inside a corner. - * Whether point \p query_point is left of the corner abc. - * Whether the \p query_point is in the circle half left of ab and left of bc, rather than to the right. - * - * Test whether the \p query_point is inside of a polygon w.r.t a single corner. - */ -inline static bool isInsideCorner(const Point &a, const Point &b, const Point &c, const Vec2i64 &query_point) -{ - // Visualisation for the algorithm below: - // - // query - // | - // | - // | - // perp-----------b - // / \ (note that the lines - // / \ AB and AC are normalized - // / \ to 10000 units length) - // a c - // - - auto normal = [](const Point &p0, coord_t len) -> Point { - int64_t _len = p0.cast().norm(); - if (_len < 1) - return {len, 0}; - return (p0.cast() * int64_t(len) / _len).cast(); - }; - - constexpr coord_t normal_length = 10000; //Create a normal vector of reasonable length in order to reduce rounding error. - const Point ba = normal(a - b, normal_length); - const Point bc = normal(c - b, normal_length); - const Vec2d bq = query_point.cast() - b.cast(); - const Vec2d perpendicular = perp(bq); //The query projects to this perpendicular to coordinate 0. - - const double project_a_perpendicular = ba.cast().dot(perpendicular); //Project vertex A on the perpendicular line. - const double project_c_perpendicular = bc.cast().dot(perpendicular); //Project vertex C on the perpendicular line. - if ((project_a_perpendicular > 0.) != (project_c_perpendicular > 0.)) //Query is between A and C on the projection. - { - return project_a_perpendicular > 0.; //Due to the winding order of corner ABC, this means that the query is inside. - } - else //Beyond either A or C, but it could still be inside of the polygon. - { - const double project_a_parallel = ba.cast().dot(bq); //Project not on the perpendicular, but on the original. - const double project_c_parallel = bc.cast().dot(bq); - - //Either: - // * A is to the right of B (project_a_perpendicular > 0) and C is below A (project_c_parallel < project_a_parallel), or - // * A is to the left of B (project_a_perpendicular < 0) and C is above A (project_c_parallel > project_a_parallel). - return (project_c_parallel < project_a_parallel) == (project_a_perpendicular > 0.); - } -} - /*! * Returns the determinant of the 2D matrix defined by the the vectors ab and ap as rows. * diff --git a/src/libslic3r/Arrange/ArrangeImpl.hpp b/src/libslic3r/Arrange/ArrangeImpl.hpp index 6b49496..c89bfa1 100644 --- a/src/libslic3r/Arrange/ArrangeImpl.hpp +++ b/src/libslic3r/Arrange/ArrangeImpl.hpp @@ -254,7 +254,7 @@ void fill_rotations(const Range &items, } } -// An arranger put together to fulfill all the requirements based +// An arranger put together to fulfill all the requirements of QIDISlicer based // on the supplied ArrangeSettings template class DefaultArranger: public Arranger { diff --git a/src/libslic3r/Arrange/ArrangeSettingsDb_AppCfg.cpp b/src/libslic3r/Arrange/ArrangeSettingsDb_AppCfg.cpp index a0ecbf8..18f4fee 100644 --- a/src/libslic3r/Arrange/ArrangeSettingsDb_AppCfg.cpp +++ b/src/libslic3r/Arrange/ArrangeSettingsDb_AppCfg.cpp @@ -1,6 +1,10 @@ #include "ArrangeSettingsDb_AppCfg.hpp" +#include "LocalesUtils.hpp" +#include "libslic3r/AppConfig.hpp" +#include "libslic3r/Arrange/ArrangeSettingsView.hpp" + namespace Slic3r { ArrangeSettingsDb_AppCfg::ArrangeSettingsDb_AppCfg(AppConfig *appcfg) : m_appcfg{appcfg} @@ -41,16 +45,6 @@ void ArrangeSettingsDb_AppCfg::sync() std::string en_rot_sla_str = m_appcfg->get("arrange", "enable_rotation_sla"); - // std::string alignment_fff_str = - // m_appcfg->get("arrange", "alignment_fff"); - - // std::string alignment_fff_seqp_str = - // m_appcfg->get("arrange", "alignment_fff_seq_pring"); - - // std::string alignment_sla_str = - // m_appcfg->get("arrange", "alignment_sla"); - - // Override default alignment and save save/load it to a temporary slot "alignment_xl" std::string alignment_xl_str = m_appcfg->get("arrange", "alignment_xl"); @@ -100,19 +94,10 @@ void ArrangeSettingsDb_AppCfg::sync() if (!en_rot_sla_str.empty()) m_settings_sla.vals.rotations = (en_rot_sla_str == "1" || en_rot_sla_str == "yes"); - - // if (!alignment_sla_str.empty()) - // m_arrange_settings_sla.alignment = std::stoi(alignment_sla_str); - - // if (!alignment_fff_str.empty()) - // m_arrange_settings_fff.alignment = std::stoi(alignment_fff_str); - - // if (!alignment_fff_seqp_str.empty()) - // m_arrange_settings_fff_seq_print.alignment = std::stoi(alignment_fff_seqp_str); else m_settings_sla.vals.rotations = m_settings_sla.defaults.rotations; - // Override default alignment and save save/load it to a temporary slot "alignment_xl" + // Override default alignment and save/load it to a temporary slot "alignment_xl" auto arr_alignment = ArrangeSettingsView::to_xl_pivots(alignment_xl_str) .value_or(m_settings_fff.defaults.xl_align); diff --git a/src/libslic3r/Arrange/ArrangeSettingsDb_AppCfg.hpp b/src/libslic3r/Arrange/ArrangeSettingsDb_AppCfg.hpp index 570510c..3637152 100644 --- a/src/libslic3r/Arrange/ArrangeSettingsDb_AppCfg.hpp +++ b/src/libslic3r/Arrange/ArrangeSettingsDb_AppCfg.hpp @@ -2,11 +2,14 @@ #ifndef ARRANGESETTINGSDB_APPCFG_HPP #define ARRANGESETTINGSDB_APPCFG_HPP +#include + #include "ArrangeSettingsView.hpp" #include "libslic3r/AppConfig.hpp" #include "libslic3r/PrintConfig.hpp" namespace Slic3r { +class AppConfig; class ArrangeSettingsDb_AppCfg: public arr2::ArrangeSettingsDb { @@ -53,6 +56,7 @@ public: explicit ArrangeSettingsDb_AppCfg(AppConfig *appcfg); void sync(); + float get_distance_from_objects() const override { return get_ref(this).d_obj; } float get_distance_from_bed() const override { return get_ref(this).d_bed; } bool is_rotation_enabled() const override { return get_ref(this).rotations; } diff --git a/src/libslic3r/Arrange/ArrangeSettingsView.hpp b/src/libslic3r/Arrange/ArrangeSettingsView.hpp index b5a9d47..3f30633 100644 --- a/src/libslic3r/Arrange/ArrangeSettingsView.hpp +++ b/src/libslic3r/Arrange/ArrangeSettingsView.hpp @@ -6,9 +6,11 @@ #include #include "libslic3r/StaticMap.hpp" + namespace Slic3r { namespace arr2 { using namespace std::string_view_literals; + class ArrangeSettingsView { public: @@ -33,6 +35,7 @@ public: virtual XLPivots get_xl_alignment() const = 0; virtual GeometryHandling get_geometry_handling() const = 0; virtual ArrangeStrategy get_arrange_strategy() const = 0; + static constexpr std::string_view get_label(GeometryHandling v) { constexpr auto STR = std::array{ diff --git a/src/libslic3r/Arrange/Core/Beds.cpp b/src/libslic3r/Arrange/Core/Beds.cpp index 9ef4f68..50bf3e2 100644 --- a/src/libslic3r/Arrange/Core/Beds.cpp +++ b/src/libslic3r/Arrange/Core/Beds.cpp @@ -1,6 +1,12 @@ #include "Beds.hpp" +#include + +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Point.hpp" + namespace Slic3r { namespace arr2 { BoundingBox bounding_box(const InfiniteBed &bed) diff --git a/src/libslic3r/Arrange/Core/Beds.hpp b/src/libslic3r/Arrange/Core/Beds.hpp index 1bcd2ac..3dfd18f 100644 --- a/src/libslic3r/Arrange/Core/Beds.hpp +++ b/src/libslic3r/Arrange/Core/Beds.hpp @@ -2,14 +2,19 @@ #ifndef BEDS_HPP #define BEDS_HPP -#include - #include #include #include #include - #include +#include +#include +#include +#include +#include + +#include "libslic3r/Polygon.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { namespace arr2 { @@ -186,6 +191,7 @@ template<> struct IsRectangular_: public std::true_type {}; template<> struct IsRectangular_: public std::true_type {}; template static constexpr bool IsRectangular = IsRectangular_::value; + } // namespace arr2 inline BoundingBox &bounding_box(BoundingBox &bb) { return bb; } diff --git a/src/libslic3r/Arrange/Core/NFP/EdgeCache.cpp b/src/libslic3r/Arrange/Core/NFP/EdgeCache.cpp index 4a0a78a..2b4b102 100644 --- a/src/libslic3r/Arrange/Core/NFP/EdgeCache.cpp +++ b/src/libslic3r/Arrange/Core/NFP/EdgeCache.cpp @@ -1,6 +1,11 @@ #include "EdgeCache.hpp" + +#include + #include "CircularEdgeIterator.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Line.hpp" namespace Slic3r { namespace arr2 { diff --git a/src/libslic3r/Arrange/Core/NFP/EdgeCache.hpp b/src/libslic3r/Arrange/Core/NFP/EdgeCache.hpp index 98f9d28..df7e1bd 100644 --- a/src/libslic3r/Arrange/Core/NFP/EdgeCache.hpp +++ b/src/libslic3r/Arrange/Core/NFP/EdgeCache.hpp @@ -2,9 +2,18 @@ #ifndef EDGECACHE_HPP #define EDGECACHE_HPP -#include - #include +#include +#include +#include +#include +#include +#include +#include + +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { namespace arr2 { diff --git a/src/libslic3r/Arrange/Core/NFP/Kernels/TMArrangeKernel.hpp b/src/libslic3r/Arrange/Core/NFP/Kernels/TMArrangeKernel.hpp index 379aebc..a74c8a9 100644 --- a/src/libslic3r/Arrange/Core/NFP/Kernels/TMArrangeKernel.hpp +++ b/src/libslic3r/Arrange/Core/NFP/Kernels/TMArrangeKernel.hpp @@ -87,14 +87,12 @@ public: // Will hold the resulting score double score = 0; - // Distinction of cases for the arrangement scene enum e_cases { // This branch is for big items in a mixed (big and small) scene // OR for all items in a small-only scene. BIG_ITEM, - // For small items in a mixed scene. SMALL_ITEM, @@ -165,7 +163,8 @@ public: // The final mix of the score is the balance between the // distance from the full pile center, the pack density and // the alignment with the neighbors - // Let the density matter more when fewer objects remain + + // Let the density matter more when fewer objects remain score = 0.6 * dist + 0.1 * alignment_score + (1.0 - R) * (0.3 * dist) + R * 0.3 * alignment_score; break; diff --git a/src/libslic3r/Arrange/Core/NFP/NFP.cpp b/src/libslic3r/Arrange/Core/NFP/NFP.cpp index e3f4205..b1d16b7 100644 --- a/src/libslic3r/Arrange/Core/NFP/NFP.cpp +++ b/src/libslic3r/Arrange/Core/NFP/NFP.cpp @@ -3,18 +3,33 @@ #define NFP_CPP #include "NFP.hpp" -#include "CircularEdgeIterator.hpp" +#include "CircularEdgeIterator.hpp" #include "NFPConcave_Tesselate.hpp" +#include "libslic3r/Arrange/Core/Beds.hpp" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/libslic3r.h" #if !defined(_MSC_VER) && defined(__SIZEOF_INT128__) && !defined(__APPLE__) namespace Slic3r { using LargeInt = __int128; } #else #include + namespace Slic3r { using LargeInt = boost::multiprecision::int128_t; } #endif #include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace Slic3r { diff --git a/src/libslic3r/Arrange/Core/NFP/NFP.hpp b/src/libslic3r/Arrange/Core/NFP/NFP.hpp index 9c39766..eabe684 100644 --- a/src/libslic3r/Arrange/Core/NFP/NFP.hpp +++ b/src/libslic3r/Arrange/Core/NFP/NFP.hpp @@ -4,6 +4,12 @@ #include #include +#include +#include +#include + +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" namespace Slic3r { diff --git a/src/libslic3r/Arrange/Core/NFP/NFPConcave_CGAL.cpp b/src/libslic3r/Arrange/Core/NFP/NFPConcave_CGAL.cpp index 283d761..e438115 100644 --- a/src/libslic3r/Arrange/Core/NFP/NFPConcave_CGAL.cpp +++ b/src/libslic3r/Arrange/Core/NFP/NFPConcave_CGAL.cpp @@ -1,14 +1,20 @@ -#include "NFP.hpp" -#include "NFPConcave_CGAL.hpp" - #include #include #include #include #include +#include +#include +#include +#include +#include "NFP.hpp" +#include "NFPConcave_CGAL.hpp" #include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { diff --git a/src/libslic3r/Arrange/Core/NFP/NFPConcave_CGAL.hpp b/src/libslic3r/Arrange/Core/NFP/NFPConcave_CGAL.hpp index d9e2cbc..0d8bc53 100644 --- a/src/libslic3r/Arrange/Core/NFP/NFPConcave_CGAL.hpp +++ b/src/libslic3r/Arrange/Core/NFP/NFPConcave_CGAL.hpp @@ -4,6 +4,8 @@ #include +#include "libslic3r/Polygon.hpp" + namespace Slic3r { Polygons convex_decomposition_cgal(const Polygon &expoly); diff --git a/src/libslic3r/Arrange/Core/NFP/NFPConcave_Tesselate.cpp b/src/libslic3r/Arrange/Core/NFP/NFPConcave_Tesselate.cpp index 7e1961f..9b2c959 100644 --- a/src/libslic3r/Arrange/Core/NFP/NFPConcave_Tesselate.cpp +++ b/src/libslic3r/Arrange/Core/NFP/NFPConcave_Tesselate.cpp @@ -3,8 +3,15 @@ #include #include +#include +#include +#include +#include #include "NFP.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { diff --git a/src/libslic3r/Arrange/Core/NFP/NFPConcave_Tesselate.hpp b/src/libslic3r/Arrange/Core/NFP/NFPConcave_Tesselate.hpp index ef20c06..ea12806 100644 --- a/src/libslic3r/Arrange/Core/NFP/NFPConcave_Tesselate.hpp +++ b/src/libslic3r/Arrange/Core/NFP/NFPConcave_Tesselate.hpp @@ -4,6 +4,8 @@ #include +#include "libslic3r/Polygon.hpp" + namespace Slic3r { Polygons convex_decomposition_tess(const Polygon &expoly); diff --git a/src/libslic3r/Arrange/Items/ArrangeItem.cpp b/src/libslic3r/Arrange/Items/ArrangeItem.cpp index 5342acb..043aada 100644 --- a/src/libslic3r/Arrange/Items/ArrangeItem.cpp +++ b/src/libslic3r/Arrange/Items/ArrangeItem.cpp @@ -1,13 +1,13 @@ #include "ArrangeItem.hpp" +#include + #include "libslic3r/Arrange/Core/NFP/NFPConcave_Tesselate.hpp" - -#include "libslic3r/Arrange/ArrangeImpl.hpp" -#include "libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp" -#include "libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp" -#include "libslic3r/Arrange/Tasks/MultiplySelectionTaskImpl.hpp" - +#include "libslic3r/Arrange/ArrangeImpl.hpp" // IWYU pragma: keep +#include "libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp" // IWYU pragma: keep +#include "libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp" // IWYU pragma: keep +#include "libslic3r/Arrange/Tasks/MultiplySelectionTaskImpl.hpp" // IWYU pragma: keep #include "libslic3r/Geometry/ConvexHull.hpp" namespace Slic3r { namespace arr2 { diff --git a/src/libslic3r/Arrange/Items/ArrangeItem.hpp b/src/libslic3r/Arrange/Items/ArrangeItem.hpp index 9a8604d..b16bb09 100644 --- a/src/libslic3r/Arrange/Items/ArrangeItem.hpp +++ b/src/libslic3r/Arrange/Items/ArrangeItem.hpp @@ -2,29 +2,42 @@ #ifndef ARRANGEITEM_HPP #define ARRANGEITEM_HPP -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "libslic3r/ExPolygon.hpp" #include "libslic3r/BoundingBox.hpp" #include "libslic3r/AnyPtr.hpp" - #include "libslic3r/Arrange/Core/PackingContext.hpp" #include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp" #include "libslic3r/Arrange/Core/NFP/NFP.hpp" - #include "libslic3r/Arrange/Items/MutableItemTraits.hpp" - #include "libslic3r/Arrange/Arrange.hpp" #include "libslic3r/Arrange/Tasks/ArrangeTask.hpp" #include "libslic3r/Arrange/Tasks/FillBedTask.hpp" #include "libslic3r/Arrange/Tasks/MultiplySelectionTask.hpp" - #include "libslic3r/Arrange/Items/ArbitraryDataStore.hpp" - -#include +#include "libslic3r/Arrange/Core/ArrangeBase.hpp" +#include "libslic3r/Arrange/Core/ArrangeItemTraits.hpp" +#include "libslic3r/Arrange/Core/DataStoreTraits.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { namespace arr2 { +struct InfiniteBed; inline bool check_polygons_are_convex(const Polygons &pp) { return std::all_of(pp.begin(), pp.end(), [](const Polygon &p) { diff --git a/src/libslic3r/Arrange/Items/SimpleArrangeItem.cpp b/src/libslic3r/Arrange/Items/SimpleArrangeItem.cpp index 2769758..ac3ebf7 100644 --- a/src/libslic3r/Arrange/Items/SimpleArrangeItem.cpp +++ b/src/libslic3r/Arrange/Items/SimpleArrangeItem.cpp @@ -1,9 +1,9 @@ #include "SimpleArrangeItem.hpp" -#include "libslic3r/Arrange/ArrangeImpl.hpp" -#include "libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp" -#include "libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp" -#include "libslic3r/Arrange/Tasks/MultiplySelectionTaskImpl.hpp" +#include "libslic3r/Arrange/ArrangeImpl.hpp" // IWYU pragma: keep +#include "libslic3r/Arrange/Tasks/ArrangeTaskImpl.hpp" // IWYU pragma: keep +#include "libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp" // IWYU pragma: keep +#include "libslic3r/Arrange/Tasks/MultiplySelectionTaskImpl.hpp" // IWYU pragma: keep namespace Slic3r { namespace arr2 { diff --git a/src/libslic3r/Arrange/Items/SimpleArrangeItem.hpp b/src/libslic3r/Arrange/Items/SimpleArrangeItem.hpp index 988072b..7861a97 100644 --- a/src/libslic3r/Arrange/Items/SimpleArrangeItem.hpp +++ b/src/libslic3r/Arrange/Items/SimpleArrangeItem.hpp @@ -2,22 +2,30 @@ #ifndef SIMPLEARRANGEITEM_HPP #define SIMPLEARRANGEITEM_HPP -#include "libslic3r/Arrange/Core/PackingContext.hpp" +#include +#include +#include +#include +#include +#include "libslic3r/Arrange/Core/PackingContext.hpp" #include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp" #include "libslic3r/Arrange/Core/NFP/NFP.hpp" - #include "libslic3r/Arrange/Arrange.hpp" #include "libslic3r/Arrange/Tasks/ArrangeTask.hpp" #include "libslic3r/Arrange/Tasks/FillBedTask.hpp" #include "libslic3r/Arrange/Tasks/MultiplySelectionTask.hpp" - #include "libslic3r/Polygon.hpp" #include "libslic3r/Geometry/ConvexHull.hpp" - #include "MutableItemTraits.hpp" +#include "libslic3r/Arrange/Core/ArrangeItemTraits.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/ObjectID.hpp" +#include "libslic3r/Point.hpp" namespace Slic3r { namespace arr2 { +struct InfiniteBed; class SimpleArrangeItem { Polygon m_shape; diff --git a/src/libslic3r/Arrange/Scene.hpp b/src/libslic3r/Arrange/Scene.hpp index 31cbf63..1e3fd99 100644 --- a/src/libslic3r/Arrange/Scene.hpp +++ b/src/libslic3r/Arrange/Scene.hpp @@ -2,13 +2,30 @@ #ifndef ARR2_SCENE_HPP #define ARR2_SCENE_HPP +#include +#include +#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #include "libslic3r/ObjectID.hpp" #include "libslic3r/AnyPtr.hpp" #include "libslic3r/Arrange/ArrangeSettingsView.hpp" #include "libslic3r/Arrange/SegmentedRectangleBed.hpp" +#include "libslic3r/Arrange/Core/Beds.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { namespace arr2 { @@ -261,8 +278,8 @@ class Scene ExtendedBed m_bed; public: - // Can only be built from an rvalue SceneBuilder, as it's content will - // potentially be moved to the constructed ArrangeScene object + // Scene can only be built from an rvalue SceneBuilder whose content will + // potentially be moved to the constructed Scene object. template explicit Scene(SceneBuilderBase &&bld) { @@ -284,8 +301,10 @@ public: std::vector selected_ids() const; }; +// Get all the ObjectIDs of Arrangeables which are in selected state std::set selected_geometry_ids(const Scene &sc); +// A dummy, empty ArrangeableModel for testing and as placeholder to avoiod using nullptr class EmptyArrangeableModel: public ArrangeableModel { public: @@ -305,12 +324,20 @@ void SceneBuilderBase::build_scene(Scene &sc) && if (!m_settings) m_settings = std::make_unique(); + // Apply the bed minimum distance by making the original bed smaller + // and arranging on this smaller bed. coord_t inset = std::max(scaled(m_settings->get_distance_from_bed()), m_skirt_offs + m_brims_offs); + // Objects have also a minimum distance from each other implemented + // as inflation applied to object outlines. This object distance + // does not apply to the bed, so the bed is inflated by this amount + // to compensate. coord_t md = scaled(m_settings->get_distance_from_objects()); md = md / 2 - inset; + // Applying the final bed with the corrected dimensions to account + // for safety distances visit_bed([md](auto &rawbed) { rawbed = offset(rawbed, md); }, m_bed); sc.m_settings = std::move(m_settings); diff --git a/src/libslic3r/Arrange/SceneBuilder.cpp b/src/libslic3r/Arrange/SceneBuilder.cpp index f84ab80..8656ef9 100644 --- a/src/libslic3r/Arrange/SceneBuilder.cpp +++ b/src/libslic3r/Arrange/SceneBuilder.cpp @@ -4,12 +4,25 @@ #include "SceneBuilder.hpp" +#include +#include +#include +#include +#include + #include "libslic3r/Model.hpp" #include "libslic3r/Print.hpp" #include "libslic3r/SLAPrint.hpp" - -#include "Core/ArrangeItemTraits.hpp" -#include "Geometry/ConvexHull.hpp" +#include "libslic3r/Arrange/Core/ArrangeItemTraits.hpp" +#include "libslic3r/Geometry/ConvexHull.hpp" +#include "libslic3r/Arrange/Scene.hpp" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/Geometry.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/SLA/Pad.hpp" +#include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/TriangleMeshSlicer.hpp" +#include "libslic3r/Arrange/Core/Beds.hpp" namespace Slic3r { namespace arr2 { @@ -179,6 +192,7 @@ void SceneBuilder::build_scene(Scene &sc) && } } + // Call the parent class implementation of build_scene to finish constructing of the scene std::move(*this).SceneBuilderBase::build_scene(sc); } diff --git a/src/libslic3r/Arrange/SceneBuilder.hpp b/src/libslic3r/Arrange/SceneBuilder.hpp index 2ee9b69..77cbc3d 100644 --- a/src/libslic3r/Arrange/SceneBuilder.hpp +++ b/src/libslic3r/Arrange/SceneBuilder.hpp @@ -2,9 +2,29 @@ #ifndef SCENEBUILDER_HPP #define SCENEBUILDER_HPP -#include "Scene.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Scene.hpp" #include "Core/ArrangeItemTraits.hpp" +#include "libslic3r/AnyPtr.hpp" +#include "libslic3r/Arrange/Core/Beds.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/ObjectID.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { @@ -21,6 +41,9 @@ namespace arr2 { using SelectionPredicate = std::function; +// Objects implementing this interface should know how to present the wipe tower +// as an Arrangeable. If the wipe tower is not present, the overloads of visit() shouldn't do +// anything. (See MissingWipeTowerHandler) class WipeTowerHandler { public: @@ -31,6 +54,9 @@ public: virtual void set_selection_predicate(SelectionPredicate pred) = 0; }; +// Something that has a bounding box and can be displaced by arbitrary 2D offset and rotated +// by arbitrary rotation. Used as targets to place on virtual beds. Normally this would correspond +// to ModelInstances but the same functionality was needed in more contexts. class VBedPlaceable { public: virtual ~VBedPlaceable() = default; @@ -39,11 +65,11 @@ public: virtual void displace(const Vec2d &transl, double rot) = 0; }; -// An interface to handle virtual beds for ModelInstances. A ModelInstance +// An interface to handle virtual beds for VBedPlaceable objects. A VBedPlaceable // may be assigned to a logical bed identified by an integer index value (zero -// is the actual physical bed). The ModelInstance may still be outside of it's +// is the actual physical bed). The VBedPlaceable may still be outside of it's // bed, regardless of being assigned to it. The handler object should provide -// means to read the assigned bed index of a ModelInstance, to assign a +// means to read the assigned bed index of a VBedPlaceable, to assign a // different bed index and to provide a trafo that maps it to the physical bed // given a logical bed index. The reason is that the arrangement expects items // to be in the coordinate system of the physical bed. @@ -52,15 +78,15 @@ class VirtualBedHandler public: virtual ~VirtualBedHandler() = default; - // Returns the bed index on which the given ModelInstance is sitting. + // Returns the bed index on which the given VBedPlaceable is sitting. virtual int get_bed_index(const VBedPlaceable &obj) const = 0; - // The returned trafo can be used to move the outline of the ModelInstance + // The returned trafo can be used to displace the VBedPlaceable // to the coordinate system of the physical bed, should that differ from // the coordinate space of a logical bed. virtual Transform3d get_physical_bed_trafo(int bed_index) const = 0; - // Assign the ModelInstance to the given bed index. Note that this + // Assign the VBedPlaceable to the given bed index. Note that this // method can return false, indicating that the given bed is not available // to be occupied (e.g. the handler has a limited amount of logical bed) virtual bool assign_bed(VBedPlaceable &obj, int bed_idx) = 0; @@ -73,6 +99,7 @@ public: static std::unique_ptr create(const ExtendedBed &bed); }; +// Holds the info about which object (ID) is selected/unselected class SelectionMask { public: @@ -111,6 +138,7 @@ public: bool is_wipe_tower() const override { return m_wp; } }; +// Common part of any Arrangeable which is a wipe tower struct ArrangeableWipeTowerBase: public Arrangeable { ObjectID oid; @@ -163,13 +191,15 @@ class SceneBuilder; struct InstPos { size_t obj_idx = 0, inst_idx = 0; }; +// Implementing ArrangeableModel interface for QIDISlicer's Model, ModelObject, ModelInstance data +// hierarchy class ArrangeableSlicerModel: public ArrangeableModel { protected: AnyPtr m_model; - AnyPtr m_wth; - AnyPtr m_vbed_handler; - AnyPtr m_selmask; + AnyPtr m_wth; // Determines how wipe tower is handled + AnyPtr m_vbed_handler; // Determines how virtual beds are handled + AnyPtr m_selmask; // Determines which objects are selected/unselected private: friend class SceneBuilder; @@ -196,6 +226,7 @@ public: const Model &get_model() const { return *m_model; } }; +// SceneBuilder implementation for QIDISlicer API. class SceneBuilder: public SceneBuilderBase { protected: @@ -409,6 +440,7 @@ public: } }; +// Arrangeable interface implementation for ModelInstances template class ArrangeableModelInstance : public Arrangeable, VBedPlaceable { @@ -451,6 +483,7 @@ public: extern template class ArrangeableModelInstance; extern template class ArrangeableModelInstance; +// Arrangeable implementation for an SLAPrintObject to be able to arrange with the supports and pad class ArrangeableSLAPrintObject : public Arrangeable { const SLAPrintObject *m_po; @@ -488,6 +521,7 @@ public: int priority() const override { return m_arrbl->priority(); } }; +// Extension of ArrangeableSlicerModel for SLA class ArrangeableSLAPrint : public ArrangeableSlicerModel { const SLAPrint *m_slaprint; @@ -619,6 +653,8 @@ public: extern template class ArrangeableFullModel; extern template class ArrangeableFullModel; +// An implementation of the ArrangeableModel to be used for the full model 'duplicate' feature +// accessible from CLI class DuplicableModel: public ArrangeableModel { AnyPtr m_model; AnyPtr m_vbh; diff --git a/src/libslic3r/Arrange/SegmentedRectangleBed.hpp b/src/libslic3r/Arrange/SegmentedRectangleBed.hpp index afbac63..0fca911 100644 --- a/src/libslic3r/Arrange/SegmentedRectangleBed.hpp +++ b/src/libslic3r/Arrange/SegmentedRectangleBed.hpp @@ -104,6 +104,7 @@ ExPolygons to_expolygons(const SegmentedRectangleBed &bed) template struct IsRectangular_, void>> : public std::true_type {}; + }} // namespace Slic3r::arr2 #endif // SEGMENTEDRECTANGLEBED_HPP diff --git a/src/libslic3r/Arrange/Tasks/FillBedTask.hpp b/src/libslic3r/Arrange/Tasks/FillBedTask.hpp index 64e3437..7a857b6 100644 --- a/src/libslic3r/Arrange/Tasks/FillBedTask.hpp +++ b/src/libslic3r/Arrange/Tasks/FillBedTask.hpp @@ -17,7 +17,10 @@ struct FillBedTask: public ArrangeTaskBase std::vector selected, unselected; + // For workaround regarding "holes" when filling the bed with the same + // item's copies std::vector selected_fillers; + ArrangeSettings settings; ExtendedBed bed; size_t selected_existing_count = 0; diff --git a/src/libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp b/src/libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp index 2aeceb8..5cdcfef 100644 --- a/src/libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp +++ b/src/libslic3r/Arrange/Tasks/FillBedTaskImpl.hpp @@ -4,7 +4,7 @@ #include "FillBedTask.hpp" -#include "Arrange/Core/NFP/NFPArrangeItemTraits.hpp" +#include "libslic3r/Arrange/Core/NFP/NFPArrangeItemTraits.hpp" #include @@ -71,8 +71,7 @@ void extract(FillBedTask &task, // Workaround for missing items when arranging the same geometry only: // Injecting a number of items but with slightly shrinked shape, so that - // they can fill the emerging holes. Priority is set to lowest so that - // these filler items will only be inserted as the last ones. + // they can fill the emerging holes. ArrItem prototype_item_shrinked; scene.model().visit_arrangeable(selected_ids.front(), [&prototype_item_shrinked, &itm_conv](const Arrangeable &arrbl) { @@ -101,8 +100,6 @@ void extract(FillBedTask &task, } }; - // Set the lowest priority to the shrinked prototype (hole filler) item - scene.model().for_each_arrangeable(collect_task_items); int needed_items = calculate_items_needed_to_fill_bed(task.bed, @@ -185,6 +182,7 @@ std::unique_ptr FillBedTask::process_native( } arranger->arrange(selected_fillers, unsel_cpy, bed, FillBedCtl{ctl, *this}); + auto arranged_range = Range{selected.begin(), selected.begin() + selected_existing_count}; @@ -200,6 +198,7 @@ std::unique_ptr FillBedTask::process_native( for (auto &itm : selected_fillers) if (get_bed_index(itm) == PhysicalBedId) result->add_new_item(itm); + return result; } diff --git a/src/libslic3r/BlacklistedLibraryCheck.cpp b/src/libslic3r/BlacklistedLibraryCheck.cpp index 938f542..04ecb14 100644 --- a/src/libslic3r/BlacklistedLibraryCheck.cpp +++ b/src/libslic3r/BlacklistedLibraryCheck.cpp @@ -1,8 +1,5 @@ #include "BlacklistedLibraryCheck.hpp" -#include -#include - #ifdef WIN32 #include # endif //WIN32 diff --git a/src/libslic3r/BoundingBox.cpp b/src/libslic3r/BoundingBox.cpp index 7e88eb9..e6bce23 100644 --- a/src/libslic3r/BoundingBox.cpp +++ b/src/libslic3r/BoundingBox.cpp @@ -1,8 +1,10 @@ #include "BoundingBox.hpp" -#include -#include -#include +#include + +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { @@ -48,6 +50,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 b0bb997..17f8cb2 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -1,12 +1,20 @@ #ifndef slic3r_BoundingBox_hpp_ #define slic3r_BoundingBox_hpp_ +#include +#include +#include +#include +#include +#include + #include "libslic3r.h" #include "Exception.hpp" #include "Point.hpp" #include "Polygon.hpp" namespace Slic3r { +class BoundingBox; template > class BoundingBoxBase @@ -31,12 +39,7 @@ public: : BoundingBoxBase(points.begin(), points.end()) {} - void reset() - { - this->defined = false; - this->min = PointType::Zero(); - this->max = PointType::Zero(); - } + void reset() { this->defined = false; this->min = PointType::Zero(); this->max = PointType::Zero(); } // B66 Polygon polygon(bool is_scaled = false) const { @@ -164,6 +167,7 @@ public: return this->min.x() < other.max.x() && this->max.x() > other.min.x() && this->min.y() < other.max.y() && this->max.y() > other.min.y() && this->min.z() < other.max.z() && this->max.z() > other.min.z(); } + // Shares some boundary. bool shares_boundary(const BoundingBox3Base& other) const { return is_approx(this->min.x(), other.max.x()) || @@ -228,7 +232,9 @@ public: BoundingBox(const BoundingBoxBase &bb): BoundingBox(bb.min, bb.max) {} 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); }; @@ -339,6 +345,23 @@ inline double bbox_point_distance_squared(const BoundingBox &bbox, const Point & coord_t(0)); } +// Minimum distance between two Bounding boxes. +// Returns zero when Bounding boxes overlap. +inline double bbox_bbox_distance(const BoundingBox &first_bbox, const BoundingBox &second_bbox) { + if (first_bbox.overlap(second_bbox)) + return 0.; + + double bboxes_distance_squared = 0.; + + for (size_t axis = 0; axis < 2; ++axis) { + const coord_t axis_distance = std::max(std::max(0, first_bbox.min[axis] - second_bbox.max[axis]), + second_bbox.min[axis] - first_bbox.max[axis]); + bboxes_distance_squared += Slic3r::sqr(static_cast(axis_distance)); + } + + return std::sqrt(bboxes_distance_squared); +} + template BoundingBoxBase> to_2d(const BoundingBox3Base> &bb) { diff --git a/src/libslic3r/BranchingTree/BranchingTree.cpp b/src/libslic3r/BranchingTree/BranchingTree.cpp index 9826131..8aca17a 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.cpp +++ b/src/libslic3r/BranchingTree/BranchingTree.cpp @@ -1,11 +1,14 @@ #include "BranchingTree.hpp" -#include "PointCloud.hpp" -#include #include #include +#include +#include "PointCloud.hpp" #include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/BoundingBox.hpp" + +struct indexed_triangle_set; namespace Slic3r { namespace branchingtree { diff --git a/src/libslic3r/BranchingTree/BranchingTree.hpp b/src/libslic3r/BranchingTree/BranchingTree.hpp index 7e59e6f..5c7d080 100644 --- a/src/libslic3r/BranchingTree/BranchingTree.hpp +++ b/src/libslic3r/BranchingTree/BranchingTree.hpp @@ -3,8 +3,15 @@ // For indexed_triangle_set #include +#include +#include +#include #include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/libslic3r.h" + +struct indexed_triangle_set; namespace Slic3r { namespace branchingtree { diff --git a/src/libslic3r/BranchingTree/PointCloud.cpp b/src/libslic3r/BranchingTree/PointCloud.cpp index 1497f08..dba3a48 100644 --- a/src/libslic3r/BranchingTree/PointCloud.cpp +++ b/src/libslic3r/BranchingTree/PointCloud.cpp @@ -1,9 +1,14 @@ #include "PointCloud.hpp" +#include // IWYU pragma: keep +#include +#include + #include "libslic3r/Tesselate.hpp" #include "libslic3r/SLA/SupportTreeUtils.hpp" - -#include +#include "admesh/stl.h" +#include "libslic3r/BranchingTree/BranchingTree.hpp" +#include "libslic3r/SLA/Pad.hpp" namespace Slic3r { namespace branchingtree { diff --git a/src/libslic3r/BranchingTree/PointCloud.hpp b/src/libslic3r/BranchingTree/PointCloud.hpp index 03b935f..2f8d2d4 100644 --- a/src/libslic3r/BranchingTree/PointCloud.hpp +++ b/src/libslic3r/BranchingTree/PointCloud.hpp @@ -1,15 +1,30 @@ #ifndef POINTCLOUD_HPP #define POINTCLOUD_HPP +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include #include "BranchingTree.hpp" - //#include "libslic3r/Execution/Execution.hpp" #include "libslic3r/MutablePriorityQueue.hpp" - #include "libslic3r/BoostAdapter.hpp" #include "boost/geometry/index/rtree.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/libslic3r.h" + +struct indexed_triangle_set; namespace Slic3r { namespace branchingtree { diff --git a/src/libslic3r/BridgeDetector.cpp b/src/libslic3r/BridgeDetector.cpp index 03d671d..90fbfcc 100644 --- a/src/libslic3r/BridgeDetector.cpp +++ b/src/libslic3r/BridgeDetector.cpp @@ -1,7 +1,17 @@ #include "BridgeDetector.hpp" + +#include +#include + #include "ClipperUtils.hpp" #include "Geometry.hpp" -#include +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/Polyline.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { diff --git a/src/libslic3r/BridgeDetector.hpp b/src/libslic3r/BridgeDetector.hpp index cb18273..c5ba097 100644 --- a/src/libslic3r/BridgeDetector.hpp +++ b/src/libslic3r/BridgeDetector.hpp @@ -1,6 +1,15 @@ #ifndef slic3r_BridgeDetector_hpp_ #define slic3r_BridgeDetector_hpp_ +#include +#include +#include +#include +#include +#include +#include +#include + #include "ClipperUtils.hpp" #include "Line.hpp" #include "Point.hpp" @@ -9,10 +18,6 @@ #include "PrincipalComponents2D.hpp" #include "libslic3r.h" #include "ExPolygon.hpp" -#include -#include -#include -#include namespace Slic3r { diff --git a/src/libslic3r/Brim.cpp b/src/libslic3r/Brim.cpp index 672bb80..1f90ed5 100644 --- a/src/libslic3r/Brim.cpp +++ b/src/libslic3r/Brim.cpp @@ -1,23 +1,39 @@ -#include "clipper/clipper_z.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "clipper/clipper_z.hpp" #include "ClipperUtils.hpp" #include "EdgeGrid.hpp" #include "Layer.hpp" #include "Print.hpp" #include "ShortestPath.hpp" #include "libslic3r.h" - -#include -#include -#include -#include - -#include -#include - -#ifndef NDEBUG - // #define BRIM_DEBUG_TO_SVG -#endif +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/ExtrusionEntity.hpp" +#include "libslic3r/ExtrusionEntityCollection.hpp" +#include "libslic3r/ExtrusionRole.hpp" +#include "libslic3r/Flow.hpp" +#include "libslic3r/Geometry.hpp" +#include "libslic3r/LayerRegion.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/Polyline.hpp" +#include "libslic3r/PrintBase.hpp" +#include "libslic3r/PrintConfig.hpp" #if defined(BRIM_DEBUG_TO_SVG) #include "SVG.hpp" diff --git a/src/libslic3r/BuildVolume.cpp b/src/libslic3r/BuildVolume.cpp index e50e056..e6947d1 100644 --- a/src/libslic3r/BuildVolume.cpp +++ b/src/libslic3r/BuildVolume.cpp @@ -1,16 +1,26 @@ #include "BuildVolume.hpp" -#include "ClipperUtils.hpp" -#include "Geometry/ConvexHull.hpp" -#include "GCode/GCodeProcessor.hpp" -#include "Point.hpp" #include +#include +#include +#include +#include +#include + +#include "ClipperUtils.hpp" +#include "Geometry/ConvexHull.hpp" +#include "libslic3r/GCode/GCodeProcessor.hpp" +#include "Point.hpp" +#include "admesh/stl.h" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/ExtrusionRole.hpp" +#include "libslic3r/Geometry/Circle.hpp" +#include "libslic3r/Polygon.hpp" namespace Slic3r { //B52 -BuildVolume::BuildVolume(const std::vector &bed_shape, const double max_print_height, const std::vector &exclude_bed_shape) - : m_bed_shape(bed_shape), m_max_print_height(max_print_height),m_exclude_bed_shape(exclude_bed_shape) +BuildVolume::BuildVolume(const std::vector &bed_shape, const double max_print_height, const std::vector &exclude_bed_shape): m_bed_shape(bed_shape), m_max_print_height(max_print_height), m_exclude_bed_shape(exclude_bed_shape) { assert(max_print_height >= 0); @@ -398,7 +408,7 @@ bool BuildVolume::all_paths_inside(const GCodeProcessorResult& paths, const Boun const Vec2f c = unscaled(m_circle.center); const float r = unscaled(m_circle.radius) + epsilon; const float r2 = sqr(r); - return m_max_print_height == 0.0 ? + return m_max_print_height == 0.0 ? std::all_of(paths.moves.begin(), paths.moves.end(), [move_valid, c, r2](const GCodeProcessorResult::MoveVertex &move) { return ! move_valid(move) || (to_2d(move.position) - c).squaredNorm() <= r2; }) : std::all_of(paths.moves.begin(), paths.moves.end(), [move_valid, c, r2, z = m_max_print_height + epsilon](const GCodeProcessorResult::MoveVertex& move) @@ -429,40 +439,6 @@ inline bool all_inside_vertices_normals_interleaved(const std::vector &pa return true; } -bool BuildVolume::all_paths_inside_vertices_and_normals_interleaved(const std::vector& paths, const Eigen::AlignedBox& paths_bbox, bool ignore_bottom) const -{ - assert(paths.size() % 6 == 0); - static constexpr const double epsilon = BedEpsilon; - switch (m_type) { - case Type::Rectangle: - { - BoundingBox3Base build_volume = this->bounding_volume().inflated(epsilon); - if (m_max_print_height == 0.0) - build_volume.max.z() = std::numeric_limits::max(); - if (ignore_bottom) - build_volume.min.z() = -std::numeric_limits::max(); - return build_volume.contains(paths_bbox.min().cast()) && build_volume.contains(paths_bbox.max().cast()); - } - case Type::Circle: - { - const Vec2f c = unscaled(m_circle.center); - const float r = unscaled(m_circle.radius) + float(epsilon); - const float r2 = sqr(r); - return m_max_print_height == 0.0 ? - all_inside_vertices_normals_interleaved(paths, [c, r2](Vec3f p) { return (to_2d(p) - c).squaredNorm() <= r2; }) : - all_inside_vertices_normals_interleaved(paths, [c, r2, z = m_max_print_height + epsilon](Vec3f p) { return (to_2d(p) - c).squaredNorm() <= r2 && p.z() <= z; }); - } - case Type::Convex: - //FIXME doing test on convex hull until we learn to do test on non-convex polygons efficiently. - case Type::Custom: - return m_max_print_height == 0.0 ? - all_inside_vertices_normals_interleaved(paths, [this](Vec3f p) { return Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_bed, to_2d(p).cast()); }) : - all_inside_vertices_normals_interleaved(paths, [this, z = m_max_print_height + epsilon](Vec3f p) { return Geometry::inside_convex_polygon(m_top_bottom_convex_hull_decomposition_bed, to_2d(p).cast()) && p.z() <= z; }); - default: - return true; - } -} - std::string_view BuildVolume::type_name(Type type) { using namespace std::literals; diff --git a/src/libslic3r/BuildVolume.hpp b/src/libslic3r/BuildVolume.hpp index b8fd59d..221c757 100644 --- a/src/libslic3r/BuildVolume.hpp +++ b/src/libslic3r/BuildVolume.hpp @@ -1,13 +1,18 @@ #ifndef slic3r_BuildVolume_hpp_ #define slic3r_BuildVolume_hpp_ +#include +#include +#include +#include + #include "Point.hpp" #include "Geometry/Circle.hpp" #include "Polygon.hpp" #include "BoundingBox.hpp" -#include +#include "libslic3r/libslic3r.h" -#include +struct indexed_triangle_set; namespace Slic3r { @@ -96,9 +101,6 @@ public: // Called on final G-code paths. //FIXME The test does not take the thickness of the extrudates into account! bool all_paths_inside(const GCodeProcessorResult& paths, const BoundingBoxf3& paths_bbox, bool ignore_bottom = true) const; - // Called on initial G-code preview on OpenGL vertex buffer interleaved normals and vertices. - bool all_paths_inside_vertices_and_normals_interleaved(const std::vector& paths, const Eigen::AlignedBox& bbox, bool ignore_bottom = true) const; - const std::pair, std::vector>& top_bottom_convex_hull_decomposition_scene() const { return m_top_bottom_convex_hull_decomposition_scene; } const std::pair, std::vector>& top_bottom_convex_hull_decomposition_bed() const { return m_top_bottom_convex_hull_decomposition_bed; } diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 250860a..21b280f 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -18,6 +18,7 @@ find_package(LibBGCode REQUIRED COMPONENTS Convert) slic3r_remap_configs(LibBGCode::bgcode_core RelWithDebInfo Release) slic3r_remap_configs(LibBGCode::bgcode_binarize RelWithDebInfo Release) slic3r_remap_configs(LibBGCode::bgcode_convert RelWithDebInfo Release) + set(SLIC3R_SOURCES pchheader.cpp pchheader.hpp @@ -145,6 +146,8 @@ set(SLIC3R_SOURCES Format/SVG.cpp Format/SLAArchiveFormatRegistry.hpp Format/SLAArchiveFormatRegistry.cpp + Format/PrintRequest.cpp + Format/PrintRequest.cpp GCode/ThumbnailData.cpp GCode/ThumbnailData.hpp GCode/Thumbnails.cpp @@ -173,6 +176,24 @@ set(SLIC3R_SOURCES GCode/SpiralVase.hpp GCode/SeamPlacer.cpp GCode/SeamPlacer.hpp + GCode/SeamChoice.cpp + GCode/SeamChoice.hpp + GCode/SeamPerimeters.cpp + GCode/SeamPerimeters.hpp + GCode/SeamShells.cpp + GCode/SeamShells.hpp + GCode/SeamGeometry.cpp + GCode/SeamGeometry.hpp + GCode/SeamAligned.cpp + GCode/SeamAligned.hpp + GCode/SeamRear.cpp + GCode/SeamRear.hpp + GCode/SeamRandom.cpp + GCode/SeamRandom.hpp + GCode/SeamPainting.cpp + GCode/SeamPainting.hpp + GCode/ModelVisibility.cpp + GCode/ModelVisibility.hpp GCode/SmoothPath.cpp GCode/SmoothPath.hpp GCode/ToolOrdering.cpp @@ -189,6 +210,8 @@ set(SLIC3R_SOURCES GCode/AvoidCrossingPerimeters.hpp GCode/Travels.cpp GCode/Travels.hpp + GCode/ExtrusionOrder.cpp + GCode/ExtrusionOrder.hpp GCode.cpp GCode.hpp GCodeReader.cpp @@ -213,7 +236,6 @@ set(SLIC3R_SOURCES Geometry/VoronoiUtils.hpp Geometry/VoronoiUtils.cpp Geometry/VoronoiVisualUtils.hpp - Int128.hpp JumpPointSearch.cpp JumpPointSearch.hpp KDTreeIndirect.hpp @@ -227,8 +249,6 @@ set(SLIC3R_SOURCES Line.hpp BlacklistedLibraryCheck.cpp BlacklistedLibraryCheck.hpp - LocalesUtils.cpp - LocalesUtils.hpp CutUtils.cpp CutUtils.hpp Model.cpp @@ -459,11 +479,12 @@ set(SLIC3R_SOURCES SLA/DefaultSupportTree.cpp SLA/BranchingTreeSLA.hpp SLA/BranchingTreeSLA.cpp + SLA/ZCorrection.hpp + SLA/ZCorrection.cpp BranchingTree/BranchingTree.cpp BranchingTree/BranchingTree.hpp BranchingTree/PointCloud.cpp BranchingTree/PointCloud.hpp - Arachne/BeadingStrategy/BeadingStrategy.hpp Arachne/BeadingStrategy/BeadingStrategy.cpp Arachne/BeadingStrategy/BeadingStrategyFactory.hpp @@ -479,7 +500,6 @@ set(SLIC3R_SOURCES Arachne/BeadingStrategy/WideningBeadingStrategy.hpp Arachne/BeadingStrategy/WideningBeadingStrategy.cpp Arachne/utils/ExtrusionJunction.hpp - Arachne/utils/ExtrusionJunction.cpp Arachne/utils/ExtrusionLine.hpp Arachne/utils/ExtrusionLine.cpp Arachne/utils/HalfEdge.hpp @@ -497,6 +517,8 @@ set(SLIC3R_SOURCES Geometry/Voronoi.cpp Geometry/VoronoiUtils.hpp Geometry/VoronoiUtils.cpp + Arachne/PerimeterOrder.hpp + Arachne/PerimeterOrder.cpp Arachne/SkeletalTrapezoidation.hpp Arachne/SkeletalTrapezoidation.cpp Arachne/SkeletalTrapezoidationEdge.hpp @@ -506,8 +528,20 @@ set(SLIC3R_SOURCES Arachne/WallToolPaths.hpp Arachne/WallToolPaths.cpp StaticMap.hpp + ProfilesSharingUtils.hpp + ProfilesSharingUtils.cpp + Utils/DirectoriesUtils.hpp + Utils/DirectoriesUtils.cpp + Utils/JsonUtils.hpp + Utils/JsonUtils.cpp ) +if (APPLE) + list(APPEND SLIC3R_SOURCES + MacUtils.mm + ) +endif () + add_library(libslic3r STATIC ${SLIC3R_SOURCES}) if (WIN32) @@ -539,6 +573,7 @@ add_library(libslic3r_cgal STATIC Triangulation.hpp Triangulation.cpp ) target_include_directories(libslic3r_cgal PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) +target_include_directories(libslic3r_cgal PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/..) # Reset compile options of libslic3r_cgal. Despite it being linked privately, CGAL options # (-frounding-math) still propagate to dependent libs which is not desired. @@ -557,6 +592,7 @@ if (_opts) endif() target_link_libraries(libslic3r_cgal PRIVATE ${_cgal_tgt} libigl) +target_link_libraries(libslic3r_cgal PUBLIC semver admesh localesutils) if (MSVC AND "${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") # 32 bit MSVC workaround target_compile_definitions(libslic3r_cgal PRIVATE CGAL_DO_NOT_USE_MPZF) @@ -565,24 +601,19 @@ endif () encoding_check(libslic3r) target_compile_definitions(libslic3r PUBLIC -DUSE_TBB -DTBB_USE_CAPTURED_EXCEPTION=0) -target_include_directories(libslic3r PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) +target_include_directories(libslic3r PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/.. ${CMAKE_CURRENT_BINARY_DIR}) target_include_directories(libslic3r PUBLIC ${EXPAT_INCLUDE_DIRS}) find_package(JPEG REQUIRED) -target_link_libraries(libslic3r +target_link_libraries(libslic3r PRIVATE libnest2d - admesh libcereal - libigl - miniz boost_libs clipper - nowide libexpat glu-libtess qhull - semver TBB::tbb TBB::tbbmalloc libslic3r_cgal @@ -591,20 +622,33 @@ target_link_libraries(libslic3r ZLIB::ZLIB JPEG::JPEG qoi + fastfloat + int128 +) +target_link_libraries(libslic3r PUBLIC + Eigen3::Eigen + semver + admesh + localesutils LibBGCode::bgcode_convert - ) + tcbspan + miniz + libigl + agg + ankerl +) if (APPLE) # TODO: we need to fix notarization with the separate shared library - target_link_libraries(libslic3r OCCTWrapper) + target_link_libraries(libslic3r PUBLIC OCCTWrapper) endif () if (TARGET OpenVDB::openvdb) - target_link_libraries(libslic3r OpenVDB::openvdb) + target_link_libraries(libslic3r PUBLIC OpenVDB::openvdb) endif() if(WIN32) - target_link_libraries(libslic3r Psapi.lib) + target_link_libraries(libslic3r PUBLIC Psapi.lib) endif() if (APPLE) diff --git a/src/libslic3r/CSGMesh/CSGMesh.hpp b/src/libslic3r/CSGMesh/CSGMesh.hpp index a9a1811..a0db5f0 100644 --- a/src/libslic3r/CSGMesh/CSGMesh.hpp +++ b/src/libslic3r/CSGMesh/CSGMesh.hpp @@ -83,6 +83,7 @@ struct CSGPart { {} }; +// Check if there are only positive parts (Union) within the collection. template bool is_all_positive(const Cont &csgmesh) { bool is_all_pos = @@ -95,6 +96,8 @@ template bool is_all_positive(const Cont &csgmesh) return is_all_pos; } +// Merge all the positive parts of the collection into a single triangle mesh without performing +// any booleans. template indexed_triangle_set csgmesh_merge_positive_parts(const Cont &csgmesh) { diff --git a/src/libslic3r/CSGMesh/CSGMeshCopy.hpp b/src/libslic3r/CSGMesh/CSGMeshCopy.hpp index 78800f9..c0a64a9 100644 --- a/src/libslic3r/CSGMesh/CSGMeshCopy.hpp +++ b/src/libslic3r/CSGMesh/CSGMeshCopy.hpp @@ -51,6 +51,8 @@ void copy_csgrange_deep(const Range &csgrange, OutIt out) } } +// Compare two csgmeshes. They are the same if all the triangle mesh pointers are equal and contain +// the same operations and transformed similarly. template bool is_same(const Range &A, const Range &B) { diff --git a/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp b/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp index 8aa9ab4..3d97286 100644 --- a/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp +++ b/src/libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp @@ -132,6 +132,9 @@ void perform_csgmesh_booleans(MeshBoolean::cgal::CGALMeshPtr &cgalm, cgalm = std::move(opstack.top().cgalptr); } +// Check if all requirements for doing mesh booleans are met by the input csgrange. +// Returns the iterator to the first part which breaks criteria or csgrange.end() if all the parts +// are ok. The Visitor vfn is called for each "bad" part. template It check_csgmesh_booleans(const Range &csgrange, Visitor &&vfn) { @@ -182,6 +185,7 @@ It check_csgmesh_booleans(const Range &csgrange, Visitor &&vfn) return ret; } +// Overload of the previous check_csgmesh_booleans without the visitor argument template It check_csgmesh_booleans(const Range &csgrange) { diff --git a/src/libslic3r/CSGMesh/SliceCSGMesh.hpp b/src/libslic3r/CSGMesh/SliceCSGMesh.hpp index 9d7b9a0..27bd1a4 100644 --- a/src/libslic3r/CSGMesh/SliceCSGMesh.hpp +++ b/src/libslic3r/CSGMesh/SliceCSGMesh.hpp @@ -45,6 +45,8 @@ inline void collect_nonempty_indices(csg::CSGType op, } // namespace detail +// Slice the input csgrange and return the corresponding layers as a vector of ExPolygons. +// All boolean operations are performed on the 2D slices. template std::vector slice_csgmesh_ex( const Range &csgrange, diff --git a/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp b/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp index f64d17b..b7230d8 100644 --- a/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp +++ b/src/libslic3r/CSGMesh/VoxelizeCSGMesh.hpp @@ -54,6 +54,7 @@ inline void perform_csg(CSGType op, VoxelGridPtr &dst, VoxelGridPtr &src) } // namespace detail +// Convert the input csgrange to a voxel grid performing the boolean operations in the voxel realm. template VoxelGridPtr voxelize_csgmesh(const Range &csgrange, const VoxelizeParams ¶ms = {}) diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 42945c0..21e8d6f 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -1,7 +1,13 @@ #include "ClipperUtils.hpp" -#include "Geometry.hpp" + +#include + #include "ShortestPath.hpp" -#include "Utils.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/Surface.hpp" +#include "libslic3r/libslic3r.h" // #define CLIPPER_UTILS_TIMING @@ -9,7 +15,9 @@ // time limit for one ClipperLib operation (union / diff / offset), in ms #define CLIPPER_UTILS_TIME_LIMIT_DEFAULT 50 #include + #include "Timer.hpp" + #define CLIPPER_UTILS_TIME_LIMIT_SECONDS(limit) Timing::TimeLimitAlarm time_limit_alarm(uint64_t(limit) * 1000000000l, BOOST_CURRENT_FUNCTION) #define CLIPPER_UTILS_TIME_LIMIT_MILLIS(limit) Timing::TimeLimitAlarm time_limit_alarm(uint64_t(limit) * 1000000l, BOOST_CURRENT_FUNCTION) #else @@ -209,7 +217,7 @@ namespace ClipperUtils { out.insert(out.end(), temp.begin(), temp.end()); } - out.erase(std::remove_if(out.begin(), out.end(), [](const Polygon &polygon) { return polygon.empty(); }), out.end()); + out.erase(std::remove_if(out.begin(), out.end(), [](const Polygon &polygon) {return polygon.empty(); }), out.end()); return out; } } @@ -833,7 +841,6 @@ Slic3r::ExPolygons union_ex(const Slic3r::ExPolygons &subject, const Slic3r::Pol Slic3r::ExPolygons union_ex(const Slic3r::Surfaces &subject) { return PolyTreeToExPolygons(clipper_do_polytree(ClipperLib::ctUnion, ClipperUtils::SurfacesProvider(subject), ClipperUtils::EmptyPathsProvider(), ClipperLib::pftNonZero)); } - template Polylines _clipper_pl_open(ClipperLib::ClipType clipType, PathsProvider1 &&subject, PathsProvider2 &&clip) { @@ -1290,12 +1297,6 @@ static void variable_offset_inner_raw(const ExPolygon &expoly, const std::vector holes.reserve(expoly.holes.size()); for (const Polygon &hole : expoly.holes) append(holes, fix_after_outer_offset(mittered_offset_path_scaled(hole.points, deltas[1 + &hole - expoly.holes.data()], miter_limit), ClipperLib::pftNegative, false)); -#ifndef NDEBUG - // Offsetting a hole curve of a C shape may close the C into a ring with a new hole inside, thus creating a hole inside a hole shape, thus a hole will be created with negative area - // and the following test will fail. -// for (auto &c : holes) -// assert(ClipperLib::Area(c) > 0.); -#endif /* NDEBUG */ } Polygons variable_offset_inner(const ExPolygon &expoly, const std::vector> &deltas, double miter_limit) @@ -1369,12 +1370,6 @@ static void variable_offset_outer_raw(const ExPolygon &expoly, const std::vector contours = fix_after_outer_offset(mittered_offset_path_scaled(expoly.contour.points, deltas.front(), miter_limit), ClipperLib::pftPositive, false); // Inflating a contour must not remove it. assert(contours.size() >= 1); -#ifndef NDEBUG - // Offsetting a positive curve of a C shape may close the C into a ring with hole shape, thus a hole will be created with negative area - // and the following test will fail. -// for (auto &c : contours) -// assert(ClipperLib::Area(c) > 0.); -#endif /* NDEBUG */ // 2) Offset the holes one by one, collect the results. holes.reserve(expoly.holes.size()); diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index a11df3f..ba728cd 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -3,10 +3,22 @@ //#define SLIC3R_USE_CLIPPER2 +#include +#include +#include +#include +#include +#include + #include "libslic3r.h" #include "ExPolygon.hpp" #include "Polygon.hpp" #include "Surface.hpp" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polyline.hpp" +#include "libslic3r/BoundingBox.hpp" #ifdef SLIC3R_USE_CLIPPER2 @@ -14,7 +26,8 @@ #else /* SLIC3R_USE_CLIPPER2 */ -#include "clipper.hpp" +#include "libslic3r/clipper.hpp" + // import these wherever we're included using Slic3r::ClipperLib::jtMiter; using Slic3r::ClipperLib::jtRound; @@ -29,8 +42,10 @@ class BoundingBox; static constexpr const float ClipperSafetyOffset = 10.f; static constexpr const Slic3r::ClipperLib::JoinType DefaultJoinType = Slic3r::ClipperLib::jtMiter; -//FIXME evaluate the default miter limit. 3 seems to be extreme, Cura uses 1.2. + static constexpr const Slic3r::ClipperLib::EndType DefaultEndType = Slic3r::ClipperLib::etOpenButt; + +//FIXME evaluate the default miter limit. 3 seems to be extreme, Cura uses 1.2. // Mitter Limit 3 is useful for perimeter generator, where sharp corners are extruded without needing a gap fill. // However such a high limit causes issues with large positive or negative offsets, where a sharp corner // is extended excessively. @@ -364,8 +379,10 @@ Slic3r::ExPolygons offset_ex(const Slic3r::ExPolygons &expolygons, const float d Slic3r::ExPolygons offset_ex(const Slic3r::Surfaces &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); Slic3r::ExPolygons offset_ex(const Slic3r::SurfacesPtr &surfaces, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit); +// convert stroke to path by offsetting of contour Polygons contour_to_polygons(const Polygon &polygon, const float line_width, ClipperLib::JoinType join_type = DefaultJoinType, double miter_limit = DefaultMiterLimit); Polygons contour_to_polygons(const Polygons &polygon, const float line_width, ClipperLib::JoinType join_type = DefaultJoinType, double miter_limit = DefaultMiterLimit); + inline Slic3r::Polygons union_safety_offset (const Slic3r::Polygons &polygons) { return offset (polygons, ClipperSafetyOffset); } inline Slic3r::Polygons union_safety_offset (const Slic3r::ExPolygons &expolygons) { return offset (expolygons, ClipperSafetyOffset); } inline Slic3r::ExPolygons union_safety_offset_ex(const Slic3r::Polygons &polygons) { return offset_ex(polygons, ClipperSafetyOffset); } @@ -461,7 +478,6 @@ Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::ExPol Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygons &clip); Slic3r::Polylines diff_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip); - inline Slic3r::Lines diff_ln(const Slic3r::Lines &subject, const Slic3r::Polygons &clip) { return _clipper_ln(ClipperLib::ctDifference, subject, clip); @@ -488,7 +504,6 @@ Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::ExPolygons intersection_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); - Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::Polygon &clip); Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygon &clip); Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygon &clip); diff --git a/src/libslic3r/Color.cpp b/src/libslic3r/Color.cpp index 954bef4..bdaa2d7 100644 --- a/src/libslic3r/Color.cpp +++ b/src/libslic3r/Color.cpp @@ -1,7 +1,9 @@ -#include "libslic3r.h" -#include "Color.hpp" - #include +#include +#include +#include + +#include "Color.hpp" static const float INV_255 = 1.0f / 255.0f; diff --git a/src/libslic3r/Color.hpp b/src/libslic3r/Color.hpp index 8044a03..60d6f8a 100644 --- a/src/libslic3r/Color.hpp +++ b/src/libslic3r/Color.hpp @@ -1,8 +1,12 @@ #ifndef slic3r_Color_hpp_ #define slic3r_Color_hpp_ +#include #include #include +#include +#include +#include #include "Point.hpp" diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index 3bc2b54..0b34016 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -1,33 +1,32 @@ #include "Config.hpp" -#include "format.hpp" -#include "Utils.hpp" -#include "LocalesUtils.hpp" -#include -#include -#include -#include -#include #include -#include #include #include #include -#include -#include -#include -#include -#include +#include #include #include +#include #include -#include -#include - #include -//FIXME for GCodeFlavor and gcfMarlin (for forward-compatibility conversion) -// This is not nice, likely it would be better to pass the ConfigSubstitutionContext to handle_legacy(). -#include "PrintConfig.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "format.hpp" +#include "Utils.hpp" +#include "LocalesUtils.hpp" +#include "libslic3r/Exception.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Semver.hpp" namespace Slic3r { @@ -273,6 +272,7 @@ ConfigOption* ConfigOptionDef::create_empty_option() const case coBool: return new ConfigOptionBool(); case coBools: return new ConfigOptionBools(); case coEnum: return new ConfigOptionEnumGeneric(this->enum_def->m_enum_keys_map); + case coEnums: return new ConfigOptionEnumsGeneric(this->enum_def->m_enum_keys_map); default: throw ConfigurationError(std::string("Unknown option type for option ") + this->label); } } @@ -283,7 +283,10 @@ ConfigOption* ConfigOptionDef::create_default_option() const if (this->default_value) return (this->default_value->type() == coEnum) ? // Special case: For a DynamicConfig, convert a templated enum to a generic enum. - new ConfigOptionEnumGeneric(this->enum_def->m_enum_keys_map, this->default_value->getInt()) : + new ConfigOptionEnumGeneric(this->enum_def->m_enum_keys_map, this->default_value->getInt()) : + (this->default_value->type() == coEnums) ? + // Special case: For a DynamicConfig, convert a templated enums to a generic enums. + new ConfigOptionEnumsGeneric(this->enum_def->m_enum_keys_map, this->default_value->getInts()) : this->default_value->clone(); return this->create_empty_option(); } @@ -312,7 +315,7 @@ void ConfigDef::finalize() // Validate & finalize open & closed enums. for (std::pair &kvp : options) { ConfigOptionDef& def = kvp.second; - if (def.type == coEnum) { + if (def.type == coEnum || def.type == coEnums) { assert(def.enum_def); assert(def.enum_def->is_valid_closed_enum()); assert(! def.is_gui_type_enum_open()); @@ -353,6 +356,9 @@ std::ostream& ConfigDef::print_cli_help(std::ostream& out, bool show_defaults, s return wrapped.str(); }; + // List of opt_keys that should be hidden from the CLI help. + const std::vector silent_options = { "webdev", "single_instance_on_url" }; + // get the unique categories std::set categories; for (const auto& opt : this->options) { @@ -372,6 +378,9 @@ std::ostream& ConfigDef::print_cli_help(std::ostream& out, bool show_defaults, s const ConfigOptionDef& def = opt.second; if (def.category != category || def.cli == ConfigOptionDef::nocli || !filter(def)) continue; + + if (std::find(silent_options.begin(), silent_options.end(), opt.second.opt_key) != silent_options.end()) + continue; // get all possible variations: --foo, --foobar, -f... std::vector cli_args = def.cli_args(opt.first); @@ -746,7 +755,7 @@ ConfigSubstitutions ConfigBase::load(const std::string& filename, ForwardCompati file_type = EFileType::Ini; switch (file_type) -{ + { case EFileType::Ini: { return this->load_from_ini(filename, compatibility_rule); } case EFileType::AsciiGCode: { return this->load_from_gcode_file(filename, compatibility_rule);} case EFileType::BinaryGCode: { return this->load_from_binary_gcode_file(filename, compatibility_rule);} @@ -888,6 +897,7 @@ size_t ConfigBase::load_from_gcode_string_legacy(ConfigBase& config, const char* // Do legacy conversion on a completely loaded dictionary. // Perform composite conversions, for example merging multiple keys into one key. config.handle_legacy_composite(); + return num_key_value_pairs; } @@ -1048,8 +1058,8 @@ ConfigSubstitutions ConfigBase::load_from_gcode_file(const std::string &filename // Try a heuristics reading the G-code from back. ifs.seekg(0, ifs.end); auto file_length = ifs.tellg(); - auto data_length = std::min(65535, file_length - header_end_pos); - ifs.seekg(file_length - data_length, ifs.beg); + auto data_length = std::min(65535, file_length - header_end_pos); + ifs.seekg(file_length - data_length, ifs.beg); std::vector data(size_t(data_length) + 1, 0); ifs.read(data.data(), data_length); ifs.close(); @@ -1406,7 +1416,7 @@ t_config_option_keys DynamicConfig::equal(const DynamicConfig &other) const } -#include +#include // IWYU pragma: keep CEREAL_REGISTER_TYPE(Slic3r::ConfigOption) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionSingle) @@ -1446,6 +1456,7 @@ CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionBool) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionBools) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionBoolsNullable) CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionEnumGeneric) +CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionEnumsGeneric) CEREAL_REGISTER_TYPE(Slic3r::ConfigBase) CEREAL_REGISTER_TYPE(Slic3r::DynamicConfig) @@ -1487,4 +1498,5 @@ CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle, Slic3r::C CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector, Slic3r::ConfigOptionBools) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector, Slic3r::ConfigOptionBoolsNullable) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionInt, Slic3r::ConfigOptionEnumGeneric) +CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionInts, Slic3r::ConfigOptionEnumsGeneric) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigBase, Slic3r::DynamicConfig) diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index ed9a9a0..49367c6 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -2,6 +2,17 @@ #define slic3r_Config_hpp_ #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -14,20 +25,22 @@ #include #include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "libslic3r.h" #include "clonable_ptr.hpp" #include "Exception.hpp" #include "Point.hpp" - -#include -#include -#include -#include -#include - -#include -#include +#include "LocalesUtils.hpp" namespace Slic3r { struct FloatOrPercent @@ -193,6 +206,8 @@ enum ConfigOptionType { coBools = coBool + coVectorType, // a generic enum coEnum = 9, + // vector of enum values + coEnums = coEnum + coVectorType, }; enum ConfigOptionMode { @@ -231,6 +246,7 @@ enum ForwardCompatibilitySubstitutionRule class ConfigDef; class ConfigOption; class ConfigOptionDef; + // For forward definition of ConfigOption in ConfigOptionUniquePtr, we have to define a custom deleter. struct ConfigOptionDeleter { void operator()(ConfigOption* p); }; using ConfigOptionUniquePtr = std::unique_ptr; @@ -269,6 +285,7 @@ public: // Set a value from a ConfigOption. The two options should be compatible. virtual void set(const ConfigOption *option) = 0; virtual int getInt() const { throw BadOptionTypeException("Calling ConfigOption::getInt on a non-int ConfigOption"); } + virtual std::vector getInts() const { throw BadOptionTypeException("Calling ConfigOption::getInts on a non-ints ConfigOption"); } virtual double getFloat() const { throw BadOptionTypeException("Calling ConfigOption::getFloat on a non-float ConfigOption"); } virtual bool getBool() const { throw BadOptionTypeException("Calling ConfigOption::getBool on a non-boolean ConfigOption"); } virtual void setInt(int /* val */) { throw BadOptionTypeException("Calling ConfigOption::setInt on a non-int ConfigOption"); } @@ -318,7 +335,7 @@ template struct NilValueTempl, void>> { using NilType = T; - static constexpr auto value = static_cast(std::numeric_limits>::max()); + static constexpr auto value = static_cast>(std::numeric_limits>::max()); }; template struct NilValueTempl, void>> { @@ -346,6 +363,7 @@ template using NilType = typename NilValueTempl>::Nil // Define shortcut as a function instead of a static const var so that it can be constexpr // even if the NilValueTempl::value is not constexpr. template static constexpr NilType NilValue() noexcept { return NilValueTempl>::value; } + // Value of a single valued option (bool, int, float, string, point, enum) template class ConfigOptionSingle : public ConfigOption { @@ -430,6 +448,7 @@ public: return ret; } + private: friend class cereal::access; template void serialize(Archive & ar) { ar(this->value); } @@ -437,6 +456,7 @@ private: template using ConfigOptionSingleNullable = ConfigOptionSingle; + // Value of a vector valued option (bools, ints, floats, strings, points) class ConfigOptionVectorBase : public ConfigOption { public: @@ -683,6 +703,7 @@ public: throw ConfigurationError("Serializing NaN"); } else throw ConfigurationError("Serializing invalid number"); + return ss.str(); } @@ -690,14 +711,16 @@ public: { UNUSED(append); std::istringstream iss(str); + if (str == "nil") { if (NULLABLE) this->value = this->nil_value(); else throw ConfigurationError("Deserializing nil into a non-nullable object"); } else { - iss >> this->value; + iss >> this->value; } + return !iss.fail(); } @@ -711,6 +734,7 @@ public: { return std::isnan(this->value); } + private: friend class cereal::access; template void serialize(Archive &ar) { ar(cereal::base_class>(this)); } @@ -868,7 +892,8 @@ public: else throw ConfigurationError("Serializing NaN"); } else - ss << this->value; + ss << this->value; + return ss.str(); } @@ -876,14 +901,16 @@ public: { UNUSED(append); std::istringstream iss(str); + if (str == "nil") { if (NULLABLE) this->value = this->nil_value(); else throw ConfigurationError("Deserializing nil into a non-nullable object"); } else { - iss >> this->value; + iss >> this->value; } + return !iss.fail(); } @@ -900,6 +927,7 @@ private: using ConfigOptionInt = ConfigOptionIntTempl; using ConfigOptionIntNullable = ConfigOptionIntTempl; + template class ConfigOptionIntsTempl : public ConfigOptionVector { @@ -1709,6 +1737,113 @@ public: } }; +template +class ConfigOptionEnums : public ConfigOptionVector +{ +public: + // by default, use the first value (0) of the T enum type + ConfigOptionEnums() : ConfigOptionVector() {} + explicit ConfigOptionEnums(size_t n, const T& value) : ConfigOptionVector(n, value) {} + explicit ConfigOptionEnums(std::initializer_list il) : ConfigOptionVector(std::move(il)) {} + explicit ConfigOptionEnums(const std::vector& values) : ConfigOptionVector(values) {} + + static ConfigOptionType static_type() { return coEnums; } + ConfigOptionType type() const override { return static_type(); } + ConfigOption* clone() const override { return new ConfigOptionEnums(*this); } + ConfigOptionEnums& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + bool operator==(const ConfigOptionEnums &rhs) const throw() { return this->values == rhs.values; } + bool operator< (const ConfigOptionEnums &rhs) const throw() { return this->values < rhs.values; } + bool is_nil(size_t) const override { return false; } + + std::vector getInts() const override { + std::vector ret; + ret.reserve(this->values.size()); + for (const auto& v : this->values) + ret.push_back(int(v)); + return ret; + } + + bool operator==(const ConfigOption& rhs) const override + { + if (rhs.type() != this->type()) + throw ConfigurationError("ConfigOptionEnums: Comparing incompatible types"); + // rhs could be of the following type: ConfigOptionEnumsGeneric or ConfigOptionEnums + return this->getInts() == rhs.getInts(); + } + + void set(const ConfigOption* rhs) override { + if (rhs->type() != this->type()) + throw ConfigurationError("ConfigOptionEnums: Assigning an incompatible type"); + // rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum + std::vector ret; + std::vector rhs_vals = rhs->getInts(); + ret.reserve(rhs_vals.size()); + for (const int& v : rhs_vals) + ret.push_back(T(v)); + this->values = ret; + } + + std::string serialize() const override + { + const t_config_enum_names& names = ConfigOptionEnum::get_enum_names(); + std::ostringstream ss; + for (const T& v : this->values) { + assert(static_cast(v) < int(names.size())); + if (&v != &this->values.front()) + ss << "," << names[static_cast(v)]; + } + return ss.str(); + } + + std::vector vserialize() const override + { + std::vector vv; + vv.reserve(this->values.size()); + for (const T v : this->values) { + std::ostringstream ss; + serialize_single_value(ss, int(v)); + vv.push_back(ss.str()); + } + return vv; + } + + bool deserialize(const std::string& str, bool append = false) override + { + if (!append) + this->values.clear(); + std::istringstream is(str); + std::string item_str; + while (std::getline(is, item_str, ',')) { + boost::trim(item_str); + if (item_str == "nil") { + throw ConfigurationError("Deserializing nil into a non-nullable object"); + } + else { + std::istringstream iss(item_str); + int value; + iss >> value; + this->values.push_back(static_cast(value)); + } + } + return true; + } + + static bool from_string(const std::string &str, T &value) + { + const t_config_enum_values &enum_keys_map = ConfigOptionEnum::get_enum_values(); + auto it = enum_keys_map.find(str); + if (it == enum_keys_map.end()) + return false; + value = static_cast(it->second); + return true; + } + +private: + void serialize_single_value(std::ostringstream& ss, const int v) const { + ss << v; + } +}; + // Generic enum configuration value. // We use this one in DynamicConfig objects when creating a config value object for ConfigOptionType == coEnum. // In the StaticConfig, it is better to use the specialized ConfigOptionEnum containers. @@ -1765,6 +1900,132 @@ private: template void serialize(Archive& ar) { ar(cereal::base_class(this)); } }; +template +class ConfigOptionEnumsGenericTempl : public ConfigOptionIntsTempl +{ +public: + ConfigOptionEnumsGenericTempl(const t_config_enum_values* keys_map = nullptr) : keys_map(keys_map) {} + explicit ConfigOptionEnumsGenericTempl(const t_config_enum_values* keys_map, std::vector values) : keys_map(keys_map) { this->values = values; } + + const t_config_enum_values* keys_map; + + ConfigOptionEnumsGenericTempl() : ConfigOptionIntsTempl() {} + explicit ConfigOptionEnumsGenericTempl(size_t n, int value) : ConfigOptionIntsTempl(n, value) {} + explicit ConfigOptionEnumsGenericTempl(std::initializer_list il) : ConfigOptionIntsTempl(std::move(il)) {} + explicit ConfigOptionEnumsGenericTempl(const std::vector& v) : ConfigOptionIntsTempl(v) {} + explicit ConfigOptionEnumsGenericTempl(std::vector&& v) : ConfigOptionIntsTempl(std::move(v)) {} + + static ConfigOptionType static_type() { return coEnums; } + ConfigOptionType type() const override { return static_type(); } + ConfigOption* clone() const override { return new ConfigOptionEnumsGenericTempl(*this); } + ConfigOptionEnumsGenericTempl& operator= (const ConfigOption* opt) { this->set(opt); return *this; } + bool operator==(const ConfigOptionEnumsGenericTempl& rhs) const throw() { return this->values == rhs.values; } + bool operator< (const ConfigOptionEnumsGenericTempl& rhs) const throw() { return this->values < rhs.values; } + std::vector getInts() const override { return this->values; } + + bool operator==(const ConfigOption& rhs) const override + { + if (rhs.type() != this->type()) + throw ConfigurationError("ConfigOptionEnumsGeneric: Comparing incompatible types"); + // rhs could be of the following type: ConfigOptionEnumsGeneric or ConfigOptionEnums + return this->values == rhs.getInts(); + } + + void set(const ConfigOption* rhs) override { + if (rhs->type() != this->type()) + throw ConfigurationError("ConfigOptionEnumsGeneric: Assigning an incompatible type"); + // rhs could be of the following type: ConfigOptionEnumsGeneric or ConfigOptionEnums + this->values = rhs->getInts(); + } + + // Could a special "nil" value be stored inside the vector, indicating undefined value? + bool nullable() const override { return NULLABLE; } + // Special "nil" value to be stored into the vector if this->supports_nil(). + static int nil_value() { return std::numeric_limits::max(); } + // A scalar is nil, or all values of a vector are nil. + bool is_nil() const override { for (auto v : this->values) if (v != nil_value()) return false; return true; } + bool is_nil(size_t idx) const override { return this->values[idx < this->values.size() ? idx : 0] == nil_value(); } + + int& get_at(size_t i) { + assert(!this->values.empty()); + return *reinterpret_cast(&((i < this->values.size()) ? this->values[i] : this->values.front())); + } + + int get_at(size_t i) const { return i < this->values.size() ? this->values[i] : this->values.front(); } + + std::string serialize() const override + { + std::ostringstream ss; + for (const int& v : this->values) { + if (&v != &this->values.front()) + ss << ","; + serialize_single_value(ss, v); + } + return ss.str(); + } + + std::vector vserialize() const override + { + std::vector vv; + vv.reserve(this->values.size()); + for (const int v : this->values) { + std::ostringstream ss; + serialize_single_value(ss, v); + vv.push_back(ss.str()); + } + return vv; + } + + bool deserialize(const std::string& str, bool append = false) override + { + if (!append) + this->values.clear(); + std::istringstream is(str); + std::string item_str; + while (std::getline(is, item_str, ',')) { + boost::trim(item_str); + if (item_str == "nil") { + if (NULLABLE) + this->values.push_back(nil_value()); + else + throw ConfigurationError("Deserializing nil into a non-nullable object"); + } + else { + auto it = this->keys_map->find(item_str); + if (it == this->keys_map->end()) + return false; + this->values.push_back(it->second); + } + } + return true; + } + +private: + void serialize_single_value(std::ostringstream& ss, const int v) const { + if (v == nil_value()) { + if (NULLABLE) + ss << "nil"; + else + throw ConfigurationError("Serializing NaN"); + } + else { + for (const auto& kvp : *this->keys_map) + if (kvp.second == v) { + ss << kvp.first; + return; + } + ss << std::string(); + } + } + + friend class cereal::access; + template void serialize(Archive& ar) { ar(cereal::base_class>(this)); } +}; + +using ConfigOptionEnumsGeneric = ConfigOptionEnumsGenericTempl; +using ConfigOptionEnumsGenericNullable = ConfigOptionEnumsGenericTempl; + + // Definition of values / labels for a combo box. // Mostly used for closed enums (when type == coEnum), but may be used for // open enums with ints resp. floats, if gui_type is set to GUIType::i_enum_open" resp. GUIType::f_enum_open. @@ -1958,6 +2219,8 @@ public: one_string, // Close parameter, string value could be one of the list values. select_close, + // Password, string vaule is hidden by asterisk. + password, }; static bool is_gui_type_enum_open(const GUIType gui_type) { return gui_type == ConfigOptionDef::GUIType::i_enum_open || gui_type == ConfigOptionDef::GUIType::f_enum_open || gui_type == ConfigOptionDef::GUIType::select_open; } @@ -1979,6 +2242,7 @@ public: ConfigOption* create_default_option() const; bool is_scalar() const { return (int(this->type) & int(coVectorType)) == 0; } + template ConfigOption* load_option_from_archive(Archive &archive) const { if (this->nullable) { switch (this->type) { @@ -2008,6 +2272,7 @@ public: case coBool: { auto opt = new ConfigOptionBool(); archive(*opt); return opt; } case coBools: { auto opt = new ConfigOptionBools(); archive(*opt); return opt; } case coEnum: { auto opt = new ConfigOptionEnumGeneric(this->enum_def->m_enum_keys_map); archive(*opt); return opt; } + case coEnums: { auto opt = new ConfigOptionEnumsGeneric(this->enum_def->m_enum_keys_map); archive(*opt); return opt; } default: throw ConfigurationError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown option type for option ") + this->opt_key); } } @@ -2042,6 +2307,7 @@ public: case coBool: archive(*static_cast(opt)); break; case coBools: archive(*static_cast(opt)); break; case coEnum: archive(*static_cast(opt)); break; + case coEnums: archive(*static_cast(opt)); break; default: throw ConfigurationError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown option type for option ") + this->opt_key); } } @@ -2449,6 +2715,8 @@ public: // Thus the virtual method getInt() is used to retrieve the enum value. template ENUM opt_enum(const t_config_option_key &opt_key) const { return static_cast(this->option(opt_key)->getInt()); } + template + ENUM opt_enum(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast*>(this->option(opt_key))->get_at(idx);} bool opt_bool(const t_config_option_key &opt_key) const { return this->option(opt_key)->value != 0; } bool opt_bool(const t_config_option_key &opt_key, unsigned int idx) const { return this->option(opt_key)->get_at(idx) != 0; } diff --git a/src/libslic3r/CustomGCode.cpp b/src/libslic3r/CustomGCode.cpp index 0c233e4..94e21c4 100644 --- a/src/libslic3r/CustomGCode.cpp +++ b/src/libslic3r/CustomGCode.cpp @@ -1,7 +1,10 @@ #include "CustomGCode.hpp" + +#include + #include "Config.hpp" #include "GCode.hpp" -#include "GCode/GCodeWriter.hpp" +#include "libslic3r/PrintConfig.hpp" namespace Slic3r { @@ -83,6 +86,7 @@ std::vector> custom_color_changes(const Info& cu } return custom_color_changes; } + } // namespace CustomGCode } // namespace Slic3r diff --git a/src/libslic3r/CustomGCode.hpp b/src/libslic3r/CustomGCode.hpp index 568963b..65703a2 100644 --- a/src/libslic3r/CustomGCode.hpp +++ b/src/libslic3r/CustomGCode.hpp @@ -1,8 +1,11 @@ #ifndef slic3r_CustomGCode_hpp_ #define slic3r_CustomGCode_hpp_ +#include #include #include +#include +#include namespace Slic3r { @@ -10,6 +13,11 @@ class DynamicPrintConfig; namespace CustomGCode { +/* For exporting GCode in GCodeWriter is used XYZF_NUM(val) = PRECISION(val, 3) for XYZ values. + * So, let use same value as a permissible error for layer height. + */ +constexpr double epsilon() { return 0.0011; } + enum Type { ColorChange, @@ -90,6 +98,7 @@ std::vector> custom_tool_changes(const Info& cus // Return pairs of sorted by increasing print_z from custom_gcode_per_print_z. // Where print_z corresponds to the layer on which we perform a color change for the specified extruder. std::vector> custom_color_changes(const Info& custom_gcode_per_print_z, size_t num_extruders); + } // namespace CustomGCode } // namespace Slic3r diff --git a/src/libslic3r/CutSurface.cpp b/src/libslic3r/CutSurface.cpp index 0eddbcd..36a4184 100644 --- a/src/libslic3r/CutSurface.cpp +++ b/src/libslic3r/CutSurface.cpp @@ -23,18 +23,41 @@ //#define DEBUG_OUTPUT_DIR std::string("C:/data/temp/cutSurface/") using namespace Slic3r; -#include "ExPolygonsIndex.hpp" - #include #include #include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ExPolygonsIndex.hpp" // libslic3r #include "TriangleMesh.hpp" // its_merge #include "Utils.hpp" // next_highest_power_of_2 -#include "ClipperUtils.hpp" // union_ex + offset_ex +#include "admesh/stl.h" +#include "libslic3r/AABBTreeIndirect.hpp" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/Emboss.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Exception.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/libslic3r.h" namespace priv { @@ -449,17 +472,21 @@ ProjectionDistances choose_best_distance( /// /// For each point selected closest distance /// All patches -/// All patches +/// Shape to cut +/// Bound of shapes +/// +/// +/// +/// /// Mask of used patch std::vector select_patches(const ProjectionDistances &best_distances, const SurfacePatches &patches, - - const ExPolygons &shapes, + const ExPolygons &shapes, const BoundingBox &shapes_bb, - const ExPolygonsIndices &s2i, - const VCutAOIs &cutAOIs, - const CutMeshes &meshes, - const Project &projection); + const ExPolygonsIndices &s2i, + const VCutAOIs &cutAOIs, + const CutMeshes &meshes, + const Project &projection); /// /// Merge two surface cuts together @@ -513,9 +540,10 @@ void store(const Emboss::IProjection &projection, const Point &point_to_project, } // namespace privat #ifdef DEBUG_OUTPUT_DIR -#include "libslic3r/SVG.hpp" #include #include + +#include "libslic3r/SVG.hpp" #endif // DEBUG_OUTPUT_DIR SurfaceCut Slic3r::cut_surface(const ExPolygons &shapes, @@ -1771,6 +1799,7 @@ priv::VDistances priv::calc_distances(const SurfacePatches &patches, #include "libslic3r/AABBTreeLines.hpp" #include "libslic3r/Line.hpp" + // functions for choose_best_distance namespace priv { @@ -2242,7 +2271,7 @@ priv::ProjectionDistances priv::choose_best_distance( if (unfinished_index >= s2i.get_count()) // no point to select return result; - + #ifdef DEBUG_OUTPUT_DIR Connections connections; connections.reserve(shapes.size()); @@ -2552,6 +2581,7 @@ void priv::create_face_types(FaceTypeMap &map, #include #include + bool priv::clip_cut(SurfacePatch &cut, CutMesh clipper) { CutMesh& tm = cut.mesh; @@ -3218,15 +3248,15 @@ bool priv::is_over_whole_expoly(const CutAOI &cutAOI, std::vector priv::select_patches(const ProjectionDistances &best_distances, const SurfacePatches &patches, - - const ExPolygons &shapes, + const ExPolygons &shapes, const BoundingBox &shapes_bb, - const ExPolygonsIndices &s2i, - const VCutAOIs &cutAOIs, - const CutMeshes &meshes, - const Project &projection) + const ExPolygonsIndices &s2i, + const VCutAOIs &cutAOIs, + const CutMeshes &meshes, + const Project &projection) { // extension to cover numerical mistake made by back projection patch from 3d to 2d + // Calculated as one percent of average size(width and height) Point s = shapes_bb.size(); const float extend_delta = (s.x() + s.y())/ float(2 * 100); diff --git a/src/libslic3r/CutSurface.hpp b/src/libslic3r/CutSurface.hpp index 9f4e315..b45a87c 100644 --- a/src/libslic3r/CutSurface.hpp +++ b/src/libslic3r/CutSurface.hpp @@ -1,12 +1,19 @@ #ifndef slic3r_CutSurface_hpp_ #define slic3r_CutSurface_hpp_ -#include #include // indexed_triangle_set +#include +#include + #include "ExPolygon.hpp" #include "Emboss.hpp" // IProjection +#include "libslic3r/BoundingBox.hpp" namespace Slic3r{ +namespace Emboss { +class IProject3d; +class IProjection; +} // namespace Emboss /// /// Represents cutted surface from object diff --git a/src/libslic3r/CutUtils.cpp b/src/libslic3r/CutUtils.cpp index c4721e5..1b6c3f6 100644 --- a/src/libslic3r/CutUtils.cpp +++ b/src/libslic3r/CutUtils.cpp @@ -1,13 +1,21 @@ #include "CutUtils.hpp" + +#include +#include +#include +#include +#include +#include + #include "Geometry.hpp" #include "libslic3r.h" #include "Model.hpp" #include "TriangleMeshSlicer.hpp" -#include "TriangleSelector.hpp" -#include "ObjectID.hpp" - -#include +#include "admesh/stl.h" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/TriangleMesh.hpp" namespace Slic3r { @@ -391,14 +399,20 @@ static void distribute_modifiers_from_object(ModelObject* from_obj, const int in for (ModelVolume* vol : from_obj->volumes) if (!vol->is_model_part()) { + // Don't add modifiers which are processed connectors if (vol->cut_info.is_connector && !vol->cut_info.is_processed) continue; + + // Modifiers are not cut, but we still need to add the instance transformation + // to the modifier volume transformation to preserve their shape properly. + const auto modifier_trafo = Transformation(from_obj->instances[instance_idx]->get_transformation().get_matrix_no_offset() * vol->get_matrix()); + auto bb = vol->mesh().transformed_bounding_box(inst_matrix * vol->get_matrix()); // Don't add modifiers which are not intersecting with solid parts if (obj1_bb.intersects(bb)) - to_obj1->add_volume(*vol); + to_obj1->add_volume(*vol)->set_transformation(modifier_trafo); if (obj2_bb.intersects(bb)) - to_obj2->add_volume(*vol); + to_obj2->add_volume(*vol)->set_transformation(modifier_trafo); } } @@ -423,6 +437,7 @@ static void merge_solid_parts_inside_object(ModelObjectPtrs& objects) if (mv->is_model_part() && !mv->is_cut_connector()) mo->delete_volume(i); } + // Ensuring that volumes start with solid parts for proper slicing mo->sort_volumes(true); } } @@ -463,6 +478,8 @@ const ModelObjectPtrs& Cut::perform_by_contour(std::vector parts, int dowe // Just add Upper and Lower objects to cut_object_ptrs post_process(upper, lower, cut_object_ptrs); + + // Now merge all model parts together: merge_solid_parts_inside_object(cut_object_ptrs); // replace initial objects in model with cut object @@ -493,17 +510,18 @@ const ModelObjectPtrs& Cut::perform_by_contour(std::vector parts, int dowe // Add Upper and Lower objects to cut_object_ptrs post_process(upper, lower, cut_object_ptrs); - // Add Dowel-connectors as separate objects to cut_object_ptrs + // Now merge all model parts together: + merge_solid_parts_inside_object(cut_object_ptrs); - // Now merge all model parts together: - merge_solid_parts_inside_object(cut_object_ptrs); - - finalize(cut_object_ptrs); + // replace initial objects in model with cut object + finalize(cut_object_ptrs); + // Add Dowel-connectors as separate objects to model if (cut_connectors_obj.size() >= 3) for (size_t id = 2; id < cut_connectors_obj.size(); id++) m_model.add_object(*cut_connectors_obj[id]); } + return m_model.objects; } @@ -618,8 +636,12 @@ const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, const Tran // add modifiers for (const ModelVolume* volume : cut_mo->volumes) - if (!volume->is_model_part()) - upper->add_volume(*volume); + if (!volume->is_model_part()) { + // Modifiers are not cut, but we still need to add the instance transformation + // to the modifier volume transformation to preserve their shape properly. + const auto modifier_trafo = Transformation(cut_mo->instances[m_instance]->get_transformation().get_matrix_no_offset() * volume->get_matrix()); + upper->add_volume(*volume)->set_transformation(modifier_trafo); + } cut_object_ptrs.push_back(upper); @@ -627,7 +649,11 @@ const ModelObjectPtrs& Cut::perform_with_groove(const Groove& groove, const Tran cut_object_ptrs.push_back(lower); } else { - // add modifiers if object has any + reset_instance_transformation(upper, m_instance, m_cut_matrix); + reset_instance_transformation(lower, m_instance, m_cut_matrix); + + // Add modifiers if object has any + // Note: make it after all transformations are reset for upper/lower object for (const ModelVolume* volume : cut_mo->volumes) if (!volume->is_model_part()) { distribute_modifiers_from_object(cut_mo, m_instance, upper, lower); diff --git a/src/libslic3r/EdgeGrid.cpp b/src/libslic3r/EdgeGrid.cpp index 4985b78..4d15088 100644 --- a/src/libslic3r/EdgeGrid.cpp +++ b/src/libslic3r/EdgeGrid.cpp @@ -1,9 +1,9 @@ #include #include -#include #include - -#include +#include +#include +#include #include "libslic3r.h" #include "ClipperUtils.hpp" @@ -11,6 +11,9 @@ #include "Geometry.hpp" #include "SVG.hpp" #include "PNGReadWrite.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Point.hpp" // #define EDGE_GRID_DEBUG_OUTPUT @@ -21,8 +24,6 @@ #undef NDEBUG #endif -#include - namespace Slic3r { void EdgeGrid::Grid::create(const Polygons &polygons, coord_t resolution) diff --git a/src/libslic3r/EdgeGrid.hpp b/src/libslic3r/EdgeGrid.hpp index 744a23e..f4bd731 100644 --- a/src/libslic3r/EdgeGrid.hpp +++ b/src/libslic3r/EdgeGrid.hpp @@ -3,10 +3,25 @@ #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "Point.hpp" #include "BoundingBox.hpp" #include "ExPolygon.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/Polyline.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { namespace EdgeGrid { diff --git a/src/libslic3r/ElephantFootCompensation.cpp b/src/libslic3r/ElephantFootCompensation.cpp index f78e2f2..c9b3547 100644 --- a/src/libslic3r/ElephantFootCompensation.cpp +++ b/src/libslic3r/ElephantFootCompensation.cpp @@ -1,5 +1,13 @@ #include "clipper/clipper_z.hpp" +#include +#include +#include +#include +#include +#include +#include + #include "libslic3r.h" #include "ClipperUtils.hpp" #include "EdgeGrid.hpp" @@ -7,11 +15,10 @@ #include "ElephantFootCompensation.hpp" #include "Flow.hpp" #include "Geometry.hpp" -#include "SVG.hpp" #include "Utils.hpp" - -#include -#include +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" // #define CONTOUR_DISTANCE_DEBUG_SVG diff --git a/src/libslic3r/ElephantFootCompensation.hpp b/src/libslic3r/ElephantFootCompensation.hpp index 596a3e9..0687db8 100644 --- a/src/libslic3r/ElephantFootCompensation.hpp +++ b/src/libslic3r/ElephantFootCompensation.hpp @@ -1,9 +1,10 @@ #ifndef slic3r_ElephantFootCompensation_hpp_ #define slic3r_ElephantFootCompensation_hpp_ +#include + #include "libslic3r.h" #include "ExPolygon.hpp" -#include namespace Slic3r { diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp index 42de50e..1734f3a 100644 --- a/src/libslic3r/Emboss.cpp +++ b/src/libslic3r/Emboss.cpp @@ -1,22 +1,36 @@ -#include -#include "Emboss.hpp" -#include -#include -#include #include #include -#include // union_ex + for boldness(polygon extend(offset)) +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Emboss.hpp" #include "IntersectionPoints.hpp" +#include "admesh/stl.h" +#include "libslic3r/AABBTreeIndirect.hpp" +#include "libslic3r/EmbossShape.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Exception.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/TextConfiguration.hpp" #define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation -#include "imgui/imstb_truetype.h" // stbtt_fontinfo + +#include // CGAL project + +// Explicit horror include (used to be implicit) - libslic3r "officialy" does not depend on imgui. +#include "../../bundled_deps/imgui/imgui/imstb_truetype.h" // stbtt_fontinfo #include "Utils.hpp" // ScopeGuard - -#include // CGAL project #include "libslic3r.h" - // to heal shape -#include "ExPolygonsIndex.hpp" +#include "libslic3r/ClipperUtils.hpp" // union_ex + for boldness(polygon extend(offset)) +#include "libslic3r/ExPolygonsIndex.hpp" #include "libslic3r/AABBTreeLines.hpp" // search structure for found close points #include "libslic3r/Line.hpp" #include "libslic3r/BoundingBox.hpp" @@ -52,6 +66,8 @@ namespace { // for debug purpose only // NOTE: check scale when store svg !! #include "libslic3r/SVG.hpp" // for visualize_heal + +Points get_unique_intersections(const Slic3r::IntersectionsLines &intersections); // fast forward declaration static std::string visualize_heal_svg_filepath = "C:/data/temp/heal.svg"; void visualize_heal(const std::string &svg_filepath, const ExPolygons &expolygons) { @@ -635,6 +651,10 @@ bool heal_dupl_inter(ExPolygons &shape, unsigned max_iteration) expoly = create_bounding_rect({expoly}); } } + + // After insert bounding box unify and heal + shape = union_ex(shape); + heal_dupl_inter(shape, 1); return false; } @@ -766,14 +786,13 @@ const Glyph* get_glyph( unsigned int font_index = font_prop.collection_number.value_or(0); if (!is_valid(font, font_index)) return nullptr; - if (!font_info_opt.has_value()) { - - font_info_opt = load_font_info(font.data->data(), font_index); + if (!font_info_opt.has_value()) { + font_info_opt = load_font_info(font.data->data(), font_index); // can load font info? if (!font_info_opt.has_value()) return nullptr; } - float flatness = font.infos[font_index].ascent * RESOLUTION / font_prop.size_in_mm; + float flatness = font.infos[font_index].unit_per_em / font_prop.size_in_mm * RESOLUTION; // Fix for very small flatness because it create huge amount of points from curve if (flatness < RESOLUTION) flatness = RESOLUTION; @@ -1057,11 +1076,10 @@ std::unique_ptr Emboss::create_font_file( int ascent, descent, linegap; stbtt_GetFontVMetrics(info, &ascent, &descent, &linegap); - float pixels = 1000.; // value is irelevant - float em_pixels = stbtt_ScaleForMappingEmToPixels(info, pixels); - int units_per_em = static_cast(std::round(pixels / em_pixels)); - - infos.emplace_back(FontFile::Info{ascent, descent, linegap, units_per_em}); + float pixels = 1000.; // value is irelevant + float em_pixels = stbtt_ScaleForMappingEmToPixels(info, pixels); + int unit_per_em = static_cast(std::round(pixels / em_pixels)); + infos.emplace_back(FontFile::Info{ascent, descent, linegap, unit_per_em}); } return std::make_unique(std::move(data), std::move(infos)); } @@ -1191,15 +1209,13 @@ int Emboss::get_line_height(const FontFile &font, const FontProp &prop) { namespace { ExPolygons letter2shapes( - wchar_t letter, Point &cursor, FontFileWithCache &font_with_cache, const FontProp &font_prop, fontinfo_opt& font_info_cache) -{ - assert(font_with_cache.has_value()); - if (!font_with_cache.has_value()) - return {}; - - Glyphs &cache = *font_with_cache.cache; - const FontFile &font = *font_with_cache.font_file; - + wchar_t letter, + Point &cursor, + const FontFile &font, + Glyphs &cache, + const FontProp &font_prop, + fontinfo_opt &font_info_cache +) { if (letter == '\n') { cursor.x() = 0; // 2d shape has opposit direction of y @@ -1313,12 +1329,16 @@ void align_shape(ExPolygonsWithIds &shapes, const std::wstring &text, const Font ExPolygonsWithIds Emboss::text2vshapes(FontFileWithCache &font_with_cache, const std::wstring& text, const FontProp &font_prop, const std::function& was_canceled){ assert(font_with_cache.has_value()); + if (!font_with_cache.has_value()) + return {}; + const FontFile &font = *font_with_cache.font_file; unsigned int font_index = font_prop.collection_number.value_or(0); if (!is_valid(font, font_index)) return {}; - unsigned counter = 0; + std::shared_ptr cache = font_with_cache.cache; // copy pointer + unsigned counter = CANCEL_CHECK-1; // it is needed to validate using of cache Point cursor(0, 0); fontinfo_opt font_info_cache; @@ -1331,14 +1351,13 @@ ExPolygonsWithIds Emboss::text2vshapes(FontFileWithCache &font_with_cache, const return {}; } unsigned id = static_cast(letter); - result.push_back({id, letter2shapes(letter, cursor, font_with_cache, font_prop, font_info_cache)}); + result.push_back({id, letter2shapes(letter, cursor, font, *cache, font_prop, font_info_cache)}); } align_shape(result, text, font_prop, font); return result; } -#include unsigned Emboss::get_count_lines(const std::wstring& ws) { if (ws.empty()) @@ -1878,12 +1897,27 @@ double Emboss::calculate_angle(int32_t distance, PolygonPoint polygon_point, con return std::atan2(norm_d.y(), norm_d.x()); } -std::vector Emboss::calculate_angles(int32_t distance, const PolygonPoints& polygon_points, const Polygon &polygon) +std::vector Emboss::calculate_angles(const BoundingBoxes &glyph_sizes, const PolygonPoints& polygon_points, const Polygon &polygon) { + const int32_t default_distance = static_cast(std::round(scale_(5.))); + const int32_t min_distance = static_cast(std::round(scale_(.1))); + std::vector result; result.reserve(polygon_points.size()); - for(const PolygonPoint& pp: polygon_points) - result.emplace_back(calculate_angle(distance, pp, polygon)); + assert(glyph_sizes.size() == polygon_points.size()); + if (glyph_sizes.size() != polygon_points.size()) { + // only backup solution should not be used + for (const PolygonPoint &pp : polygon_points) + result.emplace_back(calculate_angle(default_distance, pp, polygon)); + return result; + } + + for (size_t i = 0; i < polygon_points.size(); i++) { + int32_t distance = glyph_sizes[i].size().x() / 2; + if (distance < min_distance) // too small could lead to false angle + distance = default_distance; + result.emplace_back(calculate_angle(distance, polygon_points[i], polygon)); + } return result; } @@ -2025,6 +2059,7 @@ double Emboss::get_align_y_offset_in_mm(FontProp::VerticalAlign align, unsigned #ifdef REMOVE_SPIKES #include + void remove_spikes(Polygon &polygon, const SpikeDesc &spike_desc) { enum class Type { diff --git a/src/libslic3r/Emboss.hpp b/src/libslic3r/Emboss.hpp index 9214860..c21167c 100644 --- a/src/libslic3r/Emboss.hpp +++ b/src/libslic3r/Emboss.hpp @@ -1,16 +1,29 @@ #ifndef slic3r_Emboss_hpp_ #define slic3r_Emboss_hpp_ +#include // indexed_triangle_set +#include +#include +#include #include #include #include #include -#include // indexed_triangle_set +#include +#include +#include +#include +#include +#include +#include +#include + #include "Polygon.hpp" #include "ExPolygon.hpp" #include "EmbossShape.hpp" // ExPolygonsWithIds #include "BoundingBox.hpp" #include "TextConfiguration.hpp" +#include "libslic3r/Point.hpp" namespace Slic3r { @@ -20,8 +33,6 @@ namespace Slic3r { /// namespace Emboss { - // every glyph's shape point is divided by SHAPE_SCALE - increase precission of fixed point value - // stored in fonts (to be able represents curve by sequence of lines) static const float UNION_DELTA = 50.0f; // [approx in nano meters depends on volume scale] static const unsigned UNION_MAX_ITERATIN = 10; // [count] @@ -167,8 +178,9 @@ namespace Emboss /// Fix duplicit points and self intersections in polygons. /// Also try to reduce amount of points and remove useless polygon parts /// - /// Define wanted precision of shape after heal - /// Healed shapes + /// Fill type ClipperLib::pftNonZero for overlapping otherwise + /// Look at heal_expolygon()::max_iteration + /// Healed shapes with flag is fully healed HealedExPolygons heal_polygons(const Polygons &shape, bool is_non_zero = true, unsigned max_iteration = 10); /// @@ -199,8 +211,8 @@ namespace Emboss /// /// Use data from font property to modify transformation /// - /// Z-move as surface distance(FontProp::distance) - /// Z-rotation as angle to Y axis(FontProp::angle) + /// Z-rotation as angle to Y axis + /// Z-move as surface distance /// In / Out transformation to modify by property void apply_transformation(const std::optional &angle, const std::optional &distance, Transform3d &transformation); @@ -244,7 +256,7 @@ namespace Emboss /// /// Infos for collections /// Collection index + Additional line gap - /// Line height with spacing in ExPolygon size + /// Line height with spacing in scaled font points (same as ExPolygons) int get_line_height(const FontFile &font, const FontProp &prop); /// @@ -279,8 +291,6 @@ namespace Emboss class IProjection : public IProject3d { public: - virtual ~IProjection() = default; - /// /// convert 2d point to 3d points /// @@ -458,9 +468,16 @@ namespace Emboss /// Polygon know neighbor of point /// angle(atan2) of normal in polygon point double calculate_angle(int32_t distance, PolygonPoint polygon_point, const Polygon &polygon); - std::vector calculate_angles(int32_t distance, const PolygonPoints& polygon_points, const Polygon &polygon); + std::vector calculate_angles( + const BoundingBoxes &glyph_sizes, + const PolygonPoints &polygon_points, + const Polygon &polygon + ); } // namespace Emboss + +/////////////////////// +// Move to ExPolygonsWithIds Utils void translate(ExPolygonsWithIds &e, const Point &p); BoundingBox get_extents(const ExPolygonsWithIds &e); void center(ExPolygonsWithIds &e); diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp index e4c17dd..43f5ff3 100644 --- a/src/libslic3r/ExPolygon.cpp +++ b/src/libslic3r/ExPolygon.cpp @@ -1,16 +1,20 @@ +#include +#include +#include +#include +#include +#include + #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 - -#include +#include "libslic3r/MultiPoint.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polyline.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { @@ -474,6 +478,7 @@ bool remove_same_neighbor(ExPolygons &expolygons) expolygons.end()); return remove_from_holes || remove_from_contour; } + bool remove_sticks(ExPolygon &poly) { return remove_sticks(poly.contour) || remove_sticks(poly.holes); diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index 1f8490a..a915434 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -1,15 +1,29 @@ #ifndef slic3r_ExPolygon_hpp_ #define slic3r_ExPolygon_hpp_ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "Point.hpp" #include "libslic3r.h" #include "Polygon.hpp" #include "Polyline.hpp" -#include +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/Line.hpp" namespace Slic3r { class ExPolygon; + using ExPolygons = std::vector; class ExPolygon @@ -376,6 +390,7 @@ inline void translate(ExPolygons &expolys, const Point &p) { for (ExPolygon &expoly : expolys) expoly.translate(p); } + inline void polygons_append(Polygons &dst, const ExPolygon &src) { dst.reserve(dst.size() + src.holes.size() + 1); @@ -467,6 +482,7 @@ bool has_duplicate_points(const ExPolygons &expolys); // Return True when erase some otherwise False. bool remove_same_neighbor(ExPolygons &expolys); + bool remove_sticks(ExPolygon &poly); void keep_largest_contour_only(ExPolygons &polygons); @@ -480,6 +496,7 @@ bool remove_small_and_small_holes(ExPolygons &expolygons, double min_area // start Boost #include + namespace boost { namespace polygon { template <> struct polygon_traits { diff --git a/src/libslic3r/ExPolygonsIndex.cpp b/src/libslic3r/ExPolygonsIndex.cpp index 9765317..c627b9b 100644 --- a/src/libslic3r/ExPolygonsIndex.cpp +++ b/src/libslic3r/ExPolygonsIndex.cpp @@ -1,4 +1,13 @@ #include "ExPolygonsIndex.hpp" + +#include +#include +#include + +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Exception.hpp" +#include "libslic3r/Polygon.hpp" + using namespace Slic3r; // IMPROVE: use one dimensional vector for polygons offset with searching by std::lower_bound diff --git a/src/libslic3r/ExPolygonsIndex.hpp b/src/libslic3r/ExPolygonsIndex.hpp index b46fd50..720dbdc 100644 --- a/src/libslic3r/ExPolygonsIndex.hpp +++ b/src/libslic3r/ExPolygonsIndex.hpp @@ -1,7 +1,11 @@ #ifndef slic3r_ExPolygonsIndex_hpp_ #define slic3r_ExPolygonsIndex_hpp_ +#include +#include + #include "ExPolygon.hpp" + namespace Slic3r { /// diff --git a/src/libslic3r/Extruder.cpp b/src/libslic3r/Extruder.cpp index 460be0e..a2aa376 100644 --- a/src/libslic3r/Extruder.cpp +++ b/src/libslic3r/Extruder.cpp @@ -1,6 +1,13 @@ #include "Extruder.hpp" -#include "GCode/GCodeWriter.hpp" + +#include +#include +#include + +#include "libslic3r/GCode/GCodeWriter.hpp" #include "PrintConfig.hpp" +#include "libslic3r/Exception.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { @@ -132,7 +139,6 @@ double Extruder::retract_length() const return m_config->retract_length.get_at(m_id); } - int Extruder::retract_speed() const { return int(floor(m_config->retract_speed.get_at(m_id)+0.5)); diff --git a/src/libslic3r/Extruder.hpp b/src/libslic3r/Extruder.hpp index 7c37f19..ee27b73 100644 --- a/src/libslic3r/Extruder.hpp +++ b/src/libslic3r/Extruder.hpp @@ -1,6 +1,8 @@ #ifndef slic3r_Extruder_hpp_ #define slic3r_Extruder_hpp_ +#include + #include "libslic3r.h" #include "Point.hpp" diff --git a/src/libslic3r/ExtrusionEntity.cpp b/src/libslic3r/ExtrusionEntity.cpp index 179058f..e33376d 100644 --- a/src/libslic3r/ExtrusionEntity.cpp +++ b/src/libslic3r/ExtrusionEntity.cpp @@ -1,17 +1,18 @@ #include "ExtrusionEntity.hpp" + +#include +#include + #include "ExtrusionEntityCollection.hpp" -#include "ExPolygon.hpp" #include "ClipperUtils.hpp" #include "Exception.hpp" -#include "Extruder.hpp" #include "Flow.hpp" -#include -#include -#include - +#include "libslic3r/Polygon.hpp" +#include "libslic3r/Polyline.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { - + void ExtrusionPath::intersect_expolygons(const ExPolygons &collection, ExtrusionEntityCollection* retval) const { this->_inflate_collection(intersection_pl(Polylines{ polyline }, collection), retval); @@ -351,5 +352,4 @@ double ExtrusionLoop::min_mm3_per_mm() const return min_mm3_per_mm; } - } diff --git a/src/libslic3r/ExtrusionEntity.hpp b/src/libslic3r/ExtrusionEntity.hpp index 31ae8ea..bb4b86d 100644 --- a/src/libslic3r/ExtrusionEntity.hpp +++ b/src/libslic3r/ExtrusionEntity.hpp @@ -1,20 +1,30 @@ #ifndef slic3r_ExtrusionEntity_hpp_ #define slic3r_ExtrusionEntity_hpp_ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "libslic3r.h" #include "ExtrusionRole.hpp" #include "Flow.hpp" #include "Polygon.hpp" #include "Polyline.hpp" - -#include -#include -#include -#include +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Point.hpp" namespace Slic3r { class ExPolygon; + using ExPolygons = std::vector; class ExtrusionEntityCollection; class Extruder; @@ -59,6 +69,7 @@ public: using ExtrusionEntitiesPtr = std::vector; +// Const reference for ordering extrusion entities without having to modify them. class ExtrusionEntityReference final { public: @@ -107,25 +118,42 @@ struct OverhangAttributes { float proximity_to_curled_lines; //value between 0 and 1 }; +inline bool operator==(const OverhangAttributes &lhs, const OverhangAttributes &rhs) { + if (std::abs(lhs.start_distance_from_prev_layer - rhs.start_distance_from_prev_layer) > std::numeric_limits::epsilon()) { + return false; + } + if (std::abs(lhs.end_distance_from_prev_layer - rhs.end_distance_from_prev_layer) > std::numeric_limits::epsilon()) { + return false; + } + if (std::abs(lhs.proximity_to_curled_lines - rhs.proximity_to_curled_lines) > std::numeric_limits::epsilon()) { + return false; + } + return true; +} + struct ExtrusionAttributes : ExtrusionFlow { ExtrusionAttributes() = default; ExtrusionAttributes(ExtrusionRole role) : role{ role } {} ExtrusionAttributes(ExtrusionRole role, const Flow &flow) : role{ role }, ExtrusionFlow{ flow } {} ExtrusionAttributes(ExtrusionRole role, const ExtrusionFlow &flow) : role{ role }, ExtrusionFlow{ flow } {} + ExtrusionAttributes(ExtrusionRole role, const ExtrusionFlow &flow, const bool maybe_self_crossing) + : role{role}, ExtrusionFlow{flow}, maybe_self_crossing(maybe_self_crossing) {} // What is the role / purpose of this extrusion? ExtrusionRole role{ ExtrusionRole::None }; - // Volumetric velocity. mm^3 of plastic per mm of linear head motion. Used by the G-code generator. + bool maybe_self_crossing{false}; + // OVerhangAttributes are currently computed for perimeters if dynamic overhangs are enabled. + // They are used to control fan and print speed in export. std::optional overhang_attributes; }; - // Width of the extrusion, used for visualization purposes. + inline bool operator==(const ExtrusionAttributes &lhs, const ExtrusionAttributes &rhs) { return static_cast(lhs) == static_cast(rhs) && - lhs.role == rhs.role; + lhs.role == rhs.role && lhs.overhang_attributes == rhs.overhang_attributes; } - // Height of the extrusion, used for visualization purposes. + class ExtrusionPath : public ExtrusionEntity { public: @@ -160,6 +188,7 @@ public: void clip_end(double distance); void simplify(double tolerance); double length() const override; + const ExtrusionAttributes& attributes() const { return m_attributes; } ExtrusionRole role() const override { return m_attributes.role; } float width() const { return m_attributes.width; } @@ -168,21 +197,22 @@ public: // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm. double min_mm3_per_mm() const override { return m_attributes.mm3_per_mm; } std::optional& overhang_attributes_mutable() { return m_attributes.overhang_attributes; } + // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. - void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const override; + void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const override; // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion spacing. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. // Useful to calculate area of an infill, which has been really filled in by a 100% rectilinear infill. - void polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const override; - Polygons polygons_covered_by_width(const float scaled_epsilon = 0.f) const + void polygons_covered_by_spacing(Polygons &out, const float scaled_epsilon) const override; + Polygons polygons_covered_by_width(const float scaled_epsilon = 0.f) const { Polygons out; this->polygons_covered_by_width(out, scaled_epsilon); return out; } - Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const + Polygons polygons_covered_by_spacing(const float scaled_epsilon = 0.f) const { Polygons out; this->polygons_covered_by_spacing(out, scaled_epsilon); return out; } - // Minimum volumetric velocity of this extrusion entity. Used by the constant nozzle pressure algorithm. - Polyline as_polyline() const override { return this->polyline; } - void collect_polylines(Polylines &dst) const override { if (! this->polyline.empty()) dst.emplace_back(this->polyline); } - void collect_points(Points &dst) const override { append(dst, this->polyline.points); } + + Polyline as_polyline() const override { return this->polyline; } + void collect_polylines(Polylines &dst) const override { if (! this->polyline.empty()) dst.emplace_back(this->polyline); } + void collect_points(Points &dst) const override { append(dst, this->polyline.points); } double total_volume() const override { return m_attributes.mm3_per_mm * unscale(length()); } //w21 void set_width(float set_val) { m_attributes.width = set_val; } @@ -190,7 +220,7 @@ public: void set_mm3_per_mm(float set_val) { m_attributes.mm3_per_mm = set_val; } private: - void _inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const; + void _inflate_collection(const Polylines &polylines, ExtrusionEntityCollection* collection) const; ExtrusionAttributes m_attributes; }; @@ -201,6 +231,7 @@ public: ExtrusionPathOriented(const ExtrusionAttributes &attribs) : ExtrusionPath(attribs) {} ExtrusionPathOriented(const Polyline &polyline, const ExtrusionAttributes &attribs) : ExtrusionPath(polyline, attribs) {} ExtrusionPathOriented(Polyline &&polyline, const ExtrusionAttributes &attribs) : ExtrusionPath(std::move(polyline), attribs) {} + ExtrusionEntity* clone() const override { return new ExtrusionPathOriented(*this); } // Create a new object, initialize it with this object using the move semantics. ExtrusionEntity* clone_move() override { return new ExtrusionPathOriented(std::move(*this)); } @@ -266,7 +297,7 @@ class ExtrusionLoop : public ExtrusionEntity { public: ExtrusionPaths paths; - + ExtrusionLoop() = default; ExtrusionLoop(ExtrusionLoopRole role) : m_loop_role(role) {} ExtrusionLoop(const ExtrusionPaths &paths, ExtrusionLoopRole role = elrDefault) : paths(paths), m_loop_role(role) {} @@ -283,15 +314,18 @@ public: double area() const; bool is_counter_clockwise() const { return this->area() > 0; } bool is_clockwise() const { return this->area() < 0; } - void reverse() override; + // Reverse shall never be called on ExtrusionLoop using a virtual function call, it is most likely never what one wants, + // as this->can_reverse() returns false for an ExtrusionLoop. + void reverse() override; + // Used by PerimeterGenerator to reorient extrusion loops. void reverse_loop(); - const Point& first_point() const override { return this->paths.front().polyline.points.front(); } - const Point& last_point() const override { assert(this->first_point() == this->paths.back().polyline.points.back()); return this->first_point(); } - const Point& middle_point() const override { auto& path = this->paths[this->paths.size() / 2]; return path.polyline.points[path.polyline.size() / 2]; } - Polygon polygon() const; - double length() const override; - bool split_at_vertex(const Point &point, const double scaled_epsilon = scaled(0.001)); - void split_at(const Point &point, bool prefer_non_overhang, const double scaled_epsilon = scaled(0.001)); + const Point& first_point() const override { return this->paths.front().polyline.points.front(); } + const Point& last_point() const override { assert(this->first_point() == this->paths.back().polyline.points.back()); return this->first_point(); } + const Point& middle_point() const override { auto& path = this->paths[this->paths.size() / 2]; return path.polyline.points[path.polyline.size() / 2]; } + Polygon polygon() const; + double length() const override; + bool split_at_vertex(const Point &point, const double scaled_epsilon = scaled(0.001)); + void split_at(const Point &point, bool prefer_non_overhang, const double scaled_epsilon = scaled(0.001)); struct ClosestPathPoint { size_t path_idx; size_t segment_idx; @@ -379,8 +413,6 @@ inline void extrusion_entities_append_paths(ExtrusionEntitiesPtr &dst, Polylines polylines.clear(); } - - inline void extrusion_entities_append_loops(ExtrusionEntitiesPtr &dst, Polygons &&loops, const ExtrusionAttributes &attributes) { dst.reserve(dst.size() + loops.size()); diff --git a/src/libslic3r/ExtrusionEntityCollection.cpp b/src/libslic3r/ExtrusionEntityCollection.cpp index 67d84fb..9b18326 100644 --- a/src/libslic3r/ExtrusionEntityCollection.cpp +++ b/src/libslic3r/ExtrusionEntityCollection.cpp @@ -1,8 +1,9 @@ #include "ExtrusionEntityCollection.hpp" -#include "ShortestPath.hpp" + #include -#include -#include +#include + +#include "libslic3r/ExtrusionEntity.hpp" namespace Slic3r { @@ -85,7 +86,6 @@ void ExtrusionEntityCollection::remove(size_t i) this->entities.erase(this->entities.begin() + i); } - void ExtrusionEntityCollection::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const { for (const ExtrusionEntity *entity : this->entities) diff --git a/src/libslic3r/ExtrusionEntityCollection.hpp b/src/libslic3r/ExtrusionEntityCollection.hpp index 3d6ffba..36632bf 100644 --- a/src/libslic3r/ExtrusionEntityCollection.hpp +++ b/src/libslic3r/ExtrusionEntityCollection.hpp @@ -1,9 +1,19 @@ #ifndef slic3r_ExtrusionEntityCollection_hpp_ #define slic3r_ExtrusionEntityCollection_hpp_ +#include +#include +#include +#include +#include + #include "libslic3r.h" #include "Exception.hpp" #include "ExtrusionEntity.hpp" +#include "libslic3r/ExtrusionRole.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/Polyline.hpp" namespace Slic3r { diff --git a/src/libslic3r/ExtrusionRole.hpp b/src/libslic3r/ExtrusionRole.hpp index 6528954..c746082 100644 --- a/src/libslic3r/ExtrusionRole.hpp +++ b/src/libslic3r/ExtrusionRole.hpp @@ -90,6 +90,8 @@ struct ExtrusionRole : public ExtrusionRoleModifiers bool is_support_base() const { return this->is_support() && ! this->is_external(); } bool is_support_interface() const { return this->is_support() && this->is_external(); } bool is_mixed() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Mixed); } + + // Brim is currently marked as skirt. bool is_skirt() const { return this->ExtrusionRoleModifiers::has(ExtrusionRoleModifier::Skirt); } }; diff --git a/src/libslic3r/ExtrusionSimulator.cpp b/src/libslic3r/ExtrusionSimulator.cpp index fd3d925..58acf90 100644 --- a/src/libslic3r/ExtrusionSimulator.cpp +++ b/src/libslic3r/ExtrusionSimulator.cpp @@ -3,18 +3,23 @@ //#undef SLIC3R_DEBUG //#define NDEBUG -#include -#include - #include #include #include #include - #include +#include +#include +#include +#include +#include +#include +#include #include "libslic3r.h" #include "ExtrusionSimulator.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/ExtrusionEntity.hpp" #ifndef M_PI #define M_PI 3.1415926535897932384626433832795 @@ -695,14 +700,16 @@ void gcode_spread_points( } } */ - float area_total = 0; - float volume_total = 0; + + float area_total = 0; + float volume_total = 0; size_t n_cells = 0; #if 0 - float volume_excess = 0; - float volume_deficit = 0; - float area_circle_total = 0; + float volume_excess = 0; + float volume_deficit = 0; + float area_circle_total = 0; + // The intermediate lines. for (int j = row_first; j < row_last; ++ j) { const std::pair &span1 = spans[j]; @@ -756,9 +763,11 @@ void gcode_spread_points( cell.volume = acc[j][i]; cell.area = mask[j][i]; assert(cell.area >= 0.f && cell.area <= 1.000001f); + #if 0 - area_circle_total += area; + area_circle_total += area; #endif + if (cell.area < area) cell.area = area; cell.fraction_covered = std::clamp((cell.area > 0) ? (area / cell.area) : 0, 0.f, 1.f); @@ -768,6 +777,7 @@ void gcode_spread_points( } float cell_height = cell.volume / cell.area; cell.excess_height = cell_height - height_target; + #if 0 area_circle_total += area; if (cell.excess_height > 0.f) @@ -775,6 +785,7 @@ void gcode_spread_points( else volume_deficit -= cell.excess_height * cell.area * cell.fraction_covered; #endif + volume_total += cell.volume * cell.fraction_covered; area_total += cell.area * cell.fraction_covered; } diff --git a/src/libslic3r/ExtrusionSimulator.hpp b/src/libslic3r/ExtrusionSimulator.hpp index 0404067..a3d3f03 100644 --- a/src/libslic3r/ExtrusionSimulator.hpp +++ b/src/libslic3r/ExtrusionSimulator.hpp @@ -4,6 +4,7 @@ #include "libslic3r.h" #include "ExtrusionEntity.hpp" #include "BoundingBox.hpp" +#include "libslic3r/Point.hpp" namespace Slic3r { diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index ca923cc..b805137 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -1,6 +1,15 @@ -#include -#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "../ClipperUtils.hpp" #include "../Geometry.hpp" @@ -10,24 +19,36 @@ #include "../Surface.hpp" // for Arachne based infills #include "../PerimeterGenerator.hpp" - #include "FillBase.hpp" #include "FillRectilinear.hpp" #include "FillLightning.hpp" -#include "FillConcentric.hpp" #include "FillEnsuring.hpp" -#include "Polygon.hpp" -//w21 -#include "../ShortestPath.hpp" -//w11 -//w29 +#include "libslic3r/Polygon.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/ExtrusionEntity.hpp" +#include "libslic3r/ExtrusionEntityCollection.hpp" +#include "libslic3r/ExtrusionRole.hpp" +#include "libslic3r/Flow.hpp" +#include "libslic3r/LayerRegion.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polyline.hpp" +#include "libslic3r/libslic3r.h" +#include "libslic3r/ShortestPath.hpp" + +////w11 +////w29 #include "FillConcentricInternal.hpp" - -#include "LayerRegion.hpp" - +#include "FillConcentric.hpp" #define NARROW_INFILL_AREA_THRESHOLD 3 #define NARROW_INFILL_AREA_THRESHOLD_MIN 0.5 namespace Slic3r { +namespace FillAdaptive { +struct Octree; +} // namespace FillAdaptive +namespace FillLightning { +class Generator; +} // namespace FillLightning //static constexpr const float NarrowInfillAreaThresholdMM = 3.f; @@ -379,6 +400,7 @@ std::vector group_fills(const Layer &layer) } } } + return surface_fills; } @@ -502,10 +524,6 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: { this->clear_fills(); -#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); const Slic3r::BoundingBox bbox = this->object()->bounding_box(); const auto resolution = this->object()->print()->config().gcode_resolution.value; @@ -549,6 +567,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: } 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.; @@ -571,17 +590,17 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: // apply half spacing using this flow's own spacing and generate infill FillParams params; - params.density = float(0.01 * surface_fill.params.density); - params.dont_adjust = false; // surface_fill.params.dont_adjust; - params.anchor_length = surface_fill.params.anchor_length; - params.anchor_length_max = surface_fill.params.anchor_length_max; - params.resolution = resolution; - //w14 - //w29 + params.density = float(0.01 * surface_fill.params.density); + params.dont_adjust = false; // surface_fill.params.dont_adjust; + params.anchor_length = surface_fill.params.anchor_length; + params.anchor_length_max = surface_fill.params.anchor_length_max; + params.resolution = resolution; + //w21 params.use_arachne = (perimeter_generator == PerimeterGeneratorType::Arachne && surface_fill.params.pattern == ipConcentric) || surface_fill.params.pattern == ipEnsuring || surface_fill.params.pattern == ipConcentric; - params.layer_height = layerm.layer()->height; - //w29 - params.flow = surface_fill.params.flow; + params.layer_height = layerm.layer()->height; + params.prefer_clockwise_movements = this->object()->print()->config().prefer_clockwise_movements; + //w21 + params.flow = surface_fill.params.flow; params.extrusion_role = surface_fill.params.extrusion_role; params.using_internal_flow = !surface_fill.surface.is_solid() && !surface_fill.params.bridge; @@ -593,17 +612,9 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: surface_fill.surface.expolygon = std::move(expoly); Polylines polylines; ThickPolylines thick_polylines; - // w14 - //w29 - /* if (this->object()->config().detect_narrow_internal_solid_infill && - (surface_fill.params.pattern == ipConcentricInternal || surface_fill.params.pattern == ipEnsuring)) { - layerm.region().config().infill_overlap.percent ? f->overlap = layerm.region().config().perimeter_extrusion_width * - layerm.region().config().infill_overlap.value / 100 * (-1) : - f->overlap = float(layerm.region().config().infill_overlap.value); - } else - f->overlap = 0;*/ //w29 f->fill_surface_extrusion(&surface_fill.surface, params, polylines, thick_polylines); + if (!polylines.empty() || !thick_polylines.empty()) { // calculate actual flow from spacing (which might have been adjusted by the infill // pattern generator) @@ -698,8 +709,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: } insert_fills_into_islands(*this, uint32_t(surface_fill.region_id), fill_begin, uint32_t(layerm.fills().size())); } - } - + } } for (LayerSlice &lslice : this->lslices_ex) @@ -924,8 +934,9 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Oc case ipHilbertCurve: case ipArchimedeanChords: case ipOctagramSpiral: - //w14 - case ipConcentricInternal: break; + case ipZigZag: break; + // //w14 + // case ipConcentricInternal: break; } // Create the filler object. @@ -1034,8 +1045,8 @@ void Layer::make_ironing() bool operator==(const IroningParams &rhs) const { return this->extruder == rhs.extruder && this->just_infill == rhs.just_infill && - this->line_spacing == rhs.line_spacing && this->height == rhs.height && this->speed == rhs.speed && - this->angle == rhs.angle + this->line_spacing == rhs.line_spacing && this->height == rhs.height && this->speed == rhs.speed && + this->angle == rhs.angle //w33 && this->pattern == rhs.pattern; } @@ -1094,15 +1105,14 @@ void Layer::make_ironing() } std::sort(by_extruder.begin(), by_extruder.end()); - //w33 - //FillRectilinear fill; + FillRectilinear fill; FillParams fill_params; - //fill.set_bounding_box(this->object()->bounding_box()); + fill.set_bounding_box(this->object()->bounding_box()); // Layer ID is used for orienting the infill in alternating directions. // Layer::id() returns layer ID including raft layers, subtract them to make the infill direction independent // from raft. //FIXME ironing does not take fill angle into account. Shall it? Does it matter? - //w33 + //w33 //fill.layer_id = this->id() - this->object()->get_layer(0)->id(); //fill.z = this->print_z; //fill.overlap = 0; @@ -1207,7 +1217,7 @@ void Layer::make_ironing() Polylines polylines; try { assert(!fill_params.use_arachne); - //w33 + //w33 //polylines = fill.fill_surface(&surface_fill, fill_params); polylines = f->fill_surface(&surface_fill, fill_params); } catch (InfillFailedException &) { diff --git a/src/libslic3r/Fill/Fill3DHoneycomb.cpp b/src/libslic3r/Fill/Fill3DHoneycomb.cpp index 23444b6..b927aa5 100644 --- a/src/libslic3r/Fill/Fill3DHoneycomb.cpp +++ b/src/libslic3r/Fill/Fill3DHoneycomb.cpp @@ -1,8 +1,17 @@ #include "../ClipperUtils.hpp" #include "../ShortestPath.hpp" #include "../Surface.hpp" - +#include +#include +#include +#include +#include #include "Fill3DHoneycomb.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/Fill/FillBase.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { diff --git a/src/libslic3r/Fill/Fill3DHoneycomb.hpp b/src/libslic3r/Fill/Fill3DHoneycomb.hpp index 3f23e0f..fdf5fb7 100644 --- a/src/libslic3r/Fill/Fill3DHoneycomb.hpp +++ b/src/libslic3r/Fill/Fill3DHoneycomb.hpp @@ -2,12 +2,15 @@ #define slic3r_Fill3DHoneycomb_hpp_ #include +#include -#include "../libslic3r.h" - +#include "libslic3r/libslic3r.h" #include "FillBase.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Polyline.hpp" namespace Slic3r { +class Point; class Fill3DHoneycomb : public Fill { @@ -16,8 +19,8 @@ public: ~Fill3DHoneycomb() override {} // require bridge flow since most of this pattern hangs in air - //w36 - + bool use_bridge_flow() const override { return true; } + bool is_self_crossing() override { return false; } protected: void _fill_surface_single( diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index f935d0e..670c7e2 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -1,3 +1,17 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "../ClipperUtils.hpp" #include "../ExPolygon.hpp" #include "../Surface.hpp" @@ -5,25 +19,20 @@ #include "../Layer.hpp" #include "../Print.hpp" #include "../ShortestPath.hpp" - -#include "FillAdaptive.hpp" - -// for indexed_triangle_set -#include - -#include -#include -#include -#include +#include "libslic3r/Fill/FillAdaptive.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/Fill/FillBase.hpp" +#include "libslic3r/Flow.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "tcbspan/span.hpp" // Boost pool: Don't use mutexes to synchronize memory allocation. #define BOOST_POOL_NO_MT #include - #include -#include #include -#include namespace Slic3r { @@ -504,10 +513,6 @@ static void generate_infill_lines_recursive( } } -#ifndef NDEBUG -// #define ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT -#endif - #ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT static void export_infill_lines_to_svg(const ExPolygon &expoly, const Polylines &polylines, const std::string &path, const Points &pts = Points()) { diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp index 0578cc3..4a0b420 100644 --- a/src/libslic3r/Fill/FillAdaptive.hpp +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -11,7 +11,16 @@ #ifndef slic3r_FillAdaptive_hpp_ #define slic3r_FillAdaptive_hpp_ +#include +#include +#include +#include + #include "FillBase.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polyline.hpp" +#include "libslic3r/libslic3r.h" struct indexed_triangle_set; @@ -23,6 +32,7 @@ namespace FillAdaptive { struct Octree; + // To keep the definition of Octree opaque, we have to define a custom deleter. struct OctreeDeleter { void operator()(Octree *p); }; using OctreePtr = std::unique_ptr; @@ -71,6 +81,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 66aeb12..1037bf7 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -1,15 +1,20 @@ #include #include +#include +#include +#include +#include +#include +#include -#include "../ClipperUtils.hpp" -#include "../EdgeGrid.hpp" -#include "../Geometry.hpp" -#include "../Geometry/Circle.hpp" -#include "../Point.hpp" -#include "../PrintConfig.hpp" -#include "../Surface.hpp" -#include "../libslic3r.h" - +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/EdgeGrid.hpp" +#include "libslic3r/Geometry.hpp" +#include "libslic3r/Geometry/Circle.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/Surface.hpp" +#include "libslic3r/libslic3r.h" #include "FillBase.hpp" #include "FillConcentric.hpp" #include "FillHoneycomb.hpp" @@ -21,6 +26,10 @@ #include "FillAdaptive.hpp" #include "FillLightning.hpp" #include "FillEnsuring.hpp" +#include "libslic3r/Config.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/ShortestPath.hpp" + //w29 #include "FillConcentricInternal.hpp" //w32 @@ -1076,9 +1085,6 @@ void mark_boundary_segments_touching_infill( BoundingBoxf bbox_seg; bbox_seg.merge(seg_pt1); bbox_seg.merge(seg_pt2); -#ifdef INFILL_DEBUG_OUTPUT - //if (this->infill_bbox.overlap(bbox_seg)) this->perimeter_overlaps.push_back({ segment.first, segment.second }); -#endif // INFILL_DEBUG_OUTPUT if (this->infill_bbox.overlap(bbox_seg) && line_rounded_thick_segment_collision(seg_pt1, seg_pt2, *this->infill_pt1, *this->infill_pt2, this->radius, interval)) { // The boundary segment intersects with the infill segment thickened by radius. // Interval is specified in Euclidian length from seg_pt1 to seg_pt2. @@ -1234,9 +1240,6 @@ void mark_boundary_segments_touching_infill( assert(grid.bbox().contains(b.cast())); grid.visit_cells_intersecting_line(a.cast(), b.cast(), visitor); #endif -#ifdef INFILL_DEBUG_OUTPUT -// export_infill_to_svg(boundary, boundary_parameters, boundary_intersections, infill, distance_colliding * 2, debug_out_path("%s-%03d-%03d-%03d.svg", "FillBase-mark_boundary_segments_touching_infill-step", iRun, iStep, int(point_idx)), { polyline }); -#endif // INFILL_DEBUG_OUTPUT } #ifdef INFILL_DEBUG_OUTPUT Polylines perimeter_overlaps; diff --git a/src/libslic3r/Fill/FillBase.hpp b/src/libslic3r/Fill/FillBase.hpp index b3907b3..9c71a49 100644 --- a/src/libslic3r/Fill/FillBase.hpp +++ b/src/libslic3r/Fill/FillBase.hpp @@ -5,16 +5,27 @@ #include #include #include +#include +#include #include - #include +#include +#include +#include +#include +#include +#include + +#include "libslic3r/libslic3r.h" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/Exception.hpp" +#include "libslic3r/Utils.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/Polyline.hpp" -#include "../libslic3r.h" -#include "../BoundingBox.hpp" -#include "../Exception.hpp" -#include "../Utils.hpp" -#include "../ExPolygon.hpp" -#include "../PrintConfig.hpp" //w29 #include "../ExtrusionEntity.hpp" #include "../ExtrusionEntityCollection.hpp" @@ -22,6 +33,9 @@ namespace Slic3r { class Surface; +class PrintConfig; +class PrintObjectConfig; + enum InfillPattern : int; namespace FillAdaptive { @@ -66,6 +80,9 @@ struct FillParams bool use_arachne { false }; // Layer height for Concentric infill with Arachne. coordf_t layer_height { 0.f }; + + // For infills that produce closed loops to force printing those loops clockwise. + bool prefer_clockwise_movements { false }; //w29 Flow flow; ExtrusionRole extrusion_role{ExtrusionRole::None}; @@ -121,6 +138,11 @@ public: // 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 ¶ms); diff --git a/src/libslic3r/Fill/FillConcentric.cpp b/src/libslic3r/Fill/FillConcentric.cpp index 2a5e629..6ef6657 100644 --- a/src/libslic3r/Fill/FillConcentric.cpp +++ b/src/libslic3r/Fill/FillConcentric.cpp @@ -1,11 +1,19 @@ +#include +#include +#include +#include + #include "../ClipperUtils.hpp" #include "../ExPolygon.hpp" -#include "../Surface.hpp" -#include "Arachne/WallToolPaths.hpp" - +#include "libslic3r/Arachne/WallToolPaths.hpp" #include "FillConcentric.hpp" - -#include +#include "libslic3r/Arachne/utils/ExtrusionLine.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/ExtrusionEntity.hpp" +#include "libslic3r/Fill/FillBase.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { @@ -49,14 +57,19 @@ void FillConcentric::_fill_surface_single( // clip the paths to prevent the extruder from getting exactly on the first point of the loop // Keep valid paths only. size_t j = iPathFirst; - for (size_t i = iPathFirst; i < polylines_out.size(); ++ i) { + for (size_t i = iPathFirst; i < polylines_out.size(); ++i) { polylines_out[i].clip_end(this->loop_clipping); if (polylines_out[i].is_valid()) { + if (params.prefer_clockwise_movements) + polylines_out[i].reverse(); + if (j < i) polylines_out[j] = std::move(polylines_out[i]); - ++ j; + + ++j; } } + if (j < polylines_out.size()) polylines_out.erase(polylines_out.begin() + int(j), polylines_out.end()); //TODO: return ExtrusionLoop objects to get better chained paths, @@ -87,6 +100,7 @@ void FillConcentric::_fill_surface_single(const FillParams ¶ms, for (Arachne::VariableWidthLines &loop : loops) { if (loop.empty()) continue; + for (const Arachne::ExtrusionLine &wall : loop) all_extrusions.emplace_back(&wall); } @@ -99,8 +113,14 @@ void FillConcentric::_fill_surface_single(const FillParams ¶ms, continue; ThickPolyline thick_polyline = Arachne::to_thick_polyline(*extrusion); - if (extrusion->is_closed) + if (extrusion->is_closed) { + // Arachne produces contour with clockwise orientation and holes with counterclockwise orientation. + if (const bool extrusion_reverse = params.prefer_clockwise_movements ? !extrusion->is_contour() : extrusion->is_contour(); extrusion_reverse) + thick_polyline.reverse(); + thick_polyline.start_at_index(nearest_point_index(thick_polyline.points, last_pos)); + } + thick_polylines_out.emplace_back(std::move(thick_polyline)); last_pos = thick_polylines_out.back().last_point(); } @@ -118,7 +138,6 @@ void FillConcentric::_fill_surface_single(const FillParams ¶ms, } if (j < thick_polylines_out.size()) thick_polylines_out.erase(thick_polylines_out.begin() + int(j), thick_polylines_out.end()); - //reorder_by_shortest_traverse(thick_polylines_out); } else { Polylines polylines; this->_fill_surface_single(params, thickness_layers, direction, expolygon, polylines); diff --git a/src/libslic3r/Fill/FillConcentric.hpp b/src/libslic3r/Fill/FillConcentric.hpp index c059cc0..ec975bb 100644 --- a/src/libslic3r/Fill/FillConcentric.hpp +++ b/src/libslic3r/Fill/FillConcentric.hpp @@ -1,14 +1,20 @@ #ifndef slic3r_FillConcentric_hpp_ #define slic3r_FillConcentric_hpp_ +#include + #include "FillBase.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Polyline.hpp" namespace Slic3r { +class Point; 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.cpp b/src/libslic3r/Fill/FillConcentricInternal.cpp index d34fa4b..ee70c46 100644 --- a/src/libslic3r/Fill/FillConcentricInternal.cpp +++ b/src/libslic3r/Fill/FillConcentricInternal.cpp @@ -1,7 +1,7 @@ #include "../ClipperUtils.hpp" #include "../ExPolygon.hpp" #include "../Surface.hpp" -#include "Arachne/WallToolPaths.hpp" +#include "../Arachne/WallToolPaths.hpp" #include "FillConcentricInternal.hpp" diff --git a/src/libslic3r/Fill/FillConcentricInternal.hpp b/src/libslic3r/Fill/FillConcentricInternal.hpp index 194910c..20bf45e 100644 --- a/src/libslic3r/Fill/FillConcentricInternal.hpp +++ b/src/libslic3r/Fill/FillConcentricInternal.hpp @@ -149,6 +149,7 @@ public: return paths; } + 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 17a0df7..112eaba 100644 --- a/src/libslic3r/Fill/FillCrossHatch.hpp +++ b/src/libslic3r/Fill/FillCrossHatch.hpp @@ -12,7 +12,7 @@ namespace Slic3r { class FillCrossHatch : public Fill { public: - Fill *clone() const override { return new FillCrossHatch(*this); }; + //Fill *clone() const override { return new FillCrossHatch(*this); }; ~FillCrossHatch() override {} protected: diff --git a/src/libslic3r/Fill/FillEnsuring.cpp b/src/libslic3r/Fill/FillEnsuring.cpp index f1b1b3e..c14457c 100644 --- a/src/libslic3r/Fill/FillEnsuring.cpp +++ b/src/libslic3r/Fill/FillEnsuring.cpp @@ -1,28 +1,31 @@ -#include "../ClipperUtils.hpp" -#include "../ShortestPath.hpp" -#include "../Arachne/WallToolPaths.hpp" - -#include "AABBTreeLines.hpp" -#include "Algorithm/PathSorting.hpp" -#include "BoundingBox.hpp" -#include "ExPolygon.hpp" #include "FillEnsuring.hpp" -#include "KDTreeIndirect.hpp" -#include "Line.hpp" -#include "Point.hpp" -#include "Polygon.hpp" -#include "Polyline.hpp" -#include "SVG.hpp" -#include "libslic3r.h" -//w11 -#include "../PrintConfig.hpp" + #include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include +#include + +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/ShortestPath.hpp" +#include "libslic3r/Arachne/WallToolPaths.hpp" +#include "libslic3r/AABBTreeLines.hpp" +#include "libslic3r/Algorithm/PathSorting.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/KDTreeIndirect.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/Polyline.hpp" +#include "libslic3r/libslic3r.h" +#include "libslic3r/Arachne/utils/ExtrusionLine.hpp" +#include "libslic3r/Fill/FillBase.hpp" +#include "libslic3r/Surface.hpp" namespace Slic3r { @@ -315,19 +318,21 @@ ThickPolylines make_fill_polylines( for (Arachne::VariableWidthLines &loop : loops) { if (loop.empty()) continue; + for (const Arachne::ExtrusionLine &wall : loop) all_extrusions.emplace_back(&wall); } for (const Arachne::ExtrusionLine *extrusion : all_extrusions) { - if (extrusion->junctions.size() < 2) { + if (extrusion->junctions.size() < 2) continue; - } + ThickPolyline thick_polyline = Arachne::to_thick_polyline(*extrusion); if (extrusion->is_closed) { thick_polyline.start_at_index(nearest_point_index(thick_polyline.points, ex_bb.min)); thick_polyline.clip_end(scaled_spacing * 0.5); } + if (thick_polyline.is_valid() && thick_polyline.length() > 0 && thick_polyline.points.size() > 1) { thick_polylines.push_back(thick_polyline); } diff --git a/src/libslic3r/Fill/FillEnsuring.hpp b/src/libslic3r/Fill/FillEnsuring.hpp index c20c93a..5fc3a6a 100644 --- a/src/libslic3r/Fill/FillEnsuring.hpp +++ b/src/libslic3r/Fill/FillEnsuring.hpp @@ -3,8 +3,11 @@ #include "FillBase.hpp" #include "FillRectilinear.hpp" +#include "libslic3r/Polyline.hpp" namespace Slic3r { +class PrintRegionConfig; +class Surface; ThickPolylines make_fill_polylines( const Fill *fill, const Surface *surface, const FillParams ¶ms, bool stop_vibrations, bool fill_gaps, bool connect_extrusions); @@ -19,6 +22,7 @@ public: { return make_fill_polylines(this, surface, params, true, true, true); }; + bool is_self_crossing() override { return false; } protected: void fill_surface_single_arachne(const Surface &surface, const FillParams ¶ms, ThickPolylines &thick_polylines_out); diff --git a/src/libslic3r/Fill/FillGyroid.cpp b/src/libslic3r/Fill/FillGyroid.cpp index 5c555df..6ea6dde 100644 --- a/src/libslic3r/Fill/FillGyroid.cpp +++ b/src/libslic3r/Fill/FillGyroid.cpp @@ -1,11 +1,16 @@ -#include "../ClipperUtils.hpp" -#include "../ShortestPath.hpp" -#include "../Surface.hpp" #include #include -#include +#include +#include +#include "../ClipperUtils.hpp" +#include "../ShortestPath.hpp" #include "FillGyroid.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/Fill/FillBase.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { diff --git a/src/libslic3r/Fill/FillGyroid.hpp b/src/libslic3r/Fill/FillGyroid.hpp index ac66dfc..a9ca436 100644 --- a/src/libslic3r/Fill/FillGyroid.hpp +++ b/src/libslic3r/Fill/FillGyroid.hpp @@ -1,11 +1,15 @@ #ifndef slic3r_FillGyroid_hpp_ #define slic3r_FillGyroid_hpp_ -#include "../libslic3r.h" +#include +#include "libslic3r/libslic3r.h" #include "FillBase.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Polyline.hpp" namespace Slic3r { +class Point; class FillGyroid : public Fill { @@ -15,6 +19,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.cpp b/src/libslic3r/Fill/FillHoneycomb.cpp index 5dc2ed5..c756920 100644 --- a/src/libslic3r/Fill/FillHoneycomb.cpp +++ b/src/libslic3r/Fill/FillHoneycomb.cpp @@ -1,8 +1,12 @@ +#include +#include + #include "../ClipperUtils.hpp" #include "../ShortestPath.hpp" -#include "../Surface.hpp" - #include "FillHoneycomb.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/Fill/FillBase.hpp" +#include "libslic3r/Polygon.hpp" namespace Slic3r { diff --git a/src/libslic3r/Fill/FillHoneycomb.hpp b/src/libslic3r/Fill/FillHoneycomb.hpp index 707e976..f268330 100644 --- a/src/libslic3r/Fill/FillHoneycomb.hpp +++ b/src/libslic3r/Fill/FillHoneycomb.hpp @@ -1,11 +1,18 @@ #ifndef slic3r_FillHoneycomb_hpp_ #define slic3r_FillHoneycomb_hpp_ +#include +#include #include +#include +#include +#include -#include "../libslic3r.h" - +#include "libslic3r/libslic3r.h" #include "FillBase.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polyline.hpp" namespace Slic3r { @@ -13,6 +20,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.cpp b/src/libslic3r/Fill/FillLightning.cpp index 36a48e5..4cc4706 100644 --- a/src/libslic3r/Fill/FillLightning.cpp +++ b/src/libslic3r/Fill/FillLightning.cpp @@ -1,8 +1,10 @@ #include "../Print.hpp" #include "../ShortestPath.hpp" - -#include "FillLightning.hpp" #include "Lightning/Generator.hpp" +#include "libslic3r/Fill/FillLightning.hpp" +#include "libslic3r/Fill/FillBase.hpp" +#include "libslic3r/Fill/Lightning/Layer.hpp" +#include "libslic3r/Point.hpp" namespace Slic3r::FillLightning { diff --git a/src/libslic3r/Fill/FillLightning.hpp b/src/libslic3r/Fill/FillLightning.hpp index 3413995..dff211c 100644 --- a/src/libslic3r/Fill/FillLightning.hpp +++ b/src/libslic3r/Fill/FillLightning.hpp @@ -1,15 +1,24 @@ #ifndef slic3r_FillLightning_hpp_ #define slic3r_FillLightning_hpp_ +#include +#include +#include + #include "FillBase.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Polyline.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { class PrintObject; +class Point; namespace FillLightning { class Generator; + // To keep the definition of Octree opaque, we have to define a custom deleter. struct GeneratorDeleter { void operator()(Generator *p); }; using GeneratorPtr = std::unique_ptr; @@ -20,8 +29,10 @@ class Filler : public Slic3r::Fill { public: ~Filler() override = default; + bool is_self_crossing() override { return false; } Generator *generator { nullptr }; + protected: Fill* clone() const override { return new Filler(*this); } diff --git a/src/libslic3r/Fill/FillLine.cpp b/src/libslic3r/Fill/FillLine.cpp index b7ab543..3773f38 100644 --- a/src/libslic3r/Fill/FillLine.cpp +++ b/src/libslic3r/Fill/FillLine.cpp @@ -1,9 +1,15 @@ +#include +#include +#include +#include + #include "../ClipperUtils.hpp" #include "../ExPolygon.hpp" #include "../ShortestPath.hpp" -#include "../Surface.hpp" - #include "FillLine.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/Fill/FillBase.hpp" +#include "libslic3r/Polygon.hpp" namespace Slic3r { @@ -76,7 +82,7 @@ void FillLine::_fill_surface_single( size_t n_polylines_out_old = polylines_out.size(); // connect lines - if (! params.dont_connect() && ! polylines.empty()) { // prevent calling leftmost_point() on empty collections + if (! polylines.empty()) { // prevent calling leftmost_point() on empty collections // offset the expolygon by max(min_spacing/2, extra) ExPolygon expolygon_off; { diff --git a/src/libslic3r/Fill/FillLine.hpp b/src/libslic3r/Fill/FillLine.hpp index 9bf2b97..f87bbc6 100644 --- a/src/libslic3r/Fill/FillLine.hpp +++ b/src/libslic3r/Fill/FillLine.hpp @@ -1,9 +1,14 @@ #ifndef slic3r_FillLine_hpp_ #define slic3r_FillLine_hpp_ -#include "../libslic3r.h" +#include +#include "libslic3r/libslic3r.h" #include "FillBase.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polyline.hpp" namespace Slic3r { @@ -14,6 +19,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.cpp b/src/libslic3r/Fill/FillPlanePath.cpp index 183d4bf..ff3230d 100644 --- a/src/libslic3r/Fill/FillPlanePath.cpp +++ b/src/libslic3r/Fill/FillPlanePath.cpp @@ -1,8 +1,10 @@ +#include + #include "../ClipperUtils.hpp" #include "../ShortestPath.hpp" -#include "../Surface.hpp" - #include "FillPlanePath.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/Fill/FillBase.hpp" namespace Slic3r { diff --git a/src/libslic3r/Fill/FillPlanePath.hpp b/src/libslic3r/Fill/FillPlanePath.hpp index 4c6539f..f1e27d4 100644 --- a/src/libslic3r/Fill/FillPlanePath.hpp +++ b/src/libslic3r/Fill/FillPlanePath.hpp @@ -1,11 +1,19 @@ #ifndef slic3r_FillPlanePath_hpp_ #define slic3r_FillPlanePath_hpp_ +#include +#include #include +#include +#include +#include +#include -#include "../libslic3r.h" - +#include "libslic3r/libslic3r.h" #include "FillBase.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polyline.hpp" namespace Slic3r { @@ -17,6 +25,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 e27017b..8c6e61c 100644 --- a/src/libslic3r/Fill/FillRectilinear.cpp +++ b/src/libslic3r/Fill/FillRectilinear.cpp @@ -1,22 +1,26 @@ -#include -#include - +#include +#include +#include +#include #include #include #include #include +#include +#include +#include +#include +#include +#include -#include -#include -#include - -#include "../ClipperUtils.hpp" -#include "../ExPolygon.hpp" -#include "../Geometry.hpp" -#include "../Surface.hpp" -#include "../ShortestPath.hpp" - +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Geometry.hpp" +#include "libslic3r/Surface.hpp" +#include "libslic3r/ShortestPath.hpp" #include "FillRectilinear.hpp" +#include "libslic3r/Fill/FillBase.hpp" +#include "libslic3r/Utils.hpp" // #define SLIC3R_DEBUG // #define INFILL_DEBUG_OUTPUT @@ -34,7 +38,7 @@ #include // We want our version of assert. -#include "../libslic3r.h" +#include "libslic3r/libslic3r.h" namespace Slic3r { @@ -1350,8 +1354,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. @@ -1385,34 +1392,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; } } } @@ -1485,9 +1486,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; @@ -2735,6 +2740,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. @@ -2764,11 +2780,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 @@ -2846,8 +2865,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 { diff --git a/src/libslic3r/Fill/FillRectilinear.hpp b/src/libslic3r/Fill/FillRectilinear.hpp index aa5c014..2917782 100644 --- a/src/libslic3r/Fill/FillRectilinear.hpp +++ b/src/libslic3r/Fill/FillRectilinear.hpp @@ -1,9 +1,18 @@ #ifndef slic3r_FillRectilinear_hpp_ #define slic3r_FillRectilinear_hpp_ -#include "../libslic3r.h" +#include +#include +#include +#include "libslic3r/libslic3r.h" #include "FillBase.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/Polyline.hpp" +#include "libslic3r/ShortestPath.hpp" namespace Slic3r { @@ -16,6 +25,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 +38,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 +78,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. @@ -77,6 +91,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. @@ -89,6 +104,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. @@ -101,6 +117,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. @@ -119,6 +136,15 @@ protected: float _layer_angle(size_t idx) const override { return 0.f; } }; +class FillZigZag : public FillRectilinear +{ +public: + Fill* clone() const override { return new FillZigZag(*this); } + ~FillZigZag() override = default; + + bool has_consistent_pattern() const override { return true; } +}; + 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); diff --git a/src/libslic3r/Fill/Lightning/DistanceField.cpp b/src/libslic3r/Fill/Lightning/DistanceField.cpp index ad1a85a..f0ccabe 100644 --- a/src/libslic3r/Fill/Lightning/DistanceField.cpp +++ b/src/libslic3r/Fill/Lightning/DistanceField.cpp @@ -2,9 +2,20 @@ //CuraEngine is released under the terms of the AGPLv3 or higher. #include "DistanceField.hpp" //Class we're implementing. + +#include +#include +#include +#include +#include + #include "../FillRectilinear.hpp" #include "../../ClipperUtils.hpp" - +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" #include #ifdef LIGHTNING_DISTANCE_FIELD_DEBUG_OUTPUT diff --git a/src/libslic3r/Fill/Lightning/DistanceField.hpp b/src/libslic3r/Fill/Lightning/DistanceField.hpp index 6e72243..f3bf5e7 100644 --- a/src/libslic3r/Fill/Lightning/DistanceField.hpp +++ b/src/libslic3r/Fill/Lightning/DistanceField.hpp @@ -4,9 +4,21 @@ #ifndef LIGHTNING_DISTANCE_FIELD_H #define LIGHTNING_DISTANCE_FIELD_H +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "../../BoundingBox.hpp" #include "../../Point.hpp" #include "../../Polygon.hpp" +#include "libslic3r/libslic3r.h" //#define LIGHTNING_DISTANCE_FIELD_DEBUG_OUTPUT diff --git a/src/libslic3r/Fill/Lightning/Generator.cpp b/src/libslic3r/Fill/Lightning/Generator.cpp index 185cb60..7753117 100644 --- a/src/libslic3r/Fill/Lightning/Generator.cpp +++ b/src/libslic3r/Fill/Lightning/Generator.cpp @@ -2,11 +2,25 @@ //CuraEngine is released under the terms of the AGPLv3 or higher. #include "Generator.hpp" -#include "TreeNode.hpp" +#include +#include +#include +#include + +#include "TreeNode.hpp" #include "../../ClipperUtils.hpp" #include "../../Layer.hpp" #include "../../Print.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/EdgeGrid.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Fill/Lightning/Layer.hpp" +#include "libslic3r/Flow.hpp" +#include "libslic3r/LayerRegion.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/Surface.hpp" /* Possible future tasks/optimizations,etc.: * - Improve connecting heuristic to favor connecting to shorter trees diff --git a/src/libslic3r/Fill/Lightning/Generator.hpp b/src/libslic3r/Fill/Lightning/Generator.hpp index 15ac7b2..5173376 100644 --- a/src/libslic3r/Fill/Lightning/Generator.hpp +++ b/src/libslic3r/Fill/Lightning/Generator.hpp @@ -4,11 +4,15 @@ #ifndef LIGHTNING_GENERATOR_H #define LIGHTNING_GENERATOR_H -#include "Layer.hpp" - +#include #include #include #include +#include + +#include "Layer.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { diff --git a/src/libslic3r/Fill/Lightning/Layer.cpp b/src/libslic3r/Fill/Lightning/Layer.cpp index 354623e..f054c96 100644 --- a/src/libslic3r/Fill/Lightning/Layer.cpp +++ b/src/libslic3r/Fill/Lightning/Layer.cpp @@ -3,16 +3,23 @@ #include "Layer.hpp" //The class we're implementing. +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "DistanceField.hpp" #include "TreeNode.hpp" - -#include "../../ClipperUtils.hpp" -#include "../../Geometry.hpp" -#include "Utils.hpp" - -#include -#include -#include +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/Geometry.hpp" +#include "libslic3r/EdgeGrid.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/Polygon.hpp" namespace Slic3r::FillLightning { diff --git a/src/libslic3r/Fill/Lightning/Layer.hpp b/src/libslic3r/Fill/Lightning/Layer.hpp index e8c0a38..df5cb2d 100644 --- a/src/libslic3r/Fill/Lightning/Layer.hpp +++ b/src/libslic3r/Fill/Lightning/Layer.hpp @@ -4,19 +4,25 @@ #ifndef LIGHTNING_LAYER_H #define LIGHTNING_LAYER_H -#include "../../EdgeGrid.hpp" -#include "../../Polygon.hpp" - #include #include #include #include #include +#include + +#include "../../EdgeGrid.hpp" +#include "../../Polygon.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polyline.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r::FillLightning { class Node; + using NodeSPtr = std::shared_ptr; using SparseNodeGrid = std::unordered_multimap, PointHash>; diff --git a/src/libslic3r/Fill/Lightning/TreeNode.cpp b/src/libslic3r/Fill/Lightning/TreeNode.cpp index 801a46d..cd659fd 100644 --- a/src/libslic3r/Fill/Lightning/TreeNode.cpp +++ b/src/libslic3r/Fill/Lightning/TreeNode.cpp @@ -3,7 +3,13 @@ #include "TreeNode.hpp" +#include +#include +#include +#include + #include "../../Geometry.hpp" +#include "libslic3r/Line.hpp" namespace Slic3r::FillLightning { diff --git a/src/libslic3r/Fill/Lightning/TreeNode.hpp b/src/libslic3r/Fill/Lightning/TreeNode.hpp index 8791b43..7468639 100644 --- a/src/libslic3r/Fill/Lightning/TreeNode.hpp +++ b/src/libslic3r/Fill/Lightning/TreeNode.hpp @@ -4,14 +4,23 @@ #ifndef LIGHTNING_TREE_NODE_H #define LIGHTNING_TREE_NODE_H +#include +#include #include #include #include #include +#include +#include +#include -#include "../../EdgeGrid.hpp" -#include "../../Polygon.hpp" -#include "SVG.hpp" +#include "libslic3r/EdgeGrid.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/SVG.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polyline.hpp" +#include "libslic3r/libslic3r.h" //#define LIGHTNING_TREE_NODE_DEBUG_OUTPUT diff --git a/src/libslic3r/Flow.cpp b/src/libslic3r/Flow.cpp index bc3a2d7..c1f9b71 100644 --- a/src/libslic3r/Flow.cpp +++ b/src/libslic3r/Flow.cpp @@ -1,10 +1,15 @@ #include "Flow.hpp" -#include "I18N.hpp" -#include "Print.hpp" -#include -#include #include +#include +#include + +#include "I18N.hpp" +#include "Print.hpp" +#include "libslic3r/Config.hpp" +#include "libslic3r/Exception.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { diff --git a/src/libslic3r/Flow.hpp b/src/libslic3r/Flow.hpp index 8300a38..8925a98 100644 --- a/src/libslic3r/Flow.hpp +++ b/src/libslic3r/Flow.hpp @@ -1,6 +1,10 @@ #ifndef slic3r_Flow_hpp_ #define slic3r_Flow_hpp_ +#include +#include +#include + #include "libslic3r.h" #include "Config.hpp" #include "Exception.hpp" @@ -9,6 +13,8 @@ namespace Slic3r { class PrintObject; +class ConfigOptionFloatOrPercent; +class ConfigOptionResolver; // Extra spacing of bridge threads, in mm. #define BRIDGE_EXTRA_SPACING 0.05 diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index ccbb955..12810c2 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -1,15 +1,14 @@ -#include "../libslic3r.h" -#include "../Exception.hpp" -#include "../Model.hpp" -#include "../Utils.hpp" -#include "../LocalesUtils.hpp" -#include "../GCode.hpp" -#include "../Geometry.hpp" -#include "../GCode/ThumbnailData.hpp" -#include "../Semver.hpp" -#include "../Time.hpp" +#include "libslic3r/libslic3r.h" +#include "libslic3r/Exception.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/Utils.hpp" +#include "libslic3r/GCode.hpp" +#include "libslic3r/Geometry.hpp" +#include "libslic3r/GCode/ThumbnailData.hpp" +#include "libslic3r/Semver.hpp" +#include "libslic3r/Time.hpp" -#include "../I18N.hpp" +#include "libslic3r/I18N.hpp" #include "3mf.hpp" @@ -33,15 +32,17 @@ namespace pt = boost::property_tree; #include #include -#include "miniz_extension.hpp" +#include -#include "TextConfiguration.hpp" -#include "EmbossShape.hpp" -#include "ExPolygonSerialize.hpp" +#include "libslic3r/miniz_extension.hpp" -#include "NSVGUtils.hpp" +#include "libslic3r/TextConfiguration.hpp" +#include "libslic3r/EmbossShape.hpp" +#include "libslic3r/ExPolygonSerialize.hpp" -#include +#include "libslic3r/NSVGUtils.hpp" + +#include // Slightly faster than sprintf("%.9g"), but there is an issue with the karma floating point formatter, // https://github.com/boostorg/spirit/pull/586 @@ -823,7 +824,7 @@ namespace Slic3r { add_error("Archive does not contain a valid model config"); return false; } - } + } else if (_is_svg_shape_file(name)) { _extract_embossed_svg_shape_file(name, archive, stat); } diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index 6ce52cb..4fe3b5f 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -6,19 +6,20 @@ #include -#include "../libslic3r.h" -#include "../Exception.hpp" -#include "../Model.hpp" -#include "../GCode.hpp" -#include "../PrintConfig.hpp" -#include "../Utils.hpp" -#include "../I18N.hpp" -#include "../Geometry.hpp" -#include "../CustomGCode.hpp" -#include "../LocalesUtils.hpp" +#include "libslic3r/libslic3r.h" +#include "libslic3r/Exception.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/GCode.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/Utils.hpp" +#include "libslic3r/I18N.hpp" +#include "libslic3r/Geometry.hpp" +#include "libslic3r/CustomGCode.hpp" +#include "libslic3r/miniz_extension.hpp" #include "AMF.hpp" +#include #include #include namespace pt = boost::property_tree; @@ -27,7 +28,6 @@ namespace pt = boost::property_tree; #include #include #include -#include "miniz_extension.hpp" #if 0 // Enable debugging and assert in this file. diff --git a/src/libslic3r/Format/AnycubicSLA.cpp b/src/libslic3r/Format/AnycubicSLA.cpp index fc3792e..a6179fa 100644 --- a/src/libslic3r/Format/AnycubicSLA.cpp +++ b/src/libslic3r/Format/AnycubicSLA.cpp @@ -1,14 +1,25 @@ #include "AnycubicSLA.hpp" -#include "GCode/ThumbnailData.hpp" -#include "SLA/RasterBase.hpp" -#include "libslic3r/SLAPrint.hpp" - -#include -#include -#include #include #include +#include // IWYU pragma: keep +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libslic3r/GCode/ThumbnailData.hpp" +#include "libslic3r/SLA/RasterBase.hpp" +#include "libslic3r/SLAPrint.hpp" +#include "LocalesUtils.hpp" +#include "libslic3r/Config.hpp" +#include "libslic3r/libslic3r.h" #define TAG_INTRO "ANYCUBIC\0\0\0\0" @@ -193,7 +204,7 @@ private: namespace { float get_cfg_value_f(const DynamicConfig &cfg, - const std::string &key, + const std::string &key, const float &def = 0.f) { if (cfg.has(key)) { diff --git a/src/libslic3r/Format/AnycubicSLA.hpp b/src/libslic3r/Format/AnycubicSLA.hpp index d1f8adf..5f2f967 100644 --- a/src/libslic3r/Format/AnycubicSLA.hpp +++ b/src/libslic3r/Format/AnycubicSLA.hpp @@ -1,12 +1,21 @@ #ifndef _SLIC3R_FORMAT_PWMX_HPP_ #define _SLIC3R_FORMAT_PWMX_HPP_ +#include #include +#include +#include +#include #include "SLAArchiveWriter.hpp" #include "SLAArchiveFormatRegistry.hpp" - #include "libslic3r/PrintConfig.hpp" +#include "libslic3r/GCode/ThumbnailData.hpp" +#include "libslic3r/SLA/RasterBase.hpp" + +namespace Slic3r { +class SLAPrint; +} // namespace Slic3r constexpr uint16_t ANYCUBIC_SLA_FORMAT_VERSION_1 = 1; constexpr uint16_t ANYCUBIC_SLA_FORMAT_VERSION_515 = 515; diff --git a/src/libslic3r/Format/OBJ.cpp b/src/libslic3r/Format/OBJ.cpp index 3b05bb5..fdaf14f 100644 --- a/src/libslic3r/Format/OBJ.cpp +++ b/src/libslic3r/Format/OBJ.cpp @@ -1,13 +1,14 @@ -#include "../libslic3r.h" -#include "../Model.hpp" -#include "../TriangleMesh.hpp" +#include +#include +#include +#include +#include +#include "libslic3r/Model.hpp" +#include "libslic3r/TriangleMesh.hpp" #include "OBJ.hpp" #include "objparser.hpp" - -#include - -#include +#include "admesh/stl.h" #ifdef _WIN32 #define DIR_SEPARATOR '\\' diff --git a/src/libslic3r/Format/PrintRequest.cpp b/src/libslic3r/Format/PrintRequest.cpp new file mode 100644 index 0000000..7dceaea --- /dev/null +++ b/src/libslic3r/Format/PrintRequest.cpp @@ -0,0 +1,161 @@ +#include "PrintRequest.hpp" + +#include +#include +#include +#include +#include +#include + +#include "libslic3r/Exception.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/Format/STL.hpp" + +//#include "slic3r/GUI/format.hpp" + +namespace Slic3r { +namespace pt = boost::property_tree; +namespace { +void read_file(const char* input_file, pt::ptree& tree) +{ + boost::filesystem::path path(input_file); + + boost::nowide::ifstream ifs(path.string()); + try { + pt::read_xml(ifs, tree); + } + catch (const boost::property_tree::xml_parser::xml_parser_error& err) { + throw Slic3r::RuntimeError("Failed reading PrintRequest file. File format is corrupt."); + } +} + +void read_tree(const boost::property_tree::ptree::value_type& section, boost::filesystem::path& model_path, std::string& material, std::string& material_color, std::vector& transformation_matrix) +{ + for (const auto& data : section.second) { + if (data.first == "Path") { + model_path = boost::filesystem::path(data.second.data()); + } + else if (data.first == "Material") { + material = data.second.data(); + } + else if (data.first == "MaterialColor") { + material_color = data.second.data(); + } + else if (data.first == "TransformationMatrix") { + transformation_matrix.reserve(16); + for (const auto& element : data.second) { + transformation_matrix.emplace_back(element.second.data()); + } + } + } +} +bool fill_model(Model* model, const boost::filesystem::path& model_path, const std::string& material, const std::vector& transformation_matrix) +{ + if (!boost::filesystem::exists(model_path)) + throw Slic3r::RuntimeError("Failed reading PrintRequest file. Path doesn't exists. " + model_path.string()); + if (!boost::algorithm::iends_with(model_path.string(), ".stl")) + throw Slic3r::RuntimeError("Failed reading PrintRequest file. Path is not stl file. " + model_path.string()); + bool result = load_stl(model_path.string().c_str(), model); + if (!material.empty()) { + model->objects.back()->volumes.front()->set_material_id(material); + } + return result; +} +void add_instance(Model* model, const boost::filesystem::path& model_path, const std::vector& transformation_matrix) +{ + if (transformation_matrix.size() >= 16) { + + auto string_to_double = [model_path](const std::string& from) -> double { + double ret_val; + auto answer = fast_float::from_chars(from.data(), from.data() + from.size(), ret_val); + if (answer.ec != std::errc()) + throw Slic3r::RuntimeError("Failed reading PrintRequest file. Couldn't parse transformation matrix. " + model_path.string()); + return ret_val; + }; + + Vec3d offset_vector; + Slic3r::Transform3d matrix; + try + { + offset_vector = Slic3r::Vec3d(string_to_double(transformation_matrix[3]), string_to_double(transformation_matrix[7]), string_to_double(transformation_matrix[11])); + // PrintRequest is row-major 4x4, Slic3r::Transform3d (Eigen) is column-major by default 3x3 + matrix(0, 0) = string_to_double(transformation_matrix[0]); + matrix(1, 0) = string_to_double(transformation_matrix[1]); + matrix(2, 0) = string_to_double(transformation_matrix[2]); + matrix(0, 1) = string_to_double(transformation_matrix[4]); + matrix(1, 1) = string_to_double(transformation_matrix[5]); + matrix(2, 1) = string_to_double(transformation_matrix[6]); + matrix(0, 2) = string_to_double(transformation_matrix[8]); + matrix(1, 2) = string_to_double(transformation_matrix[9]); + matrix(2, 2) = string_to_double(transformation_matrix[10]); + } + catch (const Slic3r::RuntimeError& e) { + throw e; + } + + + ModelObject* object = model->objects.back(); + Slic3r::Geometry::Transformation transformation(matrix); + transformation.set_offset(offset_vector); + object->add_instance(transformation); + } +} + +} + +bool load_printRequest(const char* input_file, Model* model) +{ + pt::ptree tree; + try + { + read_file(input_file, tree); + } + catch (const std::exception& e) + { + throw e; + } + + bool result = true; + + for (const auto& section0 : tree) { + if (section0.first != "PrintRequest") + continue; + if (section0.second.empty()) + continue; + for (const auto& section1 : section0.second) { + if (section1.first != "Files") + continue; + if (section1.second.empty()) + continue; + for (const auto& section2 : section1.second) { + if (section2.first != "File") + continue; + if (section2.second.empty()) + continue; + boost::filesystem::path model_path; + std::string material; + std::string material_color; + std::vector transformation_matrix; + + try + { + read_tree(section2, model_path, material, material_color, transformation_matrix); + result = result && fill_model(model, model_path, material, transformation_matrix); + if (!result) + return false; + add_instance(model, model_path, transformation_matrix); + } + catch (const std::exception& e) + { + throw e; + } + + + } + } + } + + return true; +} + +} // namespace Slic3r diff --git a/src/libslic3r/Format/PrintRequest.hpp b/src/libslic3r/Format/PrintRequest.hpp new file mode 100644 index 0000000..bf92d20 --- /dev/null +++ b/src/libslic3r/Format/PrintRequest.hpp @@ -0,0 +1,12 @@ +#ifndef slic3r_Format_PrintRequest_hpp_ +#define slic3r_Format_PrintRequest_hpp_ + + + +namespace Slic3r { +class Model; +bool load_printRequest(const char* input_file, Model* model); + +} //namespace Slic3r + +#endif \ No newline at end of file diff --git a/src/libslic3r/Format/SL1.cpp b/src/libslic3r/Format/SL1.cpp index ebba27e..28f16a7 100644 --- a/src/libslic3r/Format/SL1.cpp +++ b/src/libslic3r/Format/SL1.cpp @@ -12,9 +12,10 @@ #include "libslic3r/MTUtils.hpp" #include "libslic3r/PrintConfig.hpp" -#include "libslic3r/miniz_extension.hpp" -#include "libslic3r/LocalesUtils.hpp" +#include "libslic3r/miniz_extension.hpp" // IWYU pragma: keep +#include #include "libslic3r/GCode/ThumbnailData.hpp" +#include "libslic3r/Utils/JsonUtils.hpp" #include "SLAArchiveReader.hpp" #include "SLAArchiveFormatRegistry.hpp" @@ -29,6 +30,7 @@ #include +#include #include #include @@ -43,10 +45,114 @@ std::string to_ini(const ConfMap &m) std::string ret; for (auto ¶m : m) ret += param.first + " = " + param.second + "\n"; - + return ret; } +static std::string get_key(const std::string& opt_key) +{ + static const std::set ms_opts = { + "delay_before_exposure" + , "delay_after_exposure" + , "tilt_down_offset_delay" + , "tilt_up_offset_delay" + , "tilt_down_delay" + , "tilt_up_delay" + }; + + static const std::set nm_opts = { + "tower_hop_height" + }; + + static const std::set speed_opts = { + "tower_speed" + , "tilt_down_initial_speed" + , "tilt_down_finish_speed" + , "tilt_up_initial_speed" + , "tilt_up_finish_speed" + }; + + if (ms_opts.find(opt_key) != ms_opts.end()) + return opt_key + "_ms"; + + if (nm_opts.find(opt_key) != nm_opts.end()) + return opt_key + "_nm"; + + if (speed_opts.find(opt_key) != speed_opts.end()) + return boost::replace_all_copy(opt_key, "_speed", "_profile"); + + return opt_key; +} + +namespace pt = boost::property_tree; + +std::string to_json(const SLAPrint& print, const ConfMap &m) +{ + auto& cfg = print.full_print_config(); + + pt::ptree below_node; + pt::ptree above_node; + + const t_config_enum_names& tilt_enum_names = ConfigOptionEnum< TiltSpeeds>::get_enum_names(); + const t_config_enum_names& tower_enum_names = ConfigOptionEnum::get_enum_names(); + + for (const std::string& opt_key : tilt_options()) { + const ConfigOption* opt = cfg.option(opt_key); + assert(opt != nullptr); + + switch (opt->type()) { + case coFloats: { + auto values = static_cast(opt); + // those options have to be exported in ms instead of s + below_node.put(get_key(opt_key), int(1000 * values->get_at(0))); + above_node.put(get_key(opt_key), int(1000 * values->get_at(1))); + } + break; + case coInts: { + auto values = static_cast(opt); + int koef = opt_key == "tower_hop_height" ? 1000000 : 1; + below_node.put(get_key(opt_key), koef * values->get_at(0)); + above_node.put(get_key(opt_key), koef * values->get_at(1)); + } + break; + case coBools: { + auto values = static_cast(opt); + below_node.put(get_key(opt_key), values->get_at(0)); + above_node.put(get_key(opt_key), values->get_at(1)); + } + break; + case coEnums: { + const t_config_enum_names& enum_names = opt_key == "tower_speed" ? tower_enum_names : tilt_enum_names; + auto values = static_cast*>(opt); + below_node.put(get_key(opt_key), enum_names[values->get_at(0)]); + above_node.put(get_key(opt_key), enum_names[values->get_at(1)]); + } + break; + case coNone: + default: + break; + } + } + + pt::ptree profile_node; + profile_node.put("area_fill", cfg.option("area_fill")->serialize()); + profile_node.add_child("below_area_fill", below_node); + profile_node.add_child("above_area_fill", above_node); + + pt::ptree root; + // params from config.ini + for (auto& param : m) + root.put(param.first, param.second ); + + root.put("version", "1"); + root.add_child("exposure_profile", profile_node); + + // Boost confirms its implementation has no 100% conformance to JSON standard. + // In the boost libraries, boost will always serialize each value as string and parse all values to a string equivalent. + // so, post-prosess output + return write_json_with_post_process(root); +} + std::string get_cfg_value(const DynamicPrintConfig &cfg, const std::string &key) { std::string ret; @@ -119,10 +225,15 @@ void fill_slicerconf(ConfMap &m, const SLAPrint &print) auto is_banned = [](const std::string &key) { return std::binary_search(banned_keys.begin(), banned_keys.end(), key); }; + + auto is_tilt_param = [](const std::string& key) -> bool { + const auto& keys = tilt_options(); + return std::find(keys.begin(), keys.end(), key) != keys.end(); + }; auto &cfg = print.full_print_config(); for (const std::string &key : cfg.keys()) - if (! is_banned(key) && ! cfg.option(key)->is_nil()) + if (! is_banned(key) && !is_tilt_param(key) && ! cfg.option(key)->is_nil()) m[key] = cfg.opt_serialize(key); } @@ -208,6 +319,9 @@ void SL1Archive::export_print(Zipper &zipper, zipper.add_entry("qidislicer.ini"); zipper << to_ini(slicerconf); + zipper.add_entry("config.json"); + zipper << to_json(print, iniconf); + size_t i = 0; for (const sla::EncodedRaster &rst : m_layers) { diff --git a/src/libslic3r/Format/SL1_SVG.cpp b/src/libslic3r/Format/SL1_SVG.cpp index 7370355..3d85051 100644 --- a/src/libslic3r/Format/SL1_SVG.cpp +++ b/src/libslic3r/Format/SL1_SVG.cpp @@ -1,17 +1,35 @@ #include "SL1_SVG.hpp" -#include "SLA/RasterBase.hpp" -#include "libslic3r/LocalesUtils.hpp" + +#include + +#include "libslic3r/SLA/RasterBase.hpp" #include "libslic3r/ClipperUtils.hpp" #include "libslic3r/BoundingBox.hpp" #include "libslic3r/Format/ZipperArchiveImport.hpp" +#include "libslic3r/Format/SL1.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/Zipper.hpp" +#include "libslic3r/libslic3r.h" #define NANOSVG_IMPLEMENTATION -#include "nanosvg/nanosvg.h" - -#include #include #include #include +#include +#include +#include +#include +#include +#include + +#include "nanosvg/nanosvg.h" + +namespace Slic3r { +class SLAPrint; +} // namespace Slic3r + using namespace std::literals; namespace Slic3r { diff --git a/src/libslic3r/Format/SL1_SVG.hpp b/src/libslic3r/Format/SL1_SVG.hpp index d94a515..c26836d 100644 --- a/src/libslic3r/Format/SL1_SVG.hpp +++ b/src/libslic3r/Format/SL1_SVG.hpp @@ -1,9 +1,21 @@ #ifndef SL1_SVG_HPP #define SL1_SVG_HPP +#include +#include +#include +#include + #include "SL1.hpp" +#include "libslic3r/Config.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Format/SLAArchiveReader.hpp" +#include "libslic3r/GCode/ThumbnailData.hpp" +#include "libslic3r/SLA/RasterBase.hpp" namespace Slic3r { +class DynamicPrintConfig; +class SLAPrint; class SL1_SVGArchive: public SL1Archive { protected: diff --git a/src/libslic3r/Format/SLAArchiveFormatRegistry.cpp b/src/libslic3r/Format/SLAArchiveFormatRegistry.cpp index 0a72cbc..eb99cd9 100644 --- a/src/libslic3r/Format/SLAArchiveFormatRegistry.cpp +++ b/src/libslic3r/Format/SLAArchiveFormatRegistry.cpp @@ -1,13 +1,13 @@ #include -#include #include #include "SL1.hpp" #include "SL1_SVG.hpp" #include "AnycubicSLA.hpp" -#include "I18N.hpp" - +#include "libslic3r/I18N.hpp" #include "SLAArchiveFormatRegistry.hpp" +#include "libslic3r/Format/SLAArchiveReader.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { diff --git a/src/libslic3r/Format/SLAArchiveFormatRegistry.hpp b/src/libslic3r/Format/SLAArchiveFormatRegistry.hpp index 896888b..51a8e0c 100644 --- a/src/libslic3r/Format/SLAArchiveFormatRegistry.hpp +++ b/src/libslic3r/Format/SLAArchiveFormatRegistry.hpp @@ -1,11 +1,19 @@ #ifndef SLA_ARCHIVE_FORMAT_REGISTRY_HPP #define SLA_ARCHIVE_FORMAT_REGISTRY_HPP +#include +#include +#include +#include +#include +#include +#include + #include "SLAArchiveWriter.hpp" #include "SLAArchiveReader.hpp" -#include namespace Slic3r { +class SLAPrinterConfig; // Factory function that returns an implementation of SLAArchiveWriter given // a printer configuration. diff --git a/src/libslic3r/Format/SLAArchiveReader.cpp b/src/libslic3r/Format/SLAArchiveReader.cpp index c8a15bc..d086ddc 100644 --- a/src/libslic3r/Format/SLAArchiveReader.cpp +++ b/src/libslic3r/Format/SLAArchiveReader.cpp @@ -1,7 +1,7 @@ #include "SLAArchiveReader.hpp" #include "SL1.hpp" #include "SL1_SVG.hpp" -#include "I18N.hpp" +#include "libslic3r/I18N.hpp" #include "libslic3r/SlicesToTriangleMesh.hpp" diff --git a/src/libslic3r/Format/SLAArchiveWriter.cpp b/src/libslic3r/Format/SLAArchiveWriter.cpp index 5d3cee7..7eeb9fd 100644 --- a/src/libslic3r/Format/SLAArchiveWriter.cpp +++ b/src/libslic3r/Format/SLAArchiveWriter.cpp @@ -1,5 +1,7 @@ #include "SLAArchiveWriter.hpp" + #include "SLAArchiveFormatRegistry.hpp" +#include "libslic3r/PrintConfig.hpp" namespace Slic3r { diff --git a/src/libslic3r/Format/SLAArchiveWriter.hpp b/src/libslic3r/Format/SLAArchiveWriter.hpp index 1e6ed64..7a87675 100644 --- a/src/libslic3r/Format/SLAArchiveWriter.hpp +++ b/src/libslic3r/Format/SLAArchiveWriter.hpp @@ -1,11 +1,16 @@ #ifndef SLAARCHIVE_HPP #define SLAARCHIVE_HPP +#include #include +#include +#include +#include #include "libslic3r/SLA/RasterBase.hpp" #include "libslic3r/Execution/ExecutionTBB.hpp" #include "libslic3r/GCode/ThumbnailData.hpp" +#include "libslic3r/Execution/Execution.hpp" namespace Slic3r { diff --git a/src/libslic3r/Format/STEP.cpp b/src/libslic3r/Format/STEP.cpp index 0817267..2737bbb 100644 --- a/src/libslic3r/Format/STEP.cpp +++ b/src/libslic3r/Format/STEP.cpp @@ -102,23 +102,13 @@ bool load_step(const char *path, Model *model /*BBS:, ImportStepProgressFn proFn else new_object->name = occt_object.object_name; - - for (size_t i=0; iadd_volume(std::move(triangle_mesh)); new_volume->name = occt_object.volumes[i].volume_name.empty() - ? std::string("Part") + std::to_string(i+1) + ? std::string("Part") + std::to_string(i + 1) : occt_object.volumes[i].volume_name; new_volume->source.input_file = path; new_volume->source.object_idx = (int)model->objects.size() - 1; diff --git a/src/libslic3r/Format/STL.cpp b/src/libslic3r/Format/STL.cpp index 2f2c9ec..d61f758 100644 --- a/src/libslic3r/Format/STL.cpp +++ b/src/libslic3r/Format/STL.cpp @@ -1,10 +1,10 @@ -#include "../libslic3r.h" -#include "../Model.hpp" -#include "../TriangleMesh.hpp" - -#include "STL.hpp" - #include +#include +#include + +#include "libslic3r/Model.hpp" +#include "libslic3r/TriangleMesh.hpp" +#include "STL.hpp" #ifdef _WIN32 #define DIR_SEPARATOR '\\' diff --git a/src/libslic3r/Format/STL.hpp b/src/libslic3r/Format/STL.hpp index cff7dc0..b2c4345 100644 --- a/src/libslic3r/Format/STL.hpp +++ b/src/libslic3r/Format/STL.hpp @@ -5,6 +5,7 @@ namespace Slic3r { class TriangleMesh; class ModelObject; +class Model; // Load an STL file into a provided model. extern bool load_stl(const char *path, Model *model, const char *object_name = nullptr); diff --git a/src/libslic3r/Format/SVG.cpp b/src/libslic3r/Format/SVG.cpp index 40bf8d0..1c70579 100644 --- a/src/libslic3r/Format/SVG.cpp +++ b/src/libslic3r/Format/SVG.cpp @@ -1,10 +1,19 @@ -#include "../libslic3r.h" -#include "../Model.hpp" -#include "../TriangleMesh.hpp" -#include "../NSVGUtils.hpp" -#include "../Emboss.hpp" - #include +#include +#include +#include +#include +#include +#include + +#include "libslic3r/Model.hpp" +#include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/NSVGUtils.hpp" +#include "libslic3r/Emboss.hpp" +#include "admesh/stl.h" +#include "libslic3r/EmbossShape.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Point.hpp" namespace { std::string get_file_name(const std::string &file_path) diff --git a/src/libslic3r/Format/objparser.cpp b/src/libslic3r/Format/objparser.cpp index e7d02a0..a905afe 100644 --- a/src/libslic3r/Format/objparser.cpp +++ b/src/libslic3r/Format/objparser.cpp @@ -1,14 +1,17 @@ -#include -#include - #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "objparser.hpp" -#include "libslic3r/LocalesUtils.hpp" -#include "fast_float/fast_float.h" - namespace ObjParser { static double strtod_clocale(const char* str, char const** str_end) @@ -21,6 +24,7 @@ static double strtod_clocale(const char* str, char const** str_end) *str_end = str; return val; } + static bool obj_parseline(const char *line, ObjData &data) { #define EATWS() while (*line == ' ' || *line == '\t') ++ line @@ -343,11 +347,20 @@ bool objparse(const char *path, ObjData &data) if (pFile == 0) return false; + constexpr size_t half_buf = 65536; try { - char buf[65536 * 2]; + char buf[half_buf * 2]; size_t len = 0; size_t lenPrev = 0; - while ((len = ::fread(buf + lenPrev, 1, 65536, pFile)) != 0) { + while ((len = ::fread(buf + lenPrev, 1, half_buf-1, pFile)) != 0) { + if (std::feof(pFile)) { + // Fix issue with missing last trinagle in obj file: + // https://github.com/qidi3d/QIDISlicer/issues/12157 + // algorithm expect line endings after last face + // but file format support it + buf[len+lenPrev] = '\n'; + ++len; + } len += lenPrev; size_t lastLine = 0; for (size_t i = 0; i < len; ++ i) @@ -362,7 +375,7 @@ bool objparse(const char *path, ObjData &data) lastLine = i + 1; } lenPrev = len - lastLine; - if (lenPrev > 65536) { + if (lenPrev > half_buf) { BOOST_LOG_TRIVIAL(error) << "ObjParser: Excessive line length"; ::fclose(pFile); return false; diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 050e187..c7709be 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1,18 +1,18 @@ #include "Config.hpp" #include "Geometry/Circle.hpp" #include "libslic3r.h" -#include "GCode/ExtrusionProcessor.hpp" +#include "libslic3r/GCode/ExtrusionProcessor.hpp" #include "I18N.hpp" #include "GCode.hpp" #include "Exception.hpp" #include "ExtrusionEntity.hpp" #include "Geometry/ConvexHull.hpp" -#include "GCode/LabelObjects.hpp" -#include "GCode/PrintExtents.hpp" -#include "GCode/Thumbnails.hpp" -#include "GCode/WipeTower.hpp" -#include "GCode/WipeTowerIntegration.hpp" -#include "GCode/Travels.hpp" +#include "libslic3r/GCode/LabelObjects.hpp" +#include "libslic3r/GCode/PrintExtents.hpp" +#include "libslic3r/GCode/Thumbnails.hpp" +#include "libslic3r/GCode/WipeTower.hpp" +#include "libslic3r/GCode/WipeTowerIntegration.hpp" +#include "libslic3r/GCode/Travels.hpp" #include "Point.hpp" #include "Polygon.hpp" #include "PrintConfig.hpp" @@ -548,9 +548,6 @@ GCodeGenerator::GCodeGenerator(const Print* print) : m_volumetric_speed(0), m_last_extrusion_role(GCodeExtrusionRole::None), m_last_width(0.0f), -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - m_last_mm3_per_mm(0.0), -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING m_brim_done(false), m_second_layer_things_done(false), m_silent_time_estimator_enabled(false), @@ -940,8 +937,8 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail // printer data - this section contains duplicates from the slicer metadata // that we just created. Find and copy the entries that we want to duplicate. const auto& slicer_metadata = binary_data.slicer_metadata.raw_data; - const std::vector keys_to_duplicate = { "printer_model", "filament_type", "nozzle_diameter", "bed_temperature", - "brim_width", "fill_density", "layer_height", "temperature", "ironing", "support_material", "extruder_colour" }; + const std::vector keys_to_duplicate = { "printer_model", "filament_type", "filament_abrasive", "nozzle_diameter", "nozzle_high_flow", "bed_temperature", + "brim_width", "fill_density", "layer_height", "temperature", "ironing", "support_material", "extruder_colour"}; assert(std::is_sorted(slicer_metadata.begin(), slicer_metadata.end(), [](const auto& a, const auto& b) { return a.first < b.first; })); for (const std::string& key : keys_to_duplicate) { @@ -965,9 +962,6 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail m_last_layer_z = 0.f; m_max_layer_z = 0.f; m_last_width = 0.f; -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - m_last_mm3_per_mm = 0.; -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING // How many times will be change_layer() called?gcode.cpp // change_layer() in turn increments the progress bar status. @@ -1198,9 +1192,9 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail DoExport::init_ooze_prevention(print, m_ooze_prevention); std::string start_gcode = this->placeholder_parser_process("start_gcode", print.config().start_gcode.value, initial_extruder_id); - // Set bed temperature if the start G-code does not contain any bed temp control G-codes. + + // this->_print_first_layer_chamber_temperature(file, print, start_gcode, config().chamber_temperature.get_at(initial_extruder_id), false, false); this->_print_first_layer_bed_temperature(file, print, start_gcode, initial_extruder_id, true); - // Set extruder(s) temperature before and after start G-code. this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, false); // adds tag for processor @@ -1229,7 +1223,9 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail // Collect custom seam data from all objects. std::function throw_if_canceled_func = [&print]() { print.throw_if_canceled();}; - m_seam_placer.init(print, throw_if_canceled_func); + + const Seams::Params params{Seams::Placer::get_params(print.full_print_config())}; + m_seam_placer.init(print.objects(), params, throw_if_canceled_func); if (! (has_wipe_tower && print.config().single_extruder_multi_material_priming)) { // Set initial extruder only after custom start G-code. @@ -1911,6 +1907,31 @@ void GCodeGenerator::_print_first_layer_bed_temperature(GCodeOutputStream &file, file.write(set_temp_gcode); } + + +// Write chamber temperatures into the G-code. +// Only do that if the start G-code does not already contain any M-code controlling chamber temperature. +// M141 - Set chamber Temperature +// M191 - Set chamber Temperature and Wait +// void GCodeGenerator::_print_first_layer_chamber_temperature(GCodeOutputStream &file, const Print &print, const std::string &gcode, int temp, bool wait, bool accurate) +// { +// if (temp == 0) +// return; +// bool autoemit = print.config().autoemit_temperature_commands; +// // Is the bed temperature set by the provided custom G-code? +// int temp_by_gcode = -1; +// bool temp_set_by_gcode = custom_gcode_sets_temperature(gcode, 141, 191, false, temp_by_gcode); +// if (autoemit && temp_set_by_gcode && temp_by_gcode >= 0 && temp_by_gcode < 1000) +// temp = temp_by_gcode; +// // Always call m_writer.set_bed_temperature() so it will set the internal "current" state of the bed temp as if +// // the custom start G-code emited these. +// std::string set_temp_gcode = m_writer.set_chamber_temperature(temp, wait, accurate); +// if (autoemit && ! temp_set_by_gcode) +// file.write(set_temp_gcode); +// } + + + // Write 1st layer extruder temperatures into the G-code. // Only do that if the start G-code does not already contain any M-code controlling an extruder temperature. // M104 - Set Extruder Temperature @@ -1954,7 +1975,7 @@ void GCodeGenerator::_print_first_layer_extruder_temperatures(GCodeOutputStream } } -std::vector GCodeGenerator::sort_print_object_instances( +std::vector GCodeGenerator::sort_print_object_instances( const std::vector &object_layers, // Ordering must be defined for normal (non-sequential print). const std::vector *ordering, @@ -2009,7 +2030,7 @@ static std::string emit_custom_color_change_gcode_per_print_z( int color_change_extruder = -1; if (color_change && custom_gcode.extruder > 0) - color_change_extruder = custom_gcode.extruder - 1; + color_change_extruder = single_extruder_printer ? 0 : custom_gcode.extruder - 1; assert(color_change_extruder >= 0); // Color Change or Tool Change as Color Change. @@ -2167,25 +2188,11 @@ bool GCodeGenerator::line_distancer_is_required(const std::vector& } Polyline GCodeGenerator::get_layer_change_xy_path(const Vec3d &from, const Vec3d &to) { - bool could_be_wipe_disabled{false}; const bool needs_retraction{true}; - const Point saved_last_position{*this->last_position}; - const bool saved_use_external_mp{this->m_avoid_crossing_perimeters.use_external_mp_once}; - const Vec2d saved_origin{this->origin()}; - const Layer* saved_layer{this->layer()}; - - this->m_avoid_crossing_perimeters.use_external_mp_once = m_layer_change_used_external_mp; - if (this->m_layer_change_origin) { - this->m_origin = *this->m_layer_change_origin; - } - this->m_layer = m_layer_change_layer; - this->m_avoid_crossing_perimeters.init_layer(*this->m_layer); - const Point start_point{this->gcode_to_point(from.head<2>())}; const Point end_point{this->gcode_to_point(to.head<2>())}; - this->last_position = start_point; Polyline xy_path{ this->generate_travel_xy_path(start_point, end_point, needs_retraction, could_be_wipe_disabled)}; @@ -2195,11 +2202,6 @@ Polyline GCodeGenerator::get_layer_change_xy_path(const Vec3d &from, const Vec3d gcode_xy_path.push_back(this->point_to_gcode(point)); } - this->last_position = saved_last_position; - this->m_avoid_crossing_perimeters.use_external_mp_once = saved_use_external_mp; - this->m_origin = saved_origin; - this->m_layer = saved_layer; - Polyline result; for (const Vec2d& point : gcode_xy_path) { result.points.push_back(gcode_to_point(point)); @@ -2276,6 +2278,129 @@ std::string GCodeGenerator::generate_ramping_layer_change_gcode( return travel_gcode; } +#ifndef NDEBUG +static inline bool validate_smooth_path(const GCode::SmoothPath &smooth_path, bool loop) +{ + for (auto it = std::next(smooth_path.begin()); it != smooth_path.end(); ++ it) { + assert(it->path.size() >= 2); + assert(std::prev(it)->path.back().point == it->path.front().point); + } + assert(! loop || smooth_path.front().path.front().point == smooth_path.back().path.back().point); + return true; +} +#endif //NDEBUG + +using GCode::ExtrusionOrder::InstancePoint; + +struct SmoothPathGenerator { + const Seams::Placer &seam_placer; + const GCode::SmoothPathCaches &smooth_path_caches; + double scaled_resolution; + const PrintConfig &config; + bool enable_loop_clipping; + + GCode::SmoothPath operator()(const Layer *layer, const ExtrusionEntityReference &extrusion_reference, const unsigned extruder_id, std::optional &previous_position) { + const ExtrusionEntity *extrusion_entity{&extrusion_reference.extrusion_entity()}; + + GCode::SmoothPath result; + + if (auto loop = dynamic_cast(extrusion_entity)) { + Point seam_point = previous_position ? previous_position->local_point : Point::Zero(); + if (!config.spiral_vase && loop->role().is_perimeter() && layer != nullptr) { + seam_point = this->seam_placer.place_seam(layer, *loop, seam_point); + } + + const GCode::SmoothPathCache &smooth_path_cache{loop->role().is_perimeter() ? smooth_path_caches.layer_local() : smooth_path_caches.global()}; + // Because the G-code export has 1um resolution, don't generate segments shorter + // than 1.5 microns, thus empty path segments will not be produced by G-code export. + GCode::SmoothPath smooth_path = + smooth_path_cache.resolve_or_fit_split_with_seam( + *loop, extrusion_reference.flipped(), scaled_resolution, seam_point, scaled(0.0015) + ); + + // Clip the path to avoid the extruder to get exactly on the first point of the + // loop; if polyline was shorter than the clipping distance we'd get a null + // polyline, so we discard it in that case. + const auto nozzle_diameter{config.nozzle_diameter.get_at(extruder_id)}; + + if (enable_loop_clipping) { + + //Y21 + + clip_end(smooth_path, scale_(nozzle_diameter) * (config.seam_gap.value / 100), scaled(GCode::ExtrusionOrder::min_gcode_segment_length)); + } + + assert(validate_smooth_path(smooth_path, !enable_loop_clipping)); + + result = smooth_path; + } else if (auto multipath = dynamic_cast(extrusion_entity)) { + result = smooth_path_caches.layer_local().resolve_or_fit(*multipath, extrusion_reference.flipped(), scaled_resolution); + } else if (auto path = dynamic_cast(extrusion_entity)) { + result = GCode::SmoothPath{GCode::SmoothPathElement{path->attributes(), smooth_path_caches.layer_local().resolve_or_fit(*path, extrusion_reference.flipped(), scaled_resolution)}}; + } + for (auto it{result.rbegin()}; it != result.rend(); ++it) { + if (!it->path.empty()) { + previous_position = InstancePoint{it->path.back().point}; + break; + } + } + + return result; + } + +}; + +std::vector GCodeGenerator::get_sorted_extrusions( + const Print &print, + const ObjectsLayerToPrint &layers, + const LayerTools &layer_tools, + const std::vector &instances_to_print, + const GCode::SmoothPathCaches &smooth_path_caches, + const bool first_layer +) { + // Map from extruder ID to index of skirt loops to be extruded with that extruder. + // Extrude skirt at the print_z of the raft layers and normal object layers + // not at the print_z of the interlaced support material layers. + std::map> skirt_loops_per_extruder{ + first_layer ? + Skirt::make_skirt_loops_per_extruder_1st_layer(print, layer_tools, m_skirt_done) : + Skirt::make_skirt_loops_per_extruder_other_layers(print, layer_tools, m_skirt_done)}; + + const SmoothPathGenerator smooth_path{ + m_seam_placer, + smooth_path_caches, + m_scaled_resolution, + m_config, + m_enable_loop_clipping + }; + + using GCode::ExtrusionOrder::ExtruderExtrusions; + using GCode::ExtrusionOrder::get_extrusions; + + const std::optional previous_position{ + this->last_position ? std::optional{scaled(this->point_to_gcode(*this->last_position))} : + std::nullopt}; + std::vector extrusions{ + get_extrusions( + print, + this->m_wipe_tower.get(), + layers, + first_layer, + layer_tools, + instances_to_print, + skirt_loops_per_extruder, + this->m_writer.extruder()->id(), + smooth_path, + !this->m_brim_done, + previous_position + ) + }; + this->m_brim_done = true; + + return extrusions; +} + + // In sequential mode, process_layer is called once per each object and its copy, // therefore layers will contain a single entry and single_object_instance_idx will point to the copy of the object. // In non-sequential mode, process_layer is called per each print_z height with all object and support layers accumulated. @@ -2324,8 +2449,6 @@ LayerResult GCodeGenerator::process_layer( unsigned int first_extruder_id = layer_tools.extruders.front(); const std::vector instances_to_print{sort_print_object_instances(layers, ordering, single_object_instance_idx)}; - const PrintInstance* first_instance{instances_to_print.empty() ? nullptr : &instances_to_print.front().print_object.instances()[instances_to_print.front().instance_id]}; - m_label_objects.update(first_instance); //B36 m_writer.set_is_first_layer(first_layer); @@ -2352,8 +2475,19 @@ LayerResult GCodeGenerator::process_layer( m_enable_loop_clipping = !enable; } + using GCode::ExtrusionOrder::ExtruderExtrusions; + const std::vector extrusions{ + this->get_sorted_extrusions(print, layers, layer_tools, instances_to_print, smooth_path_caches, first_layer)}; + + if (extrusions.empty()) { + return result; + } + const Point first_point{*GCode::ExtrusionOrder::get_first_point(extrusions)}; + const PrintInstance* first_instance{get_first_instance(extrusions, instances_to_print)}; + m_label_objects.update(first_instance); std::string gcode; + assert(is_decimal_separator_point()); // for the sprintfs // add tag for processor @@ -2371,8 +2505,6 @@ LayerResult GCodeGenerator::process_layer( m_last_layer_z = static_cast(print_z); m_max_layer_z = std::max(m_max_layer_z, m_last_layer_z); m_last_height = height; - m_current_layer_first_position = std::nullopt; - m_already_unretracted = false; // Set new layer - this will change Z and force a retraction if retract_layer_change is enabled. if (!first_layer && ! print.config().before_layer_gcode.value.empty()) { @@ -2384,7 +2516,7 @@ LayerResult GCodeGenerator::process_layer( print.config().before_layer_gcode.value, m_writer.extruder()->id(), &config) + "\n"; } - gcode += this->change_layer(previous_layer_z, print_z, result.spiral_vase_enable); // this will increase m_layer_index + gcode += this->change_layer(previous_layer_z, print_z, result.spiral_vase_enable, first_point, first_layer); // this will increase m_layer_index m_layer = &layer; if (this->line_distancer_is_required(layer_tools.extruders) && this->m_layer != nullptr && this->m_layer->lower_layer != nullptr) m_travel_obstacle_tracker.init_layer(layer, layers); @@ -2421,15 +2553,6 @@ LayerResult GCodeGenerator::process_layer( m_second_layer_things_done = true; } - // Map from extruder ID to index of skirt loops to be extruded with that extruder. - std::map> skirt_loops_per_extruder; - - // Extrude skirt at the print_z of the raft layers and normal object layers - // not at the print_z of the interlaced support material layers. - skirt_loops_per_extruder = first_layer ? - Skirt::make_skirt_loops_per_extruder_1st_layer(print, layer_tools, m_skirt_done) : - Skirt::make_skirt_loops_per_extruder_other_layers(print, layer_tools, m_skirt_done); - if (this->config().avoid_crossing_curled_overhangs) { m_avoid_crossing_curled_overhangs.clear(); for (const ObjectLayerToPrint &layer_to_print : layers) { @@ -2459,157 +2582,123 @@ LayerResult GCodeGenerator::process_layer( } } + this->set_origin({0, 0}); + this->m_moved_to_first_layer_point = false; + // Extrude the skirt, brim, support, perimeters, infill ordered by the extruders. - for (unsigned int extruder_id : layer_tools.extruders) + for (const ExtruderExtrusions &extruder_extrusions : extrusions) { gcode += (layer_tools.has_wipe_tower && m_wipe_tower) ? - m_wipe_tower->tool_change(*this, extruder_id, extruder_id == layer_tools.extruders.back()) : - this->set_extruder(extruder_id, print_z); + m_wipe_tower->tool_change(*this, extruder_extrusions.extruder_id, extruder_extrusions.extruder_id == layer_tools.extruders.back()) : + this->set_extruder(extruder_extrusions.extruder_id, print_z); // let analyzer tag generator aware of a role type change if (layer_tools.has_wipe_tower && m_wipe_tower) m_last_processor_extrusion_role = GCodeExtrusionRole::WipeTower; - if (has_custom_gcode_to_emit && extruder_id_for_custom_gcode == int(extruder_id)) { + if (has_custom_gcode_to_emit && extruder_id_for_custom_gcode == int(extruder_extrusions.extruder_id)) { assert(m_writer.extruder()->id() == extruder_id_for_custom_gcode); assert(m_pending_pre_extrusion_gcode.empty()); // Now we have picked the right extruder, so we can emit the custom g-code. gcode += ProcessLayer::emit_custom_gcode_per_print_z(*this, *layer_tools.custom_gcode, m_writer.extruder()->id(), first_extruder_id, print.config()); } - if (auto loops_it = skirt_loops_per_extruder.find(extruder_id); loops_it != skirt_loops_per_extruder.end()) { - if (!this->m_config.complete_objects.value) { - gcode += this->m_label_objects.maybe_stop_instance(); - } + if (!extruder_extrusions.skirt.empty() || !extruder_extrusions.brim.empty()) { + gcode += m_label_objects.maybe_stop_instance(); + this->m_label_objects.update(nullptr); + } + + if (!this->m_moved_to_first_layer_point) { + const Vec3crd point{to_3d(first_point, scaled(print_z))}; + + gcode += this->travel_to_first_position(point, print_z, ExtrusionRole::Mixed, [this]() { + if (m_writer.multiple_extruders) { + return std::string{""}; + } + return m_label_objects.maybe_change_instance(m_writer); + }); + } + + if (!extruder_extrusions.skirt.empty()) { this->m_label_objects.update(nullptr); - const std::pair loops = loops_it->second; - this->set_origin(0., 0.); m_avoid_crossing_perimeters.use_external_mp(); Flow layer_skirt_flow = print.skirt_flow().with_height(float(m_skirt_done.back() - (m_skirt_done.size() == 1 ? 0. : m_skirt_done[m_skirt_done.size() - 2]))); double mm3_per_mm = layer_skirt_flow.mm3_per_mm(); - for (size_t i = loops.first; i < loops.second; ++i) { + for (const auto&[_, smooth_path] : extruder_extrusions.skirt) { // Adjust flow according to this layer's layer height. //FIXME using the support_material_speed of the 1st object printed. - gcode += this->extrude_skirt(dynamic_cast(*print.skirt().entities[i]), + gcode += this->extrude_skirt(smooth_path, // Override of skirt extrusion parameters. extrude_skirt() will fill in the extrusion width. - ExtrusionFlow{ mm3_per_mm, 0., layer_skirt_flow.height() }, - smooth_path_caches.global(), "skirt"sv, m_config.support_material_speed.value); + ExtrusionFlow{ mm3_per_mm, 0., layer_skirt_flow.height() } + ); } m_avoid_crossing_perimeters.use_external_mp(false); // Allow a straight travel move to the first object point if this is the first layer (but don't in next layers). - if (first_layer && loops.first == 0) + if (first_layer && extruder_extrusions.skirt.front().first == 0) m_avoid_crossing_perimeters.disable_once(); } - // Extrude brim with the extruder of the 1st region. - if (! m_brim_done) { - - if (!this->m_config.complete_objects.value) { - gcode += this->m_label_objects.maybe_stop_instance(); - } - this->m_label_objects.update(nullptr); - - this->set_origin(0., 0.); + if (!extruder_extrusions.brim.empty()) { m_avoid_crossing_perimeters.use_external_mp(); - for (const ExtrusionEntity *ee : print.brim().entities) - gcode += this->extrude_entity({ *ee, false }, smooth_path_caches.global(), "brim"sv, m_config.support_material_speed.value); - m_brim_done = true; + + for (const GCode::ExtrusionOrder::BrimPath &brim_path : extruder_extrusions.brim) { + gcode += this->extrude_smooth_path(brim_path.path, brim_path.is_loop, "brim", m_config.support_material_speed.value); + } m_avoid_crossing_perimeters.use_external_mp(false); // Allow a straight travel move to the first object point. m_avoid_crossing_perimeters.disable_once(); } - this->m_label_objects.update(first_instance); - // We are almost ready to print. However, we must go through all the objects twice to print the the overridden extrusions first (infill/perimeter wiping feature): - bool is_anything_overridden = layer_tools.wiping_extrusions().is_anything_overridden(); - if (is_anything_overridden) { + m_label_objects.update(first_instance); + + if (!extruder_extrusions.overriden_extrusions.empty()) { // Extrude wipes. size_t gcode_size_old = gcode.size(); - for (const InstanceToPrint &instance : instances_to_print) - this->process_layer_single_object( - gcode, extruder_id, instance, - layers[instance.object_layer_to_print_id], layer_tools, smooth_path_caches.layer_local(), - is_anything_overridden, true /* print_wipe_extrusions */); - if (gcode_size_old < gcode.size()) + for (std::size_t i{0}; i < instances_to_print.size(); ++i) { + const InstanceToPrint &instance{instances_to_print[i]}; + using GCode::ExtrusionOrder::OverridenExtrusions; + const OverridenExtrusions &overriden_extrusions{extruder_extrusions.overriden_extrusions[i]}; + if (is_empty(overriden_extrusions.slices_extrusions)) { + continue; + } + this->initialize_instance(instance, layers[instance.object_layer_to_print_id]); + gcode += this->extrude_slices( + instance, layers[instance.object_layer_to_print_id], + overriden_extrusions.slices_extrusions + ); + } + if (gcode_size_old < gcode.size()) { gcode+="; PURGING FINISHED\n"; + } } + // Extrude normal extrusions. - for (const InstanceToPrint &instance : instances_to_print) - this->process_layer_single_object( - gcode, extruder_id, instance, - layers[instance.object_layer_to_print_id], layer_tools, smooth_path_caches.layer_local(), - is_anything_overridden, false /* print_wipe_extrusions */); - } + for (std::size_t i{0}; i < instances_to_print.size(); ++i) { + const InstanceToPrint &instance{instances_to_print[i]}; + using GCode::ExtrusionOrder::SupportPath; + const std::vector &support_extrusions{extruder_extrusions.normal_extrusions[i].support_extrusions}; + const ObjectLayerToPrint &layer_to_print{layers[instance.object_layer_to_print_id]}; + const std::vector &slices_extrusions{extruder_extrusions.normal_extrusions[i].slices_extrusions}; + if (support_extrusions.empty() && is_empty(slices_extrusions)) { + continue; + } + this->initialize_instance(instance, layers[instance.object_layer_to_print_id]); - // During layer change the starting position of next layer is now known. - // The solution is thus to emplace a temporary tag to the gcode, cache the postion and - // replace the tag later. The tag is Layer_Change_Travel, the cached position is - // m_current_layer_first_position and it is replaced here. - const std::string tag = GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change_Travel); - std::string layer_change_gcode; - const bool do_ramping_layer_change = ( - m_previous_layer_last_position - && m_current_layer_first_position - && m_layer_change_extruder_id - && !result.spiral_vase_enable - && print_z > previous_layer_z - && this->m_config.travel_ramping_lift.get_at(*m_layer_change_extruder_id) - && this->m_config.travel_slope.get_at(*m_layer_change_extruder_id) > 0 - && this->m_config.travel_slope.get_at(*m_layer_change_extruder_id) < 90 - ); - if (first_layer) { - layer_change_gcode = ""; // Explicit for readability. - } else if (do_ramping_layer_change) { - const Vec3d &from{*m_previous_layer_last_position}; - const Vec3d &to{*m_current_layer_first_position}; - layer_change_gcode = this->get_ramping_layer_change_gcode(from, to, *m_layer_change_extruder_id); - } else { - layer_change_gcode = this->writer().get_travel_to_z_gcode(print_z, "simple layer change"); - } + if (!support_extrusions.empty()) { + m_layer = layer_to_print.support_layer; + m_object_layer_over_raft = false; + gcode += this->extrude_support(support_extrusions); + } - const auto keep_retraciton{[&](){ - if (!do_ramping_layer_change) { - return true; - } - const double travel_length{(*m_current_layer_first_position - *m_previous_layer_last_position_before_wipe).norm()}; - if (this->m_config.retract_before_travel.get_at(*m_layer_change_extruder_id) < travel_length) { - // Travel is long, keep retraction. - return true; - } - return false; - }}; - - bool removed_retraction{false}; - if (this->m_config.travel_ramping_lift.get_at(*m_layer_change_extruder_id) && !result.spiral_vase_enable) { - const std::string retraction_start_tag = GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change_Retraction_Start); - const std::string retraction_end_tag = GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change_Retraction_End); - - if (keep_retraciton()) { - boost::algorithm::replace_first(gcode, retraction_start_tag, ""); - boost::algorithm::replace_first(gcode, retraction_end_tag, ""); - } else { - const std::size_t start{gcode.find(retraction_start_tag)}; - const std::size_t end_tag_start{gcode.find(retraction_end_tag)}; - const std::size_t end{end_tag_start + retraction_end_tag.size()}; - gcode.replace(start, end - start, ""); - - layer_change_gcode = this->get_ramping_layer_change_gcode(*m_previous_layer_last_position_before_wipe, *m_current_layer_first_position, *m_layer_change_extruder_id); - - removed_retraction = true; + gcode += this->extrude_slices( + instance, layer_to_print, slices_extrusions + ); } + this->set_origin(0.0, 0.0); } - if (removed_retraction) { - const std::size_t start{gcode.find("FIRST_UNRETRACT")}; - const std::size_t end{gcode.find("\n", start)}; - gcode.replace(start, end - start, ""); - } else { - boost::algorithm::replace_first(gcode,"FIRST_UNRETRACT", ""); - } - - boost::algorithm::replace_first(gcode, tag, layer_change_gcode); BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z << log_memory_info(); @@ -2620,228 +2709,57 @@ LayerResult GCodeGenerator::process_layer( } static const auto comment_perimeter = "perimeter"sv; -// Comparing string_view pointer & length for speed. -static inline bool comment_is_perimeter(const std::string_view comment) { - return comment.data() == comment_perimeter.data() && comment.size() == comment_perimeter.size(); -} - -void GCodeGenerator::process_layer_single_object( - // output - std::string &gcode, - // Index of the extruder currently active. - const unsigned int extruder_id, - // What object and instance is going to be printed. - const InstanceToPrint &print_instance, - // and the object & support layer of the above. - const ObjectLayerToPrint &layer_to_print, - // Container for extruder overrides (when wiping into object or infill). - const LayerTools &layer_tools, - // Optional smooth path interpolating extrusion polylines. - const GCode::SmoothPathCache &smooth_path_cache, - // Is any extrusion possibly marked as wiping extrusion? - const bool is_anything_overridden, - // Round 1 (wiping into object or infill) or round 2 (normal extrusions). - const bool print_wipe_extrusions) -{ - bool first = true; - // Delay layer initialization as many layers may not print with all extruders. - auto init_layer_delayed = [this, &print_instance, &layer_to_print, &first]() { - if (first) { - first = false; - const PrintObject &print_object = print_instance.print_object; - const Print &print = *print_object.print(); - m_config.apply(print_object.config(), true); - m_layer = layer_to_print.layer(); - if (print.config().avoid_crossing_perimeters) - m_avoid_crossing_perimeters.init_layer(*m_layer); - // When starting a new object, use the external motion planner for the first travel move. - const Point &offset = print_object.instances()[print_instance.instance_id].shift; - GCode::PrintObjectInstance next_instance = {&print_object, int(print_instance.instance_id)}; - if (m_current_instance != next_instance) { - m_avoid_crossing_perimeters.use_external_mp_once = true; - } - m_current_instance = next_instance; - this->set_origin(unscale(offset)); - m_label_objects.update(&print_instance.print_object.instances()[print_instance.instance_id]); - } - }; +void GCodeGenerator::initialize_instance( + const InstanceToPrint &print_instance, + const ObjectLayerToPrint &layer_to_print +) { const PrintObject &print_object = print_instance.print_object; const Print &print = *print_object.print(); - if (! print_wipe_extrusions && layer_to_print.support_layer != nullptr) - if (const SupportLayer &support_layer = *layer_to_print.support_layer; ! support_layer.support_fills.entities.empty()) { - ExtrusionRole role = support_layer.support_fills.role(); - bool has_support = role.is_mixed() || role.is_support_base(); - bool has_interface = role.is_mixed() || role.is_support_interface(); - // Extruder ID of the support base. -1 if "don't care". - unsigned int support_extruder = print_object.config().support_material_extruder.value - 1; - // Shall the support be printed with the active extruder, preferably with non-soluble, to avoid tool changes? - bool support_dontcare = support_extruder == std::numeric_limits::max(); - // Extruder ID of the support interface. -1 if "don't care". - unsigned int interface_extruder = print_object.config().support_material_interface_extruder.value - 1; - // Shall the support interface be printed with the active extruder, preferably with non-soluble, to avoid tool changes? - bool interface_dontcare = interface_extruder == std::numeric_limits::max(); - if (support_dontcare || interface_dontcare) { - // Some support will be printed with "don't care" material, preferably non-soluble. - // Is the current extruder assigned a soluble filament? - auto it_nonsoluble = std::find_if(layer_tools.extruders.begin(), layer_tools.extruders.end(), - [&soluble = std::as_const(print.config().filament_soluble)](unsigned int extruder_id) { return ! soluble.get_at(extruder_id); }); - // There should be a non-soluble extruder available. - assert(it_nonsoluble != layer_tools.extruders.end()); - unsigned int dontcare_extruder = it_nonsoluble == layer_tools.extruders.end() ? layer_tools.extruders.front() : *it_nonsoluble; - if (support_dontcare) - support_extruder = dontcare_extruder; - if (interface_dontcare) - interface_extruder = dontcare_extruder; - } - bool extrude_support = has_support && support_extruder == extruder_id; - bool extrude_interface = has_interface && interface_extruder == extruder_id; - if (extrude_support || extrude_interface) { - init_layer_delayed(); - m_layer = layer_to_print.support_layer; - m_object_layer_over_raft = false; - ExtrusionEntitiesPtr entities_cache; - const ExtrusionEntitiesPtr &entities = extrude_support && extrude_interface ? support_layer.support_fills.entities : entities_cache; - if (! extrude_support || ! extrude_interface) { - auto role = extrude_support ? ExtrusionRole::SupportMaterial : ExtrusionRole::SupportMaterialInterface; - entities_cache.reserve(support_layer.support_fills.entities.size()); - for (ExtrusionEntity *ee : support_layer.support_fills.entities) - if (ee->role() == role) - entities_cache.emplace_back(ee); - } - gcode += this->extrude_support(chain_extrusion_references(entities), smooth_path_cache); - } - } + m_config.apply(print_object.config(), true); + m_layer = layer_to_print.layer(); + if (print.config().avoid_crossing_perimeters) + m_avoid_crossing_perimeters.init_layer(*m_layer); + // When starting a new object, use the external motion planner for the first travel move. + const Point &offset = print_object.instances()[print_instance.instance_id].shift; + GCode::PrintObjectInstance next_instance = {&print_object, int(print_instance.instance_id)}; + if (m_current_instance != next_instance) { + m_avoid_crossing_perimeters.use_external_mp_once = true; + } + m_current_instance = next_instance; + this->set_origin(unscale(offset)); + m_label_objects.update(&print_instance.print_object.instances()[print_instance.instance_id]); +} + +std::string GCodeGenerator::extrude_slices( + const InstanceToPrint &print_instance, + const ObjectLayerToPrint &layer_to_print, + const std::vector &slices_extrusions +) { + const PrintObject &print_object = print_instance.print_object; m_layer = layer_to_print.layer(); // To control print speed of the 1st object layer printed over raft interface. m_object_layer_over_raft = layer_to_print.object_layer && layer_to_print.object_layer->id() > 0 && print_object.slicing_parameters().raft_layers() == layer_to_print.object_layer->id(); - // Check whether this ExtrusionEntityCollection should be printed now with extruder_id, given print_wipe_extrusions - // (wipe extrusions are printed before regular extrusions). - auto shall_print_this_extrusion_collection = [extruder_id, instance_id = print_instance.instance_id, &layer_tools, is_anything_overridden, print_wipe_extrusions](const ExtrusionEntityCollection *eec, const PrintRegion ®ion) -> bool { - assert(eec != nullptr); - if (eec->entities.empty()) - // This shouldn't happen. FIXME why? but first_point() would fail. - return false; - // This extrusion is part of certain Region, which tells us which extruder should be used for it: - int correct_extruder_id = layer_tools.extruder(*eec, region); - if (! layer_tools.has_extruder(correct_extruder_id)) { - // this entity is not overridden, but its extruder is not in layer_tools - we'll print it - // by last extruder on this layer (could happen e.g. when a wiping object is taller than others - dontcare extruders are eradicated from layer_tools) - correct_extruder_id = layer_tools.extruders.back(); - } - int extruder_override_id = is_anything_overridden ? layer_tools.wiping_extrusions().get_extruder_override(eec, instance_id) : -1; - return print_wipe_extrusions ? - extruder_override_id == int(extruder_id) : - extruder_override_id < 0 && int(extruder_id) == correct_extruder_id; - }; - - ExtrusionEntitiesPtr temp_fill_extrusions; - if (const Layer *layer = layer_to_print.object_layer; layer) - for (size_t idx : layer->lslice_indices_sorted_by_print_order) { - const LayerSlice &lslice = layer->lslices_ex[idx]; - auto extrude_infill_range = [&]( - const LayerRegion &layerm, const ExtrusionEntityCollection &fills, - LayerExtrusionRanges::const_iterator it_fill_ranges_begin, LayerExtrusionRanges::const_iterator it_fill_ranges_end, bool ironing) { - // PrintObjects own the PrintRegions, thus the pointer to PrintRegion would be unique to a PrintObject, they would not - // identify the content of PrintRegion accross the whole print uniquely. Translate to a Print specific PrintRegion. - const PrintRegion ®ion = print.get_print_region(layerm.region().print_region_id()); - temp_fill_extrusions.clear(); - for (auto it_fill_range = it_fill_ranges_begin; it_fill_range != it_fill_ranges_end; ++ it_fill_range) { - assert(it_fill_range->region() == it_fill_ranges_begin->region()); - for (uint32_t fill_id : *it_fill_range) { - assert(dynamic_cast(fills.entities[fill_id])); - if (auto *eec = static_cast(fills.entities[fill_id]); - (eec->role() == ExtrusionRole::Ironing) == ironing && shall_print_this_extrusion_collection(eec, region)) { - if (eec->can_reverse()) - // Flatten the infill collection for better path planning. - for (auto *ee : eec->entities) - temp_fill_extrusions.emplace_back(ee); - else - temp_fill_extrusions.emplace_back(eec); - } - } - } - if (! temp_fill_extrusions.empty()) { - init_layer_delayed(); - m_config.apply(region.config()); - const auto extrusion_name = ironing ? "ironing"sv : "infill"sv; - const Point* start_near = this->last_position ? &(*(this->last_position)) : nullptr; - for (const ExtrusionEntityReference &fill : chain_extrusion_references(temp_fill_extrusions, start_near)) - if (auto *eec = dynamic_cast(&fill.extrusion_entity()); eec) { - for (const ExtrusionEntityReference &ee : chain_extrusion_references(*eec, start_near, fill.flipped())) - gcode += this->extrude_entity(ee, smooth_path_cache, extrusion_name); - } else - gcode += this->extrude_entity(fill, smooth_path_cache, extrusion_name); - } - }; - - //FIXME order islands? - // Sequential tool path ordering of multiple parts within the same object, aka. perimeter tracking (#5511) - for (const LayerIsland &island : lslice.islands) { - auto process_perimeters = [&]() { - const LayerRegion &layerm = *layer->get_region(island.perimeters.region()); - // PrintObjects own the PrintRegions, thus the pointer to PrintRegion would be unique to a PrintObject, they would not - // identify the content of PrintRegion accross the whole print uniquely. Translate to a Print specific PrintRegion. - const PrintRegion ®ion = print.get_print_region(layerm.region().print_region_id()); - bool first = true; - for (uint32_t perimeter_id : island.perimeters) { - // Extrusions inside islands are expected to be ordered already. - // Don't reorder them. - assert(dynamic_cast(layerm.perimeters().entities[perimeter_id])); - if (const auto *eec = static_cast(layerm.perimeters().entities[perimeter_id]); - shall_print_this_extrusion_collection(eec, region)) { - // This may not apply to Arachne, but maybe the Arachne gap fill should disable reverse as well? - // assert(! eec->can_reverse()); - if (first) { - first = false; - init_layer_delayed(); - m_config.apply(region.config()); - } - for (const ExtrusionEntity *ee : *eec) { - // Don't reorder, don't flip. - gcode += this->extrude_entity({*ee, false}, smooth_path_cache, comment_perimeter, -1.); - m_travel_obstacle_tracker.mark_extruded(ee, print_instance.object_layer_to_print_id, print_instance.instance_id); - } - } - } - }; - auto process_infill = [&]() { - for (auto it = island.fills.begin(); it != island.fills.end();) { - // Gather range of fill ranges with the same region. - auto it_end = it; - for (++ it_end; it_end != island.fills.end() && it->region() == it_end->region(); ++ it_end) ; - const LayerRegion &layerm = *layer->get_region(it->region()); - extrude_infill_range(layerm, layerm.fills(), it, it_end, false /* normal extrusions, not ironing */); - it = it_end; - } - }; - if (print.config().infill_first) { - process_infill(); - process_perimeters(); - } else { - process_perimeters(); - process_infill(); - } - } - // ironing - //FIXME move ironing into the loop above over LayerIslands? - // First Ironing changes extrusion rate quickly, second single ironing may be done over multiple perimeter regions. - // Ironing in a second phase is safer, but it may be less efficient. - for (const LayerIsland &island : lslice.islands) { - for (auto it = island.fills.begin(); it != island.fills.end();) { - // Gather range of fill ranges with the same region. - auto it_end = it; - for (++ it_end; it_end != island.fills.end() && it->region() == it_end->region(); ++ it_end) ; - const LayerRegion &layerm = *layer->get_region(it->region()); - extrude_infill_range(layerm, layerm.fills(), it, it_end, true /* ironing, not normal extrusions */); - it = it_end; - } + std::string gcode; + for (const SliceExtrusions &slice_extrusions : slices_extrusions) { + for (const IslandExtrusions &island_extrusions : slice_extrusions.common_extrusions) { + if (island_extrusions.infill_first) { + gcode += this->extrude_infill_ranges(island_extrusions.infill_ranges, "infill"); + gcode += this->extrude_perimeters(*island_extrusions.region, island_extrusions.perimeters, print_instance); + } else { + gcode += this->extrude_perimeters(*island_extrusions.region, island_extrusions.perimeters, print_instance); + gcode += this->extrude_infill_ranges(island_extrusions.infill_ranges, "infill"); } } + + gcode += this->extrude_infill_ranges(slice_extrusions.ironing_extrusions, "ironing"); + } + + return gcode; } void GCodeGenerator::apply_print_config(const PrintConfig &print_config) @@ -2878,7 +2796,7 @@ void GCodeGenerator::encode_full_config(const Print& print, std::vectoris_nil()) + if (!is_banned(key)) config.emplace_back(key, cfg.opt_serialize(key)); } config.shrink_to_fit(); @@ -2918,7 +2836,9 @@ std::string GCodeGenerator::preamble() std::string GCodeGenerator::change_layer( coordf_t previous_layer_z, coordf_t print_z, - bool vase_mode + bool vase_mode, + const Point &first_point, + const bool first_layer ) { std::string gcode; if (m_layer_count > 0) @@ -2929,21 +2849,23 @@ std::string GCodeGenerator::change_layer( gcode += m_label_objects.maybe_change_instance(m_writer); } - if (!EXTRUDER_CONFIG(travel_ramping_lift) && EXTRUDER_CONFIG(retract_layer_change)) { - gcode += this->retract_and_wipe(); - } else if (EXTRUDER_CONFIG(travel_ramping_lift) && !vase_mode){ - m_previous_layer_last_position_before_wipe = this->last_position ? - std::optional{to_3d(this->point_to_gcode(*this->last_position), previous_layer_z)} : - std::nullopt; - gcode += GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change_Retraction_Start); - gcode += this->retract_and_wipe(false, false); - gcode += GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change_Retraction_End); - gcode += m_writer.reset_e(); - } + const unsigned extruder_id{m_writer.extruder()->id()}; + const bool do_ramping_layer_change = ( + this->last_position + && !vase_mode + && print_z > previous_layer_z + && this->m_config.travel_ramping_lift.get_at(extruder_id) + && this->m_config.travel_slope.get_at(extruder_id) > 0 + && this->m_config.travel_slope.get_at(extruder_id) < 90 + ); + if (do_ramping_layer_change) { + Vec3d from{to_3d(this->point_to_gcode(*this->last_position), previous_layer_z)}; + const Vec3d to{to_3d(unscaled(first_point), print_z)}; + const double travel_length{(to - from).norm()}; - Vec3d new_position = this->writer().get_position(); - new_position.z() = print_z; - this->writer().update_position(new_position); + if (this->m_config.retract_before_travel.get_at(extruder_id) < travel_length) { + gcode += this->retract_and_wipe(); + } //B38 //B46 m_writer.add_object_change_labels(gcode); @@ -2951,8 +2873,20 @@ std::string GCodeGenerator::change_layer( std::optional{to_3d(this->point_to_gcode(*this->last_position), previous_layer_z)} : std::nullopt; - gcode += GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change_Travel); - this->m_layer_change_extruder_id = m_writer.extruder()->id(); + this->writer().update_position(to); + this->last_position = this->gcode_to_point(unscaled(first_point)); + } else { + if (EXTRUDER_CONFIG(retract_layer_change)) { + gcode += this->retract_and_wipe(); + } + if (!first_layer) { + // travel_to_z is not used as it may not generate the travel if the writter z == print_z. + gcode += this->writer().get_travel_to_z_gcode(print_z, "simple layer change"); + Vec3d position{this->writer().get_position()}; + position.z() = print_z; + this->writer().update_position(position); + } + } // forget last wiping path as wiping after raising Z is pointless m_wipe.reset_path(); @@ -2960,27 +2894,16 @@ std::string GCodeGenerator::change_layer( return gcode; } -#ifndef NDEBUG -static inline bool validate_smooth_path(const GCode::SmoothPath &smooth_path, bool loop) -{ - for (auto it = std::next(smooth_path.begin()); it != smooth_path.end(); ++ it) { - assert(it->path.size() >= 2); - assert(std::prev(it)->path.back().point == it->path.front().point); - } - assert(! loop || smooth_path.front().path.front().point == smooth_path.back().path.back().point); - return true; -} -#endif //NDEBUG +std::string GCodeGenerator::extrude_smooth_path( + const GCode::SmoothPath &smooth_path, const bool is_loop, const std::string_view description, const double speed +) { + std::string gcode; -static constexpr const double min_gcode_segment_length = 0.002; - -std::string GCodeGenerator::extrude_loop(const ExtrusionLoop &loop_src, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed) -{ - // Extrude all loops CCW. - //w38 - //bool is_hole = loop_src.is_clockwise(); - ExtrusionLoop new_loop_src = loop_src; - bool is_hole = (new_loop_src.loop_role() & elrHole) == elrHole; + // Extrude along the smooth path. + bool is_bridge_extruded = false; + EmitModifiers emit_modifiers = EmitModifiers::create_with_disabled_emits(); + for (auto el_it = smooth_path.begin(); el_it != smooth_path.end(); ++el_it) { + const auto next_el_it = next(el_it); //w38 if (m_config.spiral_vase && !is_hole) { @@ -3050,122 +2973,88 @@ std::string GCodeGenerator::extrude_loop(const ExtrusionLoop &loop_src, const GC } std::string GCodeGenerator::extrude_skirt( - const ExtrusionLoop &loop_src, const ExtrusionFlow &extrusion_flow_override, - const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed) + GCode::SmoothPath smooth_path, const ExtrusionFlow &extrusion_flow_override) { - assert(loop_src.is_counter_clockwise()); - Point seam_point = this->last_position.has_value() ? *this->last_position : Point::Zero(); - GCode::SmoothPath smooth_path = smooth_path_cache.resolve_or_fit_split_with_seam( - loop_src, false, m_scaled_resolution, seam_point, scaled(0.0015)); - - // Clip the path to avoid the extruder to get exactly on the first point of the loop; - // if polyline was shorter than the clipping distance we'd get a null polyline, so - // we discard it in that case. - if (m_enable_loop_clipping) - clip_end(smooth_path, scale_(EXTRUDER_CONFIG(nozzle_diameter)) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER, scaled(min_gcode_segment_length)); - - if (smooth_path.empty()) - return {}; - - assert(validate_smooth_path(smooth_path, ! m_enable_loop_clipping)); - // Extrude along the smooth path. std::string gcode; for (GCode::SmoothPathElement &el : smooth_path) { // Override extrusion parameters. el.path_attributes.mm3_per_mm = extrusion_flow_override.mm3_per_mm; el.path_attributes.height = extrusion_flow_override.height; - gcode += this->_extrude(el.path_attributes, el.path, description, speed); } - // reset acceleration - gcode += m_writer.set_print_acceleration(fast_round_up(m_config.default_acceleration.value)); - - if (m_wipe.enabled()) - // Wipe will hide the seam. - m_wipe.set_path(std::move(smooth_path)); + gcode += this->extrude_smooth_path(smooth_path, true, "skirt"sv, m_config.support_material_speed.value); return gcode; } -std::string GCodeGenerator::extrude_multi_path(const ExtrusionMultiPath &multipath, bool reverse, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed) -{ -#ifndef NDEBUG - for (auto it = std::next(multipath.paths.begin()); it != multipath.paths.end(); ++ it) { - assert(it->polyline.points.size() >= 2); - assert(std::prev(it)->polyline.last_point() == it->polyline.first_point()); +std::string GCodeGenerator::extrude_infill_ranges( + const std::vector &infill_ranges, + const std::string &comment +) { + std::string gcode{}; + for (const InfillRange &infill_range : infill_ranges) { + if (!infill_range.items.empty()) { + this->m_config.apply(infill_range.region->config()); + for (const GCode::SmoothPath &path : infill_range.items) { + gcode += this->extrude_smooth_path(path, false, comment, -1.0); + } + } } -#endif // NDEBUG - GCode::SmoothPath smooth_path = smooth_path_cache.resolve_or_fit(multipath, reverse, m_scaled_resolution); - - // extrude along the path - std::string gcode; - for (GCode::SmoothPathElement &el : smooth_path) - gcode += this->_extrude(el.path_attributes, el.path, description, speed); - - GCode::reverse(smooth_path); - m_wipe.set_path(std::move(smooth_path)); - - // reset acceleration - gcode += m_writer.set_print_acceleration((unsigned int)floor(m_config.default_acceleration.value + 0.5)); return gcode; } -std::string GCodeGenerator::extrude_entity(const ExtrusionEntityReference &entity, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed) -{ - if (const ExtrusionPath *path = dynamic_cast(&entity.extrusion_entity())) - return this->extrude_path(*path, entity.flipped(), smooth_path_cache, description, speed); - else if (const ExtrusionMultiPath *multipath = dynamic_cast(&entity.extrusion_entity())) - return this->extrude_multi_path(*multipath, entity.flipped(), smooth_path_cache, description, speed); - else if (const ExtrusionLoop *loop = dynamic_cast(&entity.extrusion_entity())) - return this->extrude_loop(*loop, smooth_path_cache, description, speed); - else - throw Slic3r::InvalidArgument("Invalid argument supplied to extrude()"); - return {}; -} +std::string GCodeGenerator::extrude_perimeters( + const PrintRegion ®ion, + const std::vector &perimeters, + const InstanceToPrint &print_instance +) { + if (!perimeters.empty()) { + m_config.apply(region.config()); + } -std::string GCodeGenerator::extrude_path(const ExtrusionPath &path, bool reverse, const GCode::SmoothPathCache &smooth_path_cache, std::string_view description, double speed) -{ - Geometry::ArcWelder::Path smooth_path = smooth_path_cache.resolve_or_fit(path, reverse, m_scaled_resolution); - std::string gcode = this->_extrude(path.attributes(), smooth_path, description, speed); - Geometry::ArcWelder::reverse(smooth_path); - m_wipe.set_path(std::move(smooth_path)); - // reset acceleration - gcode += m_writer.set_print_acceleration((unsigned int)floor(m_config.default_acceleration.value + 0.5)); + std::string gcode{}; + + for (const GCode::ExtrusionOrder::Perimeter &perimeter : perimeters) { + double speed{-1}; + // Apply the small perimeter speed. + //w41 + if (perimeter.extrusion_entity->length() <= SMALL_PERIMETER_LENGTH){ + speed = m_config.small_perimeter_speed.get_abs_value(m_config.perimeter_speed); + is_small_perimeter_length = true; + } + + gcode += this->extrude_smooth_path(perimeter.smooth_path, true, comment_perimeter, speed); + this->m_travel_obstacle_tracker.mark_extruded( + perimeter.extrusion_entity, print_instance.object_layer_to_print_id, print_instance.instance_id + ); + + if (!m_wipe.enabled() && perimeter.extrusion_entity->role().is_external_perimeter() && m_layer != nullptr && m_config.perimeters.value > 1) { + // Only wipe inside if the wipe along the perimeter is disabled. + // Make a little move inwards before leaving loop. + if (std::optional pt = wipe_hide_seam(perimeter.smooth_path, perimeter.reversed, scale_(EXTRUDER_CONFIG(nozzle_diameter))); pt) { + // Generate the seam hiding travel move. + gcode += m_writer.travel_to_xy(this->point_to_gcode(*pt), "move inwards before travel"); + this->last_position = *pt; + } + } + } return gcode; -} +}; -std::string GCodeGenerator::extrude_support(const ExtrusionEntityReferences &support_fills, const GCode::SmoothPathCache &smooth_path_cache) +std::string GCodeGenerator::extrude_support(const std::vector &support_extrusions) { static constexpr const auto support_label = "support material"sv; static constexpr const auto support_interface_label = "support material interface"sv; std::string gcode; - if (! support_fills.empty()) { + if (! support_extrusions.empty()) { const double support_speed = m_config.support_material_speed.value; const double support_interface_speed = m_config.support_material_interface_speed.get_abs_value(support_speed); - for (const ExtrusionEntityReference &eref : support_fills) { - ExtrusionRole role = eref.extrusion_entity().role(); - assert(role == ExtrusionRole::SupportMaterial || role == ExtrusionRole::SupportMaterialInterface); - const auto label = (role == ExtrusionRole::SupportMaterial) ? support_label : support_interface_label; - const double speed = (role == ExtrusionRole::SupportMaterial) ? support_speed : support_interface_speed; - const ExtrusionPath *path = dynamic_cast(&eref.extrusion_entity()); - if (path) - gcode += this->extrude_path(*path, eref.flipped(), smooth_path_cache, label, speed); - else if (const ExtrusionMultiPath *multipath = dynamic_cast(&eref.extrusion_entity()); multipath) - gcode += this->extrude_multi_path(*multipath, eref.flipped(), smooth_path_cache, label, speed); - else { - const ExtrusionEntityCollection *eec = dynamic_cast(&eref.extrusion_entity()); - assert(eec); - if (eec) { - //FIXME maybe order the support here? - ExtrusionEntityReferences refs; - refs.reserve(eec->entities.size()); - std::transform(eec->entities.begin(), eec->entities.end(), std::back_inserter(refs), - [flipped = eref.flipped()](const ExtrusionEntity *ee) { return ExtrusionEntityReference{ *ee, flipped }; }); - gcode += this->extrude_support(refs, smooth_path_cache); - } - } + for (const GCode::ExtrusionOrder::SupportPath &path : support_extrusions) { + const auto label = path.is_interface ? support_interface_label : support_label; + const double speed = path.is_interface ? support_interface_speed : support_speed; + gcode += this->extrude_smooth_path(path.path, false, label, speed); } } return gcode; @@ -3251,10 +3140,6 @@ std::string GCodeGenerator::travel_to_first_position(const Vec3crd& point, const *this->last_position, point.head<2>(), role, "travel to first layer point", insert_gcode ); } else { - this->m_layer_change_used_external_mp = this->m_avoid_crossing_perimeters.use_external_mp_once; - this->m_layer_change_layer = this->layer(); - this->m_layer_change_origin = this->origin(); - double lift{ EXTRUDER_CONFIG(travel_ramping_lift) ? EXTRUDER_CONFIG(travel_max_lift) : EXTRUDER_CONFIG(retract_lift)}; @@ -3283,22 +3168,31 @@ std::string GCodeGenerator::travel_to_first_position(const Vec3crd& point, const this->writer().update_position(gcode_point); } - m_current_layer_first_position = gcode_point; - + this->m_moved_to_first_layer_point = true; return gcode; } double cap_speed( - double speed, const double mm3_per_mm, const FullPrintConfig &config, int extruder_id + double speed, const FullPrintConfig &config, int extruder_id, const ExtrusionAttributes &path_attr ) { - const double general_cap{config.max_volumetric_speed.value}; - if (general_cap > 0) { - speed = std::min(speed, general_cap / mm3_per_mm); + const double general_volumetric_cap{config.max_volumetric_speed.value}; + if (general_volumetric_cap > 0) { + speed = std::min(speed, general_volumetric_cap / path_attr.mm3_per_mm); } - const double filament_cap{config.filament_max_volumetric_speed.get_at(extruder_id)}; - if (filament_cap > 0) { - speed = std::min(speed, filament_cap / mm3_per_mm); + const double filament_volumetric_cap{config.filament_max_volumetric_speed.get_at(extruder_id)}; + if (filament_volumetric_cap > 0) { + speed = std::min(speed, filament_volumetric_cap / path_attr.mm3_per_mm); } + if (path_attr.role == ExtrusionRole::InternalInfill) { + const double infill_cap{ + path_attr.maybe_self_crossing ? + config.filament_infill_max_crossing_speed.get_at(extruder_id) : + config.filament_infill_max_speed.get_at(extruder_id)}; + if (infill_cap > 0) { + speed = std::min(speed, infill_cap); + } + } + return speed; } @@ -3306,7 +3200,8 @@ std::string GCodeGenerator::_extrude( const ExtrusionAttributes &path_attr, const Geometry::ArcWelder::Path &path, const std::string_view description, - double speed) + double speed, + const EmitModifiers &emit_modifiers) { std::string gcode; const std::string_view description_bridge = path_attr.role.is_bridge() ? " (bridge)"sv : ""sv; @@ -3318,45 +3213,29 @@ std::string GCodeGenerator::_extrude( gcode += m_label_objects.maybe_change_instance(m_writer); } - if (!m_current_layer_first_position) { - const Vec3crd point = to_3d(path.front().point, scaled(this->m_last_layer_z)); - gcode += this->travel_to_first_position(point, unscaled(point.z()), path_attr.role, [&](){ + if (!this->last_position) { + const double z = this->m_last_layer_z; + const std::string comment{"move to print after unknown position"}; + gcode += this->retract_and_wipe(); + gcode += m_writer.multiple_extruders ? "" : m_label_objects.maybe_change_instance(m_writer); + gcode += this->m_writer.travel_to_xy(this->point_to_gcode(path.front().point), comment); + gcode += this->m_writer.get_travel_to_z_gcode(z, comment); + } else if ( this->last_position != path.front().point) { + std::string comment = "move to first "; + comment += description; + comment += description_bridge; + comment += " point"; + const std::string travel_gcode{this->travel_to(*this->last_position, path.front().point, path_attr.role, comment, [this](){ return m_writer.multiple_extruders ? "" : m_label_objects.maybe_change_instance(m_writer); - }); - } else { - // go to first point of extrusion path - if (!this->last_position) { - const double z = this->m_last_layer_z; - const std::string comment{"move to print after unknown position"}; - gcode += this->retract_and_wipe(); - gcode += m_writer.multiple_extruders ? "" : m_label_objects.maybe_change_instance(m_writer); - gcode += this->m_writer.travel_to_xy(this->point_to_gcode(path.front().point), comment); - gcode += this->m_writer.get_travel_to_z_gcode(z, comment); - } else if ( this->last_position != path.front().point) { - std::string comment = "move to first "; - comment += description; - comment += description_bridge; - comment += " point"; - const std::string travel_gcode{this->travel_to(*this->last_position, path.front().point, path_attr.role, comment, [&](){ - return m_writer.multiple_extruders ? "" : m_label_objects.maybe_change_instance(m_writer); - })}; - gcode += travel_gcode; - } + })}; + gcode += travel_gcode; } //B38 //B46 m_writer.add_object_change_labels(gcode); // compensate retraction - if (this->m_already_unretracted) { - gcode += this->unretract(); - } else { - this->m_already_unretracted = true; - gcode += "FIRST_UNRETRACT" + this->unretract(); - - //First unretract may or may not be removed thus we must start from E0. - gcode += this->writer().reset_e(); - } + gcode += this->unretract(); if (m_writer.multiple_extruders && !has_active_instance) { gcode += m_label_objects.maybe_change_instance(m_writer); @@ -3454,7 +3333,7 @@ std::string GCodeGenerator::_extrude( - std::pair dynamic_speed_and_fan_speed{-1, -1}; + ExtrusionProcessor::OverhangSpeeds dynamic_print_and_fan_speeds = {-1.f, -1.f}; if (path_attr.overhang_attributes.has_value()) { double external_perim_reference_speed = m_config.get_abs_value("external_perimeter_speed"); if (external_perim_reference_speed == 0) @@ -3475,23 +3354,25 @@ std::string GCodeGenerator::_extrude( m_resonance_avoidance = false; } } - speed = dynamic_speed_and_fan_speed.first; + speed = dynamic_print_and_fan_speeds.print_speed; } // cap speed with max_volumetric_speed anyway (even if user is not using autospeed) - speed = cap_speed(speed, path_attr.mm3_per_mm, m_config, m_writer.extruder()->id()); + speed = cap_speed(speed, m_config,m_writer.extruder()->id(),path_attr); if (m_resonance_avoidance) { if (speed <= m_config.opt_float("max_resonance_avoidance_speed")) { speed = std::min(speed, m_config.opt_float("min_resonance_avoidance_speed")); + is_small_perimeter_length = false ; } m_resonance_avoidance = true; } } else { - if (dynamic_speed_and_fan_speed.first > -1) { - speed = dynamic_speed_and_fan_speed.first; + if (dynamic_print_and_fan_speeds.print_speed > -1) { + speed = dynamic_print_and_fan_speeds.print_speed; } - speed = cap_speed(speed, path_attr.mm3_per_mm, m_config, m_writer.extruder()->id()); + speed = cap_speed(speed, m_config, m_writer.extruder()->id(), path_attr); + is_small_perimeter_length = false ; } double F = speed * 60; // convert mm/sec to mm/min @@ -3530,14 +3411,6 @@ std::string GCodeGenerator::_extrude( + float_to_string_decimal_point(m_last_width) + "\n"; } -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - if (last_was_wipe_tower || (m_last_mm3_per_mm != path_attr.mm3_per_mm)) { - m_last_mm3_per_mm = path_attr.mm3_per_mm; - gcode += std::string(";") + GCodeProcessor::Mm3_Per_Mm_Tag - + float_to_string_decimal_point(m_last_mm3_per_mm) + "\n"; - } -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING - if (last_was_wipe_tower || std::abs(m_last_height - path_attr.height) > EPSILON) { m_last_height = path_attr.height; @@ -3547,18 +3420,30 @@ std::string GCodeGenerator::_extrude( std::string cooling_marker_setspeed_comments; if (m_enable_cooling_markers) { - if (path_attr.role.is_bridge()) + if (path_attr.role.is_bridge() && emit_modifiers.emit_bridge_fan_start) { gcode += ";_BRIDGE_FAN_START\n"; - else + } else if (!path_attr.role.is_bridge()) { cooling_marker_setspeed_comments = ";_EXTRUDE_SET_SPEED"; - if (path_attr.role == ExtrusionRole::ExternalPerimeter) + } + + if (path_attr.role == ExtrusionRole::ExternalPerimeter) { cooling_marker_setspeed_comments += ";_EXTERNAL_PERIMETER"; + } } // F is mm per minute. gcode += m_writer.set_speed(F, "", cooling_marker_setspeed_comments); - if (dynamic_speed_and_fan_speed.second >= 0) - gcode += ";_SET_FAN_SPEED" + std::to_string(int(dynamic_speed_and_fan_speed.second)) + "\n"; + + if (dynamic_print_and_fan_speeds.fan_speed >= 0) { + const int fan_speed = int(dynamic_print_and_fan_speeds.fan_speed); + if (!m_current_dynamic_fan_speed.has_value() || (m_current_dynamic_fan_speed.has_value() && m_current_dynamic_fan_speed != fan_speed)) { + m_current_dynamic_fan_speed = fan_speed; + gcode += ";_SET_FAN_SPEED" + std::to_string(fan_speed) + "\n"; + } + } else if (m_current_dynamic_fan_speed.has_value() && dynamic_print_and_fan_speeds.fan_speed < 0) { + m_current_dynamic_fan_speed.reset(); + gcode += ";_RESET_FAN_SPEED\n"; + } std::string comment; if (m_config.gcode_comments) { @@ -3608,11 +3493,18 @@ std::string GCodeGenerator::_extrude( } } - if (m_enable_cooling_markers) - gcode += path_attr.role.is_bridge() ? ";_BRIDGE_FAN_END\n" : ";_EXTRUDE_END\n"; + if (m_enable_cooling_markers) { + if (path_attr.role.is_bridge() && emit_modifiers.emit_bridge_fan_end) { + gcode += ";_BRIDGE_FAN_END\n"; + } else if (!path_attr.role.is_bridge()) { + gcode += ";_EXTRUDE_END\n"; + } + } - if (dynamic_speed_and_fan_speed.second >= 0) + if (m_current_dynamic_fan_speed.has_value() && emit_modifiers.emit_fan_speed_reset) { + m_current_dynamic_fan_speed.reset(); gcode += ";_RESET_FAN_SPEED\n"; + } this->last_position = path.back().point; return gcode; diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 9e852ea..1c77386 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -1,7 +1,8 @@ #ifndef slic3r_GCode_hpp_ #define slic3r_GCode_hpp_ -#include "GCode/ExtrusionProcessor.hpp" +#include "libslic3r/GCode/ExtrusionOrder.hpp" +#include "libslic3r/GCode/ExtrusionProcessor.hpp" #include "JumpPointSearch.hpp" #include "libslic3r.h" #include "ExPolygon.hpp" @@ -10,22 +11,22 @@ #include "PlaceholderParser.hpp" #include "PrintConfig.hpp" #include "Geometry/ArcWelder.hpp" -#include "GCode/AvoidCrossingPerimeters.hpp" -#include "GCode/CoolingBuffer.hpp" -#include "GCode/FindReplace.hpp" -#include "GCode/GCodeWriter.hpp" -#include "GCode/LabelObjects.hpp" -#include "GCode/PressureEqualizer.hpp" -#include "GCode/RetractWhenCrossingPerimeters.hpp" -#include "GCode/SmoothPath.hpp" -#include "GCode/SpiralVase.hpp" -#include "GCode/ToolOrdering.hpp" -#include "GCode/Wipe.hpp" -#include "GCode/WipeTowerIntegration.hpp" -#include "GCode/SeamPlacer.hpp" -#include "GCode/GCodeProcessor.hpp" -#include "GCode/ThumbnailData.hpp" -#include "GCode/Travels.hpp" +#include "libslic3r/GCode/AvoidCrossingPerimeters.hpp" +#include "libslic3r/GCode/CoolingBuffer.hpp" +#include "libslic3r/GCode/FindReplace.hpp" +#include "libslic3r/GCode/GCodeWriter.hpp" +#include "libslic3r/GCode/LabelObjects.hpp" +#include "libslic3r/GCode/PressureEqualizer.hpp" +#include "libslic3r/GCode/RetractWhenCrossingPerimeters.hpp" +#include "libslic3r/GCode/SmoothPath.hpp" +#include "libslic3r/GCode/SpiralVase.hpp" +#include "libslic3r/GCode/ToolOrdering.hpp" +#include "libslic3r/GCode/Wipe.hpp" +#include "libslic3r/GCode/WipeTowerIntegration.hpp" +#include "libslic3r/GCode/SeamPlacer.hpp" +#include "libslic3r/GCode/GCodeProcessor.hpp" +#include "libslic3r/GCode/ThumbnailData.hpp" +#include "libslic3r/GCode/Travels.hpp" #include "EdgeGrid.hpp" #include "tcbspan/span.hpp" @@ -51,7 +52,7 @@ public: OozePrevention() : enable(false) {} std::string pre_toolchange(GCodeGenerator &gcodegen); std::string post_toolchange(GCodeGenerator &gcodegen); - + private: int _get_temp(const GCodeGenerator &gcodegen) const; }; @@ -78,18 +79,6 @@ struct LayerResult { }; namespace GCode { -// Object and support extrusions of the same PrintObject at the same print_z. -// public, so that it could be accessed by free helper functions from GCode.cpp -struct ObjectLayerToPrint -{ - ObjectLayerToPrint() : object_layer(nullptr), support_layer(nullptr) {} - const Layer* object_layer; - const SupportLayer* support_layer; - const Layer* layer() const { return (object_layer != nullptr) ? object_layer : support_layer; } - const PrintObject* object() const { return (this->layer() != nullptr) ? this->layer()->object() : nullptr; } - coordf_t print_z() const { return (object_layer != nullptr && support_layer != nullptr) ? 0.5 * (object_layer->print_z + support_layer->print_z) : this->layer()->print_z; } -}; - struct PrintObjectInstance { const PrintObject *print_object = nullptr; @@ -102,7 +91,8 @@ struct PrintObjectInstance } // namespace GCode class GCodeGenerator { -public: + +public: GCodeGenerator(const Print* print = nullptr); // The default value is only used in unit tests. ~GCodeGenerator() = default; @@ -127,13 +117,14 @@ public: ); if constexpr (Derived::SizeAtCompileTime == 2) { - return Vec2d(unscaled(point.x()), unscaled(point.y())) + m_origin - - m_config.extruder_offset.get_at(m_writer.extruder()->id()); + return Vec2d(unscaled(point.x()), unscaled(point.y())) + m_origin + - m_config.extruder_offset.get_at(m_writer.extruder()->id()); } else { const Vec2d gcode_point_xy{this->point_to_gcode(point.template head<2>())}; return to_3d(gcode_point_xy, unscaled(point.z())); } } + // Convert coordinates of the active object to G-code coordinates, possibly adjusted for extruder offset and quantized to G-code resolution. template Vec2d point_to_gcode_quantized(const Eigen::MatrixBase &point) const { @@ -162,12 +153,16 @@ public: static void encode_full_config(const Print& print, std::vector>& config); using ObjectLayerToPrint = GCode::ObjectLayerToPrint; - using ObjectsLayerToPrint = std::vector; + using ObjectsLayerToPrint = GCode::ObjectsLayerToPrint; std::optional last_position; - private: + using InstanceToPrint = GCode::InstanceToPrint; + using InfillRange = GCode::ExtrusionOrder::InfillRange; + using SliceExtrusions = GCode::ExtrusionOrder::SliceExtrusions; + using IslandExtrusions = GCode::ExtrusionOrder::IslandExtrusions; + class GCodeOutputStream { public: GCodeOutputStream(FILE *f, GCodeProcessor &processor) : f(f), m_processor(processor) {} @@ -182,7 +177,7 @@ private: bool is_open() const { return f; } bool is_error() const; - + void flush(); void close(); @@ -214,12 +209,23 @@ private: Polyline get_layer_change_xy_path(const Vec3d &from, const Vec3d &to); std::string get_ramping_layer_change_gcode(const Vec3d &from, const Vec3d &to, const unsigned extruder_id); + /** @brief Generates ramping travel gcode for layer change. */ std::string generate_ramping_layer_change_gcode( const Polyline &xy_path, const double initial_elevation, const GCode::Impl::Travels::ElevatedTravelParams &elevation_params ) const; + + std::vector get_sorted_extrusions( + const Print &print, + const ObjectsLayerToPrint &layers, + const LayerTools &layer_tools, + const std::vector &instances_to_print, + const GCode::SmoothPathCaches &smooth_path_caches, + const bool first_layer + ); + LayerResult process_layer( const Print &print, // Set of object & print layers of the same PrintObject and with the same print_z. @@ -232,6 +238,7 @@ private: // If set to size_t(-1), then print all copies of all objects. // Otherwise print a single copy of a single object. const size_t single_object_idx = size_t(-1)); + // Process all layers of all objects (non-sequential mode) with a parallel pipeline: // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser // and export G-code into file. @@ -258,27 +265,14 @@ private: std::string change_layer( coordf_t previous_layer_z, coordf_t print_z, - bool vase_mode + bool vase_mode, + const Point &first_point, + const bool first_layer ); - std::string extrude_entity(const ExtrusionEntityReference &entity, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed = -1.); - std::string extrude_loop(const ExtrusionLoop &loop, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed = -1.); - std::string extrude_skirt(const ExtrusionLoop &loop_src, const ExtrusionFlow &extrusion_flow_override, - const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed); - - std::string extrude_multi_path(const ExtrusionMultiPath &multipath, bool reverse, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed = -1.); - std::string extrude_path(const ExtrusionPath &path, bool reverse, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed = -1.); - - struct InstanceToPrint - { - InstanceToPrint(size_t object_layer_to_print_id, const PrintObject &print_object, size_t instance_id) : - object_layer_to_print_id(object_layer_to_print_id), print_object(print_object), instance_id(instance_id) {} - - // Index into std::vector, which contains Object and Support layers for the current print_z, collected for a single object, or for possibly multiple objects with multiple instances. - const size_t object_layer_to_print_id; - const PrintObject &print_object; - // Instance idx of the copy of a print object. - const size_t instance_id; - }; + std::string extrude_smooth_path( + const GCode::SmoothPath &smooth_path, const bool is_loop, const std::string_view description, const double speed + ); + std::string extrude_skirt(GCode::SmoothPath smooth_path, const ExtrusionFlow &extrusion_flow_override); std::vector sort_print_object_instances( // Object and Support layers for the current print_z, collected for a single object, or for possibly multiple objects with multiple instances. @@ -288,26 +282,32 @@ private: // For sequential print, the instance of the object to be printing has to be defined. const size_t single_object_instance_idx); - // This function will be called for each printing extruder, possibly twice: First for wiping extrusions, second for normal extrusions. - void process_layer_single_object( - // output - std::string &gcode, - // Index of the extruder currently active. - const unsigned int extruder_id, - // What object and instance is going to be printed. - const InstanceToPrint &print_instance, - // and the object & support layer of the above. - const ObjectLayerToPrint &layer_to_print, - // Container for extruder overrides (when wiping into object or infill). - const LayerTools &layer_tools, - // Optional smooth path interpolating extrusion polylines. - const GCode::SmoothPathCache &smooth_path_cache, - // Is any extrusion possibly marked as wiping extrusion? - const bool is_anything_overridden, - // Round 1 (wiping into object or infill) or round 2 (normal extrusions). - const bool print_wipe_extrusions); + std::string extrude_perimeters( + const PrintRegion ®ion, + const std::vector &perimeters, + const InstanceToPrint &print_instance + ); + + std::string extrude_infill_ranges( + const std::vector &infill_ranges, + const std::string &commment + ); + + void initialize_instance( + const InstanceToPrint &print_instance, + const ObjectLayerToPrint &layer_to_print + ); + + std::string extrude_slices( + const InstanceToPrint &print_instance, + const ObjectLayerToPrint &layer_to_print, + const std::vector &slices_extrusions + ); + + std::string extrude_support( + const std::vector &support_extrusions + ); - std::string extrude_support(const ExtrusionEntityReferences &support_fills, const GCode::SmoothPathCache &smooth_path_cache); std::string generate_travel_gcode( const Points3& travel, const std::string& comment, @@ -328,6 +328,7 @@ private: ); std::string travel_to_first_position(const Vec3crd& point, const double from_z, const ExtrusionRole role, const std::function& insert_gcode); + bool needs_retraction(const Polyline &travel, ExtrusionRole role = ExtrusionRole::None); //B41 @@ -342,9 +343,8 @@ private: std::string unretract() { return m_writer.unretract(); } std::string set_extruder(unsigned int extruder_id, double print_z); bool line_distancer_is_required(const std::vector& extruder_ids); - // Cache for custom seam enforcers/blockers for each layer. - SeamPlacer m_seam_placer; + Seams::Placer m_seam_placer; /* Origin of print coordinates expressed in unscaled G-code coordinates. This affects the input arguments supplied to the extrude*() and travel_to() @@ -420,26 +420,21 @@ private: float m_last_layer_z{ 0.0f }; float m_max_layer_z{ 0.0f }; float m_last_width{ 0.0f }; -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - double m_last_mm3_per_mm; -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING std::optional m_previous_layer_last_position; std::optional m_previous_layer_last_position_before_wipe; - // This needs to be populated during the layer processing! - std::optional m_current_layer_first_position; - std::optional m_layer_change_extruder_id; - bool m_layer_change_used_external_mp{false}; - const Layer* m_layer_change_layer{nullptr}; - std::optional m_layer_change_origin; - bool m_already_unretracted{false}; + bool m_moved_to_first_layer_point{false}; + // This needs to be populated during the layer processing! std::unique_ptr m_cooling_buffer; std::unique_ptr m_spiral_vase; std::unique_ptr m_find_replace; std::unique_ptr m_pressure_equalizer; std::unique_ptr m_wipe_tower; + // Current fan speed set by dynamic fan speed control. + std::optional m_current_dynamic_fan_speed; + // Heights (print_z) at which the skirt has already been extruded. std::vector m_skirt_done; // Has the brim been extruded already? Brim is being extruded only for the first object of a multi-object print. @@ -461,12 +456,29 @@ private: // Back-pointer to Print (const). const Print* m_print; - std::string _extrude( - const ExtrusionAttributes &attribs, const Geometry::ArcWelder::Path &path, const std::string_view description, double speed = -1); + + struct EmitModifiers { + EmitModifiers(bool emit_fan_speed_reset, bool emit_bridge_fan_start, bool emit_bridge_fan_end) + : emit_fan_speed_reset(emit_fan_speed_reset), emit_bridge_fan_start(emit_bridge_fan_start), emit_bridge_fan_end(emit_bridge_fan_end) {} + + EmitModifiers() : EmitModifiers(true, true, true) {}; + + static EmitModifiers create_with_disabled_emits() { + return {false, false, false}; + } + + bool emit_fan_speed_reset = true; + + bool emit_bridge_fan_start = true; + bool emit_bridge_fan_end = true; + }; + + std::string _extrude(const ExtrusionAttributes &attribs, const Geometry::ArcWelder::Path &path, std::string_view description, double speed, const EmitModifiers &emit_modifiers = EmitModifiers()); + void print_machine_envelope(GCodeOutputStream &file, const Print &print); + // void _print_first_layer_chamber_temperature(GCodeOutputStream &file, const Print &print, const std::string &gcode, int temp, bool wait, bool accurate); void _print_first_layer_bed_temperature(GCodeOutputStream &file, const Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait); void _print_first_layer_extruder_temperatures(GCodeOutputStream &file, const Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait); - // On the first printing layer. This flag triggers first layer speeds. bool on_first_layer() const { return m_layer != nullptr && m_layer->id() == 0; } //w25 diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 136946d..05b9765 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -1,3 +1,15 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "../Layer.hpp" #include "../GCode.hpp" #include "../EdgeGrid.hpp" @@ -6,12 +18,15 @@ #include "../ExPolygon.hpp" #include "../Geometry.hpp" #include "../ClipperUtils.hpp" -#include "../SVG.hpp" -#include "AvoidCrossingPerimeters.hpp" - -#include -#include -#include +#include "libslic3r/GCode/AvoidCrossingPerimeters.hpp" +#include "libslic3r/Config.hpp" +#include "libslic3r/Flow.hpp" +#include "libslic3r/LayerRegion.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Surface.hpp" +#include "libslic3r/Utils.hpp" +#include "libslic3r/libslic3r.h" //#define AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp index ea2737c..afaff96 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp @@ -1,9 +1,16 @@ #ifndef slic3r_AvoidCrossingPerimeters_hpp_ #define slic3r_AvoidCrossingPerimeters_hpp_ -#include "../libslic3r.h" -#include "../ExPolygon.hpp" -#include "../EdgeGrid.hpp" +#include + +#include "libslic3r/libslic3r.h" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/EdgeGrid.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/Layer.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/Polyline.hpp" +#include "libslic3r/ShortestPath.hpp" namespace Slic3r { diff --git a/src/libslic3r/GCode/ConflictChecker.cpp b/src/libslic3r/GCode/ConflictChecker.cpp index 5c2cdd6..c87cd29 100644 --- a/src/libslic3r/GCode/ConflictChecker.cpp +++ b/src/libslic3r/GCode/ConflictChecker.cpp @@ -1,12 +1,23 @@ -#include "libslic3r.h" +// #include "libslic3r.h" #include "ConflictChecker.hpp" -#include -#include - +#include +#include +#include #include #include -#include +#include +#include +#include +#include +#include +#include + +#include "libslic3r/ExtrusionEntityCollection.hpp" +#include "libslic3r/GCode/WipeTower.hpp" +#include "libslic3r/Geometry.hpp" +#include "libslic3r/LayerRegion.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { @@ -89,6 +100,8 @@ inline Grids line_rasterization(const Line &line, int64_t xdist = RasteXDistance } } // namespace RasterizationImpl + + static std::vector getFakeExtrusionPathsFromWipeTower(const WipeTowerData& wtd) { float h = wtd.height; @@ -169,6 +182,9 @@ static std::vector getFakeExtrusionPathsFromWipeTower(const Wipe return paths; } + + + void LinesBucketQueue::emplace_back_bucket(std::vector &&paths, const void *objPtr, Points offsets) { if (_objsPtrToId.find(objPtr) == _objsPtrToId.end()) { @@ -291,6 +307,7 @@ ConflictResultOpt ConflictChecker::find_inter_of_lines_in_diff_objs(SpanOfConstP // The code ported from BS uses void* to identify objects... // Let's use the address of this variable to represent the wipe tower. int wtptr = 0; + LinesBucketQueue conflictQueue; if (! wipe_tower_data.z_and_depth_pairs.empty()) { // The wipe tower is being generated. @@ -344,9 +361,9 @@ ConflictResultOpt ConflictChecker::find_inter_of_lines_in_diff_objs(SpanOfConstP if (ptr1 == &wtptr || ptr2 == &wtptr) { assert(! wipe_tower_data.z_and_depth_pairs.empty()); if (ptr2 == &wtptr) { std::swap(ptr1, ptr2); } - const PrintObject *obj2 = reinterpret_cast(ptr2); - return std::make_optional("WipeTower", obj2->model_object()->name, conflictHeight, nullptr, ptr2); - } + const PrintObject *obj2 = reinterpret_cast(ptr2); + return std::make_optional("WipeTower", obj2->model_object()->name, conflictHeight, nullptr, ptr2); + } const PrintObject *obj1 = reinterpret_cast(ptr1); const PrintObject *obj2 = reinterpret_cast(ptr2); return std::make_optional(obj1->model_object()->name, obj2->model_object()->name, conflictHeight, ptr1, ptr2); diff --git a/src/libslic3r/GCode/ConflictChecker.hpp b/src/libslic3r/GCode/ConflictChecker.hpp index f5fe422..bd7d36c 100644 --- a/src/libslic3r/GCode/ConflictChecker.hpp +++ b/src/libslic3r/GCode/ConflictChecker.hpp @@ -1,13 +1,24 @@ #ifndef slic3r_ConflictChecker_hpp_ #define slic3r_ConflictChecker_hpp_ -#include "libslic3r/Print.hpp" - #include #include #include +#include +#include +#include + +#include "libslic3r/Print.hpp" +#include "libslic3r/ExtrusionEntity.hpp" +#include "libslic3r/ExtrusionRole.hpp" +#include "libslic3r/GCode/GCodeProcessor.hpp" +#include "libslic3r/Layer.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polyline.hpp" namespace Slic3r { +class ExtrusionEntityCollection; struct LineWithID { diff --git a/src/libslic3r/GCode/CoolingBuffer.cpp b/src/libslic3r/GCode/CoolingBuffer.cpp index 72af425..539669a 100644 --- a/src/libslic3r/GCode/CoolingBuffer.cpp +++ b/src/libslic3r/GCode/CoolingBuffer.cpp @@ -1,11 +1,26 @@ -#include "../GCode.hpp" -#include "CoolingBuffer.hpp" -#include #include #include #include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../GCode.hpp" +#include "libslic3r/GCode/CoolingBuffer.hpp" +#include "libslic3r/Extruder.hpp" +#include "libslic3r/GCode/GCodeWriter.hpp" +#include "libslic3r/Geometry/ArcWelder.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/libslic3r.h" #if 0 #define DEBUG @@ -13,9 +28,7 @@ #undef NDEBUG #endif -#include - -#include +#include namespace Slic3r { @@ -71,6 +84,7 @@ struct CoolingLine TYPE_ADJUSTABLE_EMPTY = 1 << 16, // Custom fan speed (introduced for overhang fan speed) TYPE_SET_FAN_SPEED = 1 << 17, + // Reset fan speed back to speed calculate by the CoolingBuffer. TYPE_RESET_FAN_SPEED = 1 << 18, }; @@ -277,12 +291,14 @@ float new_feedrate_to_reach_time_stretch( } } assert(denom > 0); - if (denom <= 0) + if (nomin <= 0 || denom <= EPSILON) return min_feedrate; + new_feedrate = nomin / denom; assert(new_feedrate > min_feedrate - EPSILON); if (new_feedrate < min_feedrate + EPSILON) goto finished; + for (auto it = it_begin; it != it_end; ++ it) for (size_t i = 0; i < (*it)->n_lines_adjustable; ++i) { const CoolingLine &line = (*it)->lines[i]; @@ -433,7 +449,7 @@ std::vector CoolingBuffer::parse_layer_gcode(const std:: if (adjustment->dont_slow_down_outer_wall && external_perimeter) adjust_external = false; - if (boost::contains(sline, ";_EXTRUDE_SET_SPEED") && ! wipe && adjust_external) { + if (boost::contains(sline, ";_EXTRUDE_SET_SPEED") && ! wipe) { line.type |= CoolingLine::TYPE_ADJUSTABLE; active_speed_modifier = adjustment->lines.size(); } @@ -556,19 +572,20 @@ std::vector CoolingBuffer::parse_layer_gcode(const std:: fast_float::from_chars(sline.data() + (has_S ? pos_S : pos_P) + 1, sline.data() + sline.size(), line.time); if (has_P) line.time *= 0.001f; - } else + } else { line.time = 0; - line.time_max = line.time; - } + } - if (boost::contains(sline, ";_SET_FAN_SPEED")) { + line.time_max = line.time; + } else if (boost::contains(sline, ";_SET_FAN_SPEED")) { auto speed_start = sline.find_last_of('D'); int speed = 0; for (char num : sline.substr(speed_start + 1)) { speed = speed * 10 + (num - '0'); } - line.type |= CoolingLine::TYPE_SET_FAN_SPEED; + line.fan_speed = speed; + line.type |= CoolingLine::TYPE_SET_FAN_SPEED; } else if (boost::contains(sline, ";_RESET_FAN_SPEED")) { line.type |= CoolingLine::TYPE_RESET_FAN_SPEED; } @@ -805,9 +822,9 @@ std::string CoolingBuffer::apply_layer_cooldown( new_gcode.reserve(gcode.size() * 2); bool bridge_fan_control = false; int bridge_fan_speed = 0; - auto change_extruder_set_fan = [this, layer_id, layer_time, &new_gcode, &bridge_fan_control, &bridge_fan_speed]() { + auto change_extruder_set_fan = [this, layer_id, layer_time, &new_gcode, &bridge_fan_control, &bridge_fan_speed](const int requested_fan_speed = -1) { #define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_current_extruder) - int min_fan_speed = EXTRUDER_CONFIG(min_fan_speed); + const int min_fan_speed = EXTRUDER_CONFIG(min_fan_speed); //B15//Y26 int enable_auxiliary_fan; if (m_config.opt_bool("seal_print")) @@ -853,6 +870,7 @@ std::string CoolingBuffer::apply_layer_cooldown( bridge_fan_speed = std::clamp(int(float(bridge_fan_speed) * factor + 0.5f), 0, 100); custom_fan_speed_limits.second = fan_speed_new; } + #undef EXTRUDER_CONFIG bridge_fan_control = bridge_fan_speed > fan_speed_new; } else { // fan disabled diff --git a/src/libslic3r/GCode/CoolingBuffer.hpp b/src/libslic3r/GCode/CoolingBuffer.hpp index cd985d5..667d63a 100644 --- a/src/libslic3r/GCode/CoolingBuffer.hpp +++ b/src/libslic3r/GCode/CoolingBuffer.hpp @@ -1,15 +1,22 @@ #ifndef slic3r_CoolingBuffer_hpp_ #define slic3r_CoolingBuffer_hpp_ -#include "../libslic3r.h" +#include #include #include +#include +#include +#include + +#include "libslic3r/libslic3r.h" +#include "libslic3r/Point.hpp" namespace Slic3r { class GCodeGenerator; class Layer; struct PerExtruderAdjustments; +class PrintConfig; // A standalone G-code filter, to control cooling of the print. // The G-code is processed per layer. Once a layer is collected, fan start / stop commands are edited diff --git a/src/libslic3r/GCode/ExtrusionOrder.cpp b/src/libslic3r/GCode/ExtrusionOrder.cpp new file mode 100644 index 0000000..713bac7 --- /dev/null +++ b/src/libslic3r/GCode/ExtrusionOrder.cpp @@ -0,0 +1,752 @@ +#include "ExtrusionOrder.hpp" + +#include +#include +#include + +#include "libslic3r/GCode/SmoothPath.hpp" +#include "libslic3r/ShortestPath.hpp" +#include "libslic3r/Config.hpp" +#include "libslic3r/ExtrusionEntity.hpp" +#include "libslic3r/ExtrusionEntityCollection.hpp" +#include "libslic3r/ExtrusionRole.hpp" +#include "libslic3r/GCode/WipeTowerIntegration.hpp" +#include "libslic3r/Geometry/ArcWelder.hpp" +#include "libslic3r/LayerRegion.hpp" +#include "libslic3r/Print.hpp" + +namespace Slic3r::GCode::ExtrusionOrder { + +bool is_overriden(const ExtrusionEntityCollection &eec, const LayerTools &layer_tools, const std::size_t instance_id) { + return layer_tools.wiping_extrusions().get_extruder_override(&eec, instance_id) > -1; +} + +int get_extruder_id( + const ExtrusionEntityCollection &eec, + const LayerTools &layer_tools, + const PrintRegion ®ion, + const std::size_t instance_id +) { + if (is_overriden(eec, layer_tools, instance_id)) { + return layer_tools.wiping_extrusions().get_extruder_override(&eec, instance_id); + } + + const int extruder_id = layer_tools.extruder(eec, region); + if (! layer_tools.has_extruder(extruder_id)) { + // Extruder is not in layer_tools - we'll print it by last extruder on this layer (could + // happen e.g. when a wiping object is taller than others - dontcare extruders are + // eradicated from layer_tools) + return layer_tools.extruders.back(); + } + return extruder_id; +} + +Point get_gcode_point(const InstancePoint &point, const Point &offset) { + return point.local_point + offset; +} + +InstancePoint get_instance_point(const Point &point, const Point &offset) { + return {point - offset}; +} + +std::optional get_gcode_point(const std::optional &point, const Point &offset) { + if (point) { + return get_gcode_point(*point, offset); + } + return std::nullopt; +} + +std::optional get_instance_point(const std::optional &point, const Point &offset) { + if (point) { + return get_instance_point(*point, offset); + } + return std::nullopt; +} + +using ExtractEntityPredicate = std::function; + +ExtrusionEntitiesPtr extract_infill_extrusions( + const PrintRegion ®ion, + const ExtrusionEntityCollection &fills, + const LayerExtrusionRanges::const_iterator& begin, + const LayerExtrusionRanges::const_iterator& end, + const ExtractEntityPredicate &should_pick_extrusion +) { + ExtrusionEntitiesPtr result; + for (auto it = begin; it != end; ++ it) { + assert(it->region() == begin->region()); + const LayerExtrusionRange &range{*it}; + for (uint32_t fill_id : range) { + assert(dynamic_cast(fills.entities[fill_id])); + + auto *eec{static_cast(fills.entities[fill_id])}; + if (eec == nullptr || eec->empty() || !should_pick_extrusion(*eec, region)) { + continue; + } + + if (eec->can_reverse()) { + // Flatten the infill collection for better path planning. + for (auto *ee : eec->entities) { + result.emplace_back(ee); + } + } else { + result.emplace_back(eec); + } + } + } + return result; +} + +std::vector extract_perimeter_extrusions( + const Print &print, + const Layer &layer, + const LayerIsland &island, + const ExtractEntityPredicate &should_pick_extrusion, + const unsigned extruder_id, + const Point &offset, + std::optional &previous_position, + const PathSmoothingFunction &smooth_path +) { + std::vector result; + + const LayerRegion &layerm = *layer.get_region(island.perimeters.region()); + const PrintRegion ®ion = print.get_print_region(layerm.region().print_region_id()); + + for (uint32_t perimeter_id : island.perimeters) { + // Extrusions inside islands are expected to be ordered already. + // Don't reorder them. + assert(dynamic_cast(layerm.perimeters().entities[perimeter_id])); + auto *eec = static_cast(layerm.perimeters().entities[perimeter_id]); + if (eec == nullptr || eec->empty() || !should_pick_extrusion(*eec, region)) { + continue; + } + + for (ExtrusionEntity *ee : *eec) { + if (ee != nullptr) { + std::optional last_position{get_instance_point(previous_position, offset)}; + bool reverse_loop{false}; + if (auto loop = dynamic_cast(ee)) { + const bool is_hole = loop->is_clockwise(); + reverse_loop = print.config().prefer_clockwise_movements ? !is_hole : is_hole; + } + SmoothPath path{smooth_path(&layer, ExtrusionEntityReference{*ee, reverse_loop}, extruder_id, last_position)}; + previous_position = get_gcode_point(last_position, offset); + if (!path.empty()) { + result.push_back(Perimeter{std::move(path), reverse_loop, ee}); + } + } + } + } + + return result; +} + +std::vector sort_fill_extrusions(const ExtrusionEntitiesPtr &fills, const Point* start_near) { + if (fills.empty()) { + return {}; + } + std::vector sorted_extrusions; + + for (const ExtrusionEntityReference &fill : chain_extrusion_references(fills, start_near)) { + if (auto *eec = dynamic_cast(&fill.extrusion_entity()); eec) { + for (const ExtrusionEntityReference &ee : chain_extrusion_references(*eec, start_near, fill.flipped())) { + sorted_extrusions.push_back(ee); + } + } else { + sorted_extrusions.push_back(fill); + } + } + return sorted_extrusions; +} + +std::vector extract_infill_ranges( + const Print &print, + const Layer &layer, + const LayerIsland &island, + const Point &offset, + std::optional &previous_position, + const ExtractEntityPredicate &should_pick_extrusion, + const PathSmoothingFunction &smooth_path, + const unsigned extruder_id +) { + std::vector result; + for (auto it = island.fills.begin(); it != island.fills.end();) { + // Gather range of fill ranges with the same region. + auto it_end = it; + for (++ it_end; it_end != island.fills.end() && it->region() == it_end->region(); ++ it_end) ; + const LayerRegion &layerm = *layer.get_region(it->region()); + // PrintObjects own the PrintRegions, thus the pointer to PrintRegion would be unique to a PrintObject, they would not + // identify the content of PrintRegion accross the whole print uniquely. Translate to a Print specific PrintRegion. + const PrintRegion ®ion = print.get_print_region(layerm.region().print_region_id()); + + ExtrusionEntitiesPtr extrusions{extract_infill_extrusions( + region, + layerm.fills(), + it, + it_end, + should_pick_extrusion + )}; + + const std::optional previous_instance_point{get_instance_point(previous_position, offset)}; + const Point* start_near{previous_instance_point ? &(previous_instance_point->local_point) : nullptr}; + const ExtrusionEntityReferences sorted_extrusions{sort_fill_extrusions(extrusions, start_near)}; + + std::vector paths; + for (const ExtrusionEntityReference &extrusion_reference : sorted_extrusions) { + std::optional last_position{get_instance_point(previous_position, offset)}; + SmoothPath path{smooth_path(&layer, extrusion_reference, extruder_id, last_position)}; + if (!path.empty()) { + paths.push_back(std::move(path)); + } + previous_position = get_gcode_point(last_position, offset); + } + if (!paths.empty()) { + result.push_back({std::move(paths), ®ion}); + } + it = it_end; + } + return result; +} + +std::vector extract_island_extrusions( + const LayerSlice &lslice, + const Print &print, + const Layer &layer, + const ExtractEntityPredicate &should_pick_extrusion, + const PathSmoothingFunction &smooth_path, + const Point &offset, + const unsigned extruder_id, + std::optional &previous_position +) { + std::vector result; + for (const LayerIsland &island : lslice.islands) { + const LayerRegion &layerm = *layer.get_region(island.perimeters.region()); + // PrintObjects own the PrintRegions, thus the pointer to PrintRegion would be + // unique to a PrintObject, they would not identify the content of PrintRegion + // accross the whole print uniquely. Translate to a Print specific PrintRegion. + const PrintRegion ®ion = print.get_print_region(layerm.region().print_region_id()); + + const auto should_pick_infill = [&should_pick_extrusion](const ExtrusionEntityCollection &eec, const PrintRegion ®ion) { + return should_pick_extrusion(eec, region) && eec.role() != ExtrusionRole::Ironing; + }; + + result.push_back(IslandExtrusions{®ion}); + IslandExtrusions &island_extrusions{result.back()}; + island_extrusions.infill_first = print.config().infill_first; + + if (print.config().infill_first) { + island_extrusions.infill_ranges = extract_infill_ranges( + print, layer, island, offset, previous_position, should_pick_infill, smooth_path, extruder_id + ); + + island_extrusions.perimeters = extract_perimeter_extrusions(print, layer, island, should_pick_extrusion, extruder_id, offset, previous_position, smooth_path); + } else { + island_extrusions.perimeters = extract_perimeter_extrusions(print, layer, island, should_pick_extrusion, extruder_id, offset, previous_position, smooth_path); + + island_extrusions.infill_ranges = {extract_infill_ranges( + print, layer, island, offset, previous_position, should_pick_infill, smooth_path, extruder_id + )}; + } + } + return result; +} + +std::vector extract_ironing_extrusions( + const LayerSlice &lslice, + const Print &print, + const Layer &layer, + const ExtractEntityPredicate &should_pick_extrusion, + const PathSmoothingFunction &smooth_path, + const Point &offset, + const unsigned extruder_id, + std::optional &previous_position +) { + std::vector result; + + for (const LayerIsland &island : lslice.islands) { + const auto should_pick_ironing = [&should_pick_extrusion](const auto &eec, const auto ®ion) { + return should_pick_extrusion(eec, region) && eec.role() == ExtrusionRole::Ironing; + }; + + const std::vector ironing_ranges{extract_infill_ranges( + print, layer, island, offset, previous_position, should_pick_ironing, smooth_path, extruder_id + )}; + result.insert( + result.end(), ironing_ranges.begin(), ironing_ranges.end() + ); + } + return result; +} + +std::vector get_slices_extrusions( + const Print &print, + const Layer &layer, + const ExtractEntityPredicate &should_pick_extrusion, + const PathSmoothingFunction &smooth_path, + const Point &offset, + const unsigned extruder_id, + std::optional &previous_position +) { + // Note: ironing. + // FIXME move ironing into the loop above over LayerIslands? + // First Ironing changes extrusion rate quickly, second single ironing may be done over + // multiple perimeter regions. Ironing in a second phase is safer, but it may be less + // efficient. + + std::vector result; + + for (size_t idx : layer.lslice_indices_sorted_by_print_order) { + const LayerSlice &lslice = layer.lslices_ex[idx]; + std::vector island_extrusions{extract_island_extrusions( + lslice, print, layer, should_pick_extrusion, smooth_path, offset, extruder_id, previous_position + )}; + std::vector ironing_extrusions{extract_ironing_extrusions( + lslice, print, layer, should_pick_extrusion, smooth_path, offset, extruder_id, previous_position + )}; + if (!island_extrusions.empty() || !ironing_extrusions.empty()) { + result.emplace_back( + SliceExtrusions{std::move(island_extrusions), std::move(ironing_extrusions)} + ); + } + } + return result; +} + +unsigned translate_support_extruder( + const int configured_extruder, + const LayerTools &layer_tools, + const ConfigOptionBools &is_soluable +) { + if (configured_extruder <= 0) { + // Some support will be printed with "don't care" material, preferably non-soluble. + // Is the current extruder assigned a soluble filament? + auto it_nonsoluble = std::find_if(layer_tools.extruders.begin(), layer_tools.extruders.end(), + [&is_soluable](unsigned int extruder_id) { return ! is_soluable.get_at(extruder_id); }); + // There should be a non-soluble extruder available. + assert(it_nonsoluble != layer_tools.extruders.end()); + return it_nonsoluble == layer_tools.extruders.end() ? layer_tools.extruders.front() : *it_nonsoluble; + } else { + return configured_extruder - 1; + } +} + +std::vector get_support_extrusions( + const unsigned int extruder_id, + const GCode::ObjectLayerToPrint &layer_to_print, + unsigned int support_extruder, + unsigned int interface_extruder, + const PathSmoothingFunction &smooth_path, + std::optional &previous_position +) { + if (const SupportLayer &support_layer = *layer_to_print.support_layer; + !support_layer.support_fills.entities.empty()) { + ExtrusionRole role = support_layer.support_fills.role(); + bool has_support = role.is_mixed() || role.is_support_base(); + bool has_interface = role.is_mixed() || role.is_support_interface(); + + bool extrude_support = has_support && support_extruder == extruder_id; + bool extrude_interface = has_interface && interface_extruder == extruder_id; + + if (extrude_support || extrude_interface) { + ExtrusionEntitiesPtr entities_cache; + const ExtrusionEntitiesPtr &entities = extrude_support && extrude_interface ? + support_layer.support_fills.entities : + entities_cache; + if (!extrude_support || !extrude_interface) { + auto role = extrude_support ? ExtrusionRole::SupportMaterial : + ExtrusionRole::SupportMaterialInterface; + entities_cache.reserve(support_layer.support_fills.entities.size()); + for (ExtrusionEntity *ee : support_layer.support_fills.entities) + if (ee->role() == role) + entities_cache.emplace_back(ee); + } + std::vector paths; + for (const ExtrusionEntityReference &entity_reference : chain_extrusion_references(entities)) { + auto collection{dynamic_cast(&entity_reference.extrusion_entity())}; + const bool is_interface{entity_reference.extrusion_entity().role() != ExtrusionRole::SupportMaterial}; + if (collection != nullptr) { + for (const ExtrusionEntity * sub_entity : *collection) { + std::optional last_position{get_instance_point(previous_position, {0, 0})}; + SmoothPath path{smooth_path(nullptr, {*sub_entity, entity_reference.flipped()}, extruder_id, last_position)}; + if (!path.empty()) { + paths.push_back({std::move(path), is_interface}); + } + previous_position = get_gcode_point(last_position, {0, 0}); + } + } else { + std::optional last_position{get_instance_point(previous_position, {0, 0})}; + SmoothPath path{smooth_path(nullptr, entity_reference, extruder_id, last_position)}; + if (!path.empty()) { + paths.push_back({std::move(path), is_interface}); + } + previous_position = get_gcode_point(last_position, {0, 0}); + } + } + return paths; + } + } + return {}; +} + +std::vector get_overriden_extrusions( + const Print &print, + const GCode::ObjectsLayerToPrint &layers, + const LayerTools &layer_tools, + const std::vector &instances_to_print, + const unsigned int extruder_id, + const PathSmoothingFunction &smooth_path, + std::optional &previous_position +) { + std::vector result; + + for (const InstanceToPrint &instance : instances_to_print) { + if (const Layer *layer = layers[instance.object_layer_to_print_id].object_layer; layer) { + const auto should_pick_extrusion = [&layer_tools, &instance, &extruder_id](const ExtrusionEntityCollection &entity_collection, + const PrintRegion ®ion) { + if (!is_overriden(entity_collection, layer_tools, instance.instance_id)) { + return false; + } + + if (get_extruder_id( + entity_collection, layer_tools, region, instance.instance_id + ) != static_cast(extruder_id)) { + return false; + } + return true; + }; + + const PrintObject &print_object{instance.print_object}; + const Point &offset{print_object.instances()[instance.instance_id].shift}; + + std::vector slices_extrusions{get_slices_extrusions( + print, *layer, should_pick_extrusion, smooth_path, offset, extruder_id, previous_position + )}; + if (!slices_extrusions.empty()) { + result.push_back({offset, std::move(slices_extrusions)}); + } + } + } + return result; +} + +std::vector get_normal_extrusions( + const Print &print, + const GCode::ObjectsLayerToPrint &layers, + const LayerTools &layer_tools, + const std::vector &instances_to_print, + const unsigned int extruder_id, + const PathSmoothingFunction &smooth_path, + std::optional &previous_position +) { + std::vector result; + + for (std::size_t i{0}; i < instances_to_print.size(); ++i) { + const InstanceToPrint &instance{instances_to_print[i]}; + const PrintObject &print_object = instance.print_object; + const Point &offset = print_object.instances()[instance.instance_id].shift; + + result.emplace_back(); + result.back().instance_offset = offset; + + if (layers[instance.object_layer_to_print_id].support_layer != nullptr) { + result.back().support_extrusions = get_support_extrusions( + extruder_id, + layers[instance.object_layer_to_print_id], + translate_support_extruder(instance.print_object.config().support_material_extruder.value, layer_tools, print.config().filament_soluble), + translate_support_extruder(instance.print_object.config().support_material_interface_extruder.value, layer_tools, print.config().filament_soluble), + smooth_path, + previous_position + ); + } + + if (const Layer *layer = layers[instance.object_layer_to_print_id].object_layer; layer) { + const auto should_pick_extrusion{[&layer_tools, &instance, &extruder_id](const ExtrusionEntityCollection &entity_collection, const PrintRegion ®ion){ + if (is_overriden(entity_collection, layer_tools, instance.instance_id)) { + return false; + } + + if (get_extruder_id(entity_collection, layer_tools, region, instance.instance_id) != static_cast(extruder_id)) { + return false; + } + return true; + }}; + + result.back().slices_extrusions = get_slices_extrusions( + print, + *layer, + should_pick_extrusion, + smooth_path, + offset, + extruder_id, + previous_position + ); + } + } + return result; +} + +bool is_empty(const std::vector &extrusions) { + for (const SliceExtrusions &slice_extrusions : extrusions) { + for (const IslandExtrusions &island_extrusions : slice_extrusions.common_extrusions) { + if (!island_extrusions.perimeters.empty() || !island_extrusions.infill_ranges.empty()) { + return false; + } + } + if (!slice_extrusions.ironing_extrusions.empty()) { + return false; + } + } + return true; +} + +bool is_empty(const ExtruderExtrusions &extruder_extrusions) { + for (const OverridenExtrusions &overriden_extrusions : extruder_extrusions.overriden_extrusions) { + if (!is_empty(overriden_extrusions.slices_extrusions)) { + return false; + } + } + for (const NormalExtrusions &normal_extrusions : extruder_extrusions.normal_extrusions) { + if (!normal_extrusions.support_extrusions.empty()) { + return false; + } + if (!is_empty(normal_extrusions.slices_extrusions)) { + return false; + } + } + return true; +} + + +std::vector get_extrusions( + const Print &print, + const GCode::WipeTowerIntegration *wipe_tower, + const GCode::ObjectsLayerToPrint &layers, + const bool is_first_layer, + const LayerTools &layer_tools, + const std::vector &instances_to_print, + const std::map> &skirt_loops_per_extruder, + unsigned current_extruder_id, + const PathSmoothingFunction &smooth_path, + bool get_brim, + std::optional previous_position +) { + unsigned toolchange_number{0}; + + std::vector extrusions; + for (const unsigned int extruder_id : layer_tools.extruders) + { + ExtruderExtrusions extruder_extrusions{extruder_id}; + + if (layer_tools.has_wipe_tower && wipe_tower != nullptr) { + const bool finish_wipe_tower{extruder_id == layer_tools.extruders.back()}; + if (finish_wipe_tower || is_toolchange_required(is_first_layer, layer_tools.extruders.back(), extruder_id, current_extruder_id)) { + const bool ignore_sparse{print.config().wipe_tower_no_sparse_layers.value}; + if (const auto tool_change{wipe_tower->get_toolchange(toolchange_number, ignore_sparse)}) { + toolchange_number++; + previous_position = Point::new_scale(wipe_tower->transform_wt_pt(tool_change->end_pos)); + current_extruder_id = tool_change->new_tool; + extruder_extrusions.wipe_tower_start = Point::new_scale(wipe_tower->transform_wt_pt(tool_change->start_pos)); + } + } + } + + + if (auto loops_it = skirt_loops_per_extruder.find(extruder_id); loops_it != skirt_loops_per_extruder.end()) { + const std::pair loops = loops_it->second; + for (std::size_t i = loops.first; i < loops.second; ++i) { + bool reverse{false}; + if (auto loop = dynamic_cast(print.skirt().entities[i])) { + const bool is_hole = loop->is_clockwise(); + reverse = print.config().prefer_clockwise_movements ? !is_hole : is_hole; + } + const ExtrusionEntityReference entity{*print.skirt().entities[i], reverse}; + std::optional last_position{get_instance_point(previous_position, {0, 0})}; + SmoothPath path{smooth_path(nullptr, entity, extruder_id, last_position)}; + previous_position = get_gcode_point(last_position, {0, 0}); + extruder_extrusions.skirt.emplace_back(i, std::move(path)); + } + } + + // Extrude brim with the extruder of the 1st region. + if (get_brim) { + for (const ExtrusionEntity *entity : print.brim().entities) { + bool reverse{false}; + bool is_loop{false}; + if (auto loop = dynamic_cast(entity)) { + const bool is_hole = loop->is_clockwise(); + is_loop = true; + reverse = print.config().prefer_clockwise_movements ? !is_hole : is_hole; + } + const ExtrusionEntityReference entity_reference{*entity, reverse}; + + std::optional last_position{get_instance_point(previous_position, {0, 0})}; + SmoothPath path{smooth_path(nullptr, entity_reference, extruder_id, last_position)}; + previous_position = get_gcode_point(last_position, {0, 0}); + extruder_extrusions.brim.push_back({std::move(path), is_loop}); + } + get_brim = false; + } + + using GCode::ExtrusionOrder::get_overriden_extrusions; + bool is_anything_overridden = layer_tools.wiping_extrusions().is_anything_overridden(); + if (is_anything_overridden) { + extruder_extrusions.overriden_extrusions = get_overriden_extrusions( + print, layers, layer_tools, instances_to_print, extruder_id, smooth_path, + previous_position + ); + } + + using GCode::ExtrusionOrder::get_normal_extrusions; + extruder_extrusions.normal_extrusions = get_normal_extrusions( + print, layers, layer_tools, instances_to_print, extruder_id, smooth_path, + previous_position + ); + + extrusions.push_back(std::move(extruder_extrusions)); + } + return extrusions; +} + +std::optional get_first_point(const SmoothPath &path) { + for (const SmoothPathElement & element : path) { + if (!element.path.empty()) { + return InstancePoint{element.path.front().point}; + } + } + return std::nullopt; +} + +std::optional get_first_point(const std::vector &smooth_paths) { + for (const SmoothPath &path : smooth_paths) { + if (auto result = get_first_point(path)) { + return result; + } + } + return std::nullopt; +} + +std::optional get_first_point(const std::vector &infill_ranges) { + for (const InfillRange &infill_range : infill_ranges) { + if (auto result = get_first_point(infill_range.items)) { + return result; + } + } + return std::nullopt; +} + +std::optional get_first_point(const std::vector &perimeters) { + for (const Perimeter &perimeter : perimeters) { + if (auto result = get_first_point(perimeter.smooth_path)) { + return result; + } + } + return std::nullopt; +} + +std::optional get_first_point(const std::vector &extrusions) { + for (const IslandExtrusions &island : extrusions) { + if (island.infill_first) { + if (auto result = get_first_point(island.infill_ranges)) { + return result; + } + if (auto result = get_first_point(island.perimeters)) { + return result; + } + } else { + if (auto result = get_first_point(island.perimeters)) { + return result; + } + if (auto result = get_first_point(island.infill_ranges)) { + return result; + } + } + } + return std::nullopt; +} + +std::optional get_first_point(const std::vector &extrusions) { + for (const SliceExtrusions &slice : extrusions) { + if (auto result = get_first_point(slice.common_extrusions)) { + return result; + } + } + return std::nullopt; +} + +std::optional get_first_point(const ExtruderExtrusions &extrusions) { + for (const auto&[_, path] : extrusions.skirt) { + if (auto result = get_first_point(path)) { + return result->local_point; + }; + } + for (const BrimPath &brim_path : extrusions.brim) { + if (auto result = get_first_point(brim_path.path)) { + return result->local_point; + }; + } + for (const OverridenExtrusions &overriden_extrusions : extrusions.overriden_extrusions) { + if (auto result = get_first_point(overriden_extrusions.slices_extrusions)) { + return result->local_point - overriden_extrusions.instance_offset; + } + } + + for (const NormalExtrusions &normal_extrusions : extrusions.normal_extrusions) { + for (const SupportPath &support_path : normal_extrusions.support_extrusions) { + if (auto result = get_first_point(support_path.path)) { + return result->local_point + normal_extrusions.instance_offset; + } + } + if (auto result = get_first_point(normal_extrusions.slices_extrusions)) { + return result->local_point + normal_extrusions.instance_offset; + } + } + + return std::nullopt; +} + +std::optional get_first_point(const std::vector &extrusions) { + if (extrusions.empty()) { + return std::nullopt; + } + + if (extrusions.front().wipe_tower_start) { + return extrusions.front().wipe_tower_start; + } + + for (const ExtruderExtrusions &extruder_extrusions : extrusions) { + if (auto result = get_first_point(extruder_extrusions)) { + return result; + } + } + + return std::nullopt; +} + +const PrintInstance * get_first_instance( + const std::vector &extrusions, + const std::vector &instances_to_print +) { + for (const ExtruderExtrusions &extruder_extrusions : extrusions) { + if (!extruder_extrusions.overriden_extrusions.empty()) { + for (std::size_t i{0}; i < instances_to_print.size(); ++i) { + const OverridenExtrusions &overriden_extrusions{extruder_extrusions.overriden_extrusions[i]}; + if (!is_empty(overriden_extrusions.slices_extrusions)) { + const InstanceToPrint &instance{instances_to_print[i]}; + return &instance.print_object.instances()[instance.instance_id]; + } + } + } + for (std::size_t i{0}; i < instances_to_print.size(); ++i) { + const InstanceToPrint &instance{instances_to_print[i]}; + const std::vector &support_extrusions{extruder_extrusions.normal_extrusions[i].support_extrusions}; + const std::vector &slices_extrusions{extruder_extrusions.normal_extrusions[i].slices_extrusions}; + + if (!support_extrusions.empty() || !is_empty(slices_extrusions)) { + return &instance.print_object.instances()[instance.instance_id]; + } + } + } + return nullptr; +} + +} diff --git a/src/libslic3r/GCode/ExtrusionOrder.hpp b/src/libslic3r/GCode/ExtrusionOrder.hpp new file mode 100644 index 0000000..c9409f0 --- /dev/null +++ b/src/libslic3r/GCode/ExtrusionOrder.hpp @@ -0,0 +1,169 @@ +#ifndef slic3r_GCode_ExtrusionOrder_hpp_ +#define slic3r_GCode_ExtrusionOrder_hpp_ + +#include +#include +#include +#include +#include +#include + +#include "libslic3r/GCode/SmoothPath.hpp" +#include "libslic3r/GCode/WipeTowerIntegration.hpp" +#include "libslic3r/ExtrusionEntity.hpp" +#include "libslic3r/GCode/SeamPlacer.hpp" +#include "libslic3r/Print.hpp" +#include "libslic3r/ShortestPath.hpp" +#include "libslic3r/GCode/ToolOrdering.hpp" +#include "libslic3r/Layer.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/libslic3r.h" + +namespace Slic3r { +class ExtrusionEntity; +class ExtrusionEntityReference; +class Print; +class PrintObject; +class PrintRegion; + +namespace GCode { +class WipeTowerIntegration; +} // namespace GCode +struct PrintInstance; +} // namespace Slic3r + +namespace Slic3r::GCode { +// Object and support extrusions of the same PrintObject at the same print_z. +// public, so that it could be accessed by free helper functions from GCode.cpp +struct ObjectLayerToPrint +{ + ObjectLayerToPrint() : object_layer(nullptr), support_layer(nullptr) {} + const Layer *object_layer; + const SupportLayer *support_layer; + const Layer *layer() const { return (object_layer != nullptr) ? object_layer : support_layer; } + const PrintObject *object() const { + return (this->layer() != nullptr) ? this->layer()->object() : nullptr; + } + coordf_t print_z() const { + return (object_layer != nullptr && support_layer != nullptr) ? + 0.5 * (object_layer->print_z + support_layer->print_z) : + this->layer()->print_z; + } +}; + +using ObjectsLayerToPrint = std::vector; + +struct InstanceToPrint +{ + InstanceToPrint( + size_t object_layer_to_print_id, const PrintObject &print_object, size_t instance_id + ) + : object_layer_to_print_id(object_layer_to_print_id) + , print_object(print_object) + , instance_id(instance_id) {} + + // Index into std::vector, which contains Object and Support layers for the + // current print_z, collected for a single object, or for possibly multiple objects with + // multiple instances. + const size_t object_layer_to_print_id; + const PrintObject &print_object; + // Instance idx of the copy of a print object. + const size_t instance_id; +}; +} // namespace Slic3r::GCode + +namespace Slic3r::GCode::ExtrusionOrder { + +struct InfillRange { + std::vector items; + const PrintRegion *region; +}; + +struct Perimeter { + GCode::SmoothPath smooth_path; + bool reversed; + const ExtrusionEntity *extrusion_entity; +}; + +struct IslandExtrusions { + const PrintRegion *region; + std::vector perimeters; + std::vector infill_ranges; + bool infill_first{false}; +}; + +struct SliceExtrusions { + std::vector common_extrusions; + std::vector ironing_extrusions; +}; + +struct SupportPath { + SmoothPath path; + bool is_interface; +}; + +struct NormalExtrusions { + Point instance_offset; + std::vector support_extrusions; + std::vector slices_extrusions; +}; + +struct OverridenExtrusions { + Point instance_offset; + std::vector slices_extrusions; +}; + +/** + * Intentionally strong type representing a point in a coordinate system + * of an instance. Introduced to avoid confusion between local and + * global coordinate systems. + */ +struct InstancePoint { + Point local_point; +}; + +using PathSmoothingFunction = std::function &previous_position +)>; + +struct BrimPath { + SmoothPath path; + bool is_loop; +}; + +struct ExtruderExtrusions { + unsigned extruder_id; + std::vector> skirt; + std::vector brim; + std::vector overriden_extrusions; + std::vector normal_extrusions; + std::optional wipe_tower_start; +}; + +static constexpr const double min_gcode_segment_length = 0.002; + +bool is_empty(const std::vector &extrusions); + +std::vector get_extrusions( + const Print &print, + const GCode::WipeTowerIntegration *wipe_tower, + const GCode::ObjectsLayerToPrint &layers, + const bool is_first_layer, + const LayerTools &layer_tools, + const std::vector &instances_to_print, + const std::map> &skirt_loops_per_extruder, + unsigned current_extruder_id, + const PathSmoothingFunction &smooth_path, + bool get_brim, + std::optional previous_position +); + +std::optional get_first_point(const std::vector &extrusions); + +const PrintInstance * get_first_instance( + const std::vector &extrusions, + const std::vector &instances_to_print +); +} + +#endif // slic3r_GCode_ExtrusionOrder_hpp_ diff --git a/src/libslic3r/GCode/ExtrusionProcessor.cpp b/src/libslic3r/GCode/ExtrusionProcessor.cpp index 99ff9b8..95ba220 100644 --- a/src/libslic3r/GCode/ExtrusionProcessor.cpp +++ b/src/libslic3r/GCode/ExtrusionProcessor.cpp @@ -1,14 +1,31 @@ #include "ExtrusionProcessor.hpp" -#include -namespace Slic3r { namespace ExtrusionProcessor { +#include +#include +#include +#include +#include + +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/Config.hpp" +#include "libslic3r/Exception.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/Polygon.hpp" + +namespace Slic3r::ExtrusionProcessor { ExtrusionPaths calculate_and_split_overhanging_extrusions(const ExtrusionPath &path, const AABBTreeLines::LinesDistancer &unscaled_prev_layer, const AABBTreeLines::LinesDistancer &prev_layer_curled_lines) { - std::vector extended_points = estimate_points_properties(path.polyline.points, - unscaled_prev_layer, path.width()); + ExtrusionProcessor::PropertiesEstimationConfig config{}; + config.add_corners = true; + config.prev_layer_boundary_offset = true; + config.flow_width = path.width(); + std::vector extended_points = estimate_points_properties( + path.polyline.points, unscaled_prev_layer, config + ); + std::vector> calculated_distances(extended_points.size()); for (size_t i = 0; i < extended_points.size(); i++) { @@ -108,9 +125,28 @@ ExtrusionEntityCollection calculate_and_split_overhanging_extrusions(const Extru } else if (auto *loop = dynamic_cast(e)) { ExtrusionLoop new_loop = *loop; new_loop.paths.clear(); - for (const ExtrusionPath &p : loop->paths) { - auto paths = calculate_and_split_overhanging_extrusions(p, unscaled_prev_layer, prev_layer_curled_lines); - new_loop.paths.insert(new_loop.paths.end(), paths.begin(), paths.end()); + + ExtrusionPaths paths{loop->paths}; + if (!paths.empty()) { + ExtrusionPath& first_path{paths.front()}; + ExtrusionPath& last_path{paths.back()}; + + if (first_path.attributes() == last_path.attributes()) { + if (first_path.polyline.size() > 1 && last_path.polyline.size() > 2) { + const Line start{first_path.polyline.front(), *std::next(first_path.polyline.begin())}; + const Line end{last_path.polyline.back(), *std::next(last_path.polyline.rbegin())}; + + if (std::abs(start.direction() - end.direction()) < 1e-5) { + first_path.polyline.points.front() = *std::next(last_path.polyline.points.rbegin()); + last_path.polyline.points.pop_back(); + } + } + } + } + + for (const ExtrusionPath &p : paths) { + auto resulting_paths = calculate_and_split_overhanging_extrusions(p, unscaled_prev_layer, prev_layer_curled_lines); + new_loop.paths.insert(new_loop.paths.end(), resulting_paths.begin(), resulting_paths.end()); } result.append(new_loop); } else if (auto *mp = dynamic_cast(e)) { @@ -136,65 +172,89 @@ ExtrusionEntityCollection calculate_and_split_overhanging_extrusions(const Extru return result; }; - -std::pair calculate_overhang_speed(const ExtrusionAttributes &attributes, - const FullPrintConfig &config, - size_t extruder_id, - float external_perim_reference_speed, - float default_speed) +static std::map calc_print_speed_sections(const ExtrusionAttributes &attributes, + const FullPrintConfig &config, + const float external_perimeter_reference_speed, + const float default_speed) { - //w19 - bool is_overhang = attributes.overhang_attributes->start_distance_from_prev_layer >= 0.25 * attributes.width && - attributes.overhang_attributes->end_distance_from_prev_layer >= 0.25 * attributes.width;//&& - //attributes.overhang_attributes->proximity_to_curled_lines > 0.05 ; +// //w19 +// bool is_overhang = attributes.overhang_attributes->start_distance_from_prev_layer >= 0.25 * attributes.width && +// attributes.overhang_attributes->end_distance_from_prev_layer >= 0.25 * attributes.width;//&& +// //attributes.overhang_attributes->proximity_to_curled_lines > 0.05 ; + struct OverhangWithSpeed + { + int percent; + ConfigOptionFloatOrPercent print_speed; + }; - if (!is_overhang) { - return {default_speed, 0}; - } - assert(attributes.overhang_attributes.has_value()); - std::vector> overhangs_with_speeds = { - {100, ConfigOptionFloatOrPercent{default_speed, false}}}; + std::vector overhangs_with_speeds = {{100, ConfigOptionFloatOrPercent{default_speed, false}}}; if (config.enable_dynamic_overhang_speeds) { - overhangs_with_speeds = {{0, config.overhang_speed_0}, - {25, config.overhang_speed_1}, - {50, config.overhang_speed_2}, - {75, config.overhang_speed_3}, + overhangs_with_speeds = {{ 0, config.overhang_speed_0}, + { 25, config.overhang_speed_1}, + { 50, config.overhang_speed_2}, + { 75, config.overhang_speed_3}, {100, ConfigOptionFloatOrPercent{default_speed, false}}}; } - std::vector> overhang_with_fan_speeds = {{100, ConfigOptionInts{0}}}; - if (config.enable_dynamic_fan_speeds.get_at(extruder_id)) { - overhang_with_fan_speeds = {{0, config.overhang_fan_speed_0}, - {25, config.overhang_fan_speed_1}, - {50, config.overhang_fan_speed_2}, - {75, config.overhang_fan_speed_3}, - {100, ConfigOptionInts{0}}}; - } - - float speed_base = external_perim_reference_speed > 0 ? external_perim_reference_speed : default_speed; + const float speed_base = external_perimeter_reference_speed > 0 ? external_perimeter_reference_speed : default_speed; std::map speed_sections; - for (size_t i = 0; i < overhangs_with_speeds.size(); i++) { - float distance = attributes.width * (1.0 - (overhangs_with_speeds[i].first / 100.0)); - float speed = overhangs_with_speeds[i].second.percent ? (speed_base * overhangs_with_speeds[i].second.value / 100.0) : - overhangs_with_speeds[i].second.value; - if (speed < EPSILON) + for (OverhangWithSpeed &overhangs_with_speed : overhangs_with_speeds) { + const float distance = attributes.width * (1.f - (float(overhangs_with_speed.percent) / 100.f)); + float speed = float(overhangs_with_speed.print_speed.get_abs_value(speed_base)); + + if (speed < EPSILON) { speed = speed_base; + } + speed_sections[distance] = speed; } + return speed_sections; +} + +static std::map calc_fan_speed_sections(const ExtrusionAttributes &attributes, + const FullPrintConfig &config, + const size_t extruder_id) +{ + struct OverhangWithFanSpeed + { + int percent; + ConfigOptionInts fan_speed; + }; + + std::vector overhang_with_fan_speeds = {{100, ConfigOptionInts{0}}}; + if (config.enable_dynamic_fan_speeds.get_at(extruder_id)) { + overhang_with_fan_speeds = {{ 0, config.overhang_fan_speed_0}, + { 25, config.overhang_fan_speed_1}, + { 50, config.overhang_fan_speed_2}, + { 75, config.overhang_fan_speed_3}, + {100, ConfigOptionInts{0}}}; + } + std::map fan_speed_sections; - for (size_t i = 0; i < overhang_with_fan_speeds.size(); i++) { - float distance = attributes.width * (1.0 - (overhang_with_fan_speeds[i].first / 100.0)); - float fan_speed = overhang_with_fan_speeds[i].second.get_at(extruder_id); + for (OverhangWithFanSpeed &overhang_with_fan_speed : overhang_with_fan_speeds) { + float distance = attributes.width * (1.f - (float(overhang_with_fan_speed.percent) / 100.f)); + float fan_speed = float(overhang_with_fan_speed.fan_speed.get_at(extruder_id)); fan_speed_sections[distance] = fan_speed; } + return fan_speed_sections; +} + +OverhangSpeeds calculate_overhang_speed(const ExtrusionAttributes &attributes, + const FullPrintConfig &config, + const size_t extruder_id, + const float external_perimeter_reference_speed, + const float default_speed, + const std::optional ¤t_fan_speed) +{ + assert(attributes.overhang_attributes.has_value()); + auto interpolate_speed = [](const std::map &values, float distance) { auto upper_dist = values.lower_bound(distance); if (upper_dist == values.end()) { return values.rbegin()->second; - } - if (upper_dist == values.begin()) { + } else if (upper_dist == values.begin()) { return upper_dist->second; } @@ -203,24 +263,33 @@ std::pair calculate_overhang_speed(const ExtrusionAttributes &attri return (1.0f - t) * lower_dist->second + t * upper_dist->second; }; - float extrusion_speed = std::min(interpolate_speed(speed_sections, attributes.overhang_attributes->start_distance_from_prev_layer), - interpolate_speed(speed_sections, attributes.overhang_attributes->end_distance_from_prev_layer)); + const std::map speed_sections = calc_print_speed_sections(attributes, config, external_perimeter_reference_speed, default_speed); + const std::map fan_speed_sections = calc_fan_speed_sections(attributes, config, extruder_id); + + const float extrusion_speed = std::min(interpolate_speed(speed_sections, attributes.overhang_attributes->start_distance_from_prev_layer), + interpolate_speed(speed_sections, attributes.overhang_attributes->end_distance_from_prev_layer)); //w19 - float curled_base_speed = interpolate_speed(speed_sections, - attributes.width * attributes.overhang_attributes->proximity_to_curled_lines/tan(67.5)); + const float curled_base_speed = interpolate_speed(speed_sections, + attributes.width * attributes.overhang_attributes->proximity_to_curled_lines/tan(67.5)); + float final_speed = std::min(curled_base_speed, extrusion_speed); float fan_speed = std::min(interpolate_speed(fan_speed_sections, attributes.overhang_attributes->start_distance_from_prev_layer), - interpolate_speed(fan_speed_sections, attributes.overhang_attributes->end_distance_from_prev_layer)); + interpolate_speed(fan_speed_sections, attributes.overhang_attributes->end_distance_from_prev_layer)); + OverhangSpeeds overhang_speeds = {std::min(curled_base_speed, extrusion_speed), fan_speed}; if (!config.enable_dynamic_overhang_speeds) { - final_speed = -1; - } - if (!config.enable_dynamic_fan_speeds.get_at(extruder_id)) { - fan_speed = -1; + overhang_speeds.print_speed = -1; } - return {final_speed, fan_speed}; + if (!config.enable_dynamic_fan_speeds.get_at(extruder_id)) { + overhang_speeds.fan_speed = -1; + } else if (current_fan_speed.has_value() && (fan_speed < *current_fan_speed) && (*current_fan_speed - fan_speed) <= MIN_FAN_SPEED_NEGATIVE_CHANGE_TO_EMIT) { + // Always allow the fan speed to be increased without any hysteresis, but the speed will be decreased only when it exceeds a limit for minimum change. + overhang_speeds.fan_speed = *current_fan_speed; + } + + return overhang_speeds; } -}} // namespace Slic3r::ExtrusionProcessor \ No newline at end of file +} // namespace Slic3r::ExtrusionProcessor diff --git a/src/libslic3r/GCode/ExtrusionProcessor.hpp b/src/libslic3r/GCode/ExtrusionProcessor.hpp index 0a33316..ad604f1 100644 --- a/src/libslic3r/GCode/ExtrusionProcessor.hpp +++ b/src/libslic3r/GCode/ExtrusionProcessor.hpp @@ -1,22 +1,7 @@ #ifndef slic3r_ExtrusionProcessor_hpp_ #define slic3r_ExtrusionProcessor_hpp_ -#include "../AABBTreeLines.hpp" -#include "../SupportSpotsGenerator.hpp" -#include "../libslic3r.h" -#include "../ExtrusionEntity.hpp" -#include "../Layer.hpp" -#include "../Point.hpp" -#include "../SVG.hpp" -#include "../BoundingBox.hpp" -#include "../Polygon.hpp" -#include "../ClipperUtils.hpp" -#include "../Flow.hpp" -#include "../Config.hpp" -#include "../Line.hpp" -#include "../Exception.hpp" -#include "../PrintConfig.hpp" - +#include #include #include #include @@ -29,23 +14,64 @@ #include #include #include +#include +#include -namespace Slic3r { namespace ExtrusionProcessor { +#include "libslic3r/AABBTreeLines.hpp" +#include "libslic3r/SupportSpotsGenerator.hpp" +#include "libslic3r/libslic3r.h" +#include "libslic3r/ExtrusionEntity.hpp" +#include "libslic3r/Layer.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/SVG.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/Flow.hpp" +#include "libslic3r/Config.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/Exception.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/ExtrusionEntityCollection.hpp" + +namespace Slic3r { +class CurledLine; +class Linef; +} // namespace Slic3r + +namespace Slic3r::ExtrusionProcessor { + +// Minimum decrease of the fan speed in percents that will be emitted into g-code. +// Decreases below this limit will be omitted to not overflow the g-code with fan speed changes. +const constexpr float MIN_FAN_SPEED_NEGATIVE_CHANGE_TO_EMIT = 3.f; struct ExtendedPoint { - Vec2d position; - float distance; - float curvature; + Vec2d position; + float distance; + float curvature; }; -template -std::vector estimate_points_properties(const POINTS &input_points, - const AABBTreeLines::LinesDistancer &unscaled_prev_layer, - float flow_width, - float max_line_length = -1.0f) +struct OverhangSpeeds { - bool looped = input_points.front() == input_points.back(); + float print_speed; + float fan_speed; +}; + +struct PropertiesEstimationConfig { + bool add_corners{}; + bool prev_layer_boundary_offset{}; + float flow_width; + float max_line_length{-1.0f}; +}; + +template +std::vector estimate_points_properties( + const POINTS &input_points, + const AABBTreeLines::LinesDistancer &unscaled_prev_layer, + const PropertiesEstimationConfig &config +) { + bool looped = input_points.front() == input_points.back(); std::function get_prev_index = [](size_t idx, size_t count) { if (idx > 0) { return idx - 1; @@ -72,33 +98,30 @@ std::vector estimate_points_properties(const POINTS return idx; }; }; - using P = typename POINTS::value_type; using AABBScalar = typename AABBTreeLines::LinesDistancer::Scalar; if (input_points.empty()) return {}; - float boundary_offset = PREV_LAYER_BOUNDARY_OFFSET ? 0.5 * flow_width : 0.0f; - auto maybe_unscale = [](const P &p) { return SCALED_INPUT ? unscaled(p) : p.template cast(); }; + float boundary_offset = config.prev_layer_boundary_offset ? 0.5 * config.flow_width : 0.0f; std::vector points; - points.reserve(input_points.size() * (ADD_INTERSECTIONS ? 1.5 : 1)); + points.reserve(input_points.size() * 1.5); { - ExtendedPoint start_point{maybe_unscale(input_points.front())}; + ExtendedPoint start_point{unscaled(input_points.front())}; auto [distance, nearest_line, x] = unscaled_prev_layer.template distance_from_lines_extra(start_point.position.cast()); - start_point.distance = distance + boundary_offset; + start_point.distance = distance + boundary_offset; points.push_back(start_point); } for (size_t i = 1; i < input_points.size(); i++) { - ExtendedPoint next_point{maybe_unscale(input_points[i])}; + ExtendedPoint next_point{unscaled(input_points[i])}; auto [distance, nearest_line, x] = unscaled_prev_layer.template distance_from_lines_extra(next_point.position.cast()); - next_point.distance = distance + boundary_offset; + next_point.distance = distance + boundary_offset; - if (ADD_INTERSECTIONS && - ((points.back().distance > boundary_offset + EPSILON) != (next_point.distance > boundary_offset + EPSILON))) { - const ExtendedPoint &prev_point = points.back(); + if (((points.back().distance > boundary_offset + EPSILON) != (next_point.distance > boundary_offset + EPSILON))) { + const ExtendedPoint &prev_point = points.back(); auto intersections = unscaled_prev_layer.template intersections_with_line( L{prev_point.position.cast(), next_point.position.cast()}); for (const auto &intersection : intersections) { @@ -111,9 +134,9 @@ std::vector estimate_points_properties(const POINTS points.push_back(next_point); } - if (PREV_LAYER_BOUNDARY_OFFSET && ADD_INTERSECTIONS) { + if (config.add_corners) { std::vector new_points; - new_points.reserve(points.size()*2); + new_points.reserve(points.size() * 2); new_points.push_back(points.front()); for (int point_idx = 0; point_idx < int(points.size()) - 1; ++point_idx) { const ExtendedPoint &curr = points[point_idx]; @@ -129,21 +152,21 @@ std::vector estimate_points_properties(const POINTS double t1 = std::max(a0, a1); if (t0 < 1.0) { - auto p0 = curr.position + t0 * (next.position - curr.position); + auto p0 = curr.position + t0 * (next.position - curr.position); auto [p0_dist, p0_near_l, p0_x] = unscaled_prev_layer.template distance_from_lines_extra(p0.cast()); ExtendedPoint new_p{}; - new_p.position = p0; - new_p.distance = float(p0_dist + boundary_offset); + new_p.position = p0; + new_p.distance = float(p0_dist + boundary_offset); new_points.push_back(new_p); } if (t1 > 0.0) { - auto p1 = curr.position + t1 * (next.position - curr.position); + auto p1 = curr.position + t1 * (next.position - curr.position); auto [p1_dist, p1_near_l, p1_x] = unscaled_prev_layer.template distance_from_lines_extra(p1.cast()); ExtendedPoint new_p{}; - new_p.position = p1; - new_p.distance = float(p1_dist + boundary_offset); + new_p.position = p1; + new_p.distance = float(p1_dist + boundary_offset); new_points.push_back(new_p); } } @@ -153,24 +176,24 @@ std::vector estimate_points_properties(const POINTS points = std::move(new_points); } - if (max_line_length > 0) { + if (config.max_line_length > 0) { std::vector new_points; - new_points.reserve(points.size()*2); + new_points.reserve(points.size() * 2); { for (size_t i = 0; i + 1 < points.size(); i++) { const ExtendedPoint &curr = points[i]; const ExtendedPoint &next = points[i + 1]; new_points.push_back(curr); double len = (next.position - curr.position).squaredNorm(); - double t = sqrt((max_line_length * max_line_length) / len); + double t = sqrt((config.max_line_length * config.max_line_length) / len); size_t new_point_count = 1.0 / t; for (size_t j = 1; j < new_point_count + 1; j++) { Vec2d pos = curr.position * (1.0 - j * t) + next.position * (j * t); auto [p_dist, p_near_l, p_x] = unscaled_prev_layer.template distance_from_lines_extra(pos.cast()); ExtendedPoint new_p{}; - new_p.position = pos; - new_p.distance = float(p_dist + boundary_offset); + new_p.position = pos; + new_p.distance = float(p_dist + boundary_offset); new_points.push_back(new_p); } } @@ -181,7 +204,6 @@ std::vector estimate_points_properties(const POINTS float accumulated_distance = 0; std::vector distances_for_curvature(points.size()); - for (size_t point_idx = 0; point_idx < points.size(); ++point_idx) { const ExtendedPoint &a = points[point_idx]; const ExtendedPoint &b = points[get_prev_index(point_idx, points.size())]; @@ -196,7 +218,7 @@ std::vector estimate_points_properties(const POINTS ExtendedPoint ¤t = points[point_idx]; Vec2d back_position = current.position; -{ + { size_t back_point_index = point_idx; float dist_backwards = 0; while (dist_backwards < window_size * 0.5 && back_point_index != get_prev_index(back_point_index, points.size())) { @@ -211,14 +233,12 @@ std::vector estimate_points_properties(const POINTS } else { dist_backwards += line_dist; back_point_index = get_prev_index(back_point_index, points.size()); - } - - } - - } + } + } + } Vec2d front_position = current.position; - { + { size_t front_point_index = point_idx; float dist_forwards = 0; while (dist_forwards < window_size * 0.5 && front_point_index != get_next_index(front_point_index, points.size())) { @@ -234,18 +254,18 @@ std::vector estimate_points_properties(const POINTS dist_forwards += line_dist; front_point_index = get_next_index(front_point_index, points.size()); } - } } + } float new_curvature = angle(current.position - back_position, front_position - current.position) / window_size; if (abs(current.curvature) < abs(new_curvature)) { current.curvature = new_curvature; - } } } + } return points; - } +} ExtrusionPaths calculate_and_split_overhanging_extrusions(const ExtrusionPath &path, const AABBTreeLines::LinesDistancer &unscaled_prev_layer, @@ -256,12 +276,13 @@ ExtrusionEntityCollection calculate_and_split_overhanging_extrusions( const AABBTreeLines::LinesDistancer &unscaled_prev_layer, const AABBTreeLines::LinesDistancer &prev_layer_curled_lines); -std::pair calculate_overhang_speed(const ExtrusionAttributes &attributes, - const FullPrintConfig &config, - size_t extruder_id, - float external_perim_reference_speed, - float default_speed); +OverhangSpeeds calculate_overhang_speed(const ExtrusionAttributes &attributes, + const FullPrintConfig &config, + size_t extruder_id, + float external_perimeter_reference_speed, + float default_speed, + const std::optional ¤t_fan_speed); -}} // namespace Slic3r::ExtrusionProcessor +} // namespace Slic3r::ExtrusionProcessor #endif // slic3r_ExtrusionProcessor_hpp_ diff --git a/src/libslic3r/GCode/FindReplace.cpp b/src/libslic3r/GCode/FindReplace.cpp index 85b027b..6fc2e7f 100644 --- a/src/libslic3r/GCode/FindReplace.cpp +++ b/src/libslic3r/GCode/FindReplace.cpp @@ -1,8 +1,17 @@ #include "FindReplace.hpp" -#include "../Utils.hpp" -#include // isalpha #include +#include +#include +#include // isalpha +#include +#include +#include +#include +#include + +#include "../Utils.hpp" +#include "libslic3r/Exception.hpp" namespace Slic3r { diff --git a/src/libslic3r/GCode/FindReplace.hpp b/src/libslic3r/GCode/FindReplace.hpp index 9bc0a48..451a464 100644 --- a/src/libslic3r/GCode/FindReplace.hpp +++ b/src/libslic3r/GCode/FindReplace.hpp @@ -1,9 +1,12 @@ #ifndef slic3r_FindReplace_hpp_ #define slic3r_FindReplace_hpp_ -#include "../PrintConfig.hpp" - #include +#include +#include +#include + +#include "../PrintConfig.hpp" namespace Slic3r { diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 7948afc..38e606c 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -1,7 +1,7 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/Utils.hpp" #include "libslic3r/Print.hpp" -#include "libslic3r/LocalesUtils.hpp" +#include #include "libslic3r/format.hpp" #include "libslic3r/I18N.hpp" #include "libslic3r/GCode/GCodeWriter.hpp" @@ -44,7 +44,6 @@ static const Slic3r::Vec3f DEFAULT_EXTRUDER_OFFSET = Slic3r::Vec3f::Zero(); // taken from QIDITechnology.ini - [printer:Original QIDI i3 MK2.5 MMU2] static const std::vector DEFAULT_EXTRUDER_COLORS = { "#FF8000", "#DB5182", "#3EC0FF", "#FF4F4F", "#FBEB7D" }; - namespace Slic3r { const std::vector GCodeProcessor::Reserved_Tags = { @@ -54,9 +53,6 @@ const std::vector GCodeProcessor::Reserved_Tags = { "HEIGHT:", "WIDTH:", "LAYER_CHANGE", - "LAYER_CHANGE_TRAVEL", - "LAYER_CHANGE_RETRACTION_START", - "LAYER_CHANGE_RETRACTION_END", "COLOR_CHANGE", "PAUSE_PRINT", "CUSTOM_GCODE", @@ -80,9 +76,6 @@ bgcode::binarize::BinarizerConfig GCodeProcessor::s_binarizer_config{ bgcode::core::EMetadataEncodingType::INI, bgcode::core::EChecksumType::CRC32 }; -#if ENABLE_GCODE_VIEWER_DATA_CHECKING -const std::string GCodeProcessor::Mm3_Per_Mm_Tag = "MM3_PER_MM:"; -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING static void set_option_value(ConfigOptionFloats& option, size_t id, float value) { @@ -109,7 +102,7 @@ static float intersection_distance(float initial_rate, float final_rate, float a static float speed_from_distance(float initial_feedrate, float distance, float acceleration) { // to avoid invalid negative numbers due to numerical errors - float value = std::max(0.0f, sqr(initial_feedrate) + 2.0f * acceleration * distance); + const float value = std::max(0.0f, sqr(initial_feedrate) + 2.0f * acceleration * distance); return ::sqrt(value); } @@ -118,7 +111,7 @@ static float speed_from_distance(float initial_feedrate, float distance, float a static float max_allowable_speed(float acceleration, float target_velocity, float distance) { // to avoid invalid negative numbers due to numerical errors - float value = std::max(0.0f, sqr(target_velocity) - 2.0f * acceleration * distance); + const float value = std::max(0.0f, sqr(target_velocity) - 2.0f * acceleration * distance); return std::sqrt(value); } @@ -141,30 +134,18 @@ void GCodeProcessor::CpColor::reset() float GCodeProcessor::Trapezoid::acceleration_time(float entry_feedrate, float acceleration) const { - return acceleration_time_from_distance(entry_feedrate, accelerate_until, acceleration); -} - -float GCodeProcessor::Trapezoid::cruise_time() const -{ - return (cruise_feedrate != 0.0f) ? cruise_distance() / cruise_feedrate : 0.0f; + return acceleration_time_from_distance(entry_feedrate, acceleration_distance(), acceleration); } float GCodeProcessor::Trapezoid::deceleration_time(float distance, float acceleration) const { - return acceleration_time_from_distance(cruise_feedrate, (distance - decelerate_after), -acceleration); -} - -float GCodeProcessor::Trapezoid::cruise_distance() const -{ - return decelerate_after - accelerate_until; + return acceleration_time_from_distance(cruise_feedrate, deceleration_distance(distance), -acceleration); } void GCodeProcessor::TimeBlock::calculate_trapezoid() { - trapezoid.cruise_feedrate = feedrate_profile.cruise; - float accelerate_distance = std::max(0.0f, estimated_acceleration_distance(feedrate_profile.entry, feedrate_profile.cruise, acceleration)); - float decelerate_distance = std::max(0.0f, estimated_acceleration_distance(feedrate_profile.cruise, feedrate_profile.exit, -acceleration)); + const float decelerate_distance = std::max(0.0f, estimated_acceleration_distance(feedrate_profile.cruise, feedrate_profile.exit, -acceleration)); float cruise_distance = distance - accelerate_distance - decelerate_distance; // Not enough space to reach the nominal feedrate. @@ -175,18 +156,13 @@ void GCodeProcessor::TimeBlock::calculate_trapezoid() cruise_distance = 0.0f; trapezoid.cruise_feedrate = speed_from_distance(feedrate_profile.entry, accelerate_distance, acceleration); } + else + trapezoid.cruise_feedrate = feedrate_profile.cruise; trapezoid.accelerate_until = accelerate_distance; trapezoid.decelerate_after = accelerate_distance + cruise_distance; } -float GCodeProcessor::TimeBlock::time() const -{ - return trapezoid.acceleration_time(feedrate_profile.entry, acceleration) - + trapezoid.cruise_time() - + trapezoid.deceleration_time(distance, acceleration); -} - void GCodeProcessor::TimeMachine::State::reset() { feedrate = 0.0f; @@ -213,59 +189,65 @@ void GCodeProcessor::TimeMachine::reset() max_travel_acceleration = 0.0f; extrude_factor_override_percentage = 1.0f; time = 0.0f; - travel_time = 0.0f; stop_times = std::vector(); curr.reset(); prev.reset(); gcode_time.reset(); blocks = std::vector(); g1_times_cache = std::vector(); - std::fill(moves_time.begin(), moves_time.end(), 0.0f); - std::fill(roles_time.begin(), roles_time.end(), 0.0f); - layers_time = std::vector(); + first_layer_time = 0.0f; } -void GCodeProcessor::TimeMachine::simulate_st_synchronize(float additional_time) +static void planner_forward_pass_kernel(const GCodeProcessor::TimeBlock& prev, GCodeProcessor::TimeBlock& curr) { - if (!enabled) - return; - - calculate_time(0, additional_time); -} - -static void planner_forward_pass_kernel(GCodeProcessor::TimeBlock& prev, GCodeProcessor::TimeBlock& curr) -{ - // If the previous block is an acceleration block, but it is not long enough to complete the - // full speed change within the block, we need to adjust the entry speed accordingly. Entry - // speeds have already been reset, maximized, and reverse planned by reverse planner. - // If nominal length is true, max junction speed is guaranteed to be reached. No need to recheck. - if (!prev.flags.nominal_length) { - if (prev.feedrate_profile.entry < curr.feedrate_profile.entry) { - float entry_speed = std::min(curr.feedrate_profile.entry, max_allowable_speed(-prev.acceleration, prev.feedrate_profile.entry, prev.distance)); - - // Check for junction speed change - if (curr.feedrate_profile.entry != entry_speed) { - curr.feedrate_profile.entry = entry_speed; - curr.flags.recalculate = true; - } + // + // C:\qidi\firmware\QIDI-Firmware-Buddy\lib\Marlin\Marlin\src\module\planner.cpp + // Line 954 + // + // If the previous block is an acceleration block, too short to complete the full speed + // change, adjust the entry speed accordingly. Entry speeds have already been reset, + // maximized, and reverse-planned. If nominal length is set, max junction speed is + // guaranteed to be reached. No need to recheck. + if (!prev.flags.nominal_length && prev.feedrate_profile.entry < curr.feedrate_profile.entry) { + // Compute the maximum allowable speed + const float new_entry_speed = max_allowable_speed(-prev.acceleration, prev.feedrate_profile.entry, prev.distance); + // If true, current block is full-acceleration and we can move the planned pointer forward. + if (new_entry_speed < curr.feedrate_profile.entry) { + // Always <= max_entry_speed_sqr. Backward pass sets this. + curr.feedrate_profile.entry = new_entry_speed; + curr.flags.recalculate = true; } } } -void planner_reverse_pass_kernel(GCodeProcessor::TimeBlock& curr, GCodeProcessor::TimeBlock& next) +static void planner_reverse_pass_kernel(GCodeProcessor::TimeBlock& curr, const GCodeProcessor::TimeBlock& next) { - // If entry speed is already at the maximum entry speed, no need to recheck. Block is cruising. - // If not, block in state of acceleration or deceleration. Reset entry speed to maximum and - // check for maximum allowable speed reductions to ensure maximum possible planned speed. - if (curr.feedrate_profile.entry != curr.max_entry_speed) { - // If nominal length true, max junction speed is guaranteed to be reached. Only compute - // for max allowable speed if block is decelerating and nominal length is false. - if (!curr.flags.nominal_length && curr.max_entry_speed > next.feedrate_profile.entry) - curr.feedrate_profile.entry = std::min(curr.max_entry_speed, max_allowable_speed(-curr.acceleration, next.feedrate_profile.entry, curr.distance)); - else - curr.feedrate_profile.entry = curr.max_entry_speed; - - curr.flags.recalculate = true; + // + // C:\qidi\firmware\QIDI-Firmware-Buddy\lib\Marlin\Marlin\src\module\planner.cpp + // Line 857 + // + // If entry speed is already at the maximum entry speed, and there was no change of speed + // in the next block, there is no need to recheck. Block is cruising and there is no need to + // compute anything for this block, + // If not, block entry speed needs to be recalculated to ensure maximum possible planned speed. + const float max_entry_speed = curr.max_entry_speed; + // Compute maximum entry speed decelerating over the current block from its exit speed. + // If not at the maximum entry speed, or the previous block entry speed changed + if (curr.feedrate_profile.entry != max_entry_speed || next.flags.recalculate) { + // If nominal length true, max junction speed is guaranteed to be reached. + // If a block can de/ac-celerate from nominal speed to zero within the length of the block, then + // the current block and next block junction speeds are guaranteed to always be at their maximum + // junction speeds in deceleration and acceleration, respectively. This is due to how the current + // block nominal speed limits both the current and next maximum junction speeds. Hence, in both + // the reverse and forward planners, the corresponding block junction speed will always be at the + // the maximum junction speed and may always be ignored for any speed reduction checks. + const float new_entry_speed = curr.flags.nominal_length ? max_entry_speed : + std::min(max_entry_speed, max_allowable_speed(-curr.acceleration, next.feedrate_profile.entry, curr.distance)); + if (curr.feedrate_profile.entry != new_entry_speed) { + // Just Set the new entry speed. + curr.feedrate_profile.entry = new_entry_speed; + curr.flags.recalculate = true; + } } } @@ -275,7 +257,7 @@ static void recalculate_trapezoids(std::vector& block GCodeProcessor::TimeBlock* next = nullptr; for (size_t i = 0; i < blocks.size(); ++i) { - GCodeProcessor::TimeBlock& b = blocks[i]; + GCodeProcessor::TimeBlock& b = blocks[i]; curr = next; next = &b; @@ -284,10 +266,8 @@ static void recalculate_trapezoids(std::vector& block // Recalculate if current block entry or exit junction speed has changed. if (curr->flags.recalculate || next->flags.recalculate) { // NOTE: Entry and exit factors always > 0 by all previous logic operations. - GCodeProcessor::TimeBlock block = *curr; - block.feedrate_profile.exit = next->feedrate_profile.entry; - block.calculate_trapezoid(); - curr->trapezoid = block.trapezoid; + curr->feedrate_profile.exit = next->feedrate_profile.entry; + curr->calculate_trapezoid(); curr->flags.recalculate = false; // Reset current only to ensure next trapezoid is computed } } @@ -295,66 +275,153 @@ static void recalculate_trapezoids(std::vector& block // Last/newest block in buffer. Always recalculated. if (next != nullptr) { - GCodeProcessor::TimeBlock block = *next; - block.feedrate_profile.exit = next->safe_feedrate; - block.calculate_trapezoid(); - next->trapezoid = block.trapezoid; + next->feedrate_profile.exit = next->safe_feedrate; + next->calculate_trapezoid(); next->flags.recalculate = false; } } -void GCodeProcessor::TimeMachine::calculate_time(size_t keep_last_n_blocks, float additional_time) +void GCodeProcessor::TimeMachine::calculate_time(GCodeProcessorResult& result, PrintEstimatedStatistics::ETimeMode mode, size_t keep_last_n_blocks, float additional_time) { if (!enabled || blocks.size() < 2) return; assert(keep_last_n_blocks <= blocks.size()); + // reverse_pass + for (int i = static_cast(blocks.size()) - 1; i > 0; --i) { + planner_reverse_pass_kernel(blocks[i - 1], blocks[i]); + } + // forward_pass for (size_t i = 0; i + 1 < blocks.size(); ++i) { planner_forward_pass_kernel(blocks[i], blocks[i + 1]); } - // reverse_pass - for (int i = static_cast(blocks.size()) - 1; i > 0; --i) - planner_reverse_pass_kernel(blocks[i - 1], blocks[i]); - recalculate_trapezoids(blocks); - size_t n_blocks_process = blocks.size() - keep_last_n_blocks; + const size_t n_blocks_process = blocks.size() - keep_last_n_blocks; for (size_t i = 0; i < n_blocks_process; ++i) { const TimeBlock& block = blocks[i]; float block_time = block.time(); if (i == 0) block_time += additional_time; - time += block_time; - if (block.move_type == EMoveType::Travel) - travel_time += block_time; - else - roles_time[static_cast(block.role)] += block_time; + time += double(block_time); + result.moves[block.move_id].time[static_cast(mode)] = block_time; gcode_time.cache += block_time; - moves_time[static_cast(block.move_type)] += block_time; - if (block.layer_id >= layers_time.size()) { - const size_t curr_size = layers_time.size(); - layers_time.resize(block.layer_id); - for (size_t i = curr_size; i < layers_time.size(); ++i) { - layers_time[i] = 0.0f; + if (block.layer_id == 1) + first_layer_time += block_time; + + // detect actual speed moves required to render toolpaths using actual speed + if (mode == PrintEstimatedStatistics::ETimeMode::Normal) { + GCodeProcessorResult::MoveVertex& curr_move = result.moves[block.move_id]; + if (curr_move.type != EMoveType::Extrude && + curr_move.type != EMoveType::Travel && + curr_move.type != EMoveType::Wipe) + continue; + + assert(curr_move.actual_feedrate == 0.0f); + + GCodeProcessorResult::MoveVertex& prev_move = result.moves[block.move_id - 1]; + const bool interpolate = (prev_move.type == curr_move.type); + if (!interpolate && + prev_move.type != EMoveType::Extrude && + prev_move.type != EMoveType::Travel && + prev_move.type != EMoveType::Wipe) + prev_move.actual_feedrate = block.feedrate_profile.entry; + + if (EPSILON < block.trapezoid.accelerate_until && block.trapezoid.accelerate_until < block.distance - EPSILON) { + const float t = block.trapezoid.accelerate_until / block.distance; + const Vec3f position = lerp(prev_move.position, curr_move.position, t); + if ((position - prev_move.position).norm() > EPSILON && + (position - curr_move.position).norm() > EPSILON) { + const float delta_extruder = interpolate ? lerp(prev_move.delta_extruder, curr_move.delta_extruder, t) : curr_move.delta_extruder; + const float feedrate = interpolate ? lerp(prev_move.feedrate, curr_move.feedrate, t) : curr_move.feedrate; + const float width = interpolate ? lerp(prev_move.width, curr_move.width, t) : curr_move.width; + const float height = interpolate ? lerp(prev_move.height, curr_move.height, t) : curr_move.height; + const float mm3_per_mm = interpolate ? lerp(prev_move.mm3_per_mm, curr_move.mm3_per_mm, t) : curr_move.mm3_per_mm; + const float fan_speed = interpolate ? lerp(prev_move.fan_speed, curr_move.fan_speed, t) : curr_move.fan_speed; + const float temperature = interpolate ? lerp(prev_move.temperature, curr_move.temperature, t) : curr_move.temperature; + actual_speed_moves.push_back({ + block.move_id, + position, + block.trapezoid.cruise_feedrate, + delta_extruder, + feedrate, + width, + height, + mm3_per_mm, + fan_speed, + temperature + }); + } } + + const bool has_deceleration = block.trapezoid.deceleration_distance(block.distance) > EPSILON; + if (has_deceleration && block.trapezoid.decelerate_after > block.trapezoid.accelerate_until + EPSILON) { + const float t = block.trapezoid.decelerate_after / block.distance; + const Vec3f position = lerp(prev_move.position, curr_move.position, t); + if ((position - prev_move.position).norm() > EPSILON && + (position - curr_move.position).norm() > EPSILON) { + const float delta_extruder = interpolate ? lerp(prev_move.delta_extruder, curr_move.delta_extruder, t) : curr_move.delta_extruder; + const float feedrate = interpolate ? lerp(prev_move.feedrate, curr_move.feedrate, t) : curr_move.feedrate; + const float width = interpolate ? lerp(prev_move.width, curr_move.width, t) : curr_move.width; + const float height = interpolate ? lerp(prev_move.height, curr_move.height, t) : curr_move.height; + const float mm3_per_mm = interpolate ? lerp(prev_move.mm3_per_mm, curr_move.mm3_per_mm, t) : curr_move.mm3_per_mm; + const float fan_speed = interpolate ? lerp(prev_move.fan_speed, curr_move.fan_speed, t) : curr_move.fan_speed; + const float temperature = interpolate ? lerp(prev_move.temperature, curr_move.temperature, t) : curr_move.temperature; + actual_speed_moves.push_back({ + block.move_id, + position, + block.trapezoid.cruise_feedrate, + delta_extruder, + feedrate, + width, + height, + mm3_per_mm, + fan_speed, + temperature + }); + } + } + + const bool is_cruise_only = block.trapezoid.is_cruise_only(block.distance); + actual_speed_moves.push_back({ + block.move_id, + std::nullopt, + (is_cruise_only || !has_deceleration) ? block.trapezoid.cruise_feedrate : block.feedrate_profile.exit, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt, + std::nullopt + }); } - layers_time[block.layer_id - 1] += block_time; - g1_times_cache.push_back({ block.g1_line_id, block.remaining_internal_g1_lines, time }); + g1_times_cache.push_back({ block.g1_line_id, block.remaining_internal_g1_lines, float(time) }); // update times for remaining time to printer stop placeholders auto it_stop_time = std::lower_bound(stop_times.begin(), stop_times.end(), block.g1_line_id, [](const StopTime& t, unsigned int value) { return t.g1_line_id < value; }); if (it_stop_time != stop_times.end() && it_stop_time->g1_line_id == block.g1_line_id) - it_stop_time->elapsed_time = time; + it_stop_time->elapsed_time = float(time); } - if (keep_last_n_blocks) + if (keep_last_n_blocks) { blocks.erase(blocks.begin(), blocks.begin() + n_blocks_process); - else + + // Ensure that the new first block's entry speed will be preserved to prevent discontinuity + // between the erased blocks' exit speed and the new first block's entry speed. + // Otherwise, the first block's entry speed could be recalculated on the next pass without + // considering that there are no more blocks before this first block. This could lead + // to discontinuity between the exit speed (of already processed blocks) and the entry + // speed of the first block. + TimeBlock &first_block = blocks.front(); + first_block.max_entry_speed = first_block.feedrate_profile.entry; + } else { blocks.clear(); + } } void GCodeProcessor::TimeProcessor::reset() @@ -451,27 +518,7 @@ void GCodeProcessor::UsedFilaments::process_caches(const GCodeProcessor* process process_role_cache(processor); } -#if ENABLE_GCODE_VIEWER_STATISTICS void GCodeProcessorResult::reset() { - moves = std::vector(); - bed_shape = Pointfs(); - max_print_height = 0.0f; - z_offset = 0.0f; - settings_ids.reset(); - extruders_count = 0; - backtrace_enabled = false; - extruder_colors = std::vector(); - filament_diameters = std::vector(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DIAMETER); - filament_densities = std::vector(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DENSITY); - filament_cost = std::vector(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_COST); - custom_gcode_per_print_z = std::vector(); - spiral_vase_layers = std::vector>>(); - conflict_result = std::nullopt; - time = 0; -} -#else -void GCodeProcessorResult::reset() { - is_binary_file = false; moves.clear(); lines_ends.clear(); @@ -486,10 +533,9 @@ void GCodeProcessorResult::reset() { filament_densities = std::vector(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_DENSITY); filament_cost = std::vector(MIN_EXTRUDERS_COUNT, DEFAULT_FILAMENT_COST); custom_gcode_per_print_z = std::vector(); - spiral_vase_layers = std::vector>>(); + spiral_vase_mode = false; conflict_result = std::nullopt; } -#endif // ENABLE_GCODE_VIEWER_STATISTICS const std::vector> GCodeProcessor::Producers = { { EProducer::QIDISlicer, "generated by QIDISlicer" }, @@ -571,6 +617,7 @@ GCodeProcessor::GCodeProcessor() void GCodeProcessor::apply_config(const PrintConfig& config) { m_parser.apply_config(config); + m_binarizer.set_enabled(config.binary_gcode); m_result.is_binary_file = config.binary_gcode; @@ -641,7 +688,7 @@ void GCodeProcessor::apply_config(const PrintConfig& config) m_extra_loading_move = float(config.extra_loading_move); } -for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, i); m_time_processor.machines[i].max_acceleration = max_acceleration; m_time_processor.machines[i].acceleration = (max_acceleration > 0.0f) ? max_acceleration : DEFAULT_ACCELERATION; @@ -669,7 +716,7 @@ for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode:: const ConfigOptionBool* spiral_vase = config.option("spiral_vase"); if (spiral_vase != nullptr) - m_spiral_vase_active = spiral_vase->value; + m_result.spiral_vase_mode = spiral_vase->value; const ConfigOptionFloat* z_offset = config.option("z_offset"); if (z_offset != nullptr) @@ -951,7 +998,7 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) const ConfigOptionBool* spiral_vase = config.option("spiral_vase"); if (spiral_vase != nullptr) - m_spiral_vase_active = spiral_vase->value; + m_result.spiral_vase_mode = spiral_vase->value; const ConfigOptionFloat* z_offset = config.option("z_offset"); if (z_offset != nullptr) @@ -1022,16 +1069,9 @@ void GCodeProcessor::reset() m_options_z_corrector.reset(); - m_spiral_vase_active = false; m_kissslicer_toolchange_time_correction = 0.0f; m_single_extruder_multi_material = false; - -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - m_mm3_per_mm_compare.reset(); - m_height_compare.reset(); - m_width_compare.reset(); -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING } static inline const char* skip_whitespaces(const char *begin, const char *end) { @@ -1046,7 +1086,8 @@ static inline const char* remove_eols(const char *begin, const char *end) { // Load a G-code into a stand-alone G-code viewer. // throws CanceledException through print->throw_if_canceled() (sent by the caller as callback). -void GCodeProcessor::process_file(const std::string& filename, std::function cancel_callback) +void GCodeProcessor::process_file(const std::string& filename, GCodeReader::ProgressCallback progress_callback, + std::function cancel_callback) { FILE* file = boost::nowide::fopen(filename.c_str(), "rb"); if (file == nullptr) @@ -1058,19 +1099,16 @@ void GCodeProcessor::process_file(const std::string& filename, std::function cancel_callback) +void GCodeProcessor::process_ascii_file(const std::string& filename, GCodeReader::ProgressCallback progress_callback, + std::function cancel_callback) { CNumericLocalesSetter locales_setter; -#if ENABLE_GCODE_VIEWER_STATISTICS - m_start_time = std::chrono::high_resolution_clock::now(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - // pre-processing // parse the gcode file to detect its producer { @@ -1118,6 +1156,7 @@ void GCodeProcessor::process_ascii_file(const std::string& filename, std::functi m_result.id = ++s_result_id; initialize_result_moves(); size_t parse_line_callback_cntr = 10000; + m_parser.set_progress_callback(progress_callback); m_parser.parse_file(filename, [this, cancel_callback, &parse_line_callback_cntr](GCodeReader& reader, const GCodeReader::GCodeLine& line) { if (-- parse_line_callback_cntr == 0) { // Don't call the cancel_callback() too often, do it every at every 10000'th line. @@ -1142,12 +1181,9 @@ static void update_lines_ends_and_out_file_pos(const std::string& out_string, st *out_file_pos += out_string.size(); } -void GCodeProcessor::process_binary_file(const std::string& filename, std::function cancel_callback) +void GCodeProcessor::process_binary_file(const std::string& filename, GCodeReader::ProgressCallback progress_callback, + std::function cancel_callback) { -#if ENABLE_GCODE_VIEWER_STATISTICS - m_start_time = std::chrono::high_resolution_clock::now(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - FilePtr file{ boost::nowide::fopen(filename.c_str(), "rb") }; if (file.f == nullptr) throw Slic3r::RuntimeError(format("Error opening file %1%", filename)); @@ -1156,29 +1192,44 @@ void GCodeProcessor::process_binary_file(const std::string& filename, std::funct const long file_size = ftell(file.f); rewind(file.f); + auto update_progress = [progress_callback, file_size, &file]() { + const long pos = ftell(file.f); + if (progress_callback != nullptr) + progress_callback(float(pos) / float(file_size)); + }; + + auto throw_error = [progress_callback](const std::string& msg) { + if (progress_callback != nullptr) + progress_callback(1.0f); + throw Slic3r::RuntimeError(msg.c_str()); + }; + // read file header using namespace bgcode::core; using namespace bgcode::binarize; FileHeader file_header; EResult res = read_header(*file.f, file_header, nullptr); + update_progress(); if (res != EResult::Success) - throw Slic3r::RuntimeError(format("File %1% does not contain a valid binary gcode\nError: %2%", filename, + throw_error(format("File %1% does not contain a valid binary gcode\nError: %2%", filename, std::string(translate_result(res)))); // read file metadata block, if present BlockHeader block_header; std::vector cs_buffer(65536); res = read_next_block_header(*file.f, file_header, block_header, cs_buffer.data(), cs_buffer.size()); + update_progress(); if (res != EResult::Success) - throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res)))); - if ((EBlockType)block_header.type != EBlockType::FileMetadata && + throw_error(format("Error reading file %1%: %2%", filename, std::string(translate_result(res)))); + if ((EBlockType)block_header.type != EBlockType::FileMetadata && (EBlockType)block_header.type != EBlockType::PrinterMetadata) - throw Slic3r::RuntimeError(format("Unable to find file metadata block in file %1%", filename)); + throw_error(format("Unable to find file metadata block in file %1%", filename)); if ((EBlockType)block_header.type == EBlockType::FileMetadata) { FileMetadataBlock file_metadata_block; res = file_metadata_block.read_data(*file.f, file_header, block_header); + update_progress(); if (res != EResult::Success) - throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res)))); + throw_error(format("Error reading file %1%: %2%", filename, std::string(translate_result(res)))); auto producer_it = std::find_if(file_metadata_block.raw_data.begin(), file_metadata_block.raw_data.end(), [](const std::pair& item) { return item.first == "Producer"; }); if (producer_it != file_metadata_block.raw_data.end() && boost::starts_with(producer_it->second, std::string(SLIC3R_APP_NAME))) @@ -1186,8 +1237,9 @@ void GCodeProcessor::process_binary_file(const std::string& filename, std::funct else m_producer = EProducer::Unknown; res = read_next_block_header(*file.f, file_header, block_header, cs_buffer.data(), cs_buffer.size()); + update_progress(); if (res != EResult::Success) - throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res)))); + throw_error(format("Error reading file %1%: %2%", filename, std::string(translate_result(res)))); } else { m_producer = EProducer::Unknown; @@ -1195,45 +1247,52 @@ void GCodeProcessor::process_binary_file(const std::string& filename, std::funct // read printer metadata block if ((EBlockType)block_header.type != EBlockType::PrinterMetadata) - throw Slic3r::RuntimeError(format("Unable to find printer metadata block in file %1%", filename)); + throw_error(format("Unable to find printer metadata block in file %1%", filename)); PrinterMetadataBlock printer_metadata_block; res = printer_metadata_block.read_data(*file.f, file_header, block_header); + update_progress(); if (res != EResult::Success) - throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res)))); + throw_error(format("Error reading file %1%: %2%", filename, std::string(translate_result(res)))); // read thumbnail blocks res = read_next_block_header(*file.f, file_header, block_header, cs_buffer.data(), cs_buffer.size()); + update_progress(); if (res != EResult::Success) - throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res)))); + throw_error(format("Error reading file %1%: %2%", filename, std::string(translate_result(res)))); while ((EBlockType)block_header.type == EBlockType::Thumbnail) { ThumbnailBlock thumbnail_block; res = thumbnail_block.read_data(*file.f, file_header, block_header); + update_progress(); if (res != EResult::Success) - throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res)))); + throw_error(format("Error reading file %1%: %2%", filename, std::string(translate_result(res)))); res = read_next_block_header(*file.f, file_header, block_header, cs_buffer.data(), cs_buffer.size()); + update_progress(); if (res != EResult::Success) - throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res)))); + throw_error(format("Error reading file %1%: %2%", filename, std::string(translate_result(res)))); } // read print metadata block if ((EBlockType)block_header.type != EBlockType::PrintMetadata) - throw Slic3r::RuntimeError(format("Unable to find print metadata block in file %1%", filename)); + throw_error(format("Unable to find print metadata block in file %1%", filename)); PrintMetadataBlock print_metadata_block; res = print_metadata_block.read_data(*file.f, file_header, block_header); + update_progress(); if (res != EResult::Success) - throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res)))); + throw_error(format("Error reading file %1%: %2%", filename, std::string(translate_result(res)))); // read slicer metadata block res = read_next_block_header(*file.f, file_header, block_header, cs_buffer.data(), cs_buffer.size()); + update_progress(); if (res != EResult::Success) - throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res)))); + throw_error(format("Error reading file %1%: %2%", filename, std::string(translate_result(res)))); if ((EBlockType)block_header.type != EBlockType::SlicerMetadata) - throw Slic3r::RuntimeError(format("Unable to find slicer metadata block in file %1%", filename)); + throw_error(format("Unable to find slicer metadata block in file %1%", filename)); SlicerMetadataBlock slicer_metadata_block; res = slicer_metadata_block.read_data(*file.f, file_header, block_header); + update_progress(); if (res != EResult::Success) - throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res)))); + throw_error(format("Error reading file %1%: %2%", filename, std::string(translate_result(res)))); DynamicPrintConfig config; config.apply(FullPrintConfig::defaults()); std::string str; @@ -1253,15 +1312,17 @@ void GCodeProcessor::process_binary_file(const std::string& filename, std::funct // read gcodes block res = read_next_block_header(*file.f, file_header, block_header, cs_buffer.data(), cs_buffer.size()); + update_progress(); if (res != EResult::Success) - throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res)))); + throw_error(format("Error reading file %1%: %2%", filename, std::string(translate_result(res)))); if ((EBlockType)block_header.type != EBlockType::GCode) - throw Slic3r::RuntimeError(format("Unable to find gcode block in file %1%", filename)); + throw_error(format("Unable to find gcode block in file %1%", filename)); while ((EBlockType)block_header.type == EBlockType::GCode) { GCodeBlock block; res = block.read_data(*file.f, file_header, block_header); + update_progress(); if (res != EResult::Success) - throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res)))); + throw_error(format("Error reading file %1%: %2%", filename, std::string(translate_result(res)))); std::vector& lines_ends = m_result.lines_ends.emplace_back(std::vector()); update_lines_ends_and_out_file_pos(block.raw_data, lines_ends, nullptr); @@ -1274,21 +1335,19 @@ void GCodeProcessor::process_binary_file(const std::string& filename, std::funct break; res = read_next_block_header(*file.f, file_header, block_header, cs_buffer.data(), cs_buffer.size()); + update_progress(); if (res != EResult::Success) - throw Slic3r::RuntimeError(format("Error reading file %1%: %2%", filename, std::string(translate_result(res)))); + throw_error(format("Error reading file %1%: %2%", filename, std::string(translate_result(res)))); } // Don't post-process the G-code to update time stamps. this->finalize(false); } + void GCodeProcessor::initialize(const std::string& filename) { assert(is_decimal_separator_point()); -#if ENABLE_GCODE_VIEWER_STATISTICS - m_start_time = std::chrono::high_resolution_clock::now(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - // process gcode m_result.filename = filename; m_result.id = ++s_result_id; @@ -1305,6 +1364,7 @@ void GCodeProcessor::process_buffer(const std::string &buffer) void GCodeProcessor::finalize(bool perform_post_process) { m_result.z_offset = m_z_offset; + // update width/height of wipe moves for (GCodeProcessorResult::MoveVertex& move : m_result.moves) { if (move.type == EMoveType::Wipe) { @@ -1313,11 +1373,12 @@ void GCodeProcessor::finalize(bool perform_post_process) } } + calculate_time(m_result); + // process the time blocks for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { TimeMachine& machine = m_time_processor.machines[i]; TimeMachine::CustomGCodeTime& gcode_time = machine.gcode_time; - machine.calculate_time(); if (gcode_time.needed && gcode_time.cache != 0.0f) gcode_time.times.push_back({ CustomGCode::ColorChange, gcode_time.cache }); } @@ -1326,38 +1387,18 @@ void GCodeProcessor::finalize(bool perform_post_process) update_estimated_statistics(); -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - std::cout << "\n"; - m_mm3_per_mm_compare.output(); - m_height_compare.output(); - m_width_compare.output(); -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING - if (perform_post_process) post_process(); -#if ENABLE_GCODE_VIEWER_STATISTICS - m_result.time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - m_start_time).count(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS } float GCodeProcessor::get_time(PrintEstimatedStatistics::ETimeMode mode) const { - return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? m_time_processor.machines[static_cast(mode)].time : 0.0f; + return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? float(m_time_processor.machines[static_cast(mode)].time) : 0.0f; } std::string GCodeProcessor::get_time_dhm(PrintEstimatedStatistics::ETimeMode mode) const { - return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? short_time(get_time_dhms(m_time_processor.machines[static_cast(mode)].time)) : std::string("N/A"); -} - -float GCodeProcessor::get_travel_time(PrintEstimatedStatistics::ETimeMode mode) const -{ - return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? m_time_processor.machines[static_cast(mode)].travel_time : 0.0f; -} - -std::string GCodeProcessor::get_travel_time_dhm(PrintEstimatedStatistics::ETimeMode mode) const -{ - return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? short_time(get_time_dhms(m_time_processor.machines[static_cast(mode)].travel_time)) : std::string("N/A"); + return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? short_time(get_time_dhms(float(m_time_processor.machines[static_cast(mode)].time))) : std::string("N/A"); } std::vector>> GCodeProcessor::get_custom_gcode_times(PrintEstimatedStatistics::ETimeMode mode, bool include_remaining) const @@ -1375,32 +1416,6 @@ std::vector>> GCodeProcesso return ret; } -std::vector> GCodeProcessor::get_moves_time(PrintEstimatedStatistics::ETimeMode mode) const -{ - std::vector> ret; - if (mode < PrintEstimatedStatistics::ETimeMode::Count) { - for (size_t i = 0; i < m_time_processor.machines[static_cast(mode)].moves_time.size(); ++i) { - float time = m_time_processor.machines[static_cast(mode)].moves_time[i]; - if (time > 0.0f) - ret.push_back({ static_cast(i), time }); - } - } - return ret; -} - -std::vector> GCodeProcessor::get_roles_time(PrintEstimatedStatistics::ETimeMode mode) const -{ - std::vector> ret; - if (mode < PrintEstimatedStatistics::ETimeMode::Count) { - for (size_t i = 0; i < m_time_processor.machines[static_cast(mode)].roles_time.size(); ++i) { - float time = m_time_processor.machines[static_cast(mode)].roles_time[i]; - if (time > 0.0f) - ret.push_back({ static_cast(i), time }); - } - } - return ret; -} - ConfigSubstitutions load_from_superslicer_gcode_file(const std::string& filename, DynamicPrintConfig& config, ForwardCompatibilitySubstitutionRule compatibility_rule) { // for reference, see: ConfigBase::load_from_gcode_file() @@ -1510,11 +1525,9 @@ void GCodeProcessor::apply_config_kissslicer(const std::string& filename) m_parser.reset(); } -std::vector GCodeProcessor::get_layers_time(PrintEstimatedStatistics::ETimeMode mode) const +float GCodeProcessor::get_first_layer_time(PrintEstimatedStatistics::ETimeMode mode) const { - return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? - m_time_processor.machines[static_cast(mode)].layers_time : - std::vector(); + return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? m_time_processor.machines[static_cast(mode)].first_layer_time : 0.0f; } void GCodeProcessor::apply_config_simplify3d(const std::string& filename) @@ -1989,29 +2002,8 @@ void GCodeProcessor::process_tags(const std::string_view comment, bool producers // layer change tag if (comment == reserved_tag(ETags::Layer_Change)) { ++m_layer_id; - if (m_spiral_vase_active) { - if (m_result.moves.empty() || m_result.spiral_vase_layers.empty()) - // add a placeholder for layer height. the actual value will be set inside process_G1() method - m_result.spiral_vase_layers.push_back({ FLT_MAX, { 0, 0 } }); - else { - const size_t move_id = m_result.moves.size() - 1; - if (!m_result.spiral_vase_layers.empty()) - m_result.spiral_vase_layers.back().second.second = move_id; - // add a placeholder for layer height. the actual value will be set inside process_G1() method - m_result.spiral_vase_layers.push_back({ FLT_MAX, { move_id, move_id } }); - } - } return; } - -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - // mm3_per_mm print tag - if (boost::starts_with(comment, Mm3_Per_Mm_Tag)) { - if (! parse_number(comment.substr(Mm3_Per_Mm_Tag.size()), m_mm3_per_mm_compare.last_tag_value)) - BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Mm3_Per_Mm (" << comment << ")."; - return; - } -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING } bool GCodeProcessor::process_producers_tags(const std::string_view comment) @@ -2625,7 +2617,7 @@ void GCodeProcessor::process_G1(const std::array, 4>& axes if (volume_extruded_filament != 0.) m_used_filaments.increase_caches(volume_extruded_filament, m_extruder_id, area_filament_cross_section * m_parking_position, - area_filament_cross_section * m_extra_loading_move); + area_filament_cross_section * m_extra_loading_move); const EMoveType type = move_type(delta_pos); if (type == EMoveType::Extrude) { @@ -2634,9 +2626,6 @@ void GCodeProcessor::process_G1(const std::array, 4>& axes // volume extruded filament / tool displacement = area toolpath cross section m_mm3_per_mm = area_toolpath_cross_section; -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - m_mm3_per_mm_compare.update(area_toolpath_cross_section, m_extrusion_role); -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING if (m_forced_height > 0.0f) // use height coming from the gcode tags @@ -2647,7 +2636,7 @@ void GCodeProcessor::process_G1(const std::array, 4>& axes m_height = std::min(m_end_position[Z], 2.0f); else // use the first layer height - m_height = m_first_layer_height + m_z_offset; + m_height = m_first_layer_height + m_z_offset; } else if (origin == G1DiscretizationOrigin::G1) { if (m_end_position[Z] > m_extruded_last_z + EPSILON && delta_pos[Z] == 0.0) @@ -2661,10 +2650,6 @@ void GCodeProcessor::process_G1(const std::array, 4>& axes m_extruded_last_z = m_end_position[Z]; m_options_z_corrector.update(m_height); -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - m_height_compare.update(m_height, m_extrusion_role); -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING - if (m_forced_width > 0.0f) // use width coming from the gcode tags m_width = m_forced_width; @@ -2683,10 +2668,6 @@ void GCodeProcessor::process_G1(const std::array, 4>& axes // clamp width to avoid artifacts which may arise from wrong values of m_height m_width = std::min(m_width, std::max(2.0f, 4.0f * m_height)); - -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - m_width_compare.update(m_width, m_extrusion_role); -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING } // time estimate section @@ -2720,6 +2701,7 @@ void GCodeProcessor::process_G1(const std::array, 4>& axes block.role = m_extrusion_role; block.distance = distance; block.g1_line_id = m_g1_line_id; + block.move_id = static_cast(m_result.moves.size()); block.remaining_internal_g1_lines = remaining_internal_g1_lines.has_value() ? *remaining_internal_g1_lines : 0; block.layer_id = std::max(1, m_layer_id); @@ -2754,8 +2736,9 @@ void GCodeProcessor::process_G1(const std::array, 4>& axes for (unsigned char a = X; a <= E; ++a) { const float axis_max_acceleration = get_axis_max_acceleration(static_cast(i), static_cast(a)); - if (acceleration * std::abs(delta_pos[a]) * inv_distance > axis_max_acceleration) - acceleration = axis_max_acceleration; + const float scale = std::abs(delta_pos[a]) * inv_distance; + if (acceleration * scale > axis_max_acceleration) + acceleration = axis_max_acceleration / scale; } block.acceleration = acceleration; @@ -2846,11 +2829,11 @@ void GCodeProcessor::process_G1(const std::array, 4>& axes prev = curr; blocks.push_back(block); - - if (blocks.size() > TimeProcessor::Planner::refresh_threshold) - machine.calculate_time(TimeProcessor::Planner::queue_size); } + if (m_time_processor.machines[0].blocks.size() > TimeProcessor::Planner::refresh_threshold) + calculate_time(m_result, TimeProcessor::Planner::queue_size); + if (m_seams_detector.is_active()) { // check for seam starting vertex if (type == EMoveType::Extrude && m_extrusion_role == GCodeExtrusionRole::ExternalPerimeter && !m_seams_detector.has_first_vertex()) @@ -2880,14 +2863,6 @@ void GCodeProcessor::process_G1(const std::array, 4>& axes m_seams_detector.set_first_vertex(m_result.moves.back().position - m_extruder_offsets[m_extruder_id]); } - if (m_spiral_vase_active && !m_result.spiral_vase_layers.empty()) { - if (m_result.spiral_vase_layers.back().first == FLT_MAX && delta_pos[Z] >= 0.0) - // replace layer height placeholder with correct value - m_result.spiral_vase_layers.back().first = static_cast(m_end_position[Z]); - if (!m_result.moves.empty()) - m_result.spiral_vase_layers.back().second.second = m_result.moves.size() - 1; - } - // store move store_move_vertex(type, origin == G1DiscretizationOrigin::G2G3); } @@ -2918,6 +2893,7 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool cloc for (unsigned char a = X; a <= E; ++a) { end_position[a] = extract_absolute_position_on_axis((Axis)a, line, double(area_filament_cross_section)); } + // relative center Vec3f rel_center = Vec3f::Zero(); #ifndef NDEBUG @@ -2941,7 +2917,7 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool cloc if (! axis_pos_I.empty() && ! line.has_value(axis_pos_I, rel_center.x())) return; if (! axis_pos_J.empty() && ! line.has_value(axis_pos_J, rel_center.y())) - return; + return; } // scale center, if needed @@ -2978,16 +2954,16 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool cloc // arc center arc.center = arc.start + rel_center.cast(); - // arc end endpoint arc.end = Vec3d(end_position[X], end_position[Y], end_position[Z]); // radii - if (std::abs(arc.end_radius() - arc.start_radius()) > EPSILON) { + if (std::abs(arc.end_radius() - arc.start_radius()) > 0.001) { // what to do ??? } assert(fitting != EFitting::R || std::abs(radius - arc.start_radius()) < EPSILON); + // updates feedrate from line std::optional feedrate; if (line.has_f()) @@ -3040,85 +3016,141 @@ void GCodeProcessor::process_G2_G3(const GCodeReader::GCodeLine& line, bool cloc auto internal_only_g1_line = [this](const AxisCoords& target, bool has_z, const std::optional& feedrate, const std::optional& extrusion, const std::optional& remaining_internal_g1_lines = std::nullopt) { - std::array, 4> g1_axes = { target[X], target[Y], std::nullopt, std::nullopt }; - std::optional g1_feedrate = std::nullopt; - if (has_z) - g1_axes[Z] = target[Z]; - if (extrusion.has_value()) - g1_axes[E] = target[E]; + std::array, 4> g1_axes = { target[X], target[Y], std::nullopt, std::nullopt }; + std::optional g1_feedrate = std::nullopt; + if (has_z) + g1_axes[Z] = target[Z]; + if (extrusion.has_value()) + g1_axes[E] = target[E]; if (feedrate.has_value()) g1_feedrate = (double)*feedrate; process_G1(g1_axes, g1_feedrate, G1DiscretizationOrigin::G2G3, remaining_internal_g1_lines); }; - // calculate arc segments - // reference: - // QIDI-Firmware\Firmware\motion_control.cpp - mc_arc() - // https://github.com/qidi3d/QIDI-Firmware/blob/MK3/Firmware/motion_control.cpp +if (m_flavor == gcfMarlinFirmware) { + static const float MAX_ARC_DEVIATION = 0.02f; + static const float MIN_ARC_SEGMENTS_PER_SEC = 50; + static const float MIN_ARC_SEGMENT_MM = 0.1f; + static const float MAX_ARC_SEGMENT_MM = 2.0f; + const float feedrate_mm_s = feedrate.has_value() ? *feedrate : m_feedrate; + const float radius_mm = rel_center.norm(); + const float segment_mm = std::clamp(std::min(std::sqrt(8.0f * radius_mm * MAX_ARC_DEVIATION), feedrate_mm_s * (1.0f / MIN_ARC_SEGMENTS_PER_SEC)), MIN_ARC_SEGMENT_MM, MAX_ARC_SEGMENT_MM); + const float flat_mm = radius_mm * std::abs(arc.angle); + const size_t segments = std::max(flat_mm / segment_mm + 0.8f, 1); - // segments count + AxisCoords prev_target = m_start_position; + + if (segments > 1) { + const float inv_segments = 1.0f / static_cast(segments); + const float theta_per_segment = static_cast(arc.angle) * inv_segments; + const float cos_T = cos(theta_per_segment); + const float sin_T = sin(theta_per_segment); + const float z_per_segment = arc.delta_z() * inv_segments; + const float extruder_per_segment = (extrusion.has_value()) ? *extrusion * inv_segments : 0.0f; + + static const size_t N_ARC_CORRECTION = 25; + size_t arc_recalc_count = N_ARC_CORRECTION; + + Vec2f rvec(-rel_center.x(), -rel_center.y()); + AxisCoords arc_target = { 0.0f, 0.0f, m_start_position[Z], m_start_position[E] }; + for (size_t i = 1; i < segments; ++i) { + if (--arc_recalc_count) { + // Apply vector rotation matrix to previous rvec.a / 1 + const float r_new_Y = rvec.x() * sin_T + rvec.y() * cos_T; + rvec.x() = rvec.x() * cos_T - rvec.y() * sin_T; + rvec.y() = r_new_Y; + } + else { + arc_recalc_count = N_ARC_CORRECTION; + // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments. + // Compute exact location by applying transformation matrix from initial radius vector(=-offset). + // To reduce stuttering, the sin and cos could be computed at different times. + // For now, compute both at the same time. + const float Ti = i * theta_per_segment; + const float cos_Ti = cos(Ti); + const float sin_Ti = sin(Ti); + rvec.x() = -rel_center.x() * cos_Ti + rel_center.y() * sin_Ti; + rvec.y() = -rel_center.x() * sin_Ti - rel_center.y() * cos_Ti; + } + + // Update arc_target location + arc_target[X] = arc.center.x() + rvec.x(); + arc_target[Y] = arc.center.y() + rvec.y(); + arc_target[Z] += z_per_segment; + arc_target[E] += extruder_per_segment; + + m_start_position = m_end_position; // this is required because we are skipping the call to process_gcode_line() + internal_only_g1_line(adjust_target(arc_target, prev_target), z_per_segment != 0.0, (i == 1) ? feedrate : std::nullopt, + extrusion, segments - i); + prev_target = arc_target; + } + } + + // Ensure last segment arrives at target location. + m_start_position = m_end_position; // this is required because we are skipping the call to process_gcode_line() + internal_only_g1_line(adjust_target(end_position, prev_target), arc.delta_z() != 0.0, (segments == 1) ? feedrate : std::nullopt, extrusion); + } + else { + // segments count #if 0 - static const double MM_PER_ARC_SEGMENT = 1.0; - const size_t segments = std::max(std::floor(travel_length / MM_PER_ARC_SEGMENT), 1); + static const double MM_PER_ARC_SEGMENT = 1.0; + const size_t segments = std::max(std::floor(travel_length / MM_PER_ARC_SEGMENT), 1); #else - static const double gcode_arc_tolerance = 0.0125; - const size_t segments = Geometry::ArcWelder::arc_discretization_steps(arc.start_radius(), std::abs(arc.angle), gcode_arc_tolerance); + static const double gcode_arc_tolerance = 0.0125; + const size_t segments = Geometry::ArcWelder::arc_discretization_steps(arc.start_radius(), std::abs(arc.angle), gcode_arc_tolerance); #endif - const double inv_segment = 1.0 / double(segments); - const double theta_per_segment = arc.angle * inv_segment; - const double z_per_segment = arc.delta_z() * inv_segment; - const double extruder_per_segment = (extrusion.has_value()) ? *extrusion * inv_segment : 0.0; + const double inv_segment = 1.0 / double(segments); + const double theta_per_segment = arc.angle * inv_segment; + const double z_per_segment = arc.delta_z() * inv_segment; + const double extruder_per_segment = (extrusion.has_value()) ? *extrusion * inv_segment : 0.0; + const double sq_theta_per_segment = sqr(theta_per_segment); + const double cos_T = 1.0 - 0.5 * sq_theta_per_segment; + const double sin_T = theta_per_segment - sq_theta_per_segment * theta_per_segment / 6.0f; - const double cos_T = 1.0 - 0.5 * sqr(theta_per_segment); // Small angle approximation - const double sin_T = theta_per_segment; + AxisCoords prev_target = m_start_position; + AxisCoords arc_target; - AxisCoords prev_target = m_start_position; - AxisCoords arc_target; + // Initialize the linear axis + arc_target[Z] = m_start_position[Z]; - // Initialize the linear axis - arc_target[Z] = m_start_position[Z]; + // Initialize the extruder axis + arc_target[E] = m_start_position[E]; - // Initialize the extruder axis - arc_target[E] = m_start_position[E]; + static const size_t N_ARC_CORRECTION = 25; + Vec3d curr_rel_arc_start = arc.relative_start(); + size_t count = N_ARC_CORRECTION; - static const size_t N_ARC_CORRECTION = 25; - Vec3d curr_rel_arc_start = arc.relative_start(); - size_t count = 0; + for (size_t i = 1; i < segments; ++i) { + if (count-- == 0) { + const double cos_Ti = ::cos(i * theta_per_segment); + const double sin_Ti = ::sin(i * theta_per_segment); + curr_rel_arc_start.x() = -double(rel_center.x()) * cos_Ti + double(rel_center.y()) * sin_Ti; + curr_rel_arc_start.y() = -double(rel_center.x()) * sin_Ti - double(rel_center.y()) * cos_Ti; + count = N_ARC_CORRECTION; + } + else { + const float r_axisi = curr_rel_arc_start.x() * sin_T + curr_rel_arc_start.y() * cos_T; + curr_rel_arc_start.x() = curr_rel_arc_start.x() * cos_T - curr_rel_arc_start.y() * sin_T; + curr_rel_arc_start.y() = r_axisi; + } - for (size_t i = 1; i < segments; ++i) { - if (count < N_ARC_CORRECTION) { - // Apply vector rotation matrix - const float r_axisi = curr_rel_arc_start.x() * sin_T + curr_rel_arc_start.y() * cos_T; - curr_rel_arc_start.x() = curr_rel_arc_start.x() * cos_T - curr_rel_arc_start.y() * sin_T; - curr_rel_arc_start.y() = r_axisi; - ++count; - } - else { - // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments. - // Compute exact location by applying transformation matrix from initial radius vector(=-offset). - const double cos_Ti = ::cos(i * theta_per_segment); - const double sin_Ti = ::sin(i * theta_per_segment); - curr_rel_arc_start.x() = -double(rel_center.x()) * cos_Ti + double(rel_center.y()) * sin_Ti; - curr_rel_arc_start.y() = -double(rel_center.x()) * sin_Ti - double(rel_center.y()) * cos_Ti; - count = 0; + // Update arc_target location + arc_target[X] = arc.center.x() + curr_rel_arc_start.x(); + arc_target[Y] = arc.center.y() + curr_rel_arc_start.y(); + arc_target[Z] += z_per_segment; + arc_target[E] += extruder_per_segment; + + m_start_position = m_end_position; // this is required because we are skipping the call to process_gcode_line() + internal_only_g1_line(adjust_target(arc_target, prev_target), z_per_segment != 0.0, (i == 1) ? feedrate : std::nullopt, + extrusion, segments - i); + prev_target = arc_target; } - // Update arc_target location - arc_target[X] = arc.center.x() + curr_rel_arc_start.x(); - arc_target[Y] = arc.center.y() + curr_rel_arc_start.y(); - arc_target[Z] += z_per_segment; - arc_target[E] += extruder_per_segment; - + // Ensure last segment arrives at target location. m_start_position = m_end_position; // this is required because we are skipping the call to process_gcode_line() - internal_only_g1_line(adjust_target(arc_target, prev_target), z_per_segment != 0.0, (i == 1) ? feedrate : std::nullopt, - extrusion, segments - i); - prev_target = arc_target; + internal_only_g1_line(adjust_target(end_position, prev_target), arc.delta_z() != 0.0, (segments == 1) ? feedrate : std::nullopt, extrusion); } - - // Ensure last segment arrives at target location. - m_start_position = m_end_position; // this is required because we are skipping the call to process_gcode_line() - internal_only_g1_line(adjust_target(end_position, prev_target), arc.delta_z() != 0.0, (segments == 1) ? feedrate : std::nullopt, extrusion); } void GCodeProcessor::process_G10(const GCodeReader::GCodeLine& line) @@ -3673,6 +3705,7 @@ void GCodeProcessor::post_process() FilePtr out{ boost::nowide::fopen(out_path.c_str(), "wb") }; if (out.f == nullptr) throw Slic3r::RuntimeError(std::string("GCode processor post process export failed.\nCannot open file for writing.\n")); + std::vector filament_mm(m_result.extruders_count, 0.0); std::vector filament_cm3(m_result.extruders_count, 0.0); std::vector filament_g(m_result.extruders_count, 0.0); @@ -3691,6 +3724,7 @@ void GCodeProcessor::post_process() } double total_g_wipe_tower = m_print->print_statistics().total_wipe_tower_filament_weight; + if (m_binarizer.is_enabled()) { // update print metadata auto stringify = [](const std::vector& values) { @@ -3726,8 +3760,7 @@ void GCodeProcessor::post_process() char buf[128]; sprintf(buf, "(%s mode)", (mode == PrintEstimatedStatistics::ETimeMode::Normal) ? "normal" : "silent"); binary_data.print_metadata.raw_data.emplace_back("estimated printing time " + std::string(buf), get_time_dhms(machine.time)); - binary_data.print_metadata.raw_data.emplace_back("estimated first layer printing time " + std::string(buf), get_time_dhms(machine.layers_time.empty() ? 0.f : machine.layers_time.front())); - + binary_data.print_metadata.raw_data.emplace_back("estimated first layer printing time " + std::string(buf), get_time_dhms(machine.first_layer_time)); binary_data.printer_metadata.raw_data.emplace_back("estimated printing time " + std::string(buf), get_time_dhms(machine.time)); } } @@ -3860,6 +3893,7 @@ void GCodeProcessor::post_process() size_t m_out_file_pos{ 0 }; bgcode::binarize::Binarizer& m_binarizer; + public: ExportLines(bgcode::binarize::Binarizer& binarizer, EWriteType type, const std::array(PrintEstimatedStatistics::ETimeMode::Count)>& machines) @@ -3890,13 +3924,13 @@ void GCodeProcessor::post_process() ++m_times_cache_id; } - if (it->id > g1_lines_counter) + if (it == m_machines[Normal].g1_times_cache.end() || it->id > g1_lines_counter) return ret; // search for internal G1 lines if (GCodeReader::GCodeLine::cmd_is(line, "G2") || GCodeReader::GCodeLine::cmd_is(line, "G3")) { while (it != m_machines[Normal].g1_times_cache.end() && it->remaining_internal_g1_lines > 0) { - ++it; + ++it; ++m_times_cache_id; ++g1_lines_counter; ++ret; @@ -3908,6 +3942,7 @@ void GCodeProcessor::post_process() if (!m_machines[Stealth].g1_times_cache.empty()) m_times[Stealth] = (m_machines[Stealth].g1_times_cache.begin() + std::distance(m_machines[Normal].g1_times_cache.begin(), it))->elapsed_time; } + return ret; } @@ -4014,7 +4049,7 @@ void GCodeProcessor::post_process() throw Slic3r::RuntimeError("Error while sending gcode to the binarizer."); } else { - write_to_file(out, out_string, result, out_path); + write_to_file(out, out_string, result, out_path); update_lines_ends_and_out_file_pos(out_string, result.lines_ends.front(), &m_out_file_pos); } } @@ -4037,7 +4072,7 @@ void GCodeProcessor::post_process() throw Slic3r::RuntimeError("Error while sending gcode to the binarizer."); } else { - write_to_file(out, out_string, result, out_path); + write_to_file(out, out_string, result, out_path); update_lines_ends_and_out_file_pos(out_string, result.lines_ends.front(), &m_out_file_pos); } } @@ -4059,12 +4094,12 @@ void GCodeProcessor::post_process() void write_to_file(FilePtr& out, const std::string& out_string, GCodeProcessorResult& result, const std::string& out_path) { if (!out_string.empty()) { if (!m_binarizer.is_enabled()) { - fwrite((const void*)out_string.c_str(), 1, out_string.length(), out.f); - if (ferror(out.f)) { - out.close(); - boost::nowide::remove(out_path.c_str()); + fwrite((const void*)out_string.c_str(), 1, out_string.length(), out.f); + if (ferror(out.f)) { + out.close(); + boost::nowide::remove(out_path.c_str()); throw Slic3r::RuntimeError("GCode processor post process export failed.\nIs the disk full?"); - } + } } } } @@ -4123,7 +4158,7 @@ void GCodeProcessor::post_process() char buf[128]; sprintf(buf, "; estimated first layer printing time (%s mode) = %s\n", (mode == PrintEstimatedStatistics::ETimeMode::Normal) ? "normal" : "silent", - get_time_dhms(machine.layers_time.empty() ? 0.f : machine.layers_time.front()).c_str()); + get_time_dhms(machine.first_layer_time).c_str()); export_lines.append_line(buf); processed = true; } @@ -4134,7 +4169,6 @@ void GCodeProcessor::post_process() return processed; }; - auto process_used_filament = [&](std::string& gcode_line) { // Prefilter for parsing speed. if (gcode_line.size() < 8 || gcode_line[0] != ';' || gcode_line[1] != ' ') @@ -4335,7 +4369,6 @@ void GCodeProcessor::post_process() gcode_line.insert(gcode_line.end(), it, it_end); if (eol) { ++line_id; - gcode_line += "\n"; const unsigned int internal_g1_lines_counter = export_lines.update(gcode_line, line_id, g1_lines_counter); // replace placeholder lines @@ -4391,6 +4424,7 @@ void GCodeProcessor::post_process() if (m_binarizer.finalize() != bgcode::core::EResult::Success) throw Slic3r::RuntimeError("Error while finalizing the gcode binarizer."); } + out.close(); in.close(); @@ -4406,7 +4440,7 @@ void GCodeProcessor::post_process() m_result.filename = result_filename; } else - export_lines.synchronize_moves(m_result); + export_lines.synchronize_moves(m_result); if (rename_file(out_path, result_filename)) throw Slic3r::RuntimeError(std::string("Failed to rename the output G-code file from ") + out_path + " to " + result_filename + '\n' + @@ -4428,12 +4462,14 @@ void GCodeProcessor::store_move_vertex(EMoveType type, bool internal_only) Vec3f(m_end_position[X], m_end_position[Y], m_end_position[Z] - m_z_offset) + m_extruder_offsets[m_extruder_id], static_cast(m_end_position[E] - m_start_position[E]), m_feedrate, + 0.0f, // actual feedrate m_width, m_height, m_mm3_per_mm, m_fan_speed, m_extruder_temps[m_extruder_id], - static_cast(m_result.moves.size()), + { 0.0f, 0.0f }, // time + std::max(1, m_layer_id) - 1, internal_only }); @@ -4577,6 +4613,9 @@ float GCodeProcessor::get_filament_unload_time(size_t extruder_id) void GCodeProcessor::process_custom_gcode_time(CustomGCode::Type code) { + //FIXME this simulates st_synchronize! is it correct? + // The estimated time may be longer than the real print time. + simulate_st_synchronize(); for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { TimeMachine& machine = m_time_processor.machines[i]; if (!machine.enabled) @@ -4584,9 +4623,6 @@ void GCodeProcessor::process_custom_gcode_time(CustomGCode::Type code) TimeMachine::CustomGCodeTime& gcode_time = machine.gcode_time; gcode_time.needed = true; - //FIXME this simulates st_synchronize! is it correct? - // The estimated time may be longer than the real print time. - machine.simulate_st_synchronize(); if (gcode_time.cache != 0.0f) { gcode_time.times.push_back({ code, gcode_time.cache }); gcode_time.cache = 0.0f; @@ -4603,11 +4639,90 @@ void GCodeProcessor::process_filaments(CustomGCode::Type code) m_used_filaments.process_extruder_cache(m_extruder_id); } +void GCodeProcessor::calculate_time(GCodeProcessorResult& result, size_t keep_last_n_blocks, float additional_time) +{ + // calculate times + std::vector actual_speed_moves; + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + TimeMachine& machine = m_time_processor.machines[i]; + machine.calculate_time(m_result, static_cast(i), keep_last_n_blocks, additional_time); + if (static_cast(i) == PrintEstimatedStatistics::ETimeMode::Normal) + actual_speed_moves = std::move(machine.actual_speed_moves); + } + + // insert actual speed moves into the move list. We will do this in two stages (to avoid inserting in the middle of + // result.moves repeatedly). First, we create individual vectors of MoveVertices, and store them along with their + // required index in the result.moves vector after they are all inserted. Then we go through the destination + // vector once and move all the elements where we want them in one go. + std::vector>> moves_to_insert = {std::make_pair(0, std::vector{})}; + size_t inserted_count = 0; + std::map id_map; + for (auto it = actual_speed_moves.begin(); it != actual_speed_moves.end(); ++it) { + const unsigned int base_id_old = it->move_id; + if (it->position.has_value()) { + // insert actual speed move into the move list + // clone from existing move + GCodeProcessorResult::MoveVertex new_move = result.moves[base_id_old]; + // override modified parameters + new_move.time = { 0.0f, 0.0f }; + new_move.position = *it->position; + new_move.actual_feedrate = it->actual_feedrate; + new_move.delta_extruder = *it->delta_extruder; + new_move.feedrate = *it->feedrate; + new_move.width = *it->width; + new_move.height = *it->height; + new_move.mm3_per_mm = *it->mm3_per_mm; + new_move.fan_speed = *it->fan_speed; + new_move.temperature = *it->temperature; + new_move.internal_only = true; + moves_to_insert.back().second.emplace_back(new_move); + } + else { + moves_to_insert.back().first = base_id_old + inserted_count; // Save required position of this range in the NEW vector. + id_map[base_id_old] = base_id_old + inserted_count; // Remember where the old element will end up. + inserted_count += moves_to_insert.back().second.size(); // Increase the number of moves that are already planned to be added. + + result.moves[base_id_old].actual_feedrate = it->actual_feedrate; // update move actual speed + + // synchronize seams actual speed + if (base_id_old + 1 < result.moves.size()) { + GCodeProcessorResult::MoveVertex& move = result.moves[base_id_old + 1]; + if (move.type == EMoveType::Seam) + move.actual_feedrate = it->actual_feedrate; + } + moves_to_insert.emplace_back(std::make_pair(0, std::vector{})); + } + } + + // Now actually do the insertion of the ranges into the destination vector. + std::vector& m = result.moves; + size_t offset = inserted_count; + m.resize(m.size() + offset); // grow the vector to its final size + size_t last_pos = m.size() - 1; // index of the last element that still needs to be moved + for (auto it = moves_to_insert.rbegin(); it != moves_to_insert.rend(); ++it) { + const auto& [new_pos, new_moves] = *it; + if (new_moves.empty()) + continue; + for (int i = last_pos; i >= new_pos + new_moves.size(); --i) // Move the elements to their final place. + m[i] = m[i - offset]; + std::copy(new_moves.begin(), new_moves.end(), m.begin() + new_pos); + last_pos = new_pos - 1; + offset -= new_moves.size(); + } + assert(offset == 0); + + // synchronize blocks' move_ids with after moves for actual speed insertion + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + for (GCodeProcessor::TimeBlock& block : m_time_processor.machines[i].blocks) { + auto it = id_map.find(block.move_id); + block.move_id = (it != id_map.end()) ? it->second : block.move_id + inserted_count; + } + } +} + void GCodeProcessor::simulate_st_synchronize(float additional_time) { - for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { - m_time_processor.machines[i].simulate_st_synchronize(additional_time); - } + calculate_time(m_result, 0, additional_time); } void GCodeProcessor::update_estimated_statistics() @@ -4615,11 +4730,7 @@ void GCodeProcessor::update_estimated_statistics() auto update_mode = [this](PrintEstimatedStatistics::ETimeMode mode) { PrintEstimatedStatistics::Mode& data = m_result.print_statistics.modes[static_cast(mode)]; data.time = get_time(mode); - data.travel_time = get_travel_time(mode); data.custom_gcode_times = get_custom_gcode_times(mode, true); - data.moves_times = get_moves_time(mode); - data.roles_times = get_roles_time(mode); - data.layers_times = get_layers_time(mode); }; update_mode(PrintEstimatedStatistics::ETimeMode::Normal); diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 3664437..0c6ebef 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -8,6 +8,7 @@ #include "libslic3r/CustomGCode.hpp" #include + #include #include #include @@ -17,7 +18,7 @@ namespace Slic3r { - class Print; + class Print; enum class EMoveType : unsigned char { @@ -47,37 +48,26 @@ namespace Slic3r { struct Mode { float time; - float travel_time; std::vector>> custom_gcode_times; - std::vector> moves_times; - std::vector> roles_times; - std::vector layers_times; void reset() { time = 0.0f; - travel_time = 0.0f; custom_gcode_times.clear(); custom_gcode_times.shrink_to_fit(); - moves_times.clear(); - moves_times.shrink_to_fit(); - roles_times.clear(); - roles_times.shrink_to_fit(); - layers_times.clear(); - layers_times.shrink_to_fit(); } }; - std::vector volumes_per_color_change; - std::map volumes_per_extruder; + std::vector volumes_per_color_change; + std::map volumes_per_extruder; std::map> used_filaments_per_role; - std::map cost_per_extruder; + std::map cost_per_extruder; std::array(ETimeMode::Count)> modes; PrintEstimatedStatistics() { reset(); } void reset() { - for (auto m : modes) { + for (Mode &m : modes) { m.reset(); } volumes_per_color_change.clear(); @@ -129,15 +119,18 @@ namespace Slic3r { Vec3f position{ Vec3f::Zero() }; // mm float delta_extruder{ 0.0f }; // mm float feedrate{ 0.0f }; // mm/s + float actual_feedrate{ 0.0f }; // mm/s float width{ 0.0f }; // mm float height{ 0.0f }; // mm float mm3_per_mm{ 0.0f }; float fan_speed{ 0.0f }; // percentage float temperature{ 0.0f }; // Celsius degrees - float time{ 0.0f }; // s + std::array(PrintEstimatedStatistics::ETimeMode::Count)> time{ 0.0f, 0.0f }; // s + unsigned int layer_id{ 0 }; bool internal_only{ false }; float volumetric_rate() const { return feedrate * mm3_per_mm; } + float actual_volumetric_rate() const { return actual_feedrate * mm3_per_mm; } }; std::string filename; @@ -161,13 +154,10 @@ namespace Slic3r { PrintEstimatedStatistics print_statistics; std::vector custom_gcode_per_print_z; - std::vector>> spiral_vase_layers; + bool spiral_vase_mode; ConflictResultOpt conflict_result; -#if ENABLE_GCODE_VIEWER_STATISTICS - int64_t time{ 0 }; -#endif // ENABLE_GCODE_VIEWER_STATISTICS void reset(); }; @@ -185,9 +175,6 @@ namespace Slic3r { Height, Width, Layer_Change, - Layer_Change_Travel, - Layer_Change_Retraction_Start, - Layer_Change_Retraction_End, Color_Change, Pause_Print, Custom_Code, @@ -206,10 +193,6 @@ namespace Slic3r { static const float Wipe_Width; static const float Wipe_Height; -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - static const std::string Mm3_Per_Mm_Tag; -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING - private: using AxisCoords = std::array; using ExtruderColors = std::vector; @@ -258,9 +241,12 @@ namespace Slic3r { float cruise_feedrate{ 0.0f }; // mm/sec float acceleration_time(float entry_feedrate, float acceleration) const; - float cruise_time() const; + float cruise_time() const { return (cruise_feedrate != 0.0f) ? cruise_distance() / cruise_feedrate : 0.0f; } float deceleration_time(float distance, float acceleration) const; - float cruise_distance() const; + float acceleration_distance() const { return accelerate_until; } + float cruise_distance() const { return decelerate_after - accelerate_until; } + float deceleration_distance(float distance) const { return distance - decelerate_after; } + bool is_cruise_only(float distance) const { return std::abs(cruise_distance() - distance) < EPSILON; } }; struct TimeBlock @@ -273,6 +259,7 @@ namespace Slic3r { EMoveType move_type{ EMoveType::Noop }; GCodeExtrusionRole role{ GCodeExtrusionRole::None }; + unsigned int move_id{ 0 }; unsigned int g1_line_id{ 0 }; unsigned int remaining_internal_g1_lines; unsigned int layer_id{ 0 }; @@ -287,7 +274,10 @@ namespace Slic3r { // Calculates this block's trapezoid void calculate_trapezoid(); - float time() const; + float time() const { + return trapezoid.acceleration_time(feedrate_profile.entry, acceleration) + + trapezoid.cruise_time() + trapezoid.deceleration_time(distance, acceleration); + } }; private: @@ -319,6 +309,20 @@ namespace Slic3r { float elapsed_time; }; + struct ActualSpeedMove + { + unsigned int move_id{ 0 }; + std::optional position; + float actual_feedrate{ 0.0f }; + std::optional delta_extruder; + std::optional feedrate; + std::optional width; + std::optional height; + std::optional mm3_per_mm; + std::optional fan_speed; + std::optional temperature; + }; + bool enabled; float acceleration; // mm/s^2 // hard limit for the acceleration, to which the firmware will clamp. @@ -330,8 +334,9 @@ namespace Slic3r { // hard limit for the travel acceleration, to which the firmware will clamp. float max_travel_acceleration; // mm/s^2 float extrude_factor_override_percentage; - float time; // s - float travel_time; // s + // We accumulate total print time in doubles to reduce the loss of precision + // while adding big floating numbers with small float numbers. + double time; // s struct StopTime { unsigned int g1_line_id; @@ -345,15 +350,12 @@ namespace Slic3r { CustomGCodeTime gcode_time; std::vector blocks; std::vector g1_times_cache; - std::array(EMoveType::Count)> moves_time; - std::array(GCodeExtrusionRole::Count)> roles_time; - std::vector layers_time; + float first_layer_time; + std::vector actual_speed_moves; void reset(); - // Simulates firmware st_synchronize() call - void simulate_st_synchronize(float additional_time = 0.0f); - void calculate_time(size_t keep_last_n_blocks = 0, float additional_time = 0.0f); + void calculate_time(GCodeProcessorResult& result, PrintEstimatedStatistics::ETimeMode mode, size_t keep_last_n_blocks = 0, float additional_time = 0.0f); }; struct TimeProcessor @@ -470,75 +472,8 @@ namespace Slic3r { } }; -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - struct DataChecker - { - struct Error - { - float value; - float tag_value; - GCodeExtrusionRole role; - }; - - std::string type; - float threshold{ 0.01f }; - float last_tag_value{ 0.0f }; - unsigned int count{ 0 }; - std::vector errors; - - DataChecker(const std::string& type, float threshold) - : type(type), threshold(threshold) - {} - - void update(float value, GCodeExtrusionRole role) { - if (role != GCodeExtrusionRole::Custom) { - ++count; - if (last_tag_value != 0.0f) { - if (std::abs(value - last_tag_value) / last_tag_value > threshold) - errors.push_back({ value, last_tag_value, role }); - } - } - } - - void reset() { last_tag_value = 0.0f; errors.clear(); count = 0; } - - std::pair get_min() const { - float delta_min = FLT_MAX; - float perc_min = 0.0f; - for (const Error& e : errors) { - if (delta_min > e.value - e.tag_value) { - delta_min = e.value - e.tag_value; - perc_min = 100.0f * delta_min / e.tag_value; - } - } - return { delta_min, perc_min }; - } - - std::pair get_max() const { - float delta_max = -FLT_MAX; - float perc_max = 0.0f; - for (const Error& e : errors) { - if (delta_max < e.value - e.tag_value) { - delta_max = e.value - e.tag_value; - perc_max = 100.0f * delta_max / e.tag_value; - } - } - return { delta_max, perc_max }; - } - - void output() const { - if (!errors.empty()) { - std::cout << type << ":\n"; - std::cout << "Errors: " << errors.size() << " (" << 100.0f * float(errors.size()) / float(count) << "%)\n"; - auto [min, perc_min] = get_min(); - auto [max, perc_max] = get_max(); - std::cout << "min: " << min << "(" << perc_min << "%) - max: " << max << "(" << perc_max << "%)\n"; - } - } - }; -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING - static bgcode::binarize::BinarizerConfig& get_binarizer_config() { return s_binarizer_config; } + private: GCodeReader m_parser; bgcode::binarize::Binarizer m_binarizer; @@ -596,11 +531,7 @@ namespace Slic3r { SeamsDetector m_seams_detector; OptionsZCorrector m_options_z_corrector; size_t m_last_default_color_id; - bool m_spiral_vase_active; float m_kissslicer_toolchange_time_correction; -#if ENABLE_GCODE_VIEWER_STATISTICS - std::chrono::time_point m_start_time; -#endif // ENABLE_GCODE_VIEWER_STATISTICS bool m_single_extruder_multi_material; enum class EProducer @@ -629,12 +560,6 @@ namespace Slic3r { GCodeProcessorResult m_result; static unsigned int s_result_id; -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - DataChecker m_mm3_per_mm_compare{ "mm3_per_mm", 0.01f }; - DataChecker m_height_compare{ "height", 0.01f }; - DataChecker m_width_compare{ "width", 0.01f }; -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING - public: GCodeProcessor(); @@ -655,7 +580,8 @@ namespace Slic3r { // Load a G-code into a stand-alone G-code viewer. // throws CanceledException through print->throw_if_canceled() (sent by the caller as callback). - void process_file(const std::string& filename, std::function cancel_callback = nullptr); + void process_file(const std::string& filename, GCodeReader::ProgressCallback progress_callback = nullptr, + std::function cancel_callback = nullptr); // Streaming interface, for processing G-codes just generated by QIDISlicer in a pipelined fashion. void initialize(const std::string& filename); @@ -669,13 +595,9 @@ namespace Slic3r { float get_time(PrintEstimatedStatistics::ETimeMode mode) const; std::string get_time_dhm(PrintEstimatedStatistics::ETimeMode mode) const; - float get_travel_time(PrintEstimatedStatistics::ETimeMode mode) const; - std::string get_travel_time_dhm(PrintEstimatedStatistics::ETimeMode mode) const; std::vector>> get_custom_gcode_times(PrintEstimatedStatistics::ETimeMode mode, bool include_remaining) const; - std::vector> get_moves_time(PrintEstimatedStatistics::ETimeMode mode) const; - std::vector> get_roles_time(PrintEstimatedStatistics::ETimeMode mode) const; - std::vector get_layers_time(PrintEstimatedStatistics::ETimeMode mode) const; + float get_first_layer_time(PrintEstimatedStatistics::ETimeMode mode) const; private: void apply_config(const DynamicPrintConfig& config); @@ -684,8 +606,11 @@ namespace Slic3r { void apply_config_kissslicer(const std::string& filename); void process_gcode_line(const GCodeReader::GCodeLine& line, bool producers_enabled); - void process_ascii_file(const std::string& filename, std::function cancel_callback = nullptr); - void process_binary_file(const std::string& filename, std::function cancel_callback = nullptr); + void process_ascii_file(const std::string& filename, GCodeReader::ProgressCallback progress_callback = nullptr, + std::function cancel_callback = nullptr); + void process_binary_file(const std::string& filename, GCodeReader::ProgressCallback progress_callback = nullptr, + std::function cancel_callback = nullptr); + // Process tags embedded into comments void process_tags(const std::string_view comment, bool producers_enabled); bool process_producers_tags(const std::string_view comment); @@ -839,6 +764,8 @@ namespace Slic3r { void process_custom_gcode_time(CustomGCode::Type code); void process_filaments(CustomGCode::Type code); + void calculate_time(GCodeProcessorResult& result, size_t keep_last_n_blocks = 0, float additional_time = 0.0f); + // Simulates firmware st_synchronize() call void simulate_st_synchronize(float additional_time = 0.0f); diff --git a/src/libslic3r/GCode/GCodeWriter.cpp b/src/libslic3r/GCode/GCodeWriter.cpp index 5fc08a3..31951d3 100644 --- a/src/libslic3r/GCode/GCodeWriter.cpp +++ b/src/libslic3r/GCode/GCodeWriter.cpp @@ -1,12 +1,12 @@ #include "GCodeWriter.hpp" -#include "../CustomGCode.hpp" + #include -#include #include -#include -#include #include -#include +#include +#include + +#include "libslic3r/libslic3r.h" #ifdef __APPLE__ #include @@ -16,6 +16,7 @@ #define FLAVOR_IS_NOT(val) this->config.gcode_flavor != val using namespace std::string_view_literals; + namespace Slic3r { // static @@ -268,9 +269,10 @@ std::string GCodeWriter::update_progress(unsigned int num, unsigned int tot, boo { if (FLAVOR_IS_NOT(gcfMakerWare) && FLAVOR_IS_NOT(gcfSailfish)) return {}; + unsigned int percent = (unsigned int)floor(100.0 * num / tot + 0.5); if (!allow_100) percent = std::min(percent, (unsigned int)99); - + std::ostringstream gcode; gcode << "M73 P" << percent; if (this->config.gcode_comments) gcode << " ; update progress"; @@ -318,7 +320,6 @@ std::string GCodeWriter::set_speed(double F, const std::string_view comment, con std::string GCodeWriter::get_travel_to_xy_gcode(const Vec2d &point, const std::string_view comment) const { - GCodeG1Formatter w; w.emit_xy(point); //B36 @@ -334,6 +335,7 @@ std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string_view m_pos.head<2>() = point.head<2>(); return this->get_travel_to_xy_gcode(point, comment); } + std::string GCodeWriter::travel_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, const std::string_view comment) { assert(std::abs(point.x()) < 1200.); @@ -341,7 +343,7 @@ std::string GCodeWriter::travel_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij assert(std::abs(ij.x()) < 1200.); assert(std::abs(ij.y()) < 1200.); assert(std::abs(ij.x()) >= 0.001 || std::abs(ij.y()) >= 0.001); - + m_pos.head<2>() = point.head<2>(); GCodeG2G3Formatter w(ccw); @@ -349,8 +351,8 @@ std::string GCodeWriter::travel_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij w.emit_ij(ij); w.emit_comment(this->config.gcode_comments, comment); return w.string(); - } - +} + std::string GCodeWriter::travel_to_xyz(const Vec3d& from, const Vec3d &to, const std::string_view comment) { if (std::abs(to.x() - m_pos.x()) < EPSILON && std::abs(to.y() - m_pos.y()) < EPSILON) { @@ -362,7 +364,7 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d& from, const Vec3d &to, const return this->get_travel_to_xyz_gcode(from, to, comment); } } - + std::string GCodeWriter::get_travel_to_xyz_gcode(const Vec3d &from, const Vec3d &to, const std::string_view comment) const { GCodeG1Formatter w; w.emit_xyz(to); @@ -381,11 +383,11 @@ std::string GCodeWriter::get_travel_to_xyz_gcode(const Vec3d &from, const Vec3d } else { w.emit_f(this->config.travel_speed.value * 60.0); } + w.emit_comment(this->config.gcode_comments, comment); return w.string(); } - std::string GCodeWriter::travel_to_z(double z, const std::string_view comment) { if (std::abs(m_pos.z() - z) < EPSILON) { @@ -398,11 +400,10 @@ std::string GCodeWriter::travel_to_z(double z, const std::string_view comment) std::string GCodeWriter::get_travel_to_z_gcode(double z, const std::string_view comment) const { - double speed = this->config.travel_speed_z.value; if (speed == 0.) speed = this->config.travel_speed.value; - + GCodeG1Formatter w; w.emit_z(z); w.emit_f(speed * 60.0); @@ -485,6 +486,7 @@ std::string GCodeWriter::_retract(double length, double restart_extra, const std { assert(std::abs(length) < 1000.0); assert(std::abs(restart_extra) < 1000.0); + /* If firmware retraction is enabled, we use a fake value of 1 since we ignore the actual configured retract_length which might be 0, in which case the retraction logic gets skipped. */ @@ -542,7 +544,6 @@ std::string GCodeWriter::unretract() return gcode; } - void GCodeWriter::update_position(const Vec3d &new_pos) { m_pos = new_pos; diff --git a/src/libslic3r/GCode/LabelObjects.cpp b/src/libslic3r/GCode/LabelObjects.cpp index 9d1413f..bd7beb1 100644 --- a/src/libslic3r/GCode/LabelObjects.cpp +++ b/src/libslic3r/GCode/LabelObjects.cpp @@ -1,12 +1,22 @@ #include "LabelObjects.hpp" -#include "ClipperUtils.hpp" -#include "GCode/GCodeWriter.hpp" -#include "Model.hpp" -#include "Print.hpp" -#include "TriangleMeshSlicer.hpp" +#include +#include +#include +#include + +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/GCode/GCodeWriter.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/Print.hpp" +#include "libslic3r/TriangleMeshSlicer.hpp" +#include "libslic3r/MultiMaterialSegmentation.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/libslic3r.h" -#include "boost/algorithm/string/replace.hpp" namespace Slic3r::GCode { @@ -56,7 +66,7 @@ void LabelObjects::init(const SpanOfConstPtrs& objects, LabelObject for (const PrintObject* po : objects) for (const PrintInstance& pi : po->instances()) model_object_to_print_instances[pi.model_instance->get_object()].emplace_back(&pi); - + // Now go through the map, assign a unique_id to each of the PrintInstances and get the indices of the // respective ModelObject and ModelInstance so we can use them in the tags. This will maintain // indices even in case that some instances are rotated (those end up in different PrintObjects) @@ -158,8 +168,7 @@ std::string LabelObjects::all_objects_header() const if (m_label_objects_style == LabelObjectsStyle::Disabled) return std::string(); - std::string out; - + std::string out; out += "\n"; for (const LabelData& label : m_label_data) { @@ -190,6 +199,7 @@ std::string LabelObjects::all_objects_header_singleline_json() const } + std::string LabelObjects::start_object(const PrintInstance& print_instance, IncludeName include_name) const { if (m_label_objects_style == LabelObjectsStyle::Disabled) diff --git a/src/libslic3r/GCode/LabelObjects.hpp b/src/libslic3r/GCode/LabelObjects.hpp index 7055ec3..ed51a65 100644 --- a/src/libslic3r/GCode/LabelObjects.hpp +++ b/src/libslic3r/GCode/LabelObjects.hpp @@ -12,7 +12,6 @@ enum GCodeFlavor : unsigned char; enum class LabelObjectsStyle; struct PrintInstance; class Print; - class GCodeWriter; namespace GCode { @@ -43,10 +42,12 @@ private: std::string polygon; int unique_id; }; + enum class IncludeName { No, Yes }; + std::string start_object(const PrintInstance& print_instance, IncludeName include_name) const; std::string stop_object(const PrintInstance& print_instance) const; @@ -56,10 +57,7 @@ private: LabelObjectsStyle m_label_objects_style; GCodeFlavor m_flavor; std::vector m_label_data; - }; - - } // namespace GCode } // namespace Slic3r diff --git a/src/libslic3r/GCode/ModelVisibility.cpp b/src/libslic3r/GCode/ModelVisibility.cpp new file mode 100644 index 0000000..0bc327e --- /dev/null +++ b/src/libslic3r/GCode/ModelVisibility.cpp @@ -0,0 +1,309 @@ +#include +#include +#include +#include +#include +#include + +#include "libslic3r/ShortEdgeCollapse.hpp" +#include "libslic3r/GCode/ModelVisibility.hpp" +#include "libslic3r/AABBTreeIndirect.hpp" +#include "admesh/stl.h" +#include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/libslic3r.h" + +namespace Slic3r::ModelInfo { +namespace Impl { + +CoordinateFunctor::CoordinateFunctor(const std::vector *coords) : coordinates(coords) {} +CoordinateFunctor::CoordinateFunctor() : coordinates(nullptr) {} + +const float &CoordinateFunctor::operator()(size_t idx, size_t dim) const { + return coordinates->operator[](idx)[dim]; +} + + +template int sgn(T val) { + return int(T(0) < val) - int(val < T(0)); +} + +/// Coordinate frame +class Frame { +public: + Frame() { + mX = Vec3f(1, 0, 0); + mY = Vec3f(0, 1, 0); + mZ = Vec3f(0, 0, 1); + } + + Frame(const Vec3f &x, const Vec3f &y, const Vec3f &z) : + mX(x), mY(y), mZ(z) { + } + + void set_from_z(const Vec3f &z) { + mZ = z.normalized(); + Vec3f tmpZ = mZ; + Vec3f tmpX = (std::abs(tmpZ.x()) > 0.99f) ? Vec3f(0, 1, 0) : Vec3f(1, 0, 0); + mY = (tmpZ.cross(tmpX)).normalized(); + mX = mY.cross(tmpZ); + } + + Vec3f to_world(const Vec3f &a) const { + return a.x() * mX + a.y() * mY + a.z() * mZ; + } + + Vec3f to_local(const Vec3f &a) const { + return Vec3f(mX.dot(a), mY.dot(a), mZ.dot(a)); + } + + const Vec3f& binormal() const { + return mX; + } + + const Vec3f& tangent() const { + return mY; + } + + const Vec3f& normal() const { + return mZ; + } + +private: + Vec3f mX, mY, mZ; +}; + +Vec3f sample_sphere_uniform(const Vec2f &samples) { + float term1 = 2.0f * float(PI) * samples.x(); + float term2 = 2.0f * sqrt(samples.y() - samples.y() * samples.y()); + return {cos(term1) * term2, sin(term1) * term2, + 1.0f - 2.0f * samples.y()}; +} + +Vec3f sample_hemisphere_uniform(const Vec2f &samples) { + float term1 = 2.0f * float(PI) * samples.x(); + float term2 = 2.0f * sqrt(samples.y() - samples.y() * samples.y()); + return {cos(term1) * term2, sin(term1) * term2, + abs(1.0f - 2.0f * samples.y())}; +} + +Vec3f sample_power_cosine_hemisphere(const Vec2f &samples, float power) { + float term1 = 2.f * float(PI) * samples.x(); + float term2 = pow(samples.y(), 1.f / (power + 1.f)); + float term3 = sqrt(1.f - term2 * term2); + + return Vec3f(cos(term1) * term3, sin(term1) * term3, term2); +} + +std::vector raycast_visibility( + const AABBTreeIndirect::Tree<3, float> &raycasting_tree, + const indexed_triangle_set &triangles, + const TriangleSetSamples &samples, + size_t negative_volumes_start_index, + const Visibility::Params ¶ms +) { + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: raycast visibility of " << samples.positions.size() << " samples over " << triangles.indices.size() + << " triangles: end"; + + //prepare uniform samples of a hemisphere + float step_size = 1.0f / params.sqr_rays_per_sample_point; + std::vector precomputed_sample_directions( + params.sqr_rays_per_sample_point * params.sqr_rays_per_sample_point); + for (size_t x_idx = 0; x_idx < params.sqr_rays_per_sample_point; ++x_idx) { + float sample_x = x_idx * step_size + step_size / 2.0; + for (size_t y_idx = 0; y_idx < params.sqr_rays_per_sample_point; ++y_idx) { + size_t dir_index = x_idx * params.sqr_rays_per_sample_point + y_idx; + float sample_y = y_idx * step_size + step_size / 2.0; + precomputed_sample_directions[dir_index] = sample_hemisphere_uniform( { sample_x, sample_y }); + } + } + + bool model_contains_negative_parts = negative_volumes_start_index < triangles.indices.size(); + + std::vector result(samples.positions.size()); + tbb::parallel_for(tbb::blocked_range(0, result.size()), + [&triangles, &precomputed_sample_directions, model_contains_negative_parts, negative_volumes_start_index, + &raycasting_tree, &result, &samples, ¶ms](tbb::blocked_range r) { + // Maintaining hits memory outside of the loop, so it does not have to be reallocated for each query. + std::vector hits; + for (size_t s_idx = r.begin(); s_idx < r.end(); ++s_idx) { + result[s_idx] = 1.0f; + const float decrease_step = 1.0f + / (params.sqr_rays_per_sample_point * params.sqr_rays_per_sample_point); + + const Vec3f ¢er = samples.positions[s_idx]; + const Vec3f &normal = samples.normals[s_idx]; + // apply the local direction via Frame struct - the local_dir is with respect to +Z being forward + Frame f; + f.set_from_z(normal); + + for (const auto &dir : precomputed_sample_directions) { + Vec3f final_ray_dir = (f.to_world(dir)); + if (!model_contains_negative_parts) { + igl::Hit hitpoint; + // FIXME: This AABBTTreeIndirect query will not compile for float ray origin and + // direction. + Vec3d final_ray_dir_d = final_ray_dir.cast(); + Vec3d ray_origin_d = (center + normal * 0.01f).cast(); // start above surface. + bool hit = AABBTreeIndirect::intersect_ray_first_hit(triangles.vertices, + triangles.indices, raycasting_tree, ray_origin_d, final_ray_dir_d, hitpoint); + if (hit && its_face_normal(triangles, hitpoint.id).dot(final_ray_dir) <= 0) { + result[s_idx] -= decrease_step; + } + } else { //TODO improve logic for order based boolean operations - consider order of volumes + bool casting_from_negative_volume = samples.triangle_indices[s_idx] + >= negative_volumes_start_index; + + Vec3d ray_origin_d = (center + normal * 0.01f).cast(); // start above surface. + if (casting_from_negative_volume) { // if casting from negative volume face, invert direction, change start pos + final_ray_dir = -1.0 * final_ray_dir; + ray_origin_d = (center - normal * 0.01f).cast(); + } + Vec3d final_ray_dir_d = final_ray_dir.cast(); + bool some_hit = AABBTreeIndirect::intersect_ray_all_hits(triangles.vertices, + triangles.indices, raycasting_tree, + ray_origin_d, final_ray_dir_d, hits); + if (some_hit) { + int counter = 0; + // NOTE: iterating in reverse, from the last hit for one simple reason: We know the state of the ray at that point; + // It cannot be inside model, and it cannot be inside negative volume + for (int hit_index = int(hits.size()) - 1; hit_index >= 0; --hit_index) { + Vec3f face_normal = its_face_normal(triangles, hits[hit_index].id); + if (hits[hit_index].id >= int(negative_volumes_start_index)) { //negative volume hit + counter -= sgn(face_normal.dot(final_ray_dir)); // if volume face aligns with ray dir, we are leaving negative space + // which in reverse hit analysis means, that we are entering negative space :) and vice versa + } else { + counter += sgn(face_normal.dot(final_ray_dir)); + } + } + if (counter == 0) { + result[s_idx] -= decrease_step; + } + } + } + } + } + }); + + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: raycast visibility of " << samples.positions.size() << " samples over " << triangles.indices.size() + << " triangles: end"; + + return result; +} +} + +Visibility::Visibility( + const Transform3d &obj_transform, + const ModelVolumePtrs &volumes, + const Params ¶ms, + const std::function &throw_if_canceled +) { + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: gather occlusion meshes: start"; + indexed_triangle_set triangle_set; + indexed_triangle_set negative_volumes_set; + //add all parts + for (const ModelVolume *model_volume : volumes) { + if (model_volume->type() == ModelVolumeType::MODEL_PART + || model_volume->type() == ModelVolumeType::NEGATIVE_VOLUME) { + auto model_transformation = model_volume->get_matrix(); + indexed_triangle_set model_its = model_volume->mesh().its; + its_transform(model_its, model_transformation); + if (model_volume->type() == ModelVolumeType::MODEL_PART) { + its_merge(triangle_set, model_its); + } else { + its_merge(negative_volumes_set, model_its); + } + } + } + throw_if_canceled(); + + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: gather occlusion meshes: end"; + + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: decimate: start"; + its_short_edge_collpase(triangle_set, params.fast_decimation_triangle_count_target); + its_short_edge_collpase(negative_volumes_set, params.fast_decimation_triangle_count_target); + + size_t negative_volumes_start_index = triangle_set.indices.size(); + its_merge(triangle_set, negative_volumes_set); + its_transform(triangle_set, obj_transform); + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: decimate: end"; + + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: Compute visibility sample points: start"; + + this->mesh_samples = sample_its_uniform_parallel(params.raycasting_visibility_samples_count, + triangle_set); + this->mesh_samples_coordinate_functor = Impl::CoordinateFunctor(&this->mesh_samples.positions); + this->mesh_samples_tree = KDTreeIndirect<3, float, Impl::CoordinateFunctor>(this->mesh_samples_coordinate_functor, + this->mesh_samples.positions.size()); + + // The following code determines search area for random visibility samples on the mesh when calculating visibility of each perimeter point + // number of random samples in the given radius (area) is approximately poisson distribution + // to compute ideal search radius (area), we use exponential distribution (complementary distr to poisson) + // parameters of exponential distribution to compute area that will have with probability="probability" more than given number of samples="samples" + float probability = 0.9f; + float samples = 4; + float density = params.raycasting_visibility_samples_count / this->mesh_samples.total_area; + // exponential probability distrubtion function is : f(x) = P(X > x) = e^(l*x) where l is the rate parameter (computed as 1/u where u is mean value) + // probability that sampled area A with S samples contains more than samples count: + // P(S > samples in A) = e^-(samples/(density*A)); express A: + float search_area = samples / (-logf(probability) * density); + float search_radius = sqrt(search_area / PI); + this->mesh_samples_radius = search_radius; + + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: Compute visiblity sample points: end"; + throw_if_canceled(); + + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: Mesh sample raidus: " << this->mesh_samples_radius; + + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: build AABB tree: start"; + auto raycasting_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(triangle_set.vertices, + triangle_set.indices); + + throw_if_canceled(); + BOOST_LOG_TRIVIAL(debug) + << "SeamPlacer: build AABB tree: end"; + this->mesh_samples_visibility = Impl::raycast_visibility(raycasting_tree, triangle_set, this->mesh_samples, + negative_volumes_start_index, params); + throw_if_canceled(); +} + +float Visibility::calculate_point_visibility(const Vec3f &position) const { + std::vector points = find_nearby_points(mesh_samples_tree, position, mesh_samples_radius); + if (points.empty()) { + return 1.0f; + } + + auto compute_dist_to_plane = [](const Vec3f &position, const Vec3f &plane_origin, + const Vec3f &plane_normal) { + Vec3f orig_to_point = position - plane_origin; + return std::abs(orig_to_point.dot(plane_normal)); + }; + + float total_weight = 0; + float total_visibility = 0; + for (size_t i = 0; i < points.size(); ++i) { + size_t sample_idx = points[i]; + + Vec3f sample_point = this->mesh_samples.positions[sample_idx]; + Vec3f sample_normal = this->mesh_samples.normals[sample_idx]; + + float weight = mesh_samples_radius - + compute_dist_to_plane(position, sample_point, sample_normal); + weight += (mesh_samples_radius - (position - sample_point).norm()); + total_visibility += weight * mesh_samples_visibility[sample_idx]; + total_weight += weight; + } + + return total_visibility / total_weight; +} + +} diff --git a/src/libslic3r/GCode/ModelVisibility.hpp b/src/libslic3r/GCode/ModelVisibility.hpp new file mode 100644 index 0000000..da12b81 --- /dev/null +++ b/src/libslic3r/GCode/ModelVisibility.hpp @@ -0,0 +1,56 @@ +#ifndef libslic3r_ModelVisibility_hpp_ +#define libslic3r_ModelVisibility_hpp_ + +#include +#include +#include +#include + +#include "libslic3r/KDTreeIndirect.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/TriangleSetSampling.hpp" + +namespace Slic3r::ModelInfo { +namespace Impl { + +struct CoordinateFunctor +{ + const std::vector *coordinates; + CoordinateFunctor(const std::vector *coords); + CoordinateFunctor(); + + const float &operator()(size_t idx, size_t dim) const; +}; +} // namespace Impl + +struct Visibility +{ + struct Params + { + // Number of samples generated on the mesh. There are + // sqr_rays_per_sample_point*sqr_rays_per_sample_point rays casted from each samples + size_t raycasting_visibility_samples_count{}; + size_t fast_decimation_triangle_count_target{}; + // square of number of rays per sample point + size_t sqr_rays_per_sample_point{}; + }; + + Visibility( + const Transform3d &obj_transform, + const ModelVolumePtrs &volumes, + const Params ¶ms, + const std::function &throw_if_canceled + ); + + TriangleSetSamples mesh_samples; + std::vector mesh_samples_visibility; + Impl::CoordinateFunctor mesh_samples_coordinate_functor; + KDTreeIndirect<3, float, Impl::CoordinateFunctor> mesh_samples_tree{Impl::CoordinateFunctor{}}; + float mesh_samples_radius; + + float calculate_point_visibility(const Vec3f &position) const; +}; + +} // namespace Slic3r::ModelInfo +#endif // libslic3r_ModelVisibility_hpp_ diff --git a/src/libslic3r/GCode/PostProcessor.cpp b/src/libslic3r/GCode/PostProcessor.cpp index 9d4ef10..d4656cf 100644 --- a/src/libslic3r/GCode/PostProcessor.cpp +++ b/src/libslic3r/GCode/PostProcessor.cpp @@ -8,8 +8,8 @@ #include #include #include +#include #include -#include #include #ifdef WIN32 @@ -87,7 +87,7 @@ static DWORD execute_process_winapi(const std::wstring &command_line) if (! ::CreateProcessW( nullptr /* lpApplicationName */, (LPWSTR)command_line.c_str(), nullptr /* lpProcessAttributes */, nullptr /* lpThreadAttributes */, false /* bInheritHandles */, CREATE_UNICODE_ENVIRONMENT /* | CREATE_NEW_CONSOLE */ /* dwCreationFlags */, (LPVOID)envstr.c_str(), nullptr /* lpCurrentDirectory */, &startup_info, &process_info)) - throw Slic3r::RuntimeError(std::string("Failed starting the script ") + boost::nowide::narrow(command_line) + ", Win32 error: " + std::to_string(int(::GetLastError()))); + throw Slic3r::RuntimeError(std::string("Failed starting the script ") + boost::nowide::narrow(command_line) + ", Win32 error: " + std::to_string(int(::GetLastError()))); ::WaitForSingleObject(process_info.hProcess, INFINITE); ULONG rc = 0; ::GetExitCodeProcess(process_info.hProcess, &rc); @@ -112,7 +112,7 @@ static int run_script(const std::string &script, const std::string &gcode, std:: std::wstring command_line; std::wstring command = szArglist[0]; if (! boost::filesystem::exists(boost::filesystem::path(command))) - throw Slic3r::RuntimeError(std::string("The configured post-processing script does not exist: ") + boost::nowide::narrow(command)); + throw Slic3r::RuntimeError(std::string("The configured post-processing script does not exist: ") + boost::nowide::narrow(command)); if (boost::iends_with(command, L".pl")) { // This is a perl script. Run it through the perl interpreter. // The current process may be slic3r.exe or slic3r-console.exe. diff --git a/src/libslic3r/GCode/PostProcessor.hpp b/src/libslic3r/GCode/PostProcessor.hpp index 1ed8fd3..19bf3f2 100644 --- a/src/libslic3r/GCode/PostProcessor.hpp +++ b/src/libslic3r/GCode/PostProcessor.hpp @@ -3,8 +3,8 @@ #include -#include "../libslic3r.h" -#include "../PrintConfig.hpp" +#include "libslic3r/libslic3r.h" +#include "libslic3r/PrintConfig.hpp" namespace Slic3r { diff --git a/src/libslic3r/GCode/PressureEqualizer.cpp b/src/libslic3r/GCode/PressureEqualizer.cpp index c82fa29..73ac662 100644 --- a/src/libslic3r/GCode/PressureEqualizer.cpp +++ b/src/libslic3r/GCode/PressureEqualizer.cpp @@ -1,16 +1,18 @@ -#include +#include +#include #include -#include #include +#include +#include +#include +#include +#include -#include "../libslic3r.h" -#include "../PrintConfig.hpp" -#include "../LocalesUtils.hpp" -#include "../GCode.hpp" - -#include "PressureEqualizer.hpp" -#include "fast_float/fast_float.h" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/GCode.hpp" #include "GCodeWriter.hpp" +#include "libslic3r/GCode/PressureEqualizer.hpp" +#include "libslic3r/Exception.hpp" namespace Slic3r { @@ -33,6 +35,7 @@ static constexpr int max_look_back_limit = 128; // This exists because often there are tiny travel moves between stuff like infill. // Lines where some extruder pressure will remain (so we should equalize between these small travels). static constexpr double max_ignored_gap_between_extruding_segments = 3.; + PressureEqualizer::PressureEqualizer(const Slic3r::GCodeConfig &config) : m_use_relative_e_distances(config.use_relative_e_distances.value) { // Preallocate some data, so that output_buffer.data() will return an empty string. @@ -103,6 +106,7 @@ void PressureEqualizer::process_layer(const std::string &gcode) } assert(!this->opened_extrude_set_speed_block); } + // At this point, we have an entire layer of gcode lines loaded into m_gcode_lines. // Now, we will split the mix of travels and extrusions into segments of continuous extrusions and process them. // We skip over large travels, and pretend that small ones are part of a continuous extrusion segment. @@ -214,8 +218,8 @@ static inline bool is_ws_or_eol(const char c) { return is_ws(c) || is_eol(c); } // Eat whitespaces. static void eatws(const char *&line) { - while (is_ws(*line)) - ++ line; + while (is_ws(*line)) + ++line; } // Parse an int starting at the current position of a line. @@ -283,7 +287,7 @@ bool PressureEqualizer::process_line(const char *line, const char *line_end, GCo buf.volumetric_extrusion_rate_end = 0.f; buf.max_volumetric_extrusion_rate_slope_positive = 0.f; buf.max_volumetric_extrusion_rate_slope_negative = 0.f; - buf.extrusion_role = m_current_extrusion_role; + buf.extrusion_role = m_current_extrusion_role; std::string str_line(line, line_end); const bool found_extrude_set_speed_tag = boost::contains(str_line, EXTRUDE_SET_SPEED_TAG); @@ -390,7 +394,7 @@ bool PressureEqualizer::process_line(const char *line, const char *line_end, GCo memcpy(m_current_pos, new_pos, sizeof(float) * 5); break; } - case 92: + case 92: { // G92 : Set Position // Set a logical coordinate position to a new value without actually moving the machine motors. @@ -427,7 +431,7 @@ bool PressureEqualizer::process_line(const char *line, const char *line_end, GCo break; default: // Ignore the rest. - break; + break; } break; } @@ -462,7 +466,6 @@ bool PressureEqualizer::process_line(const char *line, const char *line_end, GCo buf.extruder_id = m_current_extruder; memcpy(buf.pos_end, m_current_pos, sizeof(float)*5); - #ifdef PRESSURE_EQUALIZER_DEBUG ++line_idx; #endif @@ -555,13 +558,13 @@ void PressureEqualizer::output_gcode_line(const size_t line_idx) for (size_t j = 0; j < 4; ++ j) { line.pos_end[j] = pos_start[j] + (pos_end[j] - pos_start[j]) * t; line.pos_provided[j] = true; - } + } // Interpolate the feed rate at the center of the segment. push_line_to_output(line_idx, pos_start[4] + (pos_end[4] - pos_start[4]) * (float(i) - 0.5f) / float(nSegments), comment); comment = nullptr; memcpy(line.pos_start, line.pos_end, sizeof(float)*5); } - if (l_steady > 0.f && accelerating) { + if (l_steady > 0.f && accelerating) { for (int i = 0; i < 4; ++ i) { line.pos_end[i] = pos_end2[i]; line.pos_provided[i] = true; @@ -583,7 +586,7 @@ void PressureEqualizer::adjust_volumetric_rate(const size_t first_line_idx, cons if (last_line_idx <= first_line_idx || last_line_idx - first_line_idx < 2) return; - size_t line_idx = last_line_idx; + size_t line_idx = last_line_idx; if (line_idx == first_line_idx || !m_gcode_lines[line_idx].extruding()) // Nothing to do, the last move is not extruding. return; @@ -632,7 +635,7 @@ void PressureEqualizer::adjust_volumetric_rate(const size_t first_line_idx, cons } if (line.adjustable_flow) { - float rate_start = rate_end + rate_slope * line.time_corrected(); + float rate_start = sqrt(rate_end * rate_end + 2 * line.volumetric_extrusion_rate * line.dist_xyz() * rate_slope / line.feedrate()); if (rate_start < line.volumetric_extrusion_rate_start) { // Limit the volumetric extrusion rate at the start of this segment due to a segment // of ExtrusionType iRole, which will be extruded in the future. @@ -641,6 +644,7 @@ void PressureEqualizer::adjust_volumetric_rate(const size_t first_line_idx, cons line.modified = true; } } + // Don't store feed rate for ironing. if (line.extrusion_role != GCodeExtrusionRole::Ironing) feedrate_per_extrusion_role[iRole] = line.volumetric_extrusion_rate_start; @@ -688,7 +692,7 @@ void PressureEqualizer::adjust_volumetric_rate(const size_t first_line_idx, cons } if (line.adjustable_flow) { - float rate_end = rate_start + rate_slope * line.time_corrected(); + float rate_end = sqrt(rate_start * rate_start + 2 * line.volumetric_extrusion_rate * line.dist_xyz() * rate_slope / line.feedrate()); if (rate_end < line.volumetric_extrusion_rate_end) { // Limit the volumetric extrusion rate at the start of this segment due to a segment // of ExtrusionType iRole, which was extruded before. @@ -697,6 +701,7 @@ void PressureEqualizer::adjust_volumetric_rate(const size_t first_line_idx, cons line.modified = true; } } + // Don't store feed rate for ironing if (line.extrusion_role != GCodeExtrusionRole::Ironing) feedrate_per_extrusion_role[iRole] = line.volumetric_extrusion_rate_end; @@ -766,8 +771,10 @@ inline bool is_just_line_with_extrude_set_speed_tag(const std::string &line) return p_line <= line_end && is_eol(*p_line); } -void PressureEqualizer::push_line_to_output(const size_t line_idx, const float new_feedrate, const char *comment) -{ +void PressureEqualizer::push_line_to_output(const size_t line_idx, float new_feedrate, const char *comment) { + // Ensure the minimum feedrate will not be below 1 mm/s. + new_feedrate = std::max(60.f, new_feedrate); + const GCodeLine &line = m_gcode_lines[line_idx]; if (line_idx > 0 && output_buffer_length > 0) { const std::string prev_line_str = std::string(output_buffer.begin() + int(this->output_buffer_prev_length), diff --git a/src/libslic3r/GCode/PressureEqualizer.hpp b/src/libslic3r/GCode/PressureEqualizer.hpp index e7ff680..5d4208a 100644 --- a/src/libslic3r/GCode/PressureEqualizer.hpp +++ b/src/libslic3r/GCode/PressureEqualizer.hpp @@ -1,16 +1,23 @@ #ifndef slic3r_GCode_PressureEqualizer_hpp_ #define slic3r_GCode_PressureEqualizer_hpp_ -#include "../libslic3r.h" -#include "../PrintConfig.hpp" -#include "../ExtrusionRole.hpp" - +#include +#include #include +#include +#include +#include +#include +#include +#include + +#include "libslic3r/libslic3r.h" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/ExtrusionRole.hpp" namespace Slic3r { struct LayerResult; - class GCodeG1Formatter; //#define PRESSURE_EQUALIZER_STATISTIC @@ -123,8 +130,9 @@ private: float feedrate() const { return pos_end[4]; } float time() const { return dist_xyz() / feedrate(); } float time_inv() const { return feedrate() / dist_xyz(); } - float volumetric_correction_avg() const { - float avg_correction = 0.5f * (volumetric_extrusion_rate_start + volumetric_extrusion_rate_end) / volumetric_extrusion_rate; + float volumetric_correction_avg() const { + // Cap the correction to 0.05 - 1.00000001 to avoid zero feedrate. + float avg_correction = std::max(0.05f, 0.5f * (volumetric_extrusion_rate_start + volumetric_extrusion_rate_end) / volumetric_extrusion_rate); assert(avg_correction > 0.f); assert(avg_correction <= 1.00000001f); return avg_correction; @@ -171,6 +179,7 @@ private: using GCodeLines = std::vector; using GCodeLinesConstIt = GCodeLines::const_iterator; + // Output buffer will only grow. It will not be reallocated over and over. std::vector output_buffer; size_t output_buffer_length; @@ -185,6 +194,7 @@ private: void output_gcode_line(size_t line_idx); GCodeLinesConstIt advance_segment_beyond_small_gap(const GCodeLinesConstIt &last_extruding_line_it) const; + // Go back from the current circular_buffer_pos and lower the feedtrate to decrease the slope of the extrusion rate changes. // Then go forward and adjust the feedrate to decrease the slope of the extrusion rate changes. void adjust_volumetric_rate(size_t first_line_idx, size_t last_line_idx); diff --git a/src/libslic3r/GCode/PrintExtents.cpp b/src/libslic3r/GCode/PrintExtents.cpp index 9bcaa27..cb1e68f 100644 --- a/src/libslic3r/GCode/PrintExtents.cpp +++ b/src/libslic3r/GCode/PrintExtents.cpp @@ -3,14 +3,22 @@ // to decide whether to pause the print after the priming towers are extruded // to let the operator remove them from the print bed. +#include +#include +#include + #include "../BoundingBox.hpp" #include "../ExtrusionEntity.hpp" #include "../ExtrusionEntityCollection.hpp" #include "../Layer.hpp" #include "../Print.hpp" - #include "PrintExtents.hpp" #include "WipeTower.hpp" +#include "libslic3r/Exception.hpp" +#include "libslic3r/Geometry.hpp" +#include "libslic3r/LayerRegion.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polyline.hpp" namespace Slic3r { diff --git a/src/libslic3r/GCode/PrintExtents.hpp b/src/libslic3r/GCode/PrintExtents.hpp index db50768..981791a 100644 --- a/src/libslic3r/GCode/PrintExtents.hpp +++ b/src/libslic3r/GCode/PrintExtents.hpp @@ -4,7 +4,7 @@ #ifndef slic3r_PrintExtents_hpp_ #define slic3r_PrintExtents_hpp_ -#include "libslic3r.h" +#include "libslic3r/libslic3r.h" namespace Slic3r { diff --git a/src/libslic3r/GCode/RetractWhenCrossingPerimeters.cpp b/src/libslic3r/GCode/RetractWhenCrossingPerimeters.cpp index b834ca5..57b6f21 100644 --- a/src/libslic3r/GCode/RetractWhenCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/RetractWhenCrossingPerimeters.cpp @@ -1,8 +1,16 @@ +#include +#include + #include "../ClipperUtils.hpp" #include "../Layer.hpp" #include "../Polyline.hpp" - #include "RetractWhenCrossingPerimeters.hpp" +#include "libslic3r/AABBTreeIndirect.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/LayerRegion.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/Surface.hpp" namespace Slic3r { diff --git a/src/libslic3r/GCode/RetractWhenCrossingPerimeters.hpp b/src/libslic3r/GCode/RetractWhenCrossingPerimeters.hpp index fb624d7..e6029ce 100644 --- a/src/libslic3r/GCode/RetractWhenCrossingPerimeters.hpp +++ b/src/libslic3r/GCode/RetractWhenCrossingPerimeters.hpp @@ -4,6 +4,7 @@ #include #include "../AABBTreeIndirect.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { diff --git a/src/libslic3r/GCode/SeamAligned.cpp b/src/libslic3r/GCode/SeamAligned.cpp new file mode 100644 index 0000000..7a16d0b --- /dev/null +++ b/src/libslic3r/GCode/SeamAligned.cpp @@ -0,0 +1,554 @@ +#include "libslic3r/GCode/SeamAligned.hpp" + +#include +#include +#include +#include +#include +#include + +#include "libslic3r/GCode/SeamGeometry.hpp" +#include "libslic3r/GCode/ModelVisibility.hpp" +#include "libslic3r/KDTreeIndirect.hpp" +#include "libslic3r/Line.hpp" +#include "tcbspan/span.hpp" + +namespace Slic3r::Seams::Aligned { +using Perimeters::PointType; +using Perimeters::PointClassification; + +namespace Impl { +const Perimeters::Perimeter::PointTrees &pick_trees( + const Perimeters::Perimeter &perimeter, const PointType point_type +) { + switch (point_type) { + case PointType::enforcer: return perimeter.enforced_points; + case PointType::blocker: return perimeter.blocked_points; + case PointType::common: return perimeter.common_points; + } + throw std::runtime_error("Point trees for point type do not exist."); +} + +const Perimeters::Perimeter::OptionalPointTree &pick_tree( + const Perimeters::Perimeter::PointTrees &point_trees, + const PointClassification &point_classification +) { + switch (point_classification) { + case PointClassification::overhang: return point_trees.overhanging_points; + case PointClassification::embedded: return point_trees.embedded_points; + case PointClassification::common: return point_trees.common_points; + } + throw std::runtime_error("Point tree for classification does not exist."); +} + +unsigned point_value(PointType point_type, PointClassification point_classification) { + // Better be explicit than smart. + switch (point_type) { + case PointType::enforcer: + switch (point_classification) { + case PointClassification::embedded: return 9; + case PointClassification::common: return 8; + case PointClassification::overhang: return 7; + } + case PointType::common: + switch (point_classification) { + case PointClassification::embedded: return 6; + case PointClassification::common: return 5; + case PointClassification::overhang: return 4; + } + case PointType::blocker: + switch (point_classification) { + case PointClassification::embedded: return 3; + case PointClassification::common: return 2; + case PointClassification::overhang: return 1; + } + } + return 0; +} + +SeamChoice pick_seam_option(const Perimeters::Perimeter &perimeter, const SeamOptions &options) { + const std::vector &types{perimeter.point_types}; + const std::vector &classifications{perimeter.point_classifications}; + const std::vector &positions{perimeter.positions}; + + unsigned closeset_point_value = + point_value(types.at(options.closest), classifications[options.closest]); + + if (options.snapped) { + unsigned snapped_point_value = + point_value(types.at(*options.snapped), classifications[*options.snapped]); + if (snapped_point_value >= closeset_point_value) { + const Vec2d position{positions.at(*options.snapped)}; + return {*options.snapped, *options.snapped, position}; + } + } + + unsigned adjacent_point_value = + point_value(types.at(options.adjacent), classifications[options.adjacent]); + if (adjacent_point_value < closeset_point_value) { + const Vec2d position = positions[options.closest]; + return {options.closest, options.closest, position}; + } + + const std::size_t next_index{options.adjacent_forward ? options.adjacent : options.closest}; + const std::size_t previous_index{options.adjacent_forward ? options.closest : options.adjacent}; + return {previous_index, next_index, options.on_edge}; +} + +std::optional snap_to_angle( + const Vec2d &point, + const std::size_t search_start, + const Perimeters::Perimeter &perimeter, + const double max_detour +) { + using Perimeters::AngleType; + const std::vector &positions{perimeter.positions}; + const std::vector &angle_types{perimeter.angle_types}; + + std::optional match; + double min_distance{std::numeric_limits::infinity()}; + AngleType angle_type{AngleType::convex}; + + const auto visitor{[&](const std::size_t index) { + const double distance = (positions[index] - point).norm(); + if (distance > max_detour) { + return true; + } + if (angle_types[index] == angle_type && + distance < min_distance) { + match = index; + min_distance = distance; + return true; + } + return false; + }}; + Geometry::visit_near_backward(search_start, positions.size(), visitor); + Geometry::visit_near_forward(search_start, positions.size(), visitor); + if (match) { + return match; + } + + min_distance = std::numeric_limits::infinity(); + angle_type = AngleType::concave; + + Geometry::visit_near_backward(search_start, positions.size(), visitor); + Geometry::visit_near_forward(search_start, positions.size(), visitor); + + return match; +} + +SeamOptions get_seam_options( + const Perimeters::Perimeter &perimeter, + const Vec2d &prefered_position, + const Perimeters::Perimeter::PointTree &points_tree, + const double max_detour +) { + const std::vector &positions{perimeter.positions}; + + const std::size_t closest{find_closest_point(points_tree, prefered_position.head<2>())}; + std::size_t previous{closest == 0 ? positions.size() - 1 : closest - 1}; + std::size_t next{closest == positions.size() - 1 ? 0 : closest + 1}; + + const Vec2d previous_adjacent_point{positions[previous]}; + const Vec2d closest_point{positions[closest]}; + const Vec2d next_adjacent_point{positions[next]}; + + const Linef previous_segment{previous_adjacent_point, closest_point}; + const auto [previous_point, previous_distance] = + Geometry::distance_to_segment_squared(previous_segment, prefered_position); + const Linef next_segment{closest_point, next_adjacent_point}; + const auto [next_point, next_distance] = + Geometry::distance_to_segment_squared(next_segment, prefered_position); + + const bool adjacent_forward{next_distance < previous_distance}; + const Vec2d nearest_point{adjacent_forward ? next_point : previous_point}; + const std::size_t adjacent{adjacent_forward ? next : previous}; + + std::optional snapped{ + snap_to_angle(nearest_point.head<2>(), closest, perimeter, max_detour)}; + + return { + closest, adjacent, adjacent_forward, snapped, nearest_point, + }; +} + +std::optional LeastVisible::operator()( + const Perimeters::Perimeter &perimeter, + const PointType point_type, + const PointClassification point_classification +) const { + std::optional chosen_index; + double visibility{std::numeric_limits::infinity()}; + + for (std::size_t i{0}; i < perimeter.positions.size(); ++i) { + if (perimeter.point_types[i] != point_type || + perimeter.point_classifications[i] != point_classification) { + continue; + } + const Vec2d point{perimeter.positions[i]}; + const double point_visibility{precalculated_visibility[i]}; + + if (point_visibility < visibility) { + visibility = point_visibility; + chosen_index = i; + } + } + + if (chosen_index) { + return {{*chosen_index, *chosen_index, perimeter.positions[*chosen_index]}}; + } + return std::nullopt; +} + +std::optional Nearest::operator()( + const Perimeters::Perimeter &perimeter, + const PointType point_type, + const PointClassification point_classification +) const { + const Perimeters::Perimeter::PointTrees &trees{pick_trees(perimeter, point_type)}; + const Perimeters::Perimeter::OptionalPointTree &tree = pick_tree(trees, point_classification); + if (tree) { + const SeamOptions options{get_seam_options(perimeter, prefered_position, *tree, max_detour)}; + return pick_seam_option(perimeter, options); + } + return std::nullopt; +} +} // namespace Impl + +double VisibilityCalculator::operator()( + const SeamChoice &choice, const Perimeters::Perimeter &perimeter +) const { + double visibility = points_visibility.calculate_point_visibility( + to_3d(choice.position, perimeter.slice_z).cast() + ); + + const double angle{ + choice.previous_index == choice.next_index ? perimeter.angles[choice.previous_index] : 0.0}; + visibility += + get_angle_visibility_modifier(angle, convex_visibility_modifier, concave_visibility_modifier); + return visibility; +} + +double VisibilityCalculator::get_angle_visibility_modifier( + double angle, + const double convex_visibility_modifier, + const double concave_visibility_modifier +) { + const double weight_max{angle > 0 ? convex_visibility_modifier : concave_visibility_modifier}; + angle = std::abs(angle); + const double right_angle{M_PI / 2.0}; + if (angle > right_angle) { + return -weight_max; + } + const double angle_linear_weight{angle / right_angle}; + // It is smooth and at angle 0 slope is equal to `angle linear weight`, at right angle the slope is 0 and value is equal to weight max. + const double angle_smooth_weight{angle / right_angle * weight_max + (right_angle - angle) / right_angle * angle_linear_weight}; + return -angle_smooth_weight; +} + +std::vector get_starting_positions(const Shells::Shell<> &shell) { + const Perimeters::Perimeter &perimeter{shell.front().boundary}; + + std::vector enforcers{Perimeters::extract_points(perimeter, Perimeters::PointType::enforcer)}; + if (!enforcers.empty()) { + return enforcers; + } + std::vector common{Perimeters::extract_points(perimeter, Perimeters::PointType::common)}; + if (!common.empty()) { + return common; + } + return perimeter.positions; +} + +struct LeastVisiblePoint +{ + SeamChoice choice; + double visibility; +}; + +struct SeamCandidate { + std::vector choices; + std::vector visibilities; +}; + +std::vector get_shell_seam( + const Shells::Shell<> &shell, + const std::function &chooser +) { + std::vector result; + result.reserve(shell.size()); + for (std::size_t i{0}; i < shell.size(); ++i) { + const Shells::Slice<> &slice{shell[i]}; + if (slice.boundary.is_degenerate) { + if (std::optional seam_choice{ + choose_degenerate_seam_point(slice.boundary)}) { + result.push_back(*seam_choice); + } else { + result.emplace_back(); + } + } else { + const SeamChoice choice{chooser(slice.boundary, i)}; + result.push_back(choice); + } + } + return result; +} + +SeamCandidate get_seam_candidate( + const Shells::Shell<> &shell, + const Vec2d &starting_position, + const SeamChoiceVisibility &visibility_calculator, + const Params ¶ms, + const std::vector> &precalculated_visibility, + const std::vector &least_visible_points +) { + using Perimeters::Perimeter, Perimeters::AngleType; + + std::vector choice_visibilities(shell.size(), 1.0); + std::vector choices{ + get_shell_seam(shell, [&, previous_position{starting_position}](const Perimeter &perimeter, std::size_t slice_index) mutable { + SeamChoice candidate{Seams::choose_seam_point( + perimeter, Impl::Nearest{previous_position, params.max_detour} + )}; + const bool is_too_far{ + (candidate.position - previous_position).norm() > params.max_detour}; + const LeastVisiblePoint &least_visible{least_visible_points[slice_index]}; + + const bool is_on_edge{ + candidate.previous_index == candidate.next_index && + perimeter.angle_types[candidate.next_index] != AngleType::smooth}; + + if (is_on_edge) { + choice_visibilities[slice_index] = precalculated_visibility[slice_index][candidate.previous_index]; + } else { + choice_visibilities[slice_index] = + visibility_calculator(candidate, perimeter); + } + const bool is_too_visible{ + choice_visibilities[slice_index] > + least_visible.visibility + params.jump_visibility_threshold}; + const bool can_be_on_edge{ + !is_on_edge && + perimeter.angle_types[least_visible.choice.next_index] != AngleType::smooth}; + if (is_too_far || (can_be_on_edge && is_too_visible)) { + candidate = least_visible.choice; + } + previous_position = candidate.position; + return candidate; + })}; + return {std::move(choices), std::move(choice_visibilities)}; +} + +using ShellVertexVisibility = std::vector>; + +std::vector get_shells_vertex_visibility( + const Shells::Shells<> &shells, const SeamChoiceVisibility &visibility_calculator +) { + std::vector result; + + result.reserve(shells.size()); + std::transform( + shells.begin(), shells.end(), std::back_inserter(result), + [](const Shells::Shell<> &shell) { return ShellVertexVisibility(shell.size()); } + ); + + Geometry::iterate_nested(shells, [&](const std::size_t shell_index, const std::size_t slice_index) { + const Shells::Shell<> &shell{shells[shell_index]}; + const Shells::Slice<> &slice{shell[slice_index]}; + const std::vector &positions{slice.boundary.positions}; + + for (std::size_t point_index{0}; point_index < positions.size(); ++point_index) { + result[shell_index][slice_index].emplace_back(visibility_calculator( + SeamChoice{point_index, point_index, positions[point_index]}, slice.boundary + )); + } + }); + return result; +} + +using ShellLeastVisiblePoints = std::vector; + +std::vector get_shells_least_visible_points( + const Shells::Shells<> &shells, + const std::vector &precalculated_visibility +) { + std::vector result; + + result.reserve(shells.size()); + std::transform( + shells.begin(), shells.end(), std::back_inserter(result), + [](const Shells::Shell<> &shell) { return ShellLeastVisiblePoints(shell.size()); } + ); + + Geometry::iterate_nested(shells, [&](const std::size_t shell_index, const std::size_t slice_index) { + const Shells::Shell<> &shell{shells[shell_index]}; + const Shells::Slice<> &slice{shell[slice_index]}; + const SeamChoice least_visibile{ + Seams::choose_seam_point(slice.boundary, Impl::LeastVisible{precalculated_visibility[shell_index][slice_index]})}; + + const double visibility{precalculated_visibility[shell_index][slice_index][least_visibile.previous_index]}; + result[shell_index][slice_index] = LeastVisiblePoint{least_visibile, visibility}; + }); + return result; +} + +using ShellStartingPositions = std::vector; + +std::vector get_shells_starting_positions( + const Shells::Shells<> &shells +) { + std::vector result; + for (const Shells::Shell<> &shell : shells) { + std::vector starting_positions{get_starting_positions(shell)}; + result.push_back(std::move(starting_positions)); + } + return result; +} + +using ShellSeamCandidates = std::vector; + +std::vector get_shells_seam_candidates( + const Shells::Shells<> &shells, + const std::vector &starting_positions, + const SeamChoiceVisibility &visibility_calculator, + const std::vector &precalculated_visibility, + const std::vector &least_visible_points, + const Params ¶ms +) { + std::vector result; + + result.reserve(starting_positions.size()); + std::transform( + starting_positions.begin(), starting_positions.end(), std::back_inserter(result), + [](const ShellStartingPositions &positions) { return ShellSeamCandidates(positions.size()); } + ); + + Geometry::iterate_nested(starting_positions, [&](const std::size_t shell_index, const std::size_t starting_position_index){ + const Shells::Shell<> &shell{shells[shell_index]}; + using Perimeters::Perimeter, Perimeters::AngleType; + + result[shell_index][starting_position_index] = get_seam_candidate( + shell, + starting_positions[shell_index][starting_position_index], + visibility_calculator, + params, + precalculated_visibility[shell_index], + least_visible_points[shell_index] + ); + }); + return result; +} + +std::vector get_shell_seam( + const Shells::Shell<> &shell, + std::vector seam_candidates, + const Perimeters::Perimeter::OptionalPointTree &previous_points, + const Params ¶ms +) { + std::vector seam; + double visibility{std::numeric_limits::infinity()}; + + for (std::size_t i{0}; i < seam_candidates.size(); ++i) { + using Perimeters::Perimeter, Perimeters::AngleType; + + SeamCandidate seam_candidate{seam_candidates[i]}; + const Vec2d first_point{seam_candidate.choices.front().position}; + + std::optional closest_point; + if (previous_points) { + std::size_t closest_point_index{find_closest_point(*previous_points, first_point)}; + Vec2d point; + point.x() = previous_points->coordinate(closest_point_index, 0); + point.y() = previous_points->coordinate(closest_point_index, 1); + closest_point = point; + } + + std::optional previous_distance; + if (closest_point) { + previous_distance = (*closest_point - first_point).norm(); + } + const bool is_near_previous{closest_point && *previous_distance < params.max_detour}; + + double seam_candidate_visibility{ + is_near_previous ? -params.continuity_modifier * + (params.max_detour - *previous_distance) / params.max_detour : + 0.0}; + for (std::size_t slice_index{}; slice_index < shell.size(); ++slice_index) { + seam_candidate_visibility += seam_candidate.visibilities[slice_index]; + } + + if (seam_candidate_visibility < visibility) { + seam = std::move(seam_candidate.choices); + visibility = seam_candidate_visibility; + } + } + + return seam; +} + +std::vector> get_object_seams( + Shells::Shells<> &&shells, + const SeamChoiceVisibility &visibility_calculator, + const Params ¶ms +) { + const std::vector precalculated_visibility{ + get_shells_vertex_visibility(shells, visibility_calculator)}; + + const std::vector least_visible_points{ + get_shells_least_visible_points(shells, precalculated_visibility) + }; + + const std::vector starting_positions{ + get_shells_starting_positions(shells) + }; + + const std::vector seam_candidates{ + get_shells_seam_candidates( + shells, + starting_positions, + visibility_calculator, + precalculated_visibility, + least_visible_points, + params + ) + }; + + std::vector> layer_seams(get_layer_count(shells)); + + for (std::size_t shell_index{0}; shell_index < shells.size(); ++shell_index) { + Shells::Shell<> &shell{shells[shell_index]}; + + if (shell.empty()) { + continue; + } + + const std::size_t layer_index{shell.front().layer_index}; + tcb::span previous_seams{ + layer_index == 0 ? tcb::span{} : layer_seams[layer_index - 1]}; + std::vector previous_seams_positions; + std::transform( + previous_seams.begin(), previous_seams.end(), + std::back_inserter(previous_seams_positions), + [](const SeamPerimeterChoice &seam) { return seam.choice.position; } + ); + + Perimeters::Perimeter::OptionalPointTree previous_seams_positions_tree; + const Perimeters::Perimeter::IndexToCoord index_to_coord{previous_seams_positions}; + if (!previous_seams_positions.empty()) { + previous_seams_positions_tree = + Perimeters::Perimeter::PointTree{index_to_coord, index_to_coord.positions.size()}; + } + + std::vector seam{ + Aligned::get_shell_seam(shell, seam_candidates[shell_index], previous_seams_positions_tree, params)}; + + for (std::size_t perimeter_id{}; perimeter_id < shell.size(); ++perimeter_id) { + const SeamChoice &choice{seam[perimeter_id]}; + Perimeters::Perimeter &perimeter{shell[perimeter_id].boundary}; + layer_seams[shell[perimeter_id].layer_index].emplace_back(choice, std::move(perimeter)); + } + } + return layer_seams; +} + +} // namespace Slic3r::Seams::Aligned diff --git a/src/libslic3r/GCode/SeamAligned.hpp b/src/libslic3r/GCode/SeamAligned.hpp new file mode 100644 index 0000000..dc35eb7 --- /dev/null +++ b/src/libslic3r/GCode/SeamAligned.hpp @@ -0,0 +1,103 @@ +#ifndef libslic3r_SeamAligned_hpp_ +#define libslic3r_SeamAligned_hpp_ + +#include +#include +#include +#include + +#include "libslic3r/GCode/SeamPerimeters.hpp" +#include "libslic3r/GCode/SeamChoice.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/GCode/SeamShells.hpp" + +namespace Slic3r::ModelInfo { + struct Visibility; +} + +namespace Slic3r::Seams::Aligned { + +using SeamChoiceVisibility = std::function; + +namespace Impl { +struct SeamOptions +{ + std::size_t closest; + std::size_t adjacent; + bool adjacent_forward; + std::optional snapped; + Vec2d on_edge; +}; + +SeamChoice pick_seam_option(const Perimeters::Perimeter &perimeter, const SeamOptions &options); + +std::optional snap_to_angle( + const Vec2d &point, + const std::size_t search_start, + const Perimeters::Perimeter &perimeter, + const double max_detour +); + +SeamOptions get_seam_options( + const Perimeters::Perimeter &perimeter, + const Vec2d &prefered_position, + const Perimeters::Perimeter::PointTree &points_tree, + const double max_detour +); + +struct Nearest +{ + Vec2d prefered_position; + double max_detour; + + std::optional operator()( + const Perimeters::Perimeter &perimeter, + const Perimeters::PointType point_type, + const Perimeters::PointClassification point_classification + ) const; +}; + +struct LeastVisible +{ + std::optional operator()( + const Perimeters::Perimeter &perimeter, + const Perimeters::PointType point_type, + const Perimeters::PointClassification point_classification + ) const; + + const std::vector &precalculated_visibility; +}; +} + + +struct VisibilityCalculator +{ + const Slic3r::ModelInfo::Visibility &points_visibility; + double convex_visibility_modifier; + double concave_visibility_modifier; + + double operator()(const SeamChoice &choice, const Perimeters::Perimeter &perimeter) const; + +private: + static double get_angle_visibility_modifier( + const double angle, + const double convex_visibility_modifier, + const double concave_visibility_modifier + ); +}; + +struct Params { + double max_detour{}; + double jump_visibility_threshold{}; + double continuity_modifier{}; +}; + +std::vector> get_object_seams( + Shells::Shells<> &&shells, + const SeamChoiceVisibility& visibility_calculator, + const Params& params +); + +} // namespace Slic3r::Seams::Aligned + +#endif // libslic3r_SeamAligned_hpp_ diff --git a/src/libslic3r/GCode/SeamChoice.cpp b/src/libslic3r/GCode/SeamChoice.cpp new file mode 100644 index 0000000..7ed3474 --- /dev/null +++ b/src/libslic3r/GCode/SeamChoice.cpp @@ -0,0 +1,52 @@ +#include "libslic3r/GCode/SeamChoice.hpp" + +#include + +namespace Slic3r::Seams { +std::optional maybe_choose_seam_point( + const Perimeters::Perimeter &perimeter, const SeamPicker &seam_picker +) { + using Perimeters::PointType; + using Perimeters::PointClassification; + + std::vector + type_search_order{PointType::enforcer, PointType::common, PointType::blocker}; + std::vector classification_search_order{ + PointClassification::embedded, PointClassification::common, PointClassification::overhang}; + for (const PointType point_type : type_search_order) { + for (const PointClassification point_classification : classification_search_order) { + if (std::optional seam_choice{ + seam_picker(perimeter, point_type, point_classification)}) { + return seam_choice; + } + } + if (!Perimeters::extract_points(perimeter, point_type).empty()) { + return std::nullopt; + } + } + + return std::nullopt; +} + +SeamChoice choose_seam_point(const Perimeters::Perimeter &perimeter, const SeamPicker &seam_picker) { + using Perimeters::PointType; + using Perimeters::PointClassification; + + std::optional seam_choice{maybe_choose_seam_point(perimeter, seam_picker)}; + + if (seam_choice) { + return *seam_choice; + } + + // Failed to choose any reasonable point! + return SeamChoice{0, 0, perimeter.positions.front()}; +} + +std::optional choose_degenerate_seam_point(const Perimeters::Perimeter &perimeter) { + if (!perimeter.positions.empty()) { + return SeamChoice{0, 0, perimeter.positions.front()}; + } + return std::nullopt; +} + +} // namespace Slic3r::Seams diff --git a/src/libslic3r/GCode/SeamChoice.hpp b/src/libslic3r/GCode/SeamChoice.hpp new file mode 100644 index 0000000..d31833e --- /dev/null +++ b/src/libslic3r/GCode/SeamChoice.hpp @@ -0,0 +1,66 @@ +#ifndef libslic3r_SeamChoice_hpp_ +#define libslic3r_SeamChoice_hpp_ + +#include +#include +#include +#include + +#include "libslic3r/Polygon.hpp" +#include "libslic3r/GCode/SeamShells.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/GCode/SeamGeometry.hpp" +#include "libslic3r/GCode/SeamPerimeters.hpp" +#include "libslic3r/Point.hpp" + +namespace Slic3r::Seams { + +/** + * When previous_index == next_index, the point is at the point. + * Otherwise the point is at the edge. + */ +struct SeamChoice +{ + std::size_t previous_index{}; + std::size_t next_index{}; + Vec2d position{Vec2d::Zero()}; +}; + +struct SeamPerimeterChoice +{ + SeamPerimeterChoice(const SeamChoice &choice, Perimeters::Perimeter &&perimeter) + : choice(choice) + , perimeter(std::move(perimeter)) + , bounding_box(Polygon{Geometry::scaled(this->perimeter.positions)}.bounding_box()) {} + + SeamChoice choice; + Perimeters::Perimeter perimeter; + BoundingBox bounding_box; +}; + +using SeamPicker = std::function(const Perimeters::Perimeter &, const Perimeters::PointType, const Perimeters::PointClassification)>; + +std::optional maybe_choose_seam_point( + const Perimeters::Perimeter &perimeter, const SeamPicker &seam_picker +); + +/** + * Go throught points on perimeter and choose the best seam point closest to + * the prefered position. + * + * Points in the perimeter can be diveded into 3x3=9 categories. An example category is + * enforced overhanging point. These categories are searched in particualr order. + * For example enforced overhang will be always choosen over common embedded point, etc. + * + * A closest point is choosen from the first non-empty category. + */ +SeamChoice choose_seam_point( + const Perimeters::Perimeter &perimeter, const SeamPicker& seam_picker +); + +std::optional choose_degenerate_seam_point(const Perimeters::Perimeter &perimeter); + +} // namespace Slic3r::Seams + +#endif // libslic3r_SeamChoice_hpp_ diff --git a/src/libslic3r/GCode/SeamGeometry.cpp b/src/libslic3r/GCode/SeamGeometry.cpp new file mode 100644 index 0000000..fbc4d07 --- /dev/null +++ b/src/libslic3r/GCode/SeamGeometry.cpp @@ -0,0 +1,443 @@ +#include "libslic3r/GCode/SeamGeometry.hpp" + +#include +#include +#include +#include +#include + +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/Layer.hpp" +#include "libslic3r/AABBTreeLines.hpp" +#include "libslic3r/ExtrusionEntityCollection.hpp" +#include "libslic3r/Flow.hpp" +#include "libslic3r/LayerRegion.hpp" + +namespace Slic3r::Seams::Geometry { + +namespace MappingImpl { + +/** + * @brief Return 0, 1, ..., size - 1. + */ +std::vector range(std::size_t size) { + std::vector result(size); + std::iota(result.begin(), result.end(), 0); + return result; +} + +/** + * @brief A link between lists. + */ +struct Link +{ + std::size_t bucket_id; + double weight; +}; + +/** + * @brief Get optional values. Replace any nullopt Links with new_bucket_id and increment new_bucket_id. + * + * @param links A list of optional links. + * @param new_bucket_id In-out parameter incremented on each nullopt replacement. + */ +std::vector assign_buckets( + const std::vector> &links, std::size_t &new_bucket_id +) { + std::vector result; + std::transform( + links.begin(), links.end(), std::back_inserter(result), + [&](const std::optional &link) { + if (link) { + return link->bucket_id; + } + return new_bucket_id++; + } + ); + return result; +} +} // namespace MappingImpl + +Vec2d get_normal(const Vec2d &vector) { return Vec2d{vector.y(), -vector.x()}.normalized(); } + +Vec2d get_polygon_normal( + const std::vector &points, const std::size_t index, const double min_arm_length +) { + std::optional previous_index; + std::optional next_index; + + visit_near_forward(index, points.size(), [&](const std::size_t index_candidate) { + if (index == index_candidate) { + return false; + } + const double distance{(points[index_candidate] - points[index]).norm()}; + if (distance > min_arm_length) { + next_index = index_candidate; + return true; + } + return false; + }); + visit_near_backward(index, points.size(), [&](const std::size_t index_candidate) { + const double distance{(points[index_candidate] - points[index]).norm()}; + if (distance > min_arm_length) { + previous_index = index_candidate; + return true; + } + return false; + }); + + if (previous_index && next_index) { + const Vec2d previous_normal{ + Geometry::get_normal(points.at(index) - points.at(*previous_index))}; + const Vec2d next_normal{Geometry::get_normal(points.at(*next_index) - points.at(index))}; + return (previous_normal + next_normal).normalized(); + } + return Vec2d::Zero(); +} + +std::pair distance_to_segment_squared(const Linef &segment, const Vec2d &point) { + Vec2d segment_point; + const double distance{line_alg::distance_to_squared(segment, point, &segment_point)}; + return {segment_point, distance}; +} + +std::pair get_mapping( + const std::vector &list_sizes, const MappingOperator &mapping_operator +) { + using namespace MappingImpl; + + std::vector> result; + result.reserve(list_sizes.size()); + result.push_back(range(list_sizes.front())); + + std::size_t new_bucket_id{result.back().size()}; + + for (std::size_t layer_index{0}; layer_index < list_sizes.size() - 1; ++layer_index) { + // Current layer is already assigned mapping. + + // Links on the next layer to the current layer. + std::vector> links(list_sizes[layer_index + 1]); + + for (std::size_t item_index{0}; item_index < list_sizes[layer_index]; ++item_index) { + const MappingOperatorResult next_item{ + mapping_operator(layer_index, item_index)}; + if (next_item) { + const auto [index, weight] = *next_item; + const Link link{result.back()[item_index], weight}; + if (!links[index] || links[index]->weight < link.weight) { + links[index] = link; + } + } + } + result.push_back(assign_buckets(links, new_bucket_id)); + } + return {result, new_bucket_id}; +} + +Extrusion::Extrusion( + Polygon &&polygon, + BoundingBox bounding_box, + const double width, + const ExPolygon &island_boundary +) + : polygon(polygon) + , bounding_box(std::move(bounding_box)) + , width(width) + , island_boundary(island_boundary) { + this->island_boundary_bounding_boxes.push_back(island_boundary.contour.bounding_box()); + + std::transform( + this->island_boundary.holes.begin(), this->island_boundary.holes.end(), + std::back_inserter(this->island_boundary_bounding_boxes), + [](const Polygon &polygon) { return polygon.bounding_box(); } + ); +} + +Geometry::Extrusions get_external_perimeters(const Slic3r::Layer &layer, const LayerSlice &slice) { + std::vector result; + for (const LayerIsland &island : slice.islands) { + const LayerRegion &layer_region = *layer.get_region(island.perimeters.region()); + for (const uint32_t perimeter_id : island.perimeters) { + const auto collection{static_cast( + layer_region.perimeters().entities[perimeter_id] + )}; + for (const ExtrusionEntity *entity : *collection) { + if (entity->role().is_external_perimeter()) { + Polygon polygon{entity->as_polyline().points}; + const BoundingBox bounding_box{polygon.bounding_box()}; + const double width{layer_region.flow(FlowRole::frExternalPerimeter).width()}; + result.emplace_back(std::move(polygon), bounding_box, width, island.boundary); + } + } + } + } + return result; +} + +std::vector get_extrusions(tcb::span object_layers) { + std::vector result; + result.reserve(object_layers.size()); + + for (const Slic3r::Layer *object_layer : object_layers) { + Extrusions extrusions; + + for (const LayerSlice &slice : object_layer->lslices_ex) { + std::vector external_perimeters{ + get_external_perimeters(*object_layer, slice)}; + for (Geometry::Extrusion &extrusion : external_perimeters) { + extrusions.push_back(std::move(extrusion)); + } + } + + result.push_back(std::move(extrusions)); + } + + return result; +} + +BoundedPolygons project_to_geometry(const Geometry::Extrusions &external_perimeters, const double max_bb_distance) { + BoundedPolygons result; + result.reserve(external_perimeters.size()); + + using std::transform, std::back_inserter; + + transform( + external_perimeters.begin(), external_perimeters.end(), back_inserter(result), + [&](const Geometry::Extrusion &external_perimeter) { + const auto [choosen_index, _]{Geometry::pick_closest_bounding_box( + external_perimeter.bounding_box, + external_perimeter.island_boundary_bounding_boxes + )}; + + const double distance{Geometry::bounding_box_distance( + external_perimeter.island_boundary_bounding_boxes[choosen_index], + external_perimeter.bounding_box + )}; + + if (distance > max_bb_distance) { + Polygons expanded_extrusion{expand(external_perimeter.polygon, Slic3r::scaled(external_perimeter.width / 2.0))}; + if (!expanded_extrusion.empty()) { + return BoundedPolygon{ + expanded_extrusion.front(), expanded_extrusion.front().bounding_box(), external_perimeter.polygon.is_clockwise(), 0.0 + }; + } + } + + const bool is_hole{choosen_index != 0}; + const Polygon &adjacent_boundary{ + !is_hole ? external_perimeter.island_boundary.contour : + external_perimeter.island_boundary.holes[choosen_index - 1]}; + return BoundedPolygon{adjacent_boundary, external_perimeter.island_boundary_bounding_boxes[choosen_index], is_hole, 0.0}; + } + ); + return result; +} + +std::vector project_to_geometry(const std::vector &extrusions, const double max_bb_distance) { + std::vector result(extrusions.size()); + + for (std::size_t layer_index{0}; layer_index < extrusions.size(); ++layer_index) { + result[layer_index] = project_to_geometry(extrusions[layer_index], max_bb_distance); + } + + return result; +} + +std::vector convert_to_geometry(const std::vector &extrusions) { + std::vector result; + result.reserve(extrusions.size()); + + for (const Geometry::Extrusions &layer : extrusions) { + result.emplace_back(); + + using std::transform, std::back_inserter; + transform( + layer.begin(), layer.end(), back_inserter(result.back()), + [&](const Geometry::Extrusion &extrusion) { + return BoundedPolygon{ + extrusion.polygon, extrusion.bounding_box, extrusion.polygon.is_clockwise(), extrusion.width / 2.0 + }; + } + ); + } + + return result; +} + +std::vector oversample_edge(const Vec2d &from, const Vec2d &to, const double max_distance) { + const double total_distance{(from - to).norm()}; + const auto points_count{static_cast(std::ceil(total_distance / max_distance)) + 1}; + if (points_count < 3) { + return {}; + } + const double step_size{total_distance / (points_count - 1)}; + const Vec2d step_vector{step_size * (to - from).normalized()}; + std::vector result; + result.reserve(points_count - 2); + for (std::size_t i{1}; i < points_count - 1; ++i) { + result.push_back(from + i * step_vector); + } + return result; +} + +void visit_near_forward( + const std::size_t start_index, + const std::size_t loop_size, + const std::function &visitor +) { + std::size_t last_index{loop_size - 1}; + std::size_t index{start_index}; + for (unsigned _{0}; _ < 30; ++_) { // Do not visit too far. + if (visitor(index)) { + return; + } + index = index == last_index ? 0 : index + 1; + } +} + +void visit_near_backward( + const std::size_t start_index, + const std::size_t loop_size, + const std::function &visitor +) { + std::size_t last_index{loop_size - 1}; + std::size_t index{start_index == 0 ? loop_size - 1 : start_index - 1}; + for (unsigned _{0}; _ < 30; ++_) { // Do not visit too far. + if (visitor(index)) { + return; + } + index = index == 0 ? last_index : index - 1; + } +} + +std::vector unscaled(const Points &points) { + std::vector result; + result.reserve(points.size()); + using std::transform, std::begin, std::end, std::back_inserter; + transform(begin(points), end(points), back_inserter(result), [](const Point &point) { + return unscaled(point); + }); + return result; +} + +std::vector unscaled(const Lines &lines) { + std::vector result; + result.reserve(lines.size()); + std::transform(lines.begin(), lines.end(), std::back_inserter(result), [](const Line &line) { + return Linef{unscaled(line.a), unscaled(line.b)}; + }); + return result; +} + +Points scaled(const std::vector &points) { + Points result; + for (const Vec2d &point : points) { + result.push_back(Slic3r::scaled(point)); + } + return result; +} + +std::vector get_embedding_distances( + const std::vector &points, const AABBTreeLines::LinesDistancer &perimeter_distancer +) { + std::vector result; + result.reserve(points.size()); + using std::transform, std::back_inserter; + transform(points.begin(), points.end(), back_inserter(result), [&](const Vec2d &point) { + const double distance{perimeter_distancer.distance_from_lines(point)}; + return distance < 0 ? -distance : 0.0; + }); + return result; +} + +std::vector get_overhangs( + const std::vector &points, + const AABBTreeLines::LinesDistancer &previous_layer_perimeter_distancer, + const double layer_height +) { + std::vector result; + result.reserve(points.size()); + using std::transform, std::back_inserter; + transform(points.begin(), points.end(), back_inserter(result), [&](const Vec2d &point) { + const double distance{previous_layer_perimeter_distancer.distance_from_lines(point)}; + return distance > 0 ? M_PI / 2 - std::atan(layer_height / distance) : 0.0; + }); + return result; +} + +// Measured from outside, convex is positive +std::vector get_vertex_angles(const std::vector &points, const double min_arm_length) { + std::vector result; + result.reserve(points.size()); + + for (std::size_t index{0}; index < points.size(); ++index) { + std::optional previous_index; + std::optional next_index; + + visit_near_forward(index, points.size(), [&](const std::size_t index_candidate) { + if (index == index_candidate) { + return false; + } + const double distance{(points[index_candidate] - points[index]).norm()}; + if (distance > min_arm_length) { + next_index = index_candidate; + return true; + } + return false; + }); + visit_near_backward(index, points.size(), [&](const std::size_t index_candidate) { + const double distance{(points[index_candidate] - points[index]).norm()}; + if (distance > min_arm_length) { + previous_index = index_candidate; + return true; + } + return false; + }); + + if (previous_index && next_index) { + const Vec2d &previous_point = points[*previous_index]; + const Vec2d &point = points[index]; + const Vec2d &next_point = points[*next_index]; + result.push_back(-angle((point - previous_point), (next_point - point))); + } else { + result.push_back(0.0); + } + } + + return result; +} + +double bounding_box_distance(const BoundingBox &a, const BoundingBox &b) { + const double bb_max_distance{unscaled(Point{a.max - b.max}).norm()}; + const double bb_min_distance{unscaled(Point{a.min - b.min}).norm()}; + return std::max(bb_max_distance, bb_min_distance); +} + +std::pair pick_closest_bounding_box( + const BoundingBox &to, const BoundingBoxes &choose_from +) { + double min_distance{std::numeric_limits::infinity()}; + std::size_t choosen_index{0}; + + for (std::size_t i{0}; i < choose_from.size(); ++i) { + const BoundingBox &candidate{choose_from[i]}; + const double distance{bounding_box_distance(candidate, to)}; + + if (distance < min_distance) { + choosen_index = i; + min_distance = distance; + } + } + return {choosen_index, min_distance}; +} + +Polygon to_polygon(const ExtrusionLoop &loop) { + Points loop_points{}; + for (const ExtrusionPath &path : loop.paths) { + for (const Point &point : path.polyline.points) { + loop_points.push_back(point); + } + } + return Polygon{loop_points}; +} +} // namespace Slic3r::Seams::Geometry diff --git a/src/libslic3r/GCode/SeamGeometry.hpp b/src/libslic3r/GCode/SeamGeometry.hpp new file mode 100644 index 0000000..c861e3d --- /dev/null +++ b/src/libslic3r/GCode/SeamGeometry.hpp @@ -0,0 +1,197 @@ +#ifndef libslic3r_SeamGeometry_hpp_ +#define libslic3r_SeamGeometry_hpp_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libslic3r/ExtrusionEntity.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/AABBTreeLines.hpp" +#include "libslic3r/Point.hpp" +#include "tcbspan/span.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/Polygon.hpp" + +namespace Slic3r { +class Layer; + +namespace AABBTreeLines { +template class LinesDistancer; +} // namespace AABBTreeLines +} + +namespace Slic3r::Seams::Geometry { + +struct Extrusion +{ + Extrusion( + Polygon &&polygon, + BoundingBox bounding_box, + const double width, + const ExPolygon &island_boundary + ); + + Extrusion(const Extrusion &) = delete; + Extrusion(Extrusion &&) = default; + Extrusion &operator=(const Extrusion &) = delete; + Extrusion &operator=(Extrusion &&) = delete; + + Polygon polygon; + BoundingBox bounding_box; + double width; + const ExPolygon &island_boundary; + + // At index 0 there is the bounding box of contour. Rest are the bounding boxes of holes in order. + BoundingBoxes island_boundary_bounding_boxes; +}; + +using Extrusions = std::vector; + +std::vector get_extrusions(tcb::span object_layers); + +struct BoundedPolygon { + Polygon polygon; + BoundingBox bounding_box; + bool is_hole{false}; + double offset_inside{}; +}; + +using BoundedPolygons = std::vector; + +BoundedPolygons project_to_geometry(const Geometry::Extrusions &external_perimeters, const double max_bb_distance); +std::vector project_to_geometry(const std::vector &extrusions, const double max_bb_distance); +std::vector convert_to_geometry(const std::vector &extrusions); + +Vec2d get_polygon_normal( + const std::vector &points, const std::size_t index, const double min_arm_length +); + +Vec2d get_normal(const Vec2d &vector); + +std::pair distance_to_segment_squared(const Linef &segment, const Vec2d &point); + +using Mapping = std::vector>; +using MappingOperatorResult = std::optional>; +using MappingOperator = std::function; + +/** + * @brief Indirectly map list of lists into buckets. + * + * Look for chains of items accross the lists. + * It may do this mapping: [[1, 2], [3, 4, 5], [6]] -> [[1, 4, 6], [2, 3], [5]]. + * It depends on the weights provided by the mapping operator. + * + * Same bucket cannot be choosen for multiple items in any of the inner lists. + * Bucket is choosen **based on the weight** provided by the mapping operator. Multiple items from + * the same list may want to claim the same bucket. In that case, the item with the biggest weight + * wins the bucket. For example: [[1, 2], [3]] -> [[1, 3], [2]] + * + * @param list_sizes Vector of sizes of the original lists in a list. + * @param mapping_operator Operator that takes layer index and item index on that layer as input + * and returns the best fitting item index from the next layer, along with weight, representing how + * good the fit is. It may return nullopt if there is no good fit. + * + * @return Mapping [outter_list_index][inner_list_index] -> bucket id and the number of buckets. + */ +std::pair get_mapping( + const std::vector &list_sizes, const MappingOperator &mapping_operator +); + +std::vector oversample_edge(const Vec2d &from, const Vec2d &to, const double max_distance); + +template +std::size_t get_flat_size(const NestedVector &nested_vector) { + return std::accumulate( + nested_vector.begin(), nested_vector.end(), std::size_t{0}, + [](const std::size_t sum, const auto &vector) { return sum + vector.size(); } + ); +} + +template +std::vector> get_flat_index2indices_table( + const NestedVector &nested_vector +) { + std::vector> result; + for (std::size_t parent_index{0}; parent_index < nested_vector.size(); ++parent_index) { + const auto &vector{nested_vector[parent_index]}; + for (std::size_t nested_index{0}; nested_index < vector.size(); ++nested_index) { + result.push_back({parent_index, nested_index}); + } + } + return result; +} + +template +void iterate_nested(const NestedVector &nested_vector, const std::function &function) { + std::size_t flat_size{Geometry::get_flat_size(nested_vector)}; + using Range = tbb::blocked_range; + const Range range{0, flat_size}; + + std::vector> index_table{ + get_flat_index2indices_table(nested_vector)}; + + // Iterate the shells as if it was flat. + tbb::parallel_for(range, [&](Range range) { + for (std::size_t index{range.begin()}; index < range.end(); ++index) { + const auto[parent_index, nested_index]{index_table[index]}; + function(parent_index, nested_index); + } + }); +} + +void visit_near_forward( + const std::size_t start_index, + const std::size_t loop_size, + const std::function &visitor +); +void visit_near_backward( + const std::size_t start_index, + const std::size_t loop_size, + const std::function &visitor +); + +std::vector unscaled(const Points &points); + +std::vector unscaled(const Lines &lines); + +Points scaled(const std::vector &points); + +std::vector get_embedding_distances( + const std::vector &points, const AABBTreeLines::LinesDistancer &perimeter_distancer +); + +/** + * @brief Calculate overhang angle for each of the points over the previous layer perimeters. + * + * Larger angle <=> larger overhang. E.g. floating box has overhang = PI / 2. + * + * @returns Angles in radians <0, PI / 2>. + */ +std::vector get_overhangs( + const std::vector &points, + const AABBTreeLines::LinesDistancer &previous_layer_perimeter_distancer, + const double layer_height +); + +// Measured from outside, convex is positive +std::vector get_vertex_angles(const std::vector &points, const double min_arm_length); + +double bounding_box_distance(const BoundingBox &a, const BoundingBox &b); + +std::pair pick_closest_bounding_box( + const BoundingBox &to, const BoundingBoxes &choose_from +); + +Polygon to_polygon(const ExtrusionLoop &loop); + +} // namespace Slic3r::Seams::Geometry + +#endif // libslic3r_SeamGeometry_hpp_ diff --git a/src/libslic3r/GCode/SeamPainting.cpp b/src/libslic3r/GCode/SeamPainting.cpp new file mode 100644 index 0000000..d745316 --- /dev/null +++ b/src/libslic3r/GCode/SeamPainting.cpp @@ -0,0 +1,51 @@ +#include "libslic3r/GCode/SeamPainting.hpp" + +#include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/TriangleSelector.hpp" + +namespace Slic3r::Seams::ModelInfo { +Painting::Painting(const Transform3d &obj_transform, const ModelVolumePtrs &volumes) { + for (const ModelVolume *mv : volumes) { + if (mv->is_seam_painted()) { + auto model_transformation = obj_transform * mv->get_matrix(); + + indexed_triangle_set enforcers = mv->seam_facets + .get_facets(*mv, TriangleStateType::ENFORCER); + its_transform(enforcers, model_transformation); + its_merge(this->enforcers, enforcers); + + indexed_triangle_set blockers = mv->seam_facets + .get_facets(*mv, TriangleStateType::BLOCKER); + its_transform(blockers, model_transformation); + its_merge(this->blockers, blockers); + } + } + + this->enforcers_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set( + this->enforcers.vertices, this->enforcers.indices + ); + this->blockers_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set( + this->blockers.vertices, this->blockers.indices + ); +} + +bool Painting::is_enforced(const Vec3f &position, float radius) const { + if (enforcers.empty()) { + return false; + } + float radius_sqr = radius * radius; + return AABBTreeIndirect::is_any_triangle_in_radius( + enforcers.vertices, enforcers.indices, enforcers_tree, position, radius_sqr + ); +} + +bool Painting::is_blocked(const Vec3f &position, float radius) const { + if (blockers.empty()) { + return false; + } + float radius_sqr = radius * radius; + return AABBTreeIndirect::is_any_triangle_in_radius( + blockers.vertices, blockers.indices, blockers_tree, position, radius_sqr + ); +} +} // namespace Slic3r::Seams::ModelInfo diff --git a/src/libslic3r/GCode/SeamPainting.hpp b/src/libslic3r/GCode/SeamPainting.hpp new file mode 100644 index 0000000..ccc6c52 --- /dev/null +++ b/src/libslic3r/GCode/SeamPainting.hpp @@ -0,0 +1,25 @@ +#ifndef libslic3r_GlobalModelInfo_hpp_ +#define libslic3r_GlobalModelInfo_hpp_ + +#include "libslic3r/AABBTreeIndirect.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Model.hpp" +#include "admesh/stl.h" + +namespace Slic3r::Seams::ModelInfo { +class Painting +{ +public: + Painting(const Transform3d &obj_transform, const ModelVolumePtrs &volumes); + + bool is_enforced(const Vec3f &position, float radius) const; + bool is_blocked(const Vec3f &position, float radius) const; + +private: + indexed_triangle_set enforcers; + indexed_triangle_set blockers; + AABBTreeIndirect::Tree<3, float> enforcers_tree; + AABBTreeIndirect::Tree<3, float> blockers_tree; +}; +} // namespace Slic3r::Seams::ModelInfo +#endif // libslic3r_GlobalModelInfo_hpp_ diff --git a/src/libslic3r/GCode/SeamPerimeters.cpp b/src/libslic3r/GCode/SeamPerimeters.cpp new file mode 100644 index 0000000..d8879f9 --- /dev/null +++ b/src/libslic3r/GCode/SeamPerimeters.cpp @@ -0,0 +1,430 @@ +#include +#include +#include +#include +#include +#include + +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/Layer.hpp" +#include "libslic3r/GCode/SeamGeometry.hpp" +#include "libslic3r/GCode/SeamPerimeters.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/GCode/SeamPainting.hpp" +#include "libslic3r/MultiPoint.hpp" +#include "tcbspan/span.hpp" + +namespace Slic3r::Seams::Perimeters::Impl { + +std::vector oversample_painted( + const std::vector &points, + const std::function &is_painted, + const double slice_z, + const double max_distance +) { + std::vector result; + + for (std::size_t index{0}; index < points.size(); ++index) { + const Vec2d &point{points[index]}; + + result.push_back(point); + + const std::size_t next_index{index == points.size() - 1 ? 0 : index + 1}; + const Vec2d &next_point{points[next_index]}; + const float next_point_distance{static_cast((point - next_point).norm())}; + const Vec2d middle_point{(point + next_point) / 2.0}; + Vec3f point3d{to_3d(middle_point, slice_z).cast()}; + if (is_painted(point3d, next_point_distance / 2.0)) { + for (const Vec2d &edge_point : + Geometry::oversample_edge(point, next_point, max_distance)) { + result.push_back(edge_point); + } + } + } + return result; +} + +std::pair, std::vector> remove_redundant_points( + const std::vector &points, + const std::vector &point_types, + const double tolerance +) { + std::vector points_result; + std::vector point_types_result; + + auto range_start{points.begin()}; + + for (auto iterator{points.begin()}; iterator != points.end(); ++iterator) { + const std::int64_t index{std::distance(points.begin(), iterator)}; + if (next(iterator) == points.end() || point_types[index] != point_types[index + 1]) { + std::vector simplification_result; + douglas_peucker( + range_start, next(iterator), std::back_inserter(simplification_result), tolerance, + [](const Vec2d &point) { return point; } + ); + + points_result.insert( + points_result.end(), simplification_result.begin(), simplification_result.end() + ); + const std::vector + point_types_to_add(simplification_result.size(), point_types[index]); + point_types_result.insert( + point_types_result.end(), point_types_to_add.begin(), point_types_to_add.end() + ); + + range_start = next(iterator); + } + } + + return {points_result, point_types_result}; +} + +std::vector get_point_types( + const std::vector &positions, + const ModelInfo::Painting &painting, + const double slice_z, + const double painting_radius +) { + std::vector result; + result.reserve(positions.size()); + using std::transform, std::back_inserter; + transform( + positions.begin(), positions.end(), back_inserter(result), + [&](const Vec2d &point) { + const Vec3f point3d{to_3d(point.cast(), static_cast(slice_z))}; + if (painting.is_blocked(point3d, painting_radius)) { + return PointType::blocker; + } + if (painting.is_enforced(point3d, painting_radius)) { + return PointType::enforcer; + } + return PointType::common; + } + ); + return result; +} + +std::vector classify_points( + const std::vector &embeddings, + const std::optional> &overhangs, + const double overhang_threshold, + const double embedding_threshold +) { + std::vector result; + result.reserve(embeddings.size()); + using std::transform, std::back_inserter; + transform( + embeddings.begin(), embeddings.end(), back_inserter(result), + [&, i = 0](const double embedding) mutable { + const unsigned index = i++; + if (overhangs && overhangs->operator[](index) > overhang_threshold) { + return PointClassification::overhang; + } + if (embedding > embedding_threshold) { + return PointClassification::embedded; + } + return PointClassification::common; + } + ); + return result; +} + +std::vector get_angle_types( + const std::vector &angles, const double convex_threshold, const double concave_threshold +) { + std::vector result; + using std::transform, std::back_inserter; + transform(angles.begin(), angles.end(), back_inserter(result), [&](const double angle) { + if (angle > convex_threshold) { + return AngleType::convex; + } + if (angle < -concave_threshold) { + return AngleType::concave; + } + return AngleType::smooth; + }); + return result; +} + +std::vector merge_angle_types( + const std::vector &angle_types, + const std::vector &smooth_angle_types, + const std::vector &points, + const double min_arm_length +) { + std::vector result; + result.reserve(angle_types.size()); + for (std::size_t index{0}; index < angle_types.size(); ++index) { + const AngleType &angle_type{angle_types[index]}; + const AngleType &smooth_angle_type{smooth_angle_types[index]}; + + AngleType resulting_type{angle_type}; + + if (smooth_angle_type != angle_type && smooth_angle_type != AngleType::smooth) { + resulting_type = smooth_angle_type; + + // Check if there is a sharp angle in the vicinity. If so, do not use the smooth angle. + Geometry::visit_near_forward(index, angle_types.size(), [&](const std::size_t forward_index) { + const double distance{(points[forward_index] - points[index]).norm()}; + if (distance > min_arm_length) { + return true; + } + if (angle_types[forward_index] == smooth_angle_type) { + resulting_type = angle_type; + } + return false; + }); + Geometry::visit_near_backward(index, angle_types.size(), [&](const std::size_t backward_index) { + const double distance{(points[backward_index] - points[index]).norm()}; + if (distance > min_arm_length) { + return true; + } + if (angle_types[backward_index] == smooth_angle_type) { + resulting_type = angle_type; + } + return false; + }); + } + result.push_back(resulting_type); + } + return result; +} + +} // namespace Slic3r::Seams::Perimeters::Impl + +namespace Slic3r::Seams::Perimeters { + +LayerInfos get_layer_infos( + tcb::span object_layers, const double elephant_foot_compensation +) { + LayerInfos result(object_layers.size()); + + using Range = tbb::blocked_range; + const Range range{0, object_layers.size()}; + tbb::parallel_for(range, [&](Range range) { + for (std::size_t layer_index{range.begin()}; layer_index < range.end(); ++layer_index) { + result[layer_index] = LayerInfo::create( + *object_layers[layer_index], layer_index, elephant_foot_compensation + ); + } + }); + return result; +} + +LayerInfo LayerInfo::create( + const Slic3r::Layer &object_layer, + const std::size_t index, + const double elephant_foot_compensation +) { + AABBTreeLines::LinesDistancer perimeter_distancer{ + to_unscaled_linesf({object_layer.lslices})}; + + using PreviousLayerDistancer = std::optional>; + PreviousLayerDistancer previous_layer_perimeter_distancer; + if (object_layer.lower_layer != nullptr) { + previous_layer_perimeter_distancer = PreviousLayerDistancer{ + to_unscaled_linesf(object_layer.lower_layer->lslices)}; + } + + return { + std::move(perimeter_distancer), + std::move(previous_layer_perimeter_distancer), + index, + object_layer.height, + object_layer.slice_z, + index == 0 ? elephant_foot_compensation : 0.0}; +} + +double Perimeter::IndexToCoord::operator()(const size_t index, size_t dim) const { + return positions[index][dim]; +} + +Perimeter::PointTrees get_kd_trees( + const PointType point_type, + const std::vector &all_point_types, + const std::vector &point_classifications, + const Perimeter::IndexToCoord &index_to_coord +) { + std::vector overhang_indexes; + std::vector embedded_indexes; + std::vector common_indexes; + for (std::size_t i{0}; i < all_point_types.size(); ++i) { + if (all_point_types[i] == point_type) { + switch (point_classifications[i]) { + case PointClassification::overhang: overhang_indexes.push_back(i); break; + case PointClassification::embedded: embedded_indexes.push_back(i); break; + case PointClassification::common: common_indexes.push_back(i); break; + } + } + } + Perimeter::PointTrees trees; + if (!overhang_indexes.empty()) { + trees.overhanging_points = Perimeter::PointTree{index_to_coord}; + trees.overhanging_points->build(overhang_indexes); + } + if (!embedded_indexes.empty()) { + trees.embedded_points = Perimeter::PointTree{index_to_coord}; + trees.embedded_points->build(embedded_indexes); + } + if (!common_indexes.empty()) { + trees.common_points = Perimeter::PointTree{index_to_coord}; + trees.common_points->build(common_indexes); + } + return trees; +} + +Perimeter::Perimeter( + const double slice_z, + const std::size_t layer_index, + const bool is_hole, + std::vector &&positions, + std::vector &&angles, + std::vector &&point_types, + std::vector &&point_classifications, + std::vector &&angle_types +) + : slice_z(slice_z) + , layer_index(layer_index) + , is_hole(is_hole) + , positions(std::move(positions)) + , angles(std::move(angles)) + , index_to_coord(IndexToCoord{tcb::span{this->positions}}) + , point_types(std::move(point_types)) + , point_classifications(std::move(point_classifications)) + , angle_types(std::move(angle_types)) + , enforced_points(get_kd_trees( + PointType::enforcer, this->point_types, this->point_classifications, this->index_to_coord + )) + , common_points(get_kd_trees( + PointType::common, this->point_types, this->point_classifications, this->index_to_coord + )) + , blocked_points(get_kd_trees( + PointType::blocker, this->point_types, this->point_classifications, this->index_to_coord + )) {} + +Perimeter Perimeter::create_degenerate( + std::vector &&points, const double slice_z, const std::size_t layer_index +) { + std::vector point_types(points.size(), PointType::common); + std::vector + point_classifications(points.size(), PointClassification::common); + std::vector angles(points.size()); + std::vector angle_types(points.size(), AngleType::smooth); + Perimeter perimeter{ + slice_z, + layer_index, + false, + std::move(points), + std::move(angles), + std::move(point_types), + std::move(point_classifications), + std::move(angle_types)}; + perimeter.is_degenerate = true; + return perimeter; +} + +Perimeter Perimeter::create( + const Polygon &polygon, + const ModelInfo::Painting &painting, + const LayerInfo &layer_info, + const PerimeterParams ¶ms, + const double offset_inside +) { + if (polygon.size() < 3) { + return Perimeter::create_degenerate( + Geometry::unscaled(polygon.points), layer_info.slice_z, layer_info.index + ); + } + std::vector points; + if (layer_info.elephant_foot_compensation > 0) { + const Polygons expanded{expand(polygon, scaled(layer_info.elephant_foot_compensation))}; + if (expanded.empty()) { + points = Geometry::unscaled(polygon.points); + } else { + points = Geometry::unscaled(expanded.front().points); + } + } else { + points = Geometry::unscaled(polygon.points); + } + + auto is_painted{[&](const Vec3f &point, const double radius) { + return painting.is_enforced(point, radius) || painting.is_blocked(point, radius); + }}; + + std::vector perimeter_points{ + Impl::oversample_painted(points, is_painted, layer_info.slice_z, params.oversampling_max_distance)}; + + std::vector point_types{ + Impl::get_point_types(perimeter_points, painting, layer_info.slice_z, offset_inside > 0 ? offset_inside * 2 : params.painting_radius)}; + + // Geometry converted from extrusions has non zero offset_inside. + // Do not remomve redundant points for extrusions, becouse the redundant + // points can be on overhangs. + if (offset_inside < std::numeric_limits::epsilon()) { + // The following is optimization with significant impact. If in doubt, run + // the "Seam benchmarks" test case in fff_print_tests. + std::tie(perimeter_points, point_types) = + Impl::remove_redundant_points(perimeter_points, point_types, params.simplification_epsilon); + } + + const std::vector embeddings{ + Geometry::get_embedding_distances(perimeter_points, layer_info.distancer)}; + std::optional> overhangs; + if (layer_info.previous_distancer) { + overhangs = Geometry::get_overhangs( + perimeter_points, *layer_info.previous_distancer, layer_info.height + ); + } + std::vector point_classifications{ + Impl::classify_points(embeddings, overhangs, params.overhang_threshold, params.embedding_threshold)}; + + std::vector smooth_angles{Geometry::get_vertex_angles(perimeter_points, params.smooth_angle_arm_length)}; + std::vector angles{Geometry::get_vertex_angles(perimeter_points, params.sharp_angle_arm_length)}; + std::vector angle_types{ + Impl::get_angle_types(angles, params.convex_threshold, params.concave_threshold)}; + std::vector smooth_angle_types{ + Impl::get_angle_types(smooth_angles, params.convex_threshold, params.concave_threshold)}; + angle_types = Impl::merge_angle_types(angle_types, smooth_angle_types, perimeter_points, params.smooth_angle_arm_length); + + const bool is_hole{polygon.is_clockwise()}; + + return Perimeter{ + layer_info.slice_z, + layer_info.index, + is_hole, + std::move(perimeter_points), + std::move(angles), + std::move(point_types), + std::move(point_classifications), + std::move(angle_types)}; +} + +LayerPerimeters create_perimeters( + const std::vector &polygons, + const std::vector &layer_infos, + const ModelInfo::Painting &painting, + const PerimeterParams ¶ms +) { + LayerPerimeters result; + result.reserve(polygons.size()); + + std::transform( + polygons.begin(), polygons.end(), std::back_inserter(result), + [](const Geometry::BoundedPolygons &layer) { return BoundedPerimeters(layer.size()); } + ); + + Geometry::iterate_nested( + polygons, + [&](const std::size_t layer_index, const std::size_t polygon_index) { + const Geometry::BoundedPolygons &layer{polygons[layer_index]}; + const Geometry::BoundedPolygon &bounded_polygon{layer[polygon_index]}; + const LayerInfo &layer_info{layer_infos[layer_index]}; + result[layer_index][polygon_index] = BoundedPerimeter{ + Perimeter::create(bounded_polygon.polygon, painting, layer_info, params, bounded_polygon.offset_inside), + bounded_polygon.bounding_box}; + } + ); + return result; +} + +} // namespace Slic3r::Seams::Perimeter diff --git a/src/libslic3r/GCode/SeamPerimeters.hpp b/src/libslic3r/GCode/SeamPerimeters.hpp new file mode 100644 index 0000000..54660f6 --- /dev/null +++ b/src/libslic3r/GCode/SeamPerimeters.hpp @@ -0,0 +1,209 @@ +#ifndef libslic3r_SeamPerimeters_hpp_ +#define libslic3r_SeamPerimeters_hpp_ + +#include +#include +#include +#include +#include +#include +#include + +#include "libslic3r/GCode/SeamPainting.hpp" +#include "libslic3r/KDTreeIndirect.hpp" +#include "libslic3r/AABBTreeLines.hpp" +#include "libslic3r/GCode/SeamGeometry.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" + +namespace Slic3r { + class Layer; +} + +namespace Slic3r::Seams::ModelInfo { +class Painting; +} + +namespace Slic3r::Seams::Perimeters { +enum class AngleType; +enum class PointType; +enum class PointClassification; +struct Perimeter; +struct PerimeterParams; + +struct LayerInfo +{ + static LayerInfo create( + const Slic3r::Layer &object_layer, std::size_t index, const double elephant_foot_compensation + ); + + AABBTreeLines::LinesDistancer distancer; + std::optional> previous_distancer; + std::size_t index; + double height{}; + double slice_z{}; + double elephant_foot_compensation; +}; + +using LayerInfos = std::vector; + +/** + * @brief Construct LayerInfo for each of the provided layers. + */ +LayerInfos get_layer_infos( + tcb::span object_layers, const double elephant_foot_compensation +); +} // namespace Slic3r::Seams::Perimeters + +namespace Slic3r::Seams::Perimeters::Impl { + + +/** + * @brief Split edges between points into multiple points if there is a painted point anywhere on + * the edge. + * + * The edge will be split by points no more than max_distance apart. + * Smaller max_distance -> more points. + * + * @return All the points (original and added) in order along the edges. + */ +std::vector oversample_painted( + const std::vector &points, + const std::function &is_painted, + const double slice_z, + const double max_distance +); + +/** + * @brief Call Duglas-Peucker for consecutive points of the same type. + * + * It never removes the first point and last point. + * + * @param tolerance Douglas-Peucker epsilon. + */ +std::pair, std::vector> remove_redundant_points( + const std::vector &points, + const std::vector &point_types, + const double tolerance +); + +} // namespace Slic3r::Seams::Perimeters::Impl + +namespace Slic3r::Seams::Perimeters { + +enum class AngleType { convex, concave, smooth }; + +enum class PointType { enforcer, blocker, common }; + +enum class PointClassification { overhang, embedded, common }; + +struct PerimeterParams +{ + double elephant_foot_compensation{}; + double oversampling_max_distance{}; + double embedding_threshold{}; + double overhang_threshold{}; + double convex_threshold{}; + double concave_threshold{}; + double painting_radius{}; + double simplification_epsilon{}; + double smooth_angle_arm_length{}; + double sharp_angle_arm_length{}; +}; + +struct Perimeter +{ + struct IndexToCoord + { + IndexToCoord(const tcb::span positions): positions(positions) {} + IndexToCoord() = default; + double operator()(const size_t index, size_t dim) const; + + tcb::span positions; + }; + + using PointTree = KDTreeIndirect<2, double, IndexToCoord>; + using OptionalPointTree = std::optional; + + struct PointTrees + { + OptionalPointTree embedded_points; + OptionalPointTree common_points; + OptionalPointTree overhanging_points; + }; + + Perimeter() = default; + + Perimeter( + const double slice_z, + const std::size_t layer_index, + const bool is_hole, + std::vector &&positions, + std::vector &&angles, + std::vector &&point_types, + std::vector &&point_classifications, + std::vector &&angle_types + ); + + static Perimeter create( + const Polygon &polygon, + const ModelInfo::Painting &painting, + const LayerInfo &layer_info, + const PerimeterParams ¶ms, + const double offset_inside + ); + + static Perimeter create_degenerate( + std::vector &&points, const double slice_z, const std::size_t layer_index + ); + + bool is_degenerate{false}; + double slice_z{}; + bool is_hole{false}; + std::size_t layer_index{}; + std::vector positions{}; + std::vector angles{}; + IndexToCoord index_to_coord{}; + std::vector point_types{}; + std::vector point_classifications{}; + std::vector angle_types{}; + + PointTrees enforced_points{}; + PointTrees common_points{}; + PointTrees blocked_points{}; +}; + +using Perimeters = std::vector; + +struct BoundedPerimeter { + Perimeter perimeter; + BoundingBox bounding_box; +}; + +using BoundedPerimeters = std::vector; +using LayerPerimeters = std::vector; + +LayerPerimeters create_perimeters( + const std::vector &polygons, + const std::vector &layer_infos, + const ModelInfo::Painting &painting, + const PerimeterParams ¶ms +); + +inline std::vector extract_points( + const Perimeter &perimeter, const PointType point_type +) { + std::vector result; + for (std::size_t i{0}; i < perimeter.positions.size(); ++i) { + if (perimeter.point_types[i] == point_type) { + result.push_back(perimeter.positions[i]); + } + } + return result; +} + +} // namespace Slic3r::Seams::Perimeters + +#endif // libslic3r_SeamPerimeters_hpp_ diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index b6c3d4b..5a65ac7 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -1,1612 +1,393 @@ +#include +#include + #include "SeamPlacer.hpp" -#include "Color.hpp" -#include "Polygon.hpp" -#include "PrintConfig.hpp" -#include "tbb/parallel_for.h" -#include "tbb/blocked_range.h" -#include "tbb/parallel_reduce.h" -#include -#include -#include -#include +#include "libslic3r/GCode/SeamShells.hpp" +#include "libslic3r/GCode/SeamAligned.hpp" +#include "libslic3r/GCode/SeamRear.hpp" +#include "libslic3r/GCode/SeamRandom.hpp" +#include "libslic3r/GCode/ModelVisibility.hpp" +#include "libslic3r/GCode/SeamGeometry.hpp" -#include "libslic3r/AABBTreeLines.hpp" -#include "libslic3r/KDTreeIndirect.hpp" -#include "libslic3r/ExtrusionEntity.hpp" -#include "libslic3r/Print.hpp" -#include "libslic3r/BoundingBox.hpp" -#include "libslic3r/ClipperUtils.hpp" -#include "libslic3r/Layer.hpp" +namespace Slic3r::Seams { -#include "libslic3r/Geometry/Curves.hpp" -#include "libslic3r/ShortEdgeCollapse.hpp" -#include "libslic3r/TriangleSetSampling.hpp" +using ObjectPainting = std::map; -#include "libslic3r/Utils.hpp" +ObjectLayerPerimeters get_perimeters( + SpanOfConstPtrs objects, + const Params ¶ms, + const ObjectPainting& object_painting, + const std::function &throw_if_canceled +) { + ObjectLayerPerimeters result; -//#define DEBUG_FILES + for (const PrintObject *print_object : objects) { + const ModelInfo::Painting &painting{object_painting.at(print_object)}; + throw_if_canceled(); -#ifdef DEBUG_FILES -#include -#include -#endif - -namespace Slic3r { - -namespace SeamPlacerImpl { - -template int sgn(T val) { - return int(T(0) < val) - int(val < T(0)); -} - -// base function: ((e^(((1)/(x^(2)+1)))-1)/(e-1)) -// checkout e.g. here: https://www.geogebra.org/calculator -float gauss(float value, float mean_x_coord, float mean_value, float falloff_speed) { - float shifted = value - mean_x_coord; - float denominator = falloff_speed * shifted * shifted + 1.0f; - float exponent = 1.0f / denominator; - return mean_value * (std::exp(exponent) - 1.0f) / (std::exp(1.0f) - 1.0f); -} - -float compute_angle_penalty(float ccw_angle) { - // This function is used: - // ((ℯ^(((1)/(x^(2)*3+1)))-1)/(ℯ-1))*1+((1)/(2+ℯ^(-x))) - // looks scary, but it is gaussian combined with sigmoid, - // so that concave points have much smaller penalty over convex ones - // https://github.com/qidi3d/QIDISlicer/tree/master/doc/seam_placement/corner_penalty_function.png - return gauss(ccw_angle, 0.0f, 1.0f, 3.0f) + - 1.0f / (2 + std::exp(-ccw_angle)); -} - -/// Coordinate frame -class Frame { -public: - Frame() { - mX = Vec3f(1, 0, 0); - mY = Vec3f(0, 1, 0); - mZ = Vec3f(0, 0, 1); - } - - Frame(const Vec3f &x, const Vec3f &y, const Vec3f &z) : - mX(x), mY(y), mZ(z) { - } - - void set_from_z(const Vec3f &z) { - mZ = z.normalized(); - Vec3f tmpZ = mZ; - Vec3f tmpX = (std::abs(tmpZ.x()) > 0.99f) ? Vec3f(0, 1, 0) : Vec3f(1, 0, 0); - mY = (tmpZ.cross(tmpX)).normalized(); - mX = mY.cross(tmpZ); - } - - Vec3f to_world(const Vec3f &a) const { - return a.x() * mX + a.y() * mY + a.z() * mZ; - } - - Vec3f to_local(const Vec3f &a) const { - return Vec3f(mX.dot(a), mY.dot(a), mZ.dot(a)); - } - - const Vec3f& binormal() const { - return mX; - } - - const Vec3f& tangent() const { - return mY; - } - - const Vec3f& normal() const { - return mZ; - } - -private: - Vec3f mX, mY, mZ; -}; - -Vec3f sample_sphere_uniform(const Vec2f &samples) { - float term1 = 2.0f * float(PI) * samples.x(); - float term2 = 2.0f * sqrt(samples.y() - samples.y() * samples.y()); - return {cos(term1) * term2, sin(term1) * term2, - 1.0f - 2.0f * samples.y()}; -} - -Vec3f sample_hemisphere_uniform(const Vec2f &samples) { - float term1 = 2.0f * float(PI) * samples.x(); - float term2 = 2.0f * sqrt(samples.y() - samples.y() * samples.y()); - return {cos(term1) * term2, sin(term1) * term2, - abs(1.0f - 2.0f * samples.y())}; -} - -Vec3f sample_power_cosine_hemisphere(const Vec2f &samples, float power) { - float term1 = 2.f * float(PI) * samples.x(); - float term2 = pow(samples.y(), 1.f / (power + 1.f)); - float term3 = sqrt(1.f - term2 * term2); - - return Vec3f(cos(term1) * term3, sin(term1) * term3, term2); -} - -std::vector raycast_visibility(const AABBTreeIndirect::Tree<3, float> &raycasting_tree, - const indexed_triangle_set &triangles, - const TriangleSetSamples &samples, - size_t negative_volumes_start_index) { - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: raycast visibility of " << samples.positions.size() << " samples over " << triangles.indices.size() - << " triangles: end"; - - //prepare uniform samples of a hemisphere - float step_size = 1.0f / SeamPlacer::sqr_rays_per_sample_point; - std::vector precomputed_sample_directions( - SeamPlacer::sqr_rays_per_sample_point * SeamPlacer::sqr_rays_per_sample_point); - for (size_t x_idx = 0; x_idx < SeamPlacer::sqr_rays_per_sample_point; ++x_idx) { - float sample_x = x_idx * step_size + step_size / 2.0; - for (size_t y_idx = 0; y_idx < SeamPlacer::sqr_rays_per_sample_point; ++y_idx) { - size_t dir_index = x_idx * SeamPlacer::sqr_rays_per_sample_point + y_idx; - float sample_y = y_idx * step_size + step_size / 2.0; - precomputed_sample_directions[dir_index] = sample_hemisphere_uniform( { sample_x, sample_y }); - } - } - - bool model_contains_negative_parts = negative_volumes_start_index < triangles.indices.size(); - - std::vector result(samples.positions.size()); - tbb::parallel_for(tbb::blocked_range(0, result.size()), - [&triangles, &precomputed_sample_directions, model_contains_negative_parts, negative_volumes_start_index, - &raycasting_tree, &result, &samples](tbb::blocked_range r) { - // Maintaining hits memory outside of the loop, so it does not have to be reallocated for each query. - std::vector hits; - for (size_t s_idx = r.begin(); s_idx < r.end(); ++s_idx) { - result[s_idx] = 1.0f; - constexpr float decrease_step = 1.0f - / (SeamPlacer::sqr_rays_per_sample_point * SeamPlacer::sqr_rays_per_sample_point); - - const Vec3f ¢er = samples.positions[s_idx]; - const Vec3f &normal = samples.normals[s_idx]; - // apply the local direction via Frame struct - the local_dir is with respect to +Z being forward - Frame f; - f.set_from_z(normal); - - for (const auto &dir : precomputed_sample_directions) { - Vec3f final_ray_dir = (f.to_world(dir)); - if (!model_contains_negative_parts) { - igl::Hit hitpoint; - // FIXME: This AABBTTreeIndirect query will not compile for float ray origin and - // direction. - Vec3d final_ray_dir_d = final_ray_dir.cast(); - Vec3d ray_origin_d = (center + normal * 0.01f).cast(); // start above surface. - bool hit = AABBTreeIndirect::intersect_ray_first_hit(triangles.vertices, - triangles.indices, raycasting_tree, ray_origin_d, final_ray_dir_d, hitpoint); - if (hit && its_face_normal(triangles, hitpoint.id).dot(final_ray_dir) <= 0) { - result[s_idx] -= decrease_step; - } - } else { //TODO improve logic for order based boolean operations - consider order of volumes - bool casting_from_negative_volume = samples.triangle_indices[s_idx] - >= negative_volumes_start_index; - - Vec3d ray_origin_d = (center + normal * 0.01f).cast(); // start above surface. - if (casting_from_negative_volume) { // if casting from negative volume face, invert direction, change start pos - final_ray_dir = -1.0 * final_ray_dir; - ray_origin_d = (center - normal * 0.01f).cast(); - } - Vec3d final_ray_dir_d = final_ray_dir.cast(); - bool some_hit = AABBTreeIndirect::intersect_ray_all_hits(triangles.vertices, - triangles.indices, raycasting_tree, - ray_origin_d, final_ray_dir_d, hits); - if (some_hit) { - int counter = 0; - // NOTE: iterating in reverse, from the last hit for one simple reason: We know the state of the ray at that point; - // It cannot be inside model, and it cannot be inside negative volume - for (int hit_index = int(hits.size()) - 1; hit_index >= 0; --hit_index) { - Vec3f face_normal = its_face_normal(triangles, hits[hit_index].id); - if (hits[hit_index].id >= int(negative_volumes_start_index)) { //negative volume hit - counter -= sgn(face_normal.dot(final_ray_dir)); // if volume face aligns with ray dir, we are leaving negative space - // which in reverse hit analysis means, that we are entering negative space :) and vice versa - } else { - counter += sgn(face_normal.dot(final_ray_dir)); - } - } - if (counter == 0) { - result[s_idx] -= decrease_step; - } - } - } - } - } - }); - - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: raycast visibility of " << samples.positions.size() << " samples over " << triangles.indices.size() - << " triangles: end"; - - return result; -} - -std::vector calculate_polygon_angles_at_vertices(const Polygon &polygon, const std::vector &lengths, - float min_arm_length) { - std::vector result(polygon.size()); - - if (polygon.size() == 1) { - result[0] = 0.0f; - } - - size_t idx_prev = 0; - size_t idx_curr = 0; - size_t idx_next = 0; - - float distance_to_prev = 0; - float distance_to_next = 0; - - //push idx_prev far enough back as initialization - while (distance_to_prev < min_arm_length) { - idx_prev = Slic3r::prev_idx_modulo(idx_prev, polygon.size()); - distance_to_prev += lengths[idx_prev]; - } - - for (size_t _i = 0; _i < polygon.size(); ++_i) { - // pull idx_prev to current as much as possible, while respecting the min_arm_length - while (distance_to_prev - lengths[idx_prev] > min_arm_length) { - distance_to_prev -= lengths[idx_prev]; - idx_prev = Slic3r::next_idx_modulo(idx_prev, polygon.size()); - } - - //push idx_next forward as far as needed - while (distance_to_next < min_arm_length) { - distance_to_next += lengths[idx_next]; - idx_next = Slic3r::next_idx_modulo(idx_next, polygon.size()); - } - - // Calculate angle between idx_prev, idx_curr, idx_next. - const Point &p0 = polygon.points[idx_prev]; - const Point &p1 = polygon.points[idx_curr]; - const Point &p2 = polygon.points[idx_next]; - result[idx_curr] = float(angle(p1 - p0, p2 - p1)); - - // increase idx_curr by one - float curr_distance = lengths[idx_curr]; - idx_curr++; - distance_to_prev += curr_distance; - distance_to_next -= curr_distance; - } - - return result; -} - -struct CoordinateFunctor { - const std::vector *coordinates; - CoordinateFunctor(const std::vector *coords) : - coordinates(coords) { - } - CoordinateFunctor() : - coordinates(nullptr) { - } - - const float& operator()(size_t idx, size_t dim) const { - return coordinates->operator [](idx)[dim]; - } -}; - -// structure to store global information about the model - occlusion hits, enforcers, blockers -struct GlobalModelInfo { - TriangleSetSamples mesh_samples; - std::vector mesh_samples_visibility; - CoordinateFunctor mesh_samples_coordinate_functor; - KDTreeIndirect<3, float, CoordinateFunctor> mesh_samples_tree { CoordinateFunctor { } }; - float mesh_samples_radius; - - indexed_triangle_set enforcers; - indexed_triangle_set blockers; - AABBTreeIndirect::Tree<3, float> enforcers_tree; - AABBTreeIndirect::Tree<3, float> blockers_tree; - - bool is_enforced(const Vec3f &position, float radius) const { - if (enforcers.empty()) { - return false; - } - float radius_sqr = radius * radius; - return AABBTreeIndirect::is_any_triangle_in_radius(enforcers.vertices, enforcers.indices, - enforcers_tree, position, radius_sqr); - } - - bool is_blocked(const Vec3f &position, float radius) const { - if (blockers.empty()) { - return false; - } - float radius_sqr = radius * radius; - return AABBTreeIndirect::is_any_triangle_in_radius(blockers.vertices, blockers.indices, - blockers_tree, position, radius_sqr); - } - - float calculate_point_visibility(const Vec3f &position) const { - std::vector points = find_nearby_points(mesh_samples_tree, position, mesh_samples_radius); - if (points.empty()) { - return 1.0f; - } - - auto compute_dist_to_plane = [](const Vec3f &position, const Vec3f &plane_origin, const Vec3f &plane_normal) { - Vec3f orig_to_point = position - plane_origin; - return std::abs(orig_to_point.dot(plane_normal)); + const std::vector extrusions{ + Geometry::get_extrusions(print_object->layers())}; + const Perimeters::LayerInfos layer_infos{Perimeters::get_layer_infos( + print_object->layers(), params.perimeter.elephant_foot_compensation + )}; + const std::vector projected{ + print_object->config().seam_position == spRandom ? + Geometry::convert_to_geometry(extrusions) : + Geometry::project_to_geometry(extrusions, params.max_distance) }; + Perimeters::LayerPerimeters perimeters{Perimeters::create_perimeters(projected, layer_infos, painting, params.perimeter)}; - float total_weight = 0; - float total_visibility = 0; - for (size_t i = 0; i < points.size(); ++i) { - size_t sample_idx = points[i]; - - Vec3f sample_point = this->mesh_samples.positions[sample_idx]; - Vec3f sample_normal = this->mesh_samples.normals[sample_idx]; - - float weight = mesh_samples_radius - compute_dist_to_plane(position, sample_point, sample_normal); - weight += (mesh_samples_radius - (position - sample_point).norm()); - total_visibility += weight * mesh_samples_visibility[sample_idx]; - total_weight += weight; - } - - return total_visibility / total_weight; - + throw_if_canceled(); + result.emplace(print_object, std::move(perimeters)); } - -#ifdef DEBUG_FILES - void debug_export(const indexed_triangle_set &obj_mesh) const { - - indexed_triangle_set divided_mesh = obj_mesh; - Slic3r::CNumericLocalesSetter locales_setter; - - { - auto filename = debug_out_path("visiblity.obj"); - FILE *fp = boost::nowide::fopen(filename.c_str(), "w"); - if (fp == nullptr) { - BOOST_LOG_TRIVIAL(error) - << "stl_write_obj: Couldn't open " << filename << " for writing"; - return; - } - - for (size_t i = 0; i < divided_mesh.vertices.size(); ++i) { - float visibility = calculate_point_visibility(divided_mesh.vertices[i]); - Vec3f color = value_to_rgbf(0.0f, 1.0f, visibility); - fprintf(fp, "v %f %f %f %f %f %f\n", - divided_mesh.vertices[i](0), divided_mesh.vertices[i](1), divided_mesh.vertices[i](2), - color(0), color(1), color(2)); - } - for (size_t i = 0; i < divided_mesh.indices.size(); ++i) - fprintf(fp, "f %d %d %d\n", divided_mesh.indices[i][0] + 1, divided_mesh.indices[i][1] + 1, - divided_mesh.indices[i][2] + 1); - fclose(fp); - } - - { - auto filename = debug_out_path("visiblity_samples.obj"); - FILE *fp = boost::nowide::fopen(filename.c_str(), "w"); - if (fp == nullptr) { - BOOST_LOG_TRIVIAL(error) - << "stl_write_obj: Couldn't open " << filename << " for writing"; - return; - } - - for (size_t i = 0; i < mesh_samples.positions.size(); ++i) { - float visibility = mesh_samples_visibility[i]; - Vec3f color = value_to_rgbf(0.0f, 1.0f, visibility); - fprintf(fp, "v %f %f %f %f %f %f\n", - mesh_samples.positions[i](0), mesh_samples.positions[i](1), mesh_samples.positions[i](2), - color(0), color(1), color(2)); - } - fclose(fp); - } - - } -#endif + return result; } -; -//Extract perimeter polygons of the given layer -Polygons extract_perimeter_polygons(const Layer *layer, std::vector &corresponding_regions_out) { - Polygons polygons; +Perimeters::LayerPerimeters sort_to_layers(Shells::Shells<> &&shells) { + const std::size_t layer_count{Shells::get_layer_count(shells)}; + Perimeters::LayerPerimeters result(layer_count); + + for (Shells::Shell<> &shell : shells) { + for (Shells::Slice<> &slice : shell) { + const BoundingBox bounding_box{Geometry::scaled(slice.boundary.positions)}; + result[slice.layer_index].push_back( + Perimeters::BoundedPerimeter{std::move(slice.boundary), bounding_box} + ); + } + } + return result; +} + +ObjectSeams precalculate_seams( + const Params ¶ms, + ObjectLayerPerimeters &&seam_data, + const std::function &throw_if_canceled +) { + ObjectSeams result; + + for (auto &[print_object, layer_perimeters] : seam_data) { + switch (print_object->config().seam_position.value) { + case spAligned: { + const Transform3d transformation{print_object->trafo_centered()}; + const ModelVolumePtrs &volumes{print_object->model_object()->volumes}; + + Slic3r::ModelInfo::Visibility + points_visibility{transformation, volumes, params.visibility, throw_if_canceled}; + throw_if_canceled(); + const Aligned::VisibilityCalculator visibility_calculator{ + points_visibility, params.convex_visibility_modifier, + params.concave_visibility_modifier}; + + Shells::Shells<> shells{Shells::create_shells(std::move(layer_perimeters), params.max_distance)}; + result[print_object] = Aligned::get_object_seams( + std::move(shells), visibility_calculator, params.aligned + ); + break; + } + case spRear: { + result[print_object] = Rear::get_object_seams(std::move(layer_perimeters), params.rear_tolerance, params.rear_y_offset); + break; + } + case spRandom: { + result[print_object] = Random::get_object_seams(std::move(layer_perimeters), params.random_seed); + break; + } + case spNearest: { + // Do not precalculate anything. + break; + } + } + throw_if_canceled(); + } + return result; +} + +Params Placer::get_params(const DynamicPrintConfig &config) { + Params params{}; + + params.perimeter.elephant_foot_compensation = config.opt_float("elefant_foot_compensation"); + if (config.opt_int("raft_layers") > 0) { + params.perimeter.elephant_foot_compensation = 0.0; + } + params.random_seed = 1653710332u; + + params.aligned.max_detour = 1.0; + params.aligned.continuity_modifier = 2.0; + params.convex_visibility_modifier = 1.1; + params.concave_visibility_modifier = 0.9; + params.perimeter.overhang_threshold = Slic3r::Geometry::deg2rad(55.0); + params.perimeter.convex_threshold = Slic3r::Geometry::deg2rad(10.0); + params.perimeter.concave_threshold = Slic3r::Geometry::deg2rad(15.0); + + params.staggered_inner_seams = config.opt_bool("staggered_inner_seams"); + + params.max_nearest_detour = 1.0; + params.rear_tolerance = 0.2; + params.rear_y_offset = 20; + params.aligned.jump_visibility_threshold = 0.6; + params.max_distance = 5.0; + params.perimeter.oversampling_max_distance = 0.2; + params.perimeter.embedding_threshold = 0.5; + params.perimeter.painting_radius = 0.1; + params.perimeter.simplification_epsilon = 0.001; + params.perimeter.smooth_angle_arm_length = 0.2; + params.perimeter.sharp_angle_arm_length = 0.05; + + params.visibility.raycasting_visibility_samples_count = 30000; + params.visibility.fast_decimation_triangle_count_target = 16000; + params.visibility.sqr_rays_per_sample_point = 5; + + return params; +} + +void Placer::init( + SpanOfConstPtrs objects, + const Params ¶ms, + const std::function &throw_if_canceled +) { + BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: init: start"; + + ObjectPainting object_painting; + for (const PrintObject *print_object : objects) { + const Transform3d transformation{print_object->trafo_centered()}; + const ModelVolumePtrs &volumes{print_object->model_object()->volumes}; + object_painting.emplace(print_object, ModelInfo::Painting{transformation, volumes}); + } + + ObjectLayerPerimeters perimeters{get_perimeters(objects, params, object_painting, throw_if_canceled)}; + ObjectLayerPerimeters perimeters_for_precalculation; + + for (auto &[print_object, layer_perimeters] : perimeters) { + if (print_object->config().seam_position.value == spNearest) { + this->perimeters_per_layer[print_object] = std::move(layer_perimeters); + } else { + perimeters_for_precalculation[print_object] = std::move(layer_perimeters); + } + } + + this->params = params; + this->seams_per_object = precalculate_seams(params, std::move(perimeters_for_precalculation), throw_if_canceled); + + BOOST_LOG_TRIVIAL(debug) << "SeamPlacer: init: end"; +} + +const SeamPerimeterChoice &choose_closest_seam( + const std::vector &seams, const Polygon &loop_polygon +) { + BoundingBoxes choose_from; + choose_from.reserve(seams.size()); + for (const SeamPerimeterChoice &choice : seams) { + choose_from.push_back(choice.bounding_box); + } + + const std::size_t choice_index{ + Geometry::pick_closest_bounding_box(loop_polygon.bounding_box(), choose_from).first}; + + return seams[choice_index]; +} + +std::pair project_to_extrusion_loop( + const SeamChoice &seam_choice, const Perimeters::Perimeter &perimeter, const Linesf &loop_lines +) { + const AABBTreeLines::LinesDistancer distancer{loop_lines}; + + const bool is_at_vertex{seam_choice.previous_index == seam_choice.next_index}; + const Vec2d edge{ + perimeter.positions[seam_choice.next_index] - + perimeter.positions[seam_choice.previous_index]}; + const Vec2d normal{ + is_at_vertex ? + Geometry::get_polygon_normal(perimeter.positions, seam_choice.previous_index, 0.1) : + Geometry::get_normal(edge)}; + + double depth{distancer.distance_from_lines(seam_choice.position)}; + const Vec2d final_position{seam_choice.position - normal * depth}; + + auto [_, loop_line_index, loop_point] = distancer.distance_from_lines_extra(final_position + ); + return {loop_line_index, loop_point}; +} + +std::optional offset_along_loop_lines( + const Vec2d &point, + const std::size_t loop_line_index, + const Linesf &loop_lines, + const double offset +) { + double distance{0}; + Vec2d previous_point{point}; + std::optional offset_point; + Geometry::visit_near_forward(loop_line_index, loop_lines.size(), [&](std::size_t index) { + const Vec2d next_point{loop_lines[index].b}; + const Vec2d edge{next_point - previous_point}; + + if (distance + edge.norm() > offset) { + const double remaining_distance{offset - distance}; + offset_point = previous_point + remaining_distance * edge.normalized(); + return true; + } + + distance += edge.norm(); + previous_point = next_point; + + return false; + }); + + return offset_point; +} + +double get_angle(const SeamChoice &seam_choice, const Perimeters::Perimeter &perimeter) { + const bool is_at_vertex{seam_choice.previous_index == seam_choice.next_index}; + return is_at_vertex ? perimeter.angles[seam_choice.previous_index] : 0.0; +} + +Point finalize_seam_position( + const Polygon &loop_polygon, + const SeamChoice &seam_choice, + const Perimeters::Perimeter &perimeter, + const double loop_width, + const bool do_staggering +) { + const Linesf loop_lines{to_unscaled_linesf({ExPolygon{loop_polygon}})}; + const auto [loop_line_index, loop_point]{ + project_to_extrusion_loop(seam_choice, perimeter, loop_lines)}; + + // ExtrusionRole::Perimeter is inner perimeter. + if (do_staggering) { + const double depth = (loop_point - seam_choice.position).norm() - + loop_width / 2.0; + const double angle{get_angle(seam_choice, perimeter)}; + const double initial_offset{angle > 0 ? angle / 2.0 * depth : 0.0}; + const double additional_offset{angle < 0 ? std::cos(angle / 2.0) * depth : depth}; + + const double staggering_offset{initial_offset + additional_offset}; + + std::optional staggered_point{ + offset_along_loop_lines(loop_point, loop_line_index, loop_lines, staggering_offset)}; + + if (staggered_point) { + return scaled(*staggered_point); + } + } + + return scaled(loop_point); +} + +struct NearestCorner { + Vec2d prefered_position; + + std::optional operator()( + const Perimeters::Perimeter &perimeter, + const Perimeters::PointType point_type, + const Perimeters::PointClassification point_classification + ) const { + std::optional corner_candidate; + double min_distance{std::numeric_limits::infinity()}; + for (std::size_t i{0}; i < perimeter.positions.size(); ++i) { + if (perimeter.point_types[i] == point_type && + perimeter.point_classifications[i] == point_classification && + perimeter.angle_types[i] != Perimeters::AngleType::smooth) { + const Vec2d &point{perimeter.positions[i]}; + const double distance{(prefered_position - point).norm()}; + if (!corner_candidate || distance < min_distance) { + corner_candidate = {i, i, point}; + min_distance = distance; + } + } + } + return corner_candidate; + } +}; + +std::pair place_seam_near( + const std::vector &layer_perimeters, + const ExtrusionLoop &loop, + const Point &position, + const double max_detour +) { + BoundingBoxes choose_from; + choose_from.reserve(layer_perimeters.size()); + for (const Perimeters::BoundedPerimeter &perimeter : layer_perimeters) { + choose_from.push_back(perimeter.bounding_box); + } + + const Polygon loop_polygon{Geometry::to_polygon(loop)}; + + const std::size_t choice_index{ + Geometry::pick_closest_bounding_box(loop_polygon.bounding_box(), choose_from).first}; + + const NearestCorner nearest_corner{unscaled(position)}; + const std::optional corner_choice{ + Seams::maybe_choose_seam_point(layer_perimeters[choice_index].perimeter, nearest_corner)}; + + if (corner_choice) { + return {*corner_choice, choice_index}; + } + + const Seams::Aligned::Impl::Nearest nearest{unscaled(position), max_detour}; + const SeamChoice nearest_choice{ + Seams::choose_seam_point(layer_perimeters[choice_index].perimeter, nearest)}; + + return {nearest_choice, choice_index}; +} + +int get_perimeter_count(const Layer *layer){ + int count{0}; for (const LayerRegion *layer_region : layer->regions()) { for (const ExtrusionEntity *ex_entity : layer_region->perimeters()) { if (ex_entity->is_collection()) { //collection of inner, outer, and overhang perimeters - for (const ExtrusionEntity *perimeter : static_cast(ex_entity)->entities) { - ExtrusionRole role = perimeter->role(); - if (perimeter->is_loop()) { - for (const ExtrusionPath &path : static_cast(perimeter)->paths) { - if (path.role() == ExtrusionRole::ExternalPerimeter) { - role = ExtrusionRole::ExternalPerimeter; - } - } - } - - if (role == ExtrusionRole::ExternalPerimeter) { - Points p; - perimeter->collect_points(p); - polygons.emplace_back(std::move(p)); - corresponding_regions_out.push_back(layer_region); - } - } - if (polygons.empty()) { - Points p; - ex_entity->collect_points(p); - polygons.emplace_back(std::move(p)); - corresponding_regions_out.push_back(layer_region); - } - } else { - Points p; - ex_entity->collect_points(p); - polygons.emplace_back(std::move(p)); - corresponding_regions_out.push_back(layer_region); + count += static_cast(ex_entity)->entities.size(); + } + else { + count += 1; } } } - - if (polygons.empty()) { // If there are no perimeter polygons for whatever reason (disabled perimeters .. ) insert dummy point - // it is easier than checking everywhere if the layer is not emtpy, no seam will be placed to this layer anyway - polygons.emplace_back(Points{ { 0, 0 } }); - corresponding_regions_out.push_back(nullptr); - } - - return polygons; + return count; } -// Insert SeamCandidates created from perimeter polygons in to the result vector. -// Compute its type (Enfrocer,Blocker), angle, and position -//each SeamCandidate also contains pointer to shared Perimeter structure representing the polygon -// if Custom Seam modifiers are present, oversamples the polygon if necessary to better fit user intentions -void process_perimeter_polygon(const Polygon &orig_polygon, float z_coord, const LayerRegion *region, - const GlobalModelInfo &global_model_info, PrintObjectSeamData::LayerSeams &result) { - if (orig_polygon.size() == 0) { - return; - } - Polygon polygon = orig_polygon; - bool was_clockwise = polygon.make_counter_clockwise(); - float angle_arm_len = region != nullptr ? region->flow(FlowRole::frExternalPerimeter).nozzle_diameter() : 0.5f; - - std::vector lengths { }; - for (size_t point_idx = 0; point_idx < polygon.size() - 1; ++point_idx) { - lengths.push_back((unscale(polygon[point_idx]) - unscale(polygon[point_idx + 1])).norm()); - } - lengths.push_back(std::max((unscale(polygon[0]) - unscale(polygon[polygon.size() - 1])).norm(), 0.1)); - std::vector polygon_angles = calculate_polygon_angles_at_vertices(polygon, lengths, - angle_arm_len); - - result.perimeters.push_back( { }); - Perimeter &perimeter = result.perimeters.back(); - - std::queue orig_polygon_points { }; - for (size_t index = 0; index < polygon.size(); ++index) { - Vec2f unscaled_p = unscale(polygon[index]).cast(); - orig_polygon_points.emplace(unscaled_p.x(), unscaled_p.y(), z_coord); - } - Vec3f first = orig_polygon_points.front(); - std::queue oversampled_points { }; - size_t orig_angle_index = 0; - perimeter.start_index = result.points.size(); - perimeter.flow_width = region != nullptr ? region->flow(FlowRole::frExternalPerimeter).width() : 0.0f; - bool some_point_enforced = false; - while (!orig_polygon_points.empty() || !oversampled_points.empty()) { - EnforcedBlockedSeamPoint type = EnforcedBlockedSeamPoint::Neutral; - Vec3f position; - float local_ccw_angle = 0; - bool orig_point = false; - if (!oversampled_points.empty()) { - position = oversampled_points.front(); - oversampled_points.pop(); - } else { - position = orig_polygon_points.front(); - orig_polygon_points.pop(); - local_ccw_angle = was_clockwise ? -polygon_angles[orig_angle_index] : polygon_angles[orig_angle_index]; - orig_angle_index++; - orig_point = true; - } - - if (global_model_info.is_enforced(position, perimeter.flow_width)) { - type = EnforcedBlockedSeamPoint::Enforced; - } - - if (global_model_info.is_blocked(position, perimeter.flow_width)) { - type = EnforcedBlockedSeamPoint::Blocked; - } - some_point_enforced = some_point_enforced || type == EnforcedBlockedSeamPoint::Enforced; - - if (orig_point) { - Vec3f pos_of_next = orig_polygon_points.empty() ? first : orig_polygon_points.front(); - float distance_to_next = (position - pos_of_next).norm(); - if (global_model_info.is_enforced(position, distance_to_next)) { - Vec3f vec_to_next = (pos_of_next - position).normalized(); - float step_size = SeamPlacer::enforcer_oversampling_distance; - float step = step_size; - while (step < distance_to_next) { - oversampled_points.push(position + vec_to_next * step); - step += step_size; - } - } - } - - result.points.emplace_back(position, perimeter, local_ccw_angle, type); - } - - perimeter.end_index = result.points.size(); - - if (some_point_enforced) { - // We will patches of enforced points (patch: continuous section of enforced points), choose - // the longest patch, and select the middle point or sharp point (depending on the angle) - // this point will have high priority on this perimeter - size_t perimeter_size = perimeter.end_index - perimeter.start_index; - const auto next_index = [&](size_t idx) { - return perimeter.start_index + Slic3r::next_idx_modulo(idx - perimeter.start_index, perimeter_size); - }; - - std::vector patches_starts_ends; - for (size_t i = perimeter.start_index; i < perimeter.end_index; ++i) { - if (result.points[i].type != EnforcedBlockedSeamPoint::Enforced && - result.points[next_index(i)].type == EnforcedBlockedSeamPoint::Enforced) { - patches_starts_ends.push_back(next_index(i)); - } - if (result.points[i].type == EnforcedBlockedSeamPoint::Enforced && - result.points[next_index(i)].type != EnforcedBlockedSeamPoint::Enforced) { - patches_starts_ends.push_back(next_index(i)); - } - } - //if patches_starts_ends are empty, it means that the whole perimeter is enforced.. don't do anything in that case - if (!patches_starts_ends.empty()) { - //if the first point in the patches is not enforced, it marks a patch end. in that case, put it to the end and start on next - // to simplify the processing - assert(patches_starts_ends.size() % 2 == 0); - bool start_on_second = false; - if (result.points[patches_starts_ends[0]].type != EnforcedBlockedSeamPoint::Enforced) { - start_on_second = true; - patches_starts_ends.push_back(patches_starts_ends[0]); - } - //now pick the longest patch - std::pair longest_patch { 0, 0 }; - auto patch_len = [perimeter_size](const std::pair &start_end) { - if (start_end.second < start_end.first) { - return start_end.first + (perimeter_size - start_end.second); - } else { - return start_end.second - start_end.first; - } - }; - for (size_t patch_idx = start_on_second ? 1 : 0; patch_idx < patches_starts_ends.size(); patch_idx += 2) { - std::pair current_patch { patches_starts_ends[patch_idx], patches_starts_ends[patch_idx - + 1] }; - if (patch_len(longest_patch) < patch_len(current_patch)) { - longest_patch = current_patch; - } - } - std::vector viable_points_indices; - std::vector large_angle_points_indices; - for (size_t point_idx = longest_patch.first; point_idx != longest_patch.second; - point_idx = next_index(point_idx)) { - viable_points_indices.push_back(point_idx); - if (std::abs(result.points[point_idx].local_ccw_angle) - > SeamPlacer::sharp_angle_snapping_threshold) { - large_angle_points_indices.push_back(point_idx); - } - } - assert(viable_points_indices.size() > 0); - if (large_angle_points_indices.empty()) { - size_t central_idx = viable_points_indices[viable_points_indices.size() / 2]; - result.points[central_idx].central_enforcer = true; - } else { - size_t central_idx = large_angle_points_indices.size() / 2; - result.points[large_angle_points_indices[central_idx]].central_enforcer = true; - } - } - } - -} - -// Get index of previous and next perimeter point of the layer. Because SeamCandidates of all polygons of the given layer -// are sequentially stored in the vector, each perimeter contains info about start and end index. These vales are used to -// deduce index of previous and next neigbour in the corresponding perimeter. -std::pair find_previous_and_next_perimeter_point(const std::vector &perimeter_points, - size_t point_index) { - const SeamCandidate ¤t = perimeter_points[point_index]; - int prev = point_index - 1; //for majority of points, it is true that neighbours lie behind and in front of them in the vector - int next = point_index + 1; - - if (point_index == current.perimeter.start_index) { - // if point_index is equal to start, it means that the previous neighbour is at the end - prev = current.perimeter.end_index; - } - - if (point_index == current.perimeter.end_index - 1) { - // if point_index is equal to end, than next neighbour is at the start - next = current.perimeter.start_index; - } - - assert(prev >= 0); - assert(next >= 0); - return {size_t(prev),size_t(next)}; -} - -// Computes all global model info - transforms object, performs raycasting -void compute_global_occlusion(GlobalModelInfo &result, const PrintObject *po, - std::function throw_if_canceled) { - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: gather occlusion meshes: start"; - auto obj_transform = po->trafo_centered(); - indexed_triangle_set triangle_set; - indexed_triangle_set negative_volumes_set; - //add all parts - for (const ModelVolume *model_volume : po->model_object()->volumes) { - if (model_volume->type() == ModelVolumeType::MODEL_PART - || model_volume->type() == ModelVolumeType::NEGATIVE_VOLUME) { - auto model_transformation = model_volume->get_matrix(); - indexed_triangle_set model_its = model_volume->mesh().its; - its_transform(model_its, model_transformation); - if (model_volume->type() == ModelVolumeType::MODEL_PART) { - its_merge(triangle_set, model_its); - } else { - its_merge(negative_volumes_set, model_its); - } - } - } - throw_if_canceled(); - - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: gather occlusion meshes: end"; - - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: decimate: start"; - its_short_edge_collpase(triangle_set, SeamPlacer::fast_decimation_triangle_count_target); - its_short_edge_collpase(negative_volumes_set, SeamPlacer::fast_decimation_triangle_count_target); - - size_t negative_volumes_start_index = triangle_set.indices.size(); - its_merge(triangle_set, negative_volumes_set); - its_transform(triangle_set, obj_transform); - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: decimate: end"; - - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: Compute visibility sample points: start"; - - result.mesh_samples = sample_its_uniform_parallel(SeamPlacer::raycasting_visibility_samples_count, - triangle_set); - result.mesh_samples_coordinate_functor = CoordinateFunctor(&result.mesh_samples.positions); - result.mesh_samples_tree = KDTreeIndirect<3, float, CoordinateFunctor>(result.mesh_samples_coordinate_functor, - result.mesh_samples.positions.size()); - - // The following code determines search area for random visibility samples on the mesh when calculating visibility of each perimeter point - // number of random samples in the given radius (area) is approximately poisson distribution - // to compute ideal search radius (area), we use exponential distribution (complementary distr to poisson) - // parameters of exponential distribution to compute area that will have with probability="probability" more than given number of samples="samples" - float probability = 0.9f; - float samples = 4; - float density = SeamPlacer::raycasting_visibility_samples_count / result.mesh_samples.total_area; - // exponential probability distrubtion function is : f(x) = P(X > x) = e^(l*x) where l is the rate parameter (computed as 1/u where u is mean value) - // probability that sampled area A with S samples contains more than samples count: - // P(S > samples in A) = e^-(samples/(density*A)); express A: - float search_area = samples / (-logf(probability) * density); - float search_radius = sqrt(search_area / PI); - result.mesh_samples_radius = search_radius; - - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: Compute visiblity sample points: end"; - throw_if_canceled(); - - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: Mesh sample raidus: " << result.mesh_samples_radius; - - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: build AABB tree: start"; - auto raycasting_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(triangle_set.vertices, - triangle_set.indices); - - throw_if_canceled(); - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: build AABB tree: end"; - result.mesh_samples_visibility = raycast_visibility(raycasting_tree, triangle_set, result.mesh_samples, - negative_volumes_start_index); - throw_if_canceled(); -#ifdef DEBUG_FILES - result.debug_export(triangle_set); -#endif -} - -void gather_enforcers_blockers(GlobalModelInfo &result, const PrintObject *po) { - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: build AABB trees for raycasting enforcers/blockers: start"; - - auto obj_transform = po->trafo_centered(); - - for (const ModelVolume *mv : po->model_object()->volumes) { - if (mv->is_seam_painted()) { - auto model_transformation = obj_transform * mv->get_matrix(); - - indexed_triangle_set enforcers = mv->seam_facets.get_facets(*mv, EnforcerBlockerType::ENFORCER); - its_transform(enforcers, model_transformation); - its_merge(result.enforcers, enforcers); - - indexed_triangle_set blockers = mv->seam_facets.get_facets(*mv, EnforcerBlockerType::BLOCKER); - its_transform(blockers, model_transformation); - its_merge(result.blockers, blockers); - } - } - - result.enforcers_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(result.enforcers.vertices, - result.enforcers.indices); - result.blockers_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(result.blockers.vertices, - result.blockers.indices); - - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: build AABB trees for raycasting enforcers/blockers: end"; -} - -struct SeamComparator { - SeamPosition setup; - float angle_importance; - explicit SeamComparator(SeamPosition setup) : - setup(setup) { - angle_importance = - setup == spNearest ? SeamPlacer::angle_importance_nearest : SeamPlacer::angle_importance_aligned; - } - - // Standard comparator, must respect the requirements of comparators (e.g. give same result on same inputs) for sorting usage - // should return if a is better seamCandidate than b - bool is_first_better(const SeamCandidate &a, const SeamCandidate &b, const Vec2f &preffered_location = Vec2f { 0.0f, - 0.0f }) const { - if (setup == SeamPosition::spAligned && a.central_enforcer != b.central_enforcer) { - return a.central_enforcer; - } - - // Blockers/Enforcers discrimination, top priority - if (a.type != b.type) { - return a.type > b.type; - } - - //avoid overhangs - if (a.overhang > 0.0f || b.overhang > 0.0f) { - return a.overhang < b.overhang; - } - - // prefer hidden points (more than 0.5 mm inside) - if (a.embedded_distance < -0.5f && b.embedded_distance > -0.5f) { - return true; - } - if (b.embedded_distance < -0.5f && a.embedded_distance > -0.5f) { - return false; - } - - if (setup == SeamPosition::spRear && a.position.y() != b.position.y()) { - return a.position.y() > b.position.y(); - } - - float distance_penalty_a = 0.0f; - float distance_penalty_b = 0.0f; - if (setup == spNearest) { - distance_penalty_a = 1.0f - gauss((a.position.head<2>() - preffered_location).norm(), 0.0f, 1.0f, 0.005f); - distance_penalty_b = 1.0f - gauss((b.position.head<2>() - preffered_location).norm(), 0.0f, 1.0f, 0.005f); - } - - // the penalites are kept close to range [0-1.x] however, it should not be relied upon - float penalty_a = a.overhang + a.visibility + - angle_importance * compute_angle_penalty(a.local_ccw_angle) - + distance_penalty_a; - float penalty_b = b.overhang + b.visibility + - angle_importance * compute_angle_penalty(b.local_ccw_angle) - + distance_penalty_b; - - return penalty_a < penalty_b; - } - - // Comparator used during alignment. If there is close potential aligned point, it is compared to the current - // seam point of the perimeter, to find out if the aligned point is not much worse than the current seam - // Also used by the random seam generator. - bool is_first_not_much_worse(const SeamCandidate &a, const SeamCandidate &b) const { - // Blockers/Enforcers discrimination, top priority - if (setup == SeamPosition::spAligned && a.central_enforcer != b.central_enforcer) { - // Prefer centers of enforcers. - return a.central_enforcer; - } - - if (a.type == EnforcedBlockedSeamPoint::Enforced) { - return true; - } - - if (a.type == EnforcedBlockedSeamPoint::Blocked) { - return false; - } - - if (a.type != b.type) { - return a.type > b.type; - } - - //avoid overhangs - if ((a.overhang > 0.0f || b.overhang > 0.0f) - && abs(a.overhang - b.overhang) > (0.1f * a.perimeter.flow_width)) { - return a.overhang < b.overhang; - } - - // prefer hidden points (more than 0.5 mm inside) - if (a.embedded_distance < -0.5f && b.embedded_distance > -0.5f) { - return true; - } - if (b.embedded_distance < -0.5f && a.embedded_distance > -0.5f) { - return false; - } - - if (setup == SeamPosition::spRandom) { - return true; - } - - if (setup == SeamPosition::spRear) { - return a.position.y() + SeamPlacer::seam_align_score_tolerance * 5.0f > b.position.y(); - } - - float penalty_a = a.overhang + a.visibility - + angle_importance * compute_angle_penalty(a.local_ccw_angle); - float penalty_b = b.overhang + b.visibility + - angle_importance * compute_angle_penalty(b.local_ccw_angle); - - return penalty_a <= penalty_b || penalty_a - penalty_b < SeamPlacer::seam_align_score_tolerance; - } - - bool are_similar(const SeamCandidate &a, const SeamCandidate &b) const { - return is_first_not_much_worse(a, b) && is_first_not_much_worse(b, a); - } -}; - -#ifdef DEBUG_FILES -void debug_export_points(const std::vector &layers, - const BoundingBox &bounding_box, const SeamComparator &comparator) { - for (size_t layer_idx = 0; layer_idx < layers.size(); ++layer_idx) { - std::string angles_file_name = debug_out_path( - ("angles_" + std::to_string(layer_idx) + ".svg").c_str()); - SVG angles_svg { angles_file_name, bounding_box }; - float min_vis = 0; - float max_vis = min_vis; - - float min_weight = std::numeric_limits::min(); - float max_weight = min_weight; - - for (const SeamCandidate &point : layers[layer_idx].points) { - Vec3i color = value_to_rgbi(-PI, PI, point.local_ccw_angle); - std::string fill = "rgb(" + std::to_string(color.x()) + "," + std::to_string(color.y()) + "," - + std::to_string(color.z()) + ")"; - angles_svg.draw(scaled(Vec2f(point.position.head<2>())), fill); - min_vis = std::min(min_vis, point.visibility); - max_vis = std::max(max_vis, point.visibility); - - min_weight = std::min(min_weight, -compute_angle_penalty(point.local_ccw_angle)); - max_weight = std::max(max_weight, -compute_angle_penalty(point.local_ccw_angle)); - - } - - std::string visiblity_file_name = debug_out_path( - ("visibility_" + std::to_string(layer_idx) + ".svg").c_str()); - SVG visibility_svg { visiblity_file_name, bounding_box }; - std::string weights_file_name = debug_out_path( - ("weight_" + std::to_string(layer_idx) + ".svg").c_str()); - SVG weight_svg { weights_file_name, bounding_box }; - std::string overhangs_file_name = debug_out_path( - ("overhang_" + std::to_string(layer_idx) + ".svg").c_str()); - SVG overhangs_svg { overhangs_file_name, bounding_box }; - - for (const SeamCandidate &point : layers[layer_idx].points) { - Vec3i color = value_to_rgbi(min_vis, max_vis, point.visibility); - std::string visibility_fill = "rgb(" + std::to_string(color.x()) + "," + std::to_string(color.y()) + "," - + std::to_string(color.z()) + ")"; - visibility_svg.draw(scaled(Vec2f(point.position.head<2>())), visibility_fill); - - Vec3i weight_color = value_to_rgbi(min_weight, max_weight, - -compute_angle_penalty(point.local_ccw_angle)); - std::string weight_fill = "rgb(" + std::to_string(weight_color.x()) + "," + std::to_string(weight_color.y()) - + "," - + std::to_string(weight_color.z()) + ")"; - weight_svg.draw(scaled(Vec2f(point.position.head<2>())), weight_fill); - - Vec3i overhang_color = value_to_rgbi(-0.5, 0.5, std::clamp(point.overhang, -0.5f, 0.5f)); - std::string overhang_fill = "rgb(" + std::to_string(overhang_color.x()) + "," - + std::to_string(overhang_color.y()) - + "," - + std::to_string(overhang_color.z()) + ")"; - overhangs_svg.draw(scaled(Vec2f(point.position.head<2>())), overhang_fill); - } - } -} -#endif - -// Pick best seam point based on the given comparator -void pick_seam_point(std::vector &perimeter_points, size_t start_index, - const SeamComparator &comparator) { - size_t end_index = perimeter_points[start_index].perimeter.end_index; - - size_t seam_index = start_index; - for (size_t index = start_index; index < end_index; ++index) { - if (comparator.is_first_better(perimeter_points[index], perimeter_points[seam_index])) { - seam_index = index; - } - } - perimeter_points[start_index].perimeter.seam_index = seam_index; -} - -size_t pick_nearest_seam_point_index(const std::vector &perimeter_points, size_t start_index, - const Vec2f &preffered_location) { - size_t end_index = perimeter_points[start_index].perimeter.end_index; - SeamComparator comparator { spNearest }; - - size_t seam_index = start_index; - for (size_t index = start_index; index < end_index; ++index) { - if (comparator.is_first_better(perimeter_points[index], perimeter_points[seam_index], preffered_location)) { - seam_index = index; - } - } - return seam_index; -} - -// picks random seam point uniformly, respecting enforcers blockers and overhang avoidance. -void pick_random_seam_point(const std::vector &perimeter_points, size_t start_index) { - SeamComparator comparator { spRandom }; - - // algorithm keeps a list of viable points and their lengths. If it finds a point - // that is much better than the viable_example_index (e.g. better type, no overhang; see is_first_not_much_worse) - // then it throws away stored lists and starts from start - // in the end, the list should contain points with same type (Enforced > Neutral > Blocked) and also only those which are not - // big overhang. - size_t viable_example_index = start_index; - size_t end_index = perimeter_points[start_index].perimeter.end_index; - struct Viable { - // Candidate seam point index. - size_t index; - float edge_length; - Vec3f edge; - }; - std::vector viables; - - const Vec3f pseudornd_seed = perimeter_points[viable_example_index].position; - float rand = std::abs(sin(pseudornd_seed.dot(Vec3f(12.9898f,78.233f, 133.3333f))) * 43758.5453f); - rand = rand - (int) rand; - - for (size_t index = start_index; index < end_index; ++index) { - if (comparator.are_similar(perimeter_points[index], perimeter_points[viable_example_index])) { - // index ok, push info into viables - Vec3f edge_to_next { perimeter_points[index == end_index - 1 ? start_index : index + 1].position - - perimeter_points[index].position }; - float dist_to_next = edge_to_next.norm(); - viables.push_back( { index, dist_to_next, edge_to_next }); - } else if (comparator.is_first_not_much_worse(perimeter_points[viable_example_index], - perimeter_points[index])) { - // index is worse then viable_example_index, skip this point - } else { - // index is better than viable example index, update example, clear gathered info, start again - // clear up all gathered info, start from scratch, update example index - viable_example_index = index; - viables.clear(); - - Vec3f edge_to_next = (perimeter_points[index == end_index - 1 ? start_index : index + 1].position - - perimeter_points[index].position); - float dist_to_next = edge_to_next.norm(); - viables.push_back( { index, dist_to_next, edge_to_next }); - } - } - - // now pick random point from the stored options - float len_sum = std::accumulate(viables.begin(), viables.end(), 0.0f, [](const float acc, const Viable &v) { - return acc + v.edge_length; - }); - float picked_len = len_sum * rand; - - size_t point_idx = 0; - while (picked_len - viables[point_idx].edge_length > 0) { - picked_len = picked_len - viables[point_idx].edge_length; - point_idx++; - } - - Perimeter &perimeter = perimeter_points[start_index].perimeter; - perimeter.seam_index = viables[point_idx].index; - perimeter.final_seam_position = perimeter_points[perimeter.seam_index].position - + viables[point_idx].edge.normalized() * picked_len; - perimeter.finalized = true; -} - -} // namespace SeamPlacerImpl - -// Parallel process and extract each perimeter polygon of the given print object. -// Gather SeamCandidates of each layer into vector and build KDtree over them -// Store results in the SeamPlacer variables m_seam_per_object -void SeamPlacer::gather_seam_candidates(const PrintObject *po, const SeamPlacerImpl::GlobalModelInfo &global_model_info) { - using namespace SeamPlacerImpl; - PrintObjectSeamData &seam_data = m_seam_per_object.emplace(po, PrintObjectSeamData { }).first->second; - seam_data.layers.resize(po->layer_count()); - - tbb::parallel_for(tbb::blocked_range(0, po->layers().size()), - [po, &global_model_info, &seam_data] - (tbb::blocked_range r) { - for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { - PrintObjectSeamData::LayerSeams &layer_seams = seam_data.layers[layer_idx]; - const Layer *layer = po->get_layer(layer_idx); - auto unscaled_z = layer->slice_z; - std::vector regions; - //NOTE corresponding region ptr may be null, if the layer has zero perimeters - Polygons polygons = extract_perimeter_polygons(layer, regions); - for (size_t poly_index = 0; poly_index < polygons.size(); ++poly_index) { - process_perimeter_polygon(polygons[poly_index], unscaled_z, - regions[poly_index], global_model_info, layer_seams); - } - auto functor = SeamCandidateCoordinateFunctor { layer_seams.points }; - seam_data.layers[layer_idx].points_tree = - std::make_unique(functor, - layer_seams.points.size()); - } - } - ); -} - -void SeamPlacer::calculate_candidates_visibility(const PrintObject *po, - const SeamPlacerImpl::GlobalModelInfo &global_model_info) { - using namespace SeamPlacerImpl; - - std::vector &layers = m_seam_per_object[po].layers; - tbb::parallel_for(tbb::blocked_range(0, layers.size()), - [&layers, &global_model_info](tbb::blocked_range r) { - for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { - for (auto &perimeter_point : layers[layer_idx].points) { - perimeter_point.visibility = global_model_info.calculate_point_visibility( - perimeter_point.position); - } - } - }); -} - -void SeamPlacer::calculate_overhangs_and_layer_embedding(const PrintObject *po) { - using namespace SeamPlacerImpl; - using PerimeterDistancer = AABBTreeLines::LinesDistancer; - - std::vector &layers = m_seam_per_object[po].layers; - tbb::parallel_for(tbb::blocked_range(0, layers.size()), - [po, &layers](tbb::blocked_range r) { - std::unique_ptr prev_layer_distancer; - if (r.begin() > 0) { // previous layer exists - prev_layer_distancer = std::make_unique(to_unscaled_linesf(po->layers()[r.begin() - 1]->lslices)); - } - - for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { - size_t regions_with_perimeter = 0; - for (const LayerRegion *region : po->layers()[layer_idx]->regions()) { - if (region->perimeters().size() > 0) { - regions_with_perimeter++; - } - }; - bool should_compute_layer_embedding = regions_with_perimeter > 1; - std::unique_ptr current_layer_distancer = std::make_unique( - to_unscaled_linesf(po->layers()[layer_idx]->lslices)); - - for (SeamCandidate &perimeter_point : layers[layer_idx].points) { - Vec2f point = Vec2f { perimeter_point.position.head<2>() }; - if (prev_layer_distancer.get() != nullptr) { - perimeter_point.overhang = prev_layer_distancer->distance_from_lines(point.cast()) - + 0.6f * perimeter_point.perimeter.flow_width - - tan(SeamPlacer::overhang_angle_threshold) - * po->layers()[layer_idx]->height; - perimeter_point.overhang = - perimeter_point.overhang < 0.0f ? 0.0f : perimeter_point.overhang; - } - - if (should_compute_layer_embedding) { // search for embedded perimeter points (points hidden inside the print ,e.g. multimaterial join, best position for seam) - perimeter_point.embedded_distance = current_layer_distancer->distance_from_lines(point.cast()) - + 0.6f * perimeter_point.perimeter.flow_width; - } - } - - prev_layer_distancer.swap(current_layer_distancer); - } - } - ); - } - -// Estimates, if there is good seam point in the layer_idx which is close to last_point_pos -// uses comparator.is_first_not_much_worse method to compare current seam with the closest point -// (if current seam is too far away ) -// If the current chosen stream is close enough, it is stored in seam_string. returns true and updates last_point_pos -// If the closest point is good enough to replace current chosen seam, it is stored in potential_string_seams, returns true and updates last_point_pos -// Otherwise does nothing, returns false -// Used by align_seam_points(). -std::optional> SeamPlacer::find_next_seam_in_layer( - const std::vector &layers, - const Vec3f &projected_position, - const size_t layer_idx, const float max_distance, - const SeamPlacerImpl::SeamComparator &comparator) const { - using namespace SeamPlacerImpl; - std::vector nearby_points_indices = find_nearby_points(*layers[layer_idx].points_tree, projected_position, - max_distance); - - if (nearby_points_indices.empty()) { - return {}; - } - - size_t best_nearby_point_index = nearby_points_indices[0]; - size_t nearest_point_index = nearby_points_indices[0]; - - // Now find best nearby point, nearest point, and corresponding indices - for (const size_t &nearby_point_index : nearby_points_indices) { - const SeamCandidate &point = layers[layer_idx].points[nearby_point_index]; - if (point.perimeter.finalized) { - continue; // skip over finalized perimeters, try to find some that is not finalized - } - if (comparator.is_first_better(point, layers[layer_idx].points[best_nearby_point_index], - projected_position.head<2>()) - || layers[layer_idx].points[best_nearby_point_index].perimeter.finalized) { - best_nearby_point_index = nearby_point_index; - } - if ((point.position - projected_position).squaredNorm() - < (layers[layer_idx].points[nearest_point_index].position - projected_position).squaredNorm() - || layers[layer_idx].points[nearest_point_index].perimeter.finalized) { - nearest_point_index = nearby_point_index; - } - } - - const SeamCandidate &best_nearby_point = layers[layer_idx].points[best_nearby_point_index]; - const SeamCandidate &nearest_point = layers[layer_idx].points[nearest_point_index]; - - if (nearest_point.perimeter.finalized) { - //all points are from already finalized perimeter, skip - return {}; - } - - //from the nearest_point, deduce index of seam in the next layer - const SeamCandidate &next_layer_seam = layers[layer_idx].points[nearest_point.perimeter.seam_index]; - - // First try to pick central enforcer if any present - if (next_layer_seam.central_enforcer - && (next_layer_seam.position - projected_position).squaredNorm() - < sqr(3 * max_distance)) { - return {std::pair {layer_idx, nearest_point.perimeter.seam_index}}; - } - - // First try to align the nearest, then try the best nearby - if (comparator.is_first_not_much_worse(nearest_point, next_layer_seam)) { - return {std::pair {layer_idx, nearest_point_index}}; - } - // If nearest point is not good enough, try it with the best nearby point. - if (comparator.is_first_not_much_worse(best_nearby_point, next_layer_seam)) { - return {std::pair {layer_idx, best_nearby_point_index}}; - } - - return {}; -} - -std::vector> SeamPlacer::find_seam_string(const PrintObject *po, - std::pair start_seam, const SeamPlacerImpl::SeamComparator &comparator) const { - const std::vector &layers = m_seam_per_object.find(po)->second.layers; - int layer_idx = start_seam.first; - - //initialize searching for seam string - cluster of nearby seams on previous and next layers - int next_layer = layer_idx + 1; - int step = 1; - std::pair prev_point_index = start_seam; - std::vector> seam_string { start_seam }; - - auto reverse_lookup_direction = [&]() { - step = -1; - prev_point_index = start_seam; - next_layer = layer_idx - 1; - }; - - while (next_layer >= 0) { - if (next_layer >= int(layers.size())) { - reverse_lookup_direction(); - if (next_layer < 0) { - break; - } - } - float max_distance = SeamPlacer::seam_align_tolerable_dist_factor * - layers[start_seam.first].points[start_seam.second].perimeter.flow_width; - Vec3f prev_position = layers[prev_point_index.first].points[prev_point_index.second].position; - Vec3f projected_position = prev_position; - projected_position.z() = float(po->get_layer(next_layer)->slice_z); - - std::optional> maybe_next_seam = find_next_seam_in_layer(layers, projected_position, - next_layer, - max_distance, comparator); - - if (maybe_next_seam.has_value()) { - // For old macOS (pre 10.14), std::optional does not have .value() method, so the code is using operator*() instead. - seam_string.push_back(maybe_next_seam.operator*()); - prev_point_index = seam_string.back(); - //String added, prev_point_index updated - } else { - if (step == 1) { - reverse_lookup_direction(); - if (next_layer < 0) { - break; - } - } else { - break; - } - } - next_layer += step; - } - return seam_string; -} - -// clusters already chosen seam points into strings across multiple layers, and then -// aligns the strings via polynomial fit -// Does not change the positions of the SeamCandidates themselves, instead stores -// the new aligned position into the shared Perimeter structure of each perimeter -// Note that this position does not necesarilly lay on the perimeter. -void SeamPlacer::align_seam_points(const PrintObject *po, const SeamPlacerImpl::SeamComparator &comparator) { - using namespace SeamPlacerImpl; - - // Prepares Debug files for writing. -#ifdef DEBUG_FILES - Slic3r::CNumericLocalesSetter locales_setter; - auto clusters_f = debug_out_path("seam_clusters.obj"); - FILE *clusters = boost::nowide::fopen(clusters_f.c_str(), "w"); - if (clusters == nullptr) { - BOOST_LOG_TRIVIAL(error) - << "stl_write_obj: Couldn't open " << clusters_f << " for writing"; - return; - } - auto aligned_f = debug_out_path("aligned_clusters.obj"); - FILE *aligns = boost::nowide::fopen(aligned_f.c_str(), "w"); - if (aligns == nullptr) { - BOOST_LOG_TRIVIAL(error) - << "stl_write_obj: Couldn't open " << clusters_f << " for writing"; - return; - } -#endif - - //gather vector of all seams on the print_object - pair of layer_index and seam__index within that layer - const std::vector &layers = m_seam_per_object[po].layers; - std::vector> seams; - for (size_t layer_idx = 0; layer_idx < layers.size(); ++layer_idx) { - const std::vector &layer_perimeter_points = layers[layer_idx].points; - size_t current_point_index = 0; - while (current_point_index < layer_perimeter_points.size()) { - seams.emplace_back(layer_idx, layer_perimeter_points[current_point_index].perimeter.seam_index); - current_point_index = layer_perimeter_points[current_point_index].perimeter.end_index; - } - } - - //sort them before alignment. Alignment is sensitive to initializaion, this gives it better chance to choose something nice - std::stable_sort(seams.begin(), seams.end(), - [&comparator, &layers](const std::pair &left, - const std::pair &right) { - return comparator.is_first_better(layers[left.first].points[left.second], - layers[right.first].points[right.second]); - } - ); - - //align the seam points - start with the best, and check if they are aligned, if yes, skip, else start alignment - // Keeping the vectors outside, so with a bit of luck they will not get reallocated after couple of for loop iterations. - std::vector> seam_string; - std::vector> alternative_seam_string; - std::vector observations; - std::vector observation_points; - std::vector weights; - - int global_index = 0; - while (global_index < int(seams.size())) { - size_t layer_idx = seams[global_index].first; - size_t seam_index = seams[global_index].second; - global_index++; - const std::vector &layer_perimeter_points = layers[layer_idx].points; - if (layer_perimeter_points[seam_index].perimeter.finalized) { - // This perimeter is already aligned, skip seam - continue; - } else { - seam_string = this->find_seam_string(po, { layer_idx, seam_index }, comparator); - size_t step_size = 1 + seam_string.size() / 20; - for (size_t alternative_start = 0; alternative_start < seam_string.size(); alternative_start += step_size) { - size_t start_layer_idx = seam_string[alternative_start].first; - size_t seam_idx = - layers[start_layer_idx].points[seam_string[alternative_start].second].perimeter.seam_index; - alternative_seam_string = this->find_seam_string(po, - std::pair(start_layer_idx, seam_idx), comparator); - if (alternative_seam_string.size() > seam_string.size()) { - seam_string = std::move(alternative_seam_string); - } - } - if (seam_string.size() < seam_align_minimum_string_seams) { - //string NOT long enough to be worth aligning, skip - continue; - } - - // String is long enough, all string seams and potential string seams gathered, now do the alignment - //sort by layer index - std::sort(seam_string.begin(), seam_string.end(), - [](const std::pair &left, const std::pair &right) { - return left.first < right.first; - }); - - //repeat the alignment for the current seam, since it could be skipped due to alternative path being aligned. - global_index--; - - // gather all positions of seams and their weights - observations.resize(seam_string.size()); - observation_points.resize(seam_string.size()); - weights.resize(seam_string.size()); - - auto angle_3d = [](const Vec3f& a, const Vec3f& b){ - return std::abs(acosf(a.normalized().dot(b.normalized()))); - }; - - auto angle_weight = [](float angle){ - return 1.0f / (0.1f + compute_angle_penalty(angle)); - }; - - //gather points positions and weights - float total_length = 0.0f; - Vec3f last_point_pos = layers[seam_string[0].first].points[seam_string[0].second].position; - for (size_t index = 0; index < seam_string.size(); ++index) { - const SeamCandidate ¤t = layers[seam_string[index].first].points[seam_string[index].second]; - float layer_angle = 0.0f; - if (index > 0 && index < seam_string.size() - 1) { - layer_angle = angle_3d( - current.position - - layers[seam_string[index - 1].first].points[seam_string[index - 1].second].position, - layers[seam_string[index + 1].first].points[seam_string[index + 1].second].position - - current.position - ); - } - observations[index] = current.position.head<2>(); - observation_points[index] = current.position.z(); - weights[index] = angle_weight(current.local_ccw_angle); - float curling_influence = layer_angle > 2.0 * std::abs(current.local_ccw_angle) ? -0.8f : 1.0f; - if (current.type == EnforcedBlockedSeamPoint::Enforced) { - curling_influence = 1.0f; - weights[index] += 3.0f; - } - total_length += curling_influence * (last_point_pos - current.position).norm(); - last_point_pos = current.position; - } - - if (comparator.setup == spRear) { - total_length *= 0.3f; - } - - // Curve Fitting - size_t number_of_segments = std::max(size_t(1), - size_t(std::max(0.0f,total_length) / SeamPlacer::seam_align_mm_per_segment)); - auto curve = Geometry::fit_cubic_bspline(observations, observation_points, weights, number_of_segments); - - // Do alignment - compute fitted point for each point in the string from its Z coord, and store the position into - // Perimeter structure of the point; also set flag aligned to true - for (size_t index = 0; index < seam_string.size(); ++index) { - const auto &pair = seam_string[index]; - float t = std::min(1.0f, std::pow(std::abs(layers[pair.first].points[pair.second].local_ccw_angle) - / SeamPlacer::sharp_angle_snapping_threshold, 3.0f)); - if (layers[pair.first].points[pair.second].type == EnforcedBlockedSeamPoint::Enforced){ - t = std::max(0.4f, t); - } - - Vec3f current_pos = layers[pair.first].points[pair.second].position; - Vec2f fitted_pos = curve.get_fitted_value(current_pos.z()); - - //interpolate between current and fitted position, prefer current pos for large weights. - Vec3f final_position = t * current_pos + (1.0f - t) * to_3d(fitted_pos, current_pos.z()); - - Perimeter &perimeter = layers[pair.first].points[pair.second].perimeter; - perimeter.seam_index = pair.second; - perimeter.final_seam_position = final_position; - perimeter.finalized = true; - } - -#ifdef DEBUG_FILES - auto randf = []() { - return float(rand()) / float(RAND_MAX); - }; - Vec3f color { randf(), randf(), randf() }; - for (size_t i = 0; i < seam_string.size(); ++i) { - auto orig_seam = layers[seam_string[i].first].points[seam_string[i].second]; - fprintf(clusters, "v %f %f %f %f %f %f \n", orig_seam.position[0], - orig_seam.position[1], - orig_seam.position[2], color[0], color[1], - color[2]); - } - - color = Vec3f { randf(), randf(), randf() }; - for (size_t i = 0; i < seam_string.size(); ++i) { - const Perimeter &perimeter = layers[seam_string[i].first].points[seam_string[i].second].perimeter; - fprintf(aligns, "v %f %f %f %f %f %f \n", perimeter.final_seam_position[0], - perimeter.final_seam_position[1], - perimeter.final_seam_position[2], color[0], color[1], - color[2]); - } -#endif - } - } - -#ifdef DEBUG_FILES - fclose(clusters); - fclose(aligns); -#endif - -} - -void SeamPlacer::init(const Print &print, std::function throw_if_canceled_func) { - using namespace SeamPlacerImpl; - m_seam_per_object.clear(); - - for (const PrintObject *po : print.objects()) { - throw_if_canceled_func(); - SeamPosition configured_seam_preference = po->config().seam_position.value; - SeamComparator comparator { configured_seam_preference }; - - { - GlobalModelInfo global_model_info { }; - gather_enforcers_blockers(global_model_info, po); - throw_if_canceled_func(); - if (configured_seam_preference == spAligned || configured_seam_preference == spNearest) { - compute_global_occlusion(global_model_info, po, throw_if_canceled_func); - } - throw_if_canceled_func(); - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: gather_seam_candidates: start"; - gather_seam_candidates(po, global_model_info); - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: gather_seam_candidates: end"; - throw_if_canceled_func(); - if (configured_seam_preference == spAligned || configured_seam_preference == spNearest) { - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: calculate_candidates_visibility : start"; - calculate_candidates_visibility(po, global_model_info); - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: calculate_candidates_visibility : end"; - } - } // destruction of global_model_info (large structure, no longer needed) - throw_if_canceled_func(); - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: calculate_overhangs and layer embdedding : start"; - calculate_overhangs_and_layer_embedding(po); - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: calculate_overhangs and layer embdedding: end"; - throw_if_canceled_func(); - if (configured_seam_preference != spNearest) { // For spNearest, the seam is picked in the place_seam method with actual nozzle position information - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: pick_seam_point : start"; - //pick seam point - std::vector &layers = m_seam_per_object[po].layers; - tbb::parallel_for(tbb::blocked_range(0, layers.size()), - [&layers, configured_seam_preference, comparator](tbb::blocked_range r) { - for (size_t layer_idx = r.begin(); layer_idx < r.end(); ++layer_idx) { - std::vector &layer_perimeter_points = layers[layer_idx].points; - for (size_t current = 0; current < layer_perimeter_points.size(); - current = layer_perimeter_points[current].perimeter.end_index) - if (configured_seam_preference == spRandom) - pick_random_seam_point(layer_perimeter_points, current); - else - pick_seam_point(layer_perimeter_points, current, comparator); - } - }); - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: pick_seam_point : end"; - } - throw_if_canceled_func(); - if (configured_seam_preference == spAligned || configured_seam_preference == spRear) { - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: align_seam_points : start"; - align_seam_points(po, comparator); - BOOST_LOG_TRIVIAL(debug) - << "SeamPlacer: align_seam_points : end"; - } - -#ifdef DEBUG_FILES - debug_export_points(m_seam_per_object[po].layers, po->bounding_box(), comparator); -#endif - } -} - -Point SeamPlacer::place_seam(const Layer *layer, const ExtrusionLoop &loop, bool external_first, - const Point &last_pos) const { - using namespace SeamPlacerImpl; +Point Placer::place_seam(const Layer *layer, const ExtrusionLoop &loop, const Point &last_pos) const { const PrintObject *po = layer->object(); // Must not be called with supprot layer. - assert(dynamic_cast(layer) == nullptr); + assert(dynamic_cast(layer) == nullptr); // Object layer IDs are incremented by the number of raft layers. assert(layer->id() >= po->slicing_parameters().raft_layers()); const size_t layer_index = layer->id() - po->slicing_parameters().raft_layers(); - const double unscaled_z = layer->slice_z; - auto get_next_loop_point = [loop](ExtrusionLoop::ClosestPathPoint current) { - current.segment_idx += 1; - if (current.segment_idx >= loop.paths[current.path_idx].polyline.points.size()) { - current.path_idx = next_idx_modulo(current.path_idx, loop.paths.size()); - current.segment_idx = 0; - } - current.foot_pt = loop.paths[current.path_idx].polyline.points[current.segment_idx]; - return current; - }; + const Polygon loop_polygon{Geometry::to_polygon(loop)}; - const PrintObjectSeamData::LayerSeams &layer_perimeters = - m_seam_per_object.find(layer->object())->second.layers[layer_index]; + const bool do_staggering{this->params.staggered_inner_seams && loop.role() == ExtrusionRole::Perimeter}; + const double loop_width{loop.paths.empty() ? 0.0 : loop.paths.front().width()}; - // Find the closest perimeter in the SeamPlacer to this loop. - // Repeat search until two consecutive points of the loop are found, that result in the same closest_perimeter - // This is beacuse with arachne, T-Junctions may exist and sometimes the wrong perimeter was chosen - size_t closest_perimeter_point_index = 0; - { // local space for the closest_perimeter_point_index - Perimeter *closest_perimeter = nullptr; - ExtrusionLoop::ClosestPathPoint closest_point{0,0,loop.paths[0].polyline.points[0]}; - size_t points_count = std::accumulate(loop.paths.begin(), loop.paths.end(), 0, [](size_t acc,const ExtrusionPath& p) { - return acc + p.polyline.points.size(); - }); - for (size_t i = 0; i < points_count; ++i) { - Vec2f unscaled_p = unscaled(closest_point.foot_pt); - closest_perimeter_point_index = find_closest_point(*layer_perimeters.points_tree.get(), - to_3d(unscaled_p, float(unscaled_z))); - if (closest_perimeter != &layer_perimeters.points[closest_perimeter_point_index].perimeter) { - closest_perimeter = &layer_perimeters.points[closest_perimeter_point_index].perimeter; - closest_point = get_next_loop_point(closest_point); - } else { - break; - } - } - } - Vec3f seam_position; - size_t seam_index; - if (const Perimeter &perimeter = layer_perimeters.points[closest_perimeter_point_index].perimeter; - perimeter.finalized) { - seam_position = perimeter.final_seam_position; - seam_index = perimeter.seam_index; + if (po->config().seam_position.value == spNearest) { + const std::vector &perimeters{this->perimeters_per_layer.at(po)[layer_index]}; + const auto [seam_choice, perimeter_index] = place_seam_near(perimeters, loop, last_pos, this->params.max_nearest_detour); + return finalize_seam_position(loop_polygon, seam_choice, perimeters[perimeter_index].perimeter, loop_width, do_staggering); } else { - seam_index = - po->config().seam_position == spNearest ? - pick_nearest_seam_point_index(layer_perimeters.points, perimeter.start_index, - unscaled(last_pos)) : - perimeter.seam_index; - seam_position = layer_perimeters.points[seam_index].position; - } + const std::vector &seams_on_perimeters{this->seams_per_object.at(po)[layer_index]}; - Point seam_point = Point::new_scale(seam_position.x(), seam_position.y()); - - if (loop.role() == ExtrusionRole::Perimeter) { //Hopefully inner perimeter - const SeamCandidate &perimeter_point = layer_perimeters.points[seam_index]; - ExtrusionLoop::ClosestPathPoint projected_point = loop.get_closest_path_and_point(seam_point, false); - // determine depth of the seam point. - float depth = (float) unscale(Point(seam_point - projected_point.foot_pt)).norm(); - float beta_angle = cos(perimeter_point.local_ccw_angle / 2.0f); - size_t index_of_prev = - seam_index == perimeter_point.perimeter.start_index ? - perimeter_point.perimeter.end_index - 1 : - seam_index - 1; - size_t index_of_next = - seam_index == perimeter_point.perimeter.end_index - 1 ? - perimeter_point.perimeter.start_index : - seam_index + 1; - - if ((seam_position - perimeter_point.position).squaredNorm() < depth && // seam is on perimeter point - perimeter_point.local_ccw_angle < -EPSILON // In concave angles - ) { // In this case, we are at internal perimeter, where the external perimeter has seam in concave angle. We want to align - // the internal seam into the concave corner, and not on the perpendicular projection on the closest edge (which is what the split_at function does) - Vec2f dir_to_middle = - ((perimeter_point.position - layer_perimeters.points[index_of_prev].position).head<2>().normalized() - + (perimeter_point.position - layer_perimeters.points[index_of_next].position).head<2>().normalized()) - * 0.5; - depth = 1.4142 * depth / beta_angle; - // There are some nice geometric identities in determination of the correct depth of new seam point. - //overshoot the target depth, in concave angles it will correctly snap to the corner; TODO: find out why such big overshoot is needed. - Vec2f final_pos = perimeter_point.position.head<2>() + depth * dir_to_middle; - projected_point = loop.get_closest_path_and_point(Point::new_scale(final_pos.x(), final_pos.y()), false); - } else { // not concave angle, in that case the nearest point is the good candidate - // but for staggering, we also need to recompute depth of the inner perimter, because in convex corners, the distance is larger than layer width - // we want the perpendicular depth, not distance to nearest point - depth = depth * beta_angle / 1.4142; - } - - seam_point = projected_point.foot_pt; - - //lastly, for internal perimeters, do the staggering if requested - if (po->config().staggered_inner_seams && loop.length() > 0.0) { - //fix depth, it is sometimes strongly underestimated - depth = std::max(loop.paths[projected_point.path_idx].width(), depth); - - while (depth > 0.0f) { - auto next_point = get_next_loop_point(projected_point); - Vec2f a = unscale(projected_point.foot_pt).cast(); - Vec2f b = unscale(next_point.foot_pt).cast(); - float dist = (a - b).norm(); - if (dist > depth) { - Vec2f final_pos = a + (b - a) * depth / dist; - next_point.foot_pt = Point::new_scale(final_pos.x(), final_pos.y()); - } - depth -= dist; - projected_point = next_point; + // Special case. + // If there are only two perimeters and the current perimeter is hole (clockwise). + const int perimeter_count{get_perimeter_count(layer)}; + const bool has_2_or_3_perimeters{perimeter_count == 2 || perimeter_count == 3}; + if (has_2_or_3_perimeters) { + if (seams_on_perimeters.size() == 2 && + seams_on_perimeters[0].perimeter.is_hole != + seams_on_perimeters[1].perimeter.is_hole) { + const SeamPerimeterChoice &seam_perimeter_choice{ + seams_on_perimeters[0].perimeter.is_hole ? seams_on_perimeters[1] : + seams_on_perimeters[0]}; + return finalize_seam_position( + loop_polygon, seam_perimeter_choice.choice, seam_perimeter_choice.perimeter, + loop_width, do_staggering + ); } - seam_point = projected_point.foot_pt; } + + const SeamPerimeterChoice &seam_perimeter_choice{choose_closest_seam(seams_on_perimeters, loop_polygon)}; + return finalize_seam_position(loop_polygon, seam_perimeter_choice.choice, seam_perimeter_choice.perimeter, loop_width, do_staggering); } - - return seam_point; - } - -} // namespace Slic3r +} // namespace Slic3r::Seams diff --git a/src/libslic3r/GCode/SeamPlacer.hpp b/src/libslic3r/GCode/SeamPlacer.hpp index 5603e99..5f3efb8 100644 --- a/src/libslic3r/GCode/SeamPlacer.hpp +++ b/src/libslic3r/GCode/SeamPlacer.hpp @@ -6,159 +6,56 @@ #include #include -#include "libslic3r/libslic3r.h" -#include "libslic3r/ExtrusionEntity.hpp" +#include "libslic3r/GCode/SeamAligned.hpp" #include "libslic3r/Polygon.hpp" -#include "libslic3r/PrintConfig.hpp" -#include "libslic3r/BoundingBox.hpp" -#include "libslic3r/AABBTreeIndirect.hpp" -#include "libslic3r/KDTreeIndirect.hpp" +#include "libslic3r/Print.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/GCode/SeamPerimeters.hpp" +#include "libslic3r/GCode/SeamChoice.hpp" +#include "libslic3r/GCode/ModelVisibility.hpp" -namespace Slic3r { +namespace Slic3r::Seams { -class PrintObject; -class ExtrusionLoop; -class Print; -class Layer; +using ObjectSeams = + std::unordered_map>>; +using ObjectLayerPerimeters = std::unordered_map; -namespace EdgeGrid { -class Grid; -} - -namespace SeamPlacerImpl { - - -struct GlobalModelInfo; -struct SeamComparator; - -enum class EnforcedBlockedSeamPoint { - Blocked = 0, - Neutral = 1, - Enforced = 2, -}; - -// struct representing single perimeter loop -struct Perimeter { - size_t start_index{}; - size_t end_index{}; //inclusive! - size_t seam_index{}; - float flow_width{}; - - // During alignment, a final position may be stored here. In that case, finalized is set to true. - // Note that final seam position is not limited to points of the perimeter loop. In theory it can be any position - // Random position also uses this flexibility to set final seam point position - bool finalized = false; - Vec3f final_seam_position = Vec3f::Zero(); -}; - -//Struct over which all processing of perimeters is done. For each perimeter point, its respective candidate is created, -// then all the needed attributes are computed and finally, for each perimeter one point is chosen as seam. -// This seam position can be then further aligned -struct SeamCandidate { - SeamCandidate(const Vec3f &pos, Perimeter &perimeter, - float local_ccw_angle, - EnforcedBlockedSeamPoint type) : - position(pos), perimeter(perimeter), visibility(0.0f), overhang(0.0f), embedded_distance(0.0f), local_ccw_angle( - local_ccw_angle), type(type), central_enforcer(false) { - } - const Vec3f position; - // pointer to Perimeter loop of this point. It is shared across all points of the loop - Perimeter &perimeter; - float visibility; - float overhang; - // distance inside the merged layer regions, for detecting perimeter points which are hidden indside the print (e.g. multimaterial join) - // Negative sign means inside the print, comes from EdgeGrid structure - float embedded_distance; - float local_ccw_angle; - EnforcedBlockedSeamPoint type; - bool central_enforcer; //marks this candidate as central point of enforced segment on the perimeter - important for alignment -}; - -struct SeamCandidateCoordinateFunctor { - SeamCandidateCoordinateFunctor(const std::vector &seam_candidates) : - seam_candidates(seam_candidates) { - } - const std::vector &seam_candidates; - float operator()(size_t index, size_t dim) const { - return seam_candidates[index].position[dim]; - } -}; -} // namespace SeamPlacerImpl - -struct PrintObjectSeamData +struct Params { - using SeamCandidatesTree = KDTreeIndirect<3, float, SeamPlacerImpl::SeamCandidateCoordinateFunctor>; - - struct LayerSeams - { - Slic3r::deque perimeters; - std::vector points; - std::unique_ptr points_tree; - }; - // Map of PrintObjects (PO) -> vector of layers of PO -> vector of perimeter - std::vector layers; - // Map of PrintObjects (PO) -> vector of layers of PO -> unique_ptr to KD - // tree of all points of the given layer - - void clear() - { - layers.clear(); - } + double max_nearest_detour; + double rear_tolerance; + double rear_y_offset; + Aligned::Params aligned; + double max_distance{}; + unsigned random_seed{}; + double convex_visibility_modifier{}; + double concave_visibility_modifier{}; + Perimeters::PerimeterParams perimeter; + Slic3r::ModelInfo::Visibility::Params visibility; + bool staggered_inner_seams; }; -class SeamPlacer { +std::ostream& operator<<(std::ostream& os, const Params& params); + +class Placer +{ public: - // Number of samples generated on the mesh. There are sqr_rays_per_sample_point*sqr_rays_per_sample_point rays casted from each samples - static constexpr size_t raycasting_visibility_samples_count = 30000; - static constexpr size_t fast_decimation_triangle_count_target = 16000; - //square of number of rays per sample point - static constexpr size_t sqr_rays_per_sample_point = 5; + static Params get_params(const DynamicPrintConfig &config); - // snapping angle - angles larger than this value will be snapped to during seam painting - static constexpr float sharp_angle_snapping_threshold = 55.0f * float(PI) / 180.0f; - // overhang angle for seam placement that still yields good results, in degrees, measured from vertical direction - static constexpr float overhang_angle_threshold = 50.0f * float(PI) / 180.0f; + void init( + SpanOfConstPtrs objects, + const Params ¶ms, + const std::function &throw_if_canceled + ); - // determines angle importance compared to visibility ( neutral value is 1.0f. ) - static constexpr float angle_importance_aligned = 0.6f; - static constexpr float angle_importance_nearest = 1.0f; // use much higher angle importance for nearest mode, to combat the visibility info noise - - // For long polygon sides, if they are close to the custom seam drawings, they are oversampled with this step size - static constexpr float enforcer_oversampling_distance = 0.2f; - - // When searching for seam clusters for alignment: - // following value describes, how much worse score can point have and still be picked into seam cluster instead of original seam point on the same layer - static constexpr float seam_align_score_tolerance = 0.3f; - // seam_align_tolerable_dist_factor - how far to search for seam from current position, final dist is seam_align_tolerable_dist_factor * flow_width - static constexpr float seam_align_tolerable_dist_factor = 4.0f; - // minimum number of seams needed in cluster to make alignment happen - static constexpr size_t seam_align_minimum_string_seams = 6; - // millimeters covered by spline; determines number of splines for the given string - static constexpr size_t seam_align_mm_per_segment = 4.0f; - - //The following data structures hold all perimeter points for all PrintObject. - std::unordered_map m_seam_per_object; - - void init(const Print &print, std::function throw_if_canceled_func); - - Point place_seam(const Layer *layer, const ExtrusionLoop &loop, bool external_first, const Point &last_pos) const; + Point place_seam(const Layer *layer, const ExtrusionLoop &loop, const Point &last_pos) const; private: - void gather_seam_candidates(const PrintObject *po, const SeamPlacerImpl::GlobalModelInfo &global_model_info); - void calculate_candidates_visibility(const PrintObject *po, - const SeamPlacerImpl::GlobalModelInfo &global_model_info); - void calculate_overhangs_and_layer_embedding(const PrintObject *po); - void align_seam_points(const PrintObject *po, const SeamPlacerImpl::SeamComparator &comparator); - std::vector> find_seam_string(const PrintObject *po, - std::pair start_seam, - const SeamPlacerImpl::SeamComparator &comparator) const; - std::optional> find_next_seam_in_layer( - const std::vector &layers, - const Vec3f& projected_position, - const size_t layer_idx, const float max_distance, - const SeamPlacerImpl::SeamComparator &comparator) const; + Params params; + ObjectSeams seams_per_object; + ObjectLayerPerimeters perimeters_per_layer; }; -} // namespace Slic3r +} // namespace Slic3r::Seams #endif // libslic3r_SeamPlacer_hpp_ diff --git a/src/libslic3r/GCode/SeamRandom.cpp b/src/libslic3r/GCode/SeamRandom.cpp new file mode 100644 index 0000000..5681e57 --- /dev/null +++ b/src/libslic3r/GCode/SeamRandom.cpp @@ -0,0 +1,158 @@ +#include +#include +#include +#include + +#include "libslic3r/GCode/SeamRandom.hpp" +#include "libslic3r/GCode/SeamChoice.hpp" +#include "libslic3r/Point.hpp" + +namespace Slic3r::Seams::Random { +using Perimeters::PointType; +using Perimeters::PointClassification; + +namespace Impl { +std::vector get_segments( + const Perimeters::Perimeter &perimeter, + const PointType point_type, + const PointClassification point_classification +) { + const std::vector &positions{perimeter.positions}; + const std::vector &point_types{perimeter.point_types}; + const std::vector &point_classifications{perimeter.point_classifications}; + + std::optional current_begin; + std::optional current_begin_index; + Vec2d previous_position{positions.front()}; + double distance{0.0}; + std::vector result; + for (std::size_t index{0}; index < positions.size(); ++index) { + distance += (positions[index] - previous_position).norm(); + previous_position = positions[index]; + + if (point_types[index] == point_type && + point_classifications[index] == point_classification) { + if (!current_begin) { + current_begin = distance; + current_begin_index = index; + } + } else { + if (current_begin) { + result.push_back(PerimeterSegment{*current_begin, distance, *current_begin_index}); + } + current_begin = std::nullopt; + current_begin_index = std::nullopt; + } + } + + if (current_begin) { + result.push_back(PerimeterSegment{*current_begin, distance, *current_begin_index}); + } + return result; +} + +PerimeterSegment pick_random_segment( + const std::vector &segments, std::mt19937 &random_engine +) { + double length{0.0}; + for (const PerimeterSegment &segment : segments) { + length += segment.length(); + } + + std::uniform_real_distribution distribution{0.0, length}; + double random_distance{distribution(random_engine)}; + + double distance{0.0}; + return *std::find_if(segments.begin(), segments.end(), [&](const PerimeterSegment &segment) { + if (random_distance >= distance && random_distance <= distance + segment.length()) { + return true; + } + distance += segment.length(); + return false; + }); +} + +SeamChoice pick_random_point( + const PerimeterSegment &segment, const Perimeters::Perimeter &perimeter, std::mt19937 &random_engine +) { + const std::vector &positions{perimeter.positions}; + + if (segment.length() < std::numeric_limits::epsilon()) { + return {segment.begin_index, segment.begin_index, positions[segment.begin_index]}; + } + + std::uniform_real_distribution distribution{0.0, segment.length()}; + const double random_distance{distribution(random_engine)}; + + double distance{0.0}; + std::size_t previous_index{segment.begin_index}; + for (std::size_t index{segment.begin_index + 1}; index < perimeter.positions.size(); ++index) { + const Vec2d edge{positions[index] - positions[previous_index]}; + + if (distance + edge.norm() >= random_distance) { + if (random_distance - distance < std::numeric_limits::epsilon()) { + index = previous_index; + } else if (distance + edge.norm() - random_distance < std::numeric_limits::epsilon()) { + previous_index = index; + } + + const double remaining_distance{random_distance - distance}; + const Vec2d position{positions[previous_index] + remaining_distance * edge.normalized()}; + return {previous_index, index, position}; + } + + distance += edge.norm(); + previous_index = index; + } + + // Should be unreachable. + return {segment.begin_index, segment.begin_index, positions[segment.begin_index]}; +} + +std::optional Random::operator()( + const Perimeters::Perimeter &perimeter, + const PointType point_type, + const PointClassification point_classification +) const { + std::vector segments{ + get_segments(perimeter, point_type, point_classification)}; + + if (!segments.empty()) { + const PerimeterSegment segment{pick_random_segment(segments, random_engine)}; + return pick_random_point(segment, perimeter, random_engine); + } + return std::nullopt; +} +} // namespace Impl + +std::vector> get_object_seams( + Perimeters::LayerPerimeters &&perimeters, const unsigned fixed_seed +) { + std::mt19937 random_engine{fixed_seed}; + const Impl::Random random{random_engine}; + + std::vector> result; + + for (std::vector &layer : perimeters) { + result.emplace_back(); + for (Perimeters::BoundedPerimeter &perimeter : layer) { + if (perimeter.perimeter.is_degenerate) { + std::optional seam_choice{ + Seams::choose_degenerate_seam_point(perimeter.perimeter)}; + if (seam_choice) { + result.back().push_back( + SeamPerimeterChoice{*seam_choice, std::move(perimeter.perimeter)} + ); + } else { + result.back().push_back(SeamPerimeterChoice{SeamChoice{}, std::move(perimeter.perimeter)}); + } + } else { + result.back().push_back(SeamPerimeterChoice{ + Seams::choose_seam_point(perimeter.perimeter, random), + std::move(perimeter.perimeter)}); + } + } + } + return result; +} +} // namespace Slic3r::Seams::Random diff --git a/src/libslic3r/GCode/SeamRandom.hpp b/src/libslic3r/GCode/SeamRandom.hpp new file mode 100644 index 0000000..a21268d --- /dev/null +++ b/src/libslic3r/GCode/SeamRandom.hpp @@ -0,0 +1,41 @@ +#include +#include +#include +#include + +#include "libslic3r/GCode/SeamChoice.hpp" +#include "libslic3r/GCode/SeamPerimeters.hpp" + +namespace Slic3r { +namespace Seams { +struct SeamChoice; +struct SeamPerimeterChoice; +} // namespace Seams +} // namespace Slic3r + +namespace Slic3r::Seams::Random { +namespace Impl { +struct PerimeterSegment +{ + double begin{}; + double end{}; + std::size_t begin_index{}; + + double length() const { return end - begin; } +}; + +struct Random +{ + std::mt19937 &random_engine; + + std::optional operator()( + const Perimeters::Perimeter &perimeter, + const Perimeters::PointType point_type, + const Perimeters::PointClassification point_classification + ) const; +}; +} +std::vector> get_object_seams( + Perimeters::LayerPerimeters &&perimeters, const unsigned fixed_seed +); +} diff --git a/src/libslic3r/GCode/SeamRear.cpp b/src/libslic3r/GCode/SeamRear.cpp new file mode 100644 index 0000000..f17dd79 --- /dev/null +++ b/src/libslic3r/GCode/SeamRear.cpp @@ -0,0 +1,112 @@ +#include "libslic3r/GCode/SeamRear.hpp" + +#include +#include +#include + +#include "libslic3r/AABBTreeLines.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/GCode/SeamChoice.hpp" +#include "libslic3r/GCode/SeamPerimeters.hpp" +#include "libslic3r/GCode/SeamShells.hpp" + +namespace Slic3r::Seams::Rear { +using Perimeters::PointType; +using Perimeters::PointClassification; + +namespace Impl { + +BoundingBoxf get_bounding_box(const Shells::Shell<> &shell) { + BoundingBoxf result; + for (const Shells::Slice<> &slice : shell) { + result.merge(BoundingBoxf{slice.boundary.positions}); + } + return result; +} + +struct RearestPointCalculator { + double rear_tolerance; + double rear_y_offset; + BoundingBoxf bounding_box; + + std::optional operator()( + const Perimeters::Perimeter &perimeter, + const PointType point_type, + const PointClassification point_classification + ) { + std::vector possible_lines; + for (std::size_t i{0}; i < perimeter.positions.size() - 1; ++i) { + if (perimeter.point_types[i] != point_type) { + continue; + } + if (perimeter.point_classifications[i] != point_classification) { + continue; + } + if (perimeter.point_types[i + 1] != point_type) { + continue; + } + if (perimeter.point_classifications[i + 1] != point_classification) { + continue; + } + possible_lines.push_back(PerimeterLine{perimeter.positions[i], perimeter.positions[i+1], i, i + 1}); + } + if (possible_lines.empty()) { + return std::nullopt; + } + const BoundingBoxf bounding_box{perimeter.positions}; + const AABBTreeLines::LinesDistancer possible_distancer{possible_lines}; + const double center_x{(bounding_box.max.x() + bounding_box.min.x()) / 2.0}; + const Vec2d prefered_position{center_x, bounding_box.max.y() + rear_y_offset}; + auto [_, line_index, point] = possible_distancer.distance_from_lines_extra(prefered_position); + const Vec2d location_at_bb{center_x, bounding_box.max.y()}; + auto [_d, line_index_at_bb, point_at_bb] = possible_distancer.distance_from_lines_extra(location_at_bb); + const double y_distance{point.y() - point_at_bb.y()}; + + Vec2d result{point}; + if (y_distance < 0) { + result = point_at_bb; + } else if (y_distance <= rear_tolerance) { + const double factor{y_distance / rear_tolerance}; + result = factor * point + (1 - factor) * point_at_bb; + } + return SeamChoice{possible_lines[line_index].previous_index, possible_lines[line_index].next_index, result}; + } +}; +} // namespace Impl + +std::vector> get_object_seams( + std::vector> &&perimeters, + const double rear_tolerance, + const double rear_y_offset +) { + std::vector> result; + + for (std::vector &layer : perimeters) { + result.emplace_back(); + for (Perimeters::BoundedPerimeter &perimeter : layer) { + if (perimeter.perimeter.is_degenerate) { + std::optional seam_choice{ + Seams::choose_degenerate_seam_point(perimeter.perimeter)}; + if (seam_choice) { + result.back().push_back( + SeamPerimeterChoice{*seam_choice, std::move(perimeter.perimeter)} + ); + } else { + result.back().push_back(SeamPerimeterChoice{SeamChoice{}, std::move(perimeter.perimeter)}); + } + } else { + BoundingBoxf bounding_box{unscaled(perimeter.bounding_box)}; + const SeamChoice seam_choice{Seams::choose_seam_point( + perimeter.perimeter, + Impl::RearestPointCalculator{rear_tolerance, rear_y_offset, bounding_box} + )}; + result.back().push_back( + SeamPerimeterChoice{seam_choice, std::move(perimeter.perimeter)} + ); + } + } + } + + return result; +} +} // namespace Slic3r::Seams::Rear diff --git a/src/libslic3r/GCode/SeamRear.hpp b/src/libslic3r/GCode/SeamRear.hpp new file mode 100644 index 0000000..de36a03 --- /dev/null +++ b/src/libslic3r/GCode/SeamRear.hpp @@ -0,0 +1,41 @@ +#ifndef libslic3r_SeamRear_hpp_ +#define libslic3r_SeamRear_hpp_ + +#include +#include + +#include "libslic3r/GCode/SeamPerimeters.hpp" +#include "libslic3r/GCode/SeamChoice.hpp" +#include "libslic3r/Point.hpp" + +namespace Slic3r { +namespace Seams { +namespace Perimeters { +struct BoundedPerimeter; +} // namespace Perimeters +struct SeamPerimeterChoice; +} // namespace Seams +} // namespace Slic3r + +namespace Slic3r::Seams::Rear { +namespace Impl { +struct PerimeterLine +{ + Vec2d a; + Vec2d b; + std::size_t previous_index; + std::size_t next_index; + + using Scalar = Vec2d::Scalar; + static const constexpr int Dim = 2; +}; +} // namespace Impl + +std::vector> get_object_seams( + std::vector> &&perimeters, + const double rear_tolerance, + const double rear_y_offet +); +} // namespace Slic3r::Seams::Rear + +#endif // libslic3r_SeamRear_hpp_ diff --git a/src/libslic3r/GCode/SeamShells.cpp b/src/libslic3r/GCode/SeamShells.cpp new file mode 100644 index 0000000..dbe60a1 --- /dev/null +++ b/src/libslic3r/GCode/SeamShells.cpp @@ -0,0 +1,71 @@ +#include "libslic3r/GCode/SeamShells.hpp" + +#include +#include +#include + +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/GCode/SeamGeometry.hpp" + +namespace Slic3r::Seams::Shells::Impl { + +Shells<> map_to_shells( + Perimeters::LayerPerimeters &&layers, const Geometry::Mapping &mapping, const std::size_t shell_count +) { + Shells<> result(shell_count); + for (std::size_t layer_index{0}; layer_index < layers.size(); ++layer_index) { + Perimeters::BoundedPerimeters &perimeters{layers[layer_index]}; + for (std::size_t perimeter_index{0}; perimeter_index < perimeters.size(); + perimeter_index++) { + Perimeters::Perimeter &perimeter{perimeters[perimeter_index].perimeter}; + result[mapping[layer_index][perimeter_index]].push_back( + Slice<>{std::move(perimeter), layer_index} + ); + } + } + return result; +} +} // namespace Slic3r::Seams::Shells::Impl + +namespace Slic3r::Seams::Shells { +Shells<> create_shells( + Perimeters::LayerPerimeters &&perimeters, const double max_distance +) { + using Perimeters::BoundedPerimeters; + using Perimeters::BoundedPerimeter; + + std::vector layer_sizes; + layer_sizes.reserve(perimeters.size()); + for (const BoundedPerimeters &layer : perimeters) { + layer_sizes.push_back(layer.size()); + } + + const auto &[shell_mapping, shell_count]{Geometry::get_mapping( + layer_sizes, + [&](const std::size_t layer_index, + const std::size_t item_index) -> Geometry::MappingOperatorResult { + const BoundedPerimeters &layer{perimeters[layer_index]}; + const BoundedPerimeters &next_layer{perimeters[layer_index + 1]}; + if (next_layer.empty()) { + return std::nullopt; + } + + BoundingBoxes next_layer_bounding_boxes; + for (const BoundedPerimeter &bounded_perimeter : next_layer) { + next_layer_bounding_boxes.emplace_back(bounded_perimeter.bounding_box); + } + + const auto [perimeter_index, distance] = Geometry::pick_closest_bounding_box( + layer[item_index].bounding_box, next_layer_bounding_boxes + ); + + if (distance > max_distance) { + return std::nullopt; + } + return std::pair{perimeter_index, 1.0 / distance}; + } + )}; + + return Impl::map_to_shells(std::move(perimeters), shell_mapping, shell_count); +} +} // namespace Slic3r::Seams::Shells diff --git a/src/libslic3r/GCode/SeamShells.hpp b/src/libslic3r/GCode/SeamShells.hpp new file mode 100644 index 0000000..d428d76 --- /dev/null +++ b/src/libslic3r/GCode/SeamShells.hpp @@ -0,0 +1,46 @@ +#ifndef libslic3r_SeamShells_hpp_ +#define libslic3r_SeamShells_hpp_ + +#include +#include +#include + +#include "libslic3r/GCode/SeamPerimeters.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/GCode/SeamGeometry.hpp" + +namespace Slic3r { +class Layer; +} + +namespace Slic3r::Seams::Shells { +template struct Slice +{ + T boundary; + std::size_t layer_index; +}; + +template using Shell = std::vector>; + +template using Shells = std::vector>; + +inline std::size_t get_layer_count( + const Shells<> &shells +) { + std::size_t layer_count{0}; + for (const Shell<> &shell : shells) { + for (const Slice<>& slice : shell) { + if (slice.layer_index >= layer_count) { + layer_count = slice.layer_index + 1; + } + } + } + return layer_count; +} + +Shells<> create_shells( + Perimeters::LayerPerimeters &&perimeters, const double max_distance +); +} // namespace Slic3r::Seams::Shells + +#endif // libslic3r_SeamShells_hpp_ diff --git a/src/libslic3r/GCode/SmoothPath.cpp b/src/libslic3r/GCode/SmoothPath.cpp index f65d7b7..54ba3c1 100644 --- a/src/libslic3r/GCode/SmoothPath.cpp +++ b/src/libslic3r/GCode/SmoothPath.cpp @@ -1,7 +1,17 @@ #include "SmoothPath.hpp" +#include +#include +#include +#include +#include + #include "../ExtrusionEntity.hpp" #include "../ExtrusionEntityCollection.hpp" +#include "libslic3r/Geometry/ArcWelder.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polyline.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r::GCode { @@ -31,75 +41,46 @@ std::optional sample_path_point_at_distance_from_start(const SmoothPath & { if (distance >= 0) { for (const SmoothPathElement &el : path) { - auto it = el.path.begin(); - auto end = el.path.end(); - Point prev_point = it->point; - for (++ it; it != end; ++ it) { - Point point = it->point; - if (it->linear()) { + Point prev_point = el.path.front().point; + for (auto segment_it = el.path.begin() + 1; segment_it != el.path.end(); ++segment_it) { + Point point = segment_it->point; + if (segment_it->linear()) { // Linear segment - Vec2d v = (point - prev_point).cast(); - double lsqr = v.squaredNorm(); + const Vec2d v = (point - prev_point).cast(); + const double lsqr = v.squaredNorm(); if (lsqr > sqr(distance)) - return std::make_optional(prev_point + (v * (distance / sqrt(lsqr))).cast()); + return prev_point + (v * (distance / sqrt(lsqr))).cast(); distance -= sqrt(lsqr); } else { // Circular segment - float angle = Geometry::ArcWelder::arc_angle(prev_point.cast(), point.cast(), it->radius); - double len = std::abs(it->radius) * angle; + const float angle = Geometry::ArcWelder::arc_angle(prev_point.cast(), point.cast(), segment_it->radius); + const double len = std::abs(segment_it->radius) * angle; if (len > distance) { - // Rotate the segment end point in reverse towards the start point. - return std::make_optional(prev_point.rotated(- angle * (distance / len), - Geometry::ArcWelder::arc_center(prev_point.cast(), point.cast(), it->radius, it->ccw()).cast())); + const Point center_pt = Geometry::ArcWelder::arc_center(prev_point.cast(), point.cast(), segment_it->radius, segment_it->ccw()).cast(); + const float rotation_dir = (segment_it->ccw() ? 1.f : -1.f); + // Rotate the segment start point based on the arc orientation. + return prev_point.rotated(rotation_dir * angle * (distance / len), center_pt); } + distance -= len; } + if (distance < 0) - return std::make_optional(point); + return point; + prev_point = point; } } } + // Failed. return {}; } -std::optional sample_path_point_at_distance_from_end(const SmoothPath &path, double distance) -{ - if (distance >= 0) { - for (const SmoothPathElement& el : path) { - auto it = el.path.begin(); - auto end = el.path.end(); - Point prev_point = it->point; - for (++it; it != end; ++it) { - Point point = it->point; - if (it->linear()) { - // Linear segment - Vec2d v = (point - prev_point).cast(); - double lsqr = v.squaredNorm(); - if (lsqr > sqr(distance)) - return std::make_optional(prev_point + (v * (distance / sqrt(lsqr))).cast()); - distance -= sqrt(lsqr); - } - else { - // Circular segment - float angle = Geometry::ArcWelder::arc_angle(prev_point.cast(), point.cast(), it->radius); - double len = std::abs(it->radius) * angle; - if (len > distance) { - // Rotate the segment end point in reverse towards the start point. - return std::make_optional(prev_point.rotated(-angle * (distance / len), - Geometry::ArcWelder::arc_center(prev_point.cast(), point.cast(), it->radius, it->ccw()).cast())); - } - distance -= len; - } - if (distance < 0) - return std::make_optional(point); - prev_point = point; - } - } - } - // Failed. - return {}; +std::optional sample_path_point_at_distance_from_end(const SmoothPath &path, double distance) { + SmoothPath path_reversed = path; + reverse(path_reversed); + return sample_path_point_at_distance_from_start(path_reversed, distance); } // Clip length of a smooth path, for seam hiding. @@ -138,6 +119,7 @@ void reverse(SmoothPath &path) { for (SmoothPathElement &path_element : path) Geometry::ArcWelder::reverse(path_element.path); } + void SmoothPathCache::interpolate_add(const ExtrusionPath &path, const InterpolationParameters ¶ms) { double tolerance = params.tolerance; diff --git a/src/libslic3r/GCode/SmoothPath.hpp b/src/libslic3r/GCode/SmoothPath.hpp index 4e5e6cf..f4240e8 100644 --- a/src/libslic3r/GCode/SmoothPath.hpp +++ b/src/libslic3r/GCode/SmoothPath.hpp @@ -2,6 +2,8 @@ #define slic3r_GCode_SmoothPath_hpp_ #include +#include +#include #include "../ExtrusionEntity.hpp" #include "../Geometry/ArcWelder.hpp" @@ -9,6 +11,8 @@ namespace Slic3r { class ExtrusionEntityCollection; +class Point; +class Polyline; namespace GCode { @@ -34,6 +38,7 @@ std::optional sample_path_point_at_distance_from_end(const SmoothPath &pa double clip_end(SmoothPath &path, double distance, double min_point_distance_threshold); void reverse(SmoothPath &path); + class SmoothPathCache { public: diff --git a/src/libslic3r/GCode/SpiralVase.cpp b/src/libslic3r/GCode/SpiralVase.cpp index 0c53b78..2ed78f8 100644 --- a/src/libslic3r/GCode/SpiralVase.cpp +++ b/src/libslic3r/GCode/SpiralVase.cpp @@ -1,8 +1,11 @@ #include "SpiralVase.hpp" -#include "GCode.hpp" -#include -#include -#include +#include +#include + +#include "libslic3r/AABBTreeLines.hpp" +#include "libslic3r/GCode/GCodeWriter.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { @@ -24,19 +27,19 @@ std::string SpiralVase::process_layer(const std::string &gcode, bool last_layer) at the beginning - each layer is composed by suitable geometry (i.e. a single complete loop) - loops were not clipped before calling this method */ - + // If we're not going to modify G-code, just feed it to the reader // in order to update positions. - if (! m_enabled) { + if (!m_enabled) { m_reader.parse_buffer(gcode); return gcode; } - + // Get total XY length for this layer by summing all extrusion moves. float total_layer_length = 0.f; float layer_height = 0.f; - float z = 0.f; - + float z = 0.f; + { //FIXME Performance warning: This copies the GCodeConfig of the reader. GCodeReader r = m_reader; // clone @@ -56,10 +59,10 @@ std::string SpiralVase::process_layer(const std::string &gcode, bool last_layer) } }); } - - // Remove layer height from initial Z. + + // Remove layer height from initial Z. z -= layer_height; - + // FIXME Tapering of the transition layer and smoothing only works reliably with relative extruder distances. // For absolute extruder distances it will be switched off. // Tapering the absolute extruder distances requires to process every extrusion value after the first transition @@ -70,7 +73,8 @@ std::string SpiralVase::process_layer(const std::string &gcode, bool last_layer) const AABBTreeLines::LinesDistancer previous_layer_distancer = get_layer_distancer(m_previous_layer); Vec2f last_point = m_previous_layer.empty() ? Vec2f::Zero() : m_previous_layer.back(); - float len = 0.f; + float len = 0.f; + std::string new_gcode, transition_gcode; std::vector current_layer; m_reader.parse_buffer(gcode, [z, total_layer_length, layer_height, transition_in, transition_out, smooth_spiral, max_xy_smoothing = m_max_xy_smoothing, @@ -123,30 +127,30 @@ std::string SpiralVase::process_layer(const std::string &gcode, bool last_layer) // Scale the extrusion amount according to change in length line.set(reader, E, line.e() * modified_dist_XY / dist_XY, 5); last_point = target; - } else { + } else { last_point = p; } } if (emit_gcode_line) new_gcode += line.raw() + '\n'; - } - return; - - /* Skip travel moves: the move to first perimeter point will - cause a visible seam when loops are not aligned in XY; by skipping - it we blend the first loop move in the XY plane (although the smoothness - of such blend depend on how long the first segment is; maybe we should + } + return; + /* Skip travel moves: the move to first perimeter point will + cause a visible seam when loops are not aligned in XY; by skipping + it we blend the first loop move in the XY plane (although the smoothness + of such blend depend on how long the first segment is; maybe we should enforce some minimum length?). When smooth_spiral is enabled, we're gonna end up exactly where the next layer should start anyway, so we don't need the travel move */ - } } + } + new_gcode += line.raw() + '\n'; if (transition_out) transition_gcode += line.raw() + '\n'; }); - + m_previous_layer = std::move(current_layer); return new_gcode + transition_gcode; } diff --git a/src/libslic3r/GCode/SpiralVase.hpp b/src/libslic3r/GCode/SpiralVase.hpp index ad4f40d..85ae3b7 100644 --- a/src/libslic3r/GCode/SpiralVase.hpp +++ b/src/libslic3r/GCode/SpiralVase.hpp @@ -1,8 +1,14 @@ #ifndef slic3r_SpiralVase_hpp_ #define slic3r_SpiralVase_hpp_ -#include "../libslic3r.h" -#include "../GCodeReader.hpp" +#include +#include +#include + +#include "libslic3r/libslic3r.h" +#include "libslic3r/GCodeReader.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/PrintConfig.hpp" namespace Slic3r { @@ -13,8 +19,9 @@ public: explicit SpiralVase(const PrintConfig &config) : m_config(config) { - m_reader.z() = (float)m_config.z_offset; + m_reader.z() = (float) m_config.z_offset; m_reader.apply_config(m_config); + const double max_nozzle_diameter = *std::max_element(config.nozzle_diameter.values.begin(), config.nozzle_diameter.values.end()); m_max_xy_smoothing = float(2. * max_nozzle_diameter); }; @@ -26,7 +33,7 @@ public: } std::string process_layer(const std::string &gcode, bool last_layer); - + private: const PrintConfig &m_config; GCodeReader m_reader; @@ -39,7 +46,6 @@ private: bool m_smooth_spiral = true; std::vector m_previous_layer; }; - } #endif // slic3r_SpiralVase_hpp_ diff --git a/src/libslic3r/GCode/ThumbnailData.hpp b/src/libslic3r/GCode/ThumbnailData.hpp index 195dfe6..f82d61c 100644 --- a/src/libslic3r/GCode/ThumbnailData.hpp +++ b/src/libslic3r/GCode/ThumbnailData.hpp @@ -2,6 +2,8 @@ #define slic3r_ThumbnailData_hpp_ #include +#include + #include "libslic3r/Point.hpp" namespace Slic3r { diff --git a/src/libslic3r/GCode/Thumbnails.cpp b/src/libslic3r/GCode/Thumbnails.cpp index 62f558b..26dbd84 100644 --- a/src/libslic3r/GCode/Thumbnails.cpp +++ b/src/libslic3r/GCode/Thumbnails.cpp @@ -1,12 +1,22 @@ #include "Thumbnails.hpp" -#include "../miniz_extension.hpp" -#include "../format.hpp" -#include +#include #include -#include -#include +#include +#include +#include +#include #include +#include + +#include "libslic3r/miniz_extension.hpp" // IWYU pragma: keep +#include "../format.hpp" +#include "libslic3r/Config.hpp" +#include "libslic3r/GCode/ThumbnailData.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "miniz.h" + namespace Slic3r::GCodeThumbnails { using namespace std::literals; @@ -29,7 +39,6 @@ struct CompressedQOI : CompressedImageBuffer std::string_view tag() const override { return "thumbnail_QOI"sv; } }; - std::unique_ptr compress_thumbnail_png(const ThumbnailData &data) { auto out = std::make_unique(); @@ -37,7 +46,6 @@ std::unique_ptr compress_thumbnail_png(const ThumbnailDat return out; } - std::unique_ptr compress_thumbnail_jpg(const ThumbnailData& data) { // Take vector of RGBA pixels and flip the image vertically diff --git a/src/libslic3r/GCode/Thumbnails.hpp b/src/libslic3r/GCode/Thumbnails.hpp index f92552b..2e63739 100644 --- a/src/libslic3r/GCode/Thumbnails.hpp +++ b/src/libslic3r/GCode/Thumbnails.hpp @@ -1,22 +1,31 @@ #ifndef slic3r_GCodeThumbnails_hpp_ #define slic3r_GCodeThumbnails_hpp_ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "../Point.hpp" #include "../PrintConfig.hpp" #include "ThumbnailData.hpp" - -#include -#include -#include - -#include -#include - -#include "../libslic3r/enum_bitmask.hpp" +#include "libslic3r/enum_bitmask.hpp" //B3 #include "DataType.h" namespace Slic3r { +class ConfigBase; + enum class ThumbnailError : int { InvalidVal, OutOfRange, InvalidExt }; using ThumbnailErrors = enum_bitmask; ENABLE_ENUM_BITMASK_OPERATORS(ThumbnailError); @@ -192,7 +201,6 @@ inline void export_qidi_thumbnails_to_file(ThumbnailsGeneratorCallback & } } - template inline void generate_binary_thumbnails(ThumbnailsGeneratorCallback& thumbnail_cb, std::vector& out_thumbnails, const std::vector> &thumbnails_list, ThrowIfCanceledCallback throw_if_canceled) @@ -224,9 +232,6 @@ inline void generate_binary_thumbnails(ThumbnailsGeneratorCallback& thumbnail_cb } } - - - } // namespace Slic3r::GCodeThumbnails #endif // slic3r_GCodeThumbnails_hpp_ diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index 461b346..95b40a6 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -1,6 +1,13 @@ -#include "Print.hpp" -#include "ToolOrdering.hpp" -#include "Layer.hpp" +#include "libslic3r/Print.hpp" +#include "libslic3r/Layer.hpp" +#include "libslic3r/GCode/ToolOrdering.hpp" +#include "libslic3r/CustomGCode.hpp" +#include "libslic3r/ExtrusionEntity.hpp" +#include "libslic3r/ExtrusionEntityCollection.hpp" +#include "libslic3r/ExtrusionRole.hpp" +#include "libslic3r/LayerRegion.hpp" +#include "libslic3r/Model.hpp" +#include "tcbspan/span.hpp" // #define SLIC3R_DEBUG @@ -11,12 +18,12 @@ #undef NDEBUG #endif +#include +#include #include #include - -#include - -#include +#include +#include namespace Slic3r { @@ -247,6 +254,7 @@ void ToolOrdering::collect_extruders( unsigned int extruder_override = 0; std::vector>::const_iterator it_per_layer_color_changes = per_layer_color_changes.begin(); + // Collect the object extruders. for (auto layer : object.layers()) { LayerTools &layer_tools = this->tools_for_layer(layer->print_z); @@ -266,6 +274,7 @@ void ToolOrdering::collect_extruders( layer_tools.extruders.emplace_back(it_per_layer_color_changes->second); } } + // What extruders are required to print this object layer? for (const LayerRegion *layerm : layer->regions()) { const PrintRegion ®ion = layerm->region(); @@ -499,6 +508,7 @@ bool ToolOrdering::insert_wipe_tower_extruder() { if (!m_print_config_ptr->wipe_tower) return false; + // In case that wipe_tower_extruder is set to non-zero, we must make sure that the extruder will be in the list. bool changed = false; if (m_print_config_ptr->wipe_tower_extruder != 0) { @@ -856,6 +866,7 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print, const } } + int ToolOrdering::toolchanges_count() const { std::vector tools_in_order; @@ -870,4 +881,5 @@ int ToolOrdering::toolchanges_count() const tools_in_order.pop_back(); return std::max(0, int(tools_in_order.size())-1); // 5 tools = 4 toolchanges } + } // namespace Slic3r diff --git a/src/libslic3r/GCode/ToolOrdering.hpp b/src/libslic3r/GCode/ToolOrdering.hpp index d0e483b..4d750ac 100644 --- a/src/libslic3r/GCode/ToolOrdering.hpp +++ b/src/libslic3r/GCode/ToolOrdering.hpp @@ -3,12 +3,19 @@ #ifndef slic3r_ToolOrdering_hpp_ #define slic3r_ToolOrdering_hpp_ -#include "../libslic3r.h" - +#include +#include +#include +#include #include #include +#include +#include +#include +#include -#include +#include "libslic3r/libslic3r.h" +#include "libslic3r/PrintConfig.hpp" namespace Slic3r { @@ -16,8 +23,13 @@ class Print; class PrintObject; class LayerTools; class ToolOrdering; -namespace CustomGCode { struct Item; } + +namespace CustomGCode { +struct Item; +} // namespace CustomGCode class PrintRegion; +class ExtrusionEntity; +class ExtrusionEntityCollection; // Object of this class holds information about whether an extrusion is printed immediately // after a toolchange (as part of infill/perimeter wiping) or not. One extrusion can be a part diff --git a/src/libslic3r/GCode/Travels.cpp b/src/libslic3r/GCode/Travels.cpp index 6795444..9da69b9 100644 --- a/src/libslic3r/GCode/Travels.cpp +++ b/src/libslic3r/GCode/Travels.cpp @@ -1,10 +1,28 @@ #include "Travels.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + #include "libslic3r/PrintConfig.hpp" #include "libslic3r/Layer.hpp" #include "libslic3r/Print.hpp" - #include "../GCode.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Exception.hpp" +#include "libslic3r/ExtrusionEntity.hpp" +#include "libslic3r/ExtrusionEntityCollection.hpp" +#include "libslic3r/LayerRegion.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/Polyline.hpp" +#include "libslic3r/libslic3r.h" +#include "tcbspan/span.hpp" +#include "libslic3r/ShortestPath.hpp" namespace Slic3r::GCode { diff --git a/src/libslic3r/GCode/Travels.hpp b/src/libslic3r/GCode/Travels.hpp index 7f7c017..ce59c8f 100644 --- a/src/libslic3r/GCode/Travels.hpp +++ b/src/libslic3r/GCode/Travels.hpp @@ -6,15 +6,20 @@ #ifndef slic3r_GCode_Travels_hpp_ #define slic3r_GCode_Travels_hpp_ -#include #include -#include -#include - #include #include +#include +#include +#include +#include +#include +#include #include "libslic3r/AABBTreeLines.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/GCode/ExtrusionOrder.hpp" // Forward declarations. namespace Slic3r { @@ -29,6 +34,7 @@ class ExtrusionEntity; namespace Slic3r::GCode { struct ObjectLayerToPrint; + using ObjectsLayerToPrint = std::vector; class ObjectOrExtrusionLinef : public Linef diff --git a/src/libslic3r/GCode/Wipe.cpp b/src/libslic3r/GCode/Wipe.cpp index d68746b..99e28ca 100644 --- a/src/libslic3r/GCode/Wipe.cpp +++ b/src/libslic3r/GCode/Wipe.cpp @@ -1,9 +1,20 @@ #include "Wipe.hpp" -#include "../GCode.hpp" #include +#include +#include +#include +#include -#include +#include "../GCode.hpp" +#include "libslic3r/Extruder.hpp" +#include "libslic3r/GCode/GCodeProcessor.hpp" +#include "libslic3r/GCode/GCodeWriter.hpp" +#include "libslic3r/GCode/SmoothPath.hpp" +#include "libslic3r/Geometry/ArcWelder.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/libslic3r.h" using namespace std::string_view_literals; @@ -35,22 +46,22 @@ void Wipe::init(const PrintConfig &config, const std::vector &extr void Wipe::set_path(SmoothPath &&path) { this->reset_path(); - if (this->enabled() && ! path.empty()) { + if (this->enabled() && !path.empty()) { const coord_t wipe_len_max_scaled = scaled(m_wipe_len_max); - m_path = std::move(path.front().path); - int64_t len = Geometry::ArcWelder::estimate_path_length(m_path); - for (auto it = std::next(path.begin()); len < wipe_len_max_scaled && it != path.end(); ++ it) { - if (it->path_attributes.role.is_bridge()) - break; // Do not perform a wipe on bridges. - assert(it->path.size() >= 2); - assert(m_path.back().point == it->path.front().point); - if (m_path.back().point != it->path.front().point) - // ExtrusionMultiPath is interrupted in some place. This should not really happen. - break; - len += Geometry::ArcWelder::estimate_path_length(it->path); - m_path.insert(m_path.end(), it->path.begin() + 1, it->path.end()); - } + m_path = std::move(path.front().path); + int64_t len = Geometry::ArcWelder::estimate_path_length(m_path); + for (auto it = std::next(path.begin()); len < wipe_len_max_scaled && it != path.end(); ++it) { + if (it->path_attributes.role.is_bridge()) + break; // Do not perform a wipe on bridges. + assert(it->path.size() >= 2); + assert(m_path.back().point == it->path.front().point); + if (m_path.back().point != it->path.front().point) + // ExtrusionMultiPath is interrupted in some place. This should not really happen. + break; + len += Geometry::ArcWelder::estimate_path_length(it->path); + m_path.insert(m_path.end(), it->path.begin() + 1, it->path.end()); } + } assert(m_path.empty() || m_path.size() > 1); } @@ -164,7 +175,6 @@ std::string Wipe::wipe(GCodeGenerator &gcodegen, bool toolchange) p, ij, ccw, -dE, wipe_retract_comment); } retract_length -= dE; - return done; }; // Start with the current position, which may be different from the wipe path start in case of loop clipping. @@ -197,7 +207,8 @@ std::string Wipe::wipe(GCodeGenerator &gcodegen, bool toolchange) } if (wiped) { // add tag for processor - assert(p == GCodeFormatter::quantize(p)); + Vec2d test = GCodeFormatter::quantize(p); + // assert(p == GCodeFormatter::quantize(p)); gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_End) + "\n"; gcodegen.last_position = gcodegen.gcode_to_point(p); } @@ -211,7 +222,7 @@ std::string Wipe::wipe(GCodeGenerator &gcodegen, bool toolchange) // Make a little move inwards before leaving loop after path was extruded, // thus the current extruder position is at the end of a path and the path // may not be closed in case the loop was clipped to hide a seam. -std::optional wipe_hide_seam(const SmoothPath &path, bool is_hole, double wipe_length) +std::optional wipe_hide_seam(const SmoothPath &path, const bool path_reversed, const double wipe_length) { assert(! path.empty()); assert(path.front().path.size() >= 2); @@ -235,7 +246,7 @@ std::optional wipe_hide_seam(const SmoothPath &path, bool is_hole, double // Wipe move cannot be calculated, the loop is not long enough. This should not happen due to the longer_than() test above. return {}; } - if (std::optional p = sample_path_point_at_distance_from_end(path, wipe_length); p) + if (std::optional p = sample_path_point_at_distance_from_start(path, wipe_length); p) p_prev = *p; else // Wipe move cannot be calculated, the loop is not long enough. This should not happen due to the longer_than() test above. @@ -246,7 +257,7 @@ std::optional wipe_hide_seam(const SmoothPath &path, bool is_hole, double double angle_inside = angle(p_next - p_current, p_prev - p_current); assert(angle_inside >= -M_PI && angle_inside <= M_PI); // 3rd of this angle will be taken, thus make the angle monotonic before interpolation. - if (is_hole) { + if (path_reversed) { if (angle_inside > 0) angle_inside -= 2.0 * M_PI; } else { @@ -255,7 +266,7 @@ std::optional wipe_hide_seam(const SmoothPath &path, bool is_hole, double } // Rotate the forward segment inside by 1/3 of the wedge angle. auto v_rotated = Eigen::Rotation2D(angle_inside) * (p_next - p_current).cast().normalized(); - return std::make_optional(p_current + (v_rotated * wipe_length).cast()); + return p_current + (v_rotated * wipe_length).cast(); } return {}; diff --git a/src/libslic3r/GCode/Wipe.hpp b/src/libslic3r/GCode/Wipe.hpp index e65657e..666eb6a 100644 --- a/src/libslic3r/GCode/Wipe.hpp +++ b/src/libslic3r/GCode/Wipe.hpp @@ -1,15 +1,19 @@ #ifndef slic3r_GCode_Wipe_hpp_ #define slic3r_GCode_Wipe_hpp_ -#include "SmoothPath.hpp" +#include +#include +#include +#include +#include +#include +#include +#include "SmoothPath.hpp" #include "../Geometry/ArcWelder.hpp" #include "../Point.hpp" #include "../PrintConfig.hpp" -#include -#include - namespace Slic3r { class GCodeGenerator; @@ -49,7 +53,7 @@ public: // Reduce feedrate a bit; travel speed is often too high to move on existing material. // Too fast = ripping of existing material; too slow = short wipe path, thus more blob. - static double calc_wipe_speed(const GCodeConfig &config) { return config.travel_speed.value * 0.8; } + static double calc_wipe_speed(const GCodeConfig &config) { return config.travel_speed.value * 0.8; } // Reduce retraction length a bit to avoid effective retraction speed to be greater than the configured one // due to rounding (TODO: test and/or better math for this). static double calc_xy_to_e_ratio(const GCodeConfig &config, unsigned int extruder_id) diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 8e8785c..d0398b5 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -1,22 +1,27 @@ #include "WipeTower.hpp" +#include +#include #include -#include #include #include #include #include -#include +#include +#include -#include "ClipperUtils.hpp" -#include "GCodeProcessor.hpp" -#include "BoundingBox.hpp" -#include "LocalesUtils.hpp" -#include "Geometry.hpp" -#include "Surface.hpp" -#include "Fill/FillRectilinear.hpp" - -#include +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/GCode/GCodeProcessor.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/Geometry.hpp" +#include "libslic3r/Surface.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/ExtrusionRole.hpp" +#include "libslic3r/Fill/FillBase.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/Polyline.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r @@ -32,6 +37,10 @@ static float length_to_volume(float length, float line_width, float layer_height { return std::max(0.f, length * layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f))); } + + + + class WipeTowerWriter { public: @@ -43,12 +52,9 @@ public: m_extrusion_flow(0.f), m_preview_suppressed(false), m_elapsed_time(0.f), -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - m_default_analyzer_line_width(line_width), -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING - m_gcode_flavor(flavor), - m_filpar(filament_parameters) - { + m_gcode_flavor(flavor), + m_filpar(filament_parameters) + { // adds tag for analyzer: std::ostringstream str; str << ";" << GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Height) << m_layer_height << "\n"; // don't rely on GCodeAnalyzer knowing the layer height - it knows nothing at priming @@ -65,18 +71,6 @@ public: return *this; } -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - WipeTowerWriter& change_analyzer_mm3_per_mm(float len, float e) { - static const float area = float(M_PI) * 1.75f * 1.75f / 4.f; - float mm3_per_mm = (len == 0.f ? 0.f : area * e / len); - // adds tag for processor: - std::stringstream str; - str << ";" << GCodeProcessor::Mm3_Per_Mm_Tag << mm3_per_mm << "\n"; - m_gcode += str.str(); - return *this; - } -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING - WipeTowerWriter& set_initial_position(const Vec2f &pos, float width = 0.f, float depth = 0.f, float internal_angle = 0.f) { m_wipe_tower_width = width; m_wipe_tower_depth = depth; @@ -116,16 +110,12 @@ public: m_gcode += std::string("G4 S0\n") + "M591 " + (enable ? "R" : "S0") + "\n"; return *this; } + // Suppress / resume G-code preview in Slic3r. Slic3r will have difficulty to differentiate the various // filament loading and cooling moves from normal extrusion moves. Therefore the writer // is asked to suppres output of some lines, which look like extrusions. -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - WipeTowerWriter& suppress_preview() { change_analyzer_line_width(0.f); m_preview_suppressed = true; return *this; } - WipeTowerWriter& resume_preview() { change_analyzer_line_width(m_default_analyzer_line_width); m_preview_suppressed = false; return *this; } -#else WipeTowerWriter& suppress_preview() { m_preview_suppressed = true; return *this; } - WipeTowerWriter& resume_preview() { m_preview_suppressed = false; return *this; } -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING + WipeTowerWriter& resume_preview() { m_preview_suppressed = false; return *this; } WipeTowerWriter& feedrate(float f) { @@ -164,12 +154,9 @@ public: Vec2f rot(this->rotate(Vec2f(x,y))); // this is where we want to go if (! m_preview_suppressed && e > 0.f && len > 0.f) { -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - change_analyzer_mm3_per_mm(len, e); -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING - // Width of a squished extrusion, corrected for the roundings of the squished extrusions. + // Width of a squished extrusion, corrected for the roundings of the squished extrusions. // This is left zero if it is a travel move. - float width = e * m_filpar[0].filament_area / (len * m_layer_height); + float width = e * m_filpar[0].filament_area / (len * m_layer_height); // Correct for the roundings of a squished extrusion. width += m_layer_height * float(1. - M_PI / 4.); if (m_extrusions.empty() || m_extrusions.back().pos != rotated_current_pos) @@ -316,6 +303,7 @@ public: } return *this; } + // Elevate the extruder head above the current print_z position. WipeTowerWriter& z_hop(float hop, float f = 0.f) { @@ -478,9 +466,6 @@ private: float m_wipe_tower_depth = 0.f; unsigned m_last_fan_speed = 0; int current_temp = -1; -#if ENABLE_GCODE_VIEWER_DATA_CHECKING - const float m_default_analyzer_line_width; -#endif // ENABLE_GCODE_VIEWER_DATA_CHECKING float m_used_filament_length = 0.f; GCodeFlavor m_gcode_flavor; const std::vector& m_filpar; @@ -596,6 +581,7 @@ WipeTower::WipeTower(const PrintConfig& config, const PrintRegionConfig& default } m_is_mk4mmu3 = boost::icontains(config.printer_notes.value, "PRINTER_MODEL_MK4") && boost::icontains(config.printer_notes.value, "MMU"); + // Calculate where the priming lines should be - very naive test not detecting parallelograms etc. const std::vector& bed_points = config.bed_shape.values; BoundingBoxf bb(bed_points); @@ -906,7 +892,7 @@ void WipeTower::toolchange_Unload( if (do_ramming) { writer.travel(ramming_start_pos); // move to starting position if (! m_is_mk4mmu3) - writer.disable_linear_advance(); + writer.disable_linear_advance(); if (cold_ramming) writer.set_extruder_temp(old_temperature - 20); } @@ -942,11 +928,12 @@ void WipeTower::toolchange_Unload( sum_of_depths += tch.required_depth; } } - + if (m_is_mk4mmu3) { writer.switch_filament_monitoring(false); writer.wait(1.5f); } + // now the ramming itself: while (do_ramming && i < m_filpar[m_current_tool].ramming_speed.size()) @@ -987,9 +974,11 @@ void WipeTower::toolchange_Unload( .retract(0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed * 60.f) .resume_preview(); } + const int& number_of_cooling_moves = m_filpar[m_current_tool].cooling_moves; const bool cooling_will_happen = m_semm && number_of_cooling_moves > 0; bool change_temp_later = false; + // Wipe tower should only change temperature with single extruder MM. Otherwise, all temperatures should // be already set and there is no need to change anything. Also, the temperature could be changed // for wrong extruder. @@ -1000,7 +989,7 @@ void WipeTower::toolchange_Unload( if (cold_ramming && cooling_will_happen) change_temp_later = true; else - writer.set_extruder_temp(new_temperature, false); + writer.set_extruder_temp(new_temperature, false); m_old_temperature = new_temperature; } } @@ -1047,6 +1036,7 @@ void WipeTower::toolchange_Unload( // If cold_ramming, the temperature change should be done before the last cooling move. writer.set_extruder_temp(new_temperature, false); } + float speed = initial_speed + speed_inc * 2*i; writer.load_move_x_advanced(turning_point, m_cooling_tube_length, speed); speed += speed_inc; @@ -1090,6 +1080,7 @@ void WipeTower::toolchange_Change( if (m_is_mk4mmu3) writer.switch_filament_monitoring(true); + // Travel to where we assume we are. Custom toolchange or some special T code handling (parking extruder etc) // gcode could have left the extruder somewhere, we cannot just start extruding. We should also inform the // postprocessor that we absolutely want to have this in the gcode, even if it thought it is the same as before. @@ -1099,6 +1090,7 @@ void WipeTower::toolchange_Change( + " Y" + Slic3r::float_to_string_decimal_point(current_pos.y()) + never_skip_tag() + "\n" ); + writer.append("[deretraction_from_wipe_tower_generator]"); // The toolchange Tn command will be inserted later, only in case that the user does @@ -1153,6 +1145,7 @@ void WipeTower::toolchange_Wipe( writer.set_extrusion_flow(m_extrusion_flow * m_extra_flow); const float line_width = m_perimeter_width * m_extra_flow; writer.change_analyzer_line_width(line_width); + // Variables x_to_wipe and traversed_x are here to be able to make sure it always wipes at least // the ordered volume, even if it means violating the box. This can later be removed and simply // wipe until the end of the assigned area. @@ -1502,6 +1495,7 @@ std::vector> WipeTower::extract_wipe_volumes(const PrintConfi } } } + // Also include filament_minimal_purge_on_wipe_tower. This is needed for the preview. for (unsigned int i = 0; itool_changes.size()); ++i) { auto& toolchange = m_layer_info->tool_changes[i]; tool_change(toolchange.new_tool); @@ -1645,7 +1644,7 @@ void WipeTower::generate(std::vector> & return; plan_tower(); - for (int i=0;i<5;++i) { + for (int i = 0; i<5; ++i) { save_on_last_wipe(); plan_tower(); } @@ -1706,6 +1705,7 @@ void WipeTower::generate(std::vector> & } result.emplace_back(std::move(layer_result)); + if (m_used_filament_length_until_layer.empty() || m_used_filament_length_until_layer.back().first != layer.z) m_used_filament_length_until_layer.emplace_back(); m_used_filament_length_until_layer.back() = std::make_pair(layer.z, m_used_filament_length); diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index 2afdc54..d7b25ea 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -1,10 +1,15 @@ #ifndef slic3r_GCode_WipeTower_hpp_ #define slic3r_GCode_WipeTower_hpp_ + +#include #include #include #include #include #include +#include +#include +#include #include "libslic3r/Point.hpp" @@ -14,6 +19,7 @@ namespace Slic3r class WipeTowerWriter; class PrintConfig; class PrintRegionConfig; + enum GCodeFlavor : unsigned char; @@ -231,8 +237,10 @@ public: float unloading_speed = 0.f; float unloading_speed_start = 0.f; float delay = 0.f ; + float filament_stamping_loading_speed = 0.f; float filament_stamping_distance = 0.f; + int cooling_moves = 0; float cooling_initial_speed = 0.f; float cooling_final_speed = 0.f; @@ -312,6 +320,7 @@ private: // State of the wipe tower generator. unsigned int m_num_layer_changes = 0; // Layer change counter for the output statistics. unsigned int m_num_tool_changes = 0; // Tool change change counter for the output statistics. + // A fill-in direction (positive Y, negative Y) alternates with each layer. wipe_shape m_current_shape = SHAPE_NORMAL; size_t m_current_tool = 0; @@ -338,7 +347,6 @@ private: // Calculates depth for all layers and propagates them downwards void plan_tower(); - // Goes through m_plan, calculates border and finish_layer extrusions and subtracts them from last wipe void save_on_last_wipe(); diff --git a/src/libslic3r/GCode/WipeTowerIntegration.cpp b/src/libslic3r/GCode/WipeTowerIntegration.cpp index add8b3c..0b82e03 100644 --- a/src/libslic3r/GCode/WipeTowerIntegration.cpp +++ b/src/libslic3r/GCode/WipeTowerIntegration.cpp @@ -1,9 +1,23 @@ #include "WipeTowerIntegration.hpp" -#include "../GCode.hpp" -#include "../libslic3r.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "libslic3r/GCode.hpp" +#include "libslic3r/libslic3r.h" #include "boost/algorithm/string/replace.hpp" +#include "libslic3r/Exception.hpp" +#include "libslic3r/ExtrusionRole.hpp" +#include "libslic3r/GCode/Wipe.hpp" +#include "libslic3r/GCode/WipeTower.hpp" +#include "libslic3r/Geometry/ArcWelder.hpp" namespace Slic3r::GCode { @@ -19,15 +33,6 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip std::string gcode; - // Toolchangeresult.gcode assumes the wipe tower corner is at the origin (except for priming lines) - // We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position - float alpha = m_wipe_tower_rotation / 180.f * float(M_PI); - - auto transform_wt_pt = [&alpha, this](const Vec2f& pt) -> Vec2f { - Vec2f out = Eigen::Rotation2Df(alpha) * pt; - out += m_wipe_tower_pos; - return out; - }; Vec2f start_pos = tcr.start_pos; Vec2f end_pos = tcr.end_pos; @@ -37,7 +42,7 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip } Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : m_wipe_tower_pos; - float wipe_tower_rotation = tcr.priming ? 0.f : alpha; + float wipe_tower_rotation = tcr.priming ? 0.f : this->get_alpha(); std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation); @@ -62,18 +67,18 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip gcode += gcodegen.retract_and_wipe(); gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true; const std::string comment{"Travel to a Wipe Tower"}; - if (gcodegen.m_current_layer_first_position) { + if (!gcodegen.m_moved_to_first_layer_point) { + const Vec3crd point = to_3d(xy_point, scaled(z)); + gcode += gcodegen.travel_to_first_position(point, current_z, ExtrusionRole::Mixed, [](){return "";}); + } else { if (gcodegen.last_position) { - gcode += gcodegen.travel_to( + gcode += gcodegen.travel_to( *gcodegen.last_position, xy_point, ExtrusionRole::Mixed, comment, [](){return "";} ); } else { gcode += gcodegen.writer().travel_to_xy(gcodegen.point_to_gcode(xy_point), comment); gcode += gcodegen.writer().get_travel_to_z_gcode(z, comment); } - } else { - const Vec3crd point = to_3d(xy_point, scaled(z)); - gcode += gcodegen.travel_to_first_position(point, current_z, ExtrusionRole::Mixed, [](){return "";}); } gcode += gcodegen.unretract(); } else { @@ -99,7 +104,6 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip position.z() = z; gcodegen.writer().update_position(position); deretraction_str += gcodegen.unretract(); - } } assert(toolchange_gcode_str.empty() || toolchange_gcode_str.back() == '\n'); @@ -110,6 +114,7 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip boost::replace_first(tcr_rotated_gcode, "[deretraction_from_wipe_tower_generator]", deretraction_str); std::string tcr_gcode; unescape_string_cstyle(tcr_rotated_gcode, tcr_gcode); + if (gcodegen.config().default_acceleration > 0) gcode += gcodegen.writer().set_print_acceleration(fast_round_up(gcodegen.config().wipe_tower_acceleration.value)); gcode += tcr_gcode; @@ -130,7 +135,7 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip Geometry::ArcWelder::Path path; path.reserve(tcr.wipe_path.size()); std::transform(tcr.wipe_path.begin(), tcr.wipe_path.end(), std::back_inserter(path), - [&gcodegen, &transform_wt_pt](const Vec2f &wipe_pt) { + [&gcodegen, this](const Vec2f &wipe_pt) { return Geometry::ArcWelder::Segment{ wipe_tower_point_to_object_point(gcodegen, transform_wt_pt(wipe_pt)) }; }); // Pass to the wipe cache. diff --git a/src/libslic3r/GCode/WipeTowerIntegration.hpp b/src/libslic3r/GCode/WipeTowerIntegration.hpp index 231e665..beb5614 100644 --- a/src/libslic3r/GCode/WipeTowerIntegration.hpp +++ b/src/libslic3r/GCode/WipeTowerIntegration.hpp @@ -1,8 +1,15 @@ #ifndef slic3r_GCode_WipeTowerIntegration_hpp_ #define slic3r_GCode_WipeTowerIntegration_hpp_ +#include +#include +#include +#include +#include + #include "WipeTower.hpp" #include "../PrintConfig.hpp" +#include "libslic3r/Point.hpp" namespace Slic3r { @@ -35,8 +42,39 @@ public: std::string tool_change(GCodeGenerator &gcodegen, int extruder_id, bool finish_layer); std::string finalize(GCodeGenerator &gcodegen); std::vector used_filament_length() const; + std::optional get_toolchange(std::size_t index, bool ignore_sparse) const { + if (m_layer_idx >= m_tool_changes.size()) { + return std::nullopt; + } + if( + ignore_sparse + && m_tool_changes.at(m_layer_idx).size() == 1 + && ( + m_tool_changes.at(m_layer_idx).front().initial_tool + == m_tool_changes.at(m_layer_idx).front().new_tool + ) + && m_layer_idx != 0 + ) { + // Ignore sparse + return std::nullopt; + } + + return m_tool_changes.at(m_layer_idx).at(index); + } + + Vec2f transform_wt_pt(const Vec2f& pt) const { + Vec2f out = Eigen::Rotation2Df(this->get_alpha()) * pt; + out += m_wipe_tower_pos; + return out; + }; private: + // Toolchangeresult.gcode assumes the wipe tower corner is at the origin (except for priming lines) + // We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position + float get_alpha() const { + return m_wipe_tower_rotation / 180.f * float(M_PI); + } + WipeTowerIntegration& operator=(const WipeTowerIntegration&); std::string append_tcr(GCodeGenerator &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z = -1.) const; diff --git a/src/libslic3r/GCodeReader.cpp b/src/libslic3r/GCodeReader.cpp index 5215f41..e28b181 100644 --- a/src/libslic3r/GCodeReader.cpp +++ b/src/libslic3r/GCodeReader.cpp @@ -1,16 +1,16 @@ #include "GCodeReader.hpp" -#include -#include -#include + #include -#include +#include #include #include +#include +#include +#include + #include "Utils.hpp" - -#include "LocalesUtils.hpp" - -#include +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { @@ -127,6 +127,10 @@ bool GCodeReader::parse_file_raw_internal(const std::string &filename, ParseLine { FilePtr in{ boost::nowide::fopen(filename.c_str(), "rb") }; + fseek(in.f, 0, SEEK_END); + const long file_size = ftell(in.f); + rewind(in.f); + // Read the input stream 64kB at a time, extract lines and process them. std::vector buffer(65536 * 10, 0); // Line buffer. @@ -137,6 +141,7 @@ bool GCodeReader::parse_file_raw_internal(const std::string &filename, ParseLine size_t cnt_read = ::fread(buffer.data(), 1, buffer.size(), in.f); if (::ferror(in.f)) return false; + bool eof = cnt_read == 0; auto it = buffer.begin(); auto it_bufend = buffer.begin() + cnt_read; @@ -174,6 +179,8 @@ bool GCodeReader::parse_file_raw_internal(const std::string &filename, ParseLine if (eof) break; file_pos += cnt_read; + if (m_progress_callback != nullptr) + m_progress_callback(static_cast(file_pos) / static_cast(file_size)); } return true; } @@ -237,7 +244,7 @@ bool GCodeReader::GCodeLine::has(char axis) const } std::string_view GCodeReader::GCodeLine::axis_pos(char axis) const -{ +{ const std::string &s = this->raw(); const char *c = GCodeReader::axis_pos(this->raw().c_str(), axis); return c ? std::string_view{ c, s.size() - (c - s.data()) } : std::string_view(); @@ -246,15 +253,15 @@ std::string_view GCodeReader::GCodeLine::axis_pos(char axis) const bool GCodeReader::GCodeLine::has_value(std::string_view axis_pos, float &value) { if (const char *c = axis_pos.data(); c) { - // Try to parse the numeric value. - double v = 0.; + // Try to parse the numeric value. + double v = 0.; const char *end = axis_pos.data() + axis_pos.size(); - auto [pend, ec] = fast_float::from_chars(++c, end, v); - if (pend != c && is_end_of_word(*pend)) { - // The axis value has been parsed correctly. - value = float(v); - return true; - } + auto [pend, ec] = fast_float::from_chars(++ c, end, v); + if (pend != c && is_end_of_word(*pend)) { + // The axis value has been parsed correctly. + value = float(v); + return true; + } } return false; } @@ -268,14 +275,14 @@ bool GCodeReader::GCodeLine::has_value(char axis, float &value) const bool GCodeReader::GCodeLine::has_value(std::string_view axis_pos, int &value) { if (const char *c = axis_pos.data(); c) { - // Try to parse the numeric value. - char *pend = nullptr; - long v = strtol(++ c, &pend, 10); - if (pend != nullptr && is_end_of_word(*pend)) { - // The axis value has been parsed correctly. - value = int(v); - return true; - } + // Try to parse the numeric value. + char *pend = nullptr; + long v = strtol(++ c, &pend, 10); + if (pend != nullptr && is_end_of_word(*pend)) { + // The axis value has been parsed correctly. + value = int(v); + return true; + } } return false; } @@ -284,6 +291,7 @@ bool GCodeReader::GCodeLine::has_value(char axis, int &value) const { return this->has_value(this->axis_pos(axis), value); } + void GCodeReader::GCodeLine::set(const GCodeReader &reader, const Axis axis, const float new_value, const int decimal_digits) { std::ostringstream ss; diff --git a/src/libslic3r/GCodeReader.hpp b/src/libslic3r/GCodeReader.hpp index feb8723..37c270e 100644 --- a/src/libslic3r/GCodeReader.hpp +++ b/src/libslic3r/GCodeReader.hpp @@ -1,18 +1,28 @@ #ifndef slic3r_GCodeReader_hpp_ #define slic3r_GCodeReader_hpp_ -#include "libslic3r.h" +#include +#include #include #include #include #include #include +#include +#include +#include +#include + +#include "libslic3r.h" #include "PrintConfig.hpp" +#include "libslic3r/Point.hpp" namespace Slic3r { class GCodeReader { public: + typedef std::function ProgressCallback; + class GCodeLine { public: GCodeLine() { reset(); } @@ -160,6 +170,8 @@ public: char extrusion_axis() const { return m_extrusion_axis; } // void set_extrusion_axis(char axis) { m_extrusion_axis = axis; } + void set_progress_callback(ProgressCallback cb) { m_progress_callback = cb; } + private: template bool parse_file_raw_internal(const std::string &filename, ParseLineCallback parse_line_callback, LineEndCallback line_end_callback); @@ -191,6 +203,8 @@ private: bool m_verbose; // To be set by the callback to stop parsing. bool m_parsing{ false }; + + ProgressCallback m_progress_callback{ nullptr }; }; } /* namespace Slic3r */ diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index f2860ea..6670bcc 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -1,30 +1,32 @@ -#include "libslic3r.h" -#include "Exception.hpp" -#include "Geometry.hpp" -#include "ClipperUtils.hpp" -#include "ExPolygon.hpp" -#include "Line.hpp" -#include "clipper.hpp" +#include +#include +#include #include #include #include -#include -#include -#include -#include #include -#include #include +#include +#include -#include -#include -#include +#include "libslic3r.h" +#include "Geometry.hpp" +#include "ClipperUtils.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/MultiPoint.hpp" +#include "libslic3r/Polygon.hpp" + +namespace boost { +namespace polygon { +template class point_data; +} // namespace polygon +} // namespace boost #if defined(_MSC_VER) && defined(__clang__) #define BOOST_NO_CXX17_HDR_STRING_VIEW #endif -namespace Slic3r { namespace Geometry { +namespace Slic3r::Geometry { bool directions_parallel(double angle1, double angle2, double max_diff) { @@ -623,6 +625,21 @@ Transform3d Transformation::get_matrix_no_scaling_factor() const return copy.get_matrix(); } +Transform3d Transformation::get_matrix_with_applied_shrinkage_compensation(const Vec3d &shrinkage_compensation) const { + const Transform3d shrinkage_trafo = Geometry::scale_transform(shrinkage_compensation); + const Vec3d trafo_offset = this->get_offset(); + const Vec3d trafo_offset_xy = Vec3d(trafo_offset.x(), trafo_offset.y(), 0.); + + Transformation copy(*this); + copy.set_offset(Axis::X, 0.); + copy.set_offset(Axis::Y, 0.); + + Transform3d trafo_after_shrinkage = (shrinkage_trafo * copy.get_matrix()); + trafo_after_shrinkage.translation() += trafo_offset_xy; + + return trafo_after_shrinkage; +} + Transformation Transformation::operator * (const Transformation& other) const { return Transformation(get_matrix() * other.get_matrix()); @@ -746,4 +763,50 @@ bool trafos_differ_in_rotation_by_z_and_mirroring_by_xy_only(const Transform3d & return std::abs(d * d) < EPSILON * lx2 * ly2; } -}} // namespace Slic3r::Geometry +bool is_point_inside_polygon_corner(const Point &a, const Point &b, const Point &c, const Point &query_point) { + // Cast all input points into int64_t to prevent overflows when points are close to max values of coord_t. + const Vec2i64 a_i64 = a.cast(); + const Vec2i64 b_i64 = b.cast(); + const Vec2i64 c_i64 = c.cast(); + const Vec2i64 query_point_i64 = query_point.cast(); + + // Shift all points to have a base in vertex B. + // Then construct normalized vectors to ensure that we will work with vectors with endpoints on the unit circle. + const Vec2d ba = (a_i64 - b_i64).cast().normalized(); + const Vec2d bc = (c_i64 - b_i64).cast().normalized(); + const Vec2d bq = (query_point_i64 - b_i64).cast().normalized(); + + // Points A and C has to be different. + assert(ba != bc); + + // Construct a normal for the vector BQ that points to the left side of the vector BQ. + const Vec2d bq_left_normal = perp(bq); + + const double proj_a_on_bq_normal = ba.dot(bq_left_normal); // Project point A on the normal of BQ. + const double proj_c_on_bq_normal = bc.dot(bq_left_normal); // Project point C on the normal of BQ. + if ((proj_a_on_bq_normal > 0. && proj_c_on_bq_normal <= 0.) || (proj_a_on_bq_normal <= 0. && proj_c_on_bq_normal > 0.)) { + // Q is between points A and C or lies on one of those vectors (BA or BC). + + // Based on the CCW order of polygons (contours) and order of corner ABC, + // when this condition is met, the query point is inside the corner. + return proj_a_on_bq_normal > 0.; + } else { + // Q isn't between points A and C, but still it can be inside the corner. + + const double proj_a_on_bq = ba.dot(bq); // Project point A on BQ. + const double proj_c_on_bq = bc.dot(bq); // Project point C on BQ. + + // The value of proj_a_on_bq_normal is the same when we project the vector BA on the normal of BQ. + // So we can say that the Q is on the right side of the vector BA when proj_a_on_bq_normal > 0, and + // that the Q is on the left side of the vector BA proj_a_on_bq_normal < 0. + // Also, the Q is on the right side of the bisector of oriented angle ABC when proj_c_on_bq < proj_a_on_bq, and + // the Q is on the left side of the bisector of oriented angle ABC when proj_c_on_bq > proj_a_on_bq. + + // So the Q is inside the corner when one of the following conditions is met: + // * The Q is on the right side of the vector BA, and the Q is on the right side of the bisector of the oriented angle ABC. + // * The Q is on the left side of the vector BA, and the Q is on the left side of the bisector of the oriented angle ABC. + return (proj_a_on_bq_normal > 0. && proj_c_on_bq < proj_a_on_bq) || (proj_a_on_bq_normal <= 0. && proj_c_on_bq >= proj_a_on_bq); + } +} + +} // namespace Slic3r::Geometry diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index 8e29b0a..cd354f5 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -1,18 +1,30 @@ #ifndef slic3r_Geometry_hpp_ #define slic3r_Geometry_hpp_ +// Serialization through the Cereal library +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "libslic3r.h" #include "BoundingBox.hpp" #include "ExPolygon.hpp" #include "Polygon.hpp" #include "Polyline.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/Point.hpp" -// Serialization through the Cereal library -#include - -namespace Slic3r { - -namespace Geometry { +namespace Slic3r::Geometry { // Generic result of an orientation predicate. enum Orientation @@ -445,6 +457,8 @@ public: Transform3d get_matrix_no_offset() const; Transform3d get_matrix_no_scaling_factor() const; + Transform3d get_matrix_with_applied_shrinkage_compensation(const Vec3d &shrinkage_compensation) const; + void set_matrix(const Transform3d& transform) { m_matrix = transform; } Transformation operator * (const Transformation& other) const; @@ -536,6 +550,22 @@ Vec<3, T> spheric_to_dir(const Pair &v) return spheric_to_dir(plr, azm); } -} } // namespace Slicer::Geometry +/** + * Checks if a given point is inside a corner of a polygon. + * + * The corner of a polygon is defined by three points A, B, C in counterclockwise order. + * + * Adapted from CuraEngine LinearAlg2D::isInsideCorner by Tim Kuipers @BagelOrb + * and @Ghostkeeper. + * + * @param a The first point of the corner. + * @param b The second point of the corner (the common vertex of the two edges forming the corner). + * @param c The third point of the corner. + * @param query_point The point to be checked if is inside the corner. + * @return True if the query point is inside the corner, false otherwise. + */ +bool is_point_inside_polygon_corner(const Point &a, const Point &b, const Point &c, const Point &query_point); + +} // namespace Slic3r::Geometry #endif diff --git a/src/libslic3r/Geometry/ArcWelder.cpp b/src/libslic3r/Geometry/ArcWelder.cpp index 0bc71ae..34e5983 100644 --- a/src/libslic3r/Geometry/ArcWelder.cpp +++ b/src/libslic3r/Geometry/ArcWelder.cpp @@ -26,15 +26,16 @@ //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #include "ArcWelder.hpp" -#include "Circle.hpp" -#include "../MultiPoint.hpp" -#include "../Polygon.hpp" - -#include -#include -#include #include +#include +#include +#include + +#include "Circle.hpp" +#include "../MultiPoint.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/Point.hpp" namespace Slic3r { namespace Geometry { namespace ArcWelder { diff --git a/src/libslic3r/Geometry/ArcWelder.hpp b/src/libslic3r/Geometry/ArcWelder.hpp index dceec56..477f535 100644 --- a/src/libslic3r/Geometry/ArcWelder.hpp +++ b/src/libslic3r/Geometry/ArcWelder.hpp @@ -1,9 +1,25 @@ #ifndef slic3r_Geometry_ArcWelder_hpp_ #define slic3r_Geometry_ArcWelder_hpp_ +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "../Point.hpp" +#include "libslic3r/libslic3r.h" +#include "libslic3r/Point.hpp" namespace Slic3r { namespace Geometry { namespace ArcWelder { diff --git a/src/libslic3r/Geometry/Circle.cpp b/src/libslic3r/Geometry/Circle.cpp index b44aca1..d499d37 100644 --- a/src/libslic3r/Geometry/Circle.cpp +++ b/src/libslic3r/Geometry/Circle.cpp @@ -1,10 +1,13 @@ #include "Circle.hpp" -#include "../Polygon.hpp" - +#include #include #include -#include +#include +#include +#include + +#include "libslic3r/Point.hpp" namespace Slic3r { namespace Geometry { @@ -17,9 +20,14 @@ Point circle_center_taubin_newton(const Points::const_iterator& input_begin, con return Point::new_scale(center.x(), center.y()); } -/// Adapted from work in "Circular and Linear Regression: Fitting circles and lines by least squares", pg 126 -/// Returns a point corresponding to the center of a circle for which all of the points from input_begin to input_end -/// lie on. +// Robust and accurate algebraic circle fit, which works well even if data points are observed only within a small arc. +// The method was proposed by G. Taubin in +// "Estimation Of Planar Curves, Surfaces And Nonplanar Space Curves Defined By Implicit Equations, +// With Applications To Edge And Range Image Segmentation", IEEE Trans. PAMI, Vol. 13, pages 1115-1138, (1991)." +// This particular implementation was adapted from +// "Circular and Linear Regression: Fitting circles and lines by least squares", pg 126" +// Returns a point corresponding to the center of a circle for which all of the points from input_begin to input_end +// lie on. Vec2d circle_center_taubin_newton(const Vec2ds::const_iterator& input_begin, const Vec2ds::const_iterator& input_end, size_t cycles) { // calculate the centroid of the data set @@ -215,4 +223,5 @@ Circled circle_linear_least_squares_normal(const Vec2ds &input) } return out; } + } } // namespace Slic3r::Geometry diff --git a/src/libslic3r/Geometry/Circle.hpp b/src/libslic3r/Geometry/Circle.hpp index 855dbbe..558d3a5 100644 --- a/src/libslic3r/Geometry/Circle.hpp +++ b/src/libslic3r/Geometry/Circle.hpp @@ -1,9 +1,20 @@ #ifndef slic3r_Geometry_Circle_hpp_ #define slic3r_Geometry_Circle_hpp_ -#include "../Point.hpp" - +#include +#include #include +#include +#include +#include +#include +#include +#include +#include + +#include "../Point.hpp" +#include "libslic3r/libslic3r.h" +#include "libslic3r/Point.hpp" namespace Slic3r { namespace Geometry { @@ -62,6 +73,7 @@ std::optional> t return std::make_optional(a + Vector(- v.y(), v.x()) / (2 * d)); } } + // 2D circle defined by its center and squared radius template struct CircleSq { @@ -147,6 +159,7 @@ Circled circle_ransac(const Vec2ds& input, size_t iterations = 20, double* min_e Circled circle_linear_least_squares_qr(const Vec2ds &input); // Linear Least squares fitting solving normal equations. Low accuracy, high speed. Circled circle_linear_least_squares_normal(const Vec2ds &input); + // Randomized algorithm by Emo Welzl, working with squared radii for efficiency. The returned circle radius is inflated by epsilon. template CircleSq smallest_enclosing_circle2_welzl(const Points &points, const typename Vector::Scalar epsilon) diff --git a/src/libslic3r/Geometry/ConvexHull.cpp b/src/libslic3r/Geometry/ConvexHull.cpp index b8bf5eb..c2b4b76 100644 --- a/src/libslic3r/Geometry/ConvexHull.cpp +++ b/src/libslic3r/Geometry/ConvexHull.cpp @@ -1,9 +1,12 @@ -#include "libslic3r.h" -#include "ConvexHull.hpp" -#include "BoundingBox.hpp" -#include "../Geometry.hpp" - #include +#include +#include +#include + +#include "ConvexHull.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/Geometry.hpp" +#include "libslic3r/ExPolygon.hpp" namespace Slic3r { namespace Geometry { diff --git a/src/libslic3r/Geometry/ConvexHull.hpp b/src/libslic3r/Geometry/ConvexHull.hpp index fc5de53..2f35b8c 100644 --- a/src/libslic3r/Geometry/ConvexHull.hpp +++ b/src/libslic3r/Geometry/ConvexHull.hpp @@ -2,8 +2,12 @@ #define slic3r_Geometry_ConvexHull_hpp_ #include +#include #include "../ExPolygon.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/Polyline.hpp" namespace Slic3r { diff --git a/src/libslic3r/Geometry/MedialAxis.cpp b/src/libslic3r/Geometry/MedialAxis.cpp index 039d69a..3e67c83 100644 --- a/src/libslic3r/Geometry/MedialAxis.cpp +++ b/src/libslic3r/Geometry/MedialAxis.cpp @@ -1,10 +1,15 @@ #include "MedialAxis.hpp" -#include "clipper.hpp" -#include "VoronoiOffset.hpp" -#include "ClipperUtils.hpp" - #include +#include +#include +#include + +#include "VoronoiOffset.hpp" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/libslic3r.h" #ifdef SLIC3R_DEBUG namespace boost { namespace polygon { @@ -479,6 +484,7 @@ void MedialAxis::build(ThickPolylines* polylines) if (!m_vd.is_valid()) BOOST_LOG_TRIVIAL(error) << "MedialAxis - Invalid Voronoi diagram even after morphological closing."; } + Slic3r::Voronoi::annotate_inside_outside(m_vd, m_lines); // static constexpr double threshold_alpha = M_PI / 12.; // 30 degrees // std::vector skeleton_edges = Slic3r::Voronoi::skeleton_edges_rough(vd, lines, threshold_alpha); diff --git a/src/libslic3r/Geometry/MedialAxis.hpp b/src/libslic3r/Geometry/MedialAxis.hpp index b1354dd..cd1404f 100644 --- a/src/libslic3r/Geometry/MedialAxis.hpp +++ b/src/libslic3r/Geometry/MedialAxis.hpp @@ -1,8 +1,15 @@ #ifndef slic3r_Geometry_MedialAxis_hpp_ #define slic3r_Geometry_MedialAxis_hpp_ +#include +#include +#include +#include + #include "Voronoi.hpp" #include "../ExPolygon.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/Polyline.hpp" namespace Slic3r::Geometry { diff --git a/src/libslic3r/Geometry/Voronoi.cpp b/src/libslic3r/Geometry/Voronoi.cpp index e1df732..4f7173e 100644 --- a/src/libslic3r/Geometry/Voronoi.cpp +++ b/src/libslic3r/Geometry/Voronoi.cpp @@ -1,11 +1,13 @@ #include "Voronoi.hpp" +#include +#include + #include "libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp" #include "libslic3r/Geometry/VoronoiUtils.hpp" #include "libslic3r/Geometry/VoronoiUtilsCgal.hpp" #include "libslic3r/MultiMaterialSegmentation.hpp" - -#include +#include "libslic3r/Line.hpp" namespace Slic3r::Geometry { @@ -35,6 +37,8 @@ VoronoiDiagram::construct_voronoi(const SegmentIterator segment_begin, const Seg BOOST_LOG_TRIVIAL(warning) << "Detected Voronoi edge intersecting input segment, input polygons will be rotated back and forth."; } else if (m_issue_type == IssueType::FINITE_EDGE_WITH_NON_FINITE_VERTEX) { BOOST_LOG_TRIVIAL(warning) << "Detected finite Voronoi vertex with non finite vertex, input polygons will be rotated back and forth."; + } else if (m_issue_type == IssueType::PARABOLIC_VORONOI_EDGE_WITHOUT_FOCUS_POINT) { + BOOST_LOG_TRIVIAL(warning) << "Detected parabolic Voronoi edges without focus point, input polygons will be rotated back and forth."; } else { BOOST_LOG_TRIVIAL(error) << "Detected unknown Voronoi diagram issue, input polygons will be rotated back and forth."; } @@ -48,6 +52,8 @@ VoronoiDiagram::construct_voronoi(const SegmentIterator segment_begin, const Seg BOOST_LOG_TRIVIAL(error) << "Detected Voronoi edge intersecting input segment even after the rotation of input."; } else if (m_issue_type == IssueType::FINITE_EDGE_WITH_NON_FINITE_VERTEX) { BOOST_LOG_TRIVIAL(error) << "Detected finite Voronoi vertex with non finite vertex even after the rotation of input."; + } else if (m_issue_type == IssueType::PARABOLIC_VORONOI_EDGE_WITHOUT_FOCUS_POINT) { + BOOST_LOG_TRIVIAL(error) << "Detected parabolic Voronoi edges without focus point even after the rotation of input."; } else { BOOST_LOG_TRIVIAL(error) << "Detected unknown Voronoi diagram issue even after the rotation of input."; } @@ -147,6 +153,9 @@ void VoronoiDiagram::copy_to_local(voronoi_diagram_type &voronoi_diagram) { new_edge.prev(&m_edges[prev_edge_idx]); } } + + m_voronoi_diagram.clear(); + m_is_modified = true; } template @@ -156,8 +165,8 @@ typename boost::polygon::enable_if< VoronoiDiagram::IssueType>::type VoronoiDiagram::detect_known_issues(const VoronoiDiagram &voronoi_diagram, SegmentIterator segment_begin, SegmentIterator segment_end) { - if (has_finite_edge_with_non_finite_vertex(voronoi_diagram)) { - return IssueType::FINITE_EDGE_WITH_NON_FINITE_VERTEX; + if (const IssueType edge_issue_type = detect_known_voronoi_edge_issues(voronoi_diagram); edge_issue_type != IssueType::NO_ISSUE_DETECTED) { + return edge_issue_type; } else if (const IssueType cell_issue_type = detect_known_voronoi_cell_issues(voronoi_diagram, segment_begin, segment_end); cell_issue_type != IssueType::NO_ISSUE_DETECTED) { return cell_issue_type; } else if (!VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram, segment_begin, segment_end)) { @@ -215,16 +224,20 @@ VoronoiDiagram::detect_known_voronoi_cell_issues(const VoronoiDiagram &voronoi_d return IssueType::NO_ISSUE_DETECTED; } -bool VoronoiDiagram::has_finite_edge_with_non_finite_vertex(const VoronoiDiagram &voronoi_diagram) +VoronoiDiagram::IssueType VoronoiDiagram::detect_known_voronoi_edge_issues(const VoronoiDiagram &voronoi_diagram) { for (const voronoi_diagram_type::edge_type &edge : voronoi_diagram.edges()) { if (edge.is_finite()) { assert(edge.vertex0() != nullptr && edge.vertex1() != nullptr); if (edge.vertex0() == nullptr || edge.vertex1() == nullptr || !VoronoiUtils::is_finite(*edge.vertex0()) || !VoronoiUtils::is_finite(*edge.vertex1())) - return true; + return IssueType::FINITE_EDGE_WITH_NON_FINITE_VERTEX; + + if (edge.is_curved() && !edge.cell()->contains_point() && !edge.twin()->cell()->contains_point()) + return IssueType::PARABOLIC_VORONOI_EDGE_WITHOUT_FOCUS_POINT; } } - return false; + + return IssueType::NO_ISSUE_DETECTED; } template @@ -345,9 +358,6 @@ VoronoiDiagram::try_to_repair_degenerated_voronoi_diagram_by_rotation(const Segm for (vertex_type &vertex : m_vertices) vertex.color(0); - m_voronoi_diagram.clear(); - m_is_modified = true; - return issue_type; } diff --git a/src/libslic3r/Geometry/Voronoi.hpp b/src/libslic3r/Geometry/Voronoi.hpp index 8211fac..23a50bb 100644 --- a/src/libslic3r/Geometry/Voronoi.hpp +++ b/src/libslic3r/Geometry/Voronoi.hpp @@ -1,16 +1,28 @@ #ifndef slic3r_Geometry_Voronoi_hpp_ #define slic3r_Geometry_Voronoi_hpp_ +#include +#include +#include +#include + #include "../Line.hpp" #include "../Polyline.hpp" - +#include "libslic3r/Point.hpp" +#include "libslic3r/libslic3r.h" #ifdef _MSC_VER -// Suppress warning C4146 in OpenVDB: unary minus operator applied to unsigned type, result still unsigned +// Suppress warning C4146 in OpenVDB: unary minus operator applied to unsigned type, result still unsigned #pragma warning(push) #pragma warning(disable : 4146) #endif // _MSC_VER #include "boost/polygon/voronoi.hpp" + +namespace boost { +namespace polygon { +template struct segment_traits; +} // namespace polygon +} // namespace boost #ifdef _MSC_VER #pragma warning(pop) #endif // _MSC_VER @@ -45,7 +57,8 @@ public: MISSING_VORONOI_VERTEX, NON_PLANAR_VORONOI_DIAGRAM, VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT, - UNKNOWN // Repairs are disabled in the constructor. + PARABOLIC_VORONOI_EDGE_WITHOUT_FOCUS_POINT, + UNKNOWN // Repairs are disabled in the constructor. }; enum class State { @@ -159,7 +172,10 @@ private: IssueType>::type detect_known_voronoi_cell_issues(const VoronoiDiagram &voronoi_diagram, SegmentIterator segment_begin, SegmentIterator segment_end); - static bool has_finite_edge_with_non_finite_vertex(const VoronoiDiagram &voronoi_diagram); + // Detect issues related to Voronoi edges, or that can be detected by iterating over Voronoi edges. + // The first type of issue that can be detected is a finite Voronoi edge with a non-finite vertex. + // The second type of issue that can be detected is a parabolic Voronoi edge without a focus point (produced by two segments). + static IssueType detect_known_voronoi_edge_issues(const VoronoiDiagram &voronoi_diagram); voronoi_diagram_type m_voronoi_diagram; vertex_container_type m_vertices; @@ -168,6 +184,7 @@ private: bool m_is_modified = false; State m_state = State::UNKNOWN; IssueType m_issue_type = IssueType::UNKNOWN; + public: using SegmentIt = std::vector::iterator; diff --git a/src/libslic3r/Geometry/VoronoiOffset.cpp b/src/libslic3r/Geometry/VoronoiOffset.cpp index 4610522..8ecd370 100644 --- a/src/libslic3r/Geometry/VoronoiOffset.cpp +++ b/src/libslic3r/Geometry/VoronoiOffset.cpp @@ -1,15 +1,21 @@ // Polygon offsetting using Voronoi diagram prodiced by boost::polygon. -#include "Geometry.hpp" -#include "VoronoiOffset.hpp" -#include "libslic3r.h" - #include +#include +#include +#include +#include +#include +#include +#include + +#include "libslic3r/Geometry.hpp" +#include "VoronoiOffset.hpp" +#include "libslic3r/libslic3r.h" +#include "libslic3r/Geometry/Voronoi.hpp" // #define VORONOI_DEBUG_OUT -#include - #ifdef VORONOI_DEBUG_OUT #include #endif diff --git a/src/libslic3r/Geometry/VoronoiOffset.hpp b/src/libslic3r/Geometry/VoronoiOffset.hpp index fa72ae2..3cabff4 100644 --- a/src/libslic3r/Geometry/VoronoiOffset.hpp +++ b/src/libslic3r/Geometry/VoronoiOffset.hpp @@ -3,9 +3,15 @@ #ifndef slic3r_VoronoiOffset_hpp_ #define slic3r_VoronoiOffset_hpp_ -#include "../libslic3r.h" +#include +#include +#include +#include "libslic3r/libslic3r.h" #include "Voronoi.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" namespace Slic3r { diff --git a/src/libslic3r/Geometry/VoronoiUtils.cpp b/src/libslic3r/Geometry/VoronoiUtils.cpp index 1e94363..f140782 100644 --- a/src/libslic3r/Geometry/VoronoiUtils.cpp +++ b/src/libslic3r/Geometry/VoronoiUtils.cpp @@ -1,9 +1,17 @@ #include - -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "VoronoiUtils.hpp" +#include "libslic3r/Exception.hpp" +#include "libslic3r/Line.hpp" namespace Slic3r::Geometry { @@ -27,6 +35,7 @@ template SegmentCellRange VoronoiUtils::compute_segment_cell_range(const template SegmentCellRange VoronoiUtils::compute_segment_cell_range(const VoronoiDiagram::cell_type &, VD::SegmentIt, VD::SegmentIt); template SegmentCellRange VoronoiUtils::compute_segment_cell_range(const VoronoiDiagram::cell_type &, ColoredLinesConstIt, ColoredLinesConstIt); template SegmentCellRange VoronoiUtils::compute_segment_cell_range(const VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt); +template PointCellRange VoronoiUtils::compute_point_cell_range(const VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt); template Points VoronoiUtils::discretize_parabola(const Point &, const Arachne::PolygonsSegmentIndex &, const Point &, const Point &, coord_t, float); template Arachne::PolygonsPointIndex VoronoiUtils::get_source_point_index(const VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt); @@ -244,6 +253,62 @@ VoronoiUtils::compute_segment_cell_range(const VD::cell_type &cell, const Segmen return cell_range; } +template +typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + Geometry::PointCellRange< + typename boost::polygon::segment_point_type::value_type>::type>>::type +VoronoiUtils::compute_point_cell_range(const VD::cell_type &cell, const SegmentIterator segment_begin, const SegmentIterator segment_end) +{ + using Segment = typename std::iterator_traits::value_type; + using Point = typename boost::polygon::segment_point_type::type; + using PointCellRange = PointCellRange; + using CoordType = typename Point::coord_type; + + const Point source_point = Geometry::VoronoiUtils::get_source_point(cell, segment_begin, segment_end); + + // We want to ignore (by returning PointCellRange without assigned edge_begin and edge_end) cells outside the input polygon. + PointCellRange cell_range(source_point); + + const VD::edge_type *edge = cell.incident_edge(); + if (edge->is_infinite() || !is_in_range(*edge)) { + // Ignore infinite edges, because they only occur outside the polygon. + // Also ignore edges with endpoints that don't fit into CoordType, because such edges are definitely outside the polygon. + return cell_range; + } + + const Arachne::PolygonsPointIndex source_point_idx = Geometry::VoronoiUtils::get_source_point_index(cell, segment_begin, segment_end); + const Point edge_v0 = Geometry::VoronoiUtils::to_point(edge->vertex0()).template cast(); + const Point edge_v1 = Geometry::VoronoiUtils::to_point(edge->vertex1()).template cast(); + const Point edge_query_point = (edge_v0 == source_point) ? edge_v1 : edge_v0; + + // Check if the edge has another endpoint inside the corner of the polygon. + if (!Geometry::is_point_inside_polygon_corner(source_point_idx.prev().p(), source_point_idx.p(), source_point_idx.next().p(), edge_query_point)) { + // If the endpoint isn't inside the corner of the polygon, it means that + // the whole cell isn't inside the polygons, and we will ignore such cells. + return cell_range; + } + + const Vec2i64 source_point_i64 = source_point.template cast(); + edge = cell.incident_edge(); + do { + assert(edge->is_finite()); + + if (Vec2i64 v1 = Geometry::VoronoiUtils::to_point(edge->vertex1()); v1 == source_point_i64) { + cell_range.edge_begin = edge->next(); + cell_range.edge_end = edge; + } else { + // FIXME @hejllukas: With Arachne, we don't support polygons with collinear edges, + // because with collinear edges we have to handle secondary edges. + // Such edges goes through the endpoints of the input segments. + assert((Geometry::VoronoiUtils::to_point(edge->vertex0()) == source_point_i64 || edge->is_primary()) && "Point cells must end in the point! They cannot cross the point with an edge, because collinear edges are not allowed in the input."); + } + } while (edge = edge->next(), edge != cell.incident_edge()); + + return cell_range; +} + Vec2i64 VoronoiUtils::to_point(const VD::vertex_type *vertex) { assert(vertex != nullptr); diff --git a/src/libslic3r/Geometry/VoronoiUtils.hpp b/src/libslic3r/Geometry/VoronoiUtils.hpp index bf63914..6872e4e 100644 --- a/src/libslic3r/Geometry/VoronoiUtils.hpp +++ b/src/libslic3r/Geometry/VoronoiUtils.hpp @@ -1,8 +1,15 @@ #ifndef slic3r_VoronoiUtils_hpp_ #define slic3r_VoronoiUtils_hpp_ +#include +#include +#include + #include "libslic3r/Geometry/Voronoi.hpp" #include "libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp" +#include "libslic3r/Arachne/utils/PolygonsPointIndex.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/libslic3r.h" using VD = Slic3r::Geometry::VoronoiDiagram; @@ -11,15 +18,28 @@ namespace Slic3r::Geometry { // Represent trapezoid Voronoi cell around segment. template struct SegmentCellRange { - const PT segment_start_point; // The start point of the source segment of this cell. - const PT segment_end_point; // The end point of the source segment of this cell. + const PT source_segment_start_point; // The start point of the source segment of this cell. + const PT source_segment_end_point; // The end point of the source segment of this cell. + const VD::edge_type *edge_begin = nullptr; // The edge of the Voronoi diagram where the loop around the cell starts. + const VD::edge_type *edge_end = nullptr; // The edge of the Voronoi diagram where the loop around the cell ends. + + SegmentCellRange() = delete; + explicit SegmentCellRange(const PT &source_segment_start_point, const PT &source_segment_end_point) + : source_segment_start_point(source_segment_start_point), source_segment_end_point(source_segment_end_point) + {} + + bool is_valid() const { return edge_begin && edge_end && edge_begin != edge_end; } +}; + +// Represent trapezoid Voronoi cell around point. +template struct PointCellRange +{ + const PT source_point; // The source point of this cell. const VD::edge_type *edge_begin = nullptr; // The edge of the Voronoi diagram where the loop around the cell starts. const VD::edge_type *edge_end = nullptr; // The edge of the Voronoi diagram where the loop around the cell ends. - SegmentCellRange() = delete; - explicit SegmentCellRange(const PT &segment_start_point, const PT &segment_end_point) - : segment_start_point(segment_start_point), segment_end_point(segment_end_point) - {} + PointCellRange() = delete; + explicit PointCellRange(const PT &source_point) : source_point(source_point) {} bool is_valid() const { return edge_begin && edge_end && edge_begin != edge_end; } }; @@ -80,7 +100,7 @@ public: * are linked to the neighboring segments, so you can iterate over the * segments until you reach the last segment. * - * Adapted from CuraEngine VoronoiUtils::computePointCellRange by Tim Kuipers @BagelOrb, + * Adapted from CuraEngine VoronoiUtils::computeSegmentCellRange by Tim Kuipers @BagelOrb, * Jaime van Kessel @nallath, Remco Burema @rburema and @Ghostkeeper. * * @param cell The cell to compute the range of line segments for. @@ -96,6 +116,33 @@ public: typename boost::polygon::segment_point_type::value_type>::type>>::type compute_segment_cell_range(const VD::cell_type &cell, SegmentIterator segment_begin, SegmentIterator segment_end); + /** + * Compute the range of line segments that surround a cell of the skeletal + * graph that belongs to a point on the medial axis. + * + * This should only be used on cells that belong to a corner in the skeletal + * graph, e.g. triangular cells, not trapezoid cells. + * + * The resulting line segments is just the first and the last segment. They + * are linked to the neighboring segments, so you can iterate over the + * segments until you reach the last segment. + * + * Adapted from CuraEngine VoronoiUtils::computePointCellRange by Tim Kuipers @BagelOrb + * Jaime van Kessel @nallath, Remco Burema @rburema and @Ghostkeeper. + * + * @param cell The cell to compute the range of line segments for. + * @param segment_begin Begin iterator for all edges of the input Polygons. + * @param segment_end End iterator for all edges of the input Polygons. + * @return Range of line segments that surround the cell. + */ + template + static typename boost::polygon::enable_if< + typename boost::polygon::gtl_if::value_type>::type>::type>::type, + Geometry::PointCellRange< + typename boost::polygon::segment_point_type::value_type>::type>>::type + compute_point_cell_range(const VD::cell_type &cell, SegmentIterator segment_begin, SegmentIterator segment_end); + template static bool is_in_range(double value) { return double(std::numeric_limits::lowest()) <= value && value <= double(std::numeric_limits::max()); diff --git a/src/libslic3r/Geometry/VoronoiUtilsCgal.cpp b/src/libslic3r/Geometry/VoronoiUtilsCgal.cpp index ef650ac..14ed8d9 100644 --- a/src/libslic3r/Geometry/VoronoiUtilsCgal.cpp +++ b/src/libslic3r/Geometry/VoronoiUtilsCgal.cpp @@ -1,13 +1,21 @@ #include #include #include +#include +#include +#include #include "libslic3r/Geometry/Voronoi.hpp" #include "libslic3r/Geometry/VoronoiUtils.hpp" #include "libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp" #include "libslic3r/MultiMaterialSegmentation.hpp" - #include "VoronoiUtilsCgal.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/Point.hpp" + +namespace CGAL { +class MP_Float; +} // namespace CGAL using VD = Slic3r::Geometry::VoronoiDiagram; @@ -22,6 +30,7 @@ template bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &, Line template bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &, VD::SegmentIt, VD::SegmentIt); template bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &, ColoredLinesConstIt, ColoredLinesConstIt); template bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt); + // The tangent vector of the parabola is computed based on the Proof of the reflective property. // https://en.wikipedia.org/wiki/Parabola#Proof_of_the_reflective_property // https://math.stackexchange.com/q/2439647/2439663#comment5039739_2439663 @@ -199,9 +208,10 @@ get_parabolic_segment(const VD::edge_type &edge, const SegmentIterator segment_b const Point focus_pt = VoronoiUtils::get_source_point(*(left_cell->contains_point() ? left_cell : right_cell), segment_begin, segment_end); const Segment &directrix = VoronoiUtils::get_source_segment(*(left_cell->contains_point() ? right_cell : left_cell), segment_begin, segment_end); - CGAL::Orientation focus_side = CGAL::opposite(CGAL::orientation(to_cgal_point(edge.vertex0()), to_cgal_point(edge.vertex1()), to_cgal_point(focus_pt))); + CGAL::Orientation focus_side = CGAL::opposite(CGAL::orientation(to_cgal_point(edge.vertex0()), to_cgal_point(edge.vertex1()), to_cgal_point(focus_pt))); assert(focus_side == CGAL::Orientation::LEFT_TURN || focus_side == CGAL::Orientation::RIGHT_TURN); + const Point directrix_from = boost::polygon::segment_traits::get(directrix, boost::polygon::LOW); const Point directrix_to = boost::polygon::segment_traits::get(directrix, boost::polygon::HIGH); return {focus_pt, Line(directrix_from, directrix_to), make_linef(edge), focus_side}; diff --git a/src/libslic3r/Geometry/VoronoiUtilsCgal.hpp b/src/libslic3r/Geometry/VoronoiUtilsCgal.hpp index eb146a6..baafbc7 100644 --- a/src/libslic3r/Geometry/VoronoiUtilsCgal.hpp +++ b/src/libslic3r/Geometry/VoronoiUtilsCgal.hpp @@ -1,6 +1,9 @@ #ifndef slic3r_VoronoiUtilsCgal_hpp_ #define slic3r_VoronoiUtilsCgal_hpp_ +#include +#include + #include "Voronoi.hpp" #include "../Arachne/utils/PolygonsSegmentIndex.hpp" @@ -20,7 +23,6 @@ public: typename boost::polygon::geometry_concept::value_type>::type>::type>::type, bool>::type is_voronoi_diagram_planar_angle(const VoronoiDiagram &voronoi_diagram, SegmentIterator segment_begin, SegmentIterator segment_end); - }; } // namespace Slic3r::Geometry diff --git a/src/libslic3r/Geometry/VoronoiVisualUtils.hpp b/src/libslic3r/Geometry/VoronoiVisualUtils.hpp index 177f272..c89d59b 100644 --- a/src/libslic3r/Geometry/VoronoiVisualUtils.hpp +++ b/src/libslic3r/Geometry/VoronoiVisualUtils.hpp @@ -1,3 +1,6 @@ +#ifndef slic3r_VoronoiVisualUtils_hpp_ +#define slic3r_VoronoiVisualUtils_hpp_ + #include #include @@ -451,3 +454,5 @@ static inline void dump_voronoi_to_svg( } } // namespace Slic3r + +#endif // slic3r_VoronoiVisualUtils_hpp_ diff --git a/src/libslic3r/Int128.hpp b/src/libslic3r/Int128.hpp deleted file mode 100644 index 7aeacbf..0000000 --- a/src/libslic3r/Int128.hpp +++ /dev/null @@ -1,306 +0,0 @@ -// This is an excerpt of from the Clipper library by Angus Johnson, see the license below, -// implementing a 64 x 64 -> 128bit multiply, and 128bit addition, subtraction and compare -// operations, to be used with exact geometric predicates. -// The code has been extended by Vojtech Bubnik to use 128 bit intrinsic types -// and/or 64x64->128 intrinsic functions where possible. - -/******************************************************************************* -* * -* Author : Angus Johnson * -* Version : 6.2.9 * -* Date : 16 February 2015 * -* Website : http://www.angusj.com * -* Copyright : Angus Johnson 2010-2015 * -* * -* License: * -* Use, modification & distribution is subject to Boost Software License Ver 1. * -* http://www.boost.org/LICENSE_1_0.txt * -* * -* Attributions: * -* The code in this library is an extension of Bala Vatti's clipping algorithm: * -* "A generic solution to polygon clipping" * -* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. * -* http://portal.acm.org/citation.cfm?id=129906 * -* * -* Computer graphics and geometric modeling: implementation and algorithms * -* By Max K. Agoston * -* Springer; 1 edition (January 4, 2005) * -* http://books.google.com/books?q=vatti+clipping+agoston * -* * -* See also: * -* "Polygon Offsetting by Computing Winding Numbers" * -* Paper no. DETC2005-85513 pp. 565-575 * -* ASME 2005 International Design Engineering Technical Conferences * -* and Computers and Information in Engineering Conference (IDETC/CIE2005) * -* September 24-28, 2005 , Long Beach, California, USA * -* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf * -* * -*******************************************************************************/ - -#ifndef SLIC3R_INT128_HPP -#define SLIC3R_INT128_HPP -// #define SLIC3R_DEBUG - -// Make assert active if SLIC3R_DEBUG -#ifdef SLIC3R_DEBUG - #undef NDEBUG - #define DEBUG - #define _DEBUG - #undef assert -#endif - -#include -#include -#include - -#if ! defined(_MSC_VER) && defined(__SIZEOF_INT128__) - #define HAS_INTRINSIC_128_TYPE -#endif - -#if defined(_MSC_VER) && defined(_WIN64) - #include - #pragma intrinsic(_mul128) -#endif - -//------------------------------------------------------------------------------ -// Int128 class (enables safe math on signed 64bit integers) -// eg Int128 val1((int64_t)9223372036854775807); //ie 2^63 -1 -// Int128 val2((int64_t)9223372036854775807); -// Int128 val3 = val1 * val2; -//------------------------------------------------------------------------------ - -class Int128 -{ - -#ifdef HAS_INTRINSIC_128_TYPE - -/******************************************** Using the intrinsic 128bit x 128bit multiply ************************************************/ - -public: - __int128 value; - - Int128(int64_t lo = 0) : value(lo) {} - Int128(const Int128 &v) : value(v.value) {} - - Int128& operator=(const int64_t &rhs) { value = rhs; return *this; } - - uint64_t lo() const { return uint64_t(value); } - int64_t hi() const { return int64_t(value >> 64); } - int sign() const { return (value > 0) - (value < 0); } - - bool operator==(const Int128 &rhs) const { return value == rhs.value; } - bool operator!=(const Int128 &rhs) const { return value != rhs.value; } - bool operator> (const Int128 &rhs) const { return value > rhs.value; } - bool operator< (const Int128 &rhs) const { return value < rhs.value; } - bool operator>=(const Int128 &rhs) const { return value >= rhs.value; } - bool operator<=(const Int128 &rhs) const { return value <= rhs.value; } - - Int128& operator+=(const Int128 &rhs) { value += rhs.value; return *this; } - Int128 operator+ (const Int128 &rhs) const { return Int128(value + rhs.value); } - Int128& operator-=(const Int128 &rhs) { value -= rhs.value; return *this; } - Int128 operator -(const Int128 &rhs) const { return Int128(value - rhs.value); } - Int128 operator -() const { return Int128(- value); } - - operator double() const { return double(value); } - - static inline Int128 multiply(int64_t lhs, int64_t rhs) { return Int128(__int128(lhs) * __int128(rhs)); } - -#if defined(__clang__) - // When Clang is used with enabled UndefinedBehaviorSanitizer, it produces "undefined reference to '__muloti4'" when __int128 is used. - // Because of that, UndefinedBehaviorSanitizer is disabled for this function. - __attribute__((no_sanitize("undefined"))) -#endif - // Evaluate signum of a 2x2 determinant. - static int sign_determinant_2x2(int64_t a11, int64_t a12, int64_t a21, int64_t a22) - { - __int128 det = __int128(a11) * __int128(a22) - __int128(a12) * __int128(a21); - return (det > 0) - (det < 0); - } - - // Compare two rational numbers. - static int compare_rationals(int64_t p1, int64_t q1, int64_t p2, int64_t q2) - { - int invert = ((q1 < 0) == (q2 < 0)) ? 1 : -1; - __int128 det = __int128(p1) * __int128(q2) - __int128(p2) * __int128(q1); - return ((det > 0) - (det < 0)) * invert; - } - -#else /* HAS_INTRINSIC_128_TYPE */ - -/******************************************** Splitting the 128bit number into two 64bit words *********************************************/ - - Int128(int64_t lo = 0) : m_lo((uint64_t)lo), m_hi((lo < 0) ? -1 : 0) {} - Int128(const Int128 &val) : m_lo(val.m_lo), m_hi(val.m_hi) {} - Int128(const int64_t& hi, const uint64_t& lo) : m_lo(lo), m_hi(hi) {} - - Int128& operator = (const int64_t &val) - { - m_lo = (uint64_t)val; - m_hi = (val < 0) ? -1 : 0; - return *this; - } - - uint64_t lo() const { return m_lo; } - int64_t hi() const { return m_hi; } - int sign() const { return (m_hi == 0) ? (m_lo > 0) : (m_hi > 0) - (m_hi < 0); } - - bool operator == (const Int128 &val) const { return m_hi == val.m_hi && m_lo == val.m_lo; } - bool operator != (const Int128 &val) const { return ! (*this == val); } - bool operator > (const Int128 &val) const { return (m_hi == val.m_hi) ? m_lo > val.m_lo : m_hi > val.m_hi; } - bool operator < (const Int128 &val) const { return (m_hi == val.m_hi) ? m_lo < val.m_lo : m_hi < val.m_hi; } - bool operator >= (const Int128 &val) const { return ! (*this < val); } - bool operator <= (const Int128 &val) const { return ! (*this > val); } - - Int128& operator += (const Int128 &rhs) - { - m_hi += rhs.m_hi; - m_lo += rhs.m_lo; - if (m_lo < rhs.m_lo) m_hi++; - return *this; - } - - Int128 operator + (const Int128 &rhs) const - { - Int128 result(*this); - result+= rhs; - return result; - } - - Int128& operator -= (const Int128 &rhs) - { - *this += -rhs; - return *this; - } - - Int128 operator - (const Int128 &rhs) const - { - Int128 result(*this); - result -= rhs; - return result; - } - - Int128 operator-() const { return (m_lo == 0) ? Int128(-m_hi, 0) : Int128(~m_hi, ~m_lo + 1); } - - operator double() const - { - const double shift64 = 18446744073709551616.0; //2^64 - return (m_hi < 0) ? - ((m_lo == 0) ? - (double)m_hi * shift64 : - -(double)(~m_lo + ~m_hi * shift64)) : - (double)(m_lo + m_hi * shift64); - } - - static inline Int128 multiply(int64_t lhs, int64_t rhs) - { -#if defined(_MSC_VER) && defined(_WIN64) - // On Visual Studio 64bit, use the _mul128() intrinsic function. - Int128 result; - result.m_lo = (uint64_t)_mul128(lhs, rhs, &result.m_hi); - return result; -#else - // This branch should only be executed in case there is neither __int16 type nor _mul128 intrinsic - // function available. This is mostly on 32bit operating systems. - // Use a pure C implementation of _mul128(). - - int negate = (lhs < 0) != (rhs < 0); - - if (lhs < 0) - lhs = -lhs; - uint64_t int1Hi = uint64_t(lhs) >> 32; - uint64_t int1Lo = uint64_t(lhs & 0xFFFFFFFF); - - if (rhs < 0) - rhs = -rhs; - uint64_t int2Hi = uint64_t(rhs) >> 32; - uint64_t int2Lo = uint64_t(rhs & 0xFFFFFFFF); - - //because the high (sign) bits in both int1Hi & int2Hi have been zeroed, - //there's no risk of 64 bit overflow in the following assignment - //(ie: $7FFFFFFF*$FFFFFFFF + $7FFFFFFF*$FFFFFFFF < 64bits) - uint64_t a = int1Hi * int2Hi; - uint64_t b = int1Lo * int2Lo; - //Result = A shl 64 + C shl 32 + B ... - uint64_t c = int1Hi * int2Lo + int1Lo * int2Hi; - - Int128 tmp; - tmp.m_hi = int64_t(a + (c >> 32)); - tmp.m_lo = int64_t(c << 32); - tmp.m_lo += int64_t(b); - if (tmp.m_lo < b) - ++ tmp.m_hi; - if (negate) - tmp = - tmp; - return tmp; -#endif - } - - // Evaluate signum of a 2x2 determinant. - static int sign_determinant_2x2(int64_t a11, int64_t a12, int64_t a21, int64_t a22) - { - return (Int128::multiply(a11, a22) - Int128::multiply(a12, a21)).sign(); - } - - // Compare two rational numbers. - static int compare_rationals(int64_t p1, int64_t q1, int64_t p2, int64_t q2) - { - int invert = ((q1 < 0) == (q2 < 0)) ? 1 : -1; - Int128 det = Int128::multiply(p1, q2) - Int128::multiply(p2, q1); - return det.sign() * invert; - } - -private: - uint64_t m_lo; - int64_t m_hi; - - -#endif /* HAS_INTRINSIC_128_TYPE */ - - -/******************************************** Common methods ************************************************/ - -public: - - // Evaluate signum of a 2x2 determinant, use a numeric filter to avoid 128 bit multiply if possible. - static int sign_determinant_2x2_filtered(int64_t a11, int64_t a12, int64_t a21, int64_t a22) - { - // First try to calculate the determinant over the upper 31 bits. - // Round p1, p2, q1, q2 to 31 bits. - int64_t a11s = (a11 + (1 << 31)) >> 32; - int64_t a12s = (a12 + (1 << 31)) >> 32; - int64_t a21s = (a21 + (1 << 31)) >> 32; - int64_t a22s = (a22 + (1 << 31)) >> 32; - // Result fits 63 bits, it is an approximate of the determinant divided by 2^64. - int64_t det = a11s * a22s - a12s * a21s; - // Maximum absolute of the remainder of the exact determinant, divided by 2^64. - int64_t err = ((std::abs(a11s) + std::abs(a12s) + std::abs(a21s) + std::abs(a22s)) << 1) + 1; - assert(std::abs(det) <= err || ((det > 0) ? 1 : -1) == sign_determinant_2x2(a11, a12, a21, a22)); - return (std::abs(det) > err) ? - ((det > 0) ? 1 : -1) : - sign_determinant_2x2(a11, a12, a21, a22); - } - - // Compare two rational numbers, use a numeric filter to avoid 128 bit multiply if possible. - static int compare_rationals_filtered(int64_t p1, int64_t q1, int64_t p2, int64_t q2) - { - // First try to calculate the determinant over the upper 31 bits. - // Round p1, p2, q1, q2 to 31 bits. - int invert = ((q1 < 0) == (q2 < 0)) ? 1 : -1; - int64_t q1s = (q1 + (1 << 31)) >> 32; - int64_t q2s = (q2 + (1 << 31)) >> 32; - if (q1s != 0 && q2s != 0) { - int64_t p1s = (p1 + (1 << 31)) >> 32; - int64_t p2s = (p2 + (1 << 31)) >> 32; - // Result fits 63 bits, it is an approximate of the determinant divided by 2^64. - int64_t det = p1s * q2s - p2s * q1s; - // Maximum absolute of the remainder of the exact determinant, divided by 2^64. - int64_t err = ((std::abs(p1s) + std::abs(q1s) + std::abs(p2s) + std::abs(q2s)) << 1) + 1; - assert(std::abs(det) <= err || ((det > 0) ? 1 : -1) * invert == compare_rationals(p1, q1, p2, q2)); - if (std::abs(det) > err) - return ((det > 0) ? 1 : -1) * invert; - } - return sign_determinant_2x2(p1, q1, p2, q2) * invert; - } -}; - -#endif // SLIC3R_INT128_HPP diff --git a/src/libslic3r/IntersectionPoints.cpp b/src/libslic3r/IntersectionPoints.cpp index 307d51b..04738f8 100644 --- a/src/libslic3r/IntersectionPoints.cpp +++ b/src/libslic3r/IntersectionPoints.cpp @@ -2,6 +2,10 @@ #include +#include "libslic3r/AABBTreeIndirect.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Exception.hpp" + //NOTE: using CGAL SweepLines is slower !!! (example in git history) namespace { @@ -14,7 +18,7 @@ IntersectionsLines compute_intersections(const Lines &lines) auto tree = AABBTreeLines::build_aabb_tree_over_indexed_lines(lines); IntersectionsLines result; for (uint32_t li = 0; li < lines.size()-1; ++li) { - const Line &l = lines[li]; + const Line &l = lines[li]; auto intersections = AABBTreeLines::get_intersections_with_line(lines, tree, l); for (const auto &[p, node_index] : intersections) { if (node_index - 1 <= li) @@ -31,10 +35,8 @@ IntersectionsLines compute_intersections(const Lines &lines) Vec2d intersection_point = p.cast(); result.push_back(IntersectionLines{li, static_cast(node_index), intersection_point}); -} - -} - + } + } return result; } } // namespace @@ -46,5 +48,3 @@ IntersectionsLines get_intersections(const Polygons &polygons) { return comp IntersectionsLines get_intersections(const ExPolygon &expolygon) { return compute_intersections(to_lines(expolygon)); } IntersectionsLines get_intersections(const ExPolygons &expolygons) { return compute_intersections(to_lines(expolygons)); } } - - diff --git a/src/libslic3r/IntersectionPoints.hpp b/src/libslic3r/IntersectionPoints.hpp index ae0299a..914a17e 100644 --- a/src/libslic3r/IntersectionPoints.hpp +++ b/src/libslic3r/IntersectionPoints.hpp @@ -1,7 +1,14 @@ #ifndef slic3r_IntersectionPoints_hpp_ #define slic3r_IntersectionPoints_hpp_ +#include +#include +#include + #include "ExPolygon.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" namespace Slic3r { @@ -11,6 +18,7 @@ struct IntersectionLines { Vec2d intersection; }; using IntersectionsLines = std::vector; + // collect all intersecting points IntersectionsLines get_intersections(const Lines &lines); IntersectionsLines get_intersections(const Polygon &polygon); diff --git a/src/libslic3r/JumpPointSearch.cpp b/src/libslic3r/JumpPointSearch.cpp index 513d84b..229d50d 100644 --- a/src/libslic3r/JumpPointSearch.cpp +++ b/src/libslic3r/JumpPointSearch.cpp @@ -1,25 +1,22 @@ #include "JumpPointSearch.hpp" -#include "BoundingBox.hpp" -#include "ExPolygon.hpp" -#include "Point.hpp" -#include "libslic3r/AStar.hpp" -#include "libslic3r/KDTreeIndirect.hpp" -#include "libslic3r/Polygon.hpp" -#include "libslic3r/Polyline.hpp" -#include "libslic3r/libslic3r.h" + #include #include -#include #include #include #include #include -#include -#include #include #include +#include -#include +#include "BoundingBox.hpp" +#include "Point.hpp" +#include "libslic3r/AStar.hpp" +#include "libslic3r/KDTreeIndirect.hpp" +#include "libslic3r/Polyline.hpp" +#include "libslic3r/libslic3r.h" +#include "libslic3r/Layer.hpp" //#define DEBUG_FILES #ifdef DEBUG_FILES diff --git a/src/libslic3r/JumpPointSearch.hpp b/src/libslic3r/JumpPointSearch.hpp index 5f3b5fe..a4dd3e8 100644 --- a/src/libslic3r/JumpPointSearch.hpp +++ b/src/libslic3r/JumpPointSearch.hpp @@ -1,16 +1,19 @@ #ifndef SRC_LIBSLIC3R_JUMPPOINTSEARCH_HPP_ #define SRC_LIBSLIC3R_JUMPPOINTSEARCH_HPP_ +#include +#include + #include "BoundingBox.hpp" #include "Polygon.hpp" #include "libslic3r/Layer.hpp" #include "libslic3r/Point.hpp" #include "libslic3r/Polyline.hpp" #include "libslic3r/libslic3r.h" -#include -#include +#include "libslic3r/Line.hpp" namespace Slic3r { +class Layer; class JPSPathFinder { diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index 87a295d..402983d 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -1,4 +1,13 @@ #include "Layer.hpp" + +#include +#include +#include +#include +#include +#include +#include + #include "ClipperZUtils.hpp" #include "ClipperUtils.hpp" #include "Point.hpp" @@ -7,9 +16,14 @@ #include "ShortestPath.hpp" #include "SVG.hpp" #include "BoundingBox.hpp" -#include "clipper/clipper.hpp" - -#include +#include "libslic3r/ExtrusionEntity.hpp" +#include "libslic3r/ExtrusionEntityCollection.hpp" +#include "libslic3r/LayerRegion.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/Surface.hpp" +#include "libslic3r/SurfaceCollection.hpp" +#include "libslic3r/Utils.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { @@ -929,11 +943,12 @@ void Layer::sort_perimeters_into_islands( // Not referenced by any map_expolygon_to_region_and_fill. new_pos = last ++; // Move just the content of m_fill_expolygons to fills_temp, but don't move the container vector. - auto &fills = layerm.m_fill_expolygons; + auto &fills = layerm.m_fill_expolygons; auto &fill_bboxes = layerm.m_fill_expolygons_bboxes; assert(fills.size() == fill_bboxes.size()); assert(last == int(fills.size())); + fills_temp.resize(fills.size()); fills_temp.assign(std::make_move_iterator(fills.begin()), std::make_move_iterator(fills.end())); @@ -942,7 +957,7 @@ void Layer::sort_perimeters_into_islands( // Move / reorder the ExPolygons and BoundingBoxes back into m_fill_expolygons and m_fill_expolygons_bboxes. for (size_t old_pos = 0; old_pos < new_positions.size(); ++old_pos) { - fills[new_positions[old_pos]] = std::move(fills_temp[old_pos]); + fills[new_positions[old_pos]] = std::move(fills_temp[old_pos]); fill_bboxes[new_positions[old_pos]] = std::move(fill_bboxes_temp[old_pos]); } } @@ -955,9 +970,11 @@ void Layer::sort_perimeters_into_islands( auto insert_into_island = [ // Region where the perimeters, gap fills and fill expolygons are stored. - region_id, + region_id, // Whether there are infills with different regions generated for this LayerSlice. has_multiple_regions, + // Layer split into surfaces + &slices, // Perimeters and gap fills to be sorted into islands. &perimeter_and_gapfill_ranges, // Infill regions to be sorted into islands. @@ -970,13 +987,14 @@ void Layer::sort_perimeters_into_islands( lslices_ex[lslice_idx].islands.push_back({}); LayerIsland &island = lslices_ex[lslice_idx].islands.back(); island.perimeters = LayerExtrusionRange(region_id, perimeter_and_gapfill_ranges[source_slice_idx].first); + island.boundary = slices.surfaces[source_slice_idx].expolygon; island.thin_fills = perimeter_and_gapfill_ranges[source_slice_idx].second; if (ExPolygonRange fill_range = fill_expolygons_ranges[source_slice_idx]; ! fill_range.empty()) { if (has_multiple_regions) { // Check whether the fill expolygons of this island were split into multiple regions. island.fill_region_id = LayerIsland::fill_region_composite_id; for (uint32_t fill_idx : fill_range) { - if (const int fill_regon_id = map_expolygon_to_region_and_fill[fill_idx].region_id; + if (const int fill_regon_id = map_expolygon_to_region_and_fill[fill_idx].region_id; fill_regon_id == -1 || (island.fill_region_id != LayerIsland::fill_region_composite_id && int(island.fill_region_id) != fill_regon_id)) { island.fill_region_id = LayerIsland::fill_region_composite_id; break; diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index dd0212c..56b9173 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -1,6 +1,18 @@ #ifndef slic3r_Layer_hpp_ #define slic3r_Layer_hpp_ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "Line.hpp" #include "libslic3r.h" #include "BoundingBox.hpp" @@ -8,16 +20,19 @@ #include "SurfaceCollection.hpp" #include "ExtrusionEntityCollection.hpp" #include "LayerRegion.hpp" - -#include +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Polyline.hpp" namespace Slic3r { class ExPolygon; + using ExPolygons = std::vector; class Layer; + using LayerPtrs = std::vector; class LayerRegion; + using LayerRegionPtrs = std::vector; class PrintRegion; class PrintObject; @@ -30,7 +45,6 @@ namespace FillLightning { class Generator; }; - // Range of extrusions, referencing the source region by an index. class LayerExtrusionRange : public ExtrusionRange { @@ -68,6 +82,8 @@ private: static constexpr const uint32_t fill_region_composite_id = std::numeric_limits::max(); public: + // Boundary of the LayerIsland before perimeter generation. + ExPolygon boundary; // Perimeter extrusions in LayerRegion belonging to this island. LayerExtrusionRange perimeters; // Thin fills of the same region as perimeters. Generated by classic perimeter generator, while Arachne puts them into perimeters. diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 12f3a66..c0390f0 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -1,3 +1,15 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "ExPolygon.hpp" #include "Flow.hpp" #include "Layer.hpp" @@ -10,12 +22,17 @@ #include "BoundingBox.hpp" #include "SVG.hpp" #include "Algorithm/RegionExpansion.hpp" - -#include -#include -#include - -#include +#include "libslic3r/ExtrusionEntity.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/MultiMaterialSegmentation.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/Polyline.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/SurfaceCollection.hpp" +#include "libslic3r/Utils.hpp" +#include "libslic3r/libslic3r.h" +#include "LayerRegion.hpp" namespace Slic3r { diff --git a/src/libslic3r/LayerRegion.hpp b/src/libslic3r/LayerRegion.hpp index 17ffb98..c7e2d3d 100644 --- a/src/libslic3r/LayerRegion.hpp +++ b/src/libslic3r/LayerRegion.hpp @@ -1,15 +1,31 @@ #ifndef slic3r_LayerRegion_hpp_ #define slic3r_LayerRegion_hpp_ +#include +#include +#include +#include +#include +#include +#include +#include + #include "BoundingBox.hpp" #include "ExtrusionEntityCollection.hpp" #include "SurfaceCollection.hpp" #include "libslic3r/Algorithm/RegionExpansion.hpp" +#include "libslic3r/ExtrusionEntity.hpp" +#include "libslic3r/Flow.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/Polyline.hpp" +#include "libslic3r/Surface.hpp" namespace Slic3r { class Layer; +class PrintObject; + using LayerPtrs = std::vector; class PrintRegion; diff --git a/src/libslic3r/Line.cpp b/src/libslic3r/Line.cpp index 7e75d56..7995a50 100644 --- a/src/libslic3r/Line.cpp +++ b/src/libslic3r/Line.cpp @@ -1,9 +1,13 @@ +#include +#include +#include +#include + #include "Geometry.hpp" #include "Line.hpp" -#include "Polyline.hpp" -#include -#include -#include +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { diff --git a/src/libslic3r/Line.hpp b/src/libslic3r/Line.hpp index af67bae..e4ff442 100644 --- a/src/libslic3r/Line.hpp +++ b/src/libslic3r/Line.hpp @@ -1,11 +1,15 @@ #ifndef slic3r_Line_hpp_ #define slic3r_Line_hpp_ +#include +#include +#include +#include +#include + #include "libslic3r.h" #include "Point.hpp" -#include - namespace Slic3r { class BoundingBox; @@ -14,6 +18,7 @@ class Line3; class Linef3; class Polyline; class ThickLine; + typedef std::vector Lines; typedef std::vector Lines3; typedef std::vector ThickLines; @@ -180,7 +185,7 @@ template bool intersection(const L &l1, const L &l2, Vec, Scalar class Line { public: - Line() {} + Line() = default; Line(const Point& _a, const Point& _b) : a(_a), b(_b) {} explicit operator Lines() const { Lines lines; lines.emplace_back(*this); return lines; } void scale(double factor) { this->a *= factor; this->b *= factor; } @@ -305,6 +310,7 @@ BoundingBox get_extents(const Lines &lines); // start Boost #include + namespace boost { namespace polygon { template <> struct geometry_concept { typedef segment_concept type; }; diff --git a/src/libslic3r/LocalesUtils.cpp b/src/libslic3r/LocalesUtils.cpp deleted file mode 100644 index 1be8e88..0000000 --- a/src/libslic3r/LocalesUtils.cpp +++ /dev/null @@ -1,100 +0,0 @@ -#include "LocalesUtils.hpp" - -#ifdef _WIN32 - #include -#endif -#include -#include - -#include - - -namespace Slic3r { - - -CNumericLocalesSetter::CNumericLocalesSetter() -{ -#ifdef _WIN32 - _configthreadlocale(_ENABLE_PER_THREAD_LOCALE); - m_orig_numeric_locale = std::setlocale(LC_NUMERIC, nullptr); - std::setlocale(LC_NUMERIC, "C"); -#elif __APPLE__ - m_original_locale = uselocale((locale_t)0); - m_new_locale = newlocale(LC_NUMERIC_MASK, "C", m_original_locale); - uselocale(m_new_locale); -#else // linux / BSD - m_original_locale = uselocale((locale_t)0); - m_new_locale = duplocale(m_original_locale); - m_new_locale = newlocale(LC_NUMERIC_MASK, "C", m_new_locale); - uselocale(m_new_locale); -#endif -} - - - -CNumericLocalesSetter::~CNumericLocalesSetter() -{ -#ifdef _WIN32 - std::setlocale(LC_NUMERIC, m_orig_numeric_locale.data()); -#else - uselocale(m_original_locale); - freelocale(m_new_locale); -#endif -} - - - -bool is_decimal_separator_point() -{ - char str[5] = ""; - sprintf(str, "%.1f", 0.5f); - return str[1] == '.'; -} - -template -static T string_to_floating_decimal_point(const std::string_view str, size_t* pos /* = nullptr*/) -{ - T out; - size_t p = fast_float::from_chars(str.data(), str.data() + str.size(), out).ptr - str.data(); - if (pos) - *pos = p; - return out; -} - -double string_to_double_decimal_point(const std::string_view str, size_t* pos /* = nullptr*/) -{ - return string_to_floating_decimal_point(str, pos); -} - -float string_to_float_decimal_point(const std::string_view str, size_t* pos /* = nullptr*/) -{ - return string_to_floating_decimal_point(str, pos); -} - -std::string float_to_string_decimal_point(double value, int precision/* = -1*/) -{ - // Our Windows build server fully supports C++17 std::to_chars. Let's use it. - // Other platforms are behind, fall back to slow stringstreams for now. -#ifdef _WIN32 - constexpr size_t SIZE = 20; - char out[SIZE] = ""; - std::to_chars_result res; - if (precision >=0) - res = std::to_chars(out, out+SIZE, value, std::chars_format::fixed, precision); - else - res = std::to_chars(out, out+SIZE, value, std::chars_format::general, 6); - if (res.ec == std::errc::value_too_large) - throw std::invalid_argument("float_to_string_decimal_point conversion failed."); - return std::string(out, res.ptr - out); -#else - std::stringstream buf; - if (precision >= 0) - buf << std::fixed << std::setprecision(precision); - buf << value; - return buf.str(); -#endif -} - - -} // namespace Slic3r - diff --git a/src/libslic3r/LocalesUtils.hpp b/src/libslic3r/LocalesUtils.hpp deleted file mode 100644 index aec50fd..0000000 --- a/src/libslic3r/LocalesUtils.hpp +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef slic3r_LocalesUtils_hpp_ -#define slic3r_LocalesUtils_hpp_ - -#include -#include -#include -#include -#include - -#ifdef __APPLE__ -#include -#endif - -namespace Slic3r { - -// RAII wrapper that sets LC_NUMERIC to "C" on construction -// and restores the old value on destruction. -class CNumericLocalesSetter { -public: - CNumericLocalesSetter(); - ~CNumericLocalesSetter(); - -private: -#ifdef _WIN32 - std::string m_orig_numeric_locale; -#else - locale_t m_original_locale; - locale_t m_new_locale; -#endif - -}; - -// A function to check that current C locale uses decimal point as a separator. -// Intended mostly for asserts. -bool is_decimal_separator_point(); - - -// A substitute for std::to_string that works according to -// C++ locales, not C locale. Meant to be used when we need -// to be sure that decimal point is used as a separator. -// (We use user C locales and "C" C++ locales in most of the code.) -std::string float_to_string_decimal_point(double value, int precision = -1); -//std::string float_to_string_decimal_point(float value, int precision = -1); -double string_to_double_decimal_point(const std::string_view str, size_t* pos = nullptr); -float string_to_float_decimal_point (const std::string_view str, size_t* pos = nullptr); - -// Set locales to "C". -inline void set_c_locales() -{ -#ifdef _WIN32 - _configthreadlocale(_ENABLE_PER_THREAD_LOCALE); - std::setlocale(LC_ALL, "C"); -#else - // We are leaking some memory here, because the newlocale() produced memory will never be released. - // This is not a problem though, as there will be a maximum one worker thread created per physical thread. - uselocale(newlocale( -#ifdef __APPLE__ - LC_ALL_MASK -#else // some Unix / Linux / BSD - LC_ALL -#endif - , "C", nullptr)); -#endif -} - -} // namespace Slic3r - -#endif // slic3r_LocalesUtils_hpp_ diff --git a/src/libslic3r/MacUtils.mm b/src/libslic3r/MacUtils.mm new file mode 100644 index 0000000..cd5c650 --- /dev/null +++ b/src/libslic3r/MacUtils.mm @@ -0,0 +1,14 @@ +#import "Utils/DirectoriesUtils.hpp" + +#import + +// Utils/DirectoriesUtils.hpp +std::string GetDataDir() +{ + NSURL* url = [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory + inDomain:NSUserDomainMask + appropriateForURL:nil create:NO error:nil]; + + return std::string([url.path UTF8String]); +} + diff --git a/src/libslic3r/Measure.cpp b/src/libslic3r/Measure.cpp index cda424e..e503885 100644 --- a/src/libslic3r/Measure.cpp +++ b/src/libslic3r/Measure.cpp @@ -1,13 +1,21 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "libslic3r/libslic3r.h" #include "Measure.hpp" #include "MeasureUtils.hpp" - #include "libslic3r/Geometry/Circle.hpp" #include "libslic3r/SurfaceMesh.hpp" - - -#include -#include +#include "admesh/stl.h" +#include "libslic3r/Point.hpp" +#include "libslic3r/TriangleMesh.hpp" #define DEBUG_EXTRACT_ALL_FEATURES_AT_ONCE 0 diff --git a/src/libslic3r/Measure.hpp b/src/libslic3r/Measure.hpp index 70f446a..b4ba34e 100644 --- a/src/libslic3r/Measure.hpp +++ b/src/libslic3r/Measure.hpp @@ -1,11 +1,19 @@ #ifndef Slic3r_Measure_hpp_ #define Slic3r_Measure_hpp_ +#include +#include #include #include +#include +#include +#include +#include +#include +#include #include "Point.hpp" - +#include "libslic3r/libslic3r.h" struct indexed_triangle_set; diff --git a/src/libslic3r/MeshBoolean.cpp b/src/libslic3r/MeshBoolean.cpp index 06fa5f3..980a143 100644 --- a/src/libslic3r/MeshBoolean.cpp +++ b/src/libslic3r/MeshBoolean.cpp @@ -2,10 +2,15 @@ #include "MeshBoolean.hpp" #include "libslic3r/TriangleMesh.hpp" #include "libslic3r/TryCatchSignal.hpp" +#include "libslic3r/Point.hpp" + #undef PI // Include igl first. It defines "L" macro which then clashes with our localization -#include +#include // IWYU pragma: keep +#include +#include + #undef L // CGAL headers @@ -13,6 +18,10 @@ #include #include #include +#include +#include +#include +#include namespace Slic3r { namespace MeshBoolean { diff --git a/src/libslic3r/MeshBoolean.hpp b/src/libslic3r/MeshBoolean.hpp index e20425c..8016ab6 100644 --- a/src/libslic3r/MeshBoolean.hpp +++ b/src/libslic3r/MeshBoolean.hpp @@ -1,11 +1,15 @@ #ifndef libslic3r_MeshBoolean_hpp_ #define libslic3r_MeshBoolean_hpp_ +#include #include #include - -#include #include +#include +#include + +#include "admesh/stl.h" +#include "libslic3r/Point.hpp" namespace Slic3r { @@ -25,6 +29,7 @@ void self_union(TriangleMesh& mesh); namespace cgal { struct CGALMesh; + struct CGALMeshDeleter { void operator()(CGALMesh *ptr); }; using CGALMeshPtr = std::unique_ptr; diff --git a/src/libslic3r/MeshNormals.cpp b/src/libslic3r/MeshNormals.cpp index 3f71178..e0b3258 100644 --- a/src/libslic3r/MeshNormals.cpp +++ b/src/libslic3r/MeshNormals.cpp @@ -1,7 +1,15 @@ #include "MeshNormals.hpp" -#include #include +#include +#include +#include +#include +#include + +#include "libslic3r/AABBMesh.hpp" +#include "libslic3r/Execution/Execution.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { diff --git a/src/libslic3r/MeshNormals.hpp b/src/libslic3r/MeshNormals.hpp index 104f45c..0915bd2 100644 --- a/src/libslic3r/MeshNormals.hpp +++ b/src/libslic3r/MeshNormals.hpp @@ -1,12 +1,17 @@ #ifndef MESHNORMALS_HPP #define MESHNORMALS_HPP -#include "AABBMesh.hpp" +#include +#include +#include +#include "AABBMesh.hpp" #include "libslic3r/Execution/ExecutionSeq.hpp" #include "libslic3r/Execution/ExecutionTBB.hpp" +#include "libslic3r/Point.hpp" namespace Slic3r { +class AABBMesh; // Get a good approximation of the normal for any picking point on the mesh. // For points projecting to a face, this is the face normal, but when the diff --git a/src/libslic3r/MeshSplitImpl.hpp b/src/libslic3r/MeshSplitImpl.hpp index 0f1fe44..3a36efd 100644 --- a/src/libslic3r/MeshSplitImpl.hpp +++ b/src/libslic3r/MeshSplitImpl.hpp @@ -2,7 +2,6 @@ #define MESHSPLITIMPL_HPP #include "TriangleMesh.hpp" -#include "libnest2d/tools/benchmark.h" #include "Execution/ExecutionTBB.hpp" namespace Slic3r { diff --git a/src/libslic3r/MinAreaBoundingBox.cpp b/src/libslic3r/MinAreaBoundingBox.cpp index da27634..8c2306c 100644 --- a/src/libslic3r/MinAreaBoundingBox.cpp +++ b/src/libslic3r/MinAreaBoundingBox.cpp @@ -1,22 +1,29 @@ #include "MinAreaBoundingBox.hpp" #include -#include +#include #if defined(_MSC_VER) && defined(__clang__) #define BOOST_NO_CXX17_HDR_STRING_VIEW #endif #include - -#include +#include #if !defined(HAS_INTRINSIC_128_TYPE) || defined(__APPLE__) #include #endif -#include +#include // IWYU pragma: keep #include +#include +#include +#include + +#include "libnest2d/common.hpp" +#include "libnest2d/geometry_traits.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { diff --git a/src/libslic3r/MinAreaBoundingBox.hpp b/src/libslic3r/MinAreaBoundingBox.hpp index 10c71c5..87ff8b9 100644 --- a/src/libslic3r/MinAreaBoundingBox.hpp +++ b/src/libslic3r/MinAreaBoundingBox.hpp @@ -2,6 +2,7 @@ #define MINAREABOUNDINGBOX_HPP #include "libslic3r/Point.hpp" +#include "libslic3r/BoundingBox.hpp" namespace Slic3r { diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 178e020..d26fc4c 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -15,6 +15,7 @@ #include "Format/3mf.hpp" #include "Format/STEP.hpp" #include "Format/SVG.hpp" +#include "Format/PrintRequest.hpp" #include @@ -28,7 +29,7 @@ #include "SVG.hpp" #include -#include "GCode/GCodeWriter.hpp" +#include "libslic3r/GCode/GCodeWriter.hpp" namespace Slic3r { @@ -127,6 +128,8 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c result = load_3mf(input_file.c_str(), *config, *config_substitutions, &model, false); else if (boost::algorithm::iends_with(input_file, ".svg")) result = load_svg(input_file, model); + else if (boost::ends_with(input_file, ".printRequest")) + result = load_printRequest(input_file.c_str(), &model); else throw Slic3r::RuntimeError("Unknown file format. Input file must have .stl, .obj, .step/.stp, .svg, .amf(.xml) or extension .3mf(.zip)."); @@ -135,9 +138,10 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c if (model.objects.empty()) throw Slic3r::RuntimeError("The supplied file couldn't be read because it's empty"); - - for (ModelObject *o : model.objects) - o->input_file = input_file; + + if (!boost::ends_with(input_file, ".printRequest")) + for (ModelObject *o : model.objects) + o->input_file = input_file; if (options & LoadAttribute::AddDefaultInstances) model.add_default_instances(); @@ -412,10 +416,13 @@ bool Model::looks_like_multipart_object() const { if (this->objects.size() <= 1) return false; + BoundingBoxf3 tbb; + for (const ModelObject *obj : this->objects) { if (obj->volumes.size() > 1 || obj->config.keys().size() > 1) return false; + BoundingBoxf3 bb_this = obj->volumes[0]->mesh().bounding_box(); // FIXME: There is sadly the case when instances are empty (AMF files). The normalization of instances in that @@ -427,8 +434,8 @@ bool Model::looks_like_multipart_object() const tbb = tbb_this; else if (tbb.intersects(tbb_this) || tbb.shares_boundary(tbb_this)) // The volumes has intersects bounding boxes or share some boundary - return true; - } + return true; + } return false; } @@ -892,7 +899,7 @@ const BoundingBoxf3& ModelObject::bounding_box_approx() const Polygon ModelInstance::convex_hull_2d() { Polygon convex_hull; - { + { const Transform3d &trafo_instance = get_matrix(); convex_hull = get_object()->convex_hull_2d(trafo_instance); } @@ -1992,6 +1999,22 @@ void ModelVolume::convert_from_meters() this->source.is_converted_from_meters = true; } +std::vector ModelVolume::get_extruders_from_multi_material_painting() const { + if (!this->is_mm_painted()) + return {}; + + assert(static_cast(TriangleStateType::Extruder1) - 1 == 0); + const TriangleSelector::TriangleSplittingData &data = this->mm_segmentation_facets.get_data(); + + std::vector extruders; + for (size_t state_idx = static_cast(TriangleStateType::Extruder1); state_idx < data.used_states.size(); ++state_idx) { + if (data.used_states[state_idx]) + extruders.emplace_back(state_idx - 1); + } + + return extruders; +} + void ModelInstance::transform_mesh(TriangleMesh* mesh, bool dont_translate) const { mesh->transform(dont_translate ? get_matrix_no_offset() : get_matrix()); @@ -2015,7 +2038,7 @@ void ModelInstance::transform_polygon(Polygon* polygon) const polygon->scale(get_scaling_factor(X), get_scaling_factor(Y)); // scale around polygon origin } -indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, EnforcerBlockerType type) const +indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, TriangleStateType type) const { TriangleSelector selector(mv.mesh()); // Reset of TriangleSelector is done inside TriangleSelector's constructor, so we don't need it to perform it again in deserialize(). @@ -2023,7 +2046,7 @@ indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, Enforce return selector.get_facets(type); } -indexed_triangle_set FacetsAnnotation::get_facets_strict(const ModelVolume& mv, EnforcerBlockerType type) const +indexed_triangle_set FacetsAnnotation::get_facets_strict(const ModelVolume& mv, TriangleStateType type) const { TriangleSelector selector(mv.mesh()); // Reset of TriangleSelector is done inside TriangleSelector's constructor, so we don't need it to perform it again in deserialize(). @@ -2031,14 +2054,14 @@ indexed_triangle_set FacetsAnnotation::get_facets_strict(const ModelVolume& mv, return selector.get_facets_strict(type); } -bool FacetsAnnotation::has_facets(const ModelVolume& mv, EnforcerBlockerType type) const +bool FacetsAnnotation::has_facets(const ModelVolume& mv, TriangleStateType type) const { return TriangleSelector::has_facets(m_data, type); } bool FacetsAnnotation::set(const TriangleSelector& selector) { - std::pair>, std::vector> sel_map = selector.serialize(); + TriangleSelector::TriangleSplittingData sel_map = selector.serialize(); if (sel_map != m_data) { m_data = std::move(sel_map); this->touch(); @@ -2049,8 +2072,8 @@ bool FacetsAnnotation::set(const TriangleSelector& selector) void FacetsAnnotation::reset() { - m_data.first.clear(); - m_data.second.clear(); + m_data.triangles_to_split.clear(); + m_data.bitstream.clear(); this->touch(); } @@ -2061,15 +2084,15 @@ std::string FacetsAnnotation::get_triangle_as_string(int triangle_idx) const { std::string out; - auto triangle_it = std::lower_bound(m_data.first.begin(), m_data.first.end(), triangle_idx, [](const std::pair &l, const int r) { return l.first < r; }); - if (triangle_it != m_data.first.end() && triangle_it->first == triangle_idx) { - int offset = triangle_it->second; - int end = ++ triangle_it == m_data.first.end() ? int(m_data.second.size()) : triangle_it->second; + auto triangle_it = std::lower_bound(m_data.triangles_to_split.begin(), m_data.triangles_to_split.end(), triangle_idx, [](const TriangleSelector::TriangleBitStreamMapping &l, const int r) { return l.triangle_idx < r; }); + if (triangle_it != m_data.triangles_to_split.end() && triangle_it->triangle_idx == triangle_idx) { + int offset = triangle_it->bitstream_start_idx; + int end = ++ triangle_it == m_data.triangles_to_split.end() ? int(m_data.bitstream.size()) : triangle_it->bitstream_start_idx; while (offset < end) { int next_code = 0; for (int i=3; i>=0; --i) { next_code = next_code << 1; - next_code |= int(m_data.second[offset + i]); + next_code |= int(m_data.bitstream[offset + i]); } offset += 4; @@ -2086,9 +2109,10 @@ std::string FacetsAnnotation::get_triangle_as_string(int triangle_idx) const void FacetsAnnotation::set_triangle_from_string(int triangle_id, const std::string& str) { assert(! str.empty()); - assert(m_data.first.empty() || m_data.first.back().first < triangle_id); - m_data.first.emplace_back(triangle_id, int(m_data.second.size())); + assert(m_data.triangles_to_split.empty() || m_data.triangles_to_split.back().triangle_idx < triangle_id); + m_data.triangles_to_split.emplace_back(triangle_id, int(m_data.bitstream.size())); + const size_t bitstream_start_idx = m_data.bitstream.size(); for (auto it = str.crbegin(); it != str.crend(); ++it) { const char ch = *it; int dec = 0; @@ -2100,9 +2124,11 @@ void FacetsAnnotation::set_triangle_from_string(int triangle_id, const std::stri assert(false); // Convert to binary and append into code. - for (int i=0; i<4; ++i) - m_data.second.insert(m_data.second.end(), bool(dec & (1 << i))); + for (int i = 0; i < 4; ++i) + m_data.bitstream.insert(m_data.bitstream.end(), bool(dec & (1 << i))); } + + m_data.update_used_states(bitstream_start_idx); } // Test whether the two models contain the same number of ModelObjects with the same set of IDs diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 9de4f72..0a9051f 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -15,6 +15,7 @@ #include "enum_bitmask.hpp" #include "TextConfiguration.hpp" #include "EmbossShape.hpp" +#include "TriangleSelector.hpp" #include #include @@ -44,7 +45,6 @@ class ModelVolume; class ModelWipeTower; class Print; class SLAPrint; -class TriangleSelector; namespace UndoRedo { class StackImpl; @@ -642,29 +642,6 @@ private: void update_min_max_z(); }; -enum class EnforcerBlockerType : int8_t { - // Maximum is 3. The value is serialized in TriangleSelector into 2 bits. - NONE = 0, - ENFORCER = 1, - BLOCKER = 2, - // Maximum is 15. The value is serialized in TriangleSelector into 6 bits using a 2 bit prefix code. - Extruder1 = ENFORCER, - Extruder2 = BLOCKER, - Extruder3, - Extruder4, - Extruder5, - Extruder6, - Extruder7, - Extruder8, - Extruder9, - Extruder10, - Extruder11, - Extruder12, - Extruder13, - Extruder14, - Extruder15, -}; - enum class ConversionType : int { CONV_TO_INCH, CONV_FROM_INCH, @@ -675,14 +652,14 @@ enum class ConversionType : int { class FacetsAnnotation final : public ObjectWithTimestamp { public: // Assign the content if the timestamp differs, don't assign an ObjectID. - void assign(const FacetsAnnotation& rhs) { if (! this->timestamp_matches(rhs)) { m_data = rhs.m_data; this->copy_timestamp(rhs); } } - void assign(FacetsAnnotation&& rhs) { if (! this->timestamp_matches(rhs)) { m_data = std::move(rhs.m_data); this->copy_timestamp(rhs); } } - const std::pair>, std::vector>& get_data() const throw() { return m_data; } + void assign(const FacetsAnnotation &rhs) { if (! this->timestamp_matches(rhs)) { m_data = rhs.m_data; this->copy_timestamp(rhs); } } + void assign(FacetsAnnotation &&rhs) { if (! this->timestamp_matches(rhs)) { m_data = std::move(rhs.m_data); this->copy_timestamp(rhs); } } + const TriangleSelector::TriangleSplittingData &get_data() const noexcept { return m_data; } bool set(const TriangleSelector& selector); - indexed_triangle_set get_facets(const ModelVolume& mv, EnforcerBlockerType type) const; - indexed_triangle_set get_facets_strict(const ModelVolume& mv, EnforcerBlockerType type) const; - bool has_facets(const ModelVolume& mv, EnforcerBlockerType type) const; - bool empty() const { return m_data.first.empty(); } + indexed_triangle_set get_facets(const ModelVolume& mv, TriangleStateType type) const; + indexed_triangle_set get_facets_strict(const ModelVolume& mv, TriangleStateType type) const; + bool has_facets(const ModelVolume& mv, TriangleStateType type) const; + bool empty() const { return m_data.triangles_to_split.empty(); } // Following method clears the config and increases its timestamp, so the deleted // state is considered changed from perspective of the undo/redo stack. @@ -692,11 +669,11 @@ public: std::string get_triangle_as_string(int i) const; // Before deserialization, reserve space for n_triangles. - void reserve(int n_triangles) { m_data.first.reserve(n_triangles); } + void reserve(int n_triangles) { m_data.triangles_to_split.reserve(n_triangles); } // Deserialize triangles one by one, with strictly increasing triangle_id. void set_triangle_from_string(int triangle_id, const std::string& str); // After deserializing the last triangle, shrink data to fit. - void shrink_to_fit() { m_data.first.shrink_to_fit(); m_data.second.shrink_to_fit(); } + void shrink_to_fit() { m_data.triangles_to_split.shrink_to_fit(); m_data.bitstream.shrink_to_fit(); } private: // Constructors to be only called by derived classes. @@ -706,9 +683,9 @@ private: // by an existing ID copied from elsewhere. explicit FacetsAnnotation(int) : ObjectWithTimestamp(-1) {} // Copy constructor copies the ID. - explicit FacetsAnnotation(const FacetsAnnotation &rhs) = default; + FacetsAnnotation(const FacetsAnnotation &rhs) = default; // Move constructor copies the ID. - explicit FacetsAnnotation(FacetsAnnotation &&rhs) = default; + FacetsAnnotation(FacetsAnnotation &&rhs) = default; // called by ModelVolume::assign_copy() FacetsAnnotation& operator=(const FacetsAnnotation &rhs) = default; @@ -717,12 +694,9 @@ private: friend class cereal::access; friend class UndoRedo::StackImpl; - template void serialize(Archive &ar) - { - ar(cereal::base_class(this), m_data); - } + template void serialize(Archive &ar) { ar(cereal::base_class(this), m_data); } - std::pair>, std::vector> m_data; + TriangleSelector::TriangleSplittingData m_data; // To access set_new_unique_id() when copy / pasting a ModelVolume. friend class ModelVolume; @@ -821,6 +795,7 @@ public: // Is set only when volume is Embossed Shape // Contain 2d information about embossed shape to be editabled std::optional emboss_shape; + // A parent object owning this modifier volume. ModelObject* get_object() const { return this->object; } ModelVolumeType type() const { return m_type; } @@ -923,6 +898,14 @@ public: bool is_seam_painted() const { return !this->seam_facets.empty(); } bool is_mm_painted() const { return !this->mm_segmentation_facets.empty(); } + // Returns 0-based indices of extruders painted by multi-material painting gizmo. + std::vector get_extruders_from_multi_material_painting() const; + + static size_t get_extruder_color_idx(const ModelVolume& model_volume, const int extruders_count) { + const int extruder_id = model_volume.extruder_id(); + return (extruder_id <= 0 || extruder_id > extruders_count) ? 0 : extruder_id - 1; + } + protected: friend class Print; friend class SLAPrint; diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp index b5f53df..5812d02 100644 --- a/src/libslic3r/ModelArrange.cpp +++ b/src/libslic3r/ModelArrange.cpp @@ -3,9 +3,11 @@ #include #include #include - #include -#include +#include + +#include "libslic3r/Arrange/ArrangeSettingsView.hpp" +#include "libslic3r/Arrange/Scene.hpp" namespace Slic3r { diff --git a/src/libslic3r/ModelArrange.hpp b/src/libslic3r/ModelArrange.hpp index 420f102..e918a8d 100644 --- a/src/libslic3r/ModelArrange.hpp +++ b/src/libslic3r/ModelArrange.hpp @@ -2,11 +2,21 @@ #define MODELARRANGE_HPP #include +#include +#include +#include + +#include "libslic3r/Arrange/Core/Beds.hpp" namespace Slic3r { class Model; class ModelInstance; + +namespace arr2 { +class ArrangeSettingsView; +} // namespace arr2 + using ModelInstancePtrs = std::vector; //void duplicate(Model &model, ArrangePolygons &copies, VirtualBedFn); diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index 9acd9dd..6e5762e 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -1,20 +1,44 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "BoundingBox.hpp" #include "ClipperUtils.hpp" #include "EdgeGrid.hpp" #include "Layer.hpp" #include "Print.hpp" -#include "Geometry/VoronoiVisualUtils.hpp" #include "Geometry/VoronoiUtils.hpp" #include "MutablePolygon.hpp" -#include "format.hpp" - -#include -#include - -#include -#include -#include -#include +#include "admesh/stl.h" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Flow.hpp" +#include "libslic3r/Geometry/VoronoiOffset.hpp" +#include "libslic3r/LayerRegion.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/Surface.hpp" +#include "libslic3r/TriangleMeshSlicer.hpp" +#include "libslic3r/TriangleSelector.hpp" +#include "libslic3r/Utils.hpp" +#include "libslic3r/libslic3r.h" +#include "MultiMaterialSegmentation.hpp" //#define MM_SEGMENTATION_DEBUG_GRAPH //#define MM_SEGMENTATION_DEBUG_REGIONS @@ -138,8 +162,7 @@ BoundingBox get_extents(const std::vector &colored_polygons) { for (const ColoredLine &colored_line : colored_lines) { bbox.merge(colored_line.line.a); bbox.merge(colored_line.line.b); -} - + } } return bbox; } @@ -182,7 +205,6 @@ static std::vector> get_segments(const ColoredLines &p return segments; } - static std::vector filter_painted_lines(const Line &line_to_process, const size_t start_idx, const size_t end_idx, const std::vector &painted_lines) { const int filter_eps_value = scale_(0.1f); @@ -280,9 +302,9 @@ static ColoredLines colorize_line(const Line &line_to_process, assert(start_idx < painted_contour.size() && end_idx < painted_contour.size() && start_idx <= end_idx); assert(std::all_of(painted_contour.begin() + start_idx, painted_contour.begin() + end_idx + 1, [&painted_contour, &start_idx](const auto &p_line) { return painted_contour[start_idx].line_idx == p_line.line_idx; })); - const int filter_eps_value = scale_(0.1f); + const int filter_eps_value = scale_(0.1f); ColoredLines final_lines; - const PaintedLine &first_line = painted_contour[start_idx]; + const PaintedLine &first_line = painted_contour[start_idx]; if (double dist_to_start = (first_line.projected_line.a - line_to_process.a).cast().norm(); dist_to_start > filter_eps_value) final_lines.push_back({Line(line_to_process.a, first_line.projected_line.a), 0}); final_lines.push_back({first_line.projected_line, first_line.color}); @@ -487,23 +509,23 @@ static std::vector colorize_contours(const std::vector Vec2d { assert(left != middle); @@ -517,12 +539,12 @@ static inline bool points_inside(const Line &contour_first, const Line &contour_ double side = inward_normal.dot(edge_norm); // assert(side != 0.); return side > 0.; - } +} enum VD_ANNOTATION : Voronoi::VD::cell_type::color_type { VERTEX_ON_CONTOUR = 1, DELETED = 2 - }; +}; #ifdef MM_SEGMENTATION_DEBUG_GRAPH static void export_graph_to_svg(const std::string &path, const Voronoi::VD& vd, const std::vector& colored_polygons) { @@ -553,8 +575,8 @@ static void export_graph_to_svg(const std::string &path, const Voronoi::VD& vd, if (edge.color() != VD_ANNOTATION::DELETED) svg.draw(Line(from, to), "red", stroke_width); - } - } + } +} #endif // MM_SEGMENTATION_DEBUG_GRAPH static size_t non_deleted_edge_count(const VD::vertex_type &vertex) { @@ -603,7 +625,8 @@ static inline Vec2d mk_point_vec2d(const VD::vertex_type *point) { static inline Vec2d mk_vector_vec2d(const VD::edge_type *edge) { assert(edge != nullptr); return mk_point_vec2d(edge->vertex1()) - mk_point_vec2d(edge->vertex0()); - } +} + static inline Vec2d mk_flipped_vector_vec2d(const VD::edge_type *edge) { assert(edge != nullptr); return mk_point_vec2d(edge->vertex0()) - mk_point_vec2d(edge->vertex1()); @@ -684,8 +707,8 @@ static void remove_multiple_edges_in_vertex(const VD::vertex_type &vertex) { delete_vertex_deep(vertex_to_delete); edges_to_check.pop_back(); - } - } + } +} // Returns list of ExPolygons for each extruder + 1 for default unpainted regions. // It iterates through all nodes on the border between two different colors, and from this point, @@ -701,7 +724,7 @@ static std::vector extract_colored_segments(const std::vector extract_colored_segments(const std::vector cell_range = Geometry::VoronoiUtils::compute_segment_cell_range(cell, colored_lines.begin(), colored_lines.end()); cell_range.is_valid()) cell_range.edge_begin->vertex0()->color(VD_ANNOTATION::VERTEX_ON_CONTOUR); - } + } // Second, remove all Voronoi vertices that are outside the bounding box of input polygons. // Such Voronoi vertices are definitely not inside of input polygons, so we don't care about them. @@ -723,7 +746,7 @@ static std::vector extract_colored_segments(const std::vector(vertex) || !bbox.contains(Geometry::VoronoiUtils::to_point(vertex).cast())) delete_vertex_deep(vertex); - } + } // Third, remove all Voronoi edges that are infinite. for (const Voronoi::VD::edge_type &edge : vd.edges()) { @@ -751,13 +774,13 @@ static std::vector extract_colored_segments(const std::vectorcolor() == VD_ANNOTATION::DELETED) - continue; + continue; if (!points_inside(current_line.line, next_line.line, Geometry::VoronoiUtils::to_point(edge->vertex1()).cast())) { edge->color(VD_ANNOTATION::DELETED); edge->twin()->color(VD_ANNOTATION::DELETED); delete_vertex_deep(*edge->vertex1()); - } + } } while (edge = edge->prev()->twin(), edge != cell_range.edge_begin); } } @@ -766,20 +789,20 @@ static std::vector extract_colored_segments(const std::vector segmented_expolygons_per_extruder(num_extruders + 1); for (const Voronoi::VD::cell_type &cell : vd.cells()) { if (cell.is_degenerate() || !cell.contains_segment()) - continue; + continue; if (const Geometry::SegmentCellRange cell_range = Geometry::VoronoiUtils::compute_segment_cell_range(cell, colored_lines.begin(), colored_lines.end()); cell_range.is_valid()) { if (cell_range.edge_begin->vertex0()->color() != VD_ANNOTATION::VERTEX_ON_CONTOUR) @@ -802,8 +825,8 @@ static std::vector extract_colored_segments(const std::vectortwin(); } while (edge = edge->twin()->next(), edge != cell_range.edge_begin); @@ -813,8 +836,8 @@ static std::vector extract_colored_segments(const std::vectorvertex0()->color(VD_ANNOTATION::DELETED); segmented_expolygons_per_extruder[source_segment.color].emplace_back(std::move(segmented_polygon)); - } - } + } + } // Merge all polygons together for each extruder for (auto &segmented_expolygons : segmented_expolygons_per_extruder) @@ -858,8 +881,8 @@ static bool is_volume_sinking(const indexed_triangle_set &its, const Transform3d // Returns MM segmentation of top and bottom layers based on painting in MM segmentation gizmo static inline std::vector> mm_segmentation_top_and_bottom_layers(const PrintObject &print_object, - const std::vector &input_expolygons, - const std::function &throw_on_cancel_callback) + const std::vector &input_expolygons, + const std::function &throw_on_cancel_callback) { const size_t num_extruders = print_object.print()->config().nozzle_diameter.size() + 1; const size_t num_layers = input_expolygons.size(); @@ -891,7 +914,7 @@ static inline std::vector> mm_segmentation_top_and_botto if (mv->is_model_part()) { const Transform3d volume_trafo = object_trafo * mv->get_matrix(); for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++ extruder_idx) { - const indexed_triangle_set painted = mv->mm_segmentation_facets.get_facets_strict(*mv, EnforcerBlockerType(extruder_idx)); + const indexed_triangle_set painted = mv->mm_segmentation_facets.get_facets_strict(*mv, TriangleStateType(extruder_idx)); #ifdef MM_SEGMENTATION_DEBUG_TOP_BOTTOM { static int iRun = 0; @@ -1151,9 +1174,9 @@ static void export_regions_to_svg(const std::string &path, const std::vector> multi_material_segmentation_by_painting(con #ifdef MM_SEGMENTATION_DEBUG static int iRun = 0; #endif // MM_SEGMENTATION_DEBUG + // Merge all regions and remove small holes BOOST_LOG_TRIVIAL(debug) << "MM segmentation - slices preparation in parallel - begin"; tbb::parallel_for(tbb::blocked_range(0, num_layers), [&layers, &input_expolygons, &throw_on_cancel_callback](const tbb::blocked_range &range) { @@ -1294,7 +1318,7 @@ std::vector> multi_material_segmentation_by_painting(con tbb::parallel_for(tbb::blocked_range(1, num_extruders + 1), [&mv, &print_object, &layers, &edge_grids, &painted_lines, &painted_lines_mutex, &input_expolygons, &throw_on_cancel_callback](const tbb::blocked_range &range) { for (size_t extruder_idx = range.begin(); extruder_idx < range.end(); ++extruder_idx) { throw_on_cancel_callback(); - const indexed_triangle_set custom_facets = mv->mm_segmentation_facets.get_facets(*mv, EnforcerBlockerType(extruder_idx)); + const indexed_triangle_set custom_facets = mv->mm_segmentation_facets.get_facets(*mv, TriangleStateType(extruder_idx)); if (!mv->is_model_part() || custom_facets.indices.empty()) continue; @@ -1426,7 +1450,7 @@ std::vector> multi_material_segmentation_by_painting(con throw_on_cancel_callback(); #ifdef MM_SEGMENTATION_DEBUG_REGIONS - for (size_t layer_idx = 0; layer_idx < print_object.layers().size(); ++layer_idx) + for (size_t layer_idx = 0; layer_idx < print_object.layers().size(); ++layer_idx) export_regions_to_svg(debug_out_path("mm-regions-merged-%d-%d.svg", layer_idx, iRun), segmented_regions_merged[layer_idx], input_expolygons[layer_idx]); #endif // MM_SEGMENTATION_DEBUG_REGIONS diff --git a/src/libslic3r/MultiMaterialSegmentation.hpp b/src/libslic3r/MultiMaterialSegmentation.hpp index 9f10db9..0f464d4 100644 --- a/src/libslic3r/MultiMaterialSegmentation.hpp +++ b/src/libslic3r/MultiMaterialSegmentation.hpp @@ -1,14 +1,21 @@ #ifndef slic3r_MultiMaterialSegmentation_hpp_ #define slic3r_MultiMaterialSegmentation_hpp_ +#include #include #include +#include + +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { - class PrintObject; class ExPolygon; + using ExPolygons = std::vector; struct ColoredLine @@ -43,4 +50,5 @@ template<> struct segment_traits } }; } // namespace boost::polygon + #endif // slic3r_MultiMaterialSegmentation_hpp_ diff --git a/src/libslic3r/MultiPoint.cpp b/src/libslic3r/MultiPoint.cpp index b01ef13..8bb3d38 100644 --- a/src/libslic3r/MultiPoint.cpp +++ b/src/libslic3r/MultiPoint.cpp @@ -1,5 +1,13 @@ #include "MultiPoint.hpp" + +#include +#include +#include +#include + #include "BoundingBox.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { @@ -103,7 +111,6 @@ bool MultiPoint::remove_duplicate_points() return false; } - // Visivalingam simplification algorithm https://github.com/slic3r/Slic3r/pull/3825 // thanks to @fuchstraumer /* diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp index edad80e..2b28c60 100644 --- a/src/libslic3r/MultiPoint.hpp +++ b/src/libslic3r/MultiPoint.hpp @@ -1,11 +1,25 @@ #ifndef slic3r_MultiPoint_hpp_ #define slic3r_MultiPoint_hpp_ -#include "libslic3r.h" +#include +#include +#include +#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libslic3r.h" #include "Line.hpp" #include "Point.hpp" +#include "libslic3r/Point.hpp" namespace Slic3r { @@ -128,6 +142,7 @@ inline Points douglas_peucker(const Points &src, const double tolerance) douglas_peucker(src.begin(), src.end(), std::back_inserter(out), tolerance); return out; } + class MultiPoint { public: @@ -248,6 +263,7 @@ inline double length(const Points::const_iterator begin, const Points::const_ite inline double length(const Points &pts) { return length(pts.begin(), pts.end()); } + inline double area(const Points &polygon) { double area = 0.; for (size_t i = 0, j = polygon.size() - 1; i < polygon.size(); j = i ++) diff --git a/src/libslic3r/MutablePolygon.cpp b/src/libslic3r/MutablePolygon.cpp index 45c74d0..33ea7e8 100644 --- a/src/libslic3r/MutablePolygon.cpp +++ b/src/libslic3r/MutablePolygon.cpp @@ -1,6 +1,10 @@ #include "MutablePolygon.hpp" -#include "Line.hpp" +#include +#include +#include + #include "libslic3r.h" +#include "libslic3r/Point.hpp" namespace Slic3r { diff --git a/src/libslic3r/MutablePolygon.hpp b/src/libslic3r/MutablePolygon.hpp index ea41d6f..f567750 100644 --- a/src/libslic3r/MutablePolygon.hpp +++ b/src/libslic3r/MutablePolygon.hpp @@ -1,9 +1,20 @@ #ifndef slic3r_MutablePolygon_hpp_ #define slic3r_MutablePolygon_hpp_ +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "Point.hpp" #include "Polygon.hpp" #include "ExPolygon.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { diff --git a/src/libslic3r/NSVGUtils.cpp b/src/libslic3r/NSVGUtils.cpp index e9576f6..623130a 100644 --- a/src/libslic3r/NSVGUtils.cpp +++ b/src/libslic3r/NSVGUtils.cpp @@ -1,11 +1,19 @@ #include "NSVGUtils.hpp" -#include -#include // to_chars -#include #include -#include "ClipperUtils.hpp" +#include +#include +#include +#include +#include +#include + #include "Emboss.hpp" // heal for shape +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/EmbossShape.hpp" +#include "libslic3r/Exception.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/Polyline.hpp" namespace { using namespace Slic3r; // Polygon @@ -169,7 +177,7 @@ size_t get_shapes_count(const NSVGimage &image) // << "width=\"" << size.x() << "mm\" " // << "height=\"" << size.y() << "mm\" " // << "viewBox=\"0 0 " << size.x() << " " << size.y() << "\" >\n"; -// data << "\n"; +// data << "\n"; // // std::array buffer; // auto write_point = [&tl, &buffer](std::string &d, const float *p) { @@ -331,12 +339,12 @@ LinesPath linearize_path(NSVGpath *first_path, const NSVGLineParams ¶m) // multiple use of allocated memmory for points between paths Points points; for (NSVGpath *path = first_path; path != NULL; path = path->next) { - // Flatten path + // Flatten path Point::coord_type x = to_coor(path->pts[0], param.scale); Point::coord_type y = to_coor(path->pts[1], param.scale); points.emplace_back(x, y); size_t path_size = (path->npts > 1) ? static_cast(path->npts - 1) : 0; - for (size_t i = 0; i < path_size; i += 3) { + for (size_t i = 0; i < path_size; i += 3) { const float *p = &path->pts[i * 2]; if (is_line(p)) { // point p4 @@ -433,7 +441,7 @@ struct DashesParam{ } dash_length = dash_array[dash_index] - dash_offset; - } + } }; Polylines to_dashes(const Polyline &polyline, const DashesParam& param) @@ -450,7 +458,8 @@ Polylines to_dashes(const Polyline &polyline, const DashesParam& param) // is first point prev_point = point; // copy continue; - } + } + Point diff = point - prev_point; float line_segment_length = diff.cast().norm(); while (dash_length < line_segment_length) { @@ -468,8 +477,9 @@ Polylines to_dashes(const Polyline &polyline, const DashesParam& param) dash.append(intermediate); dashes.push_back(dash); dash.clear(); + } } - } + diff -= move_point; line_segment_length -= dash_length; prev_point = intermediate; @@ -478,12 +488,13 @@ Polylines to_dashes(const Polyline &polyline, const DashesParam& param) is_line = !is_line; dash_index = (dash_index + 1) % param.dash_count; dash_length = param.dash_array[dash_index]; - } + } + if (is_line) dash.append(prev_point); dash_length -= line_segment_length; prev_point = point; // copy -} + } // add last dash if (is_line){ @@ -536,4 +547,5 @@ HealedExPolygons stroke_to_expolygons(const LinesPath &lines_path, const NSVGsha bool is_non_zero = true; return Emboss::heal_polygons(result, is_non_zero, param.max_heal_iteration); } -} // namespace \ No newline at end of file + +} // namespace diff --git a/src/libslic3r/NSVGUtils.hpp b/src/libslic3r/NSVGUtils.hpp index 2a97b8e..e760b26 100644 --- a/src/libslic3r/NSVGUtils.hpp +++ b/src/libslic3r/NSVGUtils.hpp @@ -1,14 +1,21 @@ #ifndef slic3r_NSVGUtils_hpp_ #define slic3r_NSVGUtils_hpp_ +#include #include #include #include +#include +#include + #include "Polygon.hpp" #include "ExPolygon.hpp" #include "EmbossShape.hpp" // ExPolygonsWithIds #include "nanosvg/nanosvg.h" // load SVG file +#include "libslic3r/Point.hpp" +#include "libslic3r/libslic3r.h" +// Helper function to work with nano svg namespace Slic3r { /// @@ -41,6 +48,7 @@ struct NSVGLineParams arc_tolerance(std::pow(tesselation_tolerance, 1/3.)) {} }; + /// /// Convert .svg opened by nanoSvg to shapes stored in expolygons with ids /// diff --git a/src/libslic3r/NormalUtils.cpp b/src/libslic3r/NormalUtils.cpp index dc94515..da91ed6 100644 --- a/src/libslic3r/NormalUtils.cpp +++ b/src/libslic3r/NormalUtils.cpp @@ -1,5 +1,11 @@ #include "NormalUtils.hpp" +#include +#include + +#include "libslic3r/Exception.hpp" +#include "libslic3r/Point.hpp" + using namespace Slic3r; Vec3f NormalUtils::create_triangle_normal( diff --git a/src/libslic3r/NormalUtils.hpp b/src/libslic3r/NormalUtils.hpp index 60ec57f..76f46d4 100644 --- a/src/libslic3r/NormalUtils.hpp +++ b/src/libslic3r/NormalUtils.hpp @@ -1,8 +1,11 @@ #ifndef slic3r_NormalUtils_hpp_ #define slic3r_NormalUtils_hpp_ +#include + #include "Point.hpp" #include "Model.hpp" +#include "admesh/stl.h" namespace Slic3r { diff --git a/src/libslic3r/ObjectID.hpp b/src/libslic3r/ObjectID.hpp index 4f34572..b3ce985 100644 --- a/src/libslic3r/ObjectID.hpp +++ b/src/libslic3r/ObjectID.hpp @@ -3,6 +3,11 @@ #include #include +#include +#include +#include +#include +#include namespace Slic3r { diff --git a/src/libslic3r/OpenVDBUtils.cpp b/src/libslic3r/OpenVDBUtils.cpp index 950587c..cff2358 100644 --- a/src/libslic3r/OpenVDBUtils.cpp +++ b/src/libslic3r/OpenVDBUtils.cpp @@ -8,6 +8,13 @@ #endif // _MSC_VER #include #include +#include +#include +#include +#include +#include +#include +#include #ifdef _MSC_VER #pragma warning(pop) #endif // _MSC_VER @@ -16,6 +23,14 @@ #include #include #include +#include +#include +#include +#include +#include + +#include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { diff --git a/src/libslic3r/OpenVDBUtils.hpp b/src/libslic3r/OpenVDBUtils.hpp index d499685..068359e 100644 --- a/src/libslic3r/OpenVDBUtils.hpp +++ b/src/libslic3r/OpenVDBUtils.hpp @@ -2,10 +2,16 @@ #define OPENVDBUTILS_HPP #include +#include +#include + +#include "admesh/stl.h" +#include "libslic3r/Point.hpp" namespace Slic3r { struct VoxelGrid; + struct VoxelGridDeleter { void operator()(VoxelGrid *ptr); }; using VoxelGridPtr = std::unique_ptr; diff --git a/src/libslic3r/Optimize/Optimizer.hpp b/src/libslic3r/Optimize/Optimizer.hpp index faa8867..dde5ad5 100644 --- a/src/libslic3r/Optimize/Optimizer.hpp +++ b/src/libslic3r/Optimize/Optimizer.hpp @@ -1,5 +1,5 @@ -#ifndef OPTIMIZER_HPP -#define OPTIMIZER_HPP +#ifndef QIDISLICER_OPTIMIZER_HPP +#define QIDISLICER_OPTIMIZER_HPP #include #include diff --git a/src/libslic3r/PNGReadWrite.cpp b/src/libslic3r/PNGReadWrite.cpp index 51bf7de..1327848 100644 --- a/src/libslic3r/PNGReadWrite.cpp +++ b/src/libslic3r/PNGReadWrite.cpp @@ -1,12 +1,14 @@ #include "PNGReadWrite.hpp" -#include - -#include #include - #include #include +#include +#include +#include +#include +#include +#include namespace Slic3r { namespace png { diff --git a/src/libslic3r/PNGReadWrite.hpp b/src/libslic3r/PNGReadWrite.hpp index 399c622..fc9d6ea 100644 --- a/src/libslic3r/PNGReadWrite.hpp +++ b/src/libslic3r/PNGReadWrite.hpp @@ -1,10 +1,13 @@ #ifndef PNGREAD_HPP #define PNGREAD_HPP +#include #include #include #include #include +#include +#include namespace Slic3r { namespace png { diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 1a6214f..72cfcef 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -21,6 +21,7 @@ #include "SurfaceCollection.hpp" #include "clipper/clipper_z.hpp" +#include "Arachne/PerimeterOrder.hpp" #include "Arachne/WallToolPaths.hpp" #include "Arachne/utils/ExtrusionLine.hpp" #include "Arachne/utils/ExtrusionJunction.hpp" @@ -308,7 +309,7 @@ static bool detect_steep_overhang(const PrintRegionConfig &config, return true; } - Polygons lower_slcier_chopped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*lower_slices, extrusion_bboxs,true); + Polygons lower_slcier_chopped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*lower_slices, extrusion_bboxs); // All we need to check is whether we have lines outside `threshold` double off = threshold - 0.5 * extrusion_width; @@ -585,7 +586,7 @@ struct PerimeterGeneratorArachneExtrusion //w38 static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::Parameters & params, const Polygons & lower_slices_polygons_cache, - std::vector &pg_extrusions, + Arachne::PerimeterOrder::PerimeterExtrusions &pg_extrusions, bool & steep_overhang_contour, bool & steep_overhang_hole) { @@ -593,17 +594,17 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::P bool overhangs_reverse = params.config.overhang_reverse && params.layer_id % 2 == 1; ExtrusionEntityCollection extrusion_coll; - for (PerimeterGeneratorArachneExtrusion &pg_extrusion : pg_extrusions) { - Arachne::ExtrusionLine *extrusion = pg_extrusion.extrusion; - if (extrusion->empty()) + for (Arachne::PerimeterOrder::PerimeterExtrusion &pg_extrusion : pg_extrusions) { + Arachne::ExtrusionLine &extrusion = pg_extrusion.extrusion; + if (extrusion.empty()) continue; - const bool is_external = extrusion->inset_idx == 0; + const bool is_external = extrusion.inset_idx == 0; ExtrusionRole role_normal = is_external ? ExtrusionRole::ExternalPerimeter : ExtrusionRole::Perimeter; ExtrusionRole role_overhang = role_normal | ExtrusionRoleModifier::Bridge; if (pg_extrusion.fuzzify) - fuzzy_extrusion_line(*extrusion, scaled(params.config.fuzzy_skin_thickness.value), scaled(params.config.fuzzy_skin_point_dist.value)); + fuzzy_extrusion_line(extrusion, scaled(params.config.fuzzy_skin_thickness.value), scaled(params.config.fuzzy_skin_point_dist.value)); ExtrusionPaths paths; // detect overhanging/bridging perimeters @@ -612,9 +613,9 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::P params.object_config.support_material_contact_distance.value == 0)) { ClipperLib_Z::Path extrusion_path; - extrusion_path.reserve(extrusion->size()); + extrusion_path.reserve(extrusion.size()); BoundingBox extrusion_path_bbox; - for (const Arachne::ExtrusionJunction &ej : extrusion->junctions) { + for (const Arachne::ExtrusionJunction &ej : extrusion.junctions) { extrusion_path.emplace_back(ej.p.x(), ej.p.y(), ej.w); extrusion_path_bbox.merge(Point{ej.p.x(), ej.p.y()}); } @@ -643,14 +644,14 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::P //w38 if (overhangs_reverse && params.config.fuzzy_skin != FuzzySkinType::None) { - if (pg_extrusion.is_contour) { + if (pg_extrusion.is_contour()) { steep_overhang_contour = true; } else if (params.config.fuzzy_skin != FuzzySkinType::External) { steep_overhang_hole = true; } } - bool found_steep_overhang = (pg_extrusion.is_contour && steep_overhang_contour) || - (!pg_extrusion.is_contour && steep_overhang_hole); + bool found_steep_overhang = (pg_extrusion.is_contour() && steep_overhang_contour) || + (!pg_extrusion.is_contour() && steep_overhang_hole); if (overhangs_reverse && !found_steep_overhang) { std::map recognization_paths; for (const ExtrusionPath &path : paths) { @@ -668,7 +669,7 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::P BoundingBox extrusion_bboxs = get_extents(be_clipped); - if (detect_steep_overhang(params.config, pg_extrusion.is_contour, extrusion_bboxs, it.first, be_clipped, + if (detect_steep_overhang(params.config, pg_extrusion.is_contour(), extrusion_bboxs, it.first, be_clipped, params.lower_slices, steep_overhang_contour, steep_overhang_hole)) { break; } @@ -686,7 +687,7 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::P // Arachne sometimes creates extrusion with zero-length (just two same endpoints); if (!paths.empty()) { Point start_point = paths.front().first_point(); - if (!extrusion->is_closed) { + if (!extrusion.is_closed) { // Especially for open extrusion, we need to select a starting point that is at the start // or the end of the extrusions to make one continuous line. Also, we prefer a non-overhang // starting point. @@ -722,15 +723,15 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::P chain_and_reorder_extrusion_paths(paths, &start_point); } } else { - extrusion_paths_append(paths, *extrusion, role_normal, is_external ? params.ext_perimeter_flow : params.perimeter_flow); + extrusion_paths_append(paths, extrusion, role_normal, is_external ? params.ext_perimeter_flow : params.perimeter_flow); } // Append paths to collection. if (!paths.empty()) { - if (extrusion->is_closed) { + if (extrusion.is_closed) { ExtrusionLoop extrusion_loop(std::move(paths)); // Restore the orientation of the extrusion loop. - if (pg_extrusion.is_contour == extrusion_loop.is_clockwise()) + if (pg_extrusion.is_contour() == extrusion_loop.is_clockwise()) extrusion_loop.reverse_loop(); for (auto it = std::next(extrusion_loop.paths.begin()); it != extrusion_loop.paths.end(); ++it) { @@ -769,7 +770,7 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::P } #ifdef ARACHNE_DEBUG -static void export_perimeters_to_svg(const std::string &path, const Polygons &contours, const std::vector &perimeters, const ExPolygons &infill_area) +static void export_perimeters_to_svg(const std::string &path, const Polygons &contours, const Arachne::Perimeters &perimeters, const ExPolygons &infill_area) { coordf_t stroke_width = scale_(0.03); BoundingBox bbox = get_extents(contours); @@ -1444,11 +1445,11 @@ void PerimeterGenerator::process_arachne( ClipperLib_Z::Paths loops_paths; loops_paths.reserve(closed_loop_extrusions.size()); for (const auto &cl_extrusion : closed_loop_extrusions) { - assert(cl_extrusion->extrusion->junctions.front() == cl_extrusion->extrusion->junctions.back()); + assert(cl_extrusion->extrusion.junctions.front() == cl_extrusion->extrusion.junctions.back()); size_t loop_idx = &cl_extrusion - &closed_loop_extrusions.front(); ClipperLib_Z::Path loop_path; - loop_path.reserve(cl_extrusion->extrusion->junctions.size() - 1); - for (auto junction_it = cl_extrusion->extrusion->junctions.begin(); junction_it != std::prev(cl_extrusion->extrusion->junctions.end()); ++junction_it) + loop_path.reserve(cl_extrusion->extrusion.junctions.size() - 1); + for (auto junction_it = cl_extrusion->extrusion.junctions.begin(); junction_it != std::prev(cl_extrusion->extrusion.junctions.end()); ++junction_it) loop_path.emplace_back(junction_it->p.x(), junction_it->p.y(), loop_idx); loops_paths.emplace_back(loop_path); } @@ -1538,6 +1539,8 @@ void PerimeterGenerator::process_arachne( infill_areas = diff_ex(infill_areas, filled_area); } } + + append(out_fill_expolygons, std::move(infill_areas)); //w21 append(out_fill_no_overlap, offset2_ex(union_ex(pp),float(-min_perimeter_infill_spacing / 2.), float( min_perimeter_infill_spacing / 2.))); append(out_fill_expolygons, std::move(infill_areas)); @@ -2168,7 +2171,6 @@ void PerimeterGenerator::process_classic( last = union_ex(last, temp_gap); } - if (i == loop_number && (! has_gap_fill || params.config.fill_density.value == 0)) { // The last run of this loop is executed to collect gaps for gap fill. // As the gap fill is either disabled or not @@ -2350,7 +2352,6 @@ void PerimeterGenerator::process_classic( } append(out_fill_expolygons, std::move(infill_areas)); -} - } +} diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index 6e81aa2..9ebfc8c 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -1,20 +1,33 @@ #include "PlaceholderParser.hpp" -#include "Exception.hpp" -#include "Flow.hpp" -#include "Utils.hpp" + #include #include #include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Exception.hpp" +#include "Flow.hpp" +#include "Utils.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/libslic3r.h" +#include "libslic3r_version.h" #ifdef _MSC_VER #include // provides **_environ #else - #include // provides **environ #endif #ifdef __APPLE__ #include + #undef environ #define environ (*_NSGetEnviron()) #else @@ -25,8 +38,18 @@ #endif #endif -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // Spirit v2.5 allows you to suppress automatic generation // of predefined terminals to speed up complation. With @@ -37,30 +60,17 @@ #define BOOST_RESULT_OF_USE_DECLTYPE #define BOOST_SPIRIT_USE_PHOENIX_V3 -#include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include - #include #include // #define USE_CPP11_REGEX #ifdef USE_CPP11_REGEX #include + #define SLIC3R_REGEX_NAMESPACE std #else /* USE_CPP11_REGEX */ - #include #define SLIC3R_REGEX_NAMESPACE boost #endif /* USE_CPP11_REGEX */ diff --git a/src/libslic3r/PlaceholderParser.hpp b/src/libslic3r/PlaceholderParser.hpp index 39c3320..4737164 100644 --- a/src/libslic3r/PlaceholderParser.hpp +++ b/src/libslic3r/PlaceholderParser.hpp @@ -1,15 +1,19 @@ #ifndef slic3r_PlaceholderParser_hpp_ #define slic3r_PlaceholderParser_hpp_ -#include "libslic3r.h" #include #include #include #include #include +#include + +#include "libslic3r.h" #include "PrintConfig.hpp" +#include "libslic3r/Config.hpp" namespace Slic3r { +class DynamicPrintConfig; class PlaceholderParser { diff --git a/src/libslic3r/Point.cpp b/src/libslic3r/Point.cpp index aecc605..230f3ed 100644 --- a/src/libslic3r/Point.cpp +++ b/src/libslic3r/Point.cpp @@ -1,9 +1,11 @@ #include "Point.hpp" -#include "Line.hpp" -#include "MultiPoint.hpp" + +#include +#include + #include "Int128.hpp" #include "BoundingBox.hpp" -#include +#include "libslic3r/libslic3r.h" namespace Slic3r { @@ -48,8 +50,8 @@ Pointf3s transform(const Pointf3s& points, const Transform3d& t) void Point::rotate(double angle, const Point ¢er) { Vec2d cur = this->cast(); - double s = ::sin(angle); - double c = ::cos(angle); + double s = ::sin(angle); + double c = ::cos(angle); auto d = cur - center.cast(); this->x() = fast_round_up(center.x() + c * d.x() - s * d.y()); this->y() = fast_round_up(center.y() + s * d.x() + c * d.y()); diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 23bf107..3b1aa14 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -1,26 +1,34 @@ #ifndef slic3r_Point_hpp_ #define slic3r_Point_hpp_ -#include "libslic3r.h" +#include +#include #include #include #include #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include -#include - - -#include - +#include "libslic3r.h" #include "LocalesUtils.hpp" +#include "libslic3r/Point.hpp" namespace Slic3r { class BoundingBox; class BoundingBoxf; class Point; + using Vector = Point; // Base template for eigen derived vectors @@ -166,6 +174,7 @@ inline const Vec3d get_base(unsigned index, const Transform3d::LinearPart &trans inline const Vec3d get_x_base(const Transform3d::LinearPart &transform) { return get_base(0, transform); } inline const Vec3d get_y_base(const Transform3d::LinearPart &transform) { return get_base(1, transform); } inline const Vec3d get_z_base(const Transform3d::LinearPart &transform) { return get_base(2, transform); } + template using Vec = Eigen::Matrix; class Point : public Vec2crd @@ -258,6 +267,16 @@ inline bool is_approx(const Vec3d &p1, const Vec3d &p2, double epsilon = EPSILON return d.x() < epsilon && d.y() < epsilon && d.z() < epsilon; } +inline bool is_approx(const Matrix3d &m1, const Matrix3d &m2, double epsilon = EPSILON) +{ + for (size_t i = 0; i < 3; i++) + for (size_t j = 0; j < 3; j++) + if (!is_approx(m1(i, j), m2(i, j), epsilon)) + return false; + + return true; +} + inline Point lerp(const Point &a, const Point &b, double t) { assert((t >= -EPSILON) && (t <= 1. + EPSILON)); @@ -586,11 +605,13 @@ static bool apply(T &val, const MinMax &limit) } return false; } + } // namespace Slic3r // start Boost #include #include + namespace boost { namespace polygon { template <> struct geometry_concept { using type = point_concept; }; @@ -618,6 +639,7 @@ namespace boost { namespace polygon { // end Boost #include + // Serialization through the Cereal library namespace cereal { // template void serialize(Archive& archive, Slic3r::Vec2crd &v) { archive(v.x(), v.y()); } diff --git a/src/libslic3r/Polygon.cpp b/src/libslic3r/Polygon.cpp index 30e9640..7fda3f2 100644 --- a/src/libslic3r/Polygon.cpp +++ b/src/libslic3r/Polygon.cpp @@ -1,10 +1,18 @@ +#include +#include +#include +#include +#include + #include "BoundingBox.hpp" -#include "ClipperUtils.hpp" #include "Exception.hpp" #include "Polygon.hpp" #include "Polyline.hpp" - -#include +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/MultiPoint.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { @@ -468,6 +476,7 @@ bool remove_same_neighbor(Polygons &polygons) polygons.erase(std::remove_if(polygons.begin(), polygons.end(), [](const Polygon &p) { return p.points.size() <= 2; }), polygons.end()); return exist; } + static inline bool is_stick(const Point &p1, const Point &p2, const Point &p3) { Point v1 = p2 - p1; diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index 1fd74b0..0bcd6c9 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -1,9 +1,20 @@ #ifndef slic3r_Polygon_hpp_ #define slic3r_Polygon_hpp_ -#include "libslic3r.h" +#include +#include +#include #include #include +#include +#include +#include +#include +#include +#include +#include + +#include "libslic3r.h" #include "Line.hpp" #include "Point.hpp" #include "MultiPoint.hpp" @@ -12,6 +23,8 @@ namespace Slic3r { class Polygon; +class BoundingBox; + using Polygons = std::vector>; using PolygonPtrs = std::vector>; using ConstPolygonPtrs = std::vector>; @@ -112,6 +125,7 @@ bool has_duplicate_points(const Polygons &polys); // Return True when erase some otherwise False. bool remove_same_neighbor(Polygon &polygon); bool remove_same_neighbor(Polygons &polygons); + inline double total_length(const Polygons &polylines) { double total = 0; for (Polygons::const_iterator it = polylines.begin(); it != polylines.end(); ++it) @@ -257,6 +271,7 @@ inline Polygons to_polygons(const Polylines &polylines) } return out; } + inline Polygons to_polygons(const VecOfPoints &paths) { Polygons out; @@ -307,6 +322,7 @@ template IntegerOnly reserve_polygons(I cap) // start Boost #include + namespace boost { namespace polygon { template <> struct geometry_concept{ typedef polygon_concept type; }; diff --git a/src/libslic3r/PolygonTrimmer.cpp b/src/libslic3r/PolygonTrimmer.cpp index 2c4e06f..2a04fd5 100644 --- a/src/libslic3r/PolygonTrimmer.cpp +++ b/src/libslic3r/PolygonTrimmer.cpp @@ -1,6 +1,11 @@ #include "PolygonTrimmer.hpp" + +#include + #include "EdgeGrid.hpp" #include "Geometry.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { diff --git a/src/libslic3r/PolygonTrimmer.hpp b/src/libslic3r/PolygonTrimmer.hpp index 93e94e3..1ac89de 100644 --- a/src/libslic3r/PolygonTrimmer.hpp +++ b/src/libslic3r/PolygonTrimmer.hpp @@ -1,13 +1,15 @@ #ifndef slic3r_PolygonTrimmer_hpp_ #define slic3r_PolygonTrimmer_hpp_ -#include "libslic3r.h" #include #include + +#include "libslic3r.h" #include "Line.hpp" #include "MultiPoint.hpp" #include "Polyline.hpp" #include "Polygon.hpp" +#include "libslic3r/Point.hpp" namespace Slic3r { diff --git a/src/libslic3r/Polyline.cpp b/src/libslic3r/Polyline.cpp index 905fcac..78c5066 100644 --- a/src/libslic3r/Polyline.cpp +++ b/src/libslic3r/Polyline.cpp @@ -1,11 +1,14 @@ +#include +#include +#include +#include + #include "BoundingBox.hpp" #include "Polyline.hpp" #include "Exception.hpp" -#include "ExPolygon.hpp" #include "Line.hpp" -#include "Polygon.hpp" -#include -#include +#include "libslic3r/MultiPoint.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { @@ -227,6 +230,10 @@ bool Polyline::is_straight() const return true; } +BoundingBox ThickPolyline::bounding_box() const { + return BoundingBox(this->points); +} + BoundingBox get_extents(const Polyline &polyline) { return polyline.bounding_box(); @@ -268,6 +275,8 @@ bool remove_same_neighbor(Polylines &polylines){ polylines.erase(std::remove_if(polylines.begin(), polylines.end(), [](const Polyline &p) { return p.points.size() <= 1; }), polylines.end()); return exist; } + + const Point& leftmost_point(const Polylines &polylines) { if (polylines.empty()) @@ -321,6 +330,60 @@ std::pair foot_pt(const Points &polyline, const Point &pt) return std::make_pair(int(it_proj - polyline.begin()) - 1, foot_pt_min); } +size_t total_lines_count(const ThickPolylines &thick_polylines) { + size_t lines_cnt = 0; + for (const ThickPolyline &thick_polyline : thick_polylines) { + if (thick_polyline.points.size() > 1) { + lines_cnt += thick_polyline.points.size() - 1; + } + } + + return lines_cnt; +} + +Lines to_lines(const ThickPolyline &thick_polyline) { + Lines lines; + if (thick_polyline.points.size() >= 2) { + lines.reserve(thick_polyline.points.size() - 1); + + for (Points::const_iterator it = thick_polyline.points.begin(); it != thick_polyline.points.end() - 1; ++it) { + lines.emplace_back(*it, *(it + 1)); + } + } + + return lines; +} + +Lines to_lines(const ThickPolylines &thick_polylines) { + const size_t lines_cnt = total_lines_count(thick_polylines); + + Lines lines; + lines.reserve(lines_cnt); + for (const ThickPolyline &thick_polyline : thick_polylines) { + for (Points::const_iterator it = thick_polyline.points.begin(); it != thick_polyline.points.end() - 1; ++it) { + lines.emplace_back(*it, *(it + 1)); + } + } + + return lines; +} + +BoundingBox get_extents(const ThickPolyline &thick_polyline) { + return thick_polyline.bounding_box(); +} + +BoundingBox get_extents(const ThickPolylines &thick_polylines) { + BoundingBox bbox; + if (!thick_polylines.empty()) { + bbox = thick_polylines.front().bounding_box(); + for (size_t i = 1; i < thick_polylines.size(); ++i) { + bbox.merge(thick_polylines[i].points); + } + } + + return bbox; +} + ThickLines ThickPolyline::thicklines() const { ThickLines lines; diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index 3d783ff..24ec0b2 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -1,16 +1,26 @@ #ifndef slic3r_Polyline_hpp_ #define slic3r_Polyline_hpp_ +#include +#include +#include +#include +#include +#include +#include +#include + #include "libslic3r.h" #include "Line.hpp" #include "MultiPoint.hpp" -#include -#include +#include "libslic3r/Point.hpp" namespace Slic3r { class Polyline; struct ThickPolyline; +class BoundingBox; + typedef std::vector Polylines; typedef std::vector ThickPolylines; @@ -94,6 +104,7 @@ extern BoundingBox get_extents(const Polylines &polylines); // Return True when erase some otherwise False. bool remove_same_neighbor(Polyline &polyline); bool remove_same_neighbor(Polylines &polylines); + inline double total_length(const Polylines &polylines) { double total = 0; for (const Polyline &pl : polylines) @@ -101,30 +112,40 @@ inline double total_length(const Polylines &polylines) { return total; } -inline Lines to_lines(const Polyline &poly) -{ +inline size_t total_lines_count(const Polylines &polylines) { + size_t lines_cnt = 0; + for (const Polyline &polyline : polylines) { + if (polyline.points.size() > 1) { + lines_cnt += polyline.points.size() - 1; + } + } + + return lines_cnt; +} + +inline Lines to_lines(const Polyline &poly) { Lines lines; if (poly.points.size() >= 2) { lines.reserve(poly.points.size() - 1); - for (Points::const_iterator it = poly.points.begin(); it != poly.points.end()-1; ++it) - lines.push_back(Line(*it, *(it + 1))); + for (Points::const_iterator it = poly.points.begin(); it != poly.points.end() - 1; ++it) { + lines.emplace_back(*it, *(it + 1)); + } } + return lines; } -inline Lines to_lines(const Polylines &polys) -{ - size_t n_lines = 0; - for (size_t i = 0; i < polys.size(); ++ i) - if (polys[i].points.size() > 1) - n_lines += polys[i].points.size() - 1; +inline Lines to_lines(const Polylines &polylines) { + const size_t lines_cnt = total_lines_count(polylines); + Lines lines; - lines.reserve(n_lines); - for (size_t i = 0; i < polys.size(); ++ i) { - const Polyline &poly = polys[i]; - for (Points::const_iterator it = poly.points.begin(); it != poly.points.end()-1; ++it) - lines.push_back(Line(*it, *(it + 1))); + lines.reserve(lines_cnt); + for (const Polyline &polyline : polylines) { + for (Points::const_iterator it = polyline.points.begin(); it != polyline.points.end() - 1; ++it) { + lines.emplace_back(*it, *(it + 1)); + } } + return lines; } @@ -213,6 +234,8 @@ struct ThickPolyline { // On open ThickPolyline make no effect. void start_at_index(int index); + BoundingBox bounding_box() const; + Points points; // vector of startpoint width and endpoint width of each line segment. The size should be always (points.size()-1) * 2 // e.g. let four be points a,b,c,d. that are three lines ab, bc, cd. for each line, there should be start width, so the width vector is: @@ -233,6 +256,14 @@ inline ThickPolylines to_thick_polylines(Polylines &&polylines, const coordf_t w return out; } +size_t total_lines_count(const ThickPolylines &thick_polylines); + +Lines to_lines(const ThickPolyline &thick_polyline); +Lines to_lines(const ThickPolylines &thick_polylines); + +BoundingBox get_extents(const ThickPolyline &thick_polyline); +BoundingBox get_extents(const ThickPolylines &thick_polylines); + class Polyline3 : public MultiPoint3 { public: diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 0839ce9..2e91b53 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -18,13 +18,13 @@ #include #include #include +#include #include #include #include #include #include -#include #include #include #include @@ -36,7 +36,7 @@ #include "libslic3r.h" #include "Utils.hpp" #include "PlaceholderParser.hpp" -#include "GCode/Thumbnails.hpp" +#include "libslic3r/GCode/Thumbnails.hpp" #include "PresetBundle.hpp" @@ -148,6 +148,22 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem res.templates_profile = templates_profile->second.data() == "1"; } + const auto repo_id = vendor_section.find("repo_id"); + if (repo_id != vendor_section.not_found()) { + res.repo_id = repo_id->second.data(); + } else { + // For backward compatibility assume all profiles without repo_id are from "prod" repo + // DK: "No, dont!" + res.repo_id = ""; + } + + const auto repo_prefix = vendor_section.find("repo_prefix"); + if (repo_prefix != vendor_section.not_found()) { + res.repo_prefix = repo_prefix->second.data(); + } else { + res.repo_prefix = ""; + } + if (! load_all) { return res; } @@ -318,6 +334,7 @@ std::string Preset::remove_invalid_keys(DynamicPrintConfig &config, const Dynami std::string incorrect_keys; for (const std::string &key : config.keys()) if (! default_config.has(key)) { + BOOST_LOG_TRIVIAL(error) << key; if (incorrect_keys.empty()) incorrect_keys = key; else { @@ -467,6 +484,7 @@ static std::vector s_Preset_print_options { "mmu_segmented_region_interlocking_depth", "wipe_tower_extruder", "wipe_tower_no_sparse_layers", "wipe_tower_extra_flow", "wipe_tower_extra_spacing", "compatible_printers", "compatible_printers_condition", "inherits", "perimeter_generator", "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle", "wall_distribution_count", "min_feature_size", "min_bead_width", + "top_one_perimeter_type", "only_one_perimeter_first_layer", //B36 "first_layer_travel_speed", //B37 @@ -475,14 +493,8 @@ static std::vector s_Preset_print_options { "detect_narrow_internal_solid_infill", //Y21 "seam_gap", - //w16 - "top_one_wall_type", - //w17 - "top_area_threshold", //w21 "filter_top_gap_infill", - //w23 - "only_one_wall_first_layer", //w25 "slow_down_layers", //w26 @@ -504,7 +516,7 @@ static std::vector s_Preset_print_options { }; static std::vector s_Preset_filament_options { - "filament_colour", "filament_diameter", "filament_type", "filament_soluble", "filament_notes", "filament_max_volumetric_speed", + "filament_colour", "filament_diameter", "filament_type", "filament_soluble", "filament_abrasive", "filament_notes", "filament_max_volumetric_speed", "filament_infill_max_speed", "filament_infill_max_crossing_speed", "extrusion_multiplier", "filament_density", "filament_cost", "filament_spool_weight", "filament_loading_speed", "filament_loading_speed_start", "filament_load_time", "filament_unloading_speed", "filament_unloading_speed_start", "filament_unload_time", "filament_toolchange_delay", "filament_cooling_moves", "filament_stamping_loading_speed", "filament_stamping_distance", "filament_cooling_initial_speed", "filament_purge_multiplier", "filament_cooling_final_speed", "filament_ramming_parameters", "filament_minimal_purge_on_wipe_tower", @@ -662,7 +674,9 @@ static std::vector s_Preset_sla_material_options { "material_notes", "material_vendor", "material_print_speed", + "area_fill", "default_sla_material_profile", + "zcorrection_layers", "compatible_prints", "compatible_prints_condition", "compatible_printers", "compatible_printers_condition", "inherits", @@ -678,20 +692,40 @@ static std::vector s_Preset_sla_material_options { "material_ow_branchingsupport_pillar_diameter", "material_ow_support_points_density_relative", - - "material_ow_relative_correction_x", - "material_ow_relative_correction_y", - "material_ow_relative_correction_z", + "material_ow_absolute_correction", "material_ow_elefant_foot_compensation" }; +static std::vector s_Preset_sla_tilt_options{ + "delay_before_exposure" + ,"delay_after_exposure" + ,"tower_hop_height" + ,"tower_speed" + ,"use_tilt" + ,"tilt_down_initial_speed" + ,"tilt_down_offset_steps" + ,"tilt_down_offset_delay" + ,"tilt_down_finish_speed" + ,"tilt_down_cycles" + ,"tilt_down_delay" + ,"tilt_up_initial_speed" + ,"tilt_up_offset_steps" + ,"tilt_up_offset_delay" + ,"tilt_up_finish_speed" + ,"tilt_up_cycles" + ,"tilt_up_delay" +}; +const std::vector& tilt_options() { return s_Preset_sla_tilt_options; } + +static std::vector s_Preset_sla_material_options_all = boost::copy_range>(boost::join(s_Preset_sla_material_options, s_Preset_sla_tilt_options)); + static std::vector s_Preset_sla_printer_options { "printer_technology", "bed_shape", "bed_custom_texture", "bed_custom_model", "max_print_height", "display_width", "display_height", "display_pixels_x", "display_pixels_y", "display_mirror_x", "display_mirror_y", "display_orientation", - "fast_tilt_time", "slow_tilt_time", "high_viscosity_tilt_time", "area_fill", + "fast_tilt_time", "slow_tilt_time", "high_viscosity_tilt_time", //"area_fill", "relative_correction", "relative_correction_x", "relative_correction_y", @@ -717,7 +751,7 @@ const std::vector& Preset::machine_limits_options() { return s_Pres // of the nozzle_diameter vector. const std::vector& Preset::nozzle_options() { return print_config_def.extruder_option_keys(); } const std::vector& Preset::sla_print_options() { return s_Preset_sla_print_options; } -const std::vector& Preset::sla_material_options() { return s_Preset_sla_material_options; } +const std::vector& Preset::sla_material_options() { return s_Preset_sla_material_options_all; } const std::vector& Preset::sla_printer_options() { return s_Preset_sla_printer_options; } const std::vector& Preset::printer_options() @@ -918,25 +952,26 @@ ExternalPreset PresetCollection::load_external_preset( const size_t idx = it - m_presets.begin(); // The newly selected preset can be activated AND have to be make as visible. bool is_installed = !m_presets[idx].is_visible; + // Select the existing preset and override it with new values, so that // the differences will be shown in the preset editor against the referenced profile. this->select_preset(idx); // update dirty state only if it's needed if (!profile_print_params_same(it->config, cfg)) { - // The source config may contain keys from many possible preset types. Just copy those that relate to this preset. + // The source config may contain keys from many possible preset types. Just copy those that relate to this preset. - // Following keys are not used neither by the UI nor by the slicing core, therefore they are not important + // Following keys are not used neither by the UI nor by the slicing core, therefore they are not important // Erase them from config apply to avoid redundant "dirty" parameter in loaded preset. for (const char* key : { "print_settings_id", "filament_settings_id", "sla_print_settings_id", "sla_material_settings_id", "printer_settings_id", "filament_vendor", - "printer_model", "printer_variant", "default_print_profile", "default_filament_profile", "default_sla_print_profile", "default_sla_material_profile" }) - keys.erase(std::remove(keys.begin(), keys.end(), key), keys.end()); + "printer_model", "printer_variant", "default_print_profile", "default_filament_profile", "default_sla_print_profile", "default_sla_material_profile" }) + keys.erase(std::remove(keys.begin(), keys.end(), key), keys.end()); - this->get_edited_preset().config.apply_only(combined_config, keys, true); - this->update_dirty(); - // Don't save the newly loaded project as a "saved into project" state. - //update_saved_preset_from_current_preset(); - assert(this->get_edited_preset().is_dirty); + this->get_edited_preset().config.apply_only(combined_config, keys, true); + this->update_dirty(); + // Don't save the newly loaded project as a "saved into project" state. + //update_saved_preset_from_current_preset(); + assert(this->get_edited_preset().is_dirty); } return ExternalPreset(&(*it), this->get_edited_preset().is_dirty, is_installed); } @@ -1248,6 +1283,20 @@ const std::string& PresetCollection::get_preset_name_by_alias(const std::string& return alias; } +const std::string& PresetCollection::get_preset_name_by_alias_invisible(const std::string& alias) const +{ + for ( + // Find the 1st profile name with the alias. + auto it = Slic3r::lower_bound_by_predicate(m_map_alias_to_profile_name.begin(), m_map_alias_to_profile_name.end(), [&alias](auto& l) { return l.first < alias; }); + // Continue over all profile names with the same alias. + it != m_map_alias_to_profile_name.end() && it->first == alias; ++it) + if (auto it_preset = this->find_preset_internal(it->second); + it_preset != m_presets.end() && it_preset->name == it->second && + it_preset->is_compatible) + return it_preset->name; + return alias; +} + const std::string* PresetCollection::get_preset_name_renamed(const std::string &old_name) const { auto it_renamed = m_map_system_profile_renamed.find(old_name); @@ -1271,6 +1320,12 @@ Preset* PresetCollection::find_preset(const std::string &name, bool first_visibl first_visible_if_not_found ? &this->first_visible() : nullptr; } +size_t PresetCollection::get_preset_idx_by_name(const std::string name) const +{ + auto it = this->find_preset_internal(name); + return it != m_presets.end() ? it - m_presets.begin() : size_t(-1); +} + // Return index of the first visible preset. Certainly at least the '- default -' preset shall be visible. //B40 size_t PresetCollection::first_visible_idx() const @@ -1401,7 +1456,7 @@ static const std::set independent_from_extruder_number_options = { "gcode_substitutions", "post_process", //Y20 //B52 - "bed_exclude_area", + "bed_exclude_area" }; bool PresetCollection::is_independent_from_extruder_number_option(const std::string& opt_key) @@ -1443,6 +1498,7 @@ inline t_config_option_keys deep_diff(const ConfigBase &config_this, const Confi case coPercents:add_correct_opts_to_diff(opt_key, diff, config_other, config_this); break; case coPoints: add_correct_opts_to_diff(opt_key, diff, config_other, config_this); break; case coFloatsOrPercents: add_correct_opts_to_diff(opt_key, diff, config_other, config_this); break; + case coEnums: add_correct_opts_to_diff(opt_key, diff, config_other, config_this); break; default: diff.emplace_back(opt_key); break; } // "nozzle_diameter" is a vector option which contain info about diameter for each nozzle @@ -1520,13 +1576,13 @@ Preset& PresetCollection::select_preset(size_t idx) return m_presets[idx]; } -bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, bool force) +bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, bool force, bool force_invisible /*= false*/) { std::string name = Preset::remove_suffix_modified(name_w_suffix); // 1) Try to find the preset by its name. auto it = this->find_preset_internal(name); size_t idx = 0; - if (it != m_presets.end() && it->name == name && it->is_visible) + if (it != m_presets.end() && it->name == name && (force_invisible || it->is_visible)) // Preset found by its name and it is visible. idx = it - m_presets.begin(); else { @@ -1914,6 +1970,7 @@ static void update_preset_names_if_were_renamed(std::set& preset_na if (was_renamed) preset_names = new_names; } + // Load all printers found in dir_path. // Throws an exception on error. void PhysicalPrinterCollection::load_printers( @@ -1936,9 +1993,11 @@ void PhysicalPrinterCollection::load_printers( // This happens when there's is a preset (most likely legacy one) with the same name as a system preset // that's already been loaded from a bundle. BOOST_LOG_TRIVIAL(warning) << "Printer already present, not loading: " << name; + // But some of used printer_preset might have been renamed. // Check it and replace with new name(s) if it's needed update_preset_names_if_were_renamed(found_printer->preset_names, m_preset_bundle_owner->printers); + continue; } try { @@ -2244,6 +2303,7 @@ void PhysicalPrinterCollection::select_printer(const std::string& full_name) m_selected_preset = *it->preset_names.begin(); else m_selected_preset = it->get_preset_name(full_name); + // Check if selected preset wasn't renamed and replace it with new name if (const std::string* new_name = m_preset_bundle_owner->printers.get_preset_name_renamed(m_selected_preset)) m_selected_preset = *new_name; @@ -2311,6 +2371,7 @@ void ExtruderFilaments::select_filament(size_t idx) // Invalidate m_idx_selected, if idx is out of range m_extr_filaments m_idx_selected = (idx == size_t(-1) || idx < m_extr_filaments.size()) ? idx : size_t(-1); } + bool ExtruderFilaments::select_filament(const std::string &name_w_suffix, bool force/*= false*/) { std::string name = Preset::remove_suffix_modified(name_w_suffix); diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 013800a..4d182f7 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -34,6 +34,8 @@ public: Semver config_version; std::string config_update_url; std::string changelog_url; + std::string repo_id; + std::string repo_prefix; bool templates_profile { false }; struct PrinterVariant { @@ -61,6 +63,7 @@ public: return &v; return nullptr; } + const PrinterVariant* variant(const std::string &name) const { return const_cast(this)->variant(name); } }; std::vector models; @@ -77,6 +80,7 @@ public: // If `load_all` is false, only the header with basic info (name, version, URLs) is loaded. static VendorProfile from_ini(const boost::filesystem::path &path, bool load_all=true); static VendorProfile from_ini(const boost::property_tree::ptree &tree, const boost::filesystem::path &path, bool load_all=true); + size_t num_variants() const { size_t n = 0; for (auto &model : models) n += model.variants.size(); return n; } std::vector families() const; @@ -118,6 +122,7 @@ public: TYPE_PHYSICAL_PRINTER, // This type is here to support search through the Preferences TYPE_PREFERENCES, + TYPE_WEBVIEW, }; Type type = TYPE_INVALID; @@ -287,6 +292,7 @@ struct ExternalPreset virtual ~ExternalPreset() = default; }; + // Substitutions having been performed during parsing a set of configuration files, for example when starting up // QIDISlicer and reading the user Print / Filament / Printer profiles. using PresetsConfigSubstitutions = std::vector; @@ -327,7 +333,9 @@ public: Preset& load_preset(const std::string &path, const std::string &name, const DynamicPrintConfig &config, bool select = true); Preset& load_preset(const std::string &path, const std::string &name, DynamicPrintConfig &&config, bool select = true); - // Returns a loaded preset, returns true if an existing preset was selected AND modified from config. + // Returns a loaded preset, + // returns is_modified as true if an existing preset was selected AND modified from config, + // returns is_installed as true if a preset was selected AND set as visible during selection. // In that case the successive filament loaded for a multi material printer should not be modified, but // an external preset should be created instead. enum class LoadAndSelect { @@ -399,6 +407,7 @@ public: PresetWithVendorProfile get_edited_preset_with_vendor_profile() const { return this->get_preset_with_vendor_profile(this->get_edited_preset()); } const std::string& get_preset_name_by_alias(const std::string& alias) const; + const std::string& get_preset_name_by_alias_invisible(const std::string& alias) const; const std::string* get_preset_name_renamed(const std::string &old_name) const; // used to update preset_choice from Tab @@ -426,6 +435,8 @@ public: const Preset* find_preset(const std::string &name, bool first_visible_if_not_found = false, bool respect_active_preset = true) const { return const_cast(this)->find_preset(name, first_visible_if_not_found, respect_active_preset); } + size_t get_preset_idx_by_name(const std::string preset_name) const; + size_t first_visible_idx() const; // Return index of the first compatible preset. Certainly at least the '- default -' preset shall be compatible. // If one of the prefered_alternates is compatible, select it. @@ -515,7 +526,8 @@ public: // Select a profile by its name. Return true if the selection changed. // Without force, the selection is only updated if the index changes. // With force, the changes are reverted if the new index is the same as the old index. - bool select_preset_by_name(const std::string &name, bool force); + // With force_invisible, force preset selection even it's invisible. + bool select_preset_by_name(const std::string &name, bool force, bool force_invisible = false); // Generate a file path from a profile name. Add the ".ini" suffix if it is missing. std::string path_from_name(const std::string &new_name) const; @@ -619,6 +631,7 @@ public: PresetCollection(type, keys, defaults, default_name) {} const Preset& default_preset_for(const DynamicPrintConfig &config) const override; + const Preset* find_system_preset_by_model_and_variant(const std::string &model_id, const std::string &variant) const; bool only_default_printers() const; @@ -639,7 +652,7 @@ namespace PresetUtils { bool compare_vendor_profile_printers(const VendorProfile& vp_old, const VendorProfile& vp_new, std::vector& new_printers); } // namespace PresetUtils - + ////////////////////////////////////////////////////////////////////// class PhysicalPrinter @@ -839,6 +852,7 @@ private: // Path to the directory to store the config files into. std::string m_dir_path; + const PresetBundle* m_preset_bundle_owner{ nullptr }; }; diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index f7b20e5..c560d4c 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -14,7 +14,6 @@ #include #include -#include #include #include #include @@ -49,7 +48,7 @@ PresetBundle::PresetBundle() : // The following keys are handled by the UI, they do not have a counterpart in any StaticPrintConfig derived classes, // therefore they need to be handled differently. As they have no counterpart in StaticPrintConfig, they are not being // initialized based on PrintConfigDef(), but to empty values (zeros, empty vectors, empty strings). - + // // "compatible_printers", "compatible_printers_condition", "inherits", // "print_settings_id", "filament_settings_id", "printer_settings_id", "printer_settings_id" // "printer_vendor", "printer_model", "printer_variant", "default_print_profile", "default_filament_profile" @@ -122,7 +121,6 @@ PresetBundle& PresetBundle::operator=(const PresetBundle &rhs) printers = rhs.printers; physical_printers = rhs.physical_printers; - extruders_filaments = rhs.extruders_filaments; project_config = rhs.project_config; vendors = rhs.vendors; obsolete_presets = rhs.obsolete_presets; @@ -134,6 +132,21 @@ PresetBundle& PresetBundle::operator=(const PresetBundle &rhs) sla_materials.update_vendor_ptrs_after_copy(this->vendors); printers .update_vendor_ptrs_after_copy(this->vendors); + // Copy extruders filaments + { + if (!extruders_filaments.empty()) + extruders_filaments.clear(); + size_t i = 0; + // create extruders_filaments to correct pointer to the new filaments + for (const ExtruderFilaments& rhs_filaments : rhs.extruders_filaments) { + extruders_filaments.emplace_back(ExtruderFilaments(&filaments, i, rhs_filaments.get_selected_preset_name())); + ExtruderFilaments& this_filaments = extruders_filaments[i]; + for (size_t preset_id = 0; preset_id < this_filaments.m_filaments->size(); preset_id++) + this_filaments.filament(preset_id).is_compatible = rhs_filaments.filament(preset_id).is_compatible; + i++; + } + } + return *this; } @@ -262,6 +275,12 @@ void PresetBundle::import_newer_configs(const std::string& from) for (const boost::filesystem::path& from_dir : from_dirs) { copy_dir(from_dir, data_dir / from_dir.filename()); } + // copy ArchiveRepositoryManifest.json file in main directory + std::string em; + CopyFileResult cfr = copy_file((from_data_dir / "ArchiveRepositoryManifest.json").string(), (data_dir / "ArchiveRepositoryManifest.json").string(), em, false); + if (cfr != SUCCESS) { + BOOST_LOG_TRIVIAL(error) << "Error when copying files from " << from_data_dir << " to " << data_dir << ": " << em; + } } PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, ForwardCompatibilitySubstitutionRule substitution_rule, @@ -454,7 +473,7 @@ void PresetBundle::reset_extruder_filaments() this->extruders_filaments.emplace_back(ExtruderFilaments(&filaments, id, names[id])); } -PresetCollection&PresetBundle::get_presets(Preset::Type type) +const PresetCollection& PresetBundle::get_presets(Preset::Type type) const { assert(type >= Preset::TYPE_PRINT && type <= Preset::TYPE_PRINTER); @@ -465,6 +484,12 @@ PresetCollection&PresetBundle::get_presets(Preset::Type type) } +PresetCollection& PresetBundle::get_presets(Preset::Type type) +{ + return const_cast(const_cast(this)->get_presets(type)); +} + + const std::string& PresetBundle::get_preset_name_by_alias( const Preset::Type& preset_type, const std::string& alias, int extruder_id /*= -1*/) { // there are not aliases for Printers profiles @@ -479,6 +504,20 @@ const std::string& PresetBundle::get_preset_name_by_alias( const Preset::Type& p return presets.get_preset_name_by_alias(alias); } +const std::string& PresetBundle::get_preset_name_by_alias_invisible(const Preset::Type& preset_type, const std::string& alias) const +{ + // there are not aliases for Printers profiles + if (preset_type == Preset::TYPE_PRINTER || preset_type == Preset::TYPE_INVALID) + return alias; + + const PresetCollection& presets = preset_type == Preset::TYPE_PRINT ? prints : + preset_type == Preset::TYPE_SLA_PRINT ? sla_prints : + preset_type == Preset::TYPE_FILAMENT ? filaments : + sla_materials; + + return presets.get_preset_name_by_alias_invisible(alias); +} + void PresetBundle::save_changes_for_preset(const std::string& new_name, Preset::Type type, const std::vector& unselected_options) { @@ -533,6 +572,9 @@ bool PresetBundle::transfer_and_save(Preset::Type type, const std::string& prese return false; preset.config.apply_only(preset_from->config, options); + if (type == Preset::TYPE_FILAMENT) + cache_extruder_filaments_names(); + // Store new_name preset to disk. preset.save(); @@ -544,8 +586,7 @@ bool PresetBundle::transfer_and_save(Preset::Type type, const std::string& prese copy_bed_model_and_texture_if_needed(preset.config); if (type == Preset::TYPE_FILAMENT) { - // synchronize the first filament presets. - set_filament_preset(0, filaments.get_selected_preset_name()); + reset_extruder_filaments(); } return true; @@ -664,7 +705,7 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p if (auto it = extruder_frst.find_filament_internal(preferred_preset_name); it != extruder_frst.end() && it->preset->is_visible && it->is_compatible) { if (extruder_frst.select_filament(preferred_preset_name)) - filaments.select_preset_by_name_strict(preferred_preset_name); + filaments.select_preset_by_name_strict(preferred_preset_name); } } else if (printer_technology == ptSLA && ! preferred_selection.sla_material.empty()) { const std::string& preferred_preset_name = get_preset_name_by_alias(Preset::Type::TYPE_SLA_MATERIAL, preferred_selection.sla_material); @@ -692,7 +733,7 @@ void PresetBundle::export_selections(AppConfig &config) config.clear_section("presets"); config.set("presets", "print", prints.get_selected_preset_name()); if (!extruders_filaments.empty()) // Tomas: To prevent crash with SLA overrides - config.set("presets", "filament", extruders_filaments.front().get_selected_preset_name()); + config.set("presets", "filament", extruders_filaments.front().get_selected_preset_name()); for (unsigned i = 1; i < extruders_filaments.size(); ++i) { char name[64]; sprintf(name, "filament_%u", i); @@ -889,21 +930,22 @@ DynamicPrintConfig PresetBundle::full_sla_config() const // If the file is loaded successfully, its print / filament / printer profiles will be activated. ConfigSubstitutions PresetBundle::load_config_file(const std::string &path, ForwardCompatibilitySubstitutionRule compatibility_rule) { - if (is_gcode_file(path)) { + if (is_gcode_file(path)) { FILE* file = boost::nowide::fopen(path.c_str(), "rb"); if (file == nullptr) throw Slic3r::RuntimeError(format("Error opening file %1%", path)); std::vector cs_buffer(65536); const bool is_binary = bgcode::core::is_valid_binary_gcode(*file, true, cs_buffer.data(), cs_buffer.size()) == bgcode::core::EResult::Success; fclose(file); - DynamicPrintConfig config; - config.apply(FullPrintConfig::defaults()); + + DynamicPrintConfig config; + config.apply(FullPrintConfig::defaults()); ConfigSubstitutions config_substitutions = is_binary ? config.load_from_binary_gcode_file(path, compatibility_rule) : config.load_from_gcode_file(path, compatibility_rule); Preset::normalize(config); - load_config_file_config(path, true, std::move(config)); - return config_substitutions; - } + load_config_file_config(path, true, std::move(config)); + return config_substitutions; + } // 1) Try to load the config file into a boost property tree. boost::property_tree::ptree tree; @@ -957,6 +999,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool PrinterTechnology printer_technology = Preset::printer_technology(config); tmp_installed_presets.clear(); + // The "compatible_printers" field should not have been exported into a config.ini or a G-code anyway, // but some of the alpha versions of Slic3r did. { @@ -1130,6 +1173,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool else this->physical_printers.unselect_printer(); } + update_alias_maps(); } @@ -1205,6 +1249,7 @@ ConfigSubstitutions PresetBundle::load_config_file_config_bundle( this->extruders_filaments.clear(); this->extruders_filaments.emplace_back(ExtruderFilaments(&filaments)); + this->update_multi_material_filament_presets(); for (size_t i = 1; i < std::min(tmp_bundle.extruders_filaments.size(), this->extruders_filaments.size()); ++i) this->extruders_filaments[i].select_filament(load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.extruders_filaments[i].get_selected_preset_name(), false)); @@ -1695,6 +1740,7 @@ std::pair PresetBundle::load_configbundle( // Extruder_filaments have to be recreated with new loaded filaments this->extruders_filaments.clear(); this->extruders_filaments.emplace_back(ExtruderFilaments(&filaments)); + this->update_multi_material_filament_presets(); for (size_t i = 0; i < std::min(this->extruders_filaments.size(), active_filaments.size()); ++ i) this->extruders_filaments[i].select_filament(filaments.find_preset(active_filaments[i], true)->name); @@ -1702,9 +1748,12 @@ std::pair PresetBundle::load_configbundle( } update_alias_maps(); + return std::make_pair(std::move(substitutions), presets_loaded + ph_printers_loaded); } + + void PresetBundle::update_multi_material_filament_presets() { if (printers.get_edited_preset().printer_technology() != ptFFF) @@ -1735,6 +1784,7 @@ void PresetBundle::update_multi_material_filament_presets() std::vector old_matrix = this->project_config.option("wiping_volumes_matrix")->values; size_t old_number_of_extruders = size_t(std::sqrt(old_matrix.size())+EPSILON); if (num_extruders != old_number_of_extruders) { + // Extract the relevant config options, even values from possibly modified presets. const double default_purge = static_cast(this->printers.get_edited_preset().config.option("multimaterial_purging"))->value; const std::vector filament_purging_multipliers = get_config_options_for_current_filaments("filament_purge_multiplier"); @@ -1748,7 +1798,7 @@ void PresetBundle::update_multi_material_filament_presets() else new_matrix.push_back( i==j ? 0. : default_purge * filament_purging_multipliers[j] / 100.); } - } + } this->project_config.option("wiping_volumes_matrix")->values = new_matrix; } } @@ -1943,7 +1993,7 @@ void PresetBundle::update_compatible(PresetSelectCompatibleType select_other_pri } } -void PresetBundle::export_configbundle(const std::string &path, bool export_system_settings, bool export_physical_printers/* = false*/) +void PresetBundle::export_configbundle(const std::string &path, bool export_system_settings, bool export_physical_printers/* = false*/, std::function secret_callback) { boost::nowide::ofstream c; c.open(path, std::ios::out | std::ios::trunc); @@ -1970,8 +2020,14 @@ void PresetBundle::export_configbundle(const std::string &path, bool export_syst if (export_physical_printers) { for (const PhysicalPrinter& ph_printer : this->physical_printers) { c << std::endl << "[physical_printer:" << ph_printer.name << "]" << std::endl; - for (const std::string& opt_key : ph_printer.config.keys()) - c << opt_key << " = " << ph_printer.config.opt_serialize(opt_key) << std::endl; + for (const std::string& opt_key : ph_printer.config.keys()) { + std::string opt_val = ph_printer.config.opt_serialize(opt_key); + if (opt_val == "stored") { + secret_callback(ph_printer.name, opt_key, opt_val); + } + + c << opt_key << " = " << opt_val << std::endl; + } } } diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index a15befc..1333058 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -79,7 +79,11 @@ public: } return out; } - PresetCollection& get_presets(Preset::Type preset_type); + + + + const PresetCollection& get_presets(Preset::Type preset_type) const; + PresetCollection& get_presets(Preset::Type preset_type); // The project configuration values are kept separated from the print/filament/printer preset, // they are being serialized / deserialized from / to the .amf, .3mf, .config, .gcode, @@ -100,6 +104,7 @@ public: ObsoletePresets obsolete_presets; std::set tmp_installed_presets; + bool has_defauls_only() const { return prints.has_defaults_only() && filaments.has_defaults_only() && printers.has_defaults_only(); } @@ -144,7 +149,7 @@ public: const std::string &path, LoadConfigBundleAttributes flags, ForwardCompatibilitySubstitutionRule compatibility_rule); // Export a config bundle file containing all the presets and the names of the active presets. - void export_configbundle(const std::string &path, bool export_system_settings = false, bool export_physical_printers = false); + void export_configbundle(const std::string &path, bool export_system_settings = false, bool export_physical_printers = false, std::function secret_callback = nullptr); // Enable / disable the "- default -" preset. void set_default_suppressed(bool default_suppressed); @@ -173,6 +178,7 @@ public: void load_installed_printers(const AppConfig &config); const std::string& get_preset_name_by_alias(const Preset::Type& preset_type, const std::string& alias, int extruder_id = -1); + const std::string& get_preset_name_by_alias_invisible(const Preset::Type& preset_type, const std::string& alias) const; // Save current preset of a provided type under a new name. If the name is different from the old one, // Unselected option would be reverted to the beginning values diff --git a/src/libslic3r/PrincipalComponents2D.cpp b/src/libslic3r/PrincipalComponents2D.cpp index 7bdf793..e8d389b 100644 --- a/src/libslic3r/PrincipalComponents2D.cpp +++ b/src/libslic3r/PrincipalComponents2D.cpp @@ -1,5 +1,12 @@ #include "PrincipalComponents2D.hpp" + +#include +#include +#include + #include "Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { diff --git a/src/libslic3r/PrincipalComponents2D.hpp b/src/libslic3r/PrincipalComponents2D.hpp index dc8897a..94a51ee 100644 --- a/src/libslic3r/PrincipalComponents2D.hpp +++ b/src/libslic3r/PrincipalComponents2D.hpp @@ -1,11 +1,14 @@ #ifndef slic3r_PrincipalComponents2D_hpp_ #define slic3r_PrincipalComponents2D_hpp_ +#include +#include + #include "AABBTreeLines.hpp" #include "BoundingBox.hpp" #include "libslic3r.h" -#include #include "Polygon.hpp" +#include "libslic3r/Point.hpp" namespace Slic3r { diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 819c2d6..1b2b4b0 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -10,8 +10,8 @@ #include "ShortestPath.hpp" #include "Thread.hpp" #include "GCode.hpp" -#include "GCode/WipeTower.hpp" -#include "GCode/ConflictChecker.hpp" +#include "libslic3r/GCode/WipeTower.hpp" +#include "libslic3r/GCode/ConflictChecker.hpp" #include "Utils.hpp" #include "BuildVolume.hpp" #include "format.hpp" @@ -98,6 +98,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n "fan_always_on", "fan_below_layer_time", "full_fan_speed_layer", + "filament_abrasive", "filament_colour", "filament_diameter", "filament_density", @@ -110,6 +111,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n "first_layer_speed_over_raft", "gcode_comments", "gcode_label_objects", + "nozzle_high_flow", "infill_acceleration", "layer_gcode", "min_fan_speed", @@ -199,7 +201,10 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n // Spiral Vase forces different kind of slicing than the normal model: // In Spiral Vase mode, holes are closed and only the largest area contour is kept at each layer. // Therefore toggling the Spiral Vase on / off requires complete reslicing. - || opt_key == "spiral_vase") { + || opt_key == "spiral_vase" + || opt_key == "filament_shrinkage_compensation_xy" + || opt_key == "filament_shrinkage_compensation_z" + || opt_key == "prefer_clockwise_movements") { osteps.emplace_back(posSlice); } else if ( opt_key == "complete_objects" @@ -429,57 +434,57 @@ bool Print::sequential_print_horizontal_clearance_valid(const Print& print, Poly polygons->clear(); std::vector intersecting_idxs; - std::map map_model_object_to_convex_hull; - for (const PrintObject *print_object : print.objects()) { - assert(! print_object->model_object()->instances.empty()); - assert(! print_object->instances().empty()); - ObjectID model_object_id = print_object->model_object()->id(); - auto it_convex_hull = map_model_object_to_convex_hull.find(model_object_id); + std::map map_model_object_to_convex_hull; + for (const PrintObject *print_object : print.objects()) { + assert(! print_object->model_object()->instances.empty()); + assert(! print_object->instances().empty()); + ObjectID model_object_id = print_object->model_object()->id(); + auto it_convex_hull = map_model_object_to_convex_hull.find(model_object_id); // Get convex hull of all printable volumes assigned to this print object. ModelInstance *model_instance0 = print_object->model_object()->instances.front(); - if (it_convex_hull == map_model_object_to_convex_hull.end()) { - // Calculate the convex hull of a printable object. - // Grow convex hull with the clearance margin. - // FIXME: Arrangement has different parameters for offsetting (jtMiter, limit 2) - // which causes that the warning will be showed after arrangement with the - // appropriate object distance. Even if I set this to jtMiter the warning still shows up. - Geometry::Transformation trafo = model_instance0->get_transformation(); - trafo.set_offset({ 0.0, 0.0, model_instance0->get_offset().z() }); + if (it_convex_hull == map_model_object_to_convex_hull.end()) { + // Calculate the convex hull of a printable object. + // Grow convex hull with the clearance margin. + // FIXME: Arrangement has different parameters for offsetting (jtMiter, limit 2) + // which causes that the warning will be showed after arrangement with the + // appropriate object distance. Even if I set this to jtMiter the warning still shows up. + Geometry::Transformation trafo = model_instance0->get_transformation(); + trafo.set_offset({ 0.0, 0.0, model_instance0->get_offset().z() }); Polygon ch2d = print_object->model_object()->convex_hull_2d(trafo.get_matrix()); Polygons offs_ch2d = offset(ch2d, - // Shrink the extruder_clearance_radius a tiny bit, so that if the object arrangement algorithm placed the objects - // exactly by satisfying the extruder_clearance_radius, this test will not trigger collision. + // Shrink the extruder_clearance_radius a tiny bit, so that if the object arrangement algorithm placed the objects + // exactly by satisfying the extruder_clearance_radius, this test will not trigger collision. float(scale_(0.5 * print.config().extruder_clearance_radius.value - BuildVolume::BedEpsilon)), jtRound, scale_(0.1)); // for invalid geometries the vector returned by offset() may be empty if (!offs_ch2d.empty()) it_convex_hull = map_model_object_to_convex_hull.emplace_hint(it_convex_hull, model_object_id, offs_ch2d.front()); - } + } if (it_convex_hull != map_model_object_to_convex_hull.end()) { - // Make a copy, so it may be rotated for instances. - Polygon convex_hull0 = it_convex_hull->second; - const double z_diff = Geometry::rotation_diff_z(model_instance0->get_matrix(), print_object->instances().front().model_instance->get_matrix()); - if (std::abs(z_diff) > EPSILON) - convex_hull0.rotate(z_diff); - // Now we check that no instance of convex_hull intersects any of the previously checked object instances. - for (const PrintInstance &instance : print_object->instances()) { - Polygon convex_hull = convex_hull0; - // instance.shift is a position of a centered object, while model object may not be centered. - // Convert the shift from the PrintObject's coordinates into ModelObject's coordinates by removing the centering offset. - convex_hull.translate(instance.shift - print_object->center_offset()); - // if output needed, collect indices (inside convex_hulls_other) of intersecting hulls - for (size_t i = 0; i < convex_hulls_other.size(); ++i) { - if (! intersection(convex_hulls_other[i], convex_hull).empty()) { - if (polygons == nullptr) - return false; - else { - intersecting_idxs.emplace_back(i); - intersecting_idxs.emplace_back(convex_hulls_other.size()); + // Make a copy, so it may be rotated for instances. + Polygon convex_hull0 = it_convex_hull->second; + const double z_diff = Geometry::rotation_diff_z(model_instance0->get_matrix(), print_object->instances().front().model_instance->get_matrix()); + if (std::abs(z_diff) > EPSILON) + convex_hull0.rotate(z_diff); + // Now we check that no instance of convex_hull intersects any of the previously checked object instances. + for (const PrintInstance& instance : print_object->instances()) { + Polygon convex_hull = convex_hull0; + // instance.shift is a position of a centered object, while model object may not be centered. + // Convert the shift from the PrintObject's coordinates into ModelObject's coordinates by removing the centering offset. + convex_hull.translate(instance.shift - print_object->center_offset()); + // if output needed, collect indices (inside convex_hulls_other) of intersecting hulls + for (size_t i = 0; i < convex_hulls_other.size(); ++i) { + if (!intersection(convex_hulls_other[i], convex_hull).empty()) { + if (polygons == nullptr) + return false; + else { + intersecting_idxs.emplace_back(i); + intersecting_idxs.emplace_back(convex_hulls_other.size()); + } } } + convex_hulls_other.emplace_back(std::move(convex_hull)); } - convex_hulls_other.emplace_back(std::move(convex_hull)); - } - } + } } if (!intersecting_idxs.empty()) { @@ -523,6 +528,9 @@ std::string Print::validate(std::vector* warnings) const goto DONE; } DONE:; + + if (!this->has_same_shrinkage_compensations()) + warnings->emplace_back("_FILAMENT_SHRINKAGE_DIFFER"); } if (m_objects.empty()) @@ -581,7 +589,7 @@ std::string Print::validate(std::vector* warnings) const const PrintObject &print_object = *m_objects[print_object_idx]; //FIXME It is quite expensive to generate object layers just to get the print height! //w27 - if (auto layers = generate_object_layers(print_object.slicing_parameters(), layer_height_profile(print_object_idx),print_object.config().precise_z_height.value); + if (auto layers = generate_object_layers(print_object.slicing_parameters(), layer_height_profile(print_object_idx), print_object.config().precise_z_height.value); ! layers.empty() && layers.back() > this->config().max_print_height + EPSILON) { return // Test whether the last slicing plane is below or above the print volume. @@ -759,7 +767,7 @@ std::string Print::validate(std::vector* warnings) const if (! object->has_support() && warnings) { for (const ModelVolume* mv : object->model_object()->volumes) { bool has_enforcers = mv->is_support_enforcer() || - (mv->is_model_part() && mv->supported_facets.has_facets(*mv, EnforcerBlockerType::ENFORCER)); + (mv->is_model_part() && mv->supported_facets.has_facets(*mv, TriangleStateType::ENFORCER)); if (has_enforcers) { warnings->emplace_back("_SUPPORTS_OFF"); break; @@ -1160,7 +1168,7 @@ void Print::_make_skirt() // Initial offset of the brim inner edge from the object (possible with a support & raft). // The skirt will touch the brim if the brim is extruded. - auto distance = float(scale_(m_config.skirt_distance.value) - spacing/2.); + auto distance = float(scale_(m_config.skirt_distance.value - spacing/2.)); // Draw outlines from outside to inside. // Loop while we have less skirts than required or any extruder hasn't reached the min length if any. std::vector extruded_length(extruders.size(), 0.); @@ -1184,7 +1192,7 @@ void Print::_make_skirt() ExtrusionRole::Skirt, ExtrusionFlow{ float(mm3_per_mm), // this will be overridden at G-code export time - flow.width(), + flow.width(), float(first_layer_height) // this will be overridden at G-code export time } }); @@ -1468,6 +1476,21 @@ const WipeTowerData& Print::wipe_tower_data(size_t extruders_cnt) const return m_wipe_tower_data; } +bool is_toolchange_required( + const bool first_layer, + const unsigned last_extruder_id, + const unsigned extruder_id, + const unsigned current_extruder_id +) { + if (first_layer && extruder_id == last_extruder_id) { + return true; + } + if (extruder_id != current_extruder_id) { + return true; + } + return false; +} + void Print::_make_wipe_tower() { m_wipe_tower_data.clear(); @@ -1523,7 +1546,6 @@ void Print::_make_wipe_tower() // Initialize the wipe tower. WipeTower wipe_tower(m_config, m_default_region_config, wipe_volumes, m_wipe_tower_data.tool_ordering.first_extruder()); - // Set the extruder & material properties at the wipe tower object. for (size_t i = 0; i < m_config.nozzle_diameter.size(); ++ i) wipe_tower.set_extruder(i, m_config); @@ -1537,10 +1559,11 @@ void Print::_make_wipe_tower() unsigned int current_extruder_id = m_wipe_tower_data.tool_ordering.all_extruders().back(); for (auto &layer_tools : m_wipe_tower_data.tool_ordering.layer_tools()) { // for all layers if (!layer_tools.has_wipe_tower) continue; - bool first_layer = &layer_tools == &m_wipe_tower_data.tool_ordering.front(); wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, current_extruder_id, current_extruder_id, false); for (const auto extruder_id : layer_tools.extruders) { - if ((first_layer && extruder_id == m_wipe_tower_data.tool_ordering.all_extruders().back()) || extruder_id != current_extruder_id) { + const bool first_layer{&layer_tools == &m_wipe_tower_data.tool_ordering.front()}; + const unsigned last_extruder_id{m_wipe_tower_data.tool_ordering.all_extruders().back()}; + if (is_toolchange_required(first_layer, last_extruder_id, extruder_id, current_extruder_id)) { float volume_to_wipe = wipe_volumes[current_extruder_id][extruder_id]; // total volume to wipe after this toolchange // Not all of that can be used for infill purging: volume_to_wipe -= (float)m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id); @@ -1595,7 +1618,6 @@ void Print::_make_wipe_tower() m_wipe_tower_data.width = wipe_tower.width(); m_wipe_tower_data.first_layer_height = config().first_layer_height; m_wipe_tower_data.cone_angle = config().wipe_tower_cone_angle; - } // Generate a recommended G-code output file name based on the format template, default extension, and template parameters @@ -1608,6 +1630,7 @@ std::string Print::output_filename(const std::string &filename_base) const DynamicConfig config = this->finished() ? this->print_statistics().config() : this->print_statistics().placeholders(); config.set_key_value("num_extruders", new ConfigOptionInt((int)m_config.nozzle_diameter.size())); config.set_key_value("default_output_extension", new ConfigOptionString(".gcode")); + // Handle output_filename_format. There is a hack related to binary G-codes: gcode / bgcode substitution. std::string output_filename_format = m_config.output_filename_format.value; if (m_config.binary_gcode && boost::iends_with(output_filename_format, ".gcode")) @@ -1618,6 +1641,40 @@ std::string Print::output_filename(const std::string &filename_base) const return this->PrintBase::output_filename(output_filename_format, ".gcode", filename_base, &config); } +// Returns if all used filaments have same shrinkage compensations. +bool Print::has_same_shrinkage_compensations() const { + const std::vector extruders = this->extruders(); + if (extruders.empty()) + return false; + + const double filament_shrinkage_compensation_xy = m_config.filament_shrinkage_compensation_xy.get_at(extruders.front()); + const double filament_shrinkage_compensation_z = m_config.filament_shrinkage_compensation_z.get_at(extruders.front()); + + for (unsigned int extruder : extruders) { + if (filament_shrinkage_compensation_xy != m_config.filament_shrinkage_compensation_xy.get_at(extruder) || + filament_shrinkage_compensation_z != m_config.filament_shrinkage_compensation_z.get_at(extruder)) { + return false; + } + } + + return true; +} + +// Returns scaling for each axis representing shrinkage compensations in each axis. +Vec3d Print::shrinkage_compensation() const +{ + if (!this->has_same_shrinkage_compensations()) + return Vec3d::Ones(); + + const unsigned int first_extruder = this->extruders().front(); + const double xy_compensation_percent = std::clamp(m_config.filament_shrinkage_compensation_xy.get_at(first_extruder), -99., 99.); + const double z_compensation_percent = std::clamp(m_config.filament_shrinkage_compensation_z.get_at(first_extruder), -99., 99.); + const double xy_compensation = 100. / (100. - xy_compensation_percent); + const double z_compensation = 100. / (100. - z_compensation_percent); + + return { xy_compensation, xy_compensation, z_compensation }; +} + const std::string PrintStatistics::FilamentUsedG = "filament used [g]"; const std::string PrintStatistics::FilamentUsedGMask = "; filament used [g] ="; @@ -1637,8 +1694,12 @@ const std::string PrintStatistics::FilamentCostMask = "; filament cost ="; const std::string PrintStatistics::TotalFilamentCost = "total filament cost"; const std::string PrintStatistics::TotalFilamentCostMask = "; total filament cost ="; const std::string PrintStatistics::TotalFilamentCostValueMask = "; total filament cost = %.2lf\n"; + const std::string PrintStatistics::TotalFilamentUsedWipeTower = "total filament used for wipe tower [g]"; const std::string PrintStatistics::TotalFilamentUsedWipeTowerValueMask = "; total filament used for wipe tower [g] = %.2lf\n"; + + + DynamicConfig PrintStatistics::config() const { DynamicConfig config; diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index c84c9f5..d64bc54 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -1,8 +1,8 @@ #ifndef slic3r_Print_hpp_ #define slic3r_Print_hpp_ -#include "Fill/FillAdaptive.hpp" -#include "Fill/FillLightning.hpp" +#include "libslic3r/Fill/FillAdaptive.hpp" +#include "libslic3r/Fill/FillLightning.hpp" #include "PrintBase.hpp" #include "BoundingBox.hpp" @@ -12,10 +12,10 @@ #include "Slicing.hpp" #include "SupportSpotsGenerator.hpp" #include "TriangleMeshSlicer.hpp" -#include "GCode/ToolOrdering.hpp" -#include "GCode/WipeTower.hpp" -#include "GCode/ThumbnailData.hpp" -#include "GCode/GCodeProcessor.hpp" +#include "libslic3r/GCode/ToolOrdering.hpp" +#include "libslic3r/GCode/WipeTower.hpp" +#include "libslic3r/GCode/ThumbnailData.hpp" +#include "libslic3r/GCode/GCodeProcessor.hpp" #include "MultiMaterialSegmentation.hpp" #include "libslic3r.h" @@ -270,6 +270,7 @@ public: && this->config().brim_width.value > 0. && ! this->has_raft(); } + // This is the *total* layer count (including support layers) // this value is not supposed to be compared with Layer::id // since they have different semantics. @@ -306,7 +307,7 @@ public: // The slicing parameters are dependent on various configuration values // (layer height, first layer height, raft settings, print nozzle diameter etc). const SlicingParameters& slicing_parameters() const { return m_slicing_params; } - static SlicingParameters slicing_parameters(const DynamicPrintConfig &full_config, const ModelObject &model_object, float object_max_z); + static SlicingParameters slicing_parameters(const DynamicPrintConfig &full_config, const ModelObject &model_object, float object_max_z, const Vec3d &object_shrinkage_compensation); size_t num_printing_regions() const throw() { return m_shared_regions->all_regions.size(); } const PrintRegion& printing_region(size_t idx) const throw() { return *m_shared_regions->all_regions[idx].get(); } @@ -332,7 +333,7 @@ public: std::vector slice_support_enforcers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_ENFORCER); } // Helpers to project custom facets on slices - void project_and_append_custom_facets(bool seam, EnforcerBlockerType type, std::vector& expolys) const; + void project_and_append_custom_facets(bool seam, TriangleStateType type, std::vector& expolys) const; private: // to be called from Print only. @@ -447,6 +448,7 @@ struct WipeTowerData float cone_angle; Vec2d position; float rotation_angle; + void clear() { priming.reset(nullptr); tool_changes.clear(); @@ -473,6 +475,13 @@ private: WipeTowerData &operator=(const WipeTowerData & /* rhs */) = delete; }; +bool is_toolchange_required( + const bool first_layer, + const unsigned last_extruder_id, + const unsigned extruder_id, + const unsigned current_extruder_id +); + struct PrintStatistics { PrintStatistics() { clear(); } @@ -514,6 +523,7 @@ struct PrintStatistics filament_stats.clear(); printing_extruders.clear(); } + static const std::string FilamentUsedG; static const std::string FilamentUsedGMask; static const std::string TotalFilamentUsedG; @@ -618,7 +628,6 @@ public: // If zero, then the print is empty and the print shall not be executed. unsigned int num_object_instances() const; - const ExtrusionEntityCollection& skirt() const { return m_skirt; } const ExtrusionEntityCollection& brim() const { return m_brim; } // Convex hull of the 1st layer extrusions, for bed leveling and placing the initial purge line. @@ -645,6 +654,12 @@ public: const Polygons& get_sequential_print_clearance_contours() const { return m_sequential_print_clearance_contours; } static bool sequential_print_horizontal_clearance_valid(const Print& print, Polygons* polygons = nullptr); + // Returns if all used filaments have same shrinkage compensations. + bool has_same_shrinkage_compensations() const; + + // Returns scaling for each axis representing shrinkage compensations in each axis. + Vec3d shrinkage_compensation() const; + protected: // Invalidates the step, and its depending steps in Print. bool invalidate_step(PrintStep step); diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index 8d3f07a..191b665 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -1,7 +1,32 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "Model.hpp" #include "Print.hpp" - -#include +#include "admesh/stl.h" +#include "libslic3r/Config.hpp" +#include "libslic3r/CustomGCode.hpp" +#include "libslic3r/Geometry.hpp" +#include "libslic3r/ObjectID.hpp" +#include "libslic3r/PlaceholderParser.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/PrintBase.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/Slicing.hpp" +#include "libslic3r/TriangleSelector.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { @@ -130,13 +155,14 @@ struct PrintObjectTrafoAndInstances }; // Generate a list of trafos and XY offsets for instances of a ModelObject -static std::vector print_objects_from_model_object(const ModelObject &model_object) +static std::vector print_objects_from_model_object(const ModelObject &model_object, const Vec3d &shrinkage_compensation) { std::set trafos; PrintObjectTrafoAndInstances trafo; for (ModelInstance *model_instance : model_object.instances) if (model_instance->is_printable()) { - trafo.trafo = model_instance->get_matrix(); + Geometry::Transformation model_instance_transformation = model_instance->get_transformation(); + trafo.trafo = model_instance_transformation.get_matrix_with_applied_shrinkage_compensation(shrinkage_compensation); auto shift = Point::new_scale(trafo.trafo.data()[12], trafo.trafo.data()[13]); // Reset the XY axes of the transformation. trafo.trafo.data()[12] = 0; @@ -980,7 +1006,6 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ new_full_config.option("physical_printer_settings_id", true); new_full_config.normalize_fdm(); - // Find modified keys of the various configs. Resolve overrides extruder retract values by filament profiles. DynamicPrintConfig filament_overrides; t_config_option_keys print_diff = print_config_diffs(m_config, new_full_config, filament_overrides); @@ -1065,9 +1090,9 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ const CustomGCode::Mode next_mode = model.custom_gcode_per_print_z.mode; const bool multi_extruder_differ = (current_mode == next_mode) && (current_mode == CustomGCode::MultiExtruder || next_mode == CustomGCode::MultiExtruder); - // Tool change G-codes are applied as color changes for a single extruder printer, no need to invalidate tool ordering. - //FIXME The tool ordering may be invalidated unnecessarily if the custom_gcode_per_print_z.mode is not applicable - // to the active print / model state, and then it is reset, so it is being applicable, but empty, thus the effect is the same. + // Tool change G-codes are applied as color changes for a single extruder printer, no need to invalidate tool ordering. + // FIXME The tool ordering may be invalidated unnecessarily if the custom_gcode_per_print_z.mode is not applicable + // to the active print / model state, and then it is reset, so it is being applicable, but empty, thus the effect is the same. const bool tool_change_differ = num_extruders > 1 && custom_per_printz_gcodes_tool_changes_differ(m_model.custom_gcode_per_print_z.gcodes, model.custom_gcode_per_print_z.gcodes, CustomGCode::ToolChange); // For multi-extruder printers, we perform a tool change before a color change. // So, in that case, we must invalidate tool ordering and wipe tower even if custom color change g-codes differ. @@ -1240,7 +1265,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ // Copy the ModelObject name, input_file and instances. The instances will be compared against PrintObject instances in the next step. if (model_object.name != model_object_new.name) { update_apply_status(this->invalidate_step(psGCodeExport)); - model_object.name = model_object_new.name; + model_object.name = model_object_new.name; } model_object.input_file = model_object_new.input_file; // Only refresh ModelInstances if there is any change. @@ -1280,7 +1305,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ // Walk over all new model objects and check, whether there are matching PrintObjects. for (ModelObject *model_object : m_model.objects) { ModelObjectStatus &model_object_status = const_cast(model_object_status_db.reuse(*model_object)); - model_object_status.print_instances = print_objects_from_model_object(*model_object); + model_object_status.print_instances = print_objects_from_model_object(*model_object, this->shrinkage_compensation()); std::vector old; old.reserve(print_object_status_db.count(*model_object)); for (const PrintObjectStatus &print_object_status : print_object_status_db.get_range(*model_object)) @@ -1374,9 +1399,20 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ if (const auto &volumes = print_object.model_object()->volumes; num_extruders > 1 && std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume *v) { return ! v->mm_segmentation_facets.empty(); }) != volumes.end()) { - //FIXME be more specific! Don't enumerate extruders that are not used for painting! - painting_extruders.assign(num_extruders, 0); - std::iota(painting_extruders.begin(), painting_extruders.end(), 1); + + std::array(TriangleStateType::Count)> used_facet_states{}; + for (const ModelVolume *volume : volumes) { + const std::vector &volume_used_facet_states = volume->mm_segmentation_facets.get_data().used_states; + + assert(volume_used_facet_states.size() == used_facet_states.size()); + for (size_t state_idx = 0; state_idx < std::min(volume_used_facet_states.size(), used_facet_states.size()); ++state_idx) + used_facet_states[state_idx] |= volume_used_facet_states[state_idx]; + } + + for (size_t state_idx = static_cast(TriangleStateType::Extruder1); state_idx < used_facet_states.size(); ++state_idx) { + if (used_facet_states[state_idx]) + painting_extruders.emplace_back(state_idx); + } } if (model_object_status.print_object_regions_status == ModelObjectStatus::PrintObjectRegionsStatus::Valid) { // Verify that the trafo for regions & volume bounding boxes thus for regions is still applicable. diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index d9ae368..9ce7865 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -12,8 +12,21 @@ #include #include #include -#include - +#include +#include +#include +#include +#include +#include +#include +#include +#include "Config.hpp" +#include "I18N.hpp" +#include "format.hpp" +#include "libslic3r/GCode/Thumbnails.hpp" +#include "libslic3r/SLA/SupportTreeStrategies.hpp" +#include "libslic3r/enum_bitmask.hpp" +#include "libslic3r/libslic3r.h" #include namespace Slic3r { @@ -73,16 +86,18 @@ CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(MachineLimitsUsage) //B55 static const t_config_enum_values s_keys_map_PrintHostType { - { "qidilink", htQIDILink }, - { "qidiconnect", htQIDIConnect }, + { "qidilink", htQIDILink }, + { "qidiconnect", htQIDIConnect }, { "octoprint", htOctoPrint }, { "moonraker", htMoonraker }, - { "moonraker2", htMoonraker2 }, + { "moonraker2", htMoonraker2 }, { "duet", htDuet }, { "flashair", htFlashAir }, { "astrobox", htAstroBox }, { "repetier", htRepetier }, - { "mks", htMKS } + { "mks", htMKS }, + { "qidiconnectnew", htQIDIConnectNew } + }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrintHostType) @@ -119,7 +134,8 @@ static const t_config_enum_values s_keys_map_InfillPattern { { "adaptivecubic", ipAdaptiveCubic }, { "supportcubic", ipSupportCubic }, { "lightning", ipLightning }, - //w14 + { "zigzag", ipZigZag }, + //w14 { "concentricInternal", ipConcentricInternal }, //w32 { "crosshatch", ipCrossHatch} @@ -266,6 +282,7 @@ PrintConfigDef::PrintConfigDef() this->init_extruder_option_keys(); assign_printer_technology_to_unknown(this->options, ptFFF); this->init_sla_params(); + this->init_sla_tilt_params(); assign_printer_technology_to_unknown(this->options, ptSLA); this->finalize(); } @@ -419,6 +436,7 @@ void PrintConfigDef::init_common_params() def = this->add("printhost_password", coString); def->label = L("Password"); // def->tooltip = L(""); + def->gui_type = ConfigOptionDef::GUIType::password; def->mode = comAdvanced; def->cli = ConfigOptionDef::nocli; def->set_default_value(new ConfigOptionString("")); @@ -616,6 +634,24 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(1)); + def = this->add("top_one_perimeter_type", coEnum); + def->label = L("Single perimeter on top surfaces"); + def->category = L("Layers and Perimeters"); + def->tooltip = L("Use only one perimeter on flat top surface, to give more space to the top infill pattern. Could be applied on topmost surface or all top surfaces."); + def->mode = comExpert; + def->set_enum({ + { "none", L("Disabled") }, + { "top", L("All top surfaces") }, + { "topmost", L("Topmost surface only") } + }); + def->set_default_value(new ConfigOptionEnum(TopOnePerimeterType::None)); + + def = this->add("only_one_perimeter_first_layer", coBool); + def->label = L("Only one perimeter on first layer"); + def->category = L("Layers and Perimeters"); + def->tooltip = L("Use only one perimeter on the first layer."); + def->mode = comExpert; + def->set_default_value(new ConfigOptionBool(false)); //w30 def = this->add("top_solid_infill_flow_ratio", coFloat); def->label = L("Top surface flow ratio"); @@ -1166,6 +1202,26 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloats { 0. }); + def = this->add("filament_infill_max_speed", coFloats); + def->label = L("Max non-crossing infill speed"); + def->tooltip = L("Maximum speed allowed for this filament while printing infill without " + "any self intersections in a single layer. " + "Set to zero for no limit."); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloats { 0. }); + + def = this->add("filament_infill_max_crossing_speed", coFloats); + def->label = L("Max crossing infill speed"); + def->tooltip = L("Maximum speed allowed for this filament while printing infill with " + "self intersections in a single layer. " + "Set to zero for no limit."); + def->sidetext = L("mm/s"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloats { 0. }); + def = this->add("filament_loading_speed", coFloats); def->label = L("Loading speed"); def->tooltip = L("Speed used for loading the filament on the wipe tower."); @@ -1394,6 +1450,12 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionBools { false }); + def = this->add("filament_abrasive", coBools); + def->label = L("Abrasive material"); + def->tooltip = L("This flag means that the material is abrasive and requires a hardened nozzle. The value is used by the printer to check it."); + def->mode = comExpert; + def->set_default_value(new ConfigOptionBools { false }); + def = this->add("filament_cost", coFloats); def->label = L("Cost"); def->tooltip = L("Enter your filament cost per kg here. This is only for statistical information."); @@ -1419,6 +1481,28 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionString(L("(Unknown)"))); def->cli = ConfigOptionDef::nocli; + def = this->add("filament_shrinkage_compensation_xy", coPercents); + def->label = L("Shrinkage compensation XY"); + def->tooltip = L("Enter your filament shrinkage percentages for the X and Y axes here to apply scaling of the object to " + "compensate for shrinkage in the X and Y axes. For example, if you measured 99mm instead of 100mm, " + "enter 1%."); + def->sidetext = L("%"); + def->mode = comAdvanced; + def->min = -10.; + def->max = 10.; + def->set_default_value(new ConfigOptionPercents { 0 }); + + def = this->add("filament_shrinkage_compensation_z", coPercents); + def->label = L("Shrinkage compensation Z"); + def->tooltip = L("Enter your filament shrinkage percentages for the Z axis here to apply scaling of the object to " + "compensate for shrinkage in the Z axis. For example, if you measured 99mm instead of 100mm, " + "enter 1%."); + def->sidetext = L("%"); + def->mode = comAdvanced; + def->min = -10.; + def->max = 10.; + def->set_default_value(new ConfigOptionPercents { 0. }); + def = this->add("fill_angle", coFloat); def->label = L("Fill angle"); def->category = L("Infill"); @@ -1479,6 +1563,7 @@ void PrintConfigDef::init_fff_params() { "adaptivecubic", L("Adaptive Cubic")}, { "supportcubic", L("Support Cubic")}, { "lightning", L("Lightning")}, + { "zigzag", L("Zig Zag")}, //w32 { "crosshatch", L("Cross Hatch")} }); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 01c1c91..d7d7a65 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -16,10 +16,6 @@ #ifndef slic3r_PrintConfig_hpp_ #define slic3r_PrintConfig_hpp_ -#include "libslic3r.h" -#include "Config.hpp" -#include "SLA/SupportTreeStrategies.hpp" - #include #include #include @@ -27,13 +23,41 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libslic3r.h" +#include "Config.hpp" +#include "SLA/SupportTreeStrategies.hpp" +#include "libslic3r/Point.hpp" namespace Slic3r { +class FullPrintConfig; +class GCodeConfig; +class MachineEnvelopeConfig; +class PrintConfig; +class PrintObjectConfig; +class PrintRegionConfig; +class SLAFullPrintConfig; +class SLAMaterialConfig; +class SLAPrintConfig; +class SLAPrintObjectConfig; +class SLAPrinterConfig; enum class ArcFittingType { Disabled, EmitCenter }; + enum GCodeFlavor : unsigned char { gcfRepRapSprinter, gcfRepRapFirmware, gcfRepetier, gcfTeacup, gcfMakerWare, gcfMarlinLegacy, gcfMarlinFirmware, gcfKlipper, gcfSailfish, gcfMach3, gcfMachinekit, gcfSmoothie, gcfNoExtrusion, @@ -48,7 +72,7 @@ enum class MachineLimitsUsage { //B55 enum PrintHostType { - htQIDILink, htQIDIConnect, htOctoPrint, htMoonraker, htMoonraker2,htDuet, htFlashAir, htAstroBox, htRepetier, htMKS + htQIDILink, htQIDIConnect, htOctoPrint, htMoonraker, htMoonraker2,htDuet, htFlashAir, htAstroBox, htRepetier, htMKS, htQIDIConnectNew }; enum AuthorizationType { @@ -66,6 +90,7 @@ enum InfillPattern : int { ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipAdaptiveCubic, ipSupportCubic, ipSupportBase, ipLightning, ipEnsuring, + ipZigZag, ipCount, //w14 ipConcentricInternal, @@ -144,11 +169,46 @@ enum class PerimeterGeneratorType // "A framework for adaptive width control of dense contour-parallel toolpaths in fused deposition modeling" ported from Cura. Arachne }; +enum class TopOnePerimeterType +{ + None, + TopSurfaces, + TopmostOnly, + Count +}; //B3 enum class GCodeThumbnailsFormat { QIDI,PNG, JPG, QOI }; -//w16 -enum class TopOneWallType { Disable, Alltop, Onlytopmost }; +enum TowerSpeeds : int { + tsLayer1, + tsLayer2, + tsLayer3, + tsLayer4, + tsLayer5, + tsLayer8, + tsLayer11, + tsLayer14, + tsLayer18, + tsLayer22, + tsLayer24, +}; + +enum TiltSpeeds : int { + tsMove120, + tsLayer200, + tsMove300, + tsLayer400, + tsLayer600, + tsLayer800, + tsLayer1000, + tsLayer1250, + tsLayer1500, + tsLayer1750, + tsLayer2000, + tsLayer2250, + tsMove5120, + tsMove8000, +}; #define CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(NAME) \ template<> const t_config_enum_names& ConfigOptionEnum::get_enum_names(); \ @@ -177,7 +237,7 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(LabelObjectsStyle) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(GCodeThumbnailsFormat) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PerimeterGeneratorType) - +CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(TopOnePerimeterType) #undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS @@ -205,6 +265,7 @@ private: void init_fff_params(); void init_extruder_option_keys(); void init_sla_params(); + void init_sla_tilt_params(); void init_sla_support_params(const std::string &method_prefix); std::vector m_extruder_option_keys; @@ -284,6 +345,9 @@ public: { PrintConfigDef::handle_legacy_composite(*this); } }; +// This vector containes list of parameters for preview of tilt profiles +const std::vector& tilt_options(); + void handle_legacy_sla(DynamicPrintConfig &config); class StaticPrintConfig : public StaticConfig @@ -736,9 +800,12 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloats, filament_density)) ((ConfigOptionStrings, filament_type)) ((ConfigOptionBools, filament_soluble)) + ((ConfigOptionBools, filament_abrasive)) ((ConfigOptionFloats, filament_cost)) ((ConfigOptionFloats, filament_spool_weight)) ((ConfigOptionFloats, filament_max_volumetric_speed)) + ((ConfigOptionFloats, filament_infill_max_speed)) + ((ConfigOptionFloats, filament_infill_max_crossing_speed)) ((ConfigOptionFloats, filament_loading_speed)) ((ConfigOptionFloats, filament_loading_speed_start)) ((ConfigOptionFloats, filament_load_time)) @@ -757,6 +824,8 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloats, filament_multitool_ramming_flow)) ((ConfigOptionFloats, filament_stamping_loading_speed)) ((ConfigOptionFloats, filament_stamping_distance)) + ((ConfigOptionPercents, filament_shrinkage_compensation_xy)) + ((ConfigOptionPercents, filament_shrinkage_compensation_z)) ((ConfigOptionBool, gcode_comments)) ((ConfigOptionEnum, gcode_flavor)) ((ConfigOptionEnum, gcode_label_objects)) @@ -775,6 +844,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloats, travel_max_lift)) ((ConfigOptionFloats, travel_slope)) ((ConfigOptionBools, travel_lift_before_obstacle)) + ((ConfigOptionBools, nozzle_high_flow)) ((ConfigOptionPercents, retract_before_wipe)) ((ConfigOptionFloats, retract_length)) ((ConfigOptionFloats, retract_length_toolchange)) @@ -911,6 +981,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionString, output_filename_format)) ((ConfigOptionFloat, perimeter_acceleration)) ((ConfigOptionStrings, post_process)) + ((ConfigOptionBool, prefer_clockwise_movements)) ((ConfigOptionString, printer_model)) ((ConfigOptionString, printer_notes)) ((ConfigOptionFloat, resolution)) @@ -1185,6 +1256,8 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, material_correction_y)) ((ConfigOptionFloat, material_correction_z)) ((ConfigOptionEnum, material_print_speed)) + ((ConfigOptionInt, zcorrection_layers)) + ((ConfigOptionFloatNullable, material_ow_support_pillar_diameter)) ((ConfigOptionFloatNullable, material_ow_branchingsupport_pillar_diameter)) ((ConfigOptionFloatNullable, material_ow_support_head_front_diameter)) @@ -1194,11 +1267,31 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloatNullable, material_ow_support_head_width)) ((ConfigOptionFloatNullable, material_ow_branchingsupport_head_width)) ((ConfigOptionIntNullable, material_ow_support_points_density_relative)) + ((ConfigOptionFloatNullable, material_ow_absolute_correction)) + ((ConfigOptionFloat, area_fill)) ((ConfigOptionFloatNullable, material_ow_elefant_foot_compensation)) ((ConfigOptionFloatNullable, material_ow_relative_correction_x)) ((ConfigOptionFloatNullable, material_ow_relative_correction_y)) ((ConfigOptionFloatNullable, material_ow_relative_correction_z)) + //tilt params + ((ConfigOptionFloats, delay_before_exposure)) + ((ConfigOptionFloats, delay_after_exposure)) + ((ConfigOptionInts, tower_hop_height)) + ((ConfigOptionEnums, tower_speed)) + ((ConfigOptionBools, use_tilt)) + ((ConfigOptionEnums, tilt_down_initial_speed)) + ((ConfigOptionInts, tilt_down_offset_steps)) + ((ConfigOptionFloats, tilt_down_offset_delay)) + ((ConfigOptionEnums, tilt_down_finish_speed)) + ((ConfigOptionInts, tilt_down_cycles)) + ((ConfigOptionFloats, tilt_down_delay)) + ((ConfigOptionEnums, tilt_up_initial_speed)) + ((ConfigOptionInts, tilt_up_offset_steps)) + ((ConfigOptionFloats, tilt_up_offset_delay)) + ((ConfigOptionEnums, tilt_up_finish_speed)) + ((ConfigOptionInts, tilt_up_cycles)) + ((ConfigOptionFloats, tilt_up_delay)) ) PRINT_CONFIG_CLASS_DEFINE( @@ -1225,13 +1318,14 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, fast_tilt_time)) ((ConfigOptionFloat, slow_tilt_time)) ((ConfigOptionFloat, high_viscosity_tilt_time)) - ((ConfigOptionFloat, area_fill)) +// ((ConfigOptionFloat, area_fill)) ((ConfigOptionFloat, min_exposure_time)) ((ConfigOptionFloat, max_exposure_time)) ((ConfigOptionFloat, min_initial_exposure_time)) ((ConfigOptionFloat, max_initial_exposure_time)) ((ConfigOptionString, sla_archive_format)) ((ConfigOptionFloat, sla_output_precision)) + ((ConfigOptionString, printer_model)) ) PRINT_CONFIG_CLASS_DERIVED_DEFINE0( @@ -1279,6 +1373,12 @@ public: CLIMiscConfigDef(); }; +class CLIProfilesSharingConfigDef : public ConfigDef +{ +public: + CLIProfilesSharingConfigDef(); +}; + typedef std::string t_custom_gcode_key; // This map containes list of specific placeholders for each custom G-code, if any exist const std::map& custom_gcode_specific_placeholders(); @@ -1349,6 +1449,9 @@ extern const CLITransformConfigDef cli_transform_config_def; // This class defines all command line options that are not actions or transforms. extern const CLIMiscConfigDef cli_misc_config_def; +// This class defines the command line options representing profiles sharing commands. +extern const CLIProfilesSharingConfigDef cli_profiles_sharing_config_def; + class DynamicPrintAndCLIConfig : public DynamicPrintConfig { public: @@ -1373,6 +1476,7 @@ private: this->options.insert(cli_actions_config_def.options.begin(), cli_actions_config_def.options.end()); this->options.insert(cli_transform_config_def.options.begin(), cli_transform_config_def.options.end()); this->options.insert(cli_misc_config_def.options.begin(), cli_misc_config_def.options.end()); + this->options.insert(cli_profiles_sharing_config_def.options.begin(), cli_profiles_sharing_config_def.options.end()); for (const auto &kvp : this->options) this->by_serialization_key_ordinal[kvp.second.serialization_key_ordinal] = &kvp.second; } diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index b1c4c7e..3d37a16 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1,22 +1,37 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "AABBTreeLines.hpp" -#include "BridgeDetector.hpp" #include "ExPolygon.hpp" -#include "Exception.hpp" #include "Flow.hpp" -#include "GCode/ExtrusionProcessor.hpp" -#include "KDTreeIndirect.hpp" +#include "libslic3r/GCode/ExtrusionProcessor.hpp" #include "Line.hpp" -#include "Point.hpp" #include "Polygon.hpp" #include "Polyline.hpp" #include "Print.hpp" #include "BoundingBox.hpp" -#include "ClipperUtils.hpp" -#include "ElephantFootCompensation.hpp" #include "Geometry.hpp" #include "I18N.hpp" #include "Layer.hpp" -#include "MutablePolygon.hpp" #include "PrintBase.hpp" #include "PrintConfig.hpp" #include "Support/SupportMaterial.hpp" @@ -27,37 +42,19 @@ #include "Tesselate.hpp" #include "TriangleMeshSlicer.hpp" #include "Utils.hpp" -#include "Fill/FillAdaptive.hpp" -#include "Fill/FillLightning.hpp" -#include "Format/STL.hpp" -#include "Support/SupportMaterial.hpp" +#include "libslic3r/Fill/FillAdaptive.hpp" +#include "libslic3r/Fill/FillLightning.hpp" #include "SupportSpotsGenerator.hpp" -#include "TriangleSelectorWrapper.hpp" -#include "format.hpp" #include "libslic3r.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include +#include "admesh/stl.h" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/Config.hpp" +#include "libslic3r/LayerRegion.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/MultiMaterialSegmentation.hpp" +#include "libslic3r/TriangleSelector.hpp" +#include "tcbspan/span.hpp" +#include "libslic3r/Point.hpp" using namespace std::literals; @@ -67,7 +64,9 @@ using namespace std::literals; // time limit for one ClipperLib operation (union / diff / offset), in ms #define PRINT_OBJECT_TIME_LIMIT_DEFAULT 50 #include + #include "Timer.hpp" + #define PRINT_OBJECT_TIME_LIMIT_SECONDS(limit) Timing::TimeLimitAlarm time_limit_alarm(uint64_t(limit) * 1000000000l, BOOST_CURRENT_FUNCTION) #define PRINT_OBJECT_TIME_LIMIT_MILLIS(limit) Timing::TimeLimitAlarm time_limit_alarm(uint64_t(limit) * 1000000l, BOOST_CURRENT_FUNCTION) #else @@ -87,12 +86,11 @@ using namespace std::literals; #define DEBUG #define _DEBUG #include "SVG.hpp" + #undef assert #include #endif - #include "SVG.hpp" - namespace Slic3r { // Constructor is called from the main thread, therefore all Model / ModelObject / ModelIntance data are valid. @@ -593,6 +591,7 @@ void PrintObject::calculate_overhanging_perimeters() this->set_done(posCalculateOverhangingPerimeters); } } + std::pair PrintObject::prepare_adaptive_infill_data( const std::vector> &surfaces_w_bottom_z) const { @@ -705,7 +704,9 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "perimeter_extrusion_width" || opt_key == "infill_overlap" || opt_key == "external_perimeters_first" - || opt_key == "arc_fitting") { + || opt_key == "arc_fitting" + || opt_key == "top_one_perimeter_type" + || opt_key == "only_one_perimeter_first_layer") { steps.emplace_back(posPerimeters); } else if ( opt_key == "gap_fill_enabled" @@ -893,11 +894,6 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "support_material_speed" || opt_key == "support_material_interface_speed" || opt_key == "bridge_speed" - || opt_key == "enable_dynamic_overhang_speeds" - || opt_key == "overhang_speed_0" - || opt_key == "overhang_speed_1" - || opt_key == "overhang_speed_2" - || opt_key == "overhang_speed_3" || opt_key == "external_perimeter_speed" || opt_key == "small_perimeter_speed" || opt_key == "solid_infill_speed" @@ -910,6 +906,13 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "perimeter_speed") { invalidated |= m_print->invalidate_step(psWipeTower); invalidated |= m_print->invalidate_step(psGCodeExport); + } else if ( + opt_key == "enable_dynamic_overhang_speeds" + || opt_key == "overhang_speed_0" + || opt_key == "overhang_speed_1" + || opt_key == "overhang_speed_2" + || opt_key == "overhang_speed_3") { + steps.emplace_back(posPerimeters); } else { // for legacy, if we can't handle this option let's invalidate all steps this->invalidate_all_steps(); @@ -2653,15 +2656,14 @@ PrintRegionConfig region_config_from_model_volume(const PrintRegionConfig &defau return config; } -void PrintObject::update_slicing_parameters() -{ - if (!m_slicing_params.valid) - m_slicing_params = SlicingParameters::create_from_config( - this->print()->config(), m_config, this->model_object()->max_z(), this->object_extruders()); +void PrintObject::update_slicing_parameters() { + if (!m_slicing_params.valid) { + m_slicing_params = SlicingParameters::create_from_config(this->print()->config(), m_config, this->model_object()->max_z(), + this->object_extruders(), this->print()->shrinkage_compensation()); + } } -SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig& full_config, const ModelObject& model_object, float object_max_z) -{ +SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig &full_config, const ModelObject &model_object, float object_max_z, const Vec3d &object_shrinkage_compensation) { PrintConfig print_config; PrintObjectConfig object_config; PrintRegionConfig default_region_config; @@ -2694,7 +2696,8 @@ SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig& full if (object_max_z <= 0.f) object_max_z = (float)model_object.raw_bounding_box().size().z(); - return SlicingParameters::create_from_config(print_config, object_config, object_max_z, object_extruders); + + return SlicingParameters::create_from_config(print_config, object_config, object_max_z, object_extruders, object_shrinkage_compensation); } // returns 0-based indices of extruders used to print the object (without brim, support and other helper extrusions) @@ -2715,7 +2718,6 @@ bool PrintObject::update_layer_height_profile(const ModelObject &model_object, c if (layer_height_profile.empty()) { // use the constructor because the assignement is crashing on ASAN OsX layer_height_profile = model_object.layer_height_profile.get(); -// layer_height_profile = model_object.layer_height_profile; // The layer height returned is sampled with high density for the UI layer height painting // and smoothing tool to work. updated = true; @@ -2726,8 +2728,9 @@ bool PrintObject::update_layer_height_profile(const ModelObject &model_object, c // Must not be of even length. ((layer_height_profile.size() & 1) != 0 || // Last entry must be at the top of the object. - std::abs(layer_height_profile[layer_height_profile.size() - 2] - slicing_parameters.object_print_z_max + slicing_parameters.object_print_z_min) > 1e-3)) + std::abs(layer_height_profile[layer_height_profile.size() - 2] - slicing_parameters.object_print_z_uncompensated_max + slicing_parameters.object_print_z_min) > 1e-3)) { layer_height_profile.clear(); + } if (layer_height_profile.empty()) { //layer_height_profile = layer_height_profile_adaptive(slicing_parameters, model_object.layer_config_ranges, model_object.volumes); @@ -3183,7 +3186,7 @@ static void project_triangles_to_slabs(SpanOfConstPtrs layers, const inde } void PrintObject::project_and_append_custom_facets( - bool seam, EnforcerBlockerType type, std::vector& out) const + bool seam, TriangleStateType type, std::vector& out) const { for (const ModelVolume* mv : this->model_object()->volumes) if (mv->is_model_part()) { diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index f73dd28..2239bcb 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -1,3 +1,15 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "ClipperUtils.hpp" #include "ElephantFootCompensation.hpp" #include "I18N.hpp" @@ -5,10 +17,25 @@ #include "MultiMaterialSegmentation.hpp" #include "Print.hpp" #include "ShortestPath.hpp" - -#include - -#include +#include "admesh/stl.h" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Exception.hpp" +#include "libslic3r/Flow.hpp" +#include "libslic3r/LayerRegion.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/ObjectID.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/PrintBase.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/Slicing.hpp" +#include "libslic3r/Surface.hpp" +#include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/TriangleMeshSlicer.hpp" +#include "libslic3r/Utils.hpp" +#include "libslic3r/libslic3r.h" +#include "tcbspan/span.hpp" namespace Slic3r { @@ -234,7 +261,6 @@ static std::vector::const_iterator layer_ } static std::vector> slices_to_regions( - const PrintConfig &print_config, ModelVolumePtrs model_volumes, const PrintObjectRegions &print_object_regions, const std::vector &zs, @@ -715,7 +741,7 @@ void PrintObject::slice_volumes() } std::vector slice_zs = zs_from_layers(m_layers); - std::vector> region_slices = slices_to_regions(print->config(), this->model_object()->volumes, *m_shared_regions, slice_zs, + std::vector> region_slices = slices_to_regions(this->model_object()->volumes, *m_shared_regions, slice_zs, slice_volumes_inner( print->config(), this->config(), this->trafo_centered(), this->model_object()->volumes, m_shared_regions->layer_ranges, slice_zs, throw_on_cancel_callback), @@ -761,6 +787,7 @@ void PrintObject::slice_volumes() apply_mm_segmentation(*this, [print]() { print->throw_if_canceled(); }); } + BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - make_slices in parallel - begin"; { // Compensation value, scaled. Only applying the negative scaling here, as the positive scaling has already been applied during slicing. diff --git a/src/libslic3r/PrintRegion.cpp b/src/libslic3r/PrintRegion.cpp index 5dba131..060acbc 100644 --- a/src/libslic3r/PrintRegion.cpp +++ b/src/libslic3r/PrintRegion.cpp @@ -1,5 +1,14 @@ +#include +#include +#include +#include + #include "Exception.hpp" #include "Print.hpp" +#include "libslic3r/Config.hpp" +#include "libslic3r/Flow.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { diff --git a/src/libslic3r/ProfilesSharingUtils.cpp b/src/libslic3r/ProfilesSharingUtils.cpp new file mode 100644 index 0000000..52cdfa3 --- /dev/null +++ b/src/libslic3r/ProfilesSharingUtils.cpp @@ -0,0 +1,582 @@ +#include "ProfilesSharingUtils.hpp" +#include "Utils.hpp" +#include "format.hpp" +#include "PrintConfig.hpp" +#include "PresetBundle.hpp" +#include "Utils/DirectoriesUtils.hpp" +#include "Utils/JsonUtils.hpp" +#include "BuildVolume.hpp" + +#include + +namespace Slic3r { + +static bool load_preset_bundle_from_datadir(PresetBundle& preset_bundle) +{ + AppConfig app_config = AppConfig(AppConfig::EAppMode::Editor); + if (!app_config.exists()) { + BOOST_LOG_TRIVIAL(error) << "Configuration wasn't found. Check your 'datadir' value."; + return false; + } + + if (std::string error = app_config.load(); !error.empty()) { + BOOST_LOG_TRIVIAL(error) << Slic3r::format("Error parsing QIDISlicer config file, it is probably corrupted. " + "Try to manually delete the file to recover from the error. Your user profiles will not be affected." + "\n%1%\n%2%", app_config.config_path(), error); + return false; + } + + // just checking for existence of Slic3r::data_dir is not enough : it may be an empty directory + // supplied as argument to --datadir; in that case we should still run the wizard + preset_bundle.setup_directories(); + + std::string delayed_error_load_presets; + // Suppress the '- default -' presets. + preset_bundle.set_default_suppressed(app_config.get_bool("no_defaults")); + try { + auto preset_substitutions = preset_bundle.load_presets(app_config, ForwardCompatibilitySubstitutionRule::EnableSystemSilent); + if (!preset_substitutions.empty()) { + BOOST_LOG_TRIVIAL(error) << "Some substitutions are found during loading presets."; + return false; + } + + // Post-process vendor map to delete non-installed models/varians + + VendorMap& vendors = preset_bundle.vendors; + for (auto& [vendor_id, vendor_profile] : vendors) { + std::vector models; + + for (auto& printer_model : vendor_profile.models) { + std::vector variants; + + for (const auto& variant : printer_model.variants) { + // check if printer model with variant is intalled + if (app_config.get_variant(vendor_id, printer_model.id, variant.name)) + variants.push_back(variant); + } + + if (!variants.empty()) { + if (printer_model.variants.size() != variants.size()) + printer_model.variants = variants; + models.push_back(printer_model); + } + } + + if (!models.empty()) { + if (vendor_profile.models.size() != models.size()) + vendor_profile.models = models; + } + } + } + catch (const std::exception& ex) { + BOOST_LOG_TRIVIAL(error) << ex.what(); + return false; + } + + return true; +} + +namespace pt = boost::property_tree; +/* +struct PrinterAttr_ +{ + std::string model_name; + std::string variant; +}; + +static std::string get_printer_profiles(const VendorProfile* vendor_profile, + const PresetBundle* preset_bundle, + const PrinterAttr_& printer_attr) +{ + for (const auto& printer_model : vendor_profile->models) { + if (printer_model.name != printer_attr.model_name) + continue; + + for (const auto& variant : printer_model.variants) + if (variant.name == printer_attr.variant) + { + pt::ptree data_node; + data_node.put("printer_model", printer_model.name); + data_node.put("printer_variant", printer_attr.variant); + + pt::ptree printer_profiles_node; + for (const Preset& printer_preset : preset_bundle->printers) { + if (printer_preset.vendor->id == vendor_profile->id && + printer_preset.is_visible && // ??? + printer_preset.config.opt_string("printer_model") == printer_model.id && + printer_preset.config.opt_string("printer_variant") == printer_attr.variant) { + pt::ptree profile_node; + profile_node.put("", printer_preset.name); + printer_profiles_node.push_back(std::make_pair("", profile_node)); + } + } + data_node.add_child("printer_profiles", printer_profiles_node); + + // Serialize the tree into JSON and return it. + return write_json_with_post_process(data_node); + } + } + + return ""; +} + +std::string get_json_printer_profiles(const std::string& printer_model_name, const std::string& printer_variant) +{ + if (!is_datadir()) + return ""; + + PrinterAttr_ printer_attr({printer_model_name, printer_variant}); + + PresetBundle preset_bundle; + if (!load_preset_bundle_from_datadir(preset_bundle)) + return ""; + + const VendorMap& vendors = preset_bundle.vendors; + for (const auto& [vendor_id, vendor] : vendors) { + std::string out = get_printer_profiles(&vendor, &preset_bundle, printer_attr); + if (!out.empty()) + return out; + } + + return ""; +} +*/ + +struct PrinterAttr +{ + std::string vendor_id; + std::string model_id; + std::string variant_name; +}; + +static bool is_compatible_preset(const Preset& printer_preset, const PrinterAttr& attr) +{ + return printer_preset.vendor->id == attr.vendor_id && + printer_preset.config.opt_string("printer_model") == attr.model_id && + printer_preset.config.opt_string("printer_variant") == attr.variant_name; +} + +static void add_profile_node(pt::ptree& printer_profiles_node, const Preset& printer_preset) +{ + pt::ptree profile_node; + + const DynamicPrintConfig& config = printer_preset.config; + + int extruders_cnt = printer_preset.printer_technology() == ptSLA ? 0 : + config.option("nozzle_diameter")->values.size(); + + profile_node.put("name", printer_preset.name); + if (extruders_cnt > 0) + profile_node.put("extruders_cnt", extruders_cnt); + + const double max_print_height = config.opt_float("max_print_height"); + const ConfigOptionPoints& bed_shape = *config.option("bed_shape"); + + BuildVolume build_volume = BuildVolume { bed_shape.values, max_print_height, Pointfs{Vec2d{0., 0.}} }; + BoundingBoxf bb = build_volume.bounding_volume2d(); + + Vec2d origin_pt; + if (build_volume.type() == BuildVolume::Type::Circle) { + origin_pt = build_volume.bed_center(); + } + else { + origin_pt = to_2d(-1 * build_volume.bounding_volume().min); + } + std::string origin = Slic3r::format("[%1%, %2%]", is_approx(origin_pt.x(), 0.) ? 0 : origin_pt.x(), + is_approx(origin_pt.y(), 0.) ? 0 : origin_pt.y()); + + pt::ptree bed_node; + bed_node.put("type", build_volume.type_name()); + bed_node.put("width", bb.max.x() - bb.min.x()); + bed_node.put("height", bb.max.y() - bb.min.y()); + bed_node.put("origin", origin); + bed_node.put("max_print_height", max_print_height); + + profile_node.add_child("bed", bed_node); + + printer_profiles_node.push_back(std::make_pair("", profile_node)); +} + +static void get_printer_profiles_node(pt::ptree& printer_profiles_node, + pt::ptree& user_printer_profiles_node, + const PrinterPresetCollection& printer_presets, + const PrinterAttr& attr) +{ + printer_profiles_node.clear(); + user_printer_profiles_node.clear(); + + for (const Preset& printer_preset : printer_presets) { + if (!printer_preset.is_visible) + continue; + + if (printer_preset.is_user()) { + const Preset* parent_preset = printer_presets.get_preset_parent(printer_preset); + if (parent_preset && is_compatible_preset(*parent_preset, attr)) + add_profile_node(user_printer_profiles_node, printer_preset); + } + else if (is_compatible_preset(printer_preset, attr)) + add_profile_node(printer_profiles_node, printer_preset); + } +} + +static void add_printer_models(pt::ptree& vendor_node, + const VendorProfile* vendor_profile, + PrinterTechnology printer_technology, + const PrinterPresetCollection& printer_presets) +{ + for (const auto& printer_model : vendor_profile->models) { + if (printer_technology != ptUnknown && printer_model.technology != printer_technology) + continue; + + pt::ptree variants_node; + pt::ptree printer_profiles_node; + pt::ptree user_printer_profiles_node; + + if (printer_model.technology == ptSLA) { + PrinterAttr attr({ vendor_profile->id, printer_model.id, "default" }); + + get_printer_profiles_node(printer_profiles_node, user_printer_profiles_node, printer_presets, attr); + if (printer_profiles_node.empty() && user_printer_profiles_node.empty()) + continue; + } + else { + for (const auto& variant : printer_model.variants) { + + PrinterAttr attr({ vendor_profile->id, printer_model.id, variant.name }); + + get_printer_profiles_node(printer_profiles_node, user_printer_profiles_node, printer_presets, attr); + if (printer_profiles_node.empty() && user_printer_profiles_node.empty()) + continue; + + pt::ptree variant_node; + variant_node.put("name", variant.name); + variant_node.add_child("printer_profiles", printer_profiles_node); + if (!user_printer_profiles_node.empty()) + variant_node.add_child("user_printer_profiles", user_printer_profiles_node); + + variants_node.push_back(std::make_pair("", variant_node)); + } + + if (variants_node.empty()) + continue; + } + + pt::ptree data_node; + data_node.put("id", printer_model.id); + data_node.put("name", printer_model.name); + data_node.put("technology", printer_model.technology == ptFFF ? "FFF" : "SLA"); + + if (!variants_node.empty()) + data_node.add_child("variants", variants_node); + else { + data_node.add_child("printer_profiles", printer_profiles_node); + if (!user_printer_profiles_node.empty()) + data_node.add_child("user_printer_profiles", user_printer_profiles_node); + } + + data_node.put("vendor_name", vendor_profile->name); + data_node.put("vendor_id", vendor_profile->id); + + vendor_node.push_back(std::make_pair("", data_node)); + } +} + +static void add_undef_printer_models(pt::ptree& vendor_node, + PrinterTechnology printer_technology, + const PrinterPresetCollection& printer_presets) +{ + for (auto pt : { ptFFF, ptSLA }) { + if (printer_technology != ptUnknown && printer_technology != pt) + continue; + + pt::ptree printer_profiles_node; + for (const Preset& preset : printer_presets) { + if (!preset.is_visible || preset.printer_technology() != pt || + preset.vendor || printer_presets.get_preset_parent(preset)) + continue; + + add_profile_node(printer_profiles_node, preset); + } + + if (!printer_profiles_node.empty()) { + pt::ptree data_node; + data_node.put("id", ""); + data_node.put("technology", pt == ptFFF ? "FFF" : "SLA"); + data_node.add_child("printer_profiles", printer_profiles_node); + data_node.put("vendor_name", ""); + data_node.put("vendor_id", ""); + + vendor_node.push_back(std::make_pair("", data_node)); + } + } +} + +std::string get_json_printer_models(PrinterTechnology printer_technology) +{ + PresetBundle preset_bundle; + if (!load_preset_bundle_from_datadir(preset_bundle)) + return ""; + + pt::ptree vendor_node; + + const VendorMap& vendors_map = preset_bundle.vendors; + for (const auto& [vendor_id, vendor] : vendors_map) + add_printer_models(vendor_node, &vendor, printer_technology, preset_bundle.printers); + + // add printers with no vendor information + add_undef_printer_models(vendor_node, printer_technology, preset_bundle.printers); + + pt::ptree root; + root.add_child("printer_models", vendor_node); + + // Serialize the tree into JSON and return it. + return write_json_with_post_process(root); +} + +static std::string get_installed_print_and_filament_profiles(const PresetBundle* preset_bundle, const Preset* printer_preset) +{ + PrinterTechnology printer_technology = printer_preset->printer_technology(); + + pt::ptree print_profiles; + pt::ptree user_print_profiles; + + const PresetWithVendorProfile printer_preset_with_vendor_profile = preset_bundle->printers.get_preset_with_vendor_profile(*printer_preset); + + const PresetCollection& print_presets = printer_technology == ptFFF ? preset_bundle->prints : preset_bundle->sla_prints; + const PresetCollection& material_presets = printer_technology == ptFFF ? preset_bundle->filaments : preset_bundle->sla_materials; + const std::string material_node_name = printer_technology == ptFFF ? "filament_profiles" : "sla_material_profiles"; + + for (auto print_preset : print_presets) { + + const PresetWithVendorProfile print_preset_with_vendor_profile = print_presets.get_preset_with_vendor_profile(print_preset); + + if (is_compatible_with_printer(print_preset_with_vendor_profile, printer_preset_with_vendor_profile)) + { + pt::ptree materials_profile_node; + pt::ptree user_materials_profile_node; + + for (auto material_preset : material_presets) { + + // ?! check visible and no-template presets only + if (!material_preset.is_visible || (material_preset.vendor && material_preset.vendor->templates_profile)) + continue; + + const PresetWithVendorProfile material_preset_with_vendor_profile = material_presets.get_preset_with_vendor_profile(material_preset); + + if (is_compatible_with_printer(material_preset_with_vendor_profile, printer_preset_with_vendor_profile) && + is_compatible_with_print(material_preset_with_vendor_profile, print_preset_with_vendor_profile, printer_preset_with_vendor_profile)) { + pt::ptree material_node; + material_node.put("", material_preset.name); + if (material_preset.is_user()) + user_materials_profile_node.push_back(std::make_pair("", material_node)); + else + materials_profile_node.push_back(std::make_pair("", material_node)); + } + } + + pt::ptree print_profile_node; + print_profile_node.put("name", print_preset.name); + print_profile_node.add_child(material_node_name, materials_profile_node); + if (!user_materials_profile_node.empty()) + print_profile_node.add_child("user_" + material_node_name, user_materials_profile_node); + + if (print_preset.is_user()) + user_print_profiles.push_back(std::make_pair("", print_profile_node)); + else + print_profiles.push_back(std::make_pair("", print_profile_node)); + } + } + + if (print_profiles.empty() && user_print_profiles.empty()) + return ""; + + pt::ptree tree; + tree.put("printer_profile", printer_preset->name); + tree.add_child("print_profiles", print_profiles); + if (!user_print_profiles.empty()) + tree.add_child("user_print_profiles", user_print_profiles); + + // Serialize the tree into JSON and return it. + return write_json_with_post_process(tree); +} + +std::string get_json_print_filament_profiles(const std::string& printer_profile) +{ + PresetBundle preset_bundle; + if (load_preset_bundle_from_datadir(preset_bundle)) { + const Preset* preset = preset_bundle.printers.find_preset(printer_profile, false, false); + if (preset) + return get_installed_print_and_filament_profiles(&preset_bundle, preset); + } + + return ""; +} + +// Helper function for FS +bool load_full_print_config(const std::string& print_preset_name, const std::string& filament_preset_name, const std::string& printer_preset_name, DynamicPrintConfig& config) +{ + PresetBundle preset_bundle; + if (!load_preset_bundle_from_datadir(preset_bundle)){ + BOOST_LOG_TRIVIAL(error) << Slic3r::format("Failed to load data from the datadir '%1%'.", data_dir()); + return false; + } + + config = {}; + config.apply(FullPrintConfig::defaults()); + + bool is_failed{ false }; + + if (const Preset* print_preset = preset_bundle.prints.find_preset(print_preset_name)) + config.apply_only(print_preset->config, print_preset->config.keys()); + else { + BOOST_LOG_TRIVIAL(warning) << Slic3r::format("Print profile '%1%' wasn't found.", print_preset_name); + is_failed |= true; + } + + if (const Preset* filament_preset = preset_bundle.filaments.find_preset(filament_preset_name)) + config.apply_only(filament_preset->config, filament_preset->config.keys()); + else { + BOOST_LOG_TRIVIAL(warning) << Slic3r::format("Filament profile '%1%' wasn't found.", filament_preset_name); + is_failed |= true; + } + + if (const Preset* printer_preset = preset_bundle.printers.find_preset(printer_preset_name)) + config.apply_only(printer_preset->config, printer_preset->config.keys()); + else { + BOOST_LOG_TRIVIAL(warning) << Slic3r::format("Printer profile '%1%' wasn't found.", printer_preset_name); + is_failed |= true; + } + + return !is_failed; +} + +// Helper function for load full config from installed presets by profile names +std::string load_full_print_config(const std::string& print_preset_name, + const std::vector& material_preset_names_in, + const std::string& printer_preset_name, + DynamicPrintConfig& config, + PrinterTechnology printer_technology /*= ptUnknown*/) +{ + // check entered profile names + + if (print_preset_name.empty() || + material_preset_names_in.empty() || + printer_preset_name.empty()) + return "Request is not completed. All of Print/Material/Printer profiles have to be entered"; + + // check preset bundle + + PresetBundle preset_bundle; + if (!load_preset_bundle_from_datadir(preset_bundle)) + return Slic3r::format("Failed to load data from the datadir '%1%'.", data_dir()); + + // check existance of required profiles + + std::string errors; + + const Preset* printer_preset = preset_bundle.printers.find_preset(printer_preset_name); + if (!printer_preset) + errors += "\n" + Slic3r::format("Printer profile '%1%' wasn't found.", printer_preset_name); + else if (printer_technology == ptUnknown) + printer_technology = printer_preset->printer_technology(); + else if (printer_technology != printer_preset->printer_technology()) + errors += "\n" + std::string("Printer technology of the selected printer preset is differs with required printer technology"); + + PresetCollection& print_presets = printer_technology == ptFFF ? preset_bundle.prints : preset_bundle.sla_prints; + + const Preset* print_preset = print_presets.find_preset(print_preset_name); + if (!print_preset) + errors += "\n" + Slic3r::format("Print profile '%1%' wasn't found.", print_preset_name); + + PresetCollection& material_presets = printer_technology == ptFFF ? preset_bundle.filaments : preset_bundle.sla_materials; + + auto check_material = [&material_presets] (const std::string& name, std::string& errors) -> void { + const Preset* material_preset = material_presets.find_preset(name); + if (!material_preset) + errors += "\n" + Slic3r::format("Material profile '%1%' wasn't found.", name); + }; + + check_material(material_preset_names_in.front(), errors); + if (material_preset_names_in.size() > 1) { + for (size_t idx = 1; idx < material_preset_names_in.size(); idx++) { + if (material_preset_names_in[idx] != material_preset_names_in.front()) + check_material(material_preset_names_in[idx], errors); + } + } + + if (!errors.empty()) + return errors; + + // check and update list of material presets + + std::vector material_preset_names = material_preset_names_in; + + if (printer_technology == ptSLA && material_preset_names.size() > 1) { + BOOST_LOG_TRIVIAL(warning) << "Note: More than one sla material profiles were entered. Extras material profiles will be ignored."; + material_preset_names.resize(1); + } + + if (printer_technology == ptFFF) { + const int extruders_count = int(static_cast(printer_preset->config.option("nozzle_diameter"))->values.size()); + if (extruders_count > int(material_preset_names.size())) { + BOOST_LOG_TRIVIAL(warning) << "Note: Less than needed filament profiles were entered. Missed filament profiles will be filled with first material."; + material_preset_names.reserve(extruders_count); + for (int i = extruders_count - material_preset_names.size(); i > 0; i--) + material_preset_names.push_back(material_preset_names.front()); + } + else if (extruders_count < int(material_preset_names.size())) { + BOOST_LOG_TRIVIAL(warning) << "Note: More than needed filament profiles were entered. Extras filament profiles will be ignored."; + material_preset_names.resize(extruders_count); + } + } + + // check profiles compatibility + + const PresetWithVendorProfile printer_preset_with_vendor_profile = preset_bundle.printers.get_preset_with_vendor_profile(*printer_preset); + const PresetWithVendorProfile print_preset_with_vendor_profile = print_presets.get_preset_with_vendor_profile(*print_preset); + + if (!is_compatible_with_printer(print_preset_with_vendor_profile, printer_preset_with_vendor_profile)) + errors += "\n" + Slic3r::format("Print profile '%1%' is not compatible with printer profile %2%.", print_preset_name, printer_preset_name); + + auto check_material_preset_compatibility = [&material_presets, printer_preset_name, print_preset_name, printer_preset_with_vendor_profile, print_preset_with_vendor_profile] + (const std::string& name, std::string& errors) -> void { + const Preset* material_preset = material_presets.find_preset(name); + const PresetWithVendorProfile material_preset_with_vendor_profile = material_presets.get_preset_with_vendor_profile(*material_preset); + + if (!is_compatible_with_printer(material_preset_with_vendor_profile, printer_preset_with_vendor_profile)) + errors += "\n" + Slic3r::format("Material profile '%1%' is not compatible with printer profile %2%.", name, printer_preset_name); + + if (!is_compatible_with_print(material_preset_with_vendor_profile, print_preset_with_vendor_profile, printer_preset_with_vendor_profile)) + errors += "\n" + Slic3r::format("Material profile '%1%' is not compatible with print profile %2%.", name, print_preset_name); + }; + + check_material_preset_compatibility(material_preset_names.front(), errors); + if (material_preset_names.size() > 1) { + for (size_t idx = 1; idx < material_preset_names.size(); idx++) { + if (material_preset_names[idx] != material_preset_names.front()) + check_material_preset_compatibility(material_preset_names[idx], errors); + } + } + + if (!errors.empty()) + return errors; + + // get full print configuration + + preset_bundle.printers.select_preset_by_name(printer_preset_name, true); + print_presets.select_preset_by_name(print_preset_name, true); + if (printer_technology == ptSLA) + material_presets.select_preset_by_name(material_preset_names.front(), true); + else if (printer_technology == ptFFF) { + auto& extruders_filaments = preset_bundle.extruders_filaments; + extruders_filaments.clear(); + for (size_t i = 0; i < material_preset_names.size(); ++i) + extruders_filaments.emplace_back(ExtruderFilaments(&preset_bundle.filaments, i, material_preset_names[i])); + } + + config = preset_bundle.full_config(); + + return ""; +} + +} // namespace Slic3r diff --git a/src/libslic3r/ProfilesSharingUtils.hpp b/src/libslic3r/ProfilesSharingUtils.hpp new file mode 100644 index 0000000..8574bf5 --- /dev/null +++ b/src/libslic3r/ProfilesSharingUtils.hpp @@ -0,0 +1,29 @@ +#ifndef slic3r_ProfilesSharingUtils_hpp_ +#define slic3r_ProfilesSharingUtils_hpp_ + +#include +#include "Config.hpp" + +namespace Slic3r { + +std::string get_json_printer_models(PrinterTechnology printer_technology); +//std::string get_json_printer_profiles(const std::string& printer_model, const std::string& printer_variant); +std::string get_json_print_filament_profiles(const std::string& printer_profile); + +class DynamicPrintConfig; +bool load_full_print_config(const std::string& print_preset, const std::string& filament_preset, const std::string& printer_preset, DynamicPrintConfig& out_config); + +// Load full print config into config +// Return value is always error string if any exists +// Note, that all appearing warnings are added into BOOST_LOG +// When printer_technology is set, then it will be compared with printer technology of the printer_profile and return the error, when they aren't the same +std::string load_full_print_config( const std::string& print_preset_name, + const std::vector& material_preset_names, + const std::string& printer_preset_name, + DynamicPrintConfig& config, + PrinterTechnology printer_technology = ptUnknown); + + +} // namespace Slic3r + +#endif // slic3r_ProfilesSharingUtils_hpp_ diff --git a/src/libslic3r/QuadricEdgeCollapse.cpp b/src/libslic3r/QuadricEdgeCollapse.cpp index b105b60..9a1bd42 100644 --- a/src/libslic3r/QuadricEdgeCollapse.cpp +++ b/src/libslic3r/QuadricEdgeCollapse.cpp @@ -1,15 +1,27 @@ #include "QuadricEdgeCollapse.hpp" + +#include +#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "MutablePriorityQueue.hpp" -#include +#include "admesh/stl.h" +#include "libslic3r/Exception.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/libslic3r.h" using namespace Slic3r; -#ifndef NDEBUG -// #define EXPENSIVE_DEBUG_CHECKS -#endif // NDEBUG - // only private namespace not neccessary be in .hpp namespace QuadricEdgeCollapse { // SymetricMatrix diff --git a/src/libslic3r/QuadricEdgeCollapse.hpp b/src/libslic3r/QuadricEdgeCollapse.hpp index 946ef99..c30f399 100644 --- a/src/libslic3r/QuadricEdgeCollapse.hpp +++ b/src/libslic3r/QuadricEdgeCollapse.hpp @@ -1,6 +1,7 @@ #ifndef slic3r_quadric_edge_collapse_hpp_ #define slic3r_quadric_edge_collapse_hpp_ +struct indexed_triangle_set; // paper: https://people.eecs.berkeley.edu/~jrs/meshpapers/GarlandHeckbert2.pdf // sum up: https://users.csc.calpoly.edu/~zwood/teaching/csc570/final06/jseeba/ // inspiration: https://github.com/sp4cerat/Fast-Quadric-Mesh-Simplification @@ -9,6 +10,7 @@ #include #include + #include "TriangleMesh.hpp" namespace Slic3r { diff --git a/src/libslic3r/SLA/BranchingTreeSLA.cpp b/src/libslic3r/SLA/BranchingTreeSLA.cpp index bb97126..82c2ebe 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.cpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.cpp @@ -1,15 +1,28 @@ #include "BranchingTreeSLA.hpp" -#include "libslic3r/Execution/ExecutionTBB.hpp" - -#include "libslic3r/KDTreeIndirect.hpp" - -#include "SupportTreeUtils.hpp" -#include "BranchingTree/PointCloud.hpp" - -#include "Pad.hpp" - +#include +#include +#include #include +#include +#include +#include +#include +#include +#include + +#include "libslic3r/Execution/ExecutionTBB.hpp" +#include "libslic3r/KDTreeIndirect.hpp" +#include "SupportTreeUtils.hpp" +#include "libslic3r/BranchingTree/PointCloud.hpp" +#include "Pad.hpp" +#include "libslic3r/BranchingTree/BranchingTree.hpp" +#include "libslic3r/Execution/Execution.hpp" +#include "libslic3r/Execution/ExecutionSeq.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/SLA/SupportTree.hpp" +#include "libslic3r/SLA/SupportTreeBuilder.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { namespace sla { diff --git a/src/libslic3r/SLA/BranchingTreeSLA.hpp b/src/libslic3r/SLA/BranchingTreeSLA.hpp index c101029..5788491 100644 --- a/src/libslic3r/SLA/BranchingTreeSLA.hpp +++ b/src/libslic3r/SLA/BranchingTreeSLA.hpp @@ -1,12 +1,14 @@ #ifndef BRANCHINGTREESLA_HPP #define BRANCHINGTREESLA_HPP +#include + #include "libslic3r/BranchingTree/BranchingTree.hpp" #include "SupportTreeBuilder.hpp" -#include - namespace Slic3r { namespace sla { +class SupportTreeBuilder; +struct SupportableMesh; void create_branching_tree(SupportTreeBuilder& builder, const SupportableMesh &sm); diff --git a/src/libslic3r/SLA/Clustering.cpp b/src/libslic3r/SLA/Clustering.cpp index 23aefbd..b0033fc 100644 --- a/src/libslic3r/SLA/Clustering.cpp +++ b/src/libslic3r/SLA/Clustering.cpp @@ -1,8 +1,11 @@ #include "Clustering.hpp" -#include "boost/geometry/index/rtree.hpp" #include -#include +#include // IWYU pragma: keep +#include +#include +#include +#include namespace Slic3r { namespace sla { diff --git a/src/libslic3r/SLA/Clustering.hpp b/src/libslic3r/SLA/Clustering.hpp index 269ec28..af4ad96 100644 --- a/src/libslic3r/SLA/Clustering.hpp +++ b/src/libslic3r/SLA/Clustering.hpp @@ -1,10 +1,17 @@ #ifndef SLA_CLUSTERING_HPP #define SLA_CLUSTERING_HPP -#include - #include #include +#include +#include +#include +#include +#include +#include +#include + +#include "libslic3r/Point.hpp" namespace Slic3r { namespace sla { diff --git a/src/libslic3r/SLA/ConcaveHull.cpp b/src/libslic3r/SLA/ConcaveHull.cpp index f657fce..e2a8b1d 100644 --- a/src/libslic3r/SLA/ConcaveHull.cpp +++ b/src/libslic3r/SLA/ConcaveHull.cpp @@ -1,10 +1,12 @@ #include #include +#include +#include +#include -#include -#include - -#include +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Line.hpp" namespace Slic3r { namespace sla { diff --git a/src/libslic3r/SLA/ConcaveHull.hpp b/src/libslic3r/SLA/ConcaveHull.hpp index dccdafd..8448170 100644 --- a/src/libslic3r/SLA/ConcaveHull.hpp +++ b/src/libslic3r/SLA/ConcaveHull.hpp @@ -2,6 +2,12 @@ #define SLA_CONCAVEHULL_HPP #include +#include +#include + +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { namespace sla { diff --git a/src/libslic3r/SLA/DefaultSupportTree.cpp b/src/libslic3r/SLA/DefaultSupportTree.cpp index 429cd45..ce1865b 100644 --- a/src/libslic3r/SLA/DefaultSupportTree.cpp +++ b/src/libslic3r/SLA/DefaultSupportTree.cpp @@ -4,6 +4,24 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libslic3r/Geometry.hpp" +#include "libslic3r/Optimize/Optimizer.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/SLA/SpatIndex.hpp" +#include "libslic3r/SLA/SupportPoint.hpp" +#include "libslic3r/SLA/SupportTreeStrategies.hpp" +#include "libslic3r/SLA/SupportTreeUtils.hpp" +#include "libslic3r/SLA/SupportTreeUtilsLegacy.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { namespace sla { diff --git a/src/libslic3r/SLA/DefaultSupportTree.hpp b/src/libslic3r/SLA/DefaultSupportTree.hpp index 08990a8..6975747 100644 --- a/src/libslic3r/SLA/DefaultSupportTree.hpp +++ b/src/libslic3r/SLA/DefaultSupportTree.hpp @@ -1,10 +1,23 @@ #ifndef LEGACYSUPPORTTREE_HPP #define LEGACYSUPPORTTREE_HPP -#include "SupportTreeUtilsLegacy.hpp" - #include #include +#include +#include +#include +#include +#include +#include +#include + +#include "SupportTreeUtilsLegacy.hpp" +#include "libslic3r/AABBMesh.hpp" +#include "libslic3r/Execution/Execution.hpp" +#include "libslic3r/SLA/Pad.hpp" +#include "libslic3r/SLA/SupportTree.hpp" +#include "libslic3r/SLA/SupportTreeBuilder.hpp" +#include "libslic3r/Point.hpp" namespace Slic3r { namespace sla { diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index 56cecdc..8bb05f3 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -1,9 +1,3 @@ -#include -#include -#include -#include -#include - #include #include #include @@ -11,19 +5,32 @@ #include #include #include -#include -#include #include #include - #include - #include - -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/Exception.hpp" +#include "libslic3r/Execution/Execution.hpp" +#include "libslic3r/Geometry.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/SLA/JobController.hpp" +#include "libslic3r/SLA/Pad.hpp" namespace Slic3r { +struct VoxelGrid; + namespace sla { struct Interior { diff --git a/src/libslic3r/SLA/Hollowing.hpp b/src/libslic3r/SLA/Hollowing.hpp index b6c07af..61cae70 100644 --- a/src/libslic3r/SLA/Hollowing.hpp +++ b/src/libslic3r/SLA/Hollowing.hpp @@ -1,15 +1,29 @@ #ifndef SLA_HOLLOWING_HPP #define SLA_HOLLOWING_HPP -#include #include #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "admesh/stl.h" +#include "libslic3r/CSGMesh/CSGMesh.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { class ModelObject; +struct VoxelGrid; namespace sla { @@ -26,6 +40,7 @@ enum HollowingFlags { hfRemoveInsideTriangles = 0x1 }; // All data related to a generated mesh interior. Includes the 3D grid and mesh // and various metadata. No need to manipulate from outside. struct Interior; + struct InteriorDeleter { void operator()(Interior *p); }; using InteriorPtr = std::unique_ptr; @@ -77,33 +92,16 @@ using DrainHoles = std::vector; constexpr float HoleStickOutLength = 1.f; +constexpr float IsoAtZero = 0.f; + double get_voxel_scale(double mesh_volume, const HollowingConfig &hc); InteriorPtr generate_interior(const VoxelGrid &mesh, const HollowingConfig & = {}, const JobController &ctl = {}); -inline InteriorPtr generate_interior(const indexed_triangle_set &mesh, - const HollowingConfig &hc = {}, - const JobController &ctl = {}) -{ - auto voxel_scale = get_voxel_scale(its_volume(mesh), hc); - auto statusfn = [&ctl](int){ return ctl.stopcondition && ctl.stopcondition(); }; - auto grid = mesh_to_grid(mesh, MeshToGridParams{} - .voxel_scale(voxel_scale) - .exterior_bandwidth(3.f) - .interior_bandwidth(3.f) - .statusfn(statusfn)); - - if (!grid || (ctl.stopcondition && ctl.stopcondition())) - return {}; - -// if (its_is_splittable(mesh)) - grid = redistance_grid(*grid, 0.0f, 3.f, 3.f); - - return grid ? generate_interior(*grid, hc, ctl) : InteriorPtr{}; -} - +// Return the maximum possible volume (upper bound) of a csg mesh. +// Not the exact volume, that would require actually doing the booleans. template double csgmesh_positive_maxvolume(const Cont &csg) { double mesh_vol = 0; @@ -138,19 +136,30 @@ InteriorPtr generate_interior(const Range &csgparts, .voxel_scale(voxsc) .exterior_bandwidth(3.f) .interior_bandwidth(3.f) - .statusfn([&ctl](int){ return ctl.stopcondition && ctl.stopcondition(); }); + .statusfn([&ctl](int){ + return ctl.stopcondition && ctl.stopcondition(); + }); auto ptr = csg::voxelize_csgmesh(csgparts, params); if (!ptr || (ctl.stopcondition && ctl.stopcondition())) return {}; - // TODO: figure out issues without the redistance -// if (csgparts.size() > 1 || its_is_splittable(*csg::get_mesh(*csgparts.begin()))) + ptr = redistance_grid(*ptr, IsoAtZero, + params.exterior_bandwidth(), + params.interior_bandwidth()); - ptr = redistance_grid(*ptr, 0.0f, 3.f, 3.f); + return ptr ? generate_interior(*ptr, hc, ctl) : + InteriorPtr{}; +} - return ptr ? generate_interior(*ptr, hc, ctl) : InteriorPtr{}; +inline InteriorPtr generate_interior(const indexed_triangle_set &mesh, + const HollowingConfig &hc = {}, + const JobController &ctl = {}) +{ + auto csgmesh = std::array{ csg::CSGPart{&mesh} }; + + return generate_interior(range(csgmesh), hc, ctl); } // Will do the hollowing diff --git a/src/libslic3r/SLA/Pad.cpp b/src/libslic3r/SLA/Pad.cpp index 34a1b5d..12ac9b2 100644 --- a/src/libslic3r/SLA/Pad.cpp +++ b/src/libslic3r/SLA/Pad.cpp @@ -1,25 +1,26 @@ #include #include -#include //#include #include +#include +#include +#include +#include #include "ConcaveHull.hpp" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/Tesselate.hpp" +#include "libslic3r/MTUtils.hpp" +#include "libslic3r/TriangulateWall.hpp" +#include "libslic3r/I18N.hpp" +#include "admesh/stl.h" +#include "libslic3r/Point.hpp" +#include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/libslic3r.h" -#include "boost/log/trivial.hpp" -#include "ClipperUtils.hpp" -#include "Tesselate.hpp" -#include "MTUtils.hpp" - -#include "TriangulateWall.hpp" - -// For debugging: -// #include -// #include -#include "SVG.hpp" - -#include "I18N.hpp" -#include +#ifndef NDEBUG +#include "libslic3r/SVG.hpp" +#endif namespace Slic3r { namespace sla { diff --git a/src/libslic3r/SLA/Pad.hpp b/src/libslic3r/SLA/Pad.hpp index da09343..b8a045c 100644 --- a/src/libslic3r/SLA/Pad.hpp +++ b/src/libslic3r/SLA/Pad.hpp @@ -1,12 +1,14 @@ #ifndef SLA_PAD_HPP #define SLA_PAD_HPP +#include #include #include #include #include -#include +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Polygon.hpp" struct indexed_triangle_set; @@ -14,6 +16,7 @@ namespace Slic3r { class ExPolygon; class Polygon; + using ExPolygons = std::vector; using Polygons = std::vector>; diff --git a/src/libslic3r/SLA/RasterBase.cpp b/src/libslic3r/SLA/RasterBase.cpp index 7240b19..1c8f7df 100644 --- a/src/libslic3r/SLA/RasterBase.cpp +++ b/src/libslic3r/SLA/RasterBase.cpp @@ -1,13 +1,16 @@ #ifndef SLARASTER_CPP #define SLARASTER_CPP -#include - #include #include - // minz image write: #include +#include +#include +#include +#include + +#include "agg/agg_gamma_functions.h" namespace Slic3r { namespace sla { diff --git a/src/libslic3r/SLA/RasterBase.hpp b/src/libslic3r/SLA/RasterBase.hpp index 33d39ea..8c5187f 100644 --- a/src/libslic3r/SLA/RasterBase.hpp +++ b/src/libslic3r/SLA/RasterBase.hpp @@ -1,14 +1,20 @@ #ifndef SLA_RASTERBASE_HPP #define SLA_RASTERBASE_HPP +#include +#include #include #include #include #include #include #include +#include +#include +#include -#include +#include "libslic3r/Point.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { diff --git a/src/libslic3r/SLA/RasterToPolygons.cpp b/src/libslic3r/SLA/RasterToPolygons.cpp index cd84a3c..7a80bdf 100644 --- a/src/libslic3r/SLA/RasterToPolygons.cpp +++ b/src/libslic3r/SLA/RasterToPolygons.cpp @@ -1,9 +1,16 @@ #include "RasterToPolygons.hpp" +#include +#include +#include +#include +#include + #include "AGGRaster.hpp" #include "libslic3r/MarchingSquares.hpp" -#include "MTUtils.hpp" -#include "ClipperUtils.hpp" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/libslic3r.h" namespace marchsq { diff --git a/src/libslic3r/SLA/RasterToPolygons.hpp b/src/libslic3r/SLA/RasterToPolygons.hpp index c0e1f41..740e474 100644 --- a/src/libslic3r/SLA/RasterToPolygons.hpp +++ b/src/libslic3r/SLA/RasterToPolygons.hpp @@ -2,6 +2,7 @@ #define RASTERTOPOLYGONS_HPP #include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Point.hpp" namespace Slic3r { namespace sla { diff --git a/src/libslic3r/SLA/Rotfinder.cpp b/src/libslic3r/SLA/Rotfinder.cpp index 5f25d5b..4028983 100644 --- a/src/libslic3r/SLA/Rotfinder.cpp +++ b/src/libslic3r/SLA/Rotfinder.cpp @@ -1,19 +1,26 @@ -#include - #include - #include -#include - #include -#include - -#include "libslic3r/SLAPrint.hpp" -#include "libslic3r/PrintConfig.hpp" - #include - +#include #include +#include +#include +#include +#include +#include +#include +#include + +#include "libslic3r/PrintConfig.hpp" +#include "admesh/stl.h" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/Execution/Execution.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/Optimize/Optimizer.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { namespace sla { diff --git a/src/libslic3r/SLA/Rotfinder.hpp b/src/libslic3r/SLA/Rotfinder.hpp index d632419..23e5c2e 100644 --- a/src/libslic3r/SLA/Rotfinder.hpp +++ b/src/libslic3r/SLA/Rotfinder.hpp @@ -1,10 +1,10 @@ #ifndef SLA_ROTFINDER_HPP #define SLA_ROTFINDER_HPP +#include #include #include - -#include +#include namespace Slic3r { diff --git a/src/libslic3r/SLA/SpatIndex.cpp b/src/libslic3r/SLA/SpatIndex.cpp index 1af9d29..d8d0e08 100644 --- a/src/libslic3r/SLA/SpatIndex.cpp +++ b/src/libslic3r/SLA/SpatIndex.cpp @@ -1,7 +1,10 @@ #include "SpatIndex.hpp" // for concave hull merging decisions -#include +#include // IWYU pragma: keep +#include +#include +#include #ifdef _MSC_VER #pragma warning(push) @@ -9,7 +12,7 @@ #pragma warning(disable: 4267) #endif -#include "boost/geometry/index/rtree.hpp" +#include "libslic3r/BoundingBox.hpp" #ifdef _MSC_VER #pragma warning(pop) diff --git a/src/libslic3r/SLA/SpatIndex.hpp b/src/libslic3r/SLA/SpatIndex.hpp index ef059d3..541cc0d 100644 --- a/src/libslic3r/SLA/SpatIndex.hpp +++ b/src/libslic3r/SLA/SpatIndex.hpp @@ -1,13 +1,16 @@ #ifndef SLA_SPATINDEX_HPP #define SLA_SPATINDEX_HPP +#include +#include #include #include #include - #include +#include +#include -#include +#include "libslic3r/Point.hpp" namespace Slic3r { namespace sla { diff --git a/src/libslic3r/SLA/SupportPointGenerator.cpp b/src/libslic3r/SLA/SupportPointGenerator.cpp index 193333b..7c1eb0e 100644 --- a/src/libslic3r/SLA/SupportPointGenerator.cpp +++ b/src/libslic3r/SLA/SupportPointGenerator.cpp @@ -1,19 +1,24 @@ -#include +#include +#include +#include +#include +#include +#include #include "SupportPointGenerator.hpp" -#include "Execution/ExecutionTBB.hpp" -#include "Geometry/ConvexHull.hpp" -#include "Model.hpp" -#include "ExPolygon.hpp" -#include "SVG.hpp" -#include "Point.hpp" -#include "ClipperUtils.hpp" -#include "Tesselate.hpp" -#include "MinAreaBoundingBox.hpp" -#include "libslic3r.h" - -#include -#include +#include "libslic3r/Execution/ExecutionTBB.hpp" +#include "libslic3r/Geometry/ConvexHull.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/Tesselate.hpp" +#include "libslic3r/MinAreaBoundingBox.hpp" +#include "libslic3r/libslic3r.h" +#include "libslic3r/AABBMesh.hpp" +#include "libslic3r/Execution/Execution.hpp" +#include "libslic3r/SLA/SupportPoint.hpp" +#include "libslic3r/BoundingBox.hpp" namespace Slic3r { namespace sla { @@ -280,14 +285,6 @@ void SupportPointGenerator::process(const std::vector& slices, const status += increment; m_statusfn(int(std::round(status))); - -#ifdef SLA_SUPPORTPOINTGEN_DEBUG - /*std::string layer_num_str = std::string((i<10 ? "0" : "")) + std::string((i<100 ? "0" : "")) + std::to_string(i); - output_expolygons(expolys_top, "top" + layer_num_str + ".svg"); - output_expolygons(diff, "diff" + layer_num_str + ".svg"); - if (!islands.empty()) - output_expolygons(islands, "islands" + layer_num_str + ".svg");*/ -#endif /* SLA_SUPPORTPOINTGEN_DEBUG */ } } diff --git a/src/libslic3r/SLA/SupportPointGenerator.hpp b/src/libslic3r/SLA/SupportPointGenerator.hpp index a9e0943..b7dba5f 100644 --- a/src/libslic3r/SLA/SupportPointGenerator.hpp +++ b/src/libslic3r/SLA/SupportPointGenerator.hpp @@ -1,16 +1,29 @@ #ifndef SLA_SUPPORTPOINTGENERATOR_HPP #define SLA_SUPPORTPOINTGENERATOR_HPP -#include - #include - #include #include #include #include - #include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/libslic3r.h" + +namespace Slic3r { +class AABBMesh; +} // namespace Slic3r // #define SLA_SUPPORTPOINTGEN_DEBUG diff --git a/src/libslic3r/SLA/SupportTree.cpp b/src/libslic3r/SLA/SupportTree.cpp index d066e02..592a18c 100644 --- a/src/libslic3r/SLA/SupportTree.cpp +++ b/src/libslic3r/SLA/SupportTree.cpp @@ -3,21 +3,23 @@ * */ -#include #include -#include #include #include #include - #include -#include -#include #include - #include +#include +#include +#include -#include +#include "libslic3r/Point.hpp" +#include "libslic3r/SLA/JobController.hpp" +#include "libslic3r/SLA/Pad.hpp" +#include "libslic3r/SLA/SupportTreeStrategies.hpp" +#include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { namespace sla { @@ -28,8 +30,8 @@ indexed_triangle_set create_support_tree(const SupportableMesh &sm, auto builder = make_unique(ctl); if (sm.cfg.enabled) { - Benchmark bench; - bench.start(); + using std::chrono::high_resolution_clock; + auto start{high_resolution_clock::now()}; switch (sm.cfg.tree_type) { case SupportTreeType::Default: { @@ -46,10 +48,12 @@ indexed_triangle_set create_support_tree(const SupportableMesh &sm, default:; } - bench.stop(); + auto stop{high_resolution_clock::now()}; + using std::chrono::duration; + using std::chrono::seconds; BOOST_LOG_TRIVIAL(info) << "Support tree creation took: " - << bench.getElapsedSec() + << duration{stop - start}.count() << " seconds"; builder->merge_and_cleanup(); // clean metadata, leave only the meshes. diff --git a/src/libslic3r/SLA/SupportTree.hpp b/src/libslic3r/SLA/SupportTree.hpp index 83814d8..2640c45 100644 --- a/src/libslic3r/SLA/SupportTree.hpp +++ b/src/libslic3r/SLA/SupportTree.hpp @@ -1,21 +1,25 @@ #ifndef SLA_SUPPORTTREE_HPP #define SLA_SUPPORTTREE_HPP -#include -#include - #include #include #include - #include #include #include #include +#include +#include +#include +#include +#include + +#include "admesh/stl.h" namespace Slic3r { namespace sla { +struct JobController; struct SupportTreeConfig { diff --git a/src/libslic3r/SLA/SupportTreeBuilder.cpp b/src/libslic3r/SLA/SupportTreeBuilder.cpp index 7398f4d..b820c4f 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.cpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.cpp @@ -1,8 +1,11 @@ #include -#include #include +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/SLA/SupportTree.hpp" +#include "libslic3r/TriangleMesh.hpp" + namespace Slic3r { namespace sla { diff --git a/src/libslic3r/SLA/SupportTreeBuilder.hpp b/src/libslic3r/SLA/SupportTreeBuilder.hpp index 93fbead..b6c559f 100644 --- a/src/libslic3r/SLA/SupportTreeBuilder.hpp +++ b/src/libslic3r/SLA/SupportTreeBuilder.hpp @@ -6,6 +6,20 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "admesh/stl.h" +#include "libslic3r/Point.hpp" +#include "libslic3r/SLA/JobController.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { namespace sla { diff --git a/src/libslic3r/SLA/SupportTreeMesher.cpp b/src/libslic3r/SLA/SupportTreeMesher.cpp index 6d91de7..1d0519b 100644 --- a/src/libslic3r/SLA/SupportTreeMesher.cpp +++ b/src/libslic3r/SLA/SupportTreeMesher.cpp @@ -1,4 +1,7 @@ #include "SupportTreeMesher.hpp" +#include +#include +#include namespace Slic3r { namespace sla { diff --git a/src/libslic3r/SLA/SupportTreeMesher.hpp b/src/libslic3r/SLA/SupportTreeMesher.hpp index c259729..27b2218 100644 --- a/src/libslic3r/SLA/SupportTreeMesher.hpp +++ b/src/libslic3r/SLA/SupportTreeMesher.hpp @@ -1,10 +1,15 @@ #ifndef SUPPORTTREEMESHER_HPP #define SUPPORTTREEMESHER_HPP -#include "libslic3r/Point.hpp" +#include +#include +#include +#include "libslic3r/Point.hpp" #include "libslic3r/SLA/SupportTreeBuilder.hpp" #include "libslic3r/TriangleMesh.hpp" +#include "admesh/stl.h" +#include "libslic3r/libslic3r.h" //#include "libslic3r/SLA/Contour3D.hpp" namespace Slic3r { namespace sla { diff --git a/src/libslic3r/SLA/ZCorrection.cpp b/src/libslic3r/SLA/ZCorrection.cpp new file mode 100644 index 0000000..5d7faaa --- /dev/null +++ b/src/libslic3r/SLA/ZCorrection.cpp @@ -0,0 +1,133 @@ +#include "ZCorrection.hpp" + +#include + +#include "libslic3r/Execution/ExecutionTBB.hpp" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/Exception.hpp" +#include "libslic3r/libslic3r.h" + +namespace Slic3r { namespace sla { + +std::vector apply_zcorrection( + const std::vector &slices, size_t layers) +{ + return zcorr_detail::apply_zcorrection(ex_tbb, slices, layers); +} + +std::vector apply_zcorrection(const std::vector &slices, + const std::vector &grid, + float depth) +{ + return zcorr_detail::apply_zcorrection(ex_tbb, slices, grid, depth); +} + +namespace zcorr_detail { + +DepthMap create_depthmap(const std::vector &slices, + const std::vector &grid, + size_t max_depth) +{ + struct DepthPoly { + size_t depth = 0; + ExPolygons contour; + }; + + DepthMap ret; + + if (slices.empty() || slices.size() != grid.size()) + return ret; + + size_t depth_limit = max_depth > 0 ? max_depth : slices.size(); + ret.resize(slices.size()); + + ret.front() = DepthMapLayer{ {size_t{0}, slices.front()} }; + + for (size_t i = 0; i < slices.size() - 1; ++i) { + DepthMapLayer &depths_current = ret[i]; + DepthMapLayer &depths_nxt = ret[i + 1]; + + for (const auto &[depth, cntrs] : depths_current) { + DepthPoly common; + + common.contour = intersection_ex(slices[i + 1], cntrs); + common.depth = std::min(depth_limit, depth + 1); + + DepthPoly overhangs; + overhangs.contour = diff_ex(slices[i + 1], cntrs); + + if (!common.contour.empty()) { + std::copy(common.contour.begin(), common.contour.end(), + std::back_inserter(depths_nxt[common.depth])); + } + + if (!overhangs.contour.empty()) { + std::copy(overhangs.contour.begin(), overhangs.contour.end(), + std::back_inserter(depths_nxt[overhangs.depth])); + } + } + + for(auto &[i, cntrs] : depths_nxt) { + depths_nxt[i] = union_ex(cntrs); + } + } + + return ret; +} + +void apply_zcorrection(DepthMap &dmap, size_t layers) +{ + for (size_t lyr = 0; lyr < dmap.size(); ++lyr) { + size_t threshold = std::min(lyr, layers); + + auto &dlayer = dmap[lyr]; + + for (auto it = dlayer.begin(); it != dlayer.end();) + if (it->first < threshold) + it = dlayer.erase(it); + else + ++it; + } +} + +ExPolygons merged_layer(const DepthMapLayer &dlayer) +{ + using namespace Slic3r; + + ExPolygons out; + for (auto &[i, cntrs] : dlayer) { + std::copy(cntrs.begin(), cntrs.end(), std::back_inserter(out)); + } + + out = union_ex(out); + + return out; +} + +std::vector depthmap_to_slices(const DepthMap &dm) +{ + auto out = reserve_vector(dm.size()); + for (const auto &dlayer : dm) { + out.emplace_back(merged_layer(dlayer)); + } + + return out; +} + +ExPolygons intersect_layers(const std::vector &slices, + size_t layer_from, size_t layers_down) +{ + size_t drill_to = std::min(layer_from, layers_down); + auto drill_to_layer = static_cast(layer_from - drill_to); + + ExPolygons merged_lyr = slices[layer_from]; + for (int i = layer_from; i >= drill_to_layer; --i) + merged_lyr = intersection_ex(merged_lyr, slices[i]); + + return merged_lyr; +} + +} // namespace zcorr_detail + +} // namespace sla +} // namespace Slic3r diff --git a/src/libslic3r/SLA/ZCorrection.hpp b/src/libslic3r/SLA/ZCorrection.hpp new file mode 100644 index 0000000..5d9198b --- /dev/null +++ b/src/libslic3r/SLA/ZCorrection.hpp @@ -0,0 +1,90 @@ +#ifndef ZCORRECTION_HPP +#define ZCORRECTION_HPP + +#include +#include +#include +#include +#include + +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Execution/Execution.hpp" + +namespace Slic3r { +namespace sla { + +std::vector apply_zcorrection(const std::vector &slices, + size_t layers); + +std::vector apply_zcorrection(const std::vector &slices, + const std::vector &grid, + float depth); + +namespace zcorr_detail { + +ExPolygons intersect_layers(const std::vector &slices, + size_t layer_from, + size_t layers_down); + +template +std::vector apply_zcorrection(Ex ep, + const std::vector &slices, + size_t layers) +{ + std::vector output(slices.size()); + + execution::for_each(ep, size_t{0}, slices.size(), + [&output, &slices, layers] (size_t lyr) { + output[lyr] = intersect_layers(slices, lyr, layers); + }, execution::max_concurrency(ep)); + + return output; +} + +inline size_t depth_to_layers(const std::vector &grid, + size_t from_layer, + float depth) +{ + size_t depth_layers = 0; + while (from_layer > depth_layers && + grid[from_layer - depth_layers] > grid[from_layer] - depth) + depth_layers++; + + return depth_layers; +} + +template +std::vector apply_zcorrection(Ex ep, + const std::vector &slices, + const std::vector &grid, + float depth) +{ + std::vector output(slices.size()); + + execution::for_each(ep, size_t{0}, slices.size(), + [&output, &slices, &grid, depth] (size_t lyr) { + output[lyr] = intersect_layers(slices, lyr, + depth_to_layers(grid, lyr, depth)); + }, execution::max_concurrency(ep)); + + return output; +} + +using DepthMapLayer = std::map; +using DepthMap = std::vector; + +DepthMap create_depthmap(const std::vector &slices, + const std::vector &grid, size_t max_depth = 0); + +void apply_zcorrection(DepthMap &dmap, size_t layers); + +ExPolygons merged_layer(const DepthMapLayer &dlayer); + +std::vector depthmap_to_slices(const DepthMap &dm); + +} // namespace zcorr_detail + +} // namespace sla +} // namespace Slic3r + +#endif // ZCORRECTION_HPP diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 0b2cbe1..9bd5096 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -214,10 +214,8 @@ static t_config_option_keys print_config_diffs(const StaticPrintConfig &curr "branchingsupport_head_width"sv, "branchingsupport_pillar_diameter"sv, "support_points_density_relative"sv, - "relative_correction_x"sv, - "relative_correction_y"sv, - "relative_correction_z"sv, "elefant_foot_compensation"sv, + "absolute_correction"sv, }; static constexpr auto material_ow_prefix = "material_ow_"; @@ -253,6 +251,8 @@ static t_config_option_keys print_config_diffs(const StaticPrintConfig &curr return print_diff; } + + SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig config) { #ifdef _DEBUG @@ -273,6 +273,7 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con t_config_option_keys placeholder_parser_diff = m_placeholder_parser.config_diff(config); config.apply(mat_overrides, true); + // Do not use the ApplyStatus as we will use the max function when updating apply_status. unsigned int apply_status = APPLY_STATUS_UNCHANGED; auto update_apply_status = [&apply_status](bool invalidated) @@ -676,6 +677,13 @@ std::string SLAPrint::validate(std::vector*) const "Please check value of Pinhead front diameter in Print Settings or Material Overrides."); } } + + if ((!m_material_config.use_tilt.get_at(0) && m_material_config.tower_hop_height.get_at(0) == 0) + || (!m_material_config.use_tilt.get_at(1) && m_material_config.tower_hop_height.get_at(1) == 0)) + return _u8L("Disabling the 'Use tilt' function causes the object to separate away from the film in the " + "vertical direction only. Therefore, it is necessary to set the 'Tower hop height' parameter " + "to reasonable value. The recommended value is 5 mm."); + return ""; } @@ -811,6 +819,7 @@ void SLAPrint::process() bool SLAPrint::invalidate_state_by_config_options(const std::vector &opt_keys, bool &invalidate_all_model_objects) { using namespace std::string_view_literals; + if (opt_keys.empty()) return false; @@ -828,6 +837,7 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector steps; @@ -981,7 +1008,6 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector ret; - for (unsigned int step = 0; step < s; ++step) { + for (unsigned int step = 0; step <= s; ++step) { auto r = m_mesh_to_slice.equal_range(SLAPrintObjectStep(step)); csg::copy_csgrange_shallow(Range{r.first, r.second}, std::back_inserter(ret)); } diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 96ac6f4..2577515 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -1,22 +1,48 @@ #ifndef slic3r_SLAPrint_hpp_ #define slic3r_SLAPrint_hpp_ +#include +#include #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "PrintBase.hpp" #include "SLA/SupportTree.hpp" #include "Point.hpp" #include "Format/SLAArchiveWriter.hpp" -#include "GCode/ThumbnailData.hpp" +#include "libslic3r/GCode/ThumbnailData.hpp" #include "libslic3r/CSGMesh/CSGMesh.hpp" #include "libslic3r/MeshBoolean.hpp" #include "libslic3r/OpenVDBUtils.hpp" - -#include +#include "admesh/stl.h" +#include "libslic3r/AnyPtr.hpp" +#include "libslic3r/Config.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/ObjectID.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/SLA/Hollowing.hpp" +#include "libslic3r/SLA/Pad.hpp" +#include "libslic3r/SLA/SupportPoint.hpp" +#include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { +namespace sla { +struct JobController; +} // namespace sla enum SLAPrintStep : unsigned int { slapsMergeSlicesAndEval, @@ -401,13 +427,15 @@ struct SLAPrintStatistics { SLAPrintStatistics() { clear(); } double estimated_print_time; + double estimated_print_time_tolerance; double objects_used_material; double support_used_material; size_t slow_layers_count; size_t fast_layers_count; double total_cost; double total_weight; - std::vector layers_times; + std::vector layers_times_running_total; + std::vector layers_areas; // Config with the filled in print statistics. DynamicConfig config() const; @@ -418,13 +446,15 @@ struct SLAPrintStatistics void clear() { estimated_print_time = 0.; + estimated_print_time_tolerance = 0.; objects_used_material = 0.; support_used_material = 0.; slow_layers_count = 0; fast_layers_count = 0; total_cost = 0.; total_weight = 0.; - layers_times.clear(); + layers_times_running_total.clear(); + layers_areas.clear(); } }; diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index a2a4f8a..efdbead 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -1,37 +1,57 @@ -#include - #include #include #include #include - -// Need the cylinder method for the the drainholes in hollowing step -#include - #include #include #include - +#include #include -#include -#include -#include #include #include #include #include #include #include - #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include //#include #include #include "I18N.hpp" - -#include #include "format.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/CSGMesh/CSGMesh.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Execution/Execution.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/PrintBase.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/SLA/Hollowing.hpp" +#include "libslic3r/SLA/JobController.hpp" +#include "libslic3r/SLA/RasterBase.hpp" +#include "libslic3r/SLA/SupportPoint.hpp" +#include "libslic3r/SLA/SupportTree.hpp" +#include "libslic3r/SLA/SupportTreeStrategies.hpp" +#include "libslic3r/SLAPrint.hpp" +#include "libslic3r/TriangleMesh.hpp" namespace Slic3r { @@ -125,6 +145,11 @@ void SLAPrint::Steps::apply_printer_corrections(SLAPrintObject &po, SliceOrigin if (idx < slices.size()) slices[idx] = elephant_foot_compensation(slices[idx], min_w, efc(i)); } + + if (o == soModel) { // Z correction applies only to the model slices + slices = sla::apply_zcorrection(slices, + m_print->m_material_config.zcorrection_layers.getInt()); + } } indexed_triangle_set SLAPrint::Steps::generate_preview_vdb( @@ -157,9 +182,9 @@ indexed_triangle_set SLAPrint::Steps::generate_preview_vdb( void SLAPrint::Steps::generate_preview(SLAPrintObject &po, SLAPrintObjectStep step) { - Benchmark bench; + using std::chrono::high_resolution_clock; - bench.start(); + auto start{high_resolution_clock::now()}; auto r = range(po.m_mesh_to_slice); auto m = indexed_triangle_set{}; @@ -274,11 +299,14 @@ void SLAPrint::Steps::generate_preview(SLAPrintObject &po, SLAPrintObjectStep st po.m_preview_meshes[i] = {}; } - bench.stop(); + auto stop{high_resolution_clock::now()}; - if (!po.m_preview_meshes[step]->empty()) - BOOST_LOG_TRIVIAL(trace) << "Preview gen took: " << bench.getElapsedSec(); - else + if (!po.m_preview_meshes[step]->empty()) { + using std::chrono::duration; + using std::chrono::seconds; + + BOOST_LOG_TRIVIAL(trace) << "Preview gen took: " << duration{stop - start}.count(); + } else BOOST_LOG_TRIVIAL(error) << "Preview failed!"; using namespace std::string_literals; @@ -613,7 +641,7 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po) switch (cfg.support_tree_type) { case sla::SupportTreeType::Default: case sla::SupportTreeType::Organic: - config.head_diameter = float(cfg.support_head_front_diameter); + config.head_diameter = float(cfg.support_head_front_diameter); break; case sla::SupportTreeType::Branching: config.head_diameter = float(cfg.branchingsupport_head_front_diameter); @@ -907,6 +935,188 @@ void SLAPrint::Steps::initialize_printer_input() } } +static int Ms(int s) +{ + return s; +} + +// constant values from FW +int tiltHeight = 4959; //nm +int tower_microstep_size_nm = 250000; +int first_extra_slow_layers = 3; +int refresh_delay_ms = 0; + +static int nm_to_tower_microsteps(int nm) { + // add implementation + return nm / tower_microstep_size_nm; +} + +static int count_move_time(const std::string& axis_name, double length, int steprate) +{ + if (length < 0 || steprate < 0) + return 0; + + // sla - fw checks every 0.1 s if axis is still moving.See: Axis._wait_to_stop_delay.Additional 0.021 s is + // measured average delay of the system.Thus, the axis movement time is always quantized by this value. + double delay = 0.121; + + // Both axes use linear ramp movements. This factor compensates the tilt acceleration and deceleration time. + double tilt_comp_factor = 0.1; + + // Both axes use linear ramp movements.This factor compensates the tower acceleration and deceleration time. + int tower_comp_factor = 20000; + + int l = int(length); + return axis_name == "tower" ? Ms((int(l / (steprate * delay) + (steprate + l) / tower_comp_factor) + 1) * (delay * 1000)) : + Ms((int(l / (steprate * delay) + tilt_comp_factor) + 1) * (delay * 1000)); +} + +struct ExposureProfile { + + // map of internal TowerSpeeds to maximum_steprates (usteps/s) + // this values was provided in default_tower_moving_profiles.json by SLA-team + std::map tower_speeds = { + { tsLayer1 , 800 }, + { tsLayer2 , 1600 }, + { tsLayer3 , 2400 }, + { tsLayer4 , 3200 }, + { tsLayer5 , 4000 }, + { tsLayer8 , 6400 }, + { tsLayer11, 8800 }, + { tsLayer14, 11200 }, + { tsLayer18, 14400 }, + { tsLayer22, 17600 }, + { tsLayer24, 19200 }, + }; + + // map of internal TiltSpeeds to maximum_steprates (usteps/s) + // this values was provided in default_tilt_moving_profiles.json by SLA-team + std::map tilt_speeds = { + { tsMove120 , 120 }, + { tsLayer200 , 200 }, + { tsMove300 , 300 }, + { tsLayer400 , 400 }, + { tsLayer600 , 600 }, + { tsLayer800 , 800 }, + { tsLayer1000, 1000 }, + { tsLayer1250, 1250 }, + { tsLayer1500, 1500 }, + { tsLayer1750, 1750 }, + { tsLayer2000, 2000 }, + { tsLayer2250, 2250 }, + { tsMove5120 , 5120 }, + { tsMove8000 , 8000 }, + }; + + int delay_before_exposure_ms { 0 }; + int delay_after_exposure_ms { 0 }; + int tilt_down_offset_delay_ms { 0 }; + int tilt_down_delay_ms { 0 }; + int tilt_up_offset_delay_ms { 0 }; + int tilt_up_delay_ms { 0 }; + int tower_hop_height_nm { 0 }; + int tilt_down_offset_steps { 0 }; + int tilt_down_cycles { 0 }; + int tilt_up_offset_steps { 0 }; + int tilt_up_cycles { 0 }; + bool use_tilt { true }; + int tower_speed { 0 }; + int tilt_down_initial_speed { 0 }; + int tilt_down_finish_speed { 0 }; + int tilt_up_initial_speed { 0 }; + int tilt_up_finish_speed { 0 }; + + ExposureProfile() {} + + ExposureProfile(const SLAMaterialConfig& config, int opt_id) + { + delay_before_exposure_ms = int(1000 * config.delay_before_exposure.get_at(opt_id)); + delay_after_exposure_ms = int(1000 * config.delay_after_exposure.get_at(opt_id)); + tilt_down_offset_delay_ms = int(1000 * config.tilt_down_offset_delay.get_at(opt_id)); + tilt_down_delay_ms = int(1000 * config.tilt_down_delay.get_at(opt_id)); + tilt_up_offset_delay_ms = int(1000 * config.tilt_up_offset_delay.get_at(opt_id)); + tilt_up_delay_ms = int(1000 * config.tilt_up_delay.get_at(opt_id)); + tower_hop_height_nm = config.tower_hop_height.get_at(opt_id) * 1000000; + tilt_down_offset_steps = config.tilt_down_offset_steps.get_at(opt_id); + tilt_down_cycles = config.tilt_down_cycles.get_at(opt_id); + tilt_up_offset_steps = config.tilt_up_offset_steps.get_at(opt_id); + tilt_up_cycles = config.tilt_up_cycles.get_at(opt_id); + use_tilt = config.use_tilt.get_at(opt_id); + tower_speed = tower_speeds.at(static_cast(config.tower_speed.getInts()[opt_id])); + tilt_down_initial_speed = tilt_speeds.at(static_cast(config.tilt_down_initial_speed.getInts()[opt_id])); + tilt_down_finish_speed = tilt_speeds.at(static_cast(config.tilt_down_finish_speed.getInts()[opt_id])); + tilt_up_initial_speed = tilt_speeds.at(static_cast(config.tilt_up_initial_speed.getInts()[opt_id])); + tilt_up_finish_speed = tilt_speeds.at(static_cast(config.tilt_up_finish_speed.getInts()[opt_id])); + } +}; + +static int layer_peel_move_time(int layer_height_nm, ExposureProfile p) +{ + int profile_change_delay = Ms(20); // propagation delay of sending profile change command to MC + int sleep_delay = Ms(2); // average delay of the Linux system sleep function + + int tilt = Ms(0); + if (p.use_tilt) { + tilt += profile_change_delay; + // initial down movement + tilt += count_move_time( + "tilt", + p.tilt_down_offset_steps, + p.tilt_down_initial_speed); + // initial down delay + tilt += p.tilt_down_offset_delay_ms + sleep_delay; + // profile change delay if down finish profile is different from down initial + tilt += profile_change_delay; + // cycle down movement + tilt += p.tilt_down_cycles * count_move_time( + "tilt", + int((tiltHeight - p.tilt_down_offset_steps) / p.tilt_down_cycles), + p.tilt_down_finish_speed); + // cycle down delay + tilt += p.tilt_down_cycles * (p.tilt_down_delay_ms + sleep_delay); + + // profile change delay if up initial profile is different from down finish + tilt += profile_change_delay; + // initial up movement + tilt += count_move_time( + "tilt", + tiltHeight - p.tilt_up_offset_steps, + p.tilt_up_initial_speed); + // initial up delay + tilt += p.tilt_up_offset_delay_ms + sleep_delay; + // profile change delay if up initial profile is different from down finish + tilt += profile_change_delay; + // finish up movement + tilt += p.tilt_up_cycles * count_move_time( + "tilt", + int(p.tilt_up_offset_steps / p.tilt_up_cycles), + p.tilt_up_finish_speed); + // cycle down delay + tilt += p.tilt_up_cycles * (p.tilt_up_delay_ms + sleep_delay); + } + + int tower = Ms(0); + if (p.tower_hop_height_nm > 0) { + tower += count_move_time( + "tower", + nm_to_tower_microsteps(int(p.tower_hop_height_nm) + layer_height_nm), + p.tower_speed); + tower += count_move_time( + "tower", + nm_to_tower_microsteps(int(p.tower_hop_height_nm)), + p.tower_speed); + tower += profile_change_delay; + } + else { + tower += count_move_time( + "tower", + nm_to_tower_microsteps(layer_height_nm), + p.tower_speed); + tower += profile_change_delay; + } + return int(tilt + tower); +} + // Merging the slices from all the print objects into one slice grid and // calculating print statistics from the merge result. void SLAPrint::Steps::merge_slices_and_eval_stats() { @@ -920,7 +1130,7 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { print_statistics.clear(); - const double area_fill = printer_config.area_fill.getFloat()*0.01;// 0.5 (50%); + const double area_fill = material_config.area_fill.getFloat()*0.01;// 0.5 (50%); const double fast_tilt = printer_config.fast_tilt_time.getFloat();// 5.0; const double slow_tilt = printer_config.slow_tilt_time.getFloat();// 8.0; const double hv_tilt = printer_config.high_viscosity_tilt_time.getFloat();// 10.0; @@ -930,34 +1140,29 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { const int fade_layers_cnt = m_print->m_default_object_config.faded_layers.getInt();// 10 // [3;20] + ExposureProfile below(material_config, 0); + ExposureProfile above(material_config, 1); + + const int first_slow_layers = fade_layers_cnt + first_extra_slow_layers; + const std::string printer_model = printer_config.printer_model; + const bool is_qidi_print = printer_model == "SL1" || printer_model == "SL1S" || printer_model == "M1"; + const auto width = scaled(printer_config.display_width.getFloat()); const auto height = scaled(printer_config.display_height.getFloat()); const double display_area = width*height; - double supports_volume(0.0); - double models_volume(0.0); - - double estim_time(0.0); - std::vector layers_times; - layers_times.reserve(printer_input.size()); - - size_t slow_layers = 0; - size_t fast_layers = 0; + std::vector> layers_info; // time, area, is_fast, models_volume, supports_volume + layers_info.resize(printer_input.size()); const double delta_fade_time = (init_exp_time - exp_time) / (fade_layers_cnt + 1); - double fade_layer_time = init_exp_time; - - execution::SpinningMutex mutex; - using Lock = std::lock_guard; // Going to parallel: auto printlayerfn = [this, // functions and read only vars - area_fill, display_area, exp_time, init_exp_time, fast_tilt, slow_tilt, hv_tilt, material_config, delta_fade_time, + area_fill, display_area, exp_time, init_exp_time, fast_tilt, slow_tilt, hv_tilt, material_config, delta_fade_time, is_qidi_print, first_slow_layers, below, above, // write vars - &mutex, &models_volume, &supports_volume, &estim_time, &slow_layers, - &fast_layers, &fade_layer_time, &layers_times](size_t sliced_layer_cnt) + &layers_info](size_t sliced_layer_cnt) { PrintLayer &layer = m_print->m_printer_input[sliced_layer_cnt]; @@ -1007,9 +1212,7 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { for (const ExPolygon& polygon : model_polygons) layer_model_area += area(polygon); - if (layer_model_area < 0 || layer_model_area > 0) { - Lock lck(mutex); models_volume += layer_model_area * l_height; - } + const double models_volume = (layer_model_area < 0 || layer_model_area > 0) ? layer_model_area * l_height : 0.; if(!supports_polygons.empty()) { if(model_polygons.empty()) supports_polygons = union_ex(supports_polygons); @@ -1021,9 +1224,8 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { for (const ExPolygon& polygon : supports_polygons) layer_support_area += area(polygon); - if (layer_support_area < 0 || layer_support_area > 0) { - Lock lck(mutex); supports_volume += layer_support_area * l_height; - } + const double supports_volume = (layer_support_area < 0 || layer_support_area > 0) ? layer_support_area * l_height : 0.; + const double layer_area = layer_model_area + layer_support_area; // Here we can save the expensively calculated polygons for printing ExPolygons trslices; @@ -1033,34 +1235,32 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { layer.transformed_slices(union_ex(trslices)); - // Calculation of the slow and fast layers to the future controlling those values on FW + // Calculation of the printing time + // + Calculation of the slow and fast layers to the future controlling those values on FW + double layer_times = 0.0; + bool is_fast_layer = false; - const bool is_fast_layer = (layer_model_area + layer_support_area) <= display_area*area_fill; - const double tilt_time = material_config.material_print_speed == slamsSlow ? slow_tilt : - material_config.material_print_speed == slamsHighViscosity ? hv_tilt : - is_fast_layer ? fast_tilt : slow_tilt; + if (is_qidi_print) { + is_fast_layer = int(sliced_layer_cnt) < first_slow_layers || layer_area <= display_area * area_fill; + const int l_height_nm = 1000000 * l_height; - { Lock lck(mutex); - if (is_fast_layer) - fast_layers++; - else - slow_layers++; + layer_times = layer_peel_move_time(l_height_nm, is_fast_layer ? below : above) + + (is_fast_layer ? below : above).delay_before_exposure_ms + + (is_fast_layer ? below : above).delay_after_exposure_ms + + refresh_delay_ms * 5 + // ~ 5x frame display wait + 124; // Magical constant to compensate remaining computation delay in exposure thread - // Calculation of the printing time + layer_times *= 0.001; // All before calculations are made in ms, but we need it in s + } + else { + is_fast_layer = layer_area <= display_area*area_fill; + const double tilt_time = material_config.material_print_speed == slamsSlow ? slow_tilt : + material_config.material_print_speed == slamsHighViscosity ? hv_tilt : + is_fast_layer ? fast_tilt : slow_tilt; - double layer_times = 0.0; - if (sliced_layer_cnt < 3) - layer_times += init_exp_time; - else if (fade_layer_time > exp_time) { - fade_layer_time -= delta_fade_time; - layer_times += fade_layer_time; - } - else - layer_times += exp_time; layer_times += tilt_time; //// Per layer times (magical constants cuclulated from FW) - static double exposure_safe_delay_before{ 3.0 }; static double exposure_high_viscosity_delay_before{ 3.5 }; static double exposure_slow_move_delay_before{ 1.0 }; @@ -1077,33 +1277,41 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { l_height * 5 // tower move + 120 / 1000 // Magical constant to compensate remaining computation delay in exposure thread ); - - layers_times.push_back(layer_times); - estim_time += layer_times; } + + // We are done with tilt time, but we haven't added the exposure time yet. + layer_times += std::max(exp_time, init_exp_time - sliced_layer_cnt * delta_fade_time); + + // Collect values for this layer. + layers_info[sliced_layer_cnt] = std::make_tuple(layer_times, layer_area * SCALING_FACTOR * SCALING_FACTOR, is_fast_layer, models_volume, supports_volume); }; // sequential version for debugging: - // for(size_t i = 0; i < m_printer_input.size(); ++i) printlayerfn(i); + // for(size_t i = 0; i < printer_input.size(); ++i) printlayerfn(i); execution::for_each(ex_tbb, size_t(0), printer_input.size(), printlayerfn, execution::max_concurrency(ex_tbb)); - auto SCALING2 = SCALING_FACTOR * SCALING_FACTOR; - print_statistics.support_used_material = supports_volume * SCALING2; - print_statistics.objects_used_material = models_volume * SCALING2; + print_statistics.clear(); - // Estimated printing time - // A layers count o the highest object if (printer_input.size() == 0) print_statistics.estimated_print_time = NaNd; else { - print_statistics.estimated_print_time = estim_time; - print_statistics.layers_times = layers_times; + size_t i=0; + for (const auto& [time, area, is_fast, models_volume, supports_volume] : layers_info) { + print_statistics.fast_layers_count += int(is_fast); + print_statistics.slow_layers_count += int(! is_fast); + print_statistics.layers_areas.emplace_back(area); + print_statistics.estimated_print_time += time; + print_statistics.layers_times_running_total.emplace_back(time + (i==0 ? 0. : print_statistics.layers_times_running_total[i-1])); + print_statistics.objects_used_material += models_volume * SCALING_FACTOR * SCALING_FACTOR; + print_statistics.support_used_material += supports_volume * SCALING_FACTOR * SCALING_FACTOR; + ++i; + } + if (is_qidi_print) + // For our SLA printers, we add an error of the estimate: + print_statistics.estimated_print_time_tolerance = 0.03 * print_statistics.estimated_print_time; } - print_statistics.fast_layers_count = fast_layers; - print_statistics.slow_layers_count = slow_layers; - report_status(-2, "", SlicingStatus::RELOAD_SLA_PREVIEW); } diff --git a/src/libslic3r/SLAPrintSteps.hpp b/src/libslic3r/SLAPrintSteps.hpp index 32d10a4..0879ae4 100644 --- a/src/libslic3r/SLAPrintSteps.hpp +++ b/src/libslic3r/SLAPrintSteps.hpp @@ -1,12 +1,17 @@ #ifndef SLAPRINTSTEPS_HPP #define SLAPRINTSTEPS_HPP -#include - #include - #include #include +#include +#include +#include +#include +#include + +#include "admesh/stl.h" +#include "libslic3r/libslic3r.h" namespace Slic3r { diff --git a/src/libslic3r/SVG.cpp b/src/libslic3r/SVG.cpp index 4770334..622c631 100644 --- a/src/libslic3r/SVG.cpp +++ b/src/libslic3r/SVG.cpp @@ -1,7 +1,14 @@ #include "SVG.hpp" -#include #include +#include +#include +#include + +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/Surface.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { diff --git a/src/libslic3r/SVG.hpp b/src/libslic3r/SVG.hpp index 2572713..00c532b 100644 --- a/src/libslic3r/SVG.hpp +++ b/src/libslic3r/SVG.hpp @@ -1,12 +1,24 @@ #ifndef slic3r_SVG_hpp_ #define slic3r_SVG_hpp_ +#include +#include +#include +#include +#include + #include "libslic3r.h" -#include "clipper.hpp" +#include "libslic3r/clipper.hpp" #include "ExPolygon.hpp" #include "Line.hpp" #include "TriangleMesh.hpp" #include "Surface.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/MultiPoint.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/Polyline.hpp" namespace Slic3r { diff --git a/src/libslic3r/Semver.cpp b/src/libslic3r/Semver.cpp index 5d36b39..c19917c 100644 --- a/src/libslic3r/Semver.cpp +++ b/src/libslic3r/Semver.cpp @@ -1,4 +1,5 @@ -#include "libslic3r.h" +#include "libslic3r/Semver.hpp" +#include "libslic3r_version.h" namespace Slic3r { diff --git a/src/libslic3r/Semver.hpp b/src/libslic3r/Semver.hpp index a7489de..c55bcee 100644 --- a/src/libslic3r/Semver.hpp +++ b/src/libslic3r/Semver.hpp @@ -1,14 +1,18 @@ #ifndef slic3r_Semver_hpp_ #define slic3r_Semver_hpp_ +#include +#include +#include +#include +#include +#include #include #include #include #include -#include -#include - -#include "semver/semver.h" +#include +#include #include "Exception.hpp" diff --git a/src/libslic3r/ShortEdgeCollapse.cpp b/src/libslic3r/ShortEdgeCollapse.cpp index c8e4eb9..dcd6943 100644 --- a/src/libslic3r/ShortEdgeCollapse.cpp +++ b/src/libslic3r/ShortEdgeCollapse.cpp @@ -1,15 +1,36 @@ #include "ShortEdgeCollapse.hpp" -#include "libslic3r/NormalUtils.hpp" -#include -#include +#include +#include #include #include +#include +#include -#include +#include "libslic3r/NormalUtils.hpp" +#include "admesh/stl.h" +#include "libslic3r/Point.hpp" +#include "libslic3r/TriangleMesh.hpp" namespace Slic3r { +/** + * Simple implementation of Fisher-Yates algorithm using uniform int + * distribution from boost, ensurinng the result is the same + * accross platforms. + * + * DO NOT EXPECT IT TO BE PERFORMANT! Use it only when std::shuffle is + * not applicable. + */ +template +void stable_shuffle(Range &range, UniformRandomNumberGenerator &generator) { + const int n{static_cast(range.size())}; + for (int i{0}; i < n - 2; ++i) { + int j{boost::random::uniform_int_distribution{i, n-1}(generator)}; + std::swap(range[i], range[j]); + } +} + void its_short_edge_collpase(indexed_triangle_set &mesh, size_t target_triangle_count) { // whenever vertex is removed, its mapping is update to the index of vertex with wich it merged std::vector vertices_index_mapping(mesh.vertices.size()); @@ -98,8 +119,8 @@ void its_short_edge_collpase(indexed_triangle_set &mesh, size_t target_triangle_ float max_edge_len_squared = edge_len * edge_len; //shuffle the faces and traverse in random order, this MASSIVELY improves the quality of the result - std::shuffle(face_indices.begin(), face_indices.end(), generator); - + stable_shuffle(face_indices, generator); + int allowed_face_removals = int(face_indices.size()) - int(target_triangle_count); for (const size_t &face_idx : face_indices) { if (face_removal_flags[face_idx]) { diff --git a/src/libslic3r/ShortEdgeCollapse.hpp b/src/libslic3r/ShortEdgeCollapse.hpp index e6f1822..26c65c2 100644 --- a/src/libslic3r/ShortEdgeCollapse.hpp +++ b/src/libslic3r/ShortEdgeCollapse.hpp @@ -1,8 +1,13 @@ #ifndef SRC_LIBSLIC3R_SHORTEDGECOLLAPSE_HPP_ #define SRC_LIBSLIC3R_SHORTEDGECOLLAPSE_HPP_ +#include +#include + #include "libslic3r/TriangleMesh.hpp" +struct indexed_triangle_set; + namespace Slic3r{ // Decimates the model by collapsing short edges. It starts with very small edges and gradually increases the collapsible length, diff --git a/src/libslic3r/ShortestPath.cpp b/src/libslic3r/ShortestPath.cpp index 5f56f19..9ed66ac 100644 --- a/src/libslic3r/ShortestPath.cpp +++ b/src/libslic3r/ShortestPath.cpp @@ -4,14 +4,24 @@ #undef assert #endif -#include "clipper.hpp" +#include +#include +#include +#include +#include + #include "ShortestPath.hpp" #include "KDTreeIndirect.hpp" #include "MutablePriorityQueue.hpp" #include "Print.hpp" - -#include -#include +#include "libslic3r/ExtrusionEntity.hpp" +#include "libslic3r/ExtrusionEntityCollection.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/MultiMaterialSegmentation.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/libslic3r.h" +#include "tcbspan/span.hpp" namespace Slic3r { @@ -1009,7 +1019,7 @@ std::vector> chain_segments_greedy2(SegmentEndPointFunc std::vector> chain_extrusion_entities(const std::vector &entities, const Point *start_near, const bool reversed) { auto segment_end_point = [&entities, reversed](size_t idx, bool first_point) -> const Point& { return first_point == reversed ? entities[idx]->last_point() : entities[idx]->first_point(); }; - auto could_reverse = [&entities](size_t idx) { const ExtrusionEntity *ee = entities[idx]; return ee->is_loop() || ee->can_reverse(); }; + auto could_reverse = [&entities](size_t idx) { const ExtrusionEntity *ee = entities[idx]; return ee->is_loop() || ee->can_reverse(); }; std::vector> out = chain_segments_greedy_constrained_reversals( segment_end_point, could_reverse, entities.size(), start_near); for (std::pair &segment : out) { @@ -1071,6 +1081,7 @@ ExtrusionEntityReferences chain_extrusion_references(const ExtrusionEntityCollec } else return chain_extrusion_references(eec.entities, start_near, reversed); } + std::vector> chain_extrusion_paths(std::vector &extrusion_paths, const Point *start_near) { auto segment_end_point = [&extrusion_paths](size_t idx, bool first_point) -> const Point& { return first_point ? extrusion_paths[idx].first_point() : extrusion_paths[idx].last_point(); }; @@ -1115,10 +1126,6 @@ std::vector chain_expolygons(const ExPolygons &expolygons, Point *start_ return chain_points(ordering_points); } -#ifndef NDEBUG - // #define DEBUG_SVG_OUTPUT -#endif /* NDEBUG */ - #ifdef DEBUG_SVG_OUTPUT void svg_draw_polyline_chain(const char *name, size_t idx, const Polylines &polylines) { diff --git a/src/libslic3r/ShortestPath.hpp b/src/libslic3r/ShortestPath.hpp index 04b9b89..4c24166 100644 --- a/src/libslic3r/ShortestPath.hpp +++ b/src/libslic3r/ShortestPath.hpp @@ -1,21 +1,29 @@ #ifndef slic3r_ShortestPath_hpp_ #define slic3r_ShortestPath_hpp_ +#include +#include +#include +#include + #include "libslic3r.h" #include "ExtrusionEntity.hpp" #include "Point.hpp" - -#include -#include +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Polyline.hpp" namespace Slic3r { +class ExtrusionEntityCollection; +class Line; namespace ClipperLib { class PolyNode; + using PolyNodes = std::vector>; } class ExPolygon; + using ExPolygons = std::vector; // Used by chain_expolygons() @@ -36,6 +44,7 @@ void chain_and_reorder_extrusion_entities(std::v ExtrusionEntityReferences chain_extrusion_references(const std::vector &entities, const Point *start_near = nullptr, const bool reversed = false); // The same as above, respect eec.no_sort flag. ExtrusionEntityReferences chain_extrusion_references(const ExtrusionEntityCollection &eec, const Point *start_near = nullptr, const bool reversed = false); + std::vector> chain_extrusion_paths(std::vector &extrusion_paths, const Point *start_near = nullptr); void reorder_extrusion_paths(std::vector &extrusion_paths, std::vector> &chain); void chain_and_reorder_extrusion_paths(std::vector &extrusion_paths, const Point *start_near = nullptr); @@ -65,6 +74,7 @@ ClipperLib::PolyNodes chain_clipper_polynodes(const Points &points, const Cl // Returns pairs of PrintObject idx and instance of that PrintObject. class Print; struct PrintInstance; + std::vector chain_print_object_instances(const Print &print); // Chain lines into polylines. diff --git a/src/libslic3r/SlicesToTriangleMesh.cpp b/src/libslic3r/SlicesToTriangleMesh.cpp index 9e290d4..5e42870 100644 --- a/src/libslic3r/SlicesToTriangleMesh.cpp +++ b/src/libslic3r/SlicesToTriangleMesh.cpp @@ -1,15 +1,14 @@ -#include +#include +#include +#include #include "SlicesToTriangleMesh.hpp" - #include "libslic3r/Execution/ExecutionTBB.hpp" #include "libslic3r/ClipperUtils.hpp" #include "libslic3r/Tesselate.hpp" - -#include -#include - -#include +#include "libslic3r/Execution/Execution.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/TriangleMesh.hpp" namespace Slic3r { diff --git a/src/libslic3r/SlicesToTriangleMesh.hpp b/src/libslic3r/SlicesToTriangleMesh.hpp index 57b540d..e429ad7 100644 --- a/src/libslic3r/SlicesToTriangleMesh.hpp +++ b/src/libslic3r/SlicesToTriangleMesh.hpp @@ -1,8 +1,11 @@ #ifndef SLICESTOTRIANGLEMESH_HPP #define SLICESTOTRIANGLEMESH_HPP +#include + #include "libslic3r/TriangleMesh.hpp" #include "libslic3r/ExPolygon.hpp" +#include "admesh/stl.h" namespace Slic3r { diff --git a/src/libslic3r/Slicing.cpp b/src/libslic3r/Slicing.cpp index 36aef50..bbcad2b 100644 --- a/src/libslic3r/Slicing.cpp +++ b/src/libslic3r/Slicing.cpp @@ -1,10 +1,12 @@ #include +#include +#include #include "libslic3r.h" #include "Slicing.hpp" #include "SlicingAdaptive.hpp" #include "PrintConfig.hpp" -#include "Model.hpp" +#include "libslic3r/Point.hpp" // #define SLIC3R_DEBUG @@ -14,6 +16,7 @@ #define DEBUG #define _DEBUG #include "SVG.hpp" + #undef assert #include #endif @@ -59,10 +62,11 @@ coordf_t Slicing::max_layer_height_from_nozzle(const DynamicPrintConfig &print_c } SlicingParameters SlicingParameters::create_from_config( - const PrintConfig &print_config, - const PrintObjectConfig &object_config, - coordf_t object_height, - const std::vector &object_extruders) + const PrintConfig &print_config, + const PrintObjectConfig &object_config, + coordf_t object_height, + const std::vector &object_extruders, + const Vec3d &object_shrinkage_compensation) { assert(! print_config.first_layer_height.percent); coordf_t first_layer_height = (print_config.first_layer_height.value <= 0) ? @@ -81,7 +85,9 @@ SlicingParameters SlicingParameters::create_from_config( params.first_print_layer_height = first_layer_height; params.first_object_layer_height = first_layer_height; params.object_print_z_min = 0.; - params.object_print_z_max = object_height; + params.object_print_z_max = object_height * object_shrinkage_compensation.z(); + params.object_print_z_uncompensated_max = object_height; + params.object_shrinkage_compensation_z = object_shrinkage_compensation.z(); params.base_raft_layers = object_config.raft_layers.value; params.soluble_interface = soluble_interface; @@ -110,20 +116,12 @@ SlicingParameters SlicingParameters::create_from_config( params.min_layer_height = std::min(params.min_layer_height, params.layer_height); params.max_layer_height = std::max(params.max_layer_height, params.layer_height); - //w34 - /* if (!soluble_interface) { + if (! soluble_interface) { params.gap_raft_object = object_config.raft_contact_distance.value; params.gap_object_support = object_config.support_material_bottom_contact_distance.value; params.gap_support_object = object_config.support_material_contact_distance.value; if (params.gap_object_support <= 0) params.gap_object_support = params.gap_support_object; - }*/ - if (!soluble_interface ) { - params.gap_raft_object = object_config.raft_contact_distance.value; - params.gap_object_support = object_config.support_material_bottom_contact_distance.value; - params.gap_support_object = object_config.support_material_contact_distance.value; - if (params.gap_object_support <= 0) - params.gap_object_support = params.gap_support_object; //w34 if (object_config.support_material_synchronize_layers) { @@ -165,6 +163,7 @@ SlicingParameters SlicingParameters::create_from_config( coordf_t print_z = params.raft_contact_top_z + params.gap_raft_object; params.object_print_z_min = print_z; params.object_print_z_max += print_z; + params.object_print_z_uncompensated_max += print_z; } params.valid = true; @@ -237,10 +236,10 @@ std::vector layer_height_profile_from_ranges( lh_append(hi, height); } - if (coordf_t z = last_z(); z < slicing_params.object_print_z_height()) { + if (coordf_t z = last_z(); z < slicing_params.object_print_z_uncompensated_height()) { // Insert a step of normal layer height up to the object top. lh_append(z, slicing_params.layer_height); - lh_append(slicing_params.object_print_z_height(), slicing_params.layer_height); + lh_append(slicing_params.object_print_z_uncompensated_height(), slicing_params.layer_height); } return layer_height_profile; @@ -267,7 +266,7 @@ std::vector layer_height_profile_adaptive(const SlicingParameters& slici // last facet visited by the as.next_layer_height() function, where the facets are sorted by their increasing Z span. size_t current_facet = 0; // loop until we have at least one layer and the max slice_z reaches the object height - while (print_z + EPSILON < slicing_params.object_print_z_height()) { + while (print_z + EPSILON < slicing_params.object_print_z_uncompensated_height()) { float height = slicing_params.max_layer_height; // Slic3r::debugf "\n Slice layer: %d\n", $id; // determine next layer height @@ -326,10 +325,10 @@ std::vector layer_height_profile_adaptive(const SlicingParameters& slici print_z += height; } - double z_gap = slicing_params.object_print_z_height() - *(layer_height_profile.end() - 2); + double z_gap = slicing_params.object_print_z_uncompensated_height() - *(layer_height_profile.end() - 2); if (z_gap > 0.0) { - layer_height_profile.push_back(slicing_params.object_print_z_height()); + layer_height_profile.push_back(slicing_params.object_print_z_uncompensated_height()); layer_height_profile.push_back(std::clamp(z_gap, slicing_params.min_layer_height, slicing_params.max_layer_height)); } @@ -431,12 +430,12 @@ void adjust_layer_height_profile( std::pair z_span_variable = std::pair( slicing_params.first_object_layer_height_fixed() ? slicing_params.first_object_layer_height : 0., - slicing_params.object_print_z_height()); + slicing_params.object_print_z_uncompensated_height()); if (z < z_span_variable.first || z > z_span_variable.second) return; assert(layer_height_profile.size() >= 2); - assert(std::abs(layer_height_profile[layer_height_profile.size() - 2] - slicing_params.object_print_z_height()) < EPSILON); + assert(std::abs(layer_height_profile[layer_height_profile.size() - 2] - slicing_params.object_print_z_uncompensated_height()) < EPSILON); // 1) Get the current layer thickness at z. coordf_t current_layer_height = slicing_params.layer_height; @@ -597,7 +596,7 @@ void adjust_layer_height_profile( assert(layer_height_profile.size() > 2); assert(layer_height_profile.size() % 2 == 0); assert(layer_height_profile[0] == 0.); - assert(std::abs(layer_height_profile[layer_height_profile.size() - 2] - slicing_params.object_print_z_height()) < EPSILON); + assert(std::abs(layer_height_profile[layer_height_profile.size() - 2] - slicing_params.object_print_z_uncompensated_height()) < EPSILON); #ifdef _DEBUG for (size_t i = 2; i < layer_height_profile.size(); i += 2) assert(layer_height_profile[i - 2] <= layer_height_profile[i]); @@ -720,6 +719,7 @@ std::vector generate_object_layers( out.push_back(print_z); } + const coordf_t shrinkage_compensation_z = slicing_params.object_shrinkage_compensation_z; size_t idx_layer_height_profile = 0; // loop until we have at least one layer and the max slice_z reaches the object height coordf_t slice_z = print_z + 0.5 * slicing_params.min_layer_height; @@ -728,17 +728,18 @@ std::vector generate_object_layers( if (idx_layer_height_profile < layer_height_profile.size()) { size_t next = idx_layer_height_profile + 2; for (;;) { - if (next >= layer_height_profile.size() || slice_z < layer_height_profile[next]) + if (next >= layer_height_profile.size() || slice_z < layer_height_profile[next] * shrinkage_compensation_z) break; idx_layer_height_profile = next; next += 2; } - coordf_t z1 = layer_height_profile[idx_layer_height_profile]; - coordf_t h1 = layer_height_profile[idx_layer_height_profile + 1]; + + const coordf_t z1 = layer_height_profile[idx_layer_height_profile] * shrinkage_compensation_z; + const coordf_t h1 = layer_height_profile[idx_layer_height_profile + 1]; height = h1; if (next < layer_height_profile.size()) { - coordf_t z2 = layer_height_profile[next]; - coordf_t h2 = layer_height_profile[next + 1]; + const coordf_t z2 = layer_height_profile[next] * shrinkage_compensation_z; + const coordf_t h2 = layer_height_profile[next + 1]; height = lerp(h1, h2, (slice_z - z1) / (z2 - z1)); assert(height >= slicing_params.min_layer_height - EPSILON && height <= slicing_params.max_layer_height + EPSILON); } diff --git a/src/libslic3r/Slicing.hpp b/src/libslic3r/Slicing.hpp index 4c81554..a1e6e6c 100644 --- a/src/libslic3r/Slicing.hpp +++ b/src/libslic3r/Slicing.hpp @@ -3,14 +3,19 @@ #ifndef slic3r_Slicing_hpp_ #define slic3r_Slicing_hpp_ +#include #include #include #include #include #include +#include +#include +#include "Point.hpp" #include "libslic3r.h" #include "Utils.hpp" +#include "libslic3r/PrintConfig.hpp" namespace Slic3r { @@ -29,10 +34,11 @@ struct SlicingParameters SlicingParameters() = default; static SlicingParameters create_from_config( - const PrintConfig &print_config, - const PrintObjectConfig &object_config, - coordf_t object_height, - const std::vector &object_extruders); + const PrintConfig &print_config, + const PrintObjectConfig &object_config, + coordf_t object_height, + const std::vector &object_extruders, + const Vec3d &object_shrinkage_compensation); // Has any raft layers? bool has_raft() const { return raft_layers() > 0; } @@ -42,8 +48,13 @@ struct SlicingParameters bool first_object_layer_height_fixed() const { return ! has_raft() || first_object_layer_bridging; } // Height of the object to be printed. This value does not contain the raft height. + // This value is scaled by shrinkage compensation in the Z-axis. coordf_t object_print_z_height() const { return object_print_z_max - object_print_z_min; } + // Height of the object to be printed. This value does not contain the raft height. + // This value isn't scaled by shrinkage compensation in the Z-axis. + coordf_t object_print_z_uncompensated_height() const { return object_print_z_uncompensated_max - object_print_z_min; } + bool valid { false }; // Number of raft layers. @@ -95,7 +106,13 @@ struct SlicingParameters coordf_t raft_contact_top_z { 0 }; // In case of a soluble interface, object_print_z_min == raft_contact_top_z, otherwise there is a gap between the raft and the 1st object layer. coordf_t object_print_z_min { 0 }; + // This value of maximum print Z is scaled by shrinkage compensation in the Z-axis. coordf_t object_print_z_max { 0 }; + + // This value of maximum print Z isn't scaled by shrinkage compensation. + coordf_t object_print_z_uncompensated_max { 0 }; + // Scaling factor for compensating shrinkage in Z-axis. + coordf_t object_shrinkage_compensation_z { 0 }; }; static_assert(IsTriviallyCopyable::value, "SlicingParameters class is not POD (and it should be - see constructor)."); @@ -167,7 +184,6 @@ void adjust_layer_height_profile( // Produce object layers as pairs of low / high layer boundaries, stored into a linear vector. // The object layers are based at z=0, ignoring the raft layers. - std::vector generate_object_layers( const SlicingParameters &slicing_params, const std::vector &layer_height_profile, diff --git a/src/libslic3r/SlicingAdaptive.cpp b/src/libslic3r/SlicingAdaptive.cpp index 9dd57eb..d9fb891 100644 --- a/src/libslic3r/SlicingAdaptive.cpp +++ b/src/libslic3r/SlicingAdaptive.cpp @@ -1,10 +1,21 @@ +#include +#include +#include +#include + #include "libslic3r.h" #include "Model.hpp" #include "TriangleMesh.hpp" #include "SlicingAdaptive.hpp" +#include "admesh/stl.h" +#ifndef NDEBUG + #define ADAPTIVE_LAYER_HEIGHT_DEBUG +#endif /* NDEBUG */ + +#ifdef ADAPTIVE_LAYER_HEIGHT_DEBUG #include -#include +#endif // Based on the work of Florens Waserfall (@platch on github) // and his paper @@ -28,10 +39,6 @@ ylabel("layer height"); legend("tan(a) as cura - topographic lines distance limit", "sqrt(tan(a)) as QIDISlicer - error triangle area limit", "old slic3r - max distance metric", "new slic3r - Waserfall paper"); #endif -#ifndef NDEBUG - #define ADAPTIVE_LAYER_HEIGHT_DEBUG -#endif /* NDEBUG */ - namespace Slic3r { diff --git a/src/libslic3r/SlicingAdaptive.hpp b/src/libslic3r/SlicingAdaptive.hpp index a296553..dc67c8f 100644 --- a/src/libslic3r/SlicingAdaptive.hpp +++ b/src/libslic3r/SlicingAdaptive.hpp @@ -3,6 +3,11 @@ #ifndef slic3r_SlicingAdaptive_hpp_ #define slic3r_SlicingAdaptive_hpp_ +#include +#include +#include +#include + #include "Slicing.hpp" #include "admesh/stl.h" @@ -10,6 +15,7 @@ namespace Slic3r { class ModelVolume; +class ModelObject; class SlicingAdaptive { diff --git a/src/libslic3r/Subdivide.cpp b/src/libslic3r/Subdivide.cpp index 31988a8..219687a 100644 --- a/src/libslic3r/Subdivide.cpp +++ b/src/libslic3r/Subdivide.cpp @@ -1,4 +1,15 @@ #include "Subdivide.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "Point.hpp" namespace Slic3r{ diff --git a/src/libslic3r/Subdivide.hpp b/src/libslic3r/Subdivide.hpp index f97e4b3..0badd4c 100644 --- a/src/libslic3r/Subdivide.hpp +++ b/src/libslic3r/Subdivide.hpp @@ -2,6 +2,7 @@ #define libslic3r_Subdivide_hpp_ #include "TriangleMesh.hpp" +#include "admesh/stl.h" namespace Slic3r { diff --git a/src/libslic3r/Support/OrganicSupport.cpp b/src/libslic3r/Support/OrganicSupport.cpp index 05e515d..3c9cc32 100644 --- a/src/libslic3r/Support/OrganicSupport.cpp +++ b/src/libslic3r/Support/OrganicSupport.cpp @@ -1,23 +1,44 @@ #include "OrganicSupport.hpp" -#include "SupportCommon.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "../AABBTreeLines.hpp" #include "../ClipperUtils.hpp" #include "../Polygon.hpp" -#include "../Polyline.hpp" #include "../MutablePolygon.hpp" #include "../TriangleMeshSlicer.hpp" - -#include - -#include +#include "admesh/stl.h" +#include "libslic3r/AABBTreeIndirect.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Print.hpp" +#include "libslic3r/Slicing.hpp" +#include "libslic3r/Support/TreeModelVolumes.hpp" +#include "libslic3r/Support/TreeSupport.hpp" +#include "libslic3r/Support/TreeSupportCommon.hpp" +#include "libslic3r/Utils.hpp" +#include "libslic3r/libslic3r.h" #define TREE_SUPPORT_ORGANIC_NUDGE_NEW 1 #ifndef TREE_SUPPORT_ORGANIC_NUDGE_NEW + #include + // Old version using OpenVDB, works but it is extremely slow for complex meshes. #include "../OpenVDBUtilsLegacy.hpp" - #include #endif // TREE_SUPPORT_ORGANIC_NUDGE_NEW namespace Slic3r diff --git a/src/libslic3r/Support/OrganicSupport.hpp b/src/libslic3r/Support/OrganicSupport.hpp index f86caab..aba7f60 100644 --- a/src/libslic3r/Support/OrganicSupport.hpp +++ b/src/libslic3r/Support/OrganicSupport.hpp @@ -1,8 +1,12 @@ #ifndef slic3r_OrganicSupport_hpp #define slic3r_OrganicSupport_hpp +#include +#include + #include "SupportCommon.hpp" #include "TreeSupport.hpp" +#include "libslic3r/Support/SupportLayer.hpp" namespace Slic3r { @@ -13,6 +17,8 @@ namespace FFFTreeSupport { class TreeModelVolumes; +class InterfacePlacer; +struct TreeSupportSettings; // Organic specific: Smooth branches and produce one cummulative mesh to be sliced. void organic_draw_branches( diff --git a/src/libslic3r/Support/SupportCommon.cpp b/src/libslic3r/Support/SupportCommon.cpp index d82b79a..073c5eb 100644 --- a/src/libslic3r/Support/SupportCommon.cpp +++ b/src/libslic3r/Support/SupportCommon.cpp @@ -1,21 +1,39 @@ -#include "../ClipperUtils.hpp" -#include "../ClipperZUtils.hpp" -#include "../ExtrusionEntityCollection.hpp" -#include "../Layer.hpp" -#include "../Print.hpp" -#include "../Fill/FillBase.hpp" -#include "../MutablePolygon.hpp" -#include "../Geometry.hpp" -#include "../Point.hpp" - -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include -#include - +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/ClipperZUtils.hpp" // IWYU pragma: keep +#include "libslic3r/ExtrusionEntityCollection.hpp" +#include "libslic3r/Layer.hpp" +#include "libslic3r/Print.hpp" +#include "libslic3r/Fill/FillBase.hpp" +#include "libslic3r/MutablePolygon.hpp" +#include "libslic3r/Geometry.hpp" +#include "libslic3r/Point.hpp" #include "SupportCommon.hpp" #include "SupportLayer.hpp" #include "SupportParameters.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/ExtrusionEntity.hpp" +#include "libslic3r/ExtrusionRole.hpp" +#include "libslic3r/Flow.hpp" +#include "libslic3r/LayerRegion.hpp" +#include "libslic3r/MultiMaterialSegmentation.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/Polyline.hpp" +#include "libslic3r/Slicing.hpp" +#include "libslic3r/Surface.hpp" +#include "libslic3r/Utils.hpp" +#include "libslic3r/libslic3r.h" // #define SLIC3R_DEBUG @@ -613,18 +631,19 @@ static inline void fill_expolygons_generate_paths( fill_expolygons_generate_paths(dst, std::move(expolygons), filler, fill_params, density, role, flow); } -static Polylines draw_perimeters(const ExPolygon &expoly, double clip_length) -{ +static Polylines draw_perimeters(const ExPolygon &expoly, double clip_length, const bool prefer_clockwise_movements) { // Draw the perimeters. Polylines polylines; polylines.reserve(expoly.holes.size() + 1); for (size_t i = 0; i <= expoly.holes.size(); ++ i) { Polyline pl(i == 0 ? expoly.contour.points : expoly.holes[i - 1].points); pl.points.emplace_back(pl.points.front()); - if (i > 0) - // It is a hole, reverse it. + + // When prefer_clockwise_movements is true ensure that all loops are CW oriented (reverse contour), + // otherwise ensure that all loops are CCW oriented (reverse loops). + if (const bool loop_reverse = prefer_clockwise_movements ? i == 0 : i > 0; loop_reverse) pl.reverse(); - // so that all contours are CCW oriented. + pl.clip_end(clip_length); polylines.emplace_back(std::move(pl)); } @@ -741,7 +760,7 @@ static inline void tree_supports_generate_paths( ExPolygons level2 = offset2_ex({ expoly }, -1.5 * flow.scaled_width(), 0.5 * flow.scaled_width()); if (level2.size() == 1) { Polylines polylines; - extrusion_entities_append_paths(eec->entities, draw_perimeters(expoly, clip_length), { ExtrusionRole::SupportMaterial, flow }, + extrusion_entities_append_paths(eec->entities, draw_perimeters(expoly, clip_length, support_params.prefer_clockwise_movements), { ExtrusionRole::SupportMaterial, flow }, // Disable reversal of the path, always start with the anchor, always print CCW. false); expoly = level2.front(); @@ -753,10 +772,14 @@ static inline void tree_supports_generate_paths( // The anchor candidate points are annotated with an index of the source contour or with -1 if on intersection. anchor_candidates.clear(); shrink_expolygon_with_contour_idx(expoly, flow.scaled_width(), DefaultJoinType, 1.2, anchor_candidates); - // Orient all contours CW. - for (auto &path : anchor_candidates) - if (ClipperLib_Z::Area(path) > 0) + + // Based on the way how loops with anchors are constructed (we reverse orientation at the end), + // orient all loops CCW when prefer_clockwise_movements is true and orient all loops CW otherwise. + for (auto &path : anchor_candidates) { + const bool is_ccw = ClipperLib_Z::Area(path) > 0; + if (const bool path_reverse = support_params.prefer_clockwise_movements ? !is_ccw : is_ccw; path_reverse) std::reverse(path.begin(), path.end()); + } // Draw the perimeters. Polylines polylines; @@ -765,10 +788,12 @@ static inline void tree_supports_generate_paths( // Open the loop with a seam. const Polygon &loop = expoly.contour_or_hole(idx_loop); Polyline pl(loop.points); - // Orient all contours CW, because the anchor will be added to the end of polyline while we want to start a loop with the anchor. - if (idx_loop == 0) - // It is an outer contour. + + // When prefer_clockwise_movements is true, orient all loops CCW and orient all loops CW otherwise + // because the anchor will be added to the end of the polyline while we want to start a loop with the anchor. + if (const bool loop_reverse = support_params.prefer_clockwise_movements ? idx_loop != 0 : idx_loop == 0; loop_reverse) pl.reverse(); + pl.points.emplace_back(pl.points.front()); pl.clip_end(clip_length); if (pl.size() < 2) @@ -861,11 +886,12 @@ static inline void fill_expolygons_with_sheath_generate_paths( ExtrusionEntitiesPtr &dst, const Polygons &polygons, Fill *filler, - float density, - ExtrusionRole role, + const float density, + const ExtrusionRole role, const Flow &flow, - bool with_sheath, - bool no_sort) + const bool with_sheath, + const bool no_sort, + const bool prefer_clockwise_movements) { if (polygons.empty()) return; @@ -891,7 +917,7 @@ static inline void fill_expolygons_with_sheath_generate_paths( eec->no_sort = true; } ExtrusionEntitiesPtr &out = no_sort ? eec->entities : dst; - extrusion_entities_append_paths(out, draw_perimeters(expoly, clip_length), { ExtrusionRole::SupportMaterial, flow }); + extrusion_entities_append_paths(out, draw_perimeters(expoly, clip_length, prefer_clockwise_movements), { ExtrusionRole::SupportMaterial, flow }, false); // Fill in the rest. fill_expolygons_generate_paths(out, offset_ex(expoly, float(-0.4 * spacing)), filler, fill_params, density, role, flow); if (no_sort && ! eec->empty()) @@ -1250,7 +1276,6 @@ static void modulate_extrusion_by_overlapping_layers( struct ExtrusionPathFragment { ExtrusionFlow flow; - Polylines polylines; }; @@ -1621,7 +1646,7 @@ void generate_support_toolpaths( filler, float(support_params.support_density), // Extrusion parameters ExtrusionRole::SupportMaterial, flow, - support_params.with_sheath, false); + support_params.with_sheath, false, support_params.prefer_clockwise_movements); } if (! tree_polygons.empty()) tree_supports_generate_paths(support_layer.support_fills.entities, tree_polygons, flow, support_params); @@ -1656,7 +1681,7 @@ void generate_support_toolpaths( // Extrusion parameters (support_layer_id < slicing_params.base_raft_layers) ? ExtrusionRole::SupportMaterial : ExtrusionRole::SupportMaterialInterface, flow, // sheath at first layer - support_layer_id == 0, support_layer_id == 0); + support_layer_id == 0, support_layer_id == 0, support_params.prefer_clockwise_movements); } }); @@ -1897,7 +1922,7 @@ void generate_support_toolpaths( filler, density, // Extrusion parameters ExtrusionRole::SupportMaterial, flow, - sheath, no_sort); + sheath, no_sort, support_params.prefer_clockwise_movements); } // Merge base_interface_layers to base_layers to avoid unneccessary retractions diff --git a/src/libslic3r/Support/SupportCommon.hpp b/src/libslic3r/Support/SupportCommon.hpp index 16e8102..4de5a0e 100644 --- a/src/libslic3r/Support/SupportCommon.hpp +++ b/src/libslic3r/Support/SupportCommon.hpp @@ -1,16 +1,24 @@ #ifndef slic3r_SupportCommon_hpp_ #define slic3r_SupportCommon_hpp_ +#include +#include +#include + #include "../Layer.hpp" #include "../Polygon.hpp" #include "../Print.hpp" #include "SupportLayer.hpp" #include "SupportParameters.hpp" +#include "libslic3r/PrintConfig.hpp" namespace Slic3r { class PrintObject; class SupportLayer; +class Layer; +class LayerRegion; +struct SlicingParameters; namespace FFFSupport { diff --git a/src/libslic3r/Support/SupportDebug.cpp b/src/libslic3r/Support/SupportDebug.cpp index 5c18bc7..9e2524d 100644 --- a/src/libslic3r/Support/SupportDebug.cpp +++ b/src/libslic3r/Support/SupportDebug.cpp @@ -1,10 +1,15 @@ #if 1 //#ifdef SLIC3R_DEBUG +#include + #include "../ClipperUtils.hpp" #include "../SVG.hpp" #include "../Layer.hpp" - #include "SupportLayer.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r::FFFSupport { diff --git a/src/libslic3r/Support/SupportLayer.hpp b/src/libslic3r/Support/SupportLayer.hpp index a3c60b9..05f85b5 100644 --- a/src/libslic3r/Support/SupportLayer.hpp +++ b/src/libslic3r/Support/SupportLayer.hpp @@ -4,9 +4,9 @@ #include #include // for Slic3r::deque -#include "../libslic3r.h" -#include "../ClipperUtils.hpp" -#include "../Polygon.hpp" +#include "libslic3r/libslic3r.h" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/Polygon.hpp" namespace Slic3r::FFFSupport { diff --git a/src/libslic3r/Support/SupportMaterial.cpp b/src/libslic3r/Support/SupportMaterial.cpp index 2882117..478ffe9 100644 --- a/src/libslic3r/Support/SupportMaterial.cpp +++ b/src/libslic3r/Support/SupportMaterial.cpp @@ -1,24 +1,44 @@ -#include "../ClipperUtils.hpp" -#include "../ExtrusionEntityCollection.hpp" -#include "../Layer.hpp" -#include "../Print.hpp" -#include "../Fill/FillBase.hpp" -#include "../Geometry.hpp" -#include "../Point.hpp" -#include "../MutablePolygon.hpp" - -#include "Support/SupportCommon.hpp" -#include "SupportMaterial.hpp" - -#include - +#include +#include +#include +#include #include #include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include -#include -#include +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/ExtrusionEntityCollection.hpp" +#include "libslic3r/Layer.hpp" +#include "libslic3r/Print.hpp" +#include "libslic3r/Geometry.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/MutablePolygon.hpp" +#include "libslic3r/Support/SupportCommon.hpp" +#include "SupportMaterial.hpp" +#include "agg/agg_renderer_base.h" +#include "agg/agg_rendering_buffer.h" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/ExtrusionEntity.hpp" +#include "libslic3r/ExtrusionRole.hpp" +#include "libslic3r/Flow.hpp" +#include "libslic3r/LayerRegion.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/MultiMaterialSegmentation.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/Slicing.hpp" +#include "libslic3r/Support/SupportLayer.hpp" +#include "libslic3r/Support/SupportParameters.hpp" +#include "libslic3r/Surface.hpp" +#include "libslic3r/TriangleSelector.hpp" +#include "tcbspan/span.hpp" #define SUPPORT_USE_AGG_RASTERIZER @@ -28,7 +48,6 @@ #include #include #include - #include "PNGReadWrite.hpp" #else #include "EdgeGrid.hpp" #endif // SUPPORT_USE_AGG_RASTERIZER @@ -1094,8 +1113,8 @@ struct SupportAnnotations buildplate_covered(buildplate_covered) { // Append custom supports. - object.project_and_append_custom_facets(false, EnforcerBlockerType::ENFORCER, enforcers_layers); - object.project_and_append_custom_facets(false, EnforcerBlockerType::BLOCKER, blockers_layers); + object.project_and_append_custom_facets(false, TriangleStateType::ENFORCER, enforcers_layers); + object.project_and_append_custom_facets(false, TriangleStateType::BLOCKER, blockers_layers); } std::vector enforcers_layers; diff --git a/src/libslic3r/Support/SupportMaterial.hpp b/src/libslic3r/Support/SupportMaterial.hpp index fda4fa0..a301213 100644 --- a/src/libslic3r/Support/SupportMaterial.hpp +++ b/src/libslic3r/Support/SupportMaterial.hpp @@ -1,12 +1,15 @@ #ifndef slic3r_SupportMaterial_hpp_ #define slic3r_SupportMaterial_hpp_ +#include + #include "../Flow.hpp" #include "../PrintConfig.hpp" #include "../Slicing.hpp" - #include "SupportLayer.hpp" #include "SupportParameters.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { diff --git a/src/libslic3r/Support/SupportParameters.cpp b/src/libslic3r/Support/SupportParameters.cpp index 09eca96..1382395 100644 --- a/src/libslic3r/Support/SupportParameters.cpp +++ b/src/libslic3r/Support/SupportParameters.cpp @@ -1,7 +1,12 @@ +#include +#include + #include "../Print.hpp" #include "../PrintConfig.hpp" #include "../Slicing.hpp" #include "SupportParameters.hpp" +#include "libslic3r/Geometry.hpp" +#include "libslic3r/Point.hpp" namespace Slic3r::FFFSupport { @@ -139,6 +144,8 @@ SupportParameters::SupportParameters(const PrintObject &object) } this->tree_branch_diameter_double_wall_area_scaled = 0.25 * sqr(scaled(object_config.support_tree_branch_diameter_double_wall.value)) * M_PI; + + this->prefer_clockwise_movements = print_config.prefer_clockwise_movements; } } // namespace Slic3r diff --git a/src/libslic3r/Support/SupportParameters.hpp b/src/libslic3r/Support/SupportParameters.hpp index 8a63d9f..ae0cbba 100644 --- a/src/libslic3r/Support/SupportParameters.hpp +++ b/src/libslic3r/Support/SupportParameters.hpp @@ -1,12 +1,18 @@ #ifndef slic3r_SupportParameters_hpp_ #define slic3r_SupportParameters_hpp_ -#include "../libslic3r.h" -#include "../Flow.hpp" +#include +#include +#include +#include + +#include "libslic3r/libslic3r.h" +#include "libslic3r/Flow.hpp" namespace Slic3r { class PrintObject; + enum InfillPattern : int; namespace FFFSupport { @@ -84,6 +90,9 @@ struct SupportParameters { float raft_angle_base; float raft_angle_interface; + // Print closed loop clockwise when it is equal to true. + bool prefer_clockwise_movements; + // Produce a raft interface angle for a given SupportLayer::interface_id() float raft_interface_angle(size_t interface_id) const { return this->raft_angle_interface + ((interface_id & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.)); } diff --git a/src/libslic3r/Support/TreeModelVolumes.cpp b/src/libslic3r/Support/TreeModelVolumes.cpp index d774003..7a1505d 100644 --- a/src/libslic3r/Support/TreeModelVolumes.cpp +++ b/src/libslic3r/Support/TreeModelVolumes.cpp @@ -7,24 +7,31 @@ // CuraEngine is released under the terms of the AGPLv3 or higher. #include "TreeModelVolumes.hpp" -#include "TreeSupportCommon.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "TreeSupportCommon.hpp" #include "../BuildVolume.hpp" -#include "../ClipperUtils.hpp" -#include "../Flow.hpp" #include "../Layer.hpp" #include "../Point.hpp" #include "../Print.hpp" -#include "../PrintConfig.hpp" #include "../Utils.hpp" #include "../format.hpp" - -#include - -#include - -#include -#include +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Polygon.hpp" namespace Slic3r::FFFTreeSupport { diff --git a/src/libslic3r/Support/TreeModelVolumes.hpp b/src/libslic3r/Support/TreeModelVolumes.hpp index 9269e63..f23b538 100644 --- a/src/libslic3r/Support/TreeModelVolumes.hpp +++ b/src/libslic3r/Support/TreeModelVolumes.hpp @@ -9,16 +9,26 @@ #ifndef slic3r_TreeModelVolumes_hpp #define slic3r_TreeModelVolumes_hpp +#include +#include +#include +#include #include #include - -#include +#include +#include +#include +#include +#include +#include +#include +#include #include "TreeSupportCommon.hpp" - #include "../Point.hpp" #include "../Polygon.hpp" #include "../PrintConfig.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { @@ -240,6 +250,7 @@ private: */ std::optional> getArea(const TreeModelVolumes::RadiusLayerPair &key) const { std::lock_guard guard(m_mutex); + if (key.second >= LayerIndex(m_data.size())) return std::nullopt; diff --git a/src/libslic3r/Support/TreeSupport.cpp b/src/libslic3r/Support/TreeSupport.cpp index 260880a..34cd8af 100644 --- a/src/libslic3r/Support/TreeSupport.cpp +++ b/src/libslic3r/Support/TreeSupport.cpp @@ -1,11 +1,30 @@ #include "TreeSupport.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "TreeSupportCommon.hpp" #include "SupportCommon.hpp" #include "OrganicSupport.hpp" - #include "../AABBTreeIndirect.hpp" #include "../BuildVolume.hpp" -#include "../ClipperUtils.hpp" #include "../EdgeGrid.hpp" #include "../Layer.hpp" #include "../Print.hpp" @@ -13,18 +32,21 @@ #include "../Polygon.hpp" #include "../Polyline.hpp" #include "../MutablePolygon.hpp" - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Fill/FillBase.hpp" +#include "libslic3r/Flow.hpp" +#include "libslic3r/LayerRegion.hpp" +#include "libslic3r/MultiMaterialSegmentation.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/Support/SupportLayer.hpp" +#include "libslic3r/Support/SupportParameters.hpp" +#include "libslic3r/Support/TreeModelVolumes.hpp" +#include "libslic3r/Surface.hpp" +#include "libslic3r/TriangleSelector.hpp" +#include "libslic3r/Utils.hpp" // #define TREESUPPORT_DEBUG_SVG @@ -195,8 +217,8 @@ static std::vector>> group_me const int support_enforce_layers = config.support_material_enforce_layers.value; std::vector enforcers_layers{ print_object.slice_support_enforcers() }; std::vector blockers_layers{ print_object.slice_support_blockers() }; - print_object.project_and_append_custom_facets(false, EnforcerBlockerType::ENFORCER, enforcers_layers); - print_object.project_and_append_custom_facets(false, EnforcerBlockerType::BLOCKER, blockers_layers); + print_object.project_and_append_custom_facets(false, TriangleStateType::ENFORCER, enforcers_layers); + print_object.project_and_append_custom_facets(false, TriangleStateType::BLOCKER, blockers_layers); const int support_threshold = config.support_material_threshold.value; const bool support_threshold_auto = support_threshold == 0; // +1 makes the threshold inclusive diff --git a/src/libslic3r/Support/TreeSupport.hpp b/src/libslic3r/Support/TreeSupport.hpp index c79f1d4..25293fd 100644 --- a/src/libslic3r/Support/TreeSupport.hpp +++ b/src/libslic3r/Support/TreeSupport.hpp @@ -9,15 +9,26 @@ #ifndef slic3r_TreeSupport_hpp #define slic3r_TreeSupport_hpp +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "SupportLayer.hpp" #include "TreeModelVolumes.hpp" #include "TreeSupportCommon.hpp" - #include "../BoundingBox.hpp" #include "../Point.hpp" #include "../Utils.hpp" - -#include +#include "libslic3r/Polygon.hpp" +#include "libslic3r/libslic3r.h" // #define TREE_SUPPORT_SHOW_ERRORS diff --git a/src/libslic3r/Support/TreeSupportCommon.cpp b/src/libslic3r/Support/TreeSupportCommon.cpp index 7303919..1184d22 100644 --- a/src/libslic3r/Support/TreeSupportCommon.cpp +++ b/src/libslic3r/Support/TreeSupportCommon.cpp @@ -8,6 +8,13 @@ #include "TreeSupportCommon.hpp" +#include +#include + +#include "libslic3r/Flow.hpp" +#include "libslic3r/Print.hpp" +#include "libslic3r/Utils.hpp" + namespace Slic3r::FFFTreeSupport { TreeSupportMeshGroupSettings::TreeSupportMeshGroupSettings(const PrintObject &print_object) diff --git a/src/libslic3r/Support/TreeSupportCommon.hpp b/src/libslic3r/Support/TreeSupportCommon.hpp index b0d2245..6bd07e0 100644 --- a/src/libslic3r/Support/TreeSupportCommon.hpp +++ b/src/libslic3r/Support/TreeSupportCommon.hpp @@ -9,11 +9,31 @@ #ifndef slic3r_TreeSupportCommon_hpp #define slic3r_TreeSupportCommon_hpp -#include "../libslic3r.h" -#include "../Polygon.hpp" -#include "SupportCommon.hpp" - +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include + +#include "libslic3r/libslic3r.h" +#include "libslic3r/Polygon.hpp" +#include "SupportCommon.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/Slicing.hpp" +#include "libslic3r/Support/SupportLayer.hpp" +#include "libslic3r/Support/SupportParameters.hpp" + +namespace Slic3r { +class PrintObject; +} // namespace Slic3r using namespace Slic3r::FFFSupport; @@ -588,4 +608,4 @@ private: } // namespace Slic3r -#endif // slic3r_TreeSupportCommon_hpp \ No newline at end of file +#endif // slic3r_TreeSupportCommon_hpp diff --git a/src/libslic3r/SupportSpotsGenerator.cpp b/src/libslic3r/SupportSpotsGenerator.cpp index 7511f60..5802d01 100644 --- a/src/libslic3r/SupportSpotsGenerator.cpp +++ b/src/libslic3r/SupportSpotsGenerator.cpp @@ -1,10 +1,26 @@ #include "SupportSpotsGenerator.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "BoundingBox.hpp" #include "ExPolygon.hpp" #include "ExtrusionEntity.hpp" #include "ExtrusionEntityCollection.hpp" -#include "GCode/ExtrusionProcessor.hpp" +#include "libslic3r/GCode/ExtrusionProcessor.hpp" #include "Line.hpp" #include "Point.hpp" #include "Polygon.hpp" @@ -12,42 +28,24 @@ #include "Print.hpp" #include "PrintBase.hpp" #include "PrintConfig.hpp" -#include "Tesselate.hpp" -#include "Utils.hpp" #include "libslic3r.h" -#include "tbb/parallel_for.h" -#include "tbb/blocked_range.h" -#include "tbb/blocked_range2d.h" -#include "tbb/parallel_reduce.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - #include "AABBTreeLines.hpp" #include "KDTreeIndirect.hpp" #include "libslic3r/Layer.hpp" #include "libslic3r/ClipperUtils.hpp" #include "Geometry/ConvexHull.hpp" +#include "libslic3r/ExtrusionRole.hpp" +#include "libslic3r/Flow.hpp" +#include "libslic3r/LayerRegion.hpp" // #define DETAILED_DEBUG_LOGS // #define DEBUG_FILES #ifdef DEBUG_FILES #include + #include "libslic3r/Color.hpp" + constexpr bool debug_files = true; #else constexpr bool debug_files = false; @@ -58,19 +56,18 @@ namespace Slic3r::SupportSpotsGenerator { ExtrusionLine::ExtrusionLine() : a(Vec2f::Zero()), b(Vec2f::Zero()), len(0.0), origin_entity(nullptr) {} ExtrusionLine::ExtrusionLine(const Vec2f &a, const Vec2f &b, float len, const ExtrusionEntity *origin_entity) - : a(a), b(b), len(len), origin_entity(origin_entity) - {} + : a(a), b(b), len(len), origin_entity(origin_entity) +{} ExtrusionLine::ExtrusionLine(const Vec2f &a, const Vec2f &b) - : a(a), b(b), len((a-b).norm()), origin_entity(nullptr) - {} + : a(a), b(b), len((a-b).norm()), origin_entity(nullptr) +{} bool ExtrusionLine::is_external_perimeter() const - { - assert(origin_entity != nullptr); - return origin_entity->role().is_external_perimeter(); - } - +{ + assert(origin_entity != nullptr); + return origin_entity->role().is_external_perimeter(); +} using LD = AABBTreeLines::LinesDistancer; @@ -131,24 +128,25 @@ public: }; void SliceConnection::add(const SliceConnection &other) - { - this->area += other.area; - this->centroid_accumulator += other.centroid_accumulator; - this->second_moment_of_area_accumulator += other.second_moment_of_area_accumulator; - this->second_moment_of_area_covariance_accumulator += other.second_moment_of_area_covariance_accumulator; - } +{ + this->area += other.area; + this->centroid_accumulator += other.centroid_accumulator; + this->second_moment_of_area_accumulator += other.second_moment_of_area_accumulator; + this->second_moment_of_area_covariance_accumulator += other.second_moment_of_area_covariance_accumulator; +} void SliceConnection::print_info(const std::string &tag) const - { - Vec3f centroid = centroid_accumulator / area; - Vec2f variance = (second_moment_of_area_accumulator / area - centroid.head<2>().cwiseProduct(centroid.head<2>())); - float covariance = second_moment_of_area_covariance_accumulator / area - centroid.x() * centroid.y(); - std::cout << tag << std::endl; - std::cout << "area: " << area << std::endl; - std::cout << "centroid: " << centroid.x() << " " << centroid.y() << " " << centroid.z() << std::endl; - std::cout << "variance: " << variance.x() << " " << variance.y() << std::endl; - std::cout << "covariance: " << covariance << std::endl; - } +{ + Vec3f centroid = centroid_accumulator / area; + Vec2f variance = (second_moment_of_area_accumulator / area - centroid.head<2>().cwiseProduct(centroid.head<2>())); + float covariance = second_moment_of_area_covariance_accumulator / area - centroid.x() * centroid.y(); + std::cout << tag << std::endl; + std::cout << "area: " << area << std::endl; + std::cout << "centroid: " << centroid.x() << " " << centroid.y() << " " << centroid.z() << std::endl; + std::cout << "variance: " << variance.x() << " " << variance.y() << std::endl; + std::cout << "covariance: " << covariance << std::endl; +} + Integrals::Integrals(const Polygon &polygon) { if (polygon.points.size() < 3) { @@ -156,22 +154,23 @@ Integrals::Integrals(const Polygon &polygon) *this = Integrals{}; return; } - Vec2f p0 = unscaled(polygon.first_point()).cast(); - for (size_t i = 2; i < polygon.points.size(); i++) { - Vec2f p1 = unscaled(polygon.points[i - 1]).cast(); - Vec2f p2 = unscaled(polygon.points[i]).cast(); + Vec2f p0 = unscaled(polygon.first_point()).cast(); + for (size_t i = 2; i < polygon.points.size(); i++) { + Vec2f p1 = unscaled(polygon.points[i - 1]).cast(); + Vec2f p2 = unscaled(polygon.points[i]).cast(); - float sign = cross2(p1 - p0, p2 - p1) > 0 ? 1.0f : -1.0f; + float sign = cross2(p1 - p0, p2 - p1) > 0 ? 1.0f : -1.0f; - auto [area, first_moment_of_area, second_moment_area, - second_moment_of_area_covariance] = compute_moments_of_area_of_triangle(p0, p1, p2); + auto [area, first_moment_of_area, second_moment_area, + second_moment_of_area_covariance] = compute_moments_of_area_of_triangle(p0, p1, p2); - this->area += sign * area; - this->x_i += sign * first_moment_of_area; - this->x_i_squared += sign * second_moment_area; - this->xy += sign * second_moment_of_area_covariance; - } + this->area += sign * area; + this->x_i += sign * first_moment_of_area; + this->x_i_squared += sign * second_moment_area; + this->xy += sign * second_moment_of_area_covariance; } +} + Integrals::Integrals(const Polygons &polygons) { for (const Polygon &polygon : polygons) { @@ -280,7 +279,6 @@ float get_flow_width(const LayerRegion *region, ExtrusionRole role) return region->flow(FlowRole::frPerimeter).width(); } - float estimate_curled_up_height( float distance, float curvature, float layer_height, float flow_width, float prev_line_curled_height, Params params) { @@ -332,10 +330,17 @@ std::vector check_extrusion_entity_stability(const ExtrusionEntit if (entity->length() < scale_(params.min_distance_to_allow_local_supports)) { return {}; } - const float flow_width = get_flow_width(layer_region, entity->role()); + const float flow_width = get_flow_width(layer_region, entity->role()); + + ExtrusionProcessor::PropertiesEstimationConfig config{}; + config.add_corners = true; + config.prev_layer_boundary_offset = true; + config.max_line_length = params.bridge_distance; + config.flow_width = flow_width; + std::vector annotated_points = - ExtrusionProcessor::estimate_points_properties(entity->as_polyline().points, prev_layer_boundary, - flow_width, params.bridge_distance); + ExtrusionProcessor::estimate_points_properties< + true>(entity->as_polyline().points, prev_layer_boundary, config); std::vector lines_out; lines_out.reserve(annotated_points.size()); @@ -387,9 +392,12 @@ std::vector check_extrusion_entity_stability(const ExtrusionEntit const float flow_width = get_flow_width(layer_region, entity->role()); // Compute only unsigned distance - prev_layer_lines can contain unconnected paths, thus the sign of the distance is unreliable + + ExtrusionProcessor::PropertiesEstimationConfig config{}; + config.max_line_length = params.bridge_distance; + config.flow_width = flow_width; std::vector annotated_points = - ExtrusionProcessor::estimate_points_properties(entity->as_polyline().points, prev_layer_lines, - flow_width, params.bridge_distance); + ExtrusionProcessor::estimate_points_properties(entity->as_polyline().points, prev_layer_lines, config); std::vector lines_out; lines_out.reserve(annotated_points.size()); @@ -398,8 +406,8 @@ std::vector check_extrusion_entity_stability(const ExtrusionEntit for (size_t i = 0; i < annotated_points.size(); ++i) { ExtrusionProcessor::ExtendedPoint &curr_point = annotated_points[i]; const ExtrusionProcessor::ExtendedPoint &prev_point = i > 0 ? annotated_points[i - 1] : annotated_points[i]; - float line_len = (prev_point.position - curr_point.position).norm(); - ExtrusionLine line_out{prev_point.position.cast(), curr_point.position.cast(), line_len, entity}; + float line_len = (prev_point.position - curr_point.position).norm(); + ExtrusionLine line_out{prev_point.position.cast(), curr_point.position.cast(), line_len, entity}; Vec2f middle = 0.5 * (line_out.a + line_out.b); auto [middle_distance, bottom_line_idx, x] = prev_layer_lines.distance_from_lines_extra(middle); @@ -545,18 +553,18 @@ ObjectPart::ObjectPart( } const Integrals integrals{polylines, widths}; - const float volume = integrals.area * layer_height; - this->volume += volume; - this->volume_centroid_accumulator += to_3d(integrals.x_i, center_z * integrals.area) / integrals.area * volume; + const float volume = integrals.area * layer_height; + this->volume += volume; + this->volume_centroid_accumulator += to_3d(integrals.x_i, center_z * integrals.area) / integrals.area * volume; - if (this->connected_to_bed) { - this->sticking_area += integrals.area; - this->sticking_centroid_accumulator += to_3d(integrals.x_i, bottom_z * integrals.area); - this->sticking_second_moment_of_area_accumulator += integrals.x_i_squared; - this->sticking_second_moment_of_area_covariance_accumulator += integrals.xy; + if (this->connected_to_bed) { + this->sticking_area += integrals.area; + this->sticking_centroid_accumulator += to_3d(integrals.x_i, bottom_z * integrals.area); + this->sticking_second_moment_of_area_accumulator += integrals.x_i_squared; + this->sticking_second_moment_of_area_covariance_accumulator += integrals.xy; + } } } - } if (brim) { Integrals integrals{*brim}; @@ -568,28 +576,28 @@ ObjectPart::ObjectPart( } void ObjectPart::add(const ObjectPart &other) - { - this->connected_to_bed = this->connected_to_bed || other.connected_to_bed; - this->volume_centroid_accumulator += other.volume_centroid_accumulator; - this->volume += other.volume; - this->sticking_area += other.sticking_area; - this->sticking_centroid_accumulator += other.sticking_centroid_accumulator; - this->sticking_second_moment_of_area_accumulator += other.sticking_second_moment_of_area_accumulator; - this->sticking_second_moment_of_area_covariance_accumulator += other.sticking_second_moment_of_area_covariance_accumulator; - } +{ + this->connected_to_bed = this->connected_to_bed || other.connected_to_bed; + this->volume_centroid_accumulator += other.volume_centroid_accumulator; + this->volume += other.volume; + this->sticking_area += other.sticking_area; + this->sticking_centroid_accumulator += other.sticking_centroid_accumulator; + this->sticking_second_moment_of_area_accumulator += other.sticking_second_moment_of_area_accumulator; + this->sticking_second_moment_of_area_covariance_accumulator += other.sticking_second_moment_of_area_covariance_accumulator; +} void ObjectPart::add_support_point(const Vec3f &position, float sticking_area) - { - this->sticking_area += sticking_area; - this->sticking_centroid_accumulator += sticking_area * position; - this->sticking_second_moment_of_area_accumulator += sticking_area * position.head<2>().cwiseProduct(position.head<2>()); - this->sticking_second_moment_of_area_covariance_accumulator += sticking_area * position.x() * position.y(); - } +{ + this->sticking_area += sticking_area; + this->sticking_centroid_accumulator += sticking_area * position; + this->sticking_second_moment_of_area_accumulator += sticking_area * position.head<2>().cwiseProduct(position.head<2>()); + this->sticking_second_moment_of_area_covariance_accumulator += sticking_area * position.x() * position.y(); +} float ObjectPart::compute_elastic_section_modulus( const Vec2f &line_dir, - const Vec3f &extreme_point, + const Vec3f &extreme_point, const Integrals& integrals ) const { float second_moment_of_area = compute_second_moment(integrals, Vec2f{-line_dir.y(), line_dir.x()}); @@ -597,95 +605,99 @@ float ObjectPart::compute_elastic_section_modulus( if (second_moment_of_area < EPSILON) { return 0.0f; } Vec2f centroid = integrals.x_i / integrals.area; - float extreme_fiber_dist = line_alg::distance_to(Linef(centroid.head<2>().cast(), - (centroid.head<2>() + Vec2f(line_dir.y(), -line_dir.x())).cast()), - extreme_point.head<2>().cast()); + float extreme_fiber_dist = line_alg::distance_to(Linef(centroid.head<2>().cast(), + (centroid.head<2>() + Vec2f(line_dir.y(), -line_dir.x())).cast()), + extreme_point.head<2>().cast()); + float elastic_section_modulus = second_moment_of_area / extreme_fiber_dist; #ifdef DETAILED_DEBUG_LOGS - BOOST_LOG_TRIVIAL(debug) << "extreme_fiber_dist: " << extreme_fiber_dist; - BOOST_LOG_TRIVIAL(debug) << "elastic_section_modulus: " << elastic_section_modulus; + BOOST_LOG_TRIVIAL(debug) << "extreme_fiber_dist: " << extreme_fiber_dist; + BOOST_LOG_TRIVIAL(debug) << "elastic_section_modulus: " << elastic_section_modulus; #endif - return elastic_section_modulus; - } + return elastic_section_modulus; +} std::tuple ObjectPart::is_stable_while_extruding(const SliceConnection &connection, - const ExtrusionLine &extruded_line, - const Vec3f &extreme_point, - float layer_z, - const Params ¶ms) const - { + const ExtrusionLine &extruded_line, + const Vec3f &extreme_point, + float layer_z, + const Params ¶ms) const +{ // Note that exteme point is calculated for the current layer, while it should // be computed for the first layer. The shape of the first layer however changes a lot, // during support points additions (for organic supports it is not even clear how) // and during merging. Using the current layer is heuristics and also small optimization, // as the AABB tree for it is calculated anyways. This heuristic should usually be // on the safe side. - Vec2f line_dir = (extruded_line.b - extruded_line.a).normalized(); - const Vec3f &mass_centroid = this->volume_centroid_accumulator / this->volume; - float mass = this->volume * params.filament_density; - float weight = mass * params.gravity_constant; + Vec2f line_dir = (extruded_line.b - extruded_line.a).normalized(); + const Vec3f &mass_centroid = this->volume_centroid_accumulator / this->volume; + float mass = this->volume * params.filament_density; + float weight = mass * params.gravity_constant; - float movement_force = params.max_acceleration * mass; + float movement_force = params.max_acceleration * mass; - float extruder_conflict_force = params.standard_extruder_conflict_force + - std::min(extruded_line.curled_up_height, 1.0f) * params.malformations_additive_conflict_extruder_force; + float extruder_conflict_force = params.standard_extruder_conflict_force + + std::min(extruded_line.curled_up_height, 1.0f) * params.malformations_additive_conflict_extruder_force; - // section for bed calculations - { - if (this->sticking_area < EPSILON) return {1.0f, SupportPointCause::UnstableFloatingPart}; + // section for bed calculations + { + if (this->sticking_area < EPSILON) return {1.0f, SupportPointCause::UnstableFloatingPart}; Integrals integrals; integrals.area = this->sticking_area; integrals.x_i = this->sticking_centroid_accumulator.head<2>(); integrals.x_i_squared = this->sticking_second_moment_of_area_accumulator; integrals.xy = this->sticking_second_moment_of_area_covariance_accumulator; - Vec3f bed_centroid = this->sticking_centroid_accumulator / this->sticking_area; + + Vec3f bed_centroid = this->sticking_centroid_accumulator / this->sticking_area; float bed_yield_torque = -compute_elastic_section_modulus(line_dir, extreme_point, integrals) * params.get_bed_adhesion_yield_strength(); - Vec2f bed_weight_arm = (mass_centroid.head<2>() - bed_centroid.head<2>()); - float bed_weight_arm_len = bed_weight_arm.norm(); + Vec2f bed_weight_arm = (mass_centroid.head<2>() - bed_centroid.head<2>()); + float bed_weight_arm_len = bed_weight_arm.norm(); + float bed_weight_dir_xy_variance = compute_second_moment(integrals, {-bed_weight_arm.y(), bed_weight_arm.x()}) / this->sticking_area; - float bed_weight_sign = bed_weight_arm_len < 2.0f * sqrt(bed_weight_dir_xy_variance) ? -1.0f : 1.0f; - float bed_weight_torque = bed_weight_sign * bed_weight_arm_len * weight; + float bed_weight_sign = bed_weight_arm_len < 2.0f * sqrt(bed_weight_dir_xy_variance) ? -1.0f : 1.0f; + float bed_weight_torque = bed_weight_sign * bed_weight_arm_len * weight; - float bed_movement_arm = std::max(0.0f, mass_centroid.z() - bed_centroid.z()); - float bed_movement_torque = movement_force * bed_movement_arm; + float bed_movement_arm = std::max(0.0f, mass_centroid.z() - bed_centroid.z()); + float bed_movement_torque = movement_force * bed_movement_arm; - float bed_conflict_torque_arm = layer_z - bed_centroid.z(); - float bed_extruder_conflict_torque = extruder_conflict_force * bed_conflict_torque_arm; + float bed_conflict_torque_arm = layer_z - bed_centroid.z(); + float bed_extruder_conflict_torque = extruder_conflict_force * bed_conflict_torque_arm; - float bed_total_torque = bed_movement_torque + bed_extruder_conflict_torque + bed_weight_torque + bed_yield_torque; + float bed_total_torque = bed_movement_torque + bed_extruder_conflict_torque + bed_weight_torque + bed_yield_torque; #ifdef DETAILED_DEBUG_LOGS - BOOST_LOG_TRIVIAL(debug) << "bed_centroid: " << bed_centroid.x() << " " << bed_centroid.y() << " " << bed_centroid.z(); - BOOST_LOG_TRIVIAL(debug) << "SSG: bed_yield_torque: " << bed_yield_torque; - BOOST_LOG_TRIVIAL(debug) << "SSG: bed_weight_arm: " << bed_weight_arm_len; - BOOST_LOG_TRIVIAL(debug) << "SSG: bed_weight_torque: " << bed_weight_torque; - BOOST_LOG_TRIVIAL(debug) << "SSG: bed_movement_arm: " << bed_movement_arm; - BOOST_LOG_TRIVIAL(debug) << "SSG: bed_movement_torque: " << bed_movement_torque; - BOOST_LOG_TRIVIAL(debug) << "SSG: bed_conflict_torque_arm: " << bed_conflict_torque_arm; - BOOST_LOG_TRIVIAL(debug) << "SSG: extruded_line.curled_up_height: " << extruded_line.curled_up_height; - BOOST_LOG_TRIVIAL(debug) << "SSG: extruded_line.form_quality: " << extruded_line.form_quality; - BOOST_LOG_TRIVIAL(debug) << "SSG: extruder_conflict_force: " << extruder_conflict_force; - BOOST_LOG_TRIVIAL(debug) << "SSG: bed_extruder_conflict_torque: " << bed_extruder_conflict_torque; - BOOST_LOG_TRIVIAL(debug) << "SSG: total_torque: " << bed_total_torque << " layer_z: " << layer_z; + BOOST_LOG_TRIVIAL(debug) << "bed_centroid: " << bed_centroid.x() << " " << bed_centroid.y() << " " << bed_centroid.z(); + BOOST_LOG_TRIVIAL(debug) << "SSG: bed_yield_torque: " << bed_yield_torque; + BOOST_LOG_TRIVIAL(debug) << "SSG: bed_weight_arm: " << bed_weight_arm_len; + BOOST_LOG_TRIVIAL(debug) << "SSG: bed_weight_torque: " << bed_weight_torque; + BOOST_LOG_TRIVIAL(debug) << "SSG: bed_movement_arm: " << bed_movement_arm; + BOOST_LOG_TRIVIAL(debug) << "SSG: bed_movement_torque: " << bed_movement_torque; + BOOST_LOG_TRIVIAL(debug) << "SSG: bed_conflict_torque_arm: " << bed_conflict_torque_arm; + BOOST_LOG_TRIVIAL(debug) << "SSG: extruded_line.curled_up_height: " << extruded_line.curled_up_height; + BOOST_LOG_TRIVIAL(debug) << "SSG: extruded_line.form_quality: " << extruded_line.form_quality; + BOOST_LOG_TRIVIAL(debug) << "SSG: extruder_conflict_force: " << extruder_conflict_force; + BOOST_LOG_TRIVIAL(debug) << "SSG: bed_extruder_conflict_torque: " << bed_extruder_conflict_torque; + BOOST_LOG_TRIVIAL(debug) << "SSG: total_torque: " << bed_total_torque << " layer_z: " << layer_z; #endif - if (bed_total_torque > 0) { - return {bed_total_torque / bed_conflict_torque_arm, - (this->connected_to_bed ? SupportPointCause::SeparationFromBed : SupportPointCause::UnstableFloatingPart)}; - } + if (bed_total_torque > 0) { + return {bed_total_torque / bed_conflict_torque_arm, + (this->connected_to_bed ? SupportPointCause::SeparationFromBed : SupportPointCause::UnstableFloatingPart)}; } + } - // section for weak connection calculations - { - if (connection.area < EPSILON) return {1.0f, SupportPointCause::UnstableFloatingPart}; + // section for weak connection calculations + { + if (connection.area < EPSILON) return {1.0f, SupportPointCause::UnstableFloatingPart}; - Vec3f conn_centroid = connection.centroid_accumulator / connection.area; + Vec3f conn_centroid = connection.centroid_accumulator / connection.area; + + if (layer_z - conn_centroid.z() < 3.0f) { return {-1.0f, SupportPointCause::WeakObjectPart}; } - if (layer_z - conn_centroid.z() < 3.0f) { return {-1.0f, SupportPointCause::WeakObjectPart}; } Integrals integrals; integrals.area = connection.area; integrals.x_i = connection.centroid_accumulator.head<2>(); @@ -694,36 +706,36 @@ std::tuple ObjectPart::is_stable_while_extruding(const float conn_yield_torque = compute_elastic_section_modulus(line_dir, extreme_point, integrals) * params.material_yield_strength; - float conn_weight_arm = (conn_centroid.head<2>() - mass_centroid.head<2>()).norm(); - if (layer_z - conn_centroid.z() < 30.0) { - conn_weight_arm = 0.0f; // Given that we do not have very good info about the weight distribution between the connection and current layer, - // do not consider the weight until quite far away from the weak connection segment - } - float conn_weight_torque = conn_weight_arm * weight * (1.0f - conn_centroid.z() / layer_z) * (1.0f - conn_centroid.z() / layer_z); + float conn_weight_arm = (conn_centroid.head<2>() - mass_centroid.head<2>()).norm(); + if (layer_z - conn_centroid.z() < 30.0) { + conn_weight_arm = 0.0f; // Given that we do not have very good info about the weight distribution between the connection and current layer, + // do not consider the weight until quite far away from the weak connection segment + } + float conn_weight_torque = conn_weight_arm * weight * (1.0f - conn_centroid.z() / layer_z) * (1.0f - conn_centroid.z() / layer_z); - float conn_movement_arm = std::max(0.0f, mass_centroid.z() - conn_centroid.z()); - float conn_movement_torque = movement_force * conn_movement_arm; + float conn_movement_arm = std::max(0.0f, mass_centroid.z() - conn_centroid.z()); + float conn_movement_torque = movement_force * conn_movement_arm; - float conn_conflict_torque_arm = layer_z - conn_centroid.z(); - float conn_extruder_conflict_torque = extruder_conflict_force * conn_conflict_torque_arm; + float conn_conflict_torque_arm = layer_z - conn_centroid.z(); + float conn_extruder_conflict_torque = extruder_conflict_force * conn_conflict_torque_arm; - float conn_total_torque = conn_movement_torque + conn_extruder_conflict_torque + conn_weight_torque - conn_yield_torque; + float conn_total_torque = conn_movement_torque + conn_extruder_conflict_torque + conn_weight_torque - conn_yield_torque; #ifdef DETAILED_DEBUG_LOGS - BOOST_LOG_TRIVIAL(debug) << "conn_centroid: " << conn_centroid.x() << " " << conn_centroid.y() << " " << conn_centroid.z(); - BOOST_LOG_TRIVIAL(debug) << "SSG: conn_yield_torque: " << conn_yield_torque; - BOOST_LOG_TRIVIAL(debug) << "SSG: conn_weight_arm: " << conn_weight_arm; - BOOST_LOG_TRIVIAL(debug) << "SSG: conn_weight_torque: " << conn_weight_torque; - BOOST_LOG_TRIVIAL(debug) << "SSG: conn_movement_arm: " << conn_movement_arm; - BOOST_LOG_TRIVIAL(debug) << "SSG: conn_movement_torque: " << conn_movement_torque; - BOOST_LOG_TRIVIAL(debug) << "SSG: conn_conflict_torque_arm: " << conn_conflict_torque_arm; - BOOST_LOG_TRIVIAL(debug) << "SSG: conn_extruder_conflict_torque: " << conn_extruder_conflict_torque; - BOOST_LOG_TRIVIAL(debug) << "SSG: total_torque: " << conn_total_torque << " layer_z: " << layer_z; + BOOST_LOG_TRIVIAL(debug) << "conn_centroid: " << conn_centroid.x() << " " << conn_centroid.y() << " " << conn_centroid.z(); + BOOST_LOG_TRIVIAL(debug) << "SSG: conn_yield_torque: " << conn_yield_torque; + BOOST_LOG_TRIVIAL(debug) << "SSG: conn_weight_arm: " << conn_weight_arm; + BOOST_LOG_TRIVIAL(debug) << "SSG: conn_weight_torque: " << conn_weight_torque; + BOOST_LOG_TRIVIAL(debug) << "SSG: conn_movement_arm: " << conn_movement_arm; + BOOST_LOG_TRIVIAL(debug) << "SSG: conn_movement_torque: " << conn_movement_torque; + BOOST_LOG_TRIVIAL(debug) << "SSG: conn_conflict_torque_arm: " << conn_conflict_torque_arm; + BOOST_LOG_TRIVIAL(debug) << "SSG: conn_extruder_conflict_torque: " << conn_extruder_conflict_torque; + BOOST_LOG_TRIVIAL(debug) << "SSG: total_torque: " << conn_total_torque << " layer_z: " << layer_z; #endif - return {conn_total_torque / conn_conflict_torque_arm, SupportPointCause::WeakObjectPart}; - } + return {conn_total_torque / conn_conflict_torque_arm, SupportPointCause::WeakObjectPart}; } +} std::vector gather_extrusions(const LayerSlice& slice, const Layer* layer) { // TODO reserve might be good, benchmark @@ -744,46 +756,47 @@ std::vector gather_extrusions(const LayerSlice fill_region->fills().entities[fill_idx] ); result.push_back(collection); - } } + } const ExtrusionEntityCollection& collection = perimeter_region->thin_fills(); result.push_back(&collection); - } + } return result; - } +} + bool has_brim(const Layer* layer, const Params& params){ return int(layer->id()) == params.raft_layers_count && params.raft_layers_count == 0 && params.brim_type != BrimType::btNoBrim && params.brim_width > 0.0; - } +} + Polygons get_brim(const ExPolygon& slice_polygon, const BrimType brim_type, const float brim_width) { // TODO: The algorithm here should take into account that multiple slices may // have coliding Brim areas and the final brim area is smaller, - // thus has lower adhesion. For now this effect will be neglected. - ExPolygons brim; + // thus has lower adhesion. For now this effect will be neglected. + ExPolygons brim; if (brim_type == BrimType::btOuterAndInner || brim_type == BrimType::btOuterOnly) { Polygon brim_hole = slice_polygon.contour; - brim_hole.reverse(); + brim_hole.reverse(); Polygons c = expand(slice_polygon.contour, scale_(brim_width)); // For very small polygons, the expand may result in empty vector, even thought the input is correct. - if (!c.empty()) { - brim.push_back(ExPolygon{c.front(), brim_hole}); - } + if (!c.empty()) { + brim.push_back(ExPolygon{c.front(), brim_hole}); } + } if (brim_type == BrimType::btOuterAndInner || brim_type == BrimType::btInnerOnly) { Polygons brim_contours = slice_polygon.holes; - polygons_reverse(brim_contours); - for (const Polygon &brim_contour : brim_contours) { + polygons_reverse(brim_contours); + for (const Polygon &brim_contour : brim_contours) { Polygons brim_holes = shrink({brim_contour}, scale_(brim_width)); - polygons_reverse(brim_holes); - ExPolygon inner_brim{brim_contour}; - inner_brim.holes = brim_holes; - brim.push_back(inner_brim); - } + polygons_reverse(brim_holes); + ExPolygon inner_brim{brim_contour}; + inner_brim.holes = brim_holes; + brim.push_back(inner_brim); } - + } return to_polygons(brim); } @@ -839,14 +852,14 @@ void reckon_new_support_point(ObjectPart &part, // This allows local support points (e.g. bridging) to be generated densely if ((supports_presence_grid.position_taken(support_point.position) && is_global)) { return; - } + } float area = support_point.spot_radius * support_point.spot_radius * float(PI); // add the stability effect of the point only if the spot is not taken, so that the densely created local support points do // not add unrealistic amount of stability to the object (due to overlaping of local support points) if (!(supports_presence_grid.position_taken(support_point.position))) { part.add_support_point(support_point.position, area); - } + } supp_points.push_back(support_point); supports_presence_grid.take_position(support_point.position); @@ -858,12 +871,13 @@ void reckon_new_support_point(ObjectPart &part, weakest_conn.second_moment_of_area_accumulator += area * support_point.position.head<2>().cwiseProduct(support_point.position.head<2>()); weakest_conn.second_moment_of_area_covariance_accumulator += area * support_point.position.x() * support_point.position.y(); - } - } + } +} + struct LocalSupports { std::vector> unstable_lines_per_slice; std::vector> ext_perim_lines_per_slice; - }; +}; struct EnitityToCheck { @@ -874,47 +888,46 @@ struct EnitityToCheck // TODO DRY: Very similar to gather extrusions. std::vector gather_entities_to_check(const Layer* layer) { - - auto get_flat_entities = [](const ExtrusionEntity *e) { - std::vector entities; - std::vector queue{e}; - while (!queue.empty()) { - const ExtrusionEntity *next = queue.back(); - queue.pop_back(); - if (next->is_collection()) { - for (const ExtrusionEntity *e : static_cast(next)->entities) { - queue.push_back(e); - } - } else { - entities.push_back(next); + auto get_flat_entities = [](const ExtrusionEntity *e) { + std::vector entities; + std::vector queue{e}; + while (!queue.empty()) { + const ExtrusionEntity *next = queue.back(); + queue.pop_back(); + if (next->is_collection()) { + for (const ExtrusionEntity *e : static_cast(next)->entities) { + queue.push_back(e); } + } else { + entities.push_back(next); } - return entities; - }; + } + return entities; + }; - std::vector entities_to_check; - for (size_t slice_idx = 0; slice_idx < layer->lslices_ex.size(); ++slice_idx) { - const LayerSlice &slice = layer->lslices_ex.at(slice_idx); - for (const auto &island : slice.islands) { - for (const LayerExtrusionRange &fill_range : island.fills) { - const LayerRegion *fill_region = layer->get_region(fill_range.region()); - for (size_t fill_idx : fill_range) { - for (const ExtrusionEntity *e : get_flat_entities(fill_region->fills().entities[fill_idx])) { - if (e->role() == ExtrusionRole::BridgeInfill) { - entities_to_check.push_back({e, fill_region, slice_idx}); - } + std::vector entities_to_check; + for (size_t slice_idx = 0; slice_idx < layer->lslices_ex.size(); ++slice_idx) { + const LayerSlice &slice = layer->lslices_ex.at(slice_idx); + for (const auto &island : slice.islands) { + for (const LayerExtrusionRange &fill_range : island.fills) { + const LayerRegion *fill_region = layer->get_region(fill_range.region()); + for (size_t fill_idx : fill_range) { + for (const ExtrusionEntity *e : get_flat_entities(fill_region->fills().entities[fill_idx])) { + if (e->role() == ExtrusionRole::BridgeInfill) { + entities_to_check.push_back({e, fill_region, slice_idx}); } } } + } - const LayerRegion *perimeter_region = layer->get_region(island.perimeters.region()); - for (size_t perimeter_idx : island.perimeters) { - for (const ExtrusionEntity *e : get_flat_entities(perimeter_region->perimeters().entities[perimeter_idx])) { - entities_to_check.push_back({e, perimeter_region, slice_idx}); - } + const LayerRegion *perimeter_region = layer->get_region(island.perimeters.region()); + for (size_t perimeter_idx : island.perimeters) { + for (const ExtrusionEntity *e : get_flat_entities(perimeter_region->perimeters().entities[perimeter_idx])) { + entities_to_check.push_back({e, perimeter_region, slice_idx}); } } } + } return entities_to_check; } @@ -987,7 +1000,8 @@ SliceMappings update_active_object_parts(const Layer *lay PartialObjects &partial_objects) { SliceMappings new_slice_mappings; - for (size_t slice_idx = 0; slice_idx < layer->lslices_ex.size(); ++slice_idx) { + + for (size_t slice_idx = 0; slice_idx < layer->lslices_ex.size(); ++slice_idx) { const LayerSlice &slice = layer->lslices_ex.at(slice_idx); const std::vector extrusion_collections{gather_extrusions(slice, layer)}; const bool connected_to_bed = int(layer->id()) == params.raft_layers_count; @@ -1011,7 +1025,7 @@ SliceMappings update_active_object_parts(const Layer *lay std::cout << "SLICE IDX: " << slice_idx << std::endl; for (const auto &link : slice.overlaps_below) { std::cout << "connected to slice below: " << link.slice_idx << " by area : " << link.area << std::endl; - } + } connection_to_below.print_info("CONNECTION TO BELOW"); #endif @@ -1071,7 +1085,7 @@ SliceMappings update_active_object_parts(const Layer *lay } } return new_slice_mappings; - } +} void reckon_global_supports(const tbb::concurrent_vector &external_perimeter_lines, const coordf_t layer_bottom_z, @@ -1082,26 +1096,27 @@ void reckon_global_supports(const tbb::concurrent_vector &externa SupportGridFilter &supports_presence_grid) { LD current_slice_lines_distancer({external_perimeter_lines.begin(), external_perimeter_lines.end()}); - float unchecked_dist = params.min_distance_between_support_points + 1.0f; + float unchecked_dist = params.min_distance_between_support_points + 1.0f; for (const ExtrusionLine &line : external_perimeter_lines) { if ((unchecked_dist + line.len < params.min_distance_between_support_points && line.curled_up_height < params.curling_tolerance_limit) || - line.len < EPSILON) { - unchecked_dist += line.len; - } else { - unchecked_dist = line.len; - Vec2f pivot_site_search_point = Vec2f(line.b + (line.b - line.a).normalized() * 300.0f); + line.len < EPSILON) { + unchecked_dist += line.len; + } else { + unchecked_dist = line.len; + Vec2f pivot_site_search_point = Vec2f(line.b + (line.b - line.a).normalized() * 300.0f); auto [dist, nidx, nearest_point] = current_slice_lines_distancer.distance_from_lines_extra(pivot_site_search_point); Vec3f position = to_3d(nearest_point, layer_bottom_z); auto [force, cause] = part.is_stable_while_extruding(weakest_connection, line, position, layer_bottom_z, params); - if (force > 0) { + if (force > 0) { SupportPoint support_point{cause, position, params.support_points_interface_radius}; reckon_new_support_point(part, weakest_connection, supp_points, supports_presence_grid, support_point, true); } - } - } - } + } + } +} + std::tuple check_stability(const PrintObject *po, const PrecomputedSliceConnections &precomputed_slices_connections, const PrintTryCancel &cancel_func, @@ -1237,13 +1252,15 @@ void estimate_supports_malformations(SupportLayerPtrs &layers, float flow_width, Polygon pol(pl.points); pol.make_counter_clockwise(); - auto annotated_points = ExtrusionProcessor::estimate_points_properties(pol.points, prev_layer_lines, - flow_width); + ExtrusionProcessor::PropertiesEstimationConfig config{}; + config.flow_width = flow_width; + auto annotated_points = ExtrusionProcessor::estimate_points_properties< + false>(pol.points, prev_layer_lines, config); for (size_t i = 0; i < annotated_points.size(); ++i) { const ExtrusionProcessor::ExtendedPoint &a = i > 0 ? annotated_points[i - 1] : annotated_points[i]; const ExtrusionProcessor::ExtendedPoint &b = annotated_points[i]; - ExtrusionLine line_out{a.position.cast(), b.position.cast(), float((a.position - b.position).norm()), + ExtrusionLine line_out{a.position.cast(), b.position.cast(), float((a.position - b.position).norm()), extrusion}; Vec2f middle = 0.5 * (line_out.a + line_out.b); @@ -1313,10 +1330,14 @@ void estimate_malformations(LayerPtrs &layers, const Params ¶ms) Points extrusion_pts; extrusion->collect_points(extrusion_pts); float flow_width = get_flow_width(layer_region, extrusion->role()); - auto annotated_points = ExtrusionProcessor::estimate_points_properties(extrusion_pts, - prev_layer_lines, - flow_width, - params.bridge_distance); + + ExtrusionProcessor::PropertiesEstimationConfig config{}; + config.max_line_length = params.bridge_distance; + config.add_corners = true; + config.flow_width = flow_width; + auto annotated_points = ExtrusionProcessor::estimate_points_properties< + false>(extrusion_pts, prev_layer_lines, config); + for (size_t i = 0; i < annotated_points.size(); ++i) { const ExtrusionProcessor::ExtendedPoint &a = i > 0 ? annotated_points[i - 1] : annotated_points[i]; const ExtrusionProcessor::ExtendedPoint &b = annotated_points[i]; diff --git a/src/libslic3r/SupportSpotsGenerator.hpp b/src/libslic3r/SupportSpotsGenerator.hpp index b0df4e5..4a9742d 100644 --- a/src/libslic3r/SupportSpotsGenerator.hpp +++ b/src/libslic3r/SupportSpotsGenerator.hpp @@ -1,15 +1,31 @@ #ifndef SRC_LIBSLIC3R_SUPPORTABLEISSUESSEARCH_HPP_ #define SRC_LIBSLIC3R_SUPPORTABLEISSUESSEARCH_HPP_ +#include +#include +#include +#include +#include +#include +#include +#include + #include "Layer.hpp" #include "Line.hpp" #include "PrintBase.hpp" #include "PrintConfig.hpp" -#include -#include -#include +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/Polyline.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { +class ExtrusionEntity; +class ExtrusionEntityCollection; +class Layer; +class PrintObject; +class SupportLayer; namespace SupportSpotsGenerator { @@ -135,6 +151,7 @@ struct PartialObject bool connected_to_bed; }; + /** * Unsacled values of integrals over a polygonal domain. */ @@ -161,11 +178,13 @@ class Integrals{ Vec2f x_i{Vec2f::Zero()}; Vec2f x_i_squared{Vec2f::Zero()}; float xy{}; + private: void add(const Integrals& other); }; Integrals operator+(const Integrals& a, const Integrals& b); + float compute_second_moment( const Integrals& integrals, const Vec2f& axis_direction @@ -243,6 +262,7 @@ public: float layer_z, const Params ¶ms) const; }; + using PartialObjects = std::vector; // Both support points and partial objects are sorted from the lowest z to the highest diff --git a/src/libslic3r/Surface.cpp b/src/libslic3r/Surface.cpp index 58ac729..e226cef 100644 --- a/src/libslic3r/Surface.cpp +++ b/src/libslic3r/Surface.cpp @@ -1,6 +1,8 @@ #include "BoundingBox.hpp" #include "Surface.hpp" #include "SVG.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { diff --git a/src/libslic3r/Surface.hpp b/src/libslic3r/Surface.hpp index 1f352e9..ba0ba83 100644 --- a/src/libslic3r/Surface.hpp +++ b/src/libslic3r/Surface.hpp @@ -1,8 +1,18 @@ #ifndef slic3r_Surface_hpp_ #define slic3r_Surface_hpp_ +#include +#include +#include +#include +#include +#include + #include "libslic3r.h" #include "ExPolygon.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" namespace Slic3r { diff --git a/src/libslic3r/SurfaceCollection.cpp b/src/libslic3r/SurfaceCollection.cpp index fbc30ca..77a426b 100644 --- a/src/libslic3r/SurfaceCollection.cpp +++ b/src/libslic3r/SurfaceCollection.cpp @@ -1,8 +1,12 @@ #include "SurfaceCollection.hpp" + +#include +#include + #include "BoundingBox.hpp" #include "SVG.hpp" - -#include +#include "libslic3r/Point.hpp" +#include "libslic3r/Surface.hpp" namespace Slic3r { diff --git a/src/libslic3r/SurfaceCollection.hpp b/src/libslic3r/SurfaceCollection.hpp index 0f62875..53a983c 100644 --- a/src/libslic3r/SurfaceCollection.hpp +++ b/src/libslic3r/SurfaceCollection.hpp @@ -1,10 +1,16 @@ #ifndef slic3r_SurfaceCollection_hpp_ #define slic3r_SurfaceCollection_hpp_ -#include "libslic3r.h" -#include "Surface.hpp" +#include #include #include +#include +#include + +#include "libslic3r.h" +#include "Surface.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Polygon.hpp" namespace Slic3r { diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 8509702..247a934 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -18,10 +18,6 @@ #define DISABLE_INSTANCES_SYNCH 0 // Use wxDataViewRender instead of wxDataViewCustomRenderer #define ENABLE_NONCUSTOM_DATA_VIEW_RENDERING 0 -// Enable G-Code viewer statistics imgui dialog -#define ENABLE_GCODE_VIEWER_STATISTICS 0 -// Enable G-Code viewer comparison between toolpaths height and width detected from gcode and calculated at gcode generation -#define ENABLE_GCODE_VIEWER_DATA_CHECKING 0 // Enable project dirty state manager debug window #define ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW 0 // Disable using instanced models to render options in gcode preview @@ -45,13 +41,17 @@ // Enable smoothing of objects normals #define ENABLE_SMOOTH_NORMALS 0 -// Enable OpenGL ES -#define ENABLE_OPENGL_ES 0 -// Enable OpenGL core profile context (tested against Mesa 20.1.8 on Windows) -#define ENABLE_GL_CORE_PROFILE (1 && !ENABLE_OPENGL_ES) - // Enable imgui dialog which allows to set the parameters used to export binarized gcode #define ENABLE_BINARIZED_GCODE_DEBUG_WINDOW 0 +// Enable imgui debug dialog for new gcode viewer (using libvgcode) +#define ENABLE_NEW_GCODE_VIEWER_DEBUG 0 +// Enable extension of tool position imgui dialog to show actual speed profile +#define ENABLE_ACTUAL_SPEED_DEBUG 1 + +// This technology enables a hack which resolves the slow down on MAC when running the application as GCodeViewer. +// For yet unknow reason the slow down disappears if any of the toolbars is renderered. +// This hack keeps the collapse toolbar enabled and renders it outside of the screen. +#define ENABLE_HACK_GCODEVIEWER_SLOW_ON_MAC 1 #endif // _qidislicer_technologies_h_ diff --git a/src/libslic3r/Tesselate.cpp b/src/libslic3r/Tesselate.cpp index aef512f..1b20abb 100644 --- a/src/libslic3r/Tesselate.cpp +++ b/src/libslic3r/Tesselate.cpp @@ -1,8 +1,16 @@ #include "Tesselate.hpp" -#include "ExPolygon.hpp" - #include +#include +#include +#include +#include +#include + +#include "ExPolygon.hpp" +#include "admesh/stl.h" + +class GLUtesselator; namespace Slic3r { diff --git a/src/libslic3r/Tesselate.hpp b/src/libslic3r/Tesselate.hpp index b164b58..38be33b 100644 --- a/src/libslic3r/Tesselate.hpp +++ b/src/libslic3r/Tesselate.hpp @@ -1,10 +1,13 @@ #ifndef slic3r_Tesselate_hpp_ #define slic3r_Tesselate_hpp_ -#include #include +#include #include "ExPolygon.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { diff --git a/src/libslic3r/TextConfiguration.hpp b/src/libslic3r/TextConfiguration.hpp index 8414dae..40c8c83 100644 --- a/src/libslic3r/TextConfiguration.hpp +++ b/src/libslic3r/TextConfiguration.hpp @@ -26,7 +26,6 @@ struct FontProp // When not set value is zero and is not stored std::optional line_gap; // [in font point] - // positive value mean wider character shape // negative value mean tiner character shape // When not set value is zero and is not stored @@ -37,7 +36,6 @@ struct FontProp // When not set value is zero and is not stored std::optional skew; // [ration x:y] - // Parameter for True Type Font collections // Select index of font in collection std::optional collection_number; @@ -52,7 +50,7 @@ struct FontProp // change pivot of text // When not set, center is used and is not stored Align align = Align(HorizontalAlign::center, VerticalAlign::center); - + ////// // Duplicit data to wxFontDescriptor // used for store/load .3mf file @@ -180,7 +178,6 @@ struct TextConfiguration // Embossed text value std::string text = "None"; - // undo / redo stack recovery template void serialize(Archive &ar) { ar(style, text); } }; diff --git a/src/libslic3r/Thread.cpp b/src/libslic3r/Thread.cpp index 5e23a88..e3c970d 100644 --- a/src/libslic3r/Thread.cpp +++ b/src/libslic3r/Thread.cpp @@ -9,12 +9,16 @@ #endif // __APPLE__ #endif -#include +#include +#include +#include #include #include #include -#include -#include +#include +#include +#include +#include #include "Thread.hpp" #include "Utils.hpp" @@ -123,8 +127,8 @@ std::optional get_current_thread_name() return std::nullopt; wchar_t *ptr = nullptr; - s_fnGetThreadDescription(::GetCurrentThread(), &ptr); - return (ptr == nullptr) ? std::string() : boost::nowide::narrow(ptr); + s_fnGetThreadDescription(::GetCurrentThread(), &ptr); + return (ptr == nullptr) ? std::string() : boost::nowide::narrow(ptr); } #else // _WIN32 diff --git a/src/libslic3r/Thread.hpp b/src/libslic3r/Thread.hpp index 61629ad..6e8c491 100644 --- a/src/libslic3r/Thread.hpp +++ b/src/libslic3r/Thread.hpp @@ -1,14 +1,16 @@ #ifndef GUI_THREAD_HPP #define GUI_THREAD_HPP +#include +#include +#include +#include +#include #include #include #include #include -#include - -#include -#include +#include namespace Slic3r { diff --git a/src/libslic3r/Time.cpp b/src/libslic3r/Time.cpp index 8faa14a..84ab63e 100644 --- a/src/libslic3r/Time.cpp +++ b/src/libslic3r/Time.cpp @@ -1,11 +1,10 @@ #include "Time.hpp" -#include #include #include #include #include -#include +#include #ifdef _MSC_VER #include diff --git a/src/libslic3r/Timer.hpp b/src/libslic3r/Timer.hpp index f2e5dde..5103e9b 100644 --- a/src/libslic3r/Timer.hpp +++ b/src/libslic3r/Timer.hpp @@ -1,8 +1,11 @@ #ifndef libslic3r_Timer_hpp_ #define libslic3r_Timer_hpp_ +#include #include #include +#include +#include namespace Slic3r { diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 329c199..5fe05ff 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -1,4 +1,26 @@ -#include "Exception.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "TriangleMesh.hpp" #include "TriangleMeshSlicer.hpp" #include "MeshSplitImpl.hpp" @@ -9,29 +31,10 @@ #include "Execution/ExecutionTBB.hpp" #include "Execution/ExecutionSeq.hpp" #include "Utils.hpp" - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include - -#include -#include - -#include +#include "admesh/stl.h" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { @@ -179,6 +182,24 @@ static void trianglemesh_repair_on_import(stl_file &stl) BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() finished"; } +void TriangleMesh::from_facets(std::vector &&facets, bool repair) +{ + stl_file stl; + stl.stats.type = inmemory; + stl.stats.number_of_facets = uint32_t(facets.size()); + stl.stats.original_num_facets = int(stl.stats.number_of_facets); + + stl_allocate(&stl); + stl.facet_start = std::move(facets); + + if (repair) { + trianglemesh_repair_on_import(stl); + } + + stl_generate_shared_vertices(&stl, this->its); + fill_initial_stats(this->its, this->m_stats); +} + bool TriangleMesh::ReadSTLFile(const char* input_file, bool repair) { stl_file stl; diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index 4b52440..20f4c9a 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -1,15 +1,27 @@ #ifndef slic3r_TriangleMesh_hpp_ #define slic3r_TriangleMesh_hpp_ -#include "libslic3r.h" #include +#include +#include +#include +#include #include #include +#include +#include +#include +#include +#include +#include + +#include "libslic3r.h" #include "BoundingBox.hpp" #include "Line.hpp" #include "Point.hpp" #include "Polygon.hpp" #include "ExPolygon.hpp" +#include "libslic3r/Point.hpp" namespace Slic3r { @@ -93,6 +105,7 @@ public: explicit TriangleMesh(const indexed_triangle_set &M); explicit TriangleMesh(indexed_triangle_set &&M, const RepairedMeshErrors& repaired_errors = RepairedMeshErrors()); void clear() { this->its.clear(); m_stats.clear(); } + void from_facets(std::vector &&facets, bool repair = true); bool ReadSTLFile(const char* input_file, bool repair = true); bool write_ascii(const char* output_file); bool write_binary(const char* output_file); @@ -374,6 +387,7 @@ inline BoundingBoxf3 bounding_box(const indexed_triangle_set& its, const Transfo // Serialization through the Cereal library #include + namespace cereal { template struct specialize {}; template void load(Archive &archive, Slic3r::TriangleMesh &mesh) { diff --git a/src/libslic3r/TriangleMeshSlicer.cpp b/src/libslic3r/TriangleMeshSlicer.cpp index 79b2f31..eaca55e 100644 --- a/src/libslic3r/TriangleMeshSlicer.cpp +++ b/src/libslic3r/TriangleMeshSlicer.cpp @@ -1,28 +1,29 @@ -#include "ClipperUtils.hpp" -#include "Geometry.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "Tesselate.hpp" #include "TriangleMesh.hpp" #include "TriangleMeshSlicer.hpp" #include "Utils.hpp" - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include - -#include - -#ifndef NDEBUG -// #define EXPENSIVE_DEBUG_CHECKS -#endif // NDEBUG +#include "admesh/stl.h" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/MultiPoint.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/libslic3r.h" #if 0 #define DEBUG @@ -32,8 +33,6 @@ // #define SLIC3R_TRIANGLEMESH_DEBUG #endif -#include -#include #include #if defined(__cpp_lib_hardware_interference_size) && ! defined(__APPLE__) diff --git a/src/libslic3r/TriangleMeshSlicer.hpp b/src/libslic3r/TriangleMeshSlicer.hpp index ea6a726..c8ae0f4 100644 --- a/src/libslic3r/TriangleMeshSlicer.hpp +++ b/src/libslic3r/TriangleMeshSlicer.hpp @@ -1,10 +1,18 @@ #ifndef slic3r_TriangleMeshSlicer_hpp_ #define slic3r_TriangleMeshSlicer_hpp_ +#include +#include #include #include +#include +#include + #include "Polygon.hpp" #include "ExPolygon.hpp" +#include "libslic3r/Point.hpp" + +struct indexed_triangle_set; namespace Slic3r { diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index 70d5216..fb72fbb 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -1,11 +1,16 @@ #include "TriangleSelector.hpp" -#include "Model.hpp" #include +#include +#include +#include +#include -#ifndef NDEBUG -// #define EXPENSIVE_DEBUG_CHECKS -#endif // NDEBUG +#include "libslic3r/Geometry.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/Utils.hpp" +#include "libslic3r/libslic3r.h" namespace Slic3r { @@ -230,7 +235,7 @@ int TriangleSelector::select_unsplit_triangle(const Vec3f &hit, int facet_idx) c return this->select_unsplit_triangle(hit, facet_idx, neighbors); } -void TriangleSelector::select_patch(int facet_start, std::unique_ptr &&cursor, EnforcerBlockerType new_state, const Transform3d& trafo_no_translate, bool triangle_splitting, float highlight_by_angle_deg) +void TriangleSelector::select_patch(int facet_start, std::unique_ptr &&cursor, TriangleStateType new_state, const Transform3d& trafo_no_translate, bool triangle_splitting, float highlight_by_angle_deg) { assert(facet_start < m_orig_size_indices); @@ -451,7 +456,7 @@ void TriangleSelector::bucket_fill_select_triangles(const Vec3f& hit, int facet_ return; assert(!m_triangles[start_facet_idx].is_split()); - EnforcerBlockerType start_facet_state = m_triangles[start_facet_idx].get_state(); + TriangleStateType start_facet_state = m_triangles[start_facet_idx].get_state(); this->seed_fill_unselect_all_triangles(); if (!propagate) { @@ -507,8 +512,7 @@ void TriangleSelector::bucket_fill_select_triangles(const Vec3f& hit, int facet_ // This is done by an actual recursive call. Returns false if the triangle is // outside the cursor. // Called by select_patch() and by itself. -bool TriangleSelector::select_triangle(int facet_idx, EnforcerBlockerType type, bool triangle_splitting) -{ +bool TriangleSelector::select_triangle(int facet_idx, TriangleStateType type, bool triangle_splitting) { assert(facet_idx < int(m_triangles.size())); if (! m_triangles[facet_idx].valid()) @@ -857,8 +861,7 @@ Vec3i TriangleSelector::child_neighbors_propagated(const Triangle &tr, const Vec return out; } -bool TriangleSelector::select_triangle_recursive(int facet_idx, const Vec3i &neighbors, EnforcerBlockerType type, bool triangle_splitting) -{ +bool TriangleSelector::select_triangle_recursive(int facet_idx, const Vec3i &neighbors, TriangleStateType type, bool triangle_splitting) { assert(facet_idx < int(m_triangles.size())); Triangle* tr = &m_triangles[facet_idx]; @@ -910,7 +913,7 @@ bool TriangleSelector::select_triangle_recursive(int facet_idx, const Vec3i &nei return true; } -void TriangleSelector::set_facet(int facet_idx, EnforcerBlockerType state) +void TriangleSelector::set_facet(int facet_idx, TriangleStateType state) { assert(facet_idx < m_orig_size_indices); undivide_triangle(facet_idx); @@ -930,7 +933,7 @@ void TriangleSelector::split_triangle(int facet_idx, const Vec3i &neighbors) Triangle* tr = &m_triangles[facet_idx]; assert(this->verify_triangle_neighbors(*tr, neighbors)); - EnforcerBlockerType old_type = tr->get_state(); + TriangleStateType old_type = tr->get_state(); // If we got here, we are about to actually split the triangle. const double limit_squared = m_edge_limit_sqr; @@ -1113,7 +1116,7 @@ void TriangleSelector::remove_useless_children(int facet_idx) // Return if a child is not leaf or two children differ in type. - EnforcerBlockerType first_child_type = EnforcerBlockerType::NONE; + TriangleStateType first_child_type = TriangleStateType::NONE; for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) { if (m_triangles[tr.children[child_idx]].is_split()) return; @@ -1213,8 +1216,7 @@ void TriangleSelector::set_edge_limit(float edge_limit) m_edge_limit_sqr = std::pow(edge_limit, 2.f); } -int TriangleSelector::push_triangle(int a, int b, int c, int source_triangle, const EnforcerBlockerType state) -{ +int TriangleSelector::push_triangle(int a, int b, int c, int source_triangle, const TriangleStateType state) { for (int i : {a, b, c}) { assert(i >= 0 && i < int(m_vertices.size())); ++m_vertices[i].ref_cnt; @@ -1247,8 +1249,7 @@ int TriangleSelector::push_triangle(int a, int b, int c, int source_triangle, co // Split a triangle based on Triangle::number_of_split_sides() and Triangle::special_side() // by allocating child triangles and midpoint vertices. // Midpoint vertices are possibly reused by traversing children of neighbor triangles. -void TriangleSelector::perform_split(int facet_idx, const Vec3i &neighbors, EnforcerBlockerType old_state) -{ +void TriangleSelector::perform_split(int facet_idx, const Vec3i &neighbors, TriangleStateType old_state) { // Reserve space for the new triangles upfront, so that the reference to this triangle will not change. { size_t num_triangles_new = m_triangles.size() + m_triangles[facet_idx].number_of_split_sides() + 1; @@ -1314,16 +1315,14 @@ void TriangleSelector::perform_split(int facet_idx, const Vec3i &neighbors, Enfo #endif // NDEBUG } -bool TriangleSelector::has_facets(EnforcerBlockerType state) const -{ +bool TriangleSelector::has_facets(TriangleStateType state) const { for (const Triangle& tr : m_triangles) if (tr.valid() && ! tr.is_split() && tr.get_state() == state) return true; return false; } -int TriangleSelector::num_facets(EnforcerBlockerType state) const -{ +int TriangleSelector::num_facets(TriangleStateType state) const { int cnt = 0; for (const Triangle& tr : m_triangles) if (tr.valid() && ! tr.is_split() && tr.get_state() == state) @@ -1331,8 +1330,7 @@ int TriangleSelector::num_facets(EnforcerBlockerType state) const return cnt; } -indexed_triangle_set TriangleSelector::get_facets(EnforcerBlockerType state) const -{ +indexed_triangle_set TriangleSelector::get_facets(TriangleStateType state) const { indexed_triangle_set out; std::vector vertex_map(m_vertices.size(), -1); for (const Triangle& tr : m_triangles) { @@ -1352,8 +1350,7 @@ indexed_triangle_set TriangleSelector::get_facets(EnforcerBlockerType state) con return out; } -indexed_triangle_set TriangleSelector::get_facets_strict(EnforcerBlockerType state) const -{ +indexed_triangle_set TriangleSelector::get_facets_strict(TriangleStateType state) const { indexed_triangle_set out; size_t num_vertices = 0; @@ -1381,7 +1378,7 @@ indexed_triangle_set TriangleSelector::get_facets_strict(EnforcerBlockerType sta void TriangleSelector::get_facets_strict_recursive( const Triangle &tr, const Vec3i &neighbors, - EnforcerBlockerType state, + TriangleStateType state, std::vector &out_triangles) const { if (tr.is_split()) { @@ -1522,12 +1519,11 @@ void TriangleSelector::get_seed_fill_contour_recursive(const int facet_idx, cons } } -std::pair>, std::vector> TriangleSelector::serialize() const -{ +TriangleSelector::TriangleSplittingData TriangleSelector::serialize() const { // Each original triangle of the mesh is assigned a number encoding its state // or how it is split. Each triangle is encoded by 4 bits (xxyy) or 8 bits (zzzzxxyy): - // leaf triangle: xx = EnforcerBlockerType (Only values 0, 1, and 2. Value 3 is used as an indicator for additional 4 bits.), yy = 0 - // leaf triangle: xx = 0b11, yy = 0b00, zzzz = EnforcerBlockerType (subtracted by 3) + // leaf triangle: xx = TriangleStateType (Only values 0, 1, and 2. Value 3 is used as an indicator for additional 4 bits.), yy = 0 + // leaf triangle: xx = 0b11, yy = 0b00, zzzz = TriangleStateType (subtracted by 3) // non-leaf: xx = special side, yy = number of split sides // These are bitwise appended and formed into one 64-bit integer. @@ -1539,7 +1535,7 @@ std::pair>, std::vector> TriangleSelector: // (std::function calls using a pointer, while this implementation calls directly). struct Serializer { const TriangleSelector* triangle_selector; - std::pair>, std::vector> data; + TriangleSplittingData data; void serialize(int facet_idx) { const Triangle& tr = triangle_selector->m_triangles[facet_idx]; @@ -1548,8 +1544,8 @@ std::pair>, std::vector> TriangleSelector: int split_sides = tr.number_of_split_sides(); assert(split_sides >= 0 && split_sides <= 3); - data.second.push_back(split_sides & 0b01); - data.second.push_back(split_sides & 0b10); + data.bitstream.push_back(split_sides & 0b01); + data.bitstream.push_back(split_sides & 0b10); if (split_sides) { // If this triangle is split, save which side is split (in case @@ -1557,8 +1553,8 @@ std::pair>, std::vector> TriangleSelector: // be ignored for 3-side split. assert(tr.is_split() && split_sides > 0); assert(tr.special_side() >= 0 && tr.special_side() <= 3); - data.second.push_back(tr.special_side() & 0b01); - data.second.push_back(tr.special_side() & 0b10); + data.bitstream.push_back(tr.special_side() & 0b01); + data.bitstream.push_back(tr.special_side() & 0b10); // Now save all children. // Serialized in reverse order for compatibility with QIDISlicer 2.3.1. for (int child_idx = split_sides; child_idx >= 0; -- child_idx) @@ -1566,48 +1562,50 @@ std::pair>, std::vector> TriangleSelector: } else { // In case this is leaf, we better save information about its state. int n = int(tr.get_state()); + if (n < static_cast(TriangleStateType::Count)) + data.used_states[n] = true; + if (n >= 3) { assert(n <= 16); if (n <= 16) { // Store "11" plus 4 bits of (n-3). - data.second.insert(data.second.end(), { true, true }); + data.bitstream.insert(data.bitstream.end(), { true, true }); n -= 3; for (size_t bit_idx = 0; bit_idx < 4; ++bit_idx) - data.second.push_back(n & (uint64_t(0b0001) << bit_idx)); + data.bitstream.push_back(n & (uint64_t(0b0001) << bit_idx)); } } else { // Simple case, compatible with QIDISlicer 2.3.1 and older for storing paint on supports and seams. // Store 2 bits of n. - data.second.push_back(n & 0b01); - data.second.push_back(n & 0b10); + data.bitstream.push_back(n & 0b01); + data.bitstream.push_back(n & 0b10); } } } } out { this }; - out.data.first.reserve(m_orig_size_indices); + out.data.triangles_to_split.reserve(m_orig_size_indices); for (int i=0; i>, std::vector> &data, bool needs_reset) -{ +void TriangleSelector::deserialize(const TriangleSplittingData &data, bool needs_reset) { if (needs_reset) reset(); // dump any current state // Reserve number of triangles as if each triangle was saved with 4 bits. // With MMU painting this estimate may be somehow low, but better than nothing. - m_triangles.reserve(std::max(m_mesh.its.indices.size(), data.second.size() / 4)); + m_triangles.reserve(std::max(m_mesh.its.indices.size(), data.bitstream.size() / 4)); // Number of triangles is twice the number of vertices on a large manifold mesh of genus zero. // Here the triangles count account for both the nodes and leaves, thus the following line may overestimate. m_vertices.reserve(std::max(m_mesh.its.vertices.size(), m_triangles.size() / 2)); @@ -1623,13 +1621,13 @@ void TriangleSelector::deserialize(const std::pair parents; - for (auto [triangle_id, ibit] : data.first) { + for (auto [triangle_id, ibit] : data.triangles_to_split) { assert(triangle_id < int(m_triangles.size())); - assert(ibit < int(data.second.size())); + assert(ibit < int(data.bitstream.size())); auto next_nibble = [&data, &ibit = ibit]() { int n = 0; for (int i = 0; i < 4; ++ i) - n |= data.second[ibit ++] << i; + n |= data.bitstream[ibit ++] << i; return n; }; @@ -1641,7 +1639,7 @@ void TriangleSelector::deserialize(const std::pair> 2); + auto state = is_split ? TriangleStateType::NONE : TriangleStateType((code & 0b1100) == 0b1100 ? next_nibble() + 3 : code >> 2); // Only valid if is_split. int special_side = code >> 2; @@ -1653,7 +1651,7 @@ void TriangleSelector::deserialize(const std::pairchild_neighbors(tr, last.neighbors, child_idx); int this_idx = tr.children[child_idx]; m_triangles[this_idx].set_division(num_of_split_sides, special_side); - perform_split(this_idx, neighbors, EnforcerBlockerType::NONE); + perform_split(this_idx, neighbors, TriangleStateType::NONE); parents.push_back({this_idx, neighbors, 0, num_of_children}); } else { // this triangle belongs to last split one @@ -1701,21 +1699,53 @@ void TriangleSelector::deserialize(const std::pairbitstream.size()); + assert(!this->bitstream.empty() && this->bitstream.size() != bitstream_start_idx); + assert((this->bitstream.size() - bitstream_start_idx) % 4 == 0); + + if (this->bitstream.empty() || this->bitstream.size() == bitstream_start_idx) + return; + + size_t nibble_idx = bitstream_start_idx; + + auto read_next_nibble = [&data_bitstream = std::as_const(this->bitstream), &nibble_idx]() -> uint8_t { + assert(nibble_idx + 3 < data_bitstream.size()); + uint8_t code = 0; + for (size_t bit_idx = 0; bit_idx < 4; ++bit_idx) + code |= data_bitstream[nibble_idx++] << bit_idx; + return code; + }; + + while (nibble_idx < this->bitstream.size()) { + const uint8_t code = read_next_nibble(); + + if (const bool is_split = (code & 0b11) != 0; is_split) + continue; + + const uint8_t facet_state = (code & 0b1100) == 0b1100 ? read_next_nibble() + 3 : code >> 2; + assert(facet_state < this->used_states.size()); + if (facet_state >= this->used_states.size()) + continue; + + this->used_states[facet_state] = true; + } +} + // Lightweight variant of deserialization, which only tests whether a face of test_state exists. -bool TriangleSelector::has_facets(const std::pair>, std::vector> &data, const EnforcerBlockerType test_state) -{ +bool TriangleSelector::has_facets(const TriangleSplittingData &data, const TriangleStateType test_state) { // Depth-first queue of a number of unvisited children. // Kept outside of the loop to avoid re-allocating inside the loop. std::vector parents_children; parents_children.reserve(64); - for (const std::pair &triangle_id_and_ibit : data.first) { - int ibit = triangle_id_and_ibit.second; - assert(ibit < int(data.second.size())); + for (const TriangleBitStreamMapping &triangle_id_and_ibit : data.triangles_to_split) { + int ibit = triangle_id_and_ibit.bitstream_start_idx; + assert(ibit < int(data.bitstream.size())); auto next_nibble = [&data, &ibit = ibit]() { int n = 0; for (int i = 0; i < 4; ++ i) - n |= data.second[ibit ++] << i; + n |= data.bitstream[ibit ++] << i; return n; }; // < 0 -> negative of a number of children @@ -1760,8 +1790,7 @@ void TriangleSelector::seed_fill_unselect_all_triangles() triangle.unselect_by_seed_fill(); } -void TriangleSelector::seed_fill_apply_on_triangles(EnforcerBlockerType new_state) -{ +void TriangleSelector::seed_fill_apply_on_triangles(TriangleStateType new_state) { for (Triangle &triangle : m_triangles) if (!triangle.is_split() && triangle.is_selected_by_seed_fill()) triangle.set_state(new_state); diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index 5b8646c..0eb8d9b 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -3,15 +3,53 @@ // #define QIDISLICER_TRIANGLE_SELECTOR_DEBUG - +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include + #include "Point.hpp" #include "TriangleMesh.hpp" +#include "admesh/stl.h" + +namespace cereal { +class access; +} // namespace cereal namespace Slic3r { +class TriangleMesh; -enum class EnforcerBlockerType : int8_t; - +enum class TriangleStateType : int8_t { + // Maximum is 3. The value is serialized in TriangleSelector into 2 bits. + NONE = 0, + ENFORCER = 1, + BLOCKER = 2, + // Maximum is 15. The value is serialized in TriangleSelector into 6 bits using a 2 bit prefix code. + Extruder1 = ENFORCER, + Extruder2 = BLOCKER, + Extruder3, + Extruder4, + Extruder5, + Extruder6, + Extruder7, + Extruder8, + Extruder9, + Extruder10, + Extruder11, + Extruder12, + Extruder13, + Extruder14, + Extruder15, + Count +}; // Following class holds information about selected triangles. It also has power // to recursively subdivide the triangles and make the selection finer. @@ -180,6 +218,56 @@ public: } }; + struct TriangleBitStreamMapping + { + // Index of the triangle to which we assign the bitstream containing splitting information. + int triangle_idx = -1; + // Index of the first bit of the bitstream assigned to this triangle. + int bitstream_start_idx = -1; + + TriangleBitStreamMapping() = default; + explicit TriangleBitStreamMapping(int triangleIdx, int bitstreamStartIdx) : triangle_idx(triangleIdx), bitstream_start_idx(bitstreamStartIdx) {} + + friend bool operator==(const TriangleBitStreamMapping &lhs, const TriangleBitStreamMapping &rhs) { return lhs.triangle_idx == rhs.triangle_idx && lhs.bitstream_start_idx == rhs.bitstream_start_idx; } + friend bool operator!=(const TriangleBitStreamMapping &lhs, const TriangleBitStreamMapping &rhs) { return !(lhs == rhs); } + + private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(triangle_idx, bitstream_start_idx); } + }; + + struct TriangleSplittingData { + // Vector of triangles and its indexes to the bitstream. + std::vector triangles_to_split; + // Bit stream containing splitting information. + std::vector bitstream; + // Array indicating which triangle state types are used (encoded inside bitstream). + std::vector used_states { std::vector(static_cast(TriangleStateType::Count), false) }; + + TriangleSplittingData() = default; + + friend bool operator==(const TriangleSplittingData &lhs, const TriangleSplittingData &rhs) { + return lhs.triangles_to_split == rhs.triangles_to_split + && lhs.bitstream == rhs.bitstream + && lhs.used_states == rhs.used_states; + } + + friend bool operator!=(const TriangleSplittingData &lhs, const TriangleSplittingData &rhs) { return !(lhs == rhs); } + + // Reset all used states before they are recomputed based on the bitstream. + void reset_used_states() { + used_states.resize(static_cast(TriangleStateType::Count), false); + std::fill(used_states.begin(), used_states.end(), false); + } + + // Update used states based on the bitstream. It just iterated over the bitstream from the bitstream_start_idx till the end. + void update_used_states(size_t bitstream_start_idx); + + private: + friend class cereal::access; + template void serialize(Archive &ar) { ar(triangles_to_split, bitstream, used_states); } + }; + std::pair, std::vector> precompute_all_neighbors() const; void precompute_all_neighbors_recursive(int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::vector &neighbors_out, std::vector &neighbors_normal_out) const; @@ -198,7 +286,7 @@ public: // Select all triangles fully inside the circle, subdivide where needed. void select_patch(int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to std::unique_ptr &&cursor, // Cursor containing information about the point where to start, camera position (mesh coords), matrix to get from mesh to world, and its shape and type. - EnforcerBlockerType new_state, // enforcer or blocker? + TriangleStateType new_state, // enforcer or blocker? const Transform3d &trafo_no_translate, // matrix to get from mesh to world without translation bool triangle_splitting, // If triangles will be split base on the cursor or not float highlight_by_angle_deg = 0.f); // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees. @@ -217,18 +305,18 @@ public: bool propagate, // if bucket fill is propagated to neighbor faces or if it fills the only facet of the modified mesh that the hit point belongs to. bool force_reselection = false); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle - bool has_facets(EnforcerBlockerType state) const; - static bool has_facets(const std::pair>, std::vector> &data, EnforcerBlockerType test_state); - int num_facets(EnforcerBlockerType state) const; + bool has_facets(TriangleStateType state) const; + static bool has_facets(const TriangleSplittingData &data, TriangleStateType test_state); + int num_facets(TriangleStateType state) const; // Get facets at a given state. Don't triangulate T-joints. - indexed_triangle_set get_facets(EnforcerBlockerType state) const; + indexed_triangle_set get_facets(TriangleStateType state) const; // Get facets at a given state. Triangulate T-joints. - indexed_triangle_set get_facets_strict(EnforcerBlockerType state) const; + indexed_triangle_set get_facets_strict(TriangleStateType state) const; // Get edges around the selected area by seed fill. std::vector get_seed_fill_contour() const; // Set facet of the mesh to a given state. Only works for original triangles. - void set_facet(int facet_idx, EnforcerBlockerType state); + void set_facet(int facet_idx, TriangleStateType state); // Clear everything and make the tree empty. void reset(); @@ -238,17 +326,20 @@ public: // Store the division trees in compact form (a long stream of bits for each triangle of the original mesh). // First vector contains pairs of (triangle index, first bit in the second vector). - std::pair>, std::vector> serialize() const; + TriangleSplittingData serialize() const; // Load serialized data. Assumes that correct mesh is loaded. - void deserialize(const std::pair>, std::vector> &data, bool needs_reset = true); + void deserialize(const TriangleSplittingData &data, bool needs_reset = true); + + // Extract all used facet states from the given TriangleSplittingData. + static std::vector extract_used_facet_states(const TriangleSplittingData &data); // For all triangles, remove the flag indicating that the triangle was selected by seed fill. void seed_fill_unselect_all_triangles(); - // For all triangles selected by seed fill, set new EnforcerBlockerType and remove flag indicating that triangle was selected by seed fill. + // For all triangles selected by seed fill, set new TriangleStateType and remove flag indicating that triangle was selected by seed fill. // The operation may merge split triangles if they are being assigned the same color. - void seed_fill_apply_on_triangles(EnforcerBlockerType new_state); + void seed_fill_apply_on_triangles(TriangleStateType new_state); protected: // Triangle and info about how it's split. @@ -256,7 +347,7 @@ protected: public: // Use TriangleSelector::push_triangle to create a new triangle. // It increments/decrements reference counter on vertices. - Triangle(int a, int b, int c, int source_triangle, const EnforcerBlockerType init_state) + Triangle(int a, int b, int c, int source_triangle, const TriangleStateType init_state) : verts_idxs{a, b, c}, source_triangle{source_triangle}, state{init_state} @@ -278,8 +369,8 @@ protected: void set_division(int sides_to_split, int special_side_idx); // Get/set current state. - void set_state(EnforcerBlockerType type) { assert(! is_split()); state = type; } - EnforcerBlockerType get_state() const { assert(! is_split()); return state; } + void set_state(TriangleStateType type) { assert(! is_split()); state = type; } + TriangleStateType get_state() const { assert(! is_split()); return state; } // Set if the triangle has been selected or unselected by seed fill. void select_by_seed_fill() { assert(! is_split()); m_selected_by_seed_fill = true; } @@ -303,7 +394,7 @@ protected: // or index of a vertex shared by the two split edges (for number_of_splits == 2). // For number_of_splits == 3, special_side_idx is always zero. char special_side_idx { 0 }; - EnforcerBlockerType state; + TriangleStateType state; bool m_selected_by_seed_fill : 1; // Is this triangle valid or marked to be removed? bool m_valid : 1; @@ -341,14 +432,14 @@ protected: // Private functions: private: - bool select_triangle(int facet_idx, EnforcerBlockerType type, bool triangle_splitting); - bool select_triangle_recursive(int facet_idx, const Vec3i &neighbors, EnforcerBlockerType type, bool triangle_splitting); + bool select_triangle(int facet_idx, TriangleStateType type, bool triangle_splitting); + bool select_triangle_recursive(int facet_idx, const Vec3i &neighbors, TriangleStateType type, bool triangle_splitting); void undivide_triangle(int facet_idx); void split_triangle(int facet_idx, const Vec3i &neighbors); void remove_useless_children(int facet_idx); // No hidden meaning. Triangles are meant. bool is_facet_clipped(int facet_idx, const ClippingPlane &clp) const; - int push_triangle(int a, int b, int c, int source_triangle, EnforcerBlockerType state = EnforcerBlockerType{0}); - void perform_split(int facet_idx, const Vec3i &neighbors, EnforcerBlockerType old_state); + int push_triangle(int a, int b, int c, int source_triangle, TriangleStateType state = TriangleStateType::NONE); + void perform_split(int facet_idx, const Vec3i &neighbors, TriangleStateType old_state); Vec3i child_neighbors(const Triangle &tr, const Vec3i &neighbors, int child_idx) const; Vec3i child_neighbors_propagated(const Triangle &tr, const Vec3i &neighbors_propagated, int child_idx, const Vec3i &child_neighbors) const; // Return child of itriangle at a CCW oriented side (vertexi, vertexj), either first or 2nd part. @@ -377,7 +468,7 @@ private: void get_facets_strict_recursive( const Triangle &tr, const Vec3i &neighbors, - EnforcerBlockerType state, + TriangleStateType state, std::vector &out_triangles) const; void get_facets_split_by_tjoints(const Vec3i &vertices, const Vec3i &neighbors, std::vector &out_triangles) const; diff --git a/src/libslic3r/TriangleSelectorWrapper.cpp b/src/libslic3r/TriangleSelectorWrapper.cpp index 67b79e3..57421ab 100644 --- a/src/libslic3r/TriangleSelectorWrapper.cpp +++ b/src/libslic3r/TriangleSelectorWrapper.cpp @@ -1,5 +1,13 @@ #include "TriangleSelectorWrapper.hpp" +#include #include +#include +#include + +#include "admesh/stl.h" +#include "libslic3r/AABBTreeIndirect.hpp" +#include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/TriangleSelector.hpp" namespace Slic3r { @@ -25,7 +33,7 @@ void TriangleSelectorWrapper::enforce_spot(const Vec3f &point, const Vec3f &orig if ((point - pos).norm() < radius && face_normal.dot(dir) < 0) { std::unique_ptr cursor = std::make_unique( pos, origin, radius, this->mesh_transform, TriangleSelector::ClippingPlane { }); - selector.select_patch(hit.id, std::move(cursor), EnforcerBlockerType::ENFORCER, trafo_no_translate, + selector.select_patch(hit.id, std::move(cursor), TriangleStateType::ENFORCER, trafo_no_translate, true, eps_angle); break; } @@ -38,7 +46,7 @@ void TriangleSelectorWrapper::enforce_spot(const Vec3f &point, const Vec3f &orig if (dist < radius) { std::unique_ptr cursor = std::make_unique( point, origin, radius, this->mesh_transform, TriangleSelector::ClippingPlane { }); - selector.select_patch(hit_idx_out, std::move(cursor), EnforcerBlockerType::ENFORCER, + selector.select_patch(hit_idx_out, std::move(cursor), TriangleStateType::ENFORCER, trafo_no_translate, true, eps_angle); } diff --git a/src/libslic3r/TriangleSelectorWrapper.hpp b/src/libslic3r/TriangleSelectorWrapper.hpp index 22c61d6..0f11e63 100644 --- a/src/libslic3r/TriangleSelectorWrapper.hpp +++ b/src/libslic3r/TriangleSelectorWrapper.hpp @@ -4,8 +4,10 @@ #include "TriangleSelector.hpp" #include "Model.hpp" #include "AABBTreeIndirect.hpp" +#include "libslic3r/Point.hpp" namespace Slic3r { +class TriangleMesh; //NOTE: We need to replace the FacetsAnnotation struct for support storage (or extend/add another) // Problems: Does not support negative volumes, strange usage for supports computed from extrusion - diff --git a/src/libslic3r/TriangleSetSampling.cpp b/src/libslic3r/TriangleSetSampling.cpp index bb03ff6..65f1964 100644 --- a/src/libslic3r/TriangleSetSampling.cpp +++ b/src/libslic3r/TriangleSetSampling.cpp @@ -1,8 +1,13 @@ #include "TriangleSetSampling.hpp" +#include +#include +#include #include #include -#include -#include +#include +#include + +#include "admesh/stl.h" namespace Slic3r { @@ -29,8 +34,9 @@ TriangleSetSamples sample_its_uniform_parallel(size_t samples_count, const index } std::mt19937_64 mersenne_engine { 27644437 }; + // Use boost instead of std to ensure stability accross platforms! // random numbers on interval [0, 1) - std::uniform_real_distribution fdistribution; + boost::random::uniform_real_distribution fdistribution; auto get_random = [&fdistribution, &mersenne_engine]() { return Vec3d { fdistribution(mersenne_engine), fdistribution(mersenne_engine), fdistribution(mersenne_engine) }; diff --git a/src/libslic3r/TriangleSetSampling.hpp b/src/libslic3r/TriangleSetSampling.hpp index 28a661d..0c72b07 100644 --- a/src/libslic3r/TriangleSetSampling.hpp +++ b/src/libslic3r/TriangleSetSampling.hpp @@ -2,8 +2,14 @@ #define SRC_LIBSLIC3R_TRIANGLESETSAMPLING_HPP_ #include +#include +#include +#include + #include "libslic3r/Point.hpp" +struct indexed_triangle_set; + namespace Slic3r { struct TriangleSetSamples { diff --git a/src/libslic3r/Triangulation.cpp b/src/libslic3r/Triangulation.cpp index 81d8761..a6086bb 100644 --- a/src/libslic3r/Triangulation.cpp +++ b/src/libslic3r/Triangulation.cpp @@ -1,9 +1,22 @@ #include "Triangulation.hpp" -#include "IntersectionPoints.hpp" + #include #include #include #include +#include +#include +#include +#include +#include + +#include "IntersectionPoints.hpp" +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Exception.hpp" +#include "libslic3r/Line.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/libslic3r.h" using namespace Slic3r; namespace priv{ @@ -69,6 +82,7 @@ inline bool has_self_intersection( //#define VISUALIZE_TRIANGULATION #ifdef VISUALIZE_TRIANGULATION #include "admesh/stl.h" // indexed triangle set + static void visualize(const Points &points, const Triangulation::Indices &indices, const char *filename) diff --git a/src/libslic3r/Triangulation.hpp b/src/libslic3r/Triangulation.hpp index 2a6ff81..3723119 100644 --- a/src/libslic3r/Triangulation.hpp +++ b/src/libslic3r/Triangulation.hpp @@ -1,11 +1,13 @@ #ifndef libslic3r_Triangulation_hpp_ #define libslic3r_Triangulation_hpp_ -#include -#include #include #include #include +#include +#include +#include +#include namespace Slic3r { diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index 29246e0..5267ed2 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -21,6 +21,7 @@ extern void set_logging_level(unsigned int level); extern unsigned get_logging_level(); // Format memory allocated, separate thousands by comma. extern std::string format_memsize_MB(size_t n); +extern std::string format_memsize(size_t bytes, unsigned int decimals = 1); // Return string to be added to the boost::log output to inform about the current process memory allocation. // The string is non-empty if the loglevel >= info (3) or ignore_loglevel==true. // Latter is used to get the memory info from SysInfoDialog. @@ -58,6 +59,7 @@ std::string custom_shapes_dir(); void set_custom_gcodes_dir(const std::string &path); // Return a full path to the system shapes gallery directory. const std::string& custom_gcodes_dir(); + // Set a path with preset files. void set_data_dir(const std::string &path); // Return a full path to the GUI resource files. @@ -68,7 +70,6 @@ const std::string& data_dir(); // so the user knows where to search for the debugging output. std::string debug_out_path(const char *name, ...); - // Returns next utf8 sequence length. =number of bytes in string, that creates together one utf-8 character. // Starting at pos. ASCII characters returns 1. Works also if pos is in the middle of the sequence. extern size_t get_utf8_sequence_length(const std::string& text, size_t pos = 0); diff --git a/src/libslic3r/Utils/DirectoriesUtils.cpp b/src/libslic3r/Utils/DirectoriesUtils.cpp new file mode 100644 index 0000000..3c3f07a --- /dev/null +++ b/src/libslic3r/Utils/DirectoriesUtils.cpp @@ -0,0 +1,95 @@ +#include "DirectoriesUtils.hpp" +#include "libslic3r/libslic3r.h" + +#include +#include + +#if defined(_WIN32) + +#include + +static std::string GetDataDir() +{ + HRESULT hr = E_FAIL; + + std::wstring buffer; + buffer.resize(MAX_PATH); + + hr = ::SHGetFolderPathW + ( + NULL, // parent window, not used + CSIDL_APPDATA, + NULL, // access token (current user) + SHGFP_TYPE_CURRENT, // current path, not just default value + (LPWSTR)buffer.data() + ); + + if (hr == E_FAIL) + { + // directory doesn't exist, maybe we can get its default value? + hr = ::SHGetFolderPathW + ( + NULL, + CSIDL_APPDATA, + NULL, + SHGFP_TYPE_DEFAULT, + (LPWSTR)buffer.data() + ); + } + + for (int i=0; i< MAX_PATH; i++) + if (buffer.data()[i] == '\0') { + buffer.resize(i); + break; + } + + return boost::nowide::narrow(buffer); +} + +#elif defined(__linux__) + +#include +#include + +static std::string GetDataDir() +{ + std::string dir; + + char* ptr; + if ((ptr = getenv("XDG_CONFIG_HOME"))) + dir = std::string(ptr); + else { + if ((ptr = getenv("HOME"))) + dir = std::string(ptr); + else { + struct passwd* who = (struct passwd*)NULL; + if ((ptr = getenv("USER")) || (ptr = getenv("LOGNAME"))) + who = getpwnam(ptr); + // make sure the user exists! + if (!who) + who = getpwuid(getuid()); + + dir = std::string(who ? who->pw_dir : 0); + } + if (! dir.empty()) + dir += "/.config"; + } + + if (dir.empty()) + BOOST_LOG_TRIVIAL(error) << "GetDataDir() > unsupported file layout"; + + return dir; +} + +#endif + +namespace Slic3r { + +std::string get_default_datadir() +{ + const std::string config_dir = GetDataDir(); + const std::string data_dir = (boost::filesystem::path(config_dir) / SLIC3R_APP_FULL_NAME).make_preferred().string(); + return data_dir; +} + +} // namespace Slic3r diff --git a/src/libslic3r/Utils/DirectoriesUtils.hpp b/src/libslic3r/Utils/DirectoriesUtils.hpp new file mode 100644 index 0000000..cafc054 --- /dev/null +++ b/src/libslic3r/Utils/DirectoriesUtils.hpp @@ -0,0 +1,17 @@ +#ifndef slic3r_DirectoriesUtils_hpp_ +#define slic3r_DirectoriesUtils_hpp_ + +#include + +#if __APPLE__ +//implemented at MacUtils.mm +std::string GetDataDir(); +#endif //__APPLE__ + +namespace Slic3r { + +std::string get_default_datadir(); + +} // namespace Slic3r + +#endif // slic3r_DirectoriesUtils_hpp_ diff --git a/src/libslic3r/Utils/JsonUtils.cpp b/src/libslic3r/Utils/JsonUtils.cpp new file mode 100644 index 0000000..50d70cf --- /dev/null +++ b/src/libslic3r/Utils/JsonUtils.cpp @@ -0,0 +1,27 @@ +#include "JsonUtils.hpp" + +#include +#include +#include +#include + +namespace Slic3r { + +namespace pt = boost::property_tree; + +std::string write_json_with_post_process(const pt::ptree& ptree) +{ + std::stringstream oss; + pt::write_json(oss, ptree); + + // fix json-out to show node values as a string just for string nodes + std::regex reg("\\\"([0-9]+\\.{0,1}[0-9]*)\\\""); // code is borrowed from https://stackoverflow.com/questions/2855741/why-does-boost-property-tree-write-json-save-everything-as-string-is-it-possibl + std::string result = std::regex_replace(oss.str(), reg, "$1"); + + boost::replace_all(result, "\"true\"", "true"); + boost::replace_all(result, "\"false\"", "false"); + + return result; +} + +} // namespace Slic3r diff --git a/src/libslic3r/Utils/JsonUtils.hpp b/src/libslic3r/Utils/JsonUtils.hpp new file mode 100644 index 0000000..faaddff --- /dev/null +++ b/src/libslic3r/Utils/JsonUtils.hpp @@ -0,0 +1,14 @@ +#ifndef slic3r_JsonUtils_hpp_ +#define slic3r_JsonUtils_hpp_ + +#include +#include +#include + +namespace Slic3r { + +std::string write_json_with_post_process(const boost::property_tree::ptree& ptree); + +} // namespace Slic3r + +#endif // slic3r_jsonUtils_hpp_ diff --git a/src/libslic3r/Zipper.cpp b/src/libslic3r/Zipper.cpp index 329610f..957f0a9 100644 --- a/src/libslic3r/Zipper.cpp +++ b/src/libslic3r/Zipper.cpp @@ -1,10 +1,11 @@ -#include +#include +#include #include "Exception.hpp" #include "Zipper.hpp" #include "miniz_extension.hpp" -#include #include "I18N.hpp" +#include "miniz.h" #if defined(_MSC_VER) && _MSC_VER <= 1800 || __cplusplus < 201103L #define SLIC3R_NORETURN diff --git a/src/libslic3r/Zipper.hpp b/src/libslic3r/Zipper.hpp index bbaf2f0..39667bd 100644 --- a/src/libslic3r/Zipper.hpp +++ b/src/libslic3r/Zipper.hpp @@ -1,9 +1,13 @@ #ifndef ZIPPER_HPP #define ZIPPER_HPP +#include #include #include #include +#include +#include +#include namespace Slic3r { @@ -19,6 +23,7 @@ public: private: class Impl; + std::unique_ptr m_impl; std::string m_data; std::string m_entry; diff --git a/src/libslic3r/clonable_ptr.hpp b/src/libslic3r/clonable_ptr.hpp index a3b7ee2..5aa9478 100644 --- a/src/libslic3r/clonable_ptr.hpp +++ b/src/libslic3r/clonable_ptr.hpp @@ -14,6 +14,9 @@ * or copy at http://opensource.org/licenses/MIT) */ +#ifndef libslic3r_clonable_ptr_hpp_ +#define libslic3r_clonable_ptr_hpp_ + #include "assert.h" namespace Slic3r { @@ -166,3 +169,5 @@ template inline bool operator>(const clonable_ptr& l, const } } // namespace Slic3r + +#endif // libslic3r_clonable_ptr_hpp_ diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index 3f4cc44..19f08b6 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -2,6 +2,12 @@ #define _libslic3r_h_ #include "libslic3r_version.h" + +// Profiles for the alpha are stored into the QIDISlicer-alpha directory to not mix with the current release. +#define SLIC3R_APP_FULL_NAME SLIC3R_APP_KEY +//#define SLIC3R_APP_FULL_NAME SLIC3R_APP_KEY "-alpha" +//#define SLIC3R_APP_FULL_NAME SLIC3R_APP_KEY "-beta" + #define GCODEVIEWER_APP_NAME "QIDISlicer G-code Viewer" #define GCODEVIEWER_APP_KEY "QIDISlicerGcodeViewer" diff --git a/src/libslic3r/miniz_extension.cpp b/src/libslic3r/miniz_extension.cpp index bee7676..43acbee 100644 --- a/src/libslic3r/miniz_extension.cpp +++ b/src/libslic3r/miniz_extension.cpp @@ -1,11 +1,16 @@ -#include +#include #include "miniz_extension.hpp" +#include "miniz.h" #if defined(_MSC_VER) || defined(__MINGW64__) #include "boost/nowide/cstdio.hpp" #endif +#if defined(__linux__) +#include +#endif + #include "libslic3r/I18N.hpp" namespace Slic3r { diff --git a/src/libslic3r/pchheader.hpp b/src/libslic3r/pchheader.hpp index e71b146..78ea5e7 100644 --- a/src/libslic3r/pchheader.hpp +++ b/src/libslic3r/pchheader.hpp @@ -87,12 +87,11 @@ #include #include #include -#include #include #include #include #include -#include +#include #include // boost/property_tree/json_parser/detail/parser.hpp includes boost/bind.hpp, which is deprecated. @@ -118,20 +117,20 @@ #include #include -#include "clipper.hpp" -#include "BoundingBox.hpp" -#include "ClipperUtils.hpp" -#include "Config.hpp" -#include "enum_bitmask.hpp" -#include "format.hpp" -#include "I18N.hpp" -#include "MultiPoint.hpp" -#include "Point.hpp" -#include "Polygon.hpp" -#include "Polyline.hpp" -#include "SVG.hpp" +#include "libslic3r/clipper.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/Config.hpp" +#include "libslic3r/enum_bitmask.hpp" +#include "libslic3r/format.hpp" +#include "libslic3r/I18N.hpp" +#include "libslic3r/MultiPoint.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/Polyline.hpp" +#include "libslic3r/SVG.hpp" -#include "libslic3r.h" +#include "libslic3r/libslic3r.h" #include "libslic3r_version.h" #include diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index 5a25795..6d56ead 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -120,7 +120,7 @@ unsigned get_logging_level() // Force set_logging_level(<=error) after loading of the DLL. // This is used ot disable logging for unit and integration tests. static struct RunOnInit { - RunOnInit() { + RunOnInit() { set_logging_level(1); } } g_RunOnInit; @@ -203,6 +203,7 @@ const std::string& custom_gcodes_dir() { return g_custom_gcodes_dir; } + // Translate function callback, to call wxWidgets translate function to convert non-localized UTF8 string to a localized one. Slic3r::I18N::translate_fn_type Slic3r::I18N::translate_fn = nullptr; @@ -790,7 +791,7 @@ bool is_idx_file(const boost::filesystem::directory_entry &dir_entry) bool is_gcode_file(const std::string &path) { - return boost::iends_with(path, ".gcode") || boost::iends_with(path, ".gco") || + return boost::iends_with(path, ".gcode") || boost::iends_with(path, ".gco") || boost::iends_with(path, ".g") || boost::iends_with(path, ".ngc") || boost::iends_with(path, ".bgcode") || boost::iends_with(path, ".bgc"); } @@ -1076,6 +1077,42 @@ std::string format_memsize_MB(size_t n) return out + "MB"; } +std::string format_memsize(size_t bytes, unsigned int decimals) +{ + static constexpr const float kb = 1024.0f; + static constexpr const float mb = 1024.0f * kb; + static constexpr const float gb = 1024.0f * mb; + static constexpr const float tb = 1024.0f * gb; + + const float f_bytes = static_cast(bytes); + if (f_bytes < kb) + return std::to_string(bytes) + " bytes"; + else if (f_bytes < mb) { + const float f_kb = f_bytes / kb; + char buf[64]; + sprintf(buf, "%.*f", decimals, f_kb); + return std::to_string(bytes) + " bytes (" + std::string(buf) + "KB)"; + } + else if (f_bytes < gb) { + const float f_mb = f_bytes / mb; + char buf[64]; + sprintf(buf, "%.*f", decimals, f_mb); + return std::to_string(bytes) + " bytes (" + std::string(buf) + "MB)"; + } + else if (f_bytes < tb) { + const float f_gb = f_bytes / gb; + char buf[64]; + sprintf(buf, "%.*f", decimals, f_gb); + return std::to_string(bytes) + " bytes (" + std::string(buf) + "GB)"; + } + else { + const float f_tb = f_bytes / tb; + char buf[64]; + sprintf(buf, "%.*f", decimals, f_tb); + return std::to_string(bytes) + " bytes (" + std::string(buf) + "TB)"; + } +} + // Returns platform-specific string to be used as log output or parsed in SysInfoDialog. // The latter parses the string with (semi)colons as separators, it should look about as // "desc1: value1; desc2: value2" or similar (spaces should not matter). @@ -1118,7 +1155,7 @@ std::string log_memory_info(bool ignore_loglevel) out += "N/A"; #else // i.e. __linux__ size_t tSize = 0, resident = 0, share = 0; - std::ifstream buffer("/proc/self/statm"); + boost::nowide::ifstream buffer("/proc/self/statm"); if (buffer && (buffer >> tSize >> resident >> share)) { size_t page_size = (size_t)sysconf(_SC_PAGE_SIZE); // in case x86-64 is configured to use 2MB pages size_t rss = resident * page_size; diff --git a/src/platform/unix/PrusaGcodeviewer.desktop b/src/platform/unix/QIDIGcodeviewer.desktop similarity index 100% rename from src/platform/unix/PrusaGcodeviewer.desktop rename to src/platform/unix/QIDIGcodeviewer.desktop diff --git a/src/platform/unix/PrusaSlicer.desktop b/src/platform/unix/QIDISlicer.desktop similarity index 100% rename from src/platform/unix/PrusaSlicer.desktop rename to src/platform/unix/QIDISlicer.desktop