diff --git a/build_release_macos.sh b/build_release_macos.sh
index cdc5dbbe3c..9eca2e7f40 100755
--- a/build_release_macos.sh
+++ b/build_release_macos.sh
@@ -47,7 +47,7 @@ while getopts ":dpa:snt:xbc:i:j:Tuh" opt; do
u )
export BUILD_TARGET="universal"
;;
- h ) echo "Usage: ./build_release_macos.sh [-d]"
+ h ) echo "Usage: ./build_release_macos.sh [-d] [-- CMAKE_ARGS...]"
echo " -d: Build deps only"
echo " -a: Set ARCHITECTURE (arm64 or x86_64 or universal)"
echo " -s: Build slicer only"
@@ -60,13 +60,33 @@ while getopts ":dpa:snt:xbc:i:j:Tuh" opt; do
echo " -i: Add a prefix to ignore during CMake dependency discovery (repeatable), defaults to /opt/local:/usr/local:/opt/homebrew"
echo " -j: Set the number of parallel build jobs (CMAKE_BUILD_PARALLEL_LEVEL)"
echo " -T: Build and run tests (set ORCA_TESTS_BUILD_ONLY=1 to build without running)"
+ echo " -- CMAKE_ARGS...: pass everything after '--' to the cmake configure step of the deps (-d) and slicer (-s) builds,"
+ echo " e.g. -DDESTDIR=
for the deps, -DCMAKE_PREFIX_PATH=/usr/local -DSLIC3R_PCH=OFF for the slicer, --fresh."
+ echo " They come last on the cmake command line. Use absolute paths: cmake runs in the build directory."
exit 0
;;
+ : )
+ echo "Option -${OPTARG} requires an argument, aborting."
+ exit 1
+ ;;
* )
+ echo "Unknown option -${OPTARG}, aborting. CMake arguments must follow '--'."
+ exit 1
;;
esac
done
+# getopts stops at '--'; whatever follows is handed to cmake unchanged.
+if [[ ${OPTIND} -le $# ]] ; then
+ separator_index=$((OPTIND-1))
+ if [[ "${!separator_index}" != "--" ]] ; then
+ echo "Unexpected argument '${!OPTIND}': cmake arguments must follow '--', aborting."
+ exit 1
+ fi
+fi
+shift $((OPTIND-1))
+EXTRA_CMAKE_ARGS=("$@")
+
# Set defaults
if [ -z "$ARCH" ]; then
@@ -211,7 +231,8 @@ function build_deps() {
-DCMAKE_OSX_DEPLOYMENT_TARGET="${OSX_DEPLOYMENT_TARGET}" \
-DCMAKE_IGNORE_PREFIX_PATH="${CMAKE_IGNORE_PREFIX_PATH}" \
"${CMAKE_CCACHE_ARGS[@]}" \
- ${CMAKE_POLICY_COMPAT}
+ ${CMAKE_POLICY_COMPAT} \
+ "${EXTRA_CMAKE_ARGS[@]}"
fi
cmake --build . --config "$BUILD_CONFIG" --target deps
)
@@ -314,7 +335,8 @@ function build_slicer() {
-DCMAKE_OSX_DEPLOYMENT_TARGET="${OSX_DEPLOYMENT_TARGET}" \
-DCMAKE_IGNORE_PREFIX_PATH="${CMAKE_IGNORE_PREFIX_PATH}" \
"${CMAKE_CCACHE_ARGS[@]}" \
- ${CMAKE_POLICY_COMPAT}
+ ${CMAKE_POLICY_COMPAT} \
+ "${EXTRA_CMAKE_ARGS[@]}"
fi
cmake --build . --config "$BUILD_CONFIG" --target "$SLICER_BUILD_TARGET"
)
diff --git a/deps/GMP/GMP.cmake b/deps/GMP/GMP.cmake
index b3ba181753..4f2beedb4f 100644
--- a/deps/GMP/GMP.cmake
+++ b/deps/GMP/GMP.cmake
@@ -59,9 +59,13 @@ else ()
set(_cross_compile_arg --host=${TOOLCHAIN_PREFIX})
endif ()
+ # 6.3.0 is the first release whose arm64 assembly avoids x18, which macOS
+ # reserves and zeroes on every return to user space; 6.2.1 crashes on Apple
+ # silicon whenever CGAL falls back to exact arithmetic.
ExternalProject_Add(dep_GMP
- URL https://github.com/SoftFever/OrcaSlicer_deps/releases/download/gmp-6.2.1/gmp-6.2.1.tar.bz2
- URL_HASH SHA256=eae9326beb4158c386e39a356818031bd28f3124cf915f8c5b1dc4c7a36b4d7c
+ URL https://ftp.gnu.org/gnu/gmp/gmp-6.3.0.tar.xz
+ https://github.com/SoftFever/OrcaSlicer_deps/releases/download/gmp-6.3.0/gmp-6.3.0.tar.xz
+ URL_HASH SHA256=a3c2b80201b89e68616f4ad30bc66aee4927c3ce50e33929ca819d5c43538898
DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/GMP
PATCH_COMMAND git apply ${GMP_DIRECTORY_FLAG} --verbose ${CMAKE_CURRENT_LIST_DIR}/0001-GMP_GCC15.patch
BUILD_IN_SOURCE ON
diff --git a/docs/HLSD/polygon-clipping.md b/docs/HLSD/polygon-clipping.md
index 60bf97d515..00a1e2863b 100644
--- a/docs/HLSD/polygon-clipping.md
+++ b/docs/HLSD/polygon-clipping.md
@@ -62,6 +62,23 @@ Clipper2 behaves differently by default, the wrapper adjusts it.
clip do not leave slivers.
- Open polylines are clipped with the non-zero rule and keep their direction.
+### Tiled booleans
+
+The sweep slows down with the number of edges crossing a scan line, so a layer
+cut into thousands of pieces makes every whole-layer boolean expensive.
+`diff_ex_by_piece()` and `intersection_ex_by_piece()` take a subject of
+non-overlapping `ExPolygons`, group them into tiles with
+`ClipperUtils::tile_expolygons()`, and run each tile in parallel against only
+the clip polygons near it, cut to the tile's box. Below 128 pieces there is a
+single tile, and they are the plain `diff_ex()` / `intersection_ex()`.
+
+The result covers the same area as the plain call. Without the safety offset
+the rings are the same. With it, each tile unites only the clip polygons near
+it, so a clip edge that the whole-layer union splits where it crosses a distant
+clip polygon stays whole, and a crossing with the subject can round 1 unit
+differently. The tiles' results are concatenated in tile order, so the order of
+the output `ExPolygons` differs from the plain call.
+
### Offsets
- Before offsetting, input vertices closer than
diff --git a/resources/profiles/Flashforge.json b/resources/profiles/Flashforge.json
index be6e64da1e..5625649e3b 100644
--- a/resources/profiles/Flashforge.json
+++ b/resources/profiles/Flashforge.json
@@ -1,7 +1,7 @@
{
"name": "Flashforge",
"url": "",
- "version": "02.04.00.11",
+ "version": "02.04.00.12",
"force_update": "0",
"description": "Flashforge configurations",
"machine_model_list": [
diff --git a/resources/profiles/Flashforge/process/0.20mm Standard @Flashforge AD5M 0.4 Nozzle.json b/resources/profiles/Flashforge/process/0.20mm Standard @Flashforge AD5M 0.4 Nozzle.json
index 498f9c3064..849bc46c44 100644
--- a/resources/profiles/Flashforge/process/0.20mm Standard @Flashforge AD5M 0.4 Nozzle.json
+++ b/resources/profiles/Flashforge/process/0.20mm Standard @Flashforge AD5M 0.4 Nozzle.json
@@ -8,6 +8,7 @@
"compatible_printers": [
"Flashforge Adventurer 5M 0.4 Nozzle"
],
+ "exclude_object_tolerance": "0.2",
"filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode",
"post_process": "",
"only_one_wall_top": "0",
diff --git a/resources/profiles/Flashforge/process/0.20mm Standard @Flashforge AD5M Pro 0.4 Nozzle.json b/resources/profiles/Flashforge/process/0.20mm Standard @Flashforge AD5M Pro 0.4 Nozzle.json
index cf9f12b7f2..bb45f2a088 100644
--- a/resources/profiles/Flashforge/process/0.20mm Standard @Flashforge AD5M Pro 0.4 Nozzle.json
+++ b/resources/profiles/Flashforge/process/0.20mm Standard @Flashforge AD5M Pro 0.4 Nozzle.json
@@ -8,6 +8,7 @@
"compatible_printers": [
"Flashforge Adventurer 5M Pro 0.4 Nozzle"
],
+ "exclude_object_tolerance": "0.2",
"filename_format": "{input_filename_base}_{filament_type[0]}_{print_time}.gcode",
"post_process": "",
"only_one_wall_top": "0",
diff --git a/resources/profiles/Flashforge/process/0.30mm Standard @Flashforge AD5M 0.6 Nozzle.json b/resources/profiles/Flashforge/process/0.30mm Standard @Flashforge AD5M 0.6 Nozzle.json
index 7540593b77..4ee2bd6afa 100644
--- a/resources/profiles/Flashforge/process/0.30mm Standard @Flashforge AD5M 0.6 Nozzle.json
+++ b/resources/profiles/Flashforge/process/0.30mm Standard @Flashforge AD5M 0.6 Nozzle.json
@@ -8,6 +8,7 @@
"compatible_printers": [
"Flashforge Adventurer 5M 0.6 Nozzle"
],
+ "exclude_object_tolerance": "0.2",
"filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode",
"post_process": "",
"only_one_wall_top": "0",
diff --git a/resources/profiles/Flashforge/process/0.30mm Standard @Flashforge AD5M Pro 0.6 Nozzle.json b/resources/profiles/Flashforge/process/0.30mm Standard @Flashforge AD5M Pro 0.6 Nozzle.json
index efc774872f..5fefdc83e7 100644
--- a/resources/profiles/Flashforge/process/0.30mm Standard @Flashforge AD5M Pro 0.6 Nozzle.json
+++ b/resources/profiles/Flashforge/process/0.30mm Standard @Flashforge AD5M Pro 0.6 Nozzle.json
@@ -8,6 +8,7 @@
"compatible_printers": [
"Flashforge Adventurer 5M Pro 0.6 Nozzle"
],
+ "exclude_object_tolerance": "0.2",
"filename_format": "{input_filename_base}_{filament_type[initial_tool]}_{print_time}.gcode",
"post_process": "",
"only_one_wall_top": "0",
diff --git a/scripts/flatpak/com.orcaslicer.OrcaSlicer.yml b/scripts/flatpak/com.orcaslicer.OrcaSlicer.yml
index cca417f735..27c9fcfdc2 100644
--- a/scripts/flatpak/com.orcaslicer.OrcaSlicer.yml
+++ b/scripts/flatpak/com.orcaslicer.OrcaSlicer.yml
@@ -226,10 +226,10 @@ modules:
sha256: f353e7b99bd0cbfc27ac9082de51acf32a8bc0b3e21ff9661ecca6f205ec1d81
dest: external-packages/OpenVDB
- # GMP 6.2.1
+ # GMP 6.3.0
- type: file
- url: https://github.com/SoftFever/OrcaSlicer_deps/releases/download/gmp-6.2.1/gmp-6.2.1.tar.bz2
- sha256: eae9326beb4158c386e39a356818031bd28f3124cf915f8c5b1dc4c7a36b4d7c
+ url: https://ftp.gnu.org/gnu/gmp/gmp-6.3.0.tar.xz
+ sha256: a3c2b80201b89e68616f4ad30bc66aee4927c3ce50e33929ca819d5c43538898
dest: external-packages/GMP
# MPFR 4.2.2
diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp
index 8e9decf37a..0b4033ce8f 100644
--- a/src/libslic3r/ClipperUtils.cpp
+++ b/src/libslic3r/ClipperUtils.cpp
@@ -7,9 +7,13 @@
#include
#include
#include
+#include
#include
+#include
+
#include "ClipperUtils.hpp"
+#include "AABBTreeIndirect.hpp"
#include "BoundingBox.hpp"
#include "ExPolygon.hpp"
#include "Point.hpp"
@@ -276,6 +280,7 @@ Polylines c2_to_polylines(const C2::Paths64 &paths)
return out;
}
+// Only sizes the reserve of c2_to_expolygons(): for a tree Clipper2 misnested (see below) the count is approximate.
size_t c2_count_expolygons(const C2::PolyPath64 &outer)
{
size_t cnt = 1;
@@ -285,17 +290,90 @@ size_t c2_count_expolygons(const C2::PolyPath64 &outer)
return cnt;
}
-void c2_append_expolygons(const C2::PolyPath64 &outer, ExPolygons &out)
+// Clipper2 sometimes puts a path at the wrong level of its PolyTree: a hole beside the outer path around it instead of
+// under it, or an island at the level of holes. Taken as they are, a misplaced hole becomes a clockwise contour and its
+// outer an expolygon that covers it; whole-layer booleans cancel the two, but anything handling expolygons one at a time
+// sees the hole filled. So the orientation of a path decides its role: a clockwise path where a contour belongs is
+// collected in stray_holes, for c2_attach_stray_holes() to give to the contour around it.
+void c2_append_expolygons(const C2::PolyPath64 &outer, ExPolygons &out, std::vector &stray_holes)
{
- ExPolygon &expoly = out.emplace_back();
- expoly.contour.points = c2_to_points(outer.Polygon());
- expoly.holes.reserve(outer.Count());
- for (const auto &hole : outer)
- expoly.holes.emplace_back().points = c2_to_points(hole->Polygon());
- // Islands inside the holes.
- for (const auto &hole : outer)
- for (const auto &island : *hole)
- c2_append_expolygons(*island, out);
+ if (C2::Area(outer.Polygon()) < 0.) {
+ stray_holes.emplace_back(&outer.Polygon());
+ for (const auto &island : outer)
+ c2_append_expolygons(*island, out, stray_holes);
+ return;
+ }
+ // Indexed, as the recursion below may reallocate out.
+ const size_t idx = out.size();
+ out.emplace_back().contour.points = c2_to_points(outer.Polygon());
+ out[idx].holes.reserve(outer.Count());
+ for (const auto &child : outer)
+ if (C2::Area(child->Polygon()) > 0.)
+ c2_append_expolygons(*child, out, stray_holes);
+ else {
+ out[idx].holes.emplace_back().points = c2_to_points(child->Polygon());
+ // Islands inside the hole.
+ for (const auto &island : *child)
+ c2_append_expolygons(*island, out, stray_holes);
+ }
+}
+
+// Whether outer encloses inner, two paths of one Clipper2 result. Such paths don't cross, they only touch, and rounding
+// both to the grid leaves a touching vertex up to about 1.5 units across the other path. So a vertex of inner that close
+// to outer doesn't count, the first vertex clear of it decides, and a path touching at every vertex goes by a point
+// strictly inside it: either lies on the same side of outer as the whole path. A sliver too thin to hold such a point is
+// taken as not enclosed. A hole closely hugging an island inside it would pass, so callers skip any outer no larger than
+// inner: a path inside another is always the smaller.
+bool path_encloses(const Polygon &outer, const Polygon &inner)
+{
+ // The rounding of the two paths, and of the projection Polygon::on_boundary() measures to.
+ static constexpr double touching = 3.;
+ for (const Point &pt : inner.points)
+ if (! outer.on_boundary(pt, touching))
+ return contains(outer, pt, true);
+ const std::optional pt = ClipperUtils::interior_point(inner);
+ return pt && contains(outer, *pt, true);
+}
+
+// Gives each stray hole to the innermost of the contours out[first..] around it. Contours of a Clipper2 result don't
+// cross, so those around a hole are nested and the innermost one is the smallest: candidates are tried smallest first,
+// from the first one larger than the hole.
+void c2_attach_stray_holes(ExPolygons &out, size_t first, const std::vector &stray_holes)
+{
+ if (stray_holes.empty())
+ return;
+ using Tree = AABBTreeIndirect::Tree<2, coord_t>;
+ std::vector bboxes;
+ bboxes.reserve(out.size() - first);
+ for (size_t i = first; i < out.size(); ++ i)
+ bboxes.emplace_back(i, get_extents(out[i].contour));
+ Tree tree;
+ tree.build_modify_input(bboxes);
+ // Contour areas, computed when a contour first becomes a candidate.
+ std::vector areas(out.size(), 0.);
+ std::vector candidates;
+ for (const C2::Path64 *path : stray_holes) {
+ Polygon hole;
+ hole.points = c2_to_points(*path);
+ const double hole_area = - C2::Area(*path);
+ const BoundingBox bbox = get_extents(hole);
+ const Tree::BoundingBox hole_bbox(bbox.min, bbox.max);
+ candidates.clear();
+ AABBTreeIndirect::traverse(tree,
+ [&hole_bbox](const Tree::Node &node) { return node.bbox.contains(hole_bbox); },
+ [&candidates](const Tree::Node &node) { candidates.emplace_back(node.idx); return true; });
+ for (size_t idx : candidates)
+ if (areas[idx] == 0.)
+ areas[idx] = out[idx].contour.area();
+ std::sort(candidates.begin(), candidates.end(), [&areas](size_t a, size_t b) { return areas[a] < areas[b]; });
+ auto larger = std::upper_bound(candidates.begin(), candidates.end(), hole_area, [&areas](double area, size_t idx) { return area < areas[idx]; });
+ if (auto it = std::find_if(larger, candidates.end(), [&out, &hole](size_t idx) { return path_encloses(out[idx].contour, hole); });
+ it == candidates.end())
+ // Nothing around it: keep the path as Clipper2 returned it.
+ out.emplace_back().contour = std::move(hole);
+ else
+ out[*it].holes.emplace_back(std::move(hole));
+ }
}
ExPolygons c2_to_expolygons(const C2::PolyTree64 &tree)
@@ -305,8 +383,10 @@ ExPolygons c2_to_expolygons(const C2::PolyTree64 &tree)
cnt += c2_count_expolygons(*outer);
ExPolygons out;
out.reserve(cnt);
+ std::vector stray_holes;
for (const auto &outer : tree)
- c2_append_expolygons(*outer, out);
+ c2_append_expolygons(*outer, out, stray_holes);
+ c2_attach_stray_holes(out, 0, stray_holes);
return out;
}
@@ -800,6 +880,114 @@ Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r
{ return _clipper_ex(ctIntersection, ClipperUtils::SurfacesProvider(subject), ClipperUtils::SurfacesProvider(clip), do_safety_offset); }
Slic3r::ExPolygons intersection_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset)
{ return _clipper_ex(ctIntersection, ClipperUtils::SurfacesPtrProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); }
+
+namespace ClipperUtils {
+ std::vector tile_expolygons(const ExPolygons &expolygons, size_t per_tile)
+ {
+ BoundingBox extent;
+ std::vector bboxes;
+ bboxes.reserve(expolygons.size());
+ for (const ExPolygon &expoly : expolygons) {
+ bboxes.emplace_back(get_extents(expoly));
+ extent.merge(bboxes.back());
+ }
+ if (! extent.defined)
+ return {};
+ const int tiles = std::clamp(int(std::sqrt(double(expolygons.size()) / double(std::max(per_tile, 1)))), 1, 32);
+ const Point size = extent.size();
+ const coord_t tile_w = std::max(1, size.x() / tiles + 1), tile_h = std::max(1, size.y() / tiles + 1);
+ std::vector out(size_t(tiles * tiles));
+ for (size_t i = 0; i < expolygons.size(); ++ i) {
+ const Point c = bboxes[i].center();
+ ExPolygonsTile &tile = out[size_t(std::clamp(int((c.y() - extent.min.y()) / tile_h), 0, tiles - 1) * tiles +
+ std::clamp(int((c.x() - extent.min.x()) / tile_w), 0, tiles - 1))];
+ tile.members.emplace_back(i);
+ tile.bbox.merge(bboxes[i]);
+ }
+ out.erase(std::remove_if(out.begin(), out.end(), [](const ExPolygonsTile &tile) { return tile.members.empty(); }), out.end());
+ return out;
+ }
+
+ // Between two consecutive heights of its vertices the polygon is a set of trapezoids, and the line through the middle
+ // of that band meets no vertex and no horizontal edge: its crossings with the edges alternate into and out of the
+ // polygon, and there each trapezoid is at least half as wide as at its widest. So the bands are tried widest first,
+ // until the middle of the widest span on that line rounds to a point strictly inside. A band under two units has no
+ // grid line inside it.
+ std::optional interior_point(const Polygon &polygon)
+ {
+ std::vector heights;
+ heights.reserve(polygon.size());
+ for (const Point &pt : polygon.points)
+ heights.emplace_back(pt.y());
+ std::sort(heights.begin(), heights.end());
+ heights.erase(std::unique(heights.begin(), heights.end()), heights.end());
+ std::vector> bands;
+ for (size_t i = 1; i < heights.size(); ++ i)
+ if (heights[i] - heights[i - 1] >= 2)
+ bands.emplace_back(heights[i - 1], heights[i]);
+ std::sort(bands.begin(), bands.end(), [](const auto &a, const auto &b) { return a.second - a.first > b.second - b.first; });
+ std::vector crossings;
+ for (const auto &[low, high] : bands) {
+ const coord_t y = low + (high - low) / 2;
+ crossings.clear();
+ for (size_t i = 0, j = polygon.size() - 1; i < polygon.size(); j = i ++) {
+ const Point &a = polygon.points[j];
+ const Point &b = polygon.points[i];
+ if ((a.y() > y) != (b.y() > y))
+ crossings.emplace_back(double(a.x()) + double(y - a.y()) * double(b.x() - a.x()) / double(b.y() - a.y()));
+ }
+ std::sort(crossings.begin(), crossings.end());
+ // Inside from the first crossing to the second, from the third to the fourth...
+ double left = 0., right = 0.;
+ for (size_t i = 1; i < crossings.size(); i += 2)
+ if (crossings[i] - crossings[i - 1] > right - left) {
+ left = crossings[i - 1];
+ right = crossings[i];
+ }
+ if (const Point pt(coord_t(std::round(0.5 * (left + right))), y); right > left && contains(polygon, pt, false))
+ return pt;
+ }
+ return std::nullopt;
+ }
+}
+
+static Slic3r::ExPolygons clipper_ex_by_piece(ClipType clipType, const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
+{
+ // A few dozen subject ExPolygons to a tile, each tile one Clipper call with the clip cut to the tile's box.
+ const std::vector tiles = ClipperUtils::tile_expolygons(subject, 32);
+ // One tile is the plain call: cutting the clip would only cost time.
+ if (tiles.size() <= 1)
+ return _clipper_ex(clipType, ClipperUtils::ExPolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset);
+ std::vector clip_bboxes;
+ clip_bboxes.reserve(clip.size());
+ for (const Polygon &polygon : clip)
+ clip_bboxes.emplace_back(get_extents(polygon));
+
+ std::vector out_tiles(tiles.size());
+ tbb::parallel_for(size_t(0), tiles.size(), [&](size_t tile_idx) {
+ const ClipperUtils::ExPolygonsTile &tile = tiles[tile_idx];
+ Slic3r::ExPolygons local_subject;
+ local_subject.reserve(tile.members.size());
+ for (size_t i : tile.members)
+ local_subject.emplace_back(subject[i]);
+ // Grown so that the cut edges of the clip stay clear of the subject, also after the safety offset.
+ const BoundingBox bbox = tile.bbox.inflated(SCALED_EPSILON);
+ Polygons local_clip;
+ for (size_t i = 0; i < clip.size(); ++i)
+ if (clip_bboxes[i].overlap(bbox))
+ if (Polygon clipped = ClipperUtils::clip_clipper_polygon_with_subject_bbox(clip[i], bbox); ! clipped.empty())
+ local_clip.emplace_back(std::move(clipped));
+ out_tiles[tile_idx] = _clipper_ex(clipType, ClipperUtils::ExPolygonsProvider(local_subject), ClipperUtils::PolygonsProvider(local_clip), do_safety_offset);
+ });
+ Slic3r::ExPolygons out;
+ for (Slic3r::ExPolygons &out_tile : out_tiles)
+ append(out, std::move(out_tile));
+ return out;
+}
+Slic3r::ExPolygons diff_ex_by_piece(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
+ { return clipper_ex_by_piece(ctDifference, subject, clip, do_safety_offset); }
+Slic3r::ExPolygons intersection_ex_by_piece(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
+ { return clipper_ex_by_piece(ctIntersection, subject, clip, do_safety_offset); }
// May be used to "heal" unusual models (3DLabPrints etc.) by providing fill_type (pftEvenOdd, pftNonZero, pftPositive, pftNegative).
Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, PolyFillType fill_type)
{ return _clipper_ex(ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ApplySafetyOffset::No, fill_type); }
@@ -1007,24 +1195,75 @@ Lines _clipper_ln(ClipType clipType, const Lines &subject, const Polygons &clip)
return retval;
}
-static void traverse_pt_outside_in(const C2::PolyPath64 &parent, Polygons *retval)
+// Chains the loops of one level of the tree, each followed by the loops under it, depth first. Clipper2 may put a loop
+// beside the loop around it instead of under it (see c2_append_expolygons()), out of the nesting order the callers rely
+// on: the spiral of FillSpiralInset continues only into a loop inside the last one. So a loop whose orientation doesn't
+// fit its level (CCW under the top or a CW loop, CW under a CCW loop) goes under the smallest loop of the level around
+// it, the one Clipper2 failed to put it under, and stays where it is if none is.
+static void traverse_pt_outside_in(const std::vector &nodes, bool holes, Polygons *retval)
{
- // collect ordering points
- Points ordering_points;
- ordering_points.reserve(parent.Count());
- for (const auto &node : parent)
- ordering_points.emplace_back(node->Polygon().front().x, node->Polygon().front().y);
+ struct Loop
+ {
+ const C2::PolyPath64 *node;
+ double area;
+ // The loops to traverse under this one: its children, then the misplaced loops it takes in.
+ std::vector next;
+ bool moved = false;
+ };
+ std::vector loops;
+ std::vector fitting, misfits;
+ loops.reserve(nodes.size());
+ for (const C2::PolyPath64 *node : nodes) {
+ Loop &loop = loops.emplace_back(Loop{ node, C2::Area(node->Polygon()), {} });
+ loop.next.reserve(node->Count());
+ for (const auto &child : *node)
+ loop.next.emplace_back(child.get());
+ ((loop.area < 0.) == holes ? fitting : misfits).emplace_back(loops.size() - 1);
+ }
+ if (! misfits.empty()) {
+ // Candidates are tried smallest first, from the first one larger than the misplaced loop (see path_encloses()).
+ std::sort(fitting.begin(), fitting.end(), [&loops](size_t a, size_t b) { return std::abs(loops[a].area) < std::abs(loops[b].area); });
+ std::vector bounds(loops.size());
+ for (size_t i = 0; i < loops.size(); ++ i)
+ bounds[i] = C2::GetBounds(loops[i].node->Polygon());
+ for (size_t misfit : misfits) {
+ Loop &loop = loops[misfit];
+ const C2::Rect64 &box = bounds[misfit];
+ Polygon inner;
+ inner.points = c2_to_points(loop.node->Polygon());
+ for (size_t idx : fitting) {
+ // The slack of a vertex rounded across the candidate, as in c2_attach_stray_holes().
+ const C2::Rect64 &around = bounds[idx];
+ if (std::abs(loops[idx].area) <= std::abs(loop.area) || around.left - SCALED_EPSILON > box.left ||
+ around.right + SCALED_EPSILON < box.right || around.top - SCALED_EPSILON > box.top || around.bottom + SCALED_EPSILON < box.bottom)
+ continue;
+ Polygon outer;
+ outer.points = c2_to_points(loops[idx].node->Polygon());
+ if (path_encloses(outer, inner)) {
+ loops[idx].next.emplace_back(loop.node);
+ loop.moved = true;
+ break;
+ }
+ }
+ }
+ }
- // Perform the ordering, push results recursively.
+ // Chain the loops staying on this level, push results recursively.
+ std::vector staying;
+ Points ordering_points;
+ for (size_t i = 0; i < loops.size(); ++ i)
+ if (! loops[i].moved) {
+ staying.emplace_back(i);
+ ordering_points.emplace_back(loops[i].node->Polygon().front().x, loops[i].node->Polygon().front().y);
+ }
//FIXME pass the last point to chain_points?
for (size_t idx : chain_points(ordering_points)) {
- const C2::PolyPath64 &node = *parent.Child(idx);
- retval->emplace_back().points = c2_to_points(node.Polygon());
- if (node.IsHole())
+ const Loop &loop = loops[staying[idx]];
+ retval->emplace_back().points = c2_to_points(loop.node->Polygon());
+ if (loop.area < 0.)
// Orient a hole, which is clockwise oriented, to CCW.
retval->back().reverse();
- // traverse the next depth
- traverse_pt_outside_in(node, retval);
+ traverse_pt_outside_in(loop.next, loop.area > 0., retval);
}
}
@@ -1032,8 +1271,12 @@ Polygons union_pt_chained_outside_in(const Polygons &subject)
{
C2::PolyTree64 tree;
c2_clip(C2::ClipType::Union, to_paths64(ClipperUtils::PolygonsProvider(subject)), {}, C2::FillRule::EvenOdd, tree);
+ std::vector top_level;
+ top_level.reserve(tree.Count());
+ for (const auto &node : tree)
+ top_level.emplace_back(node.get());
Polygons retval;
- traverse_pt_outside_in(tree, &retval);
+ traverse_pt_outside_in(top_level, false, &retval);
return retval;
}
@@ -1070,6 +1313,8 @@ ExPolygons top_level_expolygons(const ExPolygons &expolygons, ExPolygons *nested
c2_clip(C2::ClipType::Union, to_paths64(ClipperUtils::ExPolygonsProvider(expolygons)), {}, C2::FillRule::EvenOdd, tree);
ExPolygons out;
out.reserve(tree.Count());
+ const size_t first_nested = nested ? nested->size() : 0;
+ std::vector stray_holes;
for (const auto &outer : tree) {
ExPolygon &expoly = out.emplace_back();
expoly.contour.points = c2_to_points(outer->Polygon());
@@ -1077,9 +1322,11 @@ ExPolygons top_level_expolygons(const ExPolygons &expolygons, ExPolygons *nested
expoly.holes.emplace_back().points = c2_to_points(hole->Polygon());
if (nested)
for (const auto &island : *hole)
- c2_append_expolygons(*island, *nested);
+ c2_append_expolygons(*island, *nested, stray_holes);
}
}
+ if (nested)
+ c2_attach_stray_holes(*nested, first_nested, stray_holes);
return out;
}
diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp
index 26585a1fef..43904ff4b4 100644
--- a/src/libslic3r/ClipperUtils.hpp
+++ b/src/libslic3r/ClipperUtils.hpp
@@ -5,12 +5,14 @@
#include "Polyline.hpp"
#include "Line.hpp"
#include "libslic3r.h"
+#include "BoundingBox.hpp"
#include "ExPolygon.hpp"
#include "Polygon.hpp"
#include "Surface.hpp"
#include
#include
#include
+#include
#include
namespace Slic3r {
@@ -332,6 +334,19 @@ namespace ClipperUtils {
[[nodiscard]] Polygons clip_clipper_polygons_with_subject_bbox(const ExPolygon &src, const BoundingBox &bbox, const bool get_entire_polygons = false);
[[nodiscard]] Polygons clip_clipper_polygons_with_subject_bbox(const ExPolygons &src, const BoundingBox &bbox, const bool get_entire_polygons = false);
+ // Splits ExPolygons into tiles by the centres of their boxes, about `per_tile` of them to a tile, to run Clipper on a
+ // layer of many pieces tile by tile. Returns the non-empty tiles, each with the indices of its ExPolygons and their box.
+ struct ExPolygonsTile
+ {
+ BoundingBox bbox;
+ std::vector members;
+ };
+ [[nodiscard]] std::vector tile_expolygons(const ExPolygons &expolygons, size_t per_tile);
+
+ // A grid point strictly inside the polygon, or none if the polygon is at most a few units thick everywhere.
+ // Decides on which side of another path a path lies when every one of its vertices touches that path.
+ [[nodiscard]] std::optional interior_point(const Polygon &polygon);
+
}
// offset Polygons
@@ -527,6 +542,11 @@ 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);
+// diff_ex() / intersection_ex() of the subject split into tiles, each against only the part of the clip near it, the tiles in
+// parallel. The same area as the operation on the whole subject when its ExPolygons do not overlap, and much faster for a
+// subject of thousands of pieces spread over a layer: Clipper slows down with the number of edges crossing a scan line.
+Slic3r::ExPolygons diff_ex_by_piece(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
+Slic3r::ExPolygons intersection_ex_by_piece(const Slic3r::ExPolygons &subject, const Slic3r::Polygons &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/Config.hpp b/src/libslic3r/Config.hpp
index 9d76262ec6..9f70498499 100644
--- a/src/libslic3r/Config.hpp
+++ b/src/libslic3r/Config.hpp
@@ -377,7 +377,8 @@ public:
virtual void set(const ConfigOption* rhs, size_t start, size_t len) = 0;
virtual void set_with_restore(const ConfigOptionVectorBase* rhs, std::vector& restore_index, int stride) = 0;
virtual void set_with_restore_2(const ConfigOptionVectorBase* rhs, std::vector& restore_index, int start, int len, bool skip_error = false) = 0;
- virtual void set_only_diff(const ConfigOptionVectorBase* rhs, std::vector& diff_index, int stride) = 0;
+ // keep_nil: copy a nil column from rhs as well, for options whose nil means "disabled" rather than "inherit".
+ virtual void set_only_diff(const ConfigOptionVectorBase* rhs, std::vector& diff_index, int stride, bool keep_nil = false) = 0;
virtual void set_to_index(const ConfigOptionVectorBase* rhs, std::vector& dest_index, int stride) = 0;
virtual void set_with_nil(const ConfigOptionVectorBase* rhs, const ConfigOptionVectorBase* inherits, int stride) = 0;
// Resize the vector of values, copy the newly added values from opt_default if provided.
@@ -580,7 +581,7 @@ public:
//set a item related with extruder variants when loading user config, only set the different value of some extruder
//rhs: item from user config
//diff_index: which index in this vector need to be set
- virtual void set_only_diff(const ConfigOptionVectorBase* rhs, std::vector& diff_index, int stride) override
+ virtual void set_only_diff(const ConfigOptionVectorBase* rhs, std::vector& diff_index, int stride, bool keep_nil) override
{
if (rhs->type() == this->type()) {
// Assign the first value of the rhs vector.
@@ -592,7 +593,7 @@ public:
for (size_t i = 0; i < diff_index.size(); i++) {
if (diff_index[i] != -1) {
for (size_t j = 0; j < stride; j++) {
- if (!other->is_nil(diff_index[i] * stride))
+ if (keep_nil || !other->is_nil(diff_index[i] * stride))
this->values[i * stride + j] = other->values[diff_index[i] * stride + j];
}
}
@@ -841,8 +842,12 @@ public:
{
UNUSED(append);
std::istringstream iss(str);
- iss >> this->value;
- return !iss.fail();
+ double value;
+ iss >> value;
+ if (iss.fail())
+ return false;
+ this->value = value;
+ return true;
}
bool operator==(const ConfigOption &rhs) const override
@@ -1033,8 +1038,12 @@ public:
{
UNUSED(append);
std::istringstream iss(str);
- iss >> this->value;
- return !iss.fail();
+ int value;
+ iss >> value;
+ if (iss.fail())
+ return false;
+ this->value = value;
+ return true;
}
ConfigOptionInt& operator=(const ConfigOption *opt)
@@ -1252,8 +1261,12 @@ public:
UNUSED(append);
// don't try to parse the trailing % since it's optional
std::istringstream iss(str);
- iss >> this->value;
- return !iss.fail();
+ double value;
+ iss >> value;
+ if (iss.fail())
+ return false;
+ this->value = value;
+ return true;
}
private:
@@ -1365,10 +1378,14 @@ public:
bool deserialize(const std::string &str, bool append = false) override
{
UNUSED(append);
- this->percent = str.find_first_of("%") != std::string::npos;
std::istringstream iss(str);
- iss >> this->value;
- return !iss.fail();
+ double value;
+ iss >> value;
+ if (iss.fail())
+ return false;
+ this->value = value;
+ this->percent = str.find_first_of("%") != std::string::npos;
+ return true;
}
private:
@@ -1538,8 +1555,12 @@ public:
{
UNUSED(append);
char dummy;
- return sscanf(str.data(), " %lf , %lf %c", &this->value(0), &this->value(1), &dummy) == 2 ||
- sscanf(str.data(), " %lf x %lf %c", &this->value(0), &this->value(1), &dummy) == 2;
+ Vec2d value;
+ if (sscanf(str.data(), " %lf , %lf %c", &value(0), &value(1), &dummy) != 2 &&
+ sscanf(str.data(), " %lf x %lf %c", &value(0), &value(1), &dummy) != 2)
+ return false;
+ this->value = value;
+ return true;
}
private:
@@ -1658,8 +1679,12 @@ public:
{
UNUSED(append);
char dummy;
- return sscanf(str.data(), " %lf , %lf , %lf %c", &this->value(0), &this->value(1), &this->value(2), &dummy) == 3 ||
- sscanf(str.data(), " %lf x %lf x %lf %c", &this->value(0), &this->value(1), &this->value(2), &dummy) == 3;
+ Vec3d value;
+ if (sscanf(str.data(), " %lf , %lf , %lf %c", &value(0), &value(1), &value(2), &dummy) != 3 &&
+ sscanf(str.data(), " %lf x %lf x %lf %c", &value(0), &value(1), &value(2), &dummy) != 3)
+ return false;
+ this->value = value;
+ return true;
}
private:
diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp
index 1e6eade821..d2d6bfb0f5 100644
--- a/src/libslic3r/Fill/Fill.cpp
+++ b/src/libslic3r/Fill/Fill.cpp
@@ -22,6 +22,8 @@
#include "../PrintConfig.hpp"
#include "../Surface.hpp"
+#include
+
#include "AABBTreeLines.hpp"
#include "libslic3r/Config.hpp"
#include "libslic3r/ExPolygon.hpp"
@@ -675,24 +677,28 @@ void split_solid_surface(size_t layer_id, const SurfaceFill &fill, ExPolygons &n
if (!line_based_pattern) {
const coord_t scaled_spacing = scaled(fill.params.spacing);
- for (const ExPolygon &expolygon : fill.expolygons) {
+ // Each expolygon is split on its own, so they run in parallel and are collected in their original order.
+ std::vector> split_parts(fill.expolygons.size()); // normal, narrow
+ tbb::parallel_for(size_t(0), fill.expolygons.size(), [&](size_t idx) {
+ const ExPolygon &expolygon = fill.expolygons[idx];
Polygons filled_area = to_polygons(expolygon);
// "Core" area: open (erode+dilate) to drop thin features, then clamp back to the original polygon.
Polygons inner_area = intersection(filled_area, opening(filled_area, scaled_spacing, scaled_spacing));
if (inner_area.empty()) {
- narrow_infill.emplace_back(expolygon);
- continue;
+ split_parts[idx].second.emplace_back(expolygon);
+ return;
}
ExPolygons inner_ex = union_ex(inner_area);
ExPolygons expolys{expolygon};
- ExPolygons narrow_ex = diff_ex(expolys, inner_ex);
- ExPolygons normal_ex = intersection_ex(expolys, inner_ex);
-
- append(normal_infill, normal_ex); // normal infill area
- append(narrow_infill, narrow_ex); // narrow infill area
+ split_parts[idx].second = diff_ex(expolys, inner_ex); // narrow infill area
+ split_parts[idx].first = intersection_ex(expolys, inner_ex); // normal infill area
+ });
+ for (auto &[normal_ex, narrow_ex] : split_parts) {
+ append(normal_infill, std::move(normal_ex));
+ append(narrow_infill, std::move(narrow_ex));
}
return;
@@ -714,7 +720,10 @@ void split_solid_surface(size_t layer_id, const SurfaceFill &fill, ExPolygons &n
}
const double aligning_angle = -base_angle + PI;
- for (const ExPolygon &expolygon : fill.expolygons) {
+ // Each expolygon is reconstructed on its own, so they run in parallel and are collected in their original order.
+ std::vector split_reconstructed(fill.expolygons.size());
+ tbb::parallel_for(size_t(0), fill.expolygons.size(), [&](size_t expolygon_idx) {
+ const ExPolygon &expolygon = fill.expolygons[expolygon_idx];
Polygons filled_area = to_polygons(expolygon);
polygons_rotate(filled_area, aligning_angle);
BoundingBox bb = get_extents(filled_area);
@@ -845,8 +854,10 @@ void split_solid_surface(size_t layer_id, const SurfaceFill &fill, ExPolygons &n
}
}
- polygons_append(normal_fill_areas, reconstructed_area);
- }
+ split_reconstructed[expolygon_idx] = std::move(reconstructed_area);
+ });
+ for (Polygons &reconstructed_area : split_reconstructed)
+ polygons_append(normal_fill_areas, std::move(reconstructed_area));
polygons_rotate(normal_fill_areas, -aligning_angle);
@@ -1473,7 +1484,15 @@ void Layer::make_fills(const FillAdaptive::RegionOctrees* fill_octrees, FillLigh
f->set_bounding_box(body >= 0 ? this->object()->separated_body_bboxes()[body] : bbox);
f->adapt_fill_octree = octrees ? octrees->get(body) : nullptr;
- f->no_overlap_expolygons = intersection_ex(surface_fill.no_overlap_expolygons, ExPolygons() = {expoly}, ApplySafetyOffset::Yes);
+ // Only the part of the layer-wide no-overlap area under this expolygon matters, so clip it to the
+ // expolygon's box first (padded past the safety offset, which grows the clip side). The result is
+ // identical; the cost is not: a layer split into many small fills, e.g. by colour painting,
+ // otherwise intersects every one of them with the whole layer.
+ BoundingBox no_overlap_bbox = get_extents(expoly);
+ no_overlap_bbox.offset(SCALED_EPSILON);
+ f->no_overlap_expolygons = intersection_ex(
+ ClipperUtils::clip_clipper_polygons_with_subject_bbox(surface_fill.no_overlap_expolygons, no_overlap_bbox),
+ ExPolygons() = {expoly}, ApplySafetyOffset::Yes);
if (params.symmetric_infill_y_axis) {
params.symmetric_y_axis = f->extended_object_bounding_box().center().x();
expoly.symmetric_y(params.symmetric_y_axis);
diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp
index 63ebff49c2..7a2aee3f7f 100644
--- a/src/libslic3r/Fill/FillBase.cpp
+++ b/src/libslic3r/Fill/FillBase.cpp
@@ -2914,7 +2914,7 @@ void multiline_fill(Polylines& polylines, const FillParams& params, float spacin
const float delta = float(scale_(t));
// Arc tolerance of 1/500 of the offset, Clipper2's default.
- for (const Polygon &ring : offset(polylines, delta, jtRound, 0.002 * delta, etOpenRound))
+ for (const Polygon &ring : offset(polylines, delta, jtMiter, 0.002 * delta, etOpenRound))
if (ring.size() >= 3)
all_polylines.emplace_back(ring.split_at_first_point());
}
diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp
index 88fef3bb35..6508c8453f 100644
--- a/src/libslic3r/GCode.cpp
+++ b/src/libslic3r/GCode.cpp
@@ -3979,22 +3979,22 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
this->placeholder_parser().set("hold_chamber_temp_for_flat_print", new ConfigOptionBool(hold_chamber_temp_for_flat_print));
}
- this->placeholder_parser().set("print_time_total_sec", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Total_Sec_Placeholder)));
- this->placeholder_parser().set("print_time_day", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Day_Placeholder)));
- this->placeholder_parser().set("print_time_hour", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Hour_Placeholder)));
- this->placeholder_parser().set("print_time_minute", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Minute_Placeholder)));
- this->placeholder_parser().set("print_time_sec", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Sec_Placeholder)));
- this->placeholder_parser().set("used_filament_length", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Used_Filament_Length_Placeholder)));
+ this->placeholder_parser().set_deferred("print_time_total_sec", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Total_Sec_Placeholder));
+ this->placeholder_parser().set_deferred("print_time_day", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Day_Placeholder));
+ this->placeholder_parser().set_deferred("print_time_hour", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Hour_Placeholder));
+ this->placeholder_parser().set_deferred("print_time_minute", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Minute_Placeholder));
+ this->placeholder_parser().set_deferred("print_time_sec", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Print_Time_Sec_Placeholder));
+ this->placeholder_parser().set_deferred("used_filament_length", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Used_Filament_Length_Placeholder));
// The final print statistics are known only after the whole G-code is generated, so they are filled in during post-processing too.
// The running extruded_* totals are left alone; profiles use them as numbers.
- this->placeholder_parser().set("used_filament", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Used_Filament_Length_Placeholder)));
- this->placeholder_parser().set("print_time", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Normal_Print_Time_Placeholder)));
- this->placeholder_parser().set("normal_print_time", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Normal_Print_Time_Placeholder)));
- this->placeholder_parser().set("silent_print_time", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Silent_Print_Time_Placeholder)));
- this->placeholder_parser().set("total_weight", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Total_Weight_Placeholder)));
- this->placeholder_parser().set("total_cost", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Total_Cost_Placeholder)));
- this->placeholder_parser().set("total_wipe_tower_cost", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Total_Wipe_Tower_Cost_Placeholder)));
- this->placeholder_parser().set("total_wipe_tower_filament", new ConfigOptionString(GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Total_Wipe_Tower_Filament_Placeholder)));
+ this->placeholder_parser().set_deferred("used_filament", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Used_Filament_Length_Placeholder));
+ this->placeholder_parser().set_deferred("print_time", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Normal_Print_Time_Placeholder));
+ this->placeholder_parser().set_deferred("normal_print_time", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Normal_Print_Time_Placeholder));
+ this->placeholder_parser().set_deferred("silent_print_time", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Silent_Print_Time_Placeholder));
+ this->placeholder_parser().set_deferred("total_weight", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Total_Weight_Placeholder));
+ this->placeholder_parser().set_deferred("total_cost", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Total_Cost_Placeholder));
+ this->placeholder_parser().set_deferred("total_wipe_tower_cost", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Total_Wipe_Tower_Cost_Placeholder));
+ this->placeholder_parser().set_deferred("total_wipe_tower_filament", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Total_Wipe_Tower_Filament_Placeholder));
// Sync variant-mapped params into placeholder_parser before processing start gcode
update_placeholder_parser_with_variant_params();
diff --git a/src/libslic3r/GCode/OrderingStrategies.cpp b/src/libslic3r/GCode/OrderingStrategies.cpp
index 98b8dfa37c..4ac7ce6cb6 100644
--- a/src/libslic3r/GCode/OrderingStrategies.cpp
+++ b/src/libslic3r/GCode/OrderingStrategies.cpp
@@ -6,6 +6,7 @@
#include "../ShortestPath.hpp"
#include "libslic3r/Point.hpp"
#include "libslic3r/PrintConfig.hpp"
+#include "libslic3r/libslic3r.h"
#include
#include
@@ -141,15 +142,87 @@ bool tsp_remove_crossings(std::vector& path, const Points& centers)
return {std::numeric_limits::max(), std::numeric_limits::max()};
};
+ // For many islands, the same scan with the edges binned in a uniform grid over their boxes, so each edge is only tested against the edges sharing a
+ // cell with it - two edges whose boxes overlap always do. It returns the same crossing as the all-pairs scan
+ // (smallest i, then smallest j), so the result is unchanged. The all-pairs scan is quadratic in the edge count and
+ // runs again after every reversal; rebuilding the grid costs more than it saves below the threshold.
+ constexpr size_t grid_min_size = 500;
+ BoundingBox extent;
+ for (size_t idx : path)
+ extent.merge(centers[idx]);
+ const int grid_n = std::clamp(int(std::sqrt(double(pn))), 1, 256);
+ const coord_t cell_w = std::max(1, (extent.max.x() - extent.min.x()) / grid_n + 1);
+ const coord_t cell_h = std::max(1, (extent.max.y() - extent.min.y()) / grid_n + 1);
+ const auto for_cells = [&](const Point& a, const Point& b, auto&& fn) {
+ const int x0 = int((std::min(a.x(), b.x()) - extent.min.x()) / cell_w), x1 = int((std::max(a.x(), b.x()) - extent.min.x()) / cell_w);
+ const int y0 = int((std::min(a.y(), b.y()) - extent.min.y()) / cell_h), y1 = int((std::max(a.y(), b.y()) - extent.min.y()) / cell_h);
+ for (int y = y0; y <= y1; ++y)
+ for (int x = x0; x <= x1; ++x)
+ fn(y * grid_n + x);
+ };
+ std::vector> edge_cells(size_t(grid_n) * grid_n);
+
+ auto find_crossing_grid = [&]() -> std::pair {
+ for (std::vector& cell : edge_cells)
+ cell.clear();
+ for (size_t j = 0; j < n_edges; ++j)
+ for_cells(centers[path[j]], centers[path[(j + 1) % pn]], [&](int cell) { edge_cells[cell].emplace_back(j); });
+
+ for (size_t i = 0; i < n_edges; ++i) {
+ const Point& ai = centers[path[i]];
+ const Point& bi = centers[path[(i + 1) % pn]];
+
+ size_t first_j = std::numeric_limits::max();
+ for_cells(ai, bi, [&](int cell) {
+ for (size_t j : edge_cells[cell]) {
+ if (j < i + 2 || j >= first_j) continue;
+ // Skip the (0, pn-1) pair: edges (0,1) and (pn-1,0) share node 0.
+ if (i == 0 && j == pn - 1) continue;
+
+ const Point& aj = centers[path[j]];
+ const Point& bj = centers[path[(j + 1) % pn]];
+
+ if (!bboxes_overlap(ai, bi, aj, bj)) continue;
+ if (Geometry::segments_intersect(ai, bi, aj, bj))
+ first_j = j;
+ }
+ });
+ if (first_j != std::numeric_limits::max())
+ return {i, first_j};
+ }
+ return {std::numeric_limits::max(), std::numeric_limits::max()};
+ };
+
// Process crossings one at a time: find first, reverse it, restart scan.
// Cap iterations to prevent infinite loops on collinear/overlapping segments.
- int max_iters = static_cast(pn * pn);
+ const int max_iters = static_cast(pn * pn);
bool improved = false;
- while (max_iters-- > 0) {
- auto [ci, cj] = find_crossing();
- if (ci == std::numeric_limits::max()) break;
- improved = true;
+ // Reversing between two segments that only touch or overlap along a line need not remove the intersection, so on
+ // islands laid out on a regular grid (a tiled texture, an array of parts) the loop can cycle through the same
+ // orderings until the pn * pn cap. Once an ordering repeats the rest of the loop is periodic, so only the steps
+ // to the ordering the capped loop would have stopped on are taken.
+ std::unordered_map seen_paths; // path hash -> reversals done when it was reached
+ const auto path_hash = [&path]() {
+ uint64_t h = 1469598103934665603ull; // FNV-1a
+ for (size_t idx : path)
+ h = (h ^ uint64_t(idx)) * 1099511628211ull;
+ return h;
+ };
+ const auto reverse_first_crossing = [&]() {
+ auto [ci, cj] = pn >= grid_min_size ? find_crossing_grid() : find_crossing();
+ if (ci == std::numeric_limits::max())
+ return false;
std::reverse(path.begin() + ci + 1, path.begin() + cj + 1);
+ return true;
+ };
+ seen_paths.emplace(path_hash(), 0);
+ for (int iter = 1; iter <= max_iters && reverse_first_crossing(); ++iter) {
+ improved = true;
+ if (auto [it, inserted] = seen_paths.emplace(path_hash(), iter); !inserted) {
+ for (int steps = (max_iters - iter) % (iter - it->second); steps > 0; --steps)
+ reverse_first_crossing();
+ break;
+ }
}
return improved;
}
diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp
index 932f8fb99e..c318f4f5d7 100644
--- a/src/libslic3r/GCode/SeamPlacer.cpp
+++ b/src/libslic3r/GCode/SeamPlacer.cpp
@@ -29,6 +29,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -1207,21 +1208,21 @@ std::optional> SeamPlacer::find_next_seam_in_layer(
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) {
+ // Find the best nearby point and the nearest one. A layer of a fine relief has tens of thousands of candidates within
+ // the radius, so they are looked at as the search finds them rather than collected into a vector first.
+ constexpr size_t none = std::numeric_limits::max();
+ size_t best_nearby_point_index = none;
+ size_t nearest_point_index = none;
+ visit_nearby_points(*layers[layer_idx].points_tree, projected_position, max_distance,
+ [&layers, &comparator, &projected_position, layer_idx, &best_nearby_point_index, &nearest_point_index]
+ (size_t nearby_point_index) {
+ if (best_nearby_point_index == none) {
+ // The first point found starts both, as the first of the collected ones did.
+ best_nearby_point_index = nearest_point_index = nearby_point_index;
+ }
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
+ return; // 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>())
@@ -1233,6 +1234,10 @@ std::optional> SeamPlacer::find_next_seam_in_layer(
|| layers[layer_idx].points[nearest_point_index].perimeter.finalized) {
nearest_point_index = nearby_point_index;
}
+ });
+
+ if (best_nearby_point_index == none) {
+ return {};
}
const SeamCandidate &best_nearby_point = layers[layer_idx].points[best_nearby_point_index];
diff --git a/src/libslic3r/KDTreeIndirect.hpp b/src/libslic3r/KDTreeIndirect.hpp
index d334a2ad64..3bf3d938e6 100644
--- a/src/libslic3r/KDTreeIndirect.hpp
+++ b/src/libslic3r/KDTreeIndirect.hpp
@@ -318,6 +318,36 @@ std::vector find_nearby_points(const KDTreeIndirectType &kdtree, const P
return visitor.result;
}
+// Visits the points within max_distance of center, in the order find_nearby_points() would collect them, and hands
+// each of them to `visitor_fn` instead of returning them all: a search over a dense set spends more on collecting the
+// points into a vector than on the search itself, and its caller usually keeps only a few of them.
+template
+void visit_nearby_points(const KDTreeIndirectType &kdtree, const PointType ¢er,
+ const typename KDTreeIndirectType::CoordType &max_distance, VisitorFn visitor_fn)
+{
+ using CoordType = typename KDTreeIndirectType::CoordType;
+
+ struct Visitor {
+ const KDTreeIndirectType &kdtree;
+ const PointType center;
+ const CoordType max_distance_squared;
+ VisitorFn visitor_fn;
+
+ unsigned int operator()(size_t idx, size_t dimension) {
+ auto dist = CoordType(0);
+ for (size_t i = 0; i < KDTreeIndirectType::NumDimensions; ++i) {
+ CoordType d = center[i] - kdtree.coordinate(idx, i);
+ dist += d * d;
+ }
+ if (dist < max_distance_squared)
+ visitor_fn(idx);
+ return kdtree.descent_mask(center[dimension], max_distance_squared, idx, dimension);
+ }
+ } visitor { kdtree, center, max_distance * max_distance, visitor_fn };
+
+ kdtree.visit(visitor);
+}
+
template
std::vector find_nearby_points(const KDTreeIndirectType &kdtree, const PointType ¢er,
const typename KDTreeIndirectType::CoordType& max_distance)
diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp
index c20cebc641..eef3f90c87 100644
--- a/src/libslic3r/LayerRegion.cpp
+++ b/src/libslic3r/LayerRegion.cpp
@@ -99,10 +99,11 @@ void LayerRegion::slices_to_fill_surfaces_clipped()
by_surface[size_t(surface.surface_type)].emplace_back(&surface);
// Trim surfaces by the fill_boundaries.
this->fill_surfaces.surfaces.clear();
+ const Polygons fill_boundaries = to_polygons(this->fill_expolygons);
for (size_t surface_type = 0; surface_type < size_t(stCount); ++ surface_type) {
const SurfacesPtr &this_surfaces = by_surface[surface_type];
if (! this_surfaces.empty())
- this->fill_surfaces.append(intersection_ex(this_surfaces, this->fill_expolygons), SurfaceType(surface_type));
+ this->fill_surfaces.append(intersection_ex_by_piece(to_expolygons(this_surfaces), fill_boundaries), SurfaceType(surface_type));
}
}
diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp
index dc3af43de4..507d997f1d 100644
--- a/src/libslic3r/MultiMaterialSegmentation.cpp
+++ b/src/libslic3r/MultiMaterialSegmentation.cpp
@@ -22,6 +22,7 @@
#include "Surface.hpp"
#include "libslic3r.h"
+#include
#include
#include
#include
@@ -1361,10 +1362,15 @@ static inline std::vector> segmentation_top_and_bottom_l
}
#endif // MM_SEGMENTATION_DEBUG_TOP_BOTTOM
- // When the upper surface of an object is occluded, it should no longer be considered the upper surface
+ // When the upper surface of an object is occluded, it should no longer be considered the upper surface.
+ // Every (colour, layer) pair is trimmed on its own, so they all run at once: the painted faces of a finely
+ // textured part project hundreds of thousands of triangles onto one layer, which used to be trimmed serially.
{
- for (size_t extruder_idx = 0; extruder_idx < num_facets_states; ++extruder_idx) {
- for (size_t layer_idx = 0; layer_idx < layers.size(); ++layer_idx) {
+ const size_t occluded_pairs = num_facets_states * layers.size();
+ tbb::parallel_for(tbb::blocked_range(0, occluded_pairs), [&](const tbb::blocked_range &range) {
+ for (size_t pair_idx = range.begin(); pair_idx < range.end(); ++pair_idx) {
+ const size_t extruder_idx = pair_idx / layers.size();
+ const size_t layer_idx = pair_idx % layers.size();
if (!top_raw[extruder_idx].empty() && !top_raw[extruder_idx][layer_idx].empty() && layer_idx + 1 < layers.size()) {
top_raw[extruder_idx][layer_idx] = diff(top_raw[extruder_idx][layer_idx], input_expolygons[layer_idx + 1]);
}
@@ -1372,7 +1378,7 @@ static inline std::vector> segmentation_top_and_bottom_l
bottom_raw[extruder_idx][layer_idx] = diff(bottom_raw[extruder_idx][layer_idx], input_expolygons[layer_idx - 1]);
}
}
- }
+ });
}
std::vector> triangles_by_color_bottom(num_facets_states);
@@ -1430,11 +1436,58 @@ static inline std::vector> segmentation_top_and_bottom_l
return out;
};
+ // Projects a painted top or bottom face `ex` of layer `layer_idx` onto the shell layers below or above it (in
+ // `shell_layers`, nearest first), one more perimeter in on each, stopping at the first layer where nothing is left.
+ // Only the slices within the deepest offset of `ex` (three times that with the miter joins) decide the result, so the
+ // work is done per tile of `ex`'s ExPolygons on the slices cut to the tile's box grown by that much: the same result, but
+ // each Clipper call stays the size of a tile rather than of a layer cut through a fine relief, and the tiles run in
+ // parallel.
+ const auto project_to_shells = [&input_expolygons](const ExPolygons &ex, size_t layer_idx, const std::vector &shell_layers,
+ const LayerColorStat &stat, ShellProjections &dst) {
+ std::vector offsets(shell_layers.size());
+ float offset = 0.f;
+ for (size_t i = 0; i < shell_layers.size(); ++i) {
+ //BBS: offset width should be 2*spacing to avoid too narrow area which has overlap of wall line
+ offset -= (stat.extrusion_spacing + stat.extrusion_width);
+ offsets[i] = offset;
+ }
+ if (offsets.empty())
+ return;
+ const coord_t reach = coord_t(std::ceil(DefaultMiterLimit * std::abs(offsets.back()))) + 10 * SCALED_EPSILON;
+ const std::vector tiles = ClipperUtils::tile_expolygons(ex, 16);
+ // [shell layer][tile]
+ std::vector> shells(shell_layers.size(), std::vector(tiles.size()));
+ tbb::parallel_for(size_t(0), tiles.size(), [&](size_t tile_idx) {
+ const ClipperUtils::ExPolygonsTile &tile = tiles[tile_idx];
+ const BoundingBox bbox = tile.bbox.inflated(reach);
+ ExPolygons tile_ex;
+ tile_ex.reserve(tile.members.size());
+ for (size_t i : tile.members)
+ tile_ex.emplace_back(ex[i]);
+ Polygons layer_slices_trimmed = ClipperUtils::clip_clipper_polygons_with_subject_bbox(input_expolygons[layer_idx], bbox);
+ for (size_t i = 0; i < shell_layers.size() && ! layer_slices_trimmed.empty(); ++i) {
+ const ExPolygons trimmed = intersection_ex(layer_slices_trimmed, ClipperUtils::clip_clipper_polygons_with_subject_bbox(input_expolygons[shell_layers[i]], bbox));
+ shells[i][tile_idx] = opening_ex(intersection_ex(tile_ex, offset_ex(trimmed, offsets[i])), stat.small_region_threshold);
+ layer_slices_trimmed = to_polygons(trimmed);
+ }
+ });
+ for (size_t i = 0; i < shell_layers.size(); ++i) {
+ ExPolygons shell;
+ for (ExPolygons &tile_shell : shells[i])
+ append(shell, std::move(tile_shell));
+ if (shell.empty())
+ break;
+ dst.emplace_back(shell_layers[i], std::move(shell));
+ }
+ };
+
tbb::parallel_for(tbb::blocked_range(0, num_layers), [&num_layers, &num_facets_states, &layer_color_stat, &top_raw, &triangles_by_color_top,
- &throw_on_cancel_callback, &input_expolygons, &bottom_raw, &triangles_by_color_bottom,
+ &throw_on_cancel_callback, &bottom_raw, &triangles_by_color_bottom, &project_to_shells,
&shell_triangles_by_color_top, &shell_triangles_by_color_bottom](const tbb::blocked_range &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
- for (size_t color_idx = 0; color_idx < num_facets_states; ++color_idx) {
+ // Each colour writes only its own vectors, so the colours run in parallel: a painted top or bottom face
+ // projects onto a single layer, which otherwise did all of its colours on one thread.
+ tbb::parallel_for(size_t(0), size_t(num_facets_states), [&](size_t color_idx) {
throw_on_cancel_callback();
LayerColorStat stat = layer_color_stat(layer_idx, color_idx);
if (std::vector &top = top_raw[color_idx]; ! top.empty() && ! top[layer_idx].empty())
@@ -1443,18 +1496,10 @@ static inline std::vector> segmentation_top_and_bottom_l
top_ex = opening_ex(top_ex, stat.small_region_threshold);
if (! top_ex.empty()) {
append(triangles_by_color_top[color_idx][layer_idx], top_ex);
- float offset = 0.f;
- ExPolygons layer_slices_trimmed = input_expolygons[layer_idx];
- for (int last_idx = int(layer_idx) - 1; last_idx > std::max(int(layer_idx - stat.top_shell_layers), int(0)); --last_idx) {
- //BBS: offset width should be 2*spacing to avoid too narrow area which has overlap of wall line
- //offset -= stat.extrusion_width ;
- offset -= (stat.extrusion_spacing + stat.extrusion_width);
- layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]);
- ExPolygons last = opening_ex(intersection_ex(top_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold);
- if (last.empty())
- break;
- shell_triangles_by_color_top[color_idx][layer_idx].emplace_back(size_t(last_idx), std::move(last));
- }
+ std::vector shell_layers;
+ for (int last_idx = int(layer_idx) - 1; last_idx > std::max(int(layer_idx - stat.top_shell_layers), int(0)); --last_idx)
+ shell_layers.emplace_back(size_t(last_idx));
+ project_to_shells(top_ex, layer_idx, shell_layers, stat, shell_triangles_by_color_top[color_idx][layer_idx]);
}
}
if (std::vector &bottom = bottom_raw[color_idx]; ! bottom.empty() && ! bottom[layer_idx].empty())
@@ -1463,21 +1508,13 @@ static inline std::vector> segmentation_top_and_bottom_l
bottom_ex = opening_ex(bottom_ex, stat.small_region_threshold);
if (! bottom_ex.empty()) {
append(triangles_by_color_bottom[color_idx][layer_idx], bottom_ex);
- float offset = 0.f;
- ExPolygons layer_slices_trimmed = input_expolygons[layer_idx];
- for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + stat.bottom_shell_layers, num_layers); ++last_idx) {
- //BBS: offset width should be 2*spacing to avoid too narrow area which has overlap of wall line
- //offset -= stat.extrusion_width;
- offset -= (stat.extrusion_spacing + stat.extrusion_width);
- layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]);
- ExPolygons last = opening_ex(intersection_ex(bottom_ex, offset_ex(layer_slices_trimmed, offset)), stat.small_region_threshold);
- if (last.empty())
- break;
- shell_triangles_by_color_bottom[color_idx][layer_idx].emplace_back(last_idx, std::move(last));
- }
+ std::vector shell_layers;
+ for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + stat.bottom_shell_layers, num_layers); ++last_idx)
+ shell_layers.emplace_back(last_idx);
+ project_to_shells(bottom_ex, layer_idx, shell_layers, stat, shell_triangles_by_color_bottom[color_idx][layer_idx]);
}
}
- }
+ });
}
});
@@ -1498,20 +1535,23 @@ static inline std::vector> segmentation_top_and_bottom_l
&shell_top_by_layer, &shell_bottom_by_layer](const tbb::blocked_range &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
throw_on_cancel_callback();
- ExPolygons painted_exploys;
- for (size_t color_idx = 0; color_idx < triangles_by_color_merged.size(); ++color_idx) {
+ // The per-colour unions below are independent of each other, so they run in parallel (a painted top or
+ // bottom face puts all of its colours on one layer); whatever combines the colours stays in colour order.
+ const auto merge_colour_union = [&](size_t color_idx) {
auto &self = triangles_by_color_merged[color_idx][layer_idx];
append(self, std::move(triangles_by_color_bottom[color_idx][layer_idx]));
append(self, std::move(triangles_by_color_top[color_idx][layer_idx]));
self = union_ex(self);
+ };
+ tbb::parallel_for(size_t(0), triangles_by_color_merged.size(), merge_colour_union);
- append(painted_exploys, self);
- }
-
+ ExPolygons painted_exploys;
+ for (size_t color_idx = 0; color_idx < triangles_by_color_merged.size(); ++color_idx)
+ append(painted_exploys, triangles_by_color_merged[color_idx][layer_idx]);
painted_exploys = union_ex(painted_exploys);
//BBS: merge the top and bottom shell layers
- for (size_t color_idx = 0; color_idx < triangles_by_color_merged.size(); ++color_idx) {
+ tbb::parallel_for(size_t(0), triangles_by_color_merged.size(), [&](size_t color_idx) {
auto &self = triangles_by_color_merged[color_idx][layer_idx];
auto top_area = diff_ex(union_ex(shell_top_by_layer[color_idx][layer_idx]), painted_exploys);
@@ -1520,7 +1560,7 @@ static inline std::vector> segmentation_top_and_bottom_l
append(self, top_area);
append(self, bottom_area);
self = union_ex(self);
- }
+ });
// Trim one region by the other if some of the regions overlap.
ExPolygons painted_regions;
for (size_t color_idx = 1; color_idx < triangles_by_color_merged.size(); ++color_idx) {
@@ -1887,7 +1927,69 @@ static void remove_multiple_edges_in_vertices(MMU_Graph &graph, const std::vecto
}
}
-static std::vector> merge_segmented_layers(const std::vector> &segmented_regions,
+
+// Finds the islands (layer ExPolygons) a region piece overlaps. A top or bottom region is projected from the neighbouring
+// layers and may reach past the island it belongs to, or over several islands.
+class IslandLocator
+{
+public:
+ explicit IslandLocator(const ExPolygons &islands) : m_islands(islands)
+ {
+ m_bboxes.reserve(islands.size());
+ for (const ExPolygon &island : islands) {
+ m_bboxes.emplace_back(get_extents(island));
+ m_extent.merge(m_bboxes.back());
+ }
+ if (!m_extent.defined)
+ return;
+ const Point size = m_extent.size();
+ m_cell_w = std::max(1, size.x() / GRID + 1);
+ m_cell_h = std::max(1, size.y() / GRID + 1);
+ m_grid.assign(GRID * GRID, {});
+ for (size_t i = 0; i < m_bboxes.size(); ++i)
+ for_cells(m_bboxes[i], [&](int cell) { m_grid[cell].emplace_back(i); });
+ }
+
+ void find(const ExPolygon &piece, std::vector &out) const
+ {
+ out.clear();
+ const BoundingBox bbox = get_extents(piece);
+ if (!m_extent.defined || !m_extent.overlap(bbox))
+ return;
+ for_cells(bbox, [&](int cell) {
+ for (size_t i : m_grid[cell])
+ if (m_bboxes[i].overlap(bbox))
+ out.emplace_back(i);
+ });
+ sort_remove_duplicates(out);
+ if (out.size() > 1)
+ out.erase(std::remove_if(out.begin(), out.end(), [&](size_t i) {
+ const BoundingBox common(m_bboxes[i].min.cwiseMax(bbox.min), m_bboxes[i].max.cwiseMin(bbox.max));
+ return intersection(ClipperUtils::clip_clipper_polygons_with_subject_bbox(piece, common.inflated(SCALED_EPSILON)),
+ ClipperUtils::clip_clipper_polygons_with_subject_bbox(m_islands[i], common.inflated(SCALED_EPSILON))).empty();
+ }), out.end());
+ }
+
+private:
+ static constexpr int GRID = 64;
+ template void for_cells(const BoundingBox &bb, Fn &&fn) const
+ {
+ const int x0 = std::clamp(int((bb.min.x() - m_extent.min.x()) / m_cell_w), 0, GRID - 1), x1 = std::clamp(int((bb.max.x() - m_extent.min.x()) / m_cell_w), 0, GRID - 1);
+ const int y0 = std::clamp(int((bb.min.y() - m_extent.min.y()) / m_cell_h), 0, GRID - 1), y1 = std::clamp(int((bb.max.y() - m_extent.min.y()) / m_cell_h), 0, GRID - 1);
+ for (int y = y0; y <= y1; ++y)
+ for (int x = x0; x <= x1; ++x)
+ fn(y * GRID + x);
+ }
+
+ const ExPolygons &m_islands;
+ std::vector m_bboxes;
+ BoundingBox m_extent;
+ coord_t m_cell_w = 1, m_cell_h = 1;
+ std::vector> m_grid;
+};
+
+static std::vector> merge_segmented_layers(const std::vector &input_expolygons,
+ const std::vector> &segmented_regions,
std::vector> &&top_and_bottom_layers,
const size_t num_facets_states,
const std::function &throw_on_cancel_callback)
@@ -1898,33 +2000,91 @@ static std::vector> merge_segmented_layers(const std::ve
assert(!top_and_bottom_layers.size() || num_facets_states == top_and_bottom_layers.size());
BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Merging segmented layers in parallel - Begin";
- tbb::parallel_for(tbb::blocked_range(0, num_layers), [&segmented_regions, &top_and_bottom_layers, &segmented_regions_merged, &num_facets_states, &throw_on_cancel_callback](const tbb::blocked_range &range) {
+ // Every region of a layer is merged together with the regions of the islands it overlaps, and the islands are further
+ // apart than the dimple removal below reaches, so this gives the same result as merging the layer at once. On a layer
+ // cut through a fine relief every region shares thousands of hole contours with every other, and Clipper, splitting
+ // and re-linking one huge polygon over and over, took anything up to half an hour for a layer; per island each operation
+ // stays the size of the island, and the islands run in parallel.
+ tbb::parallel_for(tbb::blocked_range(0, num_layers), [&](const tbb::blocked_range &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
assert(segmented_regions[layer_idx].size() == num_facets_states);
- // Zero is skipped because it is the default color of the volume
+ throw_on_cancel_callback();
+ // Group the islands joined by a region overlapping several of them; the last group takes the regions lying
+ // outside every island.
+ const ExPolygons &islands = input_expolygons[layer_idx];
+ const IslandLocator locator(islands);
+ std::vector parent(islands.size() + 1);
+ std::iota(parent.begin(), parent.end(), 0);
+ const auto root = [&parent](size_t i) {
+ while (parent[i] != i)
+ i = parent[i] = parent[parent[i]];
+ return i;
+ };
+ // Islands of every piece: side regions of colours 1.., then top/bottom regions of colours 0..
+ std::vector pieces;
+ for (size_t extruder_id = 1; extruder_id < num_facets_states; ++extruder_id)
+ for (const ExPolygon &piece : segmented_regions[layer_idx][extruder_id])
+ pieces.emplace_back(&piece);
+ if (!top_and_bottom_layers.empty())
+ for (size_t color_idx = 0; color_idx < num_facets_states; ++color_idx)
+ for (const ExPolygon &piece : top_and_bottom_layers[color_idx][layer_idx])
+ pieces.emplace_back(&piece);
+ std::vector> overlapped(pieces.size());
+ tbb::parallel_for(size_t(0), pieces.size(), [&](size_t i) { locator.find(*pieces[i], overlapped[i]); });
+ std::vector piece_island(pieces.size());
+ for (size_t i = 0; i < pieces.size(); ++i) {
+ piece_island[i] = overlapped[i].empty() ? islands.size() : overlapped[i].front();
+ for (size_t island : overlapped[i])
+ parent[root(island)] = root(piece_island[i]);
+ }
+ std::vector bucket_of(parent.size(), size_t(-1));
+ size_t num_buckets = 0;
+ for (size_t i = 0; i < parent.size(); ++i)
+ if (size_t &b = bucket_of[root(i)]; b == size_t(-1))
+ b = num_buckets++;
+
+ // [bucket][colour]
+ std::vector> sides(num_buckets, std::vector(num_facets_states));
+ std::vector> tops(num_buckets, std::vector(num_facets_states));
+ size_t piece_idx = 0;
+ for (size_t extruder_id = 1; extruder_id < num_facets_states; ++extruder_id)
+ for (const ExPolygon &piece : segmented_regions[layer_idx][extruder_id])
+ sides[bucket_of[root(piece_island[piece_idx++])]][extruder_id].emplace_back(piece);
+ if (!top_and_bottom_layers.empty())
+ for (size_t color_idx = 0; color_idx < num_facets_states; ++color_idx)
+ for (const ExPolygon &piece : top_and_bottom_layers[color_idx][layer_idx])
+ tops[bucket_of[root(piece_island[piece_idx++])]][color_idx].emplace_back(piece);
+
+ // Side regions minus the top/bottom regions of every colour.
+ std::vector> merged(num_buckets, std::vector(num_facets_states));
+ tbb::parallel_for(size_t(0), num_buckets, [&](size_t bucket) {
+ Polygons tops_all;
+ for (const ExPolygons &t : tops[bucket])
+ polygons_append(tops_all, t);
+ for (size_t extruder_id = 1; extruder_id < num_facets_states; ++extruder_id)
+ if (!sides[bucket][extruder_id].empty())
+ merged[bucket][extruder_id] = tops_all.empty() ? std::move(sides[bucket][extruder_id]) :
+ diff_ex_by_piece(sides[bucket][extruder_id], tops_all);
+ });
+
+ // Then this colour's top/bottom regions, with the dimples removed (#7235) when the layer has side regions left.
for (size_t extruder_id = 1; extruder_id < num_facets_states; ++extruder_id) {
- throw_on_cancel_callback();
- if (!segmented_regions[layer_idx][extruder_id].empty()) {
- ExPolygons segmented_regions_trimmed = segmented_regions[layer_idx][extruder_id];
- if (!top_and_bottom_layers.empty()) {
- for (const std::vector &top_and_bottom_by_extruder : top_and_bottom_layers) {
- if (!top_and_bottom_by_extruder[layer_idx].empty() && !segmented_regions_trimmed.empty()) {
- segmented_regions_trimmed = diff_ex(segmented_regions_trimmed, top_and_bottom_by_extruder[layer_idx]);
- }
- }
- }
-
- segmented_regions_merged[layer_idx][extruder_id - 1] = std::move(segmented_regions_trimmed);
- }
-
- if (!top_and_bottom_layers.empty() && !top_and_bottom_layers[extruder_id][layer_idx].empty()) {
- bool was_top_and_bottom_empty = segmented_regions_merged[layer_idx][extruder_id - 1].empty();
- append(segmented_regions_merged[layer_idx][extruder_id - 1], top_and_bottom_layers[extruder_id][layer_idx]);
-
- // Remove dimples (#7235) appearing after merging side segmentation of the model with tops and bottoms painted layers.
- if (!was_top_and_bottom_empty)
- segmented_regions_merged[layer_idx][extruder_id - 1] = offset2_ex(union_ex(segmented_regions_merged[layer_idx][extruder_id - 1]), float(SCALED_EPSILON), -float(SCALED_EPSILON));
+ if (top_and_bottom_layers.empty() || top_and_bottom_layers[extruder_id][layer_idx].empty()) {
+ for (size_t bucket = 0; bucket < num_buckets; ++bucket)
+ append(segmented_regions_merged[layer_idx][extruder_id - 1], std::move(merged[bucket][extruder_id]));
+ continue;
}
+ bool was_top_and_bottom_empty = true;
+ for (size_t bucket = 0; bucket < num_buckets && was_top_and_bottom_empty; ++bucket)
+ was_top_and_bottom_empty = merged[bucket][extruder_id].empty();
+ tbb::parallel_for(size_t(0), num_buckets, [&](size_t bucket) {
+ ExPolygons ®ion = merged[bucket][extruder_id];
+ append(region, tops[bucket][extruder_id]);
+ if (!was_top_and_bottom_empty && !region.empty())
+ region = offset2_ex(union_ex(region), float(SCALED_EPSILON), -float(SCALED_EPSILON));
+ });
+ for (size_t bucket = 0; bucket < num_buckets; ++bucket)
+ append(segmented_regions_merged[layer_idx][extruder_id - 1], std::move(merged[bucket][extruder_id]));
}
}
}); // end of parallel_for
@@ -2212,16 +2372,56 @@ std::vector> segmentation_by_painting(const PrintObject
assert(!color_poly.empty());
assert(!color_poly.front().empty());
- if (has_layer_only_one_color(color_poly)) {
- // If the whole layer is painted using the same color, it is not needed to construct a Voronoi diagram for the segmentation of this layer.
- segmented_regions[layer_idx][size_t(color_poly.front().front().color)] = input_expolygons[layer_idx];
- } else {
- MMU_Graph graph = build_graph(layer_idx, color_poly);
- remove_multiple_edges_in_vertices(graph, color_poly);
- graph.remove_nodes_with_one_arc();
- segmented_regions[layer_idx] = extract_colored_segments(graph, num_facets_states);
- //segmented_regions[layer_idx] = extract_colored_segments(color_poly, num_extruders, layer_idx);
+ // Each island (an ExPolygon with its holes) is segmented on its own. Any point of an island is closer to
+ // that island's contours than to any other island's - the way out crosses its own boundary first - so its
+ // Voronoi cells, and with them its colour regions, depend on nothing else. A layer cut through a fine relief
+ // has thousands of islands, and one Voronoi diagram over all of them degenerated into overlapping regions
+ // that every boolean afterwards had to untangle. Per island the diagrams stay small and the islands run in
+ // parallel; an island in a single colour needs no diagram at all.
+ const ExPolygons &islands = input_expolygons[layer_idx];
+ std::vector> island_contours(islands.size()); // [first, last) into color_poly
+ {
+ // The same order EdgeGrid::Grid::create() lists the contours in, and so colorize_contours().
+ size_t idx = 0;
+ for (size_t island_idx = 0; island_idx < islands.size(); ++island_idx) {
+ const size_t first = idx;
+ if (!islands[island_idx].contour.empty())
+ ++idx;
+ for (const Polygon &hole : islands[island_idx].holes)
+ if (!hole.empty())
+ ++idx;
+ island_contours[island_idx] = {first, idx};
+ }
+ assert(idx == color_poly.size());
}
+ std::vector> island_regions(islands.size());
+ tbb::parallel_for(size_t(0), islands.size(), [&](size_t island_idx) {
+ const auto [first, last] = island_contours[island_idx];
+ if (first == last)
+ return;
+ const std::vector island_poly(color_poly.begin() + first, color_poly.begin() + last);
+ std::vector ®ions = island_regions[island_idx];
+ if (has_layer_only_one_color(island_poly)) {
+ regions.assign(num_facets_states, ExPolygons());
+ regions[size_t(island_poly.front().front().color)].emplace_back(islands[island_idx]);
+ } else {
+ MMU_Graph graph = build_graph(layer_idx, island_poly);
+ remove_multiple_edges_in_vertices(graph, island_poly);
+ graph.remove_nodes_with_one_arc();
+ regions = extract_colored_segments(graph, num_facets_states);
+ // The faces of one colour tile it without overlapping; merged here, where an island is small,
+ // every later boolean gets a few regions instead of thousands of faces sharing their edges. An
+ // island with many holes keeps its faces: merged, each colour would be one region with thousands
+ // of holes, and subtracting from that is far slower than from the faces one at a time.
+ if (island_poly.size() <= 64)
+ for (ExPolygons &faces : regions)
+ if (faces.size() > 1)
+ faces = union_ex(faces);
+ }
+ });
+ for (std::vector ®ions : island_regions)
+ for (size_t color_idx = 0; color_idx < regions.size(); ++color_idx)
+ append(segmented_regions[layer_idx][color_idx], std::move(regions[color_idx]));
#ifdef MM_SEGMENTATION_DEBUG_REGIONS
export_regions_to_svg(debug_out_path("3-mm-regions-sides-%d-%d.svg", layer_idx, iRun), segmented_regions[layer_idx], input_expolygons[layer_idx]);
@@ -2244,7 +2444,7 @@ std::vector> segmentation_by_painting(const PrintObject
throw_on_cancel_callback();
}
- std::vector> segmented_regions_merged = merge_segmented_layers(segmented_regions, std::move(top_and_bottom_layers), num_facets_states, throw_on_cancel_callback);
+ std::vector> segmented_regions_merged = merge_segmented_layers(input_expolygons, segmented_regions, std::move(top_and_bottom_layers), num_facets_states, throw_on_cancel_callback);
throw_on_cancel_callback();
#ifdef MM_SEGMENTATION_DEBUG_REGIONS
diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp
index 82d6d5884b..772085b3be 100644
--- a/src/libslic3r/MultiPoint.hpp
+++ b/src/libslic3r/MultiPoint.hpp
@@ -23,11 +23,15 @@ public:
MultiPoint() {}
MultiPoint(const MultiPoint &other) : points(other.points) {}
- MultiPoint(MultiPoint &&other) : points(std::move(other.points)) {}
+ MultiPoint(MultiPoint &&other) noexcept : points(std::move(other.points)) {}
MultiPoint(std::initializer_list list) : points(list) {}
explicit MultiPoint(const Points &_points) : points(_points) {}
+ // Without it, the derived classes' move constructors passing std::move(points) here copied them, which
+ // also means a moved-from Polygon or Polyline is now really empty where it used to silently keep its
+ // points: a use-after-move anywhere in the tree that happened to work before now sees nothing.
+ explicit MultiPoint(Points &&_points) noexcept : points(std::move(_points)) {}
MultiPoint& operator=(const MultiPoint &other) { points = other.points; return *this; }
- MultiPoint& operator=(MultiPoint &&other) { points = std::move(other.points); return *this; }
+ MultiPoint& operator=(MultiPoint &&other) noexcept { points = std::move(other.points); return *this; }
virtual ~MultiPoint() = default;
void scale(double factor);
void scale(double factor_x, double factor_y);
diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp
index eed1adcf78..219b6b2551 100644
--- a/src/libslic3r/PerimeterGenerator.cpp
+++ b/src/libslic3r/PerimeterGenerator.cpp
@@ -32,6 +32,8 @@
#include
#include
#include
+#include
+#include
#include
#include "libslic3r.h"
#include
@@ -2583,467 +2585,490 @@ void PerimeterGenerator::process_arachne()
const bool only_one_wall_first_layer = this->config->only_one_wall_first_layer && has_bottom_shell_layers(*this->config);
// we need to process each island separately because we might have different
// extra perimeters for each one
- for (const Surface& surface : all_surfaces) {
- coord_t bead_width_0 = ext_perimeter_spacing;
- // detect how many perimeters must be generated for this island
- int loop_number = this->config->wall_loops + surface.extra_perimeters - 1; // 0-indexed loops
- int sparse_infill_density = this->config->sparse_infill_density.value;
- if (this->config->alternate_extra_wall && this->layer_id % 2 == 1 && !m_spiral_vase && sparse_infill_density > 0) // add alternating extra wall
- loop_number++;
+ // Each island is independent up to its outputs, so they are generated in parallel - a layer split into thousands
+ // of islands (e.g. by colour painting) otherwise ran on one thread - and the outputs are then committed in the
+ // original island order, which is what the extra overhang perimeters (applied to the last island's loops and to
+ // all fill surfaces so far) depend on.
+ struct ArachneSurfaceResult
+ {
+ ExtrusionEntityCollection loops;
+ bool has_loops = false;
+ ExPolygons infill;
+ ExPolygons no_overlap;
+ };
+ std::vector results(all_surfaces.size());
+ tbb::parallel_for(tbb::blocked_range(0, all_surfaces.size()), [&](const tbb::blocked_range &range) {
+ for (size_t surface_idx = range.begin(); surface_idx < range.end(); ++surface_idx) {
+ const Surface &surface = all_surfaces[surface_idx];
+ ArachneSurfaceResult &result = results[surface_idx];
+ coord_t bead_width_0 = ext_perimeter_spacing;
+ // detect how many perimeters must be generated for this island
+ int loop_number = this->config->wall_loops + surface.extra_perimeters - 1; // 0-indexed loops
+ int sparse_infill_density = this->config->sparse_infill_density.value;
+ if (this->config->alternate_extra_wall && this->layer_id % 2 == 1 && !m_spiral_vase && sparse_infill_density > 0) // add alternating extra wall
+ loop_number++;
- // Set the bottommost layer to be one wall
- const bool is_bottom_layer = (this->layer_id == object_config->raft_layers) ? true : false;
- if (is_bottom_layer && only_one_wall_first_layer)
- loop_number = 0;
+ // Set the bottommost layer to be one wall
+ const bool is_bottom_layer = (this->layer_id == object_config->raft_layers) ? true : false;
+ if (is_bottom_layer && only_one_wall_first_layer)
+ loop_number = 0;
- // Orca: set the topmost layer to be one wall according to the config
- const bool is_topmost_layer = (this->upper_slices == nullptr) ? true : false;
- if (is_topmost_layer && loop_number > 0 && only_one_wall_top)
- loop_number = 0;
+ // Orca: set the topmost layer to be one wall according to the config
+ const bool is_topmost_layer = (this->upper_slices == nullptr) ? true : false;
+ if (is_topmost_layer && loop_number > 0 && only_one_wall_top)
+ loop_number = 0;
- auto apply_precise_outer_wall = config->precise_outer_wall && config->wall_sequence == WallSequence::InnerOuter;
- // Orca: properly adjust offset for the outer wall if precise_outer_wall is enabled.
- ExPolygons last = offset_ex(surface.expolygon.simplify_p(surface_simplify_resolution),
- apply_precise_outer_wall? -float(ext_perimeter_width - ext_perimeter_spacing )
- : -float(ext_perimeter_width / 2. - ext_perimeter_spacing / 2.));
+ auto apply_precise_outer_wall = config->precise_outer_wall && config->wall_sequence == WallSequence::InnerOuter;
+ // Orca: properly adjust offset for the outer wall if precise_outer_wall is enabled.
+ ExPolygons last = offset_ex(surface.expolygon.simplify_p(surface_simplify_resolution),
+ apply_precise_outer_wall? -float(ext_perimeter_width - ext_perimeter_spacing )
+ : -float(ext_perimeter_width / 2. - ext_perimeter_spacing / 2.));
- Arachne::WallToolPathsParams input_params = Arachne::make_paths_params(this->layer_id, *object_config, *print_config);
- // Set params is_top_or_bottom_layer for adjusting short-wall removal sensitivity.
- input_params.is_top_or_bottom_layer = (is_bottom_layer || is_topmost_layer) ? true : false;
+ Arachne::WallToolPathsParams input_params = Arachne::make_paths_params(this->layer_id, *object_config, *print_config);
+ // Set params is_top_or_bottom_layer for adjusting short-wall removal sensitivity.
+ input_params.is_top_or_bottom_layer = (is_bottom_layer || is_topmost_layer) ? true : false;
- coord_t wall_0_inset = 0;
- if (apply_precise_outer_wall)
- wall_0_inset = -coord_t(ext_perimeter_width / 2 - ext_perimeter_spacing / 2);
+ coord_t wall_0_inset = 0;
+ if (apply_precise_outer_wall)
+ wall_0_inset = -coord_t(ext_perimeter_width / 2 - ext_perimeter_spacing / 2);
- //PS: One wall top surface for Arachne
- ExPolygons top_expolygons;
- // Calculate how many inner loops remain when TopSurfaces is selected.
- const int inner_loop_number = (only_one_wall_top && upper_slices != nullptr) ? loop_number - 1 : -1;
+ //PS: One wall top surface for Arachne
+ ExPolygons top_expolygons;
+ // Calculate how many inner loops remain when TopSurfaces is selected.
+ const int inner_loop_number = (only_one_wall_top && upper_slices != nullptr) ? loop_number - 1 : -1;
- // Set one perimeter when TopSurfaces is selected.
- if (only_one_wall_top && loop_number > 0)
- loop_number = 0;
+ // Set one perimeter when TopSurfaces is selected.
+ if (only_one_wall_top && loop_number > 0)
+ loop_number = 0;
- Arachne::WallToolPathsParams input_params_tmp = input_params;
+ Arachne::WallToolPathsParams input_params_tmp = input_params;
- Polygons last_p = to_polygons(last);
- Arachne::WallToolPaths wallToolPaths(last_p, bead_width_0, perimeter_spacing, coord_t(loop_number + 1),
- wall_0_inset, layer_height, input_params_tmp);
- std::vector perimeters = wallToolPaths.getToolPaths();
- ExPolygons infill_contour = union_ex(wallToolPaths.getInnerContour());
+ Polygons last_p = to_polygons(last);
+ Arachne::WallToolPaths wallToolPaths(last_p, bead_width_0, perimeter_spacing, coord_t(loop_number + 1),
+ wall_0_inset, layer_height, input_params_tmp);
+ std::vector perimeters = wallToolPaths.getToolPaths();
+ ExPolygons infill_contour = union_ex(wallToolPaths.getInnerContour());
- // Check if there are some remaining perimeters to generate (the number of perimeters
- // is greater than one together with enabled the single perimeter on top surface feature).
- if (inner_loop_number >= 0) {
- assert(upper_slices != nullptr);
+ // Check if there are some remaining perimeters to generate (the number of perimeters
+ // is greater than one together with enabled the single perimeter on top surface feature).
+ if (inner_loop_number >= 0) {
+ assert(upper_slices != nullptr);
- coord_t perimeter_width = this->perimeter_flow.scaled_width();
+ coord_t perimeter_width = this->perimeter_flow.scaled_width();
- // Filter out areas that are too thin and expand top surface polygons a bit to hide the wall line.
- // ORCA: skip if the top surface area is smaller than "min_width_top_surface"
- const float top_surface_min_width = std::max(float(ext_perimeter_spacing) / 4.f + scaled(0.00001), float(scale_(config->min_width_top_surface.get_abs_value(unscale_(perimeter_width)))) / 4.f);
+ // Filter out areas that are too thin and expand top surface polygons a bit to hide the wall line.
+ // ORCA: skip if the top surface area is smaller than "min_width_top_surface"
+ const float top_surface_min_width = std::max(float(ext_perimeter_spacing) / 4.f + scaled(0.00001), float(scale_(config->min_width_top_surface.get_abs_value(unscale_(perimeter_width)))) / 4.f);
- // Get top ExPolygons from the given contour. uncovered reports whether the upper layer leaves any of the
- // contour uncovered, before bridges and too thin areas are filtered out.
- auto get_top_expolygons = [&](const ExPolygons &contour, bool &uncovered) {
- // Contour bounding box.
- BoundingBox contour_bbox = get_extents(contour);
- contour_bbox.offset(SCALED_EPSILON);
+ // Get top ExPolygons from the given contour. uncovered reports whether the upper layer leaves any of the
+ // contour uncovered, before bridges and too thin areas are filtered out.
+ auto get_top_expolygons = [&](const ExPolygons &contour, bool &uncovered) {
+ // Contour bounding box.
+ BoundingBox contour_bbox = get_extents(contour);
+ contour_bbox.offset(SCALED_EPSILON);
- Polygons upper_slices_clipped;
- if (object_config->interface_shells) {
- auto upper_slicer_same_region = to_expolygons(this->upper_slices_same_region->surfaces);
- upper_slices_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(upper_slicer_same_region, contour_bbox);
- } else
- upper_slices_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*upper_slices, contour_bbox);
+ Polygons upper_slices_clipped;
+ if (object_config->interface_shells) {
+ auto upper_slicer_same_region = to_expolygons(this->upper_slices_same_region->surfaces);
+ upper_slices_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(upper_slicer_same_region, contour_bbox);
+ } else
+ upper_slices_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*upper_slices, contour_bbox);
- ExPolygons top = diff_ex(contour, upper_slices_clipped);
- uncovered = !top.empty();
- if (top.empty())
- return top;
+ ExPolygons top = diff_ex(contour, upper_slices_clipped);
+ uncovered = !top.empty();
+ if (top.empty())
+ return top;
- if (lower_slices != nullptr) {
- const float bridge_offset = float(std::max(ext_perimeter_spacing, perimeter_width));
- const Polygons lower_slices_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*lower_slices, contour_bbox);
- const ExPolygons current_slices_bridges = offset_ex(diff_ex(top, lower_slices_clipped), bridge_offset);
+ if (lower_slices != nullptr) {
+ const float bridge_offset = float(std::max(ext_perimeter_spacing, perimeter_width));
+ const Polygons lower_slices_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*lower_slices, contour_bbox);
+ const ExPolygons current_slices_bridges = offset_ex(diff_ex(top, lower_slices_clipped), bridge_offset);
- // Remove bridges from top surface polygons.
- top = diff_ex(top, current_slices_bridges);
+ // Remove bridges from top surface polygons.
+ top = diff_ex(top, current_slices_bridges);
+ }
+
+ // Shrink the polygon to remove the small areas, then expand it back out plus a maragin to hide the wall line a little.
+ // ORCA: Expand the polygon with half the perimeter width in addition to the contracted amount,
+ // not the full perimeter width as PS does, to enable thin lettering to print on the top surface without nozzle collisions
+ // due to thin lines being generated
+ top = offset2_ex(top, -top_surface_min_width, top_surface_min_width + float(perimeter_width * 0.85));
+
+ // Get final top ExPolygons (bridges were excluded above, so they stay walled).
+ return intersection_ex(top, contour);
+ };
+
+ // Walls with the full count, as generated when the single perimeter feature is disabled. Generated on first use.
+ std::vector full_perimeters;
+ Polygons full_inner_contour;
+ bool full_perimeters_generated = false;
+ auto generate_full_perimeters = [&]() {
+ if (full_perimeters_generated)
+ return;
+ Arachne::WallToolPaths full_tool_paths(last_p, bead_width_0, perimeter_spacing, coord_t(inner_loop_number + 2), wall_0_inset, layer_height, input_params_tmp);
+ full_perimeters = full_tool_paths.getToolPaths();
+ full_inner_contour = full_tool_paths.getInnerContour();
+ full_perimeters_generated = true;
+ };
+
+ // ORCA: the single wall pass allows Arachne 2 beads across a wall, so it fills a wall narrower than 3 outer wall
+ // widths by widening both, where the full pass adds a middle bead. Over the top surface that is the intent;
+ // anywhere else it leaves no room for the inner walls. When the single wall pass's outer walls run away from
+ // the full pass's outside the top surface, take the full pass's outer walls and the area inside them instead.
+ // Walls closer than outer_wall_tolerance count as the same wall: a widened bead's centerline moves by half
+ // the width added, and only beads widened by more than twice the tolerance are looked for.
+ const coord_t outer_wall_tolerance = bead_width_0 / 10;
+ if (widest_bead(perimeters) > bead_width_0 + 2 * outer_wall_tolerance) {
+ // The single wall pass's inner contour where it widens no bead: inside nominal width outer walls.
+ const ExPolygons nominal_infill_contour = offset_ex(last, -float(bead_width_0 + wall_0_inset));
+ bool nominal_uncovered = false;
+ // Grown by an outer wall width to take in the outer walls bordering the top surface.
+ const ExPolygons top_zone = offset_ex(get_top_expolygons(nominal_infill_contour, nominal_uncovered), float(bead_width_0));
+ if (nominal_uncovered) {
+ generate_full_perimeters();
+ if (! full_perimeters.empty() && ! full_perimeters.front().empty() &&
+ length_off_reference(perimeters, full_perimeters.front(), top_zone, outer_wall_tolerance) > double(perimeter_width)) {
+ perimeters = { full_perimeters.front() };
+ infill_contour = diff_ex(nominal_infill_contour, walls_footprint(full_perimeters.front()), ApplySafetyOffset::Yes);
+ }
+ }
}
- // Shrink the polygon to remove the small areas, then expand it back out plus a maragin to hide the wall line a little.
- // ORCA: Expand the polygon with half the perimeter width in addition to the contracted amount,
- // not the full perimeter width as PS does, to enable thin lettering to print on the top surface without nozzle collisions
- // due to thin lines being generated
- top = offset2_ex(top, -top_surface_min_width, top_surface_min_width + float(perimeter_width * 0.85));
+ bool uncovered = false;
+ top_expolygons = get_top_expolygons(infill_contour, uncovered);
- // Get final top ExPolygons (bridges were excluded above, so they stay walled).
- return intersection_ex(top, contour);
- };
+ if (uncovered) {
+ // ORCA: onion the real region (inside the outer wall) so the remaining walls follow the actual
+ // geometry, then cut away the parts over the top surface. Re-onioning the non-top complement
+ // instead - the fallback when there is no top fill - walls the top/non-top interface and rings
+ // top-surface islands with inner walls that don't exist when the feature is disabled.
+ const bool clip_walls_over_top = top_fill_replaces_inner_walls(*this->config);
+ const Polygons inner_region = to_polygons(offset_ex(clip_walls_over_top ? infill_contour
+ : diff_ex(infill_contour, top_expolygons),
+ wall_0_inset));
+ Arachne::WallToolPaths inner_wall_tool_paths(inner_region, perimeter_spacing, perimeter_spacing, coord_t(inner_loop_number + 1), 0, layer_height, input_params_tmp);
+ std::vector inner_perimeters = inner_wall_tool_paths.getToolPaths();
- // Walls with the full count, as generated when the single perimeter feature is disabled. Generated on first use.
- std::vector full_perimeters;
- Polygons full_inner_contour;
- bool full_perimeters_generated = false;
- auto generate_full_perimeters = [&]() {
- if (full_perimeters_generated)
- return;
- Arachne::WallToolPaths full_tool_paths(last_p, bead_width_0, perimeter_spacing, coord_t(inner_loop_number + 2), wall_0_inset, layer_height, input_params_tmp);
- full_perimeters = full_tool_paths.getToolPaths();
- full_inner_contour = full_tool_paths.getInnerContour();
- full_perimeters_generated = true;
- };
+ if (clip_walls_over_top) {
+ Polygons kept_over_top;
+ clip_inner_walls_over_top(inner_perimeters, top_expolygons, perimeter_width, kept_over_top);
+ // Route the top fill around the walls kept despite grazing the top.
+ if (! kept_over_top.empty())
+ top_expolygons = diff_ex(top_expolygons, kept_over_top);
+ }
- // ORCA: the single wall pass allows Arachne 2 beads across a wall, so it fills a wall narrower than 3 outer wall
- // widths by widening both, where the full pass adds a middle bead. Over the top surface that is the intent;
- // anywhere else it leaves no room for the inner walls. When the single wall pass's outer walls run away from
- // the full pass's outside the top surface, take the full pass's outer walls and the area inside them instead.
- // Walls closer than outer_wall_tolerance count as the same wall: a widened bead's centerline moves by half
- // the width added, and only beads widened by more than twice the tolerance are looked for.
- const coord_t outer_wall_tolerance = bead_width_0 / 10;
- if (widest_bead(perimeters) > bead_width_0 + 2 * outer_wall_tolerance) {
- // The single wall pass's inner contour where it widens no bead: inside nominal width outer walls.
- const ExPolygons nominal_infill_contour = offset_ex(last, -float(bead_width_0 + wall_0_inset));
- bool nominal_uncovered = false;
- // Grown by an outer wall width to take in the outer walls bordering the top surface.
- const ExPolygons top_zone = offset_ex(get_top_expolygons(nominal_infill_contour, nominal_uncovered), float(bead_width_0));
- if (nominal_uncovered) {
+ // Recalculate indexes of inner perimeters before merging them: they come after the single outer wall.
+ if (!perimeters.empty())
+ for (Arachne::VariableWidthLines &inner_perimeter : inner_perimeters)
+ for (Arachne::ExtrusionLine &el : inner_perimeter)
+ ++el.inset_idx;
+
+ perimeters.insert(perimeters.end(), inner_perimeters.begin(), inner_perimeters.end());
+ infill_contour = union_ex(top_expolygons, inner_wall_tool_paths.getInnerContour());
+ } else {
+ // There is no top surface ExPolygon, so use the walls generated like when the single perimeter
+ // feature is disabled.
generate_full_perimeters();
- if (! full_perimeters.empty() && ! full_perimeters.front().empty() &&
- length_off_reference(perimeters, full_perimeters.front(), top_zone, outer_wall_tolerance) > double(perimeter_width)) {
- perimeters = { full_perimeters.front() };
- infill_contour = diff_ex(nominal_infill_contour, walls_footprint(full_perimeters.front()), ApplySafetyOffset::Yes);
- }
+ perimeters = std::move(full_perimeters);
+ infill_contour = union_ex(full_inner_contour);
}
}
+ //PS
- bool uncovered = false;
- top_expolygons = get_top_expolygons(infill_contour, uncovered);
+ loop_number = int(perimeters.size()) - 1;
- if (uncovered) {
- // ORCA: onion the real region (inside the outer wall) so the remaining walls follow the actual
- // geometry, then cut away the parts over the top surface. Re-onioning the non-top complement
- // instead - the fallback when there is no top fill - walls the top/non-top interface and rings
- // top-surface islands with inner walls that don't exist when the feature is disabled.
- const bool clip_walls_over_top = top_fill_replaces_inner_walls(*this->config);
- const Polygons inner_region = to_polygons(offset_ex(clip_walls_over_top ? infill_contour
- : diff_ex(infill_contour, top_expolygons),
- wall_0_inset));
- Arachne::WallToolPaths inner_wall_tool_paths(inner_region, perimeter_spacing, perimeter_spacing, coord_t(inner_loop_number + 1), 0, layer_height, input_params_tmp);
- std::vector inner_perimeters = inner_wall_tool_paths.getToolPaths();
-
- if (clip_walls_over_top) {
- Polygons kept_over_top;
- clip_inner_walls_over_top(inner_perimeters, top_expolygons, perimeter_width, kept_over_top);
- // Route the top fill around the walls kept despite grazing the top.
- if (! kept_over_top.empty())
- top_expolygons = diff_ex(top_expolygons, kept_over_top);
- }
-
- // Recalculate indexes of inner perimeters before merging them: they come after the single outer wall.
- if (!perimeters.empty())
- for (Arachne::VariableWidthLines &inner_perimeter : inner_perimeters)
- for (Arachne::ExtrusionLine &el : inner_perimeter)
- ++el.inset_idx;
-
- perimeters.insert(perimeters.end(), inner_perimeters.begin(), inner_perimeters.end());
- infill_contour = union_ex(top_expolygons, inner_wall_tool_paths.getInnerContour());
- } else {
- // There is no top surface ExPolygon, so use the walls generated like when the single perimeter
- // feature is disabled.
- generate_full_perimeters();
- perimeters = std::move(full_perimeters);
- infill_contour = union_ex(full_inner_contour);
+ #ifdef ARACHNE_DEBUG
+ {
+ static int iRun = 0;
+ export_perimeters_to_svg(debug_out_path("arachne-perimeters-%d-%d.svg", layer_id, iRun++), to_polygons(last), perimeters, union_ex(wallToolPaths.getInnerContour()));
}
- }
- //PS
+ #endif
- loop_number = int(perimeters.size()) - 1;
+ // All closed ExtrusionLine should have the same the first and the last point.
+ // But in rare cases, Arachne produce ExtrusionLine marked as closed but without
+ // equal the first and the last point.
+ assert([&perimeters = std::as_const(perimeters)]() -> bool {
+ for (const Arachne::VariableWidthLines& perimeter : perimeters)
+ for (const Arachne::ExtrusionLine& el : perimeter)
+ if (el.is_closed && el.junctions.front().p != el.junctions.back().p)
+ return false;
+ return true;
+ }());
- #ifdef ARACHNE_DEBUG
- {
- static int iRun = 0;
- export_perimeters_to_svg(debug_out_path("arachne-perimeters-%d-%d.svg", layer_id, iRun++), to_polygons(last), perimeters, union_ex(wallToolPaths.getInnerContour()));
- }
-#endif
+ int start_perimeter = int(perimeters.size()) - 1;
+ int end_perimeter = -1;
+ int direction = -1;
- // All closed ExtrusionLine should have the same the first and the last point.
- // But in rare cases, Arachne produce ExtrusionLine marked as closed but without
- // equal the first and the last point.
- assert([&perimeters = std::as_const(perimeters)]() -> bool {
- for (const Arachne::VariableWidthLines& perimeter : perimeters)
- for (const Arachne::ExtrusionLine& el : perimeter)
- if (el.is_closed && el.junctions.front().p != el.junctions.back().p)
- return false;
- return true;
- }());
-
- int start_perimeter = int(perimeters.size()) - 1;
- int end_perimeter = -1;
- int direction = -1;
-
- bool is_outer_wall_first =
- this->config->wall_sequence == WallSequence::OuterInner ||
- this->config->wall_sequence == WallSequence::InnerOuterInner;
+ bool is_outer_wall_first =
+ this->config->wall_sequence == WallSequence::OuterInner ||
+ this->config->wall_sequence == WallSequence::InnerOuterInner;
- if (layer_id == 0){ // disable inner outer inner algorithm after the first layer
- is_outer_wall_first =
- this->config->wall_sequence == WallSequence::OuterInner || this->outer_walls_first_for_brim();
- }
- if (is_outer_wall_first) {
- start_perimeter = 0;
- end_perimeter = int(perimeters.size());
- direction = 1;
- }
-
- std::vector all_extrusions;
- for (int perimeter_idx = start_perimeter; perimeter_idx != end_perimeter; perimeter_idx += direction) {
- if (perimeters[perimeter_idx].empty())
- continue;
- for (Arachne::ExtrusionLine& wall : perimeters[perimeter_idx])
- all_extrusions.emplace_back(&wall);
- }
-
- // Find topological order with constraints from extrusions_constrains.
- std::vector blocked(all_extrusions.size(), 0); // Value indicating how many extrusions it is blocking (preceding extrusions) an extrusion.
- std::vector> blocking(all_extrusions.size()); // Each extrusion contains a vector of extrusions that are blocked by this extrusion.
- std::unordered_map map_extrusion_to_idx;
- for (size_t idx = 0; idx < all_extrusions.size(); idx++)
- map_extrusion_to_idx.emplace(all_extrusions[idx], idx);
-
- auto extrusions_constrains = Arachne::WallToolPaths::getRegionOrder(all_extrusions, is_outer_wall_first);
- for (auto [before, after] : extrusions_constrains) {
- auto after_it = map_extrusion_to_idx.find(after);
- ++blocked[after_it->second];
- blocking[map_extrusion_to_idx.find(before)->second].emplace_back(after_it->second);
- }
-
- std::vector processed(all_extrusions.size(), false); // Indicate that the extrusion was already processed.
- Point current_position = all_extrusions.empty() ? Point::Zero() : all_extrusions.front()->junctions.front().p; // Some starting position.
- std::vector ordered_extrusions; // To store our result in. At the end we'll std::swap.
- ordered_extrusions.reserve(all_extrusions.size());
-
- while (ordered_extrusions.size() < all_extrusions.size()) {
- size_t best_candidate = 0;
- double best_distance_sqr = std::numeric_limits::max();
- bool is_best_closed = false;
-
- std::vector available_candidates;
- for (size_t candidate = 0; candidate < all_extrusions.size(); ++candidate) {
- if (processed[candidate] || blocked[candidate])
- continue; // Not a valid candidate.
- available_candidates.push_back(candidate);
+ if (layer_id == 0){ // disable inner outer inner algorithm after the first layer
+ is_outer_wall_first =
+ this->config->wall_sequence == WallSequence::OuterInner || this->outer_walls_first_for_brim();
+ }
+ if (is_outer_wall_first) {
+ start_perimeter = 0;
+ end_perimeter = int(perimeters.size());
+ direction = 1;
}
- std::sort(available_candidates.begin(), available_candidates.end(), [&all_extrusions](const size_t a_idx, const size_t b_idx) -> bool {
- return all_extrusions[a_idx]->is_closed < all_extrusions[b_idx]->is_closed;
- });
-
- for (const size_t candidate_path_idx : available_candidates) {
- auto& path = all_extrusions[candidate_path_idx];
-
- if (path->junctions.empty()) { // No vertices in the path. Can't find the start position then or really plan it in. Put that at the end.
- if (best_distance_sqr == std::numeric_limits::max()) {
- best_candidate = candidate_path_idx;
- is_best_closed = path->is_closed;
- }
+ std::vector all_extrusions;
+ for (int perimeter_idx = start_perimeter; perimeter_idx != end_perimeter; perimeter_idx += direction) {
+ if (perimeters[perimeter_idx].empty())
continue;
+ for (Arachne::ExtrusionLine& wall : perimeters[perimeter_idx])
+ all_extrusions.emplace_back(&wall);
+ }
+
+ // Find topological order with constraints from extrusions_constrains.
+ std::vector blocked(all_extrusions.size(), 0); // Value indicating how many extrusions it is blocking (preceding extrusions) an extrusion.
+ std::vector> blocking(all_extrusions.size()); // Each extrusion contains a vector of extrusions that are blocked by this extrusion.
+ std::unordered_map map_extrusion_to_idx;
+ for (size_t idx = 0; idx < all_extrusions.size(); idx++)
+ map_extrusion_to_idx.emplace(all_extrusions[idx], idx);
+
+ auto extrusions_constrains = Arachne::WallToolPaths::getRegionOrder(all_extrusions, is_outer_wall_first);
+ for (auto [before, after] : extrusions_constrains) {
+ auto after_it = map_extrusion_to_idx.find(after);
+ ++blocked[after_it->second];
+ blocking[map_extrusion_to_idx.find(before)->second].emplace_back(after_it->second);
+ }
+
+ std::vector processed(all_extrusions.size(), false); // Indicate that the extrusion was already processed.
+ Point current_position = all_extrusions.empty() ? Point::Zero() : all_extrusions.front()->junctions.front().p; // Some starting position.
+ std::vector ordered_extrusions; // To store our result in. At the end we'll std::swap.
+ ordered_extrusions.reserve(all_extrusions.size());
+
+ while (ordered_extrusions.size() < all_extrusions.size()) {
+ size_t best_candidate = 0;
+ double best_distance_sqr = std::numeric_limits::max();
+ bool is_best_closed = false;
+
+ std::vector available_candidates;
+ for (size_t candidate = 0; candidate < all_extrusions.size(); ++candidate) {
+ if (processed[candidate] || blocked[candidate])
+ continue; // Not a valid candidate.
+ available_candidates.push_back(candidate);
}
- const Point candidate_position = path->junctions.front().p;
- double distance_sqr = (current_position - candidate_position).cast().norm();
- if (distance_sqr < best_distance_sqr) { // Closer than the best candidate so far.
- if (path->is_closed || (!path->is_closed && best_distance_sqr != std::numeric_limits::max()) || (!path->is_closed && !is_best_closed)) {
- best_candidate = candidate_path_idx;
- best_distance_sqr = distance_sqr;
- is_best_closed = path->is_closed;
+ std::sort(available_candidates.begin(), available_candidates.end(), [&all_extrusions](const size_t a_idx, const size_t b_idx) -> bool {
+ return all_extrusions[a_idx]->is_closed < all_extrusions[b_idx]->is_closed;
+ });
+
+ for (const size_t candidate_path_idx : available_candidates) {
+ auto& path = all_extrusions[candidate_path_idx];
+
+ if (path->junctions.empty()) { // No vertices in the path. Can't find the start position then or really plan it in. Put that at the end.
+ if (best_distance_sqr == std::numeric_limits::max()) {
+ best_candidate = candidate_path_idx;
+ is_best_closed = path->is_closed;
+ }
+ continue;
}
+
+ const Point candidate_position = path->junctions.front().p;
+ double distance_sqr = (current_position - candidate_position).cast().norm();
+ if (distance_sqr < best_distance_sqr) { // Closer than the best candidate so far.
+ if (path->is_closed || (!path->is_closed && best_distance_sqr != std::numeric_limits::max()) || (!path->is_closed && !is_best_closed)) {
+ best_candidate = candidate_path_idx;
+ best_distance_sqr = distance_sqr;
+ is_best_closed = path->is_closed;
+ }
+ }
+ }
+
+ auto& best_path = all_extrusions[best_candidate];
+ ordered_extrusions.push_back({ best_path, best_path->is_contour() });
+ processed[best_candidate] = true;
+ for (size_t unlocked_idx : blocking[best_candidate])
+ blocked[unlocked_idx]--;
+
+ if (!best_path->junctions.empty()) { //If all paths were empty, the best path is still empty. We don't upate the current position then.
+ if (best_path->is_closed)
+ current_position = best_path->junctions[0].p; //We end where we started.
+ else
+ current_position = best_path->junctions.back().p; //Pick the other end from where we started.
}
}
- auto& best_path = all_extrusions[best_candidate];
- ordered_extrusions.push_back({ best_path, best_path->is_contour() });
- processed[best_candidate] = true;
- for (size_t unlocked_idx : blocking[best_candidate])
- blocked[unlocked_idx]--;
+ // printf("New Layer: Layer ID %d\n",layer_id); //debug - new layer
+ if (this->config->wall_sequence == WallSequence::InnerOuterInner && layer_id > 0) { // only enable inner outer inner algorithm after first layer
+ if (ordered_extrusions.size() > 2) { // 3 walls minimum needed to do inner outer inner ordering
+ int position = 0; // index to run the re-ordering for multiple external perimeters in a single island.
+ int arr_i, arr_j = 0; // indexes to run through the walls in the for loops
+ int outer, first_internal, second_internal, max_internal, current_perimeter; // allocate index values
+
+ // To address any remaining scenarios where the outer perimeter contour is not first on the list as arachne sometimes reorders the perimeters when clustering
+ // for OI mode that is used the basis for IOI
+ bringContoursToFront(ordered_extrusions);
+ std::vector reordered_extrusions;
+
+ // Debug statement to print spacing values:
+ //printf("External threshold - Ext perimeter: %d Ext spacing: %d Int perimeter: %d Int spacing: %d\n", this->ext_perimeter_flow.scaled_width(),this->ext_perimeter_flow.scaled_spacing(),this->perimeter_flow.scaled_width(), this->perimeter_flow.scaled_spacing());
- if (!best_path->junctions.empty()) { //If all paths were empty, the best path is still empty. We don't upate the current position then.
- if (best_path->is_closed)
- current_position = best_path->junctions[0].p; //We end where we started.
- else
- current_position = best_path->junctions.back().p; //Pick the other end from where we started.
- }
- }
-
- // printf("New Layer: Layer ID %d\n",layer_id); //debug - new layer
- if (this->config->wall_sequence == WallSequence::InnerOuterInner && layer_id > 0) { // only enable inner outer inner algorithm after first layer
- if (ordered_extrusions.size() > 2) { // 3 walls minimum needed to do inner outer inner ordering
- int position = 0; // index to run the re-ordering for multiple external perimeters in a single island.
- int arr_i, arr_j = 0; // indexes to run through the walls in the for loops
- int outer, first_internal, second_internal, max_internal, current_perimeter; // allocate index values
+ // Get searching thresholds. For an external perimeter we take the external perimeter spacing/2 plus the internal perimeter spacing/2 and expand by the factor
+ // rounding errors. When precise wall is enabled, the external perimeter full spacing is used.
+ coord_t threshold_external = (apply_precise_outer_wall)
+ // Precise outer wall: use the full external spacing
+ ? ( this->ext_perimeter_flow.scaled_spacing()
+ + this->perimeter_flow.scaled_spacing()/2.0 )
+ // Normal: half ext spacing plus half int spacing
+ : ( this->ext_perimeter_flow.scaled_spacing()/2.0
+ + this->perimeter_flow.scaled_spacing()/2.0 );
- // To address any remaining scenarios where the outer perimeter contour is not first on the list as arachne sometimes reorders the perimeters when clustering
- // for OI mode that is used the basis for IOI
- bringContoursToFront(ordered_extrusions);
- std::vector reordered_extrusions;
+ // For the intenal perimeter threshold, the distance is the internal perimeter spacing expanded by the factor to cover rounding errors.
+ coord_t threshold_internal = this->perimeter_flow.scaled_spacing();
- // Debug statement to print spacing values:
- //printf("External threshold - Ext perimeter: %d Ext spacing: %d Int perimeter: %d Int spacing: %d\n", this->ext_perimeter_flow.scaled_width(),this->ext_perimeter_flow.scaled_spacing(),this->perimeter_flow.scaled_width(), this->perimeter_flow.scaled_spacing());
-
- // Get searching thresholds. For an external perimeter we take the external perimeter spacing/2 plus the internal perimeter spacing/2 and expand by the factor
- // rounding errors. When precise wall is enabled, the external perimeter full spacing is used.
- coord_t threshold_external = (apply_precise_outer_wall)
- // Precise outer wall ⇒ use “full external spacing”
- ? ( this->ext_perimeter_flow.scaled_spacing()
- + this->perimeter_flow.scaled_spacing()/2.0 )
- // Normal ⇒ half ext spacing + half int spacing
- : ( this->ext_perimeter_flow.scaled_spacing()/2.0
- + this->perimeter_flow.scaled_spacing()/2.0 );
+ // Re-order extrusions based on distance
+ // Alorithm will aggresively optimise for the appearance of the outermost perimeter
+ ordered_extrusions = reorderPerimetersByProximity(ordered_extrusions,threshold_external,threshold_internal );
+ reordered_extrusions = ordered_extrusions; // copy them into the reordered extrusions vector to allow for IOI operations to be performed below without altering the base ordered extrusions list.
- // For the intenal perimeter threshold, the distance is the internal perimeter spacing expanded by the factor to cover rounding errors.
- coord_t threshold_internal = this->perimeter_flow.scaled_spacing();
-
- // Re-order extrusions based on distance
- // Alorithm will aggresively optimise for the appearance of the outermost perimeter
- ordered_extrusions = reorderPerimetersByProximity(ordered_extrusions,threshold_external,threshold_internal );
- reordered_extrusions = ordered_extrusions; // copy them into the reordered extrusions vector to allow for IOI operations to be performed below without altering the base ordered extrusions list.
-
- // Now start the sandwich mode wall re-ordering using the reordered_extrusions as the basis
- // scan to find the external perimeter, first internal, second internal and last perimeter in the island.
- // We then advance the position index to move to the second island and continue until there are no more
- // perimeters left.
- while (position < reordered_extrusions.size()) {
- outer = first_internal = second_internal = current_perimeter = -1; // initialise all index values to -1
- max_internal = reordered_extrusions.size()-1; // initialise the maximum internal perimeter to the last perimeter on the extrusion list
- // run through the walls to get the index values that need re-ordering until the first one for each
- // is found. Start at "position" index to enable the for loop to iterate for multiple external
- // perimeters in a single island
- // printf("Reorder Loop. Position %d, extrusion list size: %d, Outer index %d, inner index %d, second inner index %d\n", position, reordered_extrusions.size(),outer,first_internal,second_internal);
- for (arr_i = position; arr_i < reordered_extrusions.size(); ++arr_i) {
- // printf("Perimeter: extrusion inset index %d, ordered extrusions array position %d\n",reordered_extrusions[arr_i].extrusion->inset_idx, arr_i);
- switch (reordered_extrusions[arr_i].extrusion->inset_idx) {
- case 0: // external perimeter
- if (outer == -1)
- outer = arr_i;
- break;
- case 1: // first internal wall
- if (first_internal==-1 && arr_i>outer && outer!=-1){
- first_internal = arr_i;
- }
- break;
- case 2: // second internal wall
- if (second_internal == -1 && arr_i > first_internal && outer!=-1){
- second_internal = arr_i;
- }
- break;
- }
- if(outer >-1 && first_internal>-1 && reordered_extrusions[arr_i].extrusion->inset_idx == 0){ // found a new external perimeter after we've found at least a first internal perimeter to re-order.
- // This means we entered a new island.
- arr_i=arr_i-1; //step back one perimeter
- max_internal = arr_i; // new maximum internal perimeter is now this as we have found a new external perimeter, hence a new island.
- break; // exit the for loop
- }
- }
-
- // printf("Layer ID %d, Outer index %d, inner index %d, second inner index %d, maximum internal perimeter %d \n",layer_id,outer,first_internal,second_internal, max_internal);
- if (outer > -1 && first_internal > -1 && second_internal > -1) { // found all three perimeters to re-order? If not the perimeters will be processed outside in.
- std::vector inner_outer_extrusions; // temporary array to hold extrusions for reordering
- inner_outer_extrusions.resize(max_internal - position + 1); // reserve array containing the number of perimeters before a new island. Variables are array indexes hence need to add +1 to convert to position allocations
- // printf("Allocated array size %d, max_internal index %d, start position index %d \n",max_internal-position+1,max_internal,position);
-
- for (arr_j = max_internal; arr_j >=position; --arr_j){ // go inside out towards the external perimeter (perimeters in reverse order) and store all internal perimeters until the first one identified with inset index 2
- if(arr_j >= second_internal){
- //printf("Inside out loop: Mapped perimeter index %d to array position %d\n", arr_j, max_internal-arr_j);
- inner_outer_extrusions[max_internal-arr_j] = reordered_extrusions[arr_j];
- current_perimeter++;
+ // Now start the sandwich mode wall re-ordering using the reordered_extrusions as the basis
+ // scan to find the external perimeter, first internal, second internal and last perimeter in the island.
+ // We then advance the position index to move to the second island and continue until there are no more
+ // perimeters left.
+ while (position < reordered_extrusions.size()) {
+ outer = first_internal = second_internal = current_perimeter = -1; // initialise all index values to -1
+ max_internal = reordered_extrusions.size()-1; // initialise the maximum internal perimeter to the last perimeter on the extrusion list
+ // run through the walls to get the index values that need re-ordering until the first one for each
+ // is found. Start at "position" index to enable the for loop to iterate for multiple external
+ // perimeters in a single island
+ // printf("Reorder Loop. Position %d, extrusion list size: %d, Outer index %d, inner index %d, second inner index %d\n", position, reordered_extrusions.size(),outer,first_internal,second_internal);
+ for (arr_i = position; arr_i < reordered_extrusions.size(); ++arr_i) {
+ // printf("Perimeter: extrusion inset index %d, ordered extrusions array position %d\n",reordered_extrusions[arr_i].extrusion->inset_idx, arr_i);
+ switch (reordered_extrusions[arr_i].extrusion->inset_idx) {
+ case 0: // external perimeter
+ if (outer == -1)
+ outer = arr_i;
+ break;
+ case 1: // first internal wall
+ if (first_internal==-1 && arr_i>outer && outer!=-1){
+ first_internal = arr_i;
+ }
+ break;
+ case 2: // second internal wall
+ if (second_internal == -1 && arr_i > first_internal && outer!=-1){
+ second_internal = arr_i;
+ }
+ break;
+ }
+ if(outer >-1 && first_internal>-1 && reordered_extrusions[arr_i].extrusion->inset_idx == 0){ // found a new external perimeter after we've found at least a first internal perimeter to re-order.
+ // This means we entered a new island.
+ arr_i=arr_i-1; //step back one perimeter
+ max_internal = arr_i; // new maximum internal perimeter is now this as we have found a new external perimeter, hence a new island.
+ break; // exit the for loop
}
}
+
+ // printf("Layer ID %d, Outer index %d, inner index %d, second inner index %d, maximum internal perimeter %d \n",layer_id,outer,first_internal,second_internal, max_internal);
+ if (outer > -1 && first_internal > -1 && second_internal > -1) { // found all three perimeters to re-order? If not the perimeters will be processed outside in.
+ std::vector inner_outer_extrusions; // temporary array to hold extrusions for reordering
+ inner_outer_extrusions.resize(max_internal - position + 1); // reserve array containing the number of perimeters before a new island. Variables are array indexes hence need to add +1 to convert to position allocations
+ // printf("Allocated array size %d, max_internal index %d, start position index %d \n",max_internal-position+1,max_internal,position);
- for (arr_j = position; arr_j < second_internal; ++arr_j){ // go outside in and map the remaining perimeters (external and first internal wall(s)) using the outside in wall order
- // printf("Outside in loop: Mapped perimeter index %d to array position %d\n", arr_j, current_perimeter+1);
- inner_outer_extrusions[++current_perimeter] = reordered_extrusions[arr_j];
+ for (arr_j = max_internal; arr_j >=position; --arr_j){ // go inside out towards the external perimeter (perimeters in reverse order) and store all internal perimeters until the first one identified with inset index 2
+ if(arr_j >= second_internal){
+ //printf("Inside out loop: Mapped perimeter index %d to array position %d\n", arr_j, max_internal-arr_j);
+ inner_outer_extrusions[max_internal-arr_j] = reordered_extrusions[arr_j];
+ current_perimeter++;
+ }
+ }
+
+ for (arr_j = position; arr_j < second_internal; ++arr_j){ // go outside in and map the remaining perimeters (external and first internal wall(s)) using the outside in wall order
+ // printf("Outside in loop: Mapped perimeter index %d to array position %d\n", arr_j, current_perimeter+1);
+ inner_outer_extrusions[++current_perimeter] = reordered_extrusions[arr_j];
+ }
+
+ for(arr_j = position; arr_j <= max_internal; ++arr_j) // replace perimeter array with the new re-ordered array
+ ordered_extrusions[arr_j] = inner_outer_extrusions[arr_j-position];
}
-
- for(arr_j = position; arr_j <= max_internal; ++arr_j) // replace perimeter array with the new re-ordered array
- ordered_extrusions[arr_j] = inner_outer_extrusions[arr_j-position];
+ // go to the next perimeter from the current position to continue scanning for external walls in the same island
+ position = arr_i + 1;
}
- // go to the next perimeter from the current position to continue scanning for external walls in the same island
- position = arr_i + 1;
}
}
- }
- bool steep_overhang_contour = false;
- bool steep_overhang_hole = false;
- if (!config->overhang_reverse) {
- // Skip steep overhang detection no reverse is specified
- steep_overhang_contour = true;
- steep_overhang_hole = true;
- }
- if (ExtrusionEntityCollection extrusion_coll = traverse_extrusions(*this, ordered_extrusions, steep_overhang_contour, steep_overhang_hole); !extrusion_coll.empty()) {
- if (config->overhang_reverse) {
- reorient_perimeters(extrusion_coll, steep_overhang_contour, steep_overhang_hole,
- this->config->overhang_reverse_internal_only);
+ bool steep_overhang_contour = false;
+ bool steep_overhang_hole = false;
+ if (!config->overhang_reverse) {
+ // Skip steep overhang detection no reverse is specified
+ steep_overhang_contour = true;
+ steep_overhang_hole = true;
+ }
+ if (ExtrusionEntityCollection extrusion_coll = traverse_extrusions(*this, ordered_extrusions, steep_overhang_contour, steep_overhang_hole); !extrusion_coll.empty()) {
+ if (config->overhang_reverse) {
+ reorient_perimeters(extrusion_coll, steep_overhang_contour, steep_overhang_hole,
+ this->config->overhang_reverse_internal_only);
+ }
+ defer_unsupported_loops(*this, extrusion_coll);
+ result.loops = std::move(extrusion_coll);
+ result.has_loops = true;
}
- defer_unsupported_loops(*this, extrusion_coll);
- this->loops->append(extrusion_coll);
- }
- const coord_t spacing = (perimeters.size() == 1) ? ext_perimeter_spacing2 : perimeter_spacing;
+ const coord_t spacing = (perimeters.size() == 1) ? ext_perimeter_spacing2 : perimeter_spacing;
- if (offset_ex(infill_contour, -float(spacing / 2.)).empty())
- infill_contour.clear(); // Infill region is too small, so let's filter it out.
+ if (offset_ex(infill_contour, -float(spacing / 2.)).empty())
+ infill_contour.clear(); // Infill region is too small, so let's filter it out.
- // create one more offset to be used as boundary for fill
- // we offset by half the perimeter spacing (to get to the actual infill boundary)
- // and then we offset back and forth by half the infill spacing to only consider the
- // non-collapsing regions
- coord_t inset =
- (loop_number < 0) ? 0 :
- (loop_number == 0) ?
- // one loop
- ext_perimeter_spacing :
- // two or more loops?
- perimeter_spacing;
- coord_t top_inset = inset;
+ // create one more offset to be used as boundary for fill
+ // we offset by half the perimeter spacing (to get to the actual infill boundary)
+ // and then we offset back and forth by half the infill spacing to only consider the
+ // non-collapsing regions
+ coord_t inset =
+ (loop_number < 0) ? 0 :
+ (loop_number == 0) ?
+ // one loop
+ ext_perimeter_spacing :
+ // two or more loops?
+ perimeter_spacing;
+ coord_t top_inset = inset;
- top_inset = coord_t(scale_(this->config->top_bottom_infill_wall_overlap.get_abs_value(unscale(inset))));
- if(is_topmost_layer || is_bottom_layer)
- inset = coord_t(scale_(this->config->top_bottom_infill_wall_overlap.get_abs_value(unscale(inset))));
- else
- inset = coord_t(scale_(this->config->infill_wall_overlap.get_abs_value(unscale(inset))));
+ top_inset = coord_t(scale_(this->config->top_bottom_infill_wall_overlap.get_abs_value(unscale(inset))));
+ if(is_topmost_layer || is_bottom_layer)
+ inset = coord_t(scale_(this->config->top_bottom_infill_wall_overlap.get_abs_value(unscale(inset))));
+ else
+ inset = coord_t(scale_(this->config->infill_wall_overlap.get_abs_value(unscale(inset))));
- // simplify infill contours according to resolution
- Polygons pp;
- for (ExPolygon& ex : infill_contour)
- ex.simplify_p(m_scaled_resolution, &pp);
- ExPolygons not_filled_exp = union_ex(pp);
- // collapse too narrow infill areas
- const auto min_perimeter_infill_spacing = coord_t(solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE));
+ // simplify infill contours according to resolution
+ Polygons pp;
+ for (ExPolygon& ex : infill_contour)
+ ex.simplify_p(m_scaled_resolution, &pp);
+ ExPolygons not_filled_exp = union_ex(pp);
+ // collapse too narrow infill areas
+ const auto min_perimeter_infill_spacing = coord_t(solid_infill_spacing * (1. - INSET_OVERLAP_TOLERANCE));
- ExPolygons infill_exp = offset2_ex(
- not_filled_exp,
- float(-min_perimeter_infill_spacing / 2.),
- float(inset + min_perimeter_infill_spacing / 2.));
- // append infill areas to fill_surfaces
- if (!top_expolygons.empty()) {
- infill_exp = union_ex(infill_exp, offset_ex(top_expolygons, double(top_inset)));
- }
- this->fill_surfaces->append(infill_exp, stInternal);
-
- apply_extra_perimeters(infill_exp);
-
- // BBS: get the no-overlap infill expolygons
- {
- ExPolygons polyWithoutOverlap;
- polyWithoutOverlap = offset2_ex(
+ ExPolygons infill_exp = offset2_ex(
not_filled_exp,
float(-min_perimeter_infill_spacing / 2.),
- float(+min_perimeter_infill_spacing / 2.));
- if (!top_expolygons.empty())
- polyWithoutOverlap = union_ex(polyWithoutOverlap, top_expolygons);
- this->fill_no_overlap->insert(this->fill_no_overlap->end(), polyWithoutOverlap.begin(), polyWithoutOverlap.end());
- }
+ float(inset + min_perimeter_infill_spacing / 2.));
+ // append infill areas to fill_surfaces
+ if (!top_expolygons.empty()) {
+ infill_exp = union_ex(infill_exp, offset_ex(top_expolygons, double(top_inset)));
+ }
+ result.infill = std::move(infill_exp);
+
+ // BBS: get the no-overlap infill expolygons
+ {
+ ExPolygons polyWithoutOverlap;
+ polyWithoutOverlap = offset2_ex(
+ not_filled_exp,
+ float(-min_perimeter_infill_spacing / 2.),
+ float(+min_perimeter_infill_spacing / 2.));
+ if (!top_expolygons.empty())
+ polyWithoutOverlap = union_ex(polyWithoutOverlap, top_expolygons);
+ result.no_overlap = std::move(polyWithoutOverlap);
+ }
+ }
+ });
+ for (ArachneSurfaceResult &result : results) {
+ if (result.has_loops)
+ // Moved, not copied: append(const ExtrusionEntity &) clones the whole wall tree of the island.
+ this->loops->append(std::move(result.loops));
+ this->fill_surfaces->append(result.infill, stInternal);
+ apply_extra_perimeters(result.infill);
+ append(*this->fill_no_overlap, std::move(result.no_overlap));
}
}
diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp
index 5354e93480..a25cb4492b 100644
--- a/src/libslic3r/PlaceholderParser.cpp
+++ b/src/libslic3r/PlaceholderParser.cpp
@@ -243,7 +243,7 @@ namespace client
struct expr
{
expr() {}
- expr(const expr &rhs) : m_type(rhs.type()), it_range(rhs.it_range)
+ expr(const expr &rhs) : m_type(rhs.type()), it_range(rhs.it_range), m_deferred(rhs.m_deferred)
{ if (rhs.type() == TYPE_STRING) m_data.s = new std::string(*rhs.m_data.s); else m_data.set(rhs.m_data); }
expr(expr &&rhs) : expr(std::move(rhs), rhs.it_range.begin(), rhs.it_range.end()) {}
@@ -258,13 +258,16 @@ namespace client
explicit expr(std::string &&s) : m_type(TYPE_STRING) { m_data.s = new std::string(std::move(s)); }
explicit expr(const std::string &s, const Iterator &it_begin, const Iterator &it_end) :
m_type(TYPE_STRING), it_range(it_begin, it_end) { m_data.s = new std::string(s); }
- explicit expr(expr &&rhs, const Iterator &it_begin, const Iterator &it_end) : m_type(rhs.type()), it_range{ it_begin, it_end }
+ explicit expr(expr &&rhs, const Iterator &it_begin, const Iterator &it_end) : m_type(rhs.type()), it_range{ it_begin, it_end }, m_deferred(rhs.m_deferred)
{
m_data.set(rhs.m_data);
- rhs.m_type = TYPE_EMPTY;
+ rhs.m_type = TYPE_EMPTY;
+ rhs.m_deferred = Deferred::None;
}
expr &operator=(const expr &rhs)
{
+ // set_s() clears m_deferred, which is also rhs.m_deferred on a self-assignment.
+ const Deferred deferred = rhs.m_deferred;
if (rhs.type() == TYPE_STRING) {
this->set_s(rhs.s());
} else {
@@ -272,6 +275,7 @@ namespace client
m_data.set(rhs.m_data);
}
this->it_range = rhs.it_range;
+ m_deferred = deferred;
return *this;
}
@@ -281,8 +285,10 @@ namespace client
this->reset();
m_type = rhs.type();
this->it_range = rhs.it_range;
+ m_deferred = rhs.m_deferred;
m_data.set(rhs.m_data);
rhs.m_type = TYPE_EMPTY;
+ rhs.m_deferred = Deferred::None;
}
return *this;
}
@@ -291,7 +297,8 @@ namespace client
{
if (this->type() == TYPE_STRING)
delete m_data.s;
- m_type = TYPE_EMPTY;
+ m_type = TYPE_EMPTY;
+ m_deferred = Deferred::None;
}
~expr() { reset(); }
@@ -304,6 +311,10 @@ namespace client
};
Type type() const { return m_type; }
bool numeric_type() const { return m_type == TYPE_INT || m_type == TYPE_DOUBLE; }
+ // A deferred string is either one deferred value or text that contains at least one.
+ enum class Deferred : unsigned char { None, Value, Text };
+ bool deferred() const { return m_deferred != Deferred::None; }
+ void set_deferred(Deferred deferred) { m_deferred = deferred; }
bool& b() { return m_data.b; }
bool b() const { return m_data.b; }
@@ -323,18 +334,21 @@ namespace client
std::string& s() { return *m_data.s; }
const std::string& s() const { return *m_data.s; }
void set_s(const std::string &s) {
+ m_deferred = Deferred::None;
if (this->type() == TYPE_STRING)
*m_data.s = s;
else
this->set_s_take_ownership(new std::string(s));
}
void set_s(std::string &&s) {
+ m_deferred = Deferred::None;
if (this->type() == TYPE_STRING)
*m_data.s = std::move(s);
else
this->set_s_take_ownership(new std::string(std::move(s)));
}
void set_s(const char *s) {
+ m_deferred = Deferred::None;
if (this->type() == TYPE_STRING)
*m_data.s = s;
else
@@ -478,12 +492,21 @@ namespace client
{
if (this->type() == TYPE_EMPTY) {
// Inside an if / else block to be skipped.
+ } else if (m_deferred == Deferred::Value && (rhs.type() != TYPE_STRING || rhs.m_deferred == Deferred::Value)) {
+ // A deferred value added to a number or to another deferred value is meant as a sum.
+ this->throw_if_deferred();
+ } else if (rhs.m_deferred == Deferred::Value && this->type() != TYPE_STRING) {
+ rhs.throw_if_deferred();
} else if (this->type() == TYPE_STRING) {
// Convert the right hand side to string and append.
*m_data.s += rhs.to_string();
+ if (this->deferred() || rhs.deferred())
+ m_deferred = Deferred::Text;
} else if (rhs.type() == TYPE_STRING) {
// Conver the left hand side to string, append rhs.
this->set_s(this->to_string() + rhs.s());
+ if (rhs.deferred())
+ m_deferred = Deferred::Text;
} else {
const char *err_msg = L("Cannot add non-numeric types.");
this->throw_if_not_numeric(err_msg);
@@ -602,6 +625,8 @@ namespace client
if (lhs.type() == TYPE_EMPTY)
// Inside an if / else block to be skipped
return;
+ lhs.throw_if_deferred();
+ rhs.throw_if_deferred();
bool value = false;
if (lhs.numeric_type() && rhs.numeric_type()) {
// Both types are numeric.
@@ -731,6 +756,7 @@ namespace client
if (lhs.type() == TYPE_EMPTY)
// Inside an if / else block to be skipped
return;
+ lhs.throw_if_deferred();
const std::string *subject = nullptr;
if (lhs.type() == TYPE_STRING) {
// One type is string, the other could be converted to string.
@@ -761,6 +787,7 @@ namespace client
if (subject.type() == TYPE_EMPTY)
// Inside an if / else block to be skipped
return;
+ subject.throw_if_deferred();
if (subject.type() != TYPE_STRING)
subject.throw_exception(L("regex_replace() first parameter must be a string."));
try {
@@ -785,6 +812,8 @@ namespace client
return;
}
if (! out.b()) {
+ match.throw_if_deferred();
+ pattern.throw_if_deferred();
if (match.type() != TYPE_STRING)
match.throw_exception(L("one_of(): First parameter (the string to match against) has to be a string value"));
if (pattern.type() != TYPE_STRING)
@@ -818,6 +847,8 @@ namespace client
if (lhs.type() == TYPE_EMPTY)
// Inside an if / else block to be skipped
return;
+ lhs.throw_if_deferred();
+ rhs.throw_if_deferred();
bool value = false;
if (lhs.type() == TYPE_BOOL && rhs.type() == TYPE_BOOL) {
value = (op == '|') ? (lhs.b() || rhs.b()) : (lhs.b() && rhs.b());
@@ -830,8 +861,16 @@ namespace client
static void logical_or (expr &lhs, expr &rhs) { logical_op(lhs, rhs, '|'); }
static void logical_and(expr &lhs, expr &rhs) { logical_op(lhs, rhs, '&'); }
+ static const char* deferred_message()
+ {
+ return L("This value is known only after the G-code is generated. It can be printed, but not used in a calculation or a comparison.");
+ }
+
+ // Reports the deferred message for a type error on a deferred value, since it holds a tag in place of the value.
void throw_exception(const char *message) const
{
+ if (this->deferred())
+ message = deferred_message();
boost::throw_exception(qi::expectation_failure(
this->it_range.begin(), this->it_range.end(), spirit::info(std::string("*") + message)));
}
@@ -842,11 +881,18 @@ namespace client
this->throw_exception(message);
}
+ void throw_if_deferred() const
+ {
+ if (this->deferred())
+ this->throw_exception(deferred_message());
+ }
+
private:
// This object will take ownership of the parameter string object "s".
void set_s_take_ownership(std::string* s) { assert(this->type() != TYPE_STRING); Data tmp; tmp.s = s; m_data.set(tmp); m_type = TYPE_STRING; }
Type m_type = TYPE_EMPTY;
+ Deferred m_deferred = Deferred::None;
union Data {
bool b;
@@ -895,6 +941,8 @@ namespace client
std::string error_message;
// Local variables declared in {if} branches that were not taken, see PlaceholderParser::check_inactive_branches.
mutable std::set inactive_local_variables;
+ // Tags of PlaceholderParser::set_deferred(), matched by value so that a copy in a variable is deferred too.
+ const std::set *deferred_tags = nullptr;
// Table to translate symbol tag to a human readable error message.
static std::map tag_to_error_message;
@@ -970,6 +1018,18 @@ namespace client
} else
this->config_local.set_key_value(opt_key, opt.release());
}
+ void mark_deferred(expr &output) const {
+ if (output.type() != expr::TYPE_STRING)
+ return;
+ expr::Deferred deferred = expr::Deferred::None;
+ if (deferred_tags != nullptr && output.s().find('@') != std::string::npos)
+ for (const std::string &tag : *deferred_tags)
+ if (output.s().find(tag) != std::string::npos) {
+ deferred = output.s() == tag ? expr::Deferred::Value : expr::Deferred::Text;
+ break;
+ }
+ output.set_deferred(deferred);
+ }
static void legacy_variable_expansion(const MyContext *ctx, IteratorRange &opt_key, std::string &output)
{
@@ -1430,6 +1490,7 @@ namespace client
vector_element_to_expr(ctx, opt, output);
else
scalar_variable_to_expr(ctx, opt, output);
+ ctx->mark_deferred(output);
}
output.it_range = opt.it_range;
}
@@ -1516,7 +1577,7 @@ namespace client
if (ctx->skipping()) {
} else if (lhs.opt) {
if (lhs.opt->is_vector())
- rhs.throw_exception(L("Cannot assign a scalar value to a vector variable."));
+ ctx->throw_exception(L("Cannot assign a scalar value to a vector variable."), rhs.it_range);
OptWithPos lhs_opt{ lhs.opt, lhs.it_range, true };
scalar_variable_assign_scalar(ctx, lhs_opt, rhs);
} else {
@@ -1542,7 +1603,7 @@ namespace client
if (ctx->skipping()) {
} else if (lhs.opt) {
if (lhs.opt->is_scalar())
- rhs_value.throw_exception(L("Cannot assign a vector value to a scalar variable."));
+ ctx->throw_exception(L("Cannot assign a vector value to a scalar variable."), rhs_value.it_range);
OptWithPos lhs_opt{ lhs.opt, lhs.it_range, true };
vector_variable_assign_expr_with_count(ctx, lhs_opt, rhs_count, rhs_value);
} else {
@@ -1568,7 +1629,7 @@ namespace client
if (! ctx->skipping()) {
check_writable(ctx, lhs);
if (lhs.opt->is_scalar())
- rhs_value.throw_exception(L("Cannot assign a vector value to a scalar variable."));
+ ctx->throw_exception(L("Cannot assign a vector value to a scalar variable."), rhs_value.it_range);
vector_variable_assign_expr_with_count(ctx, lhs, rhs_count, rhs_value);
}
}
@@ -1803,10 +1864,21 @@ namespace client
static void filament_change(const MyContext* ctx, expr& param)
{
+ if (param.type() == expr::TYPE_EMPTY)
+ // Inside an if / else block to be skipped
+ return;
MyContext *context = const_cast(ctx);
+ param.throw_if_deferred();
context->current_extruder_id = param.as_i();
}
+ // The replacement may put a deferred tag into the result.
+ static void regex_replace(const MyContext *ctx, expr &subject, IteratorRange &pattern, expr &replacement)
+ {
+ expr::regex_replace(subject, pattern, replacement);
+ ctx->mark_deferred(subject);
+ }
+
static void throw_exception(const std::string &msg, const IteratorRange &it_range)
{
// An asterix is added to the start of the string to differentiate the boost::spirit::info::tag content
@@ -2409,7 +2481,7 @@ namespace client
| (kw["zdigits"] > '(' > conditional_expression(_r1) [_val = _1] > ',' > conditional_expression(_r1) > optional_parameter(_r1))
[ px::bind(&expr::digits, _val, _2, _3) ]
| (kw["regex_replace"] > '(' > conditional_expression(_r1) [_val = _1] > ',' > regular_expression > ',' > conditional_expression(_r1) > ')')
- [ px::bind(&expr::regex_replace, _val, _2, _3) ]
+ [ px::bind(&MyContext::regex_replace, _r1, _val, _2, _3) ]
| (kw["int"] > '(' > conditional_expression(_r1) > ')') [ px::bind(&FactorActions::to_int, _1, _val) ]
| (kw["round"] > '(' > conditional_expression(_r1) > ')') [ px::bind(&FactorActions::round, _1, _val) ]
| (kw["ceil"] > '(' > conditional_expression(_r1) > ')') [ px::bind(&FactorActions::ceil, _1, _val) ]
@@ -2614,6 +2686,7 @@ std::string PlaceholderParser::process(const std::string &templ, unsigned int cu
context.config = &this->config();
context.config_override = config_override;
context.config_outputs = config_outputs;
+ context.deferred_tags = &m_deferred_tags;
context.current_extruder_id = current_extruder_id;
context.context_data = context_data;
return process_macro(templ, context);
diff --git a/src/libslic3r/PlaceholderParser.hpp b/src/libslic3r/PlaceholderParser.hpp
index 1ac758267b..c8094011c0 100644
--- a/src/libslic3r/PlaceholderParser.hpp
+++ b/src/libslic3r/PlaceholderParser.hpp
@@ -60,6 +60,9 @@ public:
void set(const std::string &key, double value) { this->set(key, new ConfigOptionFloat(value)); }
void set(const std::string &key, const std::vector &values) { this->set(key, new ConfigOptionStrings(values)); }
void set(const std::string &key, ConfigOption *opt) { m_config.set_key_value(key, opt); }
+ // Orca: sets a value known only after the G-code is generated, as a tag that post-processing replaces.
+ // A template may print it or join it to text, and a calculation or a comparison on it throws.
+ void set_deferred(const std::string &key, const std::string &tag) { this->set(key, tag); m_deferred_tags.insert(tag); }
DynamicConfig& config_writable() { return m_config; }
const DynamicConfig& config() const { return m_config; }
const ConfigOption* option(const std::string &key) const { return m_config.option(key); }
@@ -88,6 +91,7 @@ private:
// config has a higher priority than external_config when looking up a symbol.
DynamicConfig m_config;
const DynamicConfig *m_external_config;
+ std::set m_deferred_tags;
};
}
diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp
index 8e7ca2ed91..9c5801985e 100644
--- a/src/libslic3r/Polygon.hpp
+++ b/src/libslic3r/Polygon.hpp
@@ -34,7 +34,7 @@ public:
explicit Polygon(const Points &points) : MultiPoint(points) {}
Polygon(std::initializer_list points) : MultiPoint(points) {}
Polygon(const Polygon &other) : MultiPoint(other.points) {}
- Polygon(Polygon &&other) : MultiPoint(std::move(other.points)) {}
+ Polygon(Polygon &&other) noexcept : MultiPoint(std::move(other.points)) {}
static Polygon new_scale(const std::vector &points) {
Polygon pgn;
pgn.points.reserve(points.size());
@@ -43,7 +43,7 @@ public:
return pgn;
}
Polygon& operator=(const Polygon &other) { points = other.points; return *this; }
- Polygon& operator=(Polygon &&other) { points = std::move(other.points); return *this; }
+ Polygon& operator=(Polygon &&other) noexcept { points = std::move(other.points); return *this; }
Point& operator[](Points::size_type idx) { return this->points[idx]; }
const Point& operator[](Points::size_type idx) const { return this->points[idx]; }
diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp
index 29c795db33..3b59ab6c4c 100644
--- a/src/libslic3r/Polyline.hpp
+++ b/src/libslic3r/Polyline.hpp
@@ -26,7 +26,7 @@ class Polyline : public MultiPoint {
public:
Polyline() {};
Polyline(const Polyline& other) : MultiPoint(other.points), fitting_result(other.fitting_result) {}
- Polyline(Polyline &&other) : MultiPoint(std::move(other.points)), fitting_result(std::move(other.fitting_result)) {}
+ Polyline(Polyline &&other) noexcept : MultiPoint(std::move(other.points)), fitting_result(std::move(other.fitting_result)) {}
Polyline(std::initializer_list list) : MultiPoint(list) {
fitting_result.clear();
}
@@ -47,7 +47,7 @@ public:
fitting_result = other.fitting_result;
return *this;
}
- Polyline& operator=(Polyline&& other) {
+ Polyline& operator=(Polyline&& other) noexcept {
points = std::move(other.points);
fitting_result = std::move(other.fitting_result);
return *this;
diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp
index edd93390b4..81b78ec092 100644
--- a/src/libslic3r/Preset.cpp
+++ b/src/libslic3r/Preset.cpp
@@ -797,7 +797,7 @@ bool Preset::save(DynamicPrintConfig* parent_config)
{
ConfigOption *opt_src = config.option(option);
ConfigOption *opt_dst = temp_config.option(option, true);
- if (opt_dst->is_scalar() || !(opt_dst->nullable()))
+ if (opt_dst->is_scalar() || !(opt_dst->nullable()) || is_filament_extruder_override_key(option))
opt_dst->set(opt_src);
else {
ConfigOptionVectorBase* opt_vec_src = static_cast(opt_src);
@@ -2132,7 +2132,7 @@ Preset* PresetCollection::get_preset_differed_for_save(Preset& preset)
{
ConfigOption *opt_src = preset.config.option(option);
ConfigOption *opt_dst = temp_config.option(option, true);
- if (opt_dst->is_scalar() || !(opt_dst->nullable()))
+ if (opt_dst->is_scalar() || !(opt_dst->nullable()) || is_filament_extruder_override_key(option))
opt_dst->set(opt_src);
else {
ConfigOptionVectorBase* opt_vec_src = static_cast(opt_src);
@@ -2199,9 +2199,12 @@ int PresetCollection::get_differed_values_to_update(Preset& preset, std::map 100%), silently
// resurrecting an override the user turned off. See GitHub issue on Retract Before Wipe.
if (m_type == Preset::TYPE_FILAMENT) {
- for (const std::string& opt_key : filament_extruder_override_keys) {
- if (key_values.count(opt_key))
- continue; // already carried by the diff
+ for (const std::string& opt_key : preset.config.keys()) {
+ if (!is_filament_extruder_override_key(opt_key) || key_values.count(opt_key))
+ continue; // not an override, or already carried by the diff
+ const ConfigOptionDef *def = print_config_def.get(opt_key);
+ if (def == nullptr || !def->default_value || def->default_value->is_nil())
+ continue; // the option's default is nil too, so an absent key cannot come back as a value
const auto* opt_vec = dynamic_cast(preset.config.option(opt_key));
if (opt_vec == nullptr)
continue;
diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp
index 56c3dc5af6..0876d7ebb8 100644
--- a/src/libslic3r/PrintConfig.cpp
+++ b/src/libslic3r/PrintConfig.cpp
@@ -119,12 +119,18 @@ const std::vector filament_extruder_override_keys = {
};
// Some filament override parameters are generated from filament_extruder_override_keys,
-// while filament_retract_length_nc is defined separately. Keep the generator list
-// unchanged and use this helper for behavior checks that need the full override set.
+// while filament_retract_length_nc and the ironing overrides are defined separately.
+// Keep the generator list unchanged and use this helper for behavior checks that need
+// the full override set. A nil in one of these keys means the override is disabled, so
+// child presets store and load it verbatim instead of treating nil as "inherit".
bool is_filament_extruder_override_key(const std::string &opt_key)
{
return std::find(filament_extruder_override_keys.begin(), filament_extruder_override_keys.end(), opt_key) != filament_extruder_override_keys.end() ||
- opt_key == "filament_retract_length_nc";
+ opt_key == "filament_retract_length_nc" ||
+ opt_key == "filament_ironing_flow" ||
+ opt_key == "filament_ironing_spacing" ||
+ opt_key == "filament_ironing_inset" ||
+ opt_key == "filament_ironing_speed";
}
size_t get_extruder_index(const GCodeConfig& config, unsigned int filament_id)
@@ -6453,7 +6459,7 @@ void PrintConfigDef::init_fff_params()
def->label = L("Seam gap");
def->category = L("Quality");
def->tooltip = L("In order to reduce the visibility of the seam in a closed loop extrusion, the loop is interrupted and shortened by a specified amount.\n"
- "This amount can be specified in millimeters or as a percentage of the current extruder diameter. The default value for this parameter is 10%.");
+ "This amount can be specified in millimeters or as a percentage of the nozzle diameter. The default value for this parameter is 10%.");
def->sidetext = L("mm or %");
def->min = 0;
def->mode = comAdvanced;
@@ -12272,7 +12278,8 @@ void DynamicPrintConfig::update_diff_values_to_child_config(DynamicPrintConfig&
opt_src->set(opt_target);
}
else
- opt_vec_src->set_only_diff(opt_vec_dest, variant_index, stride);
+ // An override's nil means "disabled" and is the child's value; every other nil column inherits.
+ opt_vec_src->set_only_diff(opt_vec_dest, variant_index, stride, is_filament_extruder_override_key(opt));
}
}
}
diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp
index 150e987bcb..e48f8d71f1 100644
--- a/src/libslic3r/PrintObject.cpp
+++ b/src/libslic3r/PrintObject.cpp
@@ -50,6 +50,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -76,6 +77,7 @@
#include
#include
+#include
#include
#include
@@ -1930,7 +1932,9 @@ void PrintObject::detect_surfaces_type()
bool interface_shells = ! spiral_mode && m_config.interface_shells.value;
size_t num_layers = spiral_mode ? std::min(size_t(this->printing_region(0).config().bottom_shell_layers), m_layers.size()) : m_layers.size();
- for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) {
+ // The regions of a layer do not see each other here, and a layer cut through a fine relief takes far longer than the
+ // others, so the regions run next to each other instead of one after another, each still over all layers.
+ tbb::parallel_for(size_t(0), this->num_printing_regions(), [&](size_t region_id) {
BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << region_id << " in parallel - start";
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
for (Layer *layer : m_layers)
@@ -1988,7 +1992,7 @@ void PrintObject::detect_surfaces_type()
if (upper_layer) {
ExPolygons upper_slices = interface_shells ?
diff_ex(layerm_slices_surfaces, upper_layer->m_regions[region_id]->slices.surfaces, ApplySafetyOffset::Yes) :
- diff_ex(layerm_slices_surfaces, upper_layer->lslices, ApplySafetyOffset::Yes);
+ diff_ex_by_piece(layerm_slices_surfaces, to_polygons(upper_layer->lslices), ApplySafetyOffset::Yes);
surfaces_append(top, opening_ex(upper_slices, offset), stTop);
} else {
// if no upper layer, all surfaces of this one are solid
@@ -2014,7 +2018,7 @@ void PrintObject::detect_surfaces_type()
surfaces_append(
bottom,
opening_ex(
- diff_ex(layerm_slices_surfaces, lower_layer->lslices, ApplySafetyOffset::Yes),
+ diff_ex_by_piece(layerm_slices_surfaces, to_polygons(lower_layer->lslices), ApplySafetyOffset::Yes),
offset),
surface_type_bottom_other);
// if user requested internal shells, we need to identify surfaces
@@ -2045,34 +2049,44 @@ void PrintObject::detect_surfaces_type()
// and top surfaces; let's do an intersection to discover them and consider them
// as bottom surfaces (to allow for bridge detection)
if (! top.empty() && ! bottom.empty()) {
- const auto cracks = intersection_ex(top, bottom);
+ const auto cracks = intersection_ex_by_piece(to_expolygons(top), to_polygons(bottom));
if (!cracks.empty()) {
if (lower_layer) { // Only detect small cracks for non-first layer, because first layer should always be bottom
const float small_crack_threshold = -layerm->flow(frExternalPerimeter).scaled_width() * 1.5;
+ // Only the bottom surfaces near a crack can take part: one that contains it must contain its box,
+ // and one whose box misses the grown crack is left unchanged by removing it. A layer cut through
+ // a fine relief has thousands of both, which made this loop quadratic.
for (const auto& crack : cracks) {
if (offset_ex(crack, small_crack_threshold).empty()) {
// For small cracks, if it's part of a large bottom surface, then it should be added to bottom as well
- if (std::any_of(bottom.begin(), bottom.end(), [&crack, small_crack_threshold](const Surface& s) {
+ const BoundingBox crack_bbox = get_extents(crack);
+ if (std::any_of(bottom.begin(), bottom.end(), [&crack, &crack_bbox, small_crack_threshold](const Surface& s) {
const auto& se = s.expolygon;
- return diff_ex(crack, se, ApplySafetyOffset::Yes).empty()
+ return get_extents(se).inflated(SCALED_EPSILON).contains(crack_bbox)
+ && diff_ex(crack, se, ApplySafetyOffset::Yes).empty()
&& se.area() > crack.area() * 2
&& !offset_ex(diff_ex(se, crack), small_crack_threshold).empty();
})) continue;
// Crack too small, leave it as part of the top surface, remove it from bottom surfaces
+ const ExPolygons grown_crack = offset_ex(crack, -small_crack_threshold);
+ const BoundingBox grown_bbox = get_extents(grown_crack);
Surfaces bot_tmp;
for (auto& b : bottom) {
- surfaces_append(bot_tmp, diff_ex(b.expolygon, offset_ex(crack, -small_crack_threshold)), b.surface_type);
+ if (get_extents(b.expolygon).overlap(grown_bbox))
+ surfaces_append(bot_tmp, diff_ex(b.expolygon, grown_crack), b.surface_type);
+ else
+ bot_tmp.emplace_back(std::move(b));
}
bottom = std::move(bot_tmp);
}
}
}
- Polygons top_polygons = to_polygons(std::move(top));
+ ExPolygons top_expolygons = to_expolygons(std::move(top));
top.clear();
- surfaces_append(top, diff_ex(top_polygons, bottom), stTop);
+ surfaces_append(top, diff_ex_by_piece(top_expolygons, to_polygons(bottom)), stTop);
}
}
@@ -2163,7 +2177,7 @@ void PrintObject::detect_surfaces_type()
{
Polygons topbottom = to_polygons(top);
polygons_append(topbottom, to_polygons(bottom));
- surfaces_append(surfaces_out, diff_ex(surfaces_prev_expolys, topbottom), stInternal);
+ surfaces_append(surfaces_out, diff_ex_by_piece(surfaces_prev_expolys, topbottom), stInternal);
}
surfaces_append(surfaces_out, std::move(top));
@@ -2340,29 +2354,31 @@ void PrintObject::detect_surfaces_type()
}
}
);
- // ==============================================================================================================
- // === ORCA: Interim workaround - for now the new stInternalAfterExternalBridge surfaace is re-classified ==============
- // === back to a bottom bridge. As a starting point, this improves bridging reliability as it extrudes ==========
- // === two external bridge layers. However, TODO: Implement a new surface type throughout the codebase ==========
- // ==============================================================================================================
- for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) {
- tbb::parallel_for( tbb::blocked_range(0, m_layers.size()), [this, region_id](const tbb::blocked_range &range) {
- for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) {
- Surfaces &surfs = m_layers[idx_layer]->m_regions[region_id]->slices.surfaces;
- for (Surface &s : surfs) {
- if (s.surface_type == stInternalAfterExternalBridge) {
- s.surface_type = stBottomBridge;
- }
- }
- }
- }
- );
- }
}
// ==============================================================================================================
// === ORCA: End of second external bridge layer changes =======================================================
// ==============================================================================================================
+ }); // for each this->print->region_count
+
+ // ==============================================================================================================
+ // === ORCA: Interim workaround - for now the new stInternalAfterExternalBridge surfaace is re-classified ==============
+ // === back to a bottom bridge. As a starting point, this improves bridging reliability as it extrudes ==========
+ // === two external bridge layers. However, TODO: Implement a new surface type throughout the codebase ==========
+ // ==============================================================================================================
+ // Once all the regions have their second bridge layer, and before their slices are trimmed into fill surfaces below.
+ if ((this->config().enable_extra_bridge_layer.value == eblApplyToAll) || (this->config().enable_extra_bridge_layer.value == eblExternalBridgeOnly)) {
+ tbb::parallel_for(tbb::blocked_range(0, m_layers.size()), [this](const tbb::blocked_range &range) {
+ for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer)
+ for (LayerRegion *layerm : m_layers[idx_layer]->regions())
+ for (Surface &s : layerm->slices.surfaces)
+ if (s.surface_type == stInternalAfterExternalBridge)
+ s.surface_type = stBottomBridge;
+ });
+ m_print->throw_if_canceled();
+ }
+
+ tbb::parallel_for(size_t(0), this->num_printing_regions(), [&](size_t region_id) {
BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << region_id << " - clipping in parallel - start";
// Fill in layerm->fill_surfaces by trimming the layerm->slices by the cummulative layerm->fill_surfaces.
tbb::parallel_for(
@@ -2379,7 +2395,7 @@ void PrintObject::detect_surfaces_type()
});
m_print->throw_if_canceled();
BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << region_id << " - clipping in parallel - end";
- } // for each this->print->region_count
+ });
// Mark the object to have the region slices classified (typed, which also means they are split based on whether they are supported, bridging, top layers etc.)
m_typed_slices = true;
@@ -2446,8 +2462,10 @@ void PrintObject::process_external_surfaces()
BOOST_LOG_TRIVIAL(debug) << "Collecting surfaces covered with extrusions in parallel - end";
}
- for (size_t region_id = 0; region_id < this->num_printing_regions(); ++region_id) {
- BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces for region " << region_id << " in parallel - start";
+ BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces in parallel - start";
+ // The regions of a layer do not see each other here, and a layer cut through a fine relief takes far longer than the
+ // others, so the regions run next to each other instead of one after another, each still over all layers.
+ tbb::parallel_for(size_t(0), this->num_printing_regions(), [this, &surfaces_covered](size_t region_id) {
tbb::parallel_for(
tbb::blocked_range(0, m_layers.size()),
[this, &surfaces_covered, region_id](const tbb::blocked_range& range) {
@@ -2462,9 +2480,9 @@ void PrintObject::process_external_surfaces()
}
}
);
- m_print->throw_if_canceled();
- BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces for region " << region_id << " in parallel - end";
- }
+ });
+ m_print->throw_if_canceled();
+ BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces in parallel - end";
}
void PrintObject::discover_vertical_shells()
@@ -2503,10 +2521,10 @@ void PrintObject::discover_vertical_shells()
// The "ensure vertical wall thickness" feature is not applicable to any of the regions. Quit.
return;
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells in parallel - start : cache top / bottom";
- //FIXME Improve the heuristics for a grain size.
- size_t grain_size = std::max(num_layers / 16, size_t(1));
+ // One layer per task: on a layer cut through a fine relief the unions below take far longer than elsewhere, and a
+ // few such layers next to each other must not end up in one task.
tbb::parallel_for(
- tbb::blocked_range(0, num_layers, grain_size),
+ tbb::blocked_range(0, num_layers, 1),
[this, &cache_top_botom_regions](const tbb::blocked_range& range) {
const std::initializer_list surfaces_bottom { stBottom, stBottomBridge };
const size_t num_regions = this->num_printing_regions();
@@ -2514,67 +2532,198 @@ void PrintObject::discover_vertical_shells()
m_print->throw_if_canceled();
const Layer &layer = *m_layers[idx_layer];
DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[idx_layer];
- // Simulate single set of perimeters over all merged regions.
- float perimeter_offset = 0.f;
- float perimeter_min_spacing = FLT_MAX;
+ const auto top_bottom_expansion = [&layer](size_t region_id) {
+ return float(layer.m_regions[region_id]->flow(frSolidInfill).scaled_spacing()) * top_bottom_expansion_coeff;
+ };
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
static size_t debug_idx = 0;
++ debug_idx;
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
- for (size_t region_id = 0; region_id < num_regions; ++ region_id) {
- LayerRegion &layerm = *layer.m_regions[region_id];
- float top_bottom_expansion = float(layerm.flow(frSolidInfill).scaled_spacing()) * top_bottom_expansion_coeff;
- // Top surfaces.
- append(cache.top_surfaces, offset(layerm.slices.filter_by_type(stTop), top_bottom_expansion));
-// append(cache.top_surfaces, offset(layerm.fill_surfaces.filter_by_type(stTop), top_bottom_expansion));
- // Bottom surfaces.
- append(cache.bottom_surfaces, offset(layerm.slices.filter_by_types(surfaces_bottom), top_bottom_expansion));
-// append(cache.bottom_surfaces, offset(layerm.fill_surfaces.filter_by_types(surfaces_bottom), top_bottom_expansion));
- // Calculate the maximum perimeter offset as if the slice was extruded with a single extruder only.
- // First find the maxium number of perimeters per region slice.
- unsigned int perimeters = 0;
- for (Surface &s : layerm.slices.surfaces)
- perimeters = std::max(perimeters, s.extra_perimeters);
- perimeters += layerm.region().config().wall_loops.value;
- // Then calculate the infill offset.
- if (perimeters > 0) {
- Flow extflow = layerm.flow(frExternalPerimeter);
- Flow flow = layerm.flow(frPerimeter);
- perimeter_offset = std::max(perimeter_offset,
- 0.5f * float(extflow.scaled_width() + extflow.scaled_spacing()) + (float(perimeters) - 1.f) * flow.scaled_spacing());
- perimeter_min_spacing = std::min(perimeter_min_spacing, float(std::min(extflow.scaled_spacing(), flow.scaled_spacing())));
- }
- polygons_append(cache.holes, to_polygons(layerm.fill_expolygons));
- }
- // Save some computing time by reducing the number of polygons.
- cache.top_surfaces = union_(cache.top_surfaces);
- cache.bottom_surfaces = union_(cache.bottom_surfaces);
- // For a multi-material print, simulate perimeter / infill split as if only a single extruder has been used for the whole print.
- if (perimeter_offset > 0.) {
- // The layer.lslices are forced to merge by expanding them first.
- polygons_append(cache.holes, offset2(layer.lslices, 0.3f * perimeter_min_spacing, - perimeter_offset - 0.3f * perimeter_min_spacing));
+ // The top surfaces, the bottom surfaces and the holes are independent of each other.
+ tbb::parallel_invoke(
+ [&]() {
+ for (size_t region_id = 0; region_id < num_regions; ++ region_id)
+ append(cache.top_surfaces, offset(layer.m_regions[region_id]->slices.filter_by_type(stTop), top_bottom_expansion(region_id)));
+// append(cache.top_surfaces, offset(layerm.fill_surfaces.filter_by_type(stTop), top_bottom_expansion));
+ // Save some computing time by reducing the number of polygons.
+ cache.top_surfaces = union_(cache.top_surfaces);
+ },
+ [&]() {
+ for (size_t region_id = 0; region_id < num_regions; ++ region_id)
+ append(cache.bottom_surfaces, offset(layer.m_regions[region_id]->slices.filter_by_types(surfaces_bottom), top_bottom_expansion(region_id)));
+// append(cache.bottom_surfaces, offset(layerm.fill_surfaces.filter_by_types(surfaces_bottom), top_bottom_expansion));
+ cache.bottom_surfaces = union_(cache.bottom_surfaces);
+ },
+ [&]() {
+ // Simulate single set of perimeters over all merged regions.
+ float perimeter_offset = 0.f;
+ float perimeter_min_spacing = FLT_MAX;
+ for (size_t region_id = 0; region_id < num_regions; ++ region_id) {
+ const LayerRegion &layerm = *layer.m_regions[region_id];
+ // Calculate the maximum perimeter offset as if the slice was extruded with a single extruder only.
+ // First find the maxium number of perimeters per region slice.
+ unsigned int perimeters = 0;
+ for (const Surface &s : layerm.slices.surfaces)
+ perimeters = std::max(perimeters, s.extra_perimeters);
+ perimeters += layerm.region().config().wall_loops.value;
+ // Then calculate the infill offset.
+ if (perimeters > 0) {
+ Flow extflow = layerm.flow(frExternalPerimeter);
+ Flow flow = layerm.flow(frPerimeter);
+ perimeter_offset = std::max(perimeter_offset,
+ 0.5f * float(extflow.scaled_width() + extflow.scaled_spacing()) + (float(perimeters) - 1.f) * flow.scaled_spacing());
+ perimeter_min_spacing = std::min(perimeter_min_spacing, float(std::min(extflow.scaled_spacing(), flow.scaled_spacing())));
+ }
+ polygons_append(cache.holes, to_polygons(layerm.fill_expolygons));
+ }
+ // For a multi-material print, simulate perimeter / infill split as if only a single extruder has been used for the whole print.
+ if (perimeter_offset > 0.) {
+ // The layer.lslices are forced to merge by expanding them first.
+ polygons_append(cache.holes, offset2(layer.lslices, 0.3f * perimeter_min_spacing, - perimeter_offset - 0.3f * perimeter_min_spacing));
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
- {
- Slic3r::SVG svg(debug_out_path("discover_vertical_shells-extra-holes-%d.svg", debug_idx), get_extents(layer.lslices));
- svg.draw(layer.lslices, "blue");
- svg.draw(union_ex(cache.holes), "red");
- svg.draw_outline(union_ex(cache.holes), "black", "blue", scale_(0.05));
- svg.Close();
- }
+ {
+ Slic3r::SVG svg(debug_out_path("discover_vertical_shells-extra-holes-%d.svg", debug_idx), get_extents(layer.lslices));
+ svg.draw(layer.lslices, "blue");
+ svg.draw(union_ex(cache.holes), "red");
+ svg.draw_outline(union_ex(cache.holes), "black", "blue", scale_(0.05));
+ svg.Close();
+ }
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
- }
- cache.holes = union_(cache.holes);
+ }
+ cache.holes = union_(cache.holes);
+ });
}
});
m_print->throw_if_canceled();
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells in parallel - end : cache top / bottom";
}
- for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) {
+ // With one top/bottom cache for all regions, the shell and hole accumulation in the loop below depends on nothing
+ // region-specific but the shell settings and the external perimeter spacing, so a region sharing them with an earlier
+ // one reuses its result instead of repeating it: that accumulation is a union over several layers of top/bottom
+ // surfaces, and a multi-material print has a region per filament.
+ using AccumulationKey = std::array;
+ struct ShellAccumulation
+ {
+ AccumulationKey key;
+ Polygons shell;
+ Polygons holes;
+ };
+ const auto accumulation_key = [](const PrintRegionConfig ®ion_config, const LayerRegion *layerm) {
+ return AccumulationKey{ double(region_config.top_shell_layers.value), region_config.top_shell_thickness.value,
+ double(region_config.bottom_shell_layers.value), region_config.bottom_shell_thickness.value,
+ double(layerm->flow(frExternalPerimeter).scaled_spacing()) };
+ };
+ const auto accumulate_shell = [this, &cache_top_botom_regions](size_t idx_layer, const PrintRegionConfig ®ion_config,
+ const LayerRegion *layerm, Polygons &shell, Polygons &holes) {
+ const Layer *layer = m_layers[idx_layer];
+ polygons_append(holes, cache_top_botom_regions[idx_layer].holes);
+ auto combine_holes = [&holes](const Polygons &holes2) {
+ if (holes.empty() || holes2.empty())
+ holes.clear();
+ else
+ holes = intersection(holes, holes2);
+ };
+ auto combine_shells = [&shell](const Polygons &shells2) {
+ if (shell.empty())
+ shell = std::move(shells2);
+ else if (! shells2.empty()) {
+ polygons_append(shell, shells2);
+ // Running the union_ using the Clipper library piece by piece is cheaper
+ // than running the union_ all at once.
+ shell = union_(shell);
+ }
+ };
+ static constexpr const bool one_more_layer_below_top_bottom_surfaces = false;
+ if (int n_top_layers = region_config.top_shell_layers.value; n_top_layers > 0) {
+ // Gather top regions projected to this layer.
+ coordf_t print_z = layer->print_z;
+ int i = int(idx_layer) + 1;
+ int itop = int(idx_layer) + n_top_layers;
+ bool at_least_one_top_projected = false;
+ for (; i < int(cache_top_botom_regions.size()) &&
+ (i < itop || m_layers[i]->print_z - print_z < region_config.top_shell_thickness - EPSILON);
+ ++ i) {
+ at_least_one_top_projected = true;
+ const DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[i];
+ combine_holes(cache.holes);
+ combine_shells(cache.top_surfaces);
+ }
+ if (!at_least_one_top_projected && i < int(cache_top_botom_regions.size())) {
+ // Lets consider this a special case - with only 1 top solid and minimal shell thickness settings, the
+ // boundaries of solid layers are not anchored over/under perimeters, so lets fix it by adding at least one
+ // perimeter width of area
+ Polygons anchor_area = intersection(expand(cache_top_botom_regions[idx_layer].top_surfaces,
+ layerm->flow(frExternalPerimeter).scaled_spacing()),
+ to_polygons(m_layers[i]->lslices));
+ combine_shells(anchor_area);
+ }
+
+ if (one_more_layer_below_top_bottom_surfaces)
+ if (i < int(cache_top_botom_regions.size()) &&
+ (i <= itop || m_layers[i]->bottom_z() - print_z < region_config.top_shell_thickness - EPSILON))
+ combine_holes(cache_top_botom_regions[i].holes);
+ }
+ if (int n_bottom_layers = region_config.bottom_shell_layers.value; n_bottom_layers > 0) {
+ // Gather bottom regions projected to this layer.
+ coordf_t bottom_z = layer->bottom_z();
+ int i = int(idx_layer) - 1;
+ int ibottom = int(idx_layer) - n_bottom_layers;
+ bool at_least_one_bottom_projected = false;
+ for (; i >= 0 &&
+ (i > ibottom || bottom_z - m_layers[i]->bottom_z() < region_config.bottom_shell_thickness - EPSILON);
+ -- i) {
+ at_least_one_bottom_projected = true;
+ const DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[i];
+ combine_holes(cache.holes);
+ combine_shells(cache.bottom_surfaces);
+ }
+
+ if (!at_least_one_bottom_projected && i >= 0) {
+ Polygons anchor_area = intersection(expand(cache_top_botom_regions[idx_layer].bottom_surfaces,
+ layerm->flow(frExternalPerimeter).scaled_spacing()),
+ to_polygons(m_layers[i]->lslices));
+ combine_shells(anchor_area);
+ }
+
+ if (one_more_layer_below_top_bottom_surfaces)
+ if (i >= 0 &&
+ (i > ibottom || bottom_z - m_layers[i]->print_z < region_config.bottom_shell_thickness - EPSILON))
+ combine_holes(cache_top_botom_regions[i].holes);
+ }
+ };
+ std::vector> shell_accumulations(top_bottom_surfaces_all_regions ? num_layers : 0);
+ if (! shell_accumulations.empty()) {
+ // Every (layer, key) pair is accumulated once, before the regions, so that nothing in the loop below is shared
+ // between them and they can run next to each other.
+ std::vector> todo; // layer, its slot, a region holding the key
+ for (size_t idx_layer = 0; idx_layer < num_layers; ++ idx_layer) {
+ std::vector &accumulations = shell_accumulations[idx_layer];
+ for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) {
+ if (this->printing_region(region_id).config().ensure_vertical_shell_thickness.value != evstAll)
+ continue;
+ const LayerRegion *layerm = m_layers[idx_layer]->m_regions[region_id];
+ const AccumulationKey key = accumulation_key(layerm->region().config(), layerm);
+ if (std::none_of(accumulations.begin(), accumulations.end(), [&key](const ShellAccumulation &a) { return a.key == key; })) {
+ todo.push_back({ idx_layer, accumulations.size(), region_id });
+ accumulations.push_back({ key, {}, {} });
+ }
+ }
+ }
+ tbb::parallel_for(size_t(0), todo.size(), [this, &todo, &shell_accumulations, &accumulate_shell](size_t i) {
+ m_print->throw_if_canceled();
+ const LayerRegion *layerm = m_layers[todo[i][0]]->m_regions[todo[i][2]];
+ ShellAccumulation &out = shell_accumulations[todo[i][0]][todo[i][1]];
+ accumulate_shell(todo[i][0], layerm->region().config(), layerm, out.shell, out.holes);
+ });
+ m_print->throw_if_canceled();
+ }
+
+ const auto process_region = [&](size_t region_id) {
const PrintRegion ®ion = this->printing_region(region_id);
if (region.config().ensure_vertical_shell_thickness.value != evstAll )
// This region will be handled by discover_horizontal_shells().
- continue;
+ return;
//FIXME Improve the heuristics for a grain size.
size_t grain_size = std::max(num_layers / 16, size_t(1));
@@ -2614,7 +2763,7 @@ void PrintObject::discover_vertical_shells()
grain_size = 1;
tbb::parallel_for(
tbb::blocked_range