Compare commits

..
Author SHA1 Message Date
Ian Bassi 7b0e2f3ce5 Keep the G-code identical to main on Clipper2
Since main moved to Clipper2, parts of this branch no longer gave the same
G-code as main:

- bridge_over_infill dropped expand(limiting_area, 0.3 * flow.spacing()) as
  a no-op. The offset is below one unit, but Clipper2 still unites its
  result, which splits and merges touching polygons and so changes the
  anchor lines. Running it on the polygons next to the bridge gives main's
  anchors without the whole-layer pass.
- tsp_remove_crossings stopped at the first repeated ordering, where main
  runs on to its pn * pn cap. The loop is periodic from that point, so it
  now takes only the steps to the ordering main stops on.
- With a single tile, the tiled booleans now make the plain call instead of
  cutting the clip to the tile first.

The tiled boolean test compared rings exactly. With the safety offset a tile
unites only the clip polygons near it, and Clipper2 can then round a
crossing 1 unit differently, so that case allows 1 unit.

Comments that named ClipperLib now say Clipper, and
docs/HLSD/polygon-clipping.md describes the tiled booleans.

G-code of the five handy models in four configurations and of a baked
texture relief is byte-identical to main. Colour-painted models still
differ: segmenting each island on its own splits a colour's region into
different pieces than one diagram over the layer, which on one model also
changes the first layer's tool order.
2026-10-02 20:26:59 -03:00
ExPikaPaka d80c69341c Say what the code does, not what it replaced
The timings and the runs that never finished belong in the commit
messages, where they can be read against the change; a reader of the
code cannot check them. Kept the cost that still explains the design.

MultiPoint also spells out the consequence: a moved-from Polygon or
Polyline is now really empty where it used to silently keep its points.

The two wall spacing comments the parallel loop reindented are plain
ASCII now, so the whole file is.
2026-10-02 18:13:46 -03:00
ExPikaPaka 4a63a7d916 Compare the tiled booleans polygon by polygon
Area alone would pass on a result whose pieces were merged across tiles
or which kept the cut edges of the clip. The rings are compared after
rotating each to its lowest point and sorting, so only the ordering is
free. The fixture now also asserts it really is split into more than one
tile, which is the path being tested.
2026-10-02 18:13:46 -03:00
ExPikaPaka de1dfd0611 Hand the island's walls over instead of copying them
append(const ExtrusionEntity &) clones; the collection each island
produced was deep-copied into the layer's loops and then thrown away.
The no-overlap areas are moved as well.
2026-10-02 18:13:46 -03:00
ExPikaPaka 11a5971cef Include what the new code uses
The Windows build stopped on test_kdtree.cpp: it calls std::iota without
including <numeric>, which libstdc++ happens to pull in anyway. Added
there, and the same for <limits> and <algorithm>/<cmath> where the
recent changes rely on them being included by something else.
2026-10-02 18:13:45 -03:00
ExPikaPaka 82f37ddb3a Fix the Windows build: near and far are macros there
bridge_over_infill's helper for splitting polygons by proximity named
its locals near and far. The Windows headers define both as macros that
expand to nothing, so "Polygons near;" declared nothing and the uses of
it did not compile. Renamed; no behaviour change.
2026-10-02 18:13:37 -03:00
ExPikaPaka fb03d1a1cb Visit seam candidates as the search finds them
Collecting every candidate within the radius into a vector cost more
than the search itself. Same order, so the seams are unchanged;
align_seam_points ~19.6 s at 0.1 mm / 2000k, was ~21.
2026-10-02 18:13:37 -03:00
ExPikaPaka 2b4bdead73 Run a layer's regions in parallel where they are independent
detect_surfaces_type, process_external_surfaces and the vertical shells
each waited on their own heaviest layer in turn. The LOTR map plate
slices in ~10.5 min at 0.1 mm / 2000k, was ~11.5; ~87 s at normal
settings, was ~97.
2026-10-02 18:13:37 -03:00
ExPikaPaka b1f0d6c6f6 Move polygons instead of copying them on move
MultiPoint had no rvalue constructor, so the derived move constructors
bound to the const reference and copied; append reserved exactly, so
collecting pieces one by one was quadratic. Colour segmentation ~3 s at
0.1 mm / 2000k, was ~40, and ordinary prints gain too.
2026-10-02 18:13:37 -03:00
ExPikaPaka 14751a8b06 Project painted faces onto the shell layers per tile
Only the slices within the deepest shell offset decide the result, so
the work is done per tile of the face. Top and bottom segmentation
~130 s at 0.1 mm / 2000k, was ~180.
2026-10-02 18:13:36 -03:00
ExPikaPaka 9a86d79038 Tile the booleans on layers of many pieces
ClipperLib slows down with the number of edges on a scan line, and a
layer cut through a fine relief has tens of thousands of pieces.
detect_surfaces_type ~50 s at 0.1 mm / 2000k, was ~145.
2026-10-02 18:13:05 -03:00
ExPikaPaka d905f1a39b Merge colour and top/bottom regions per island
The merge took anything from 3 to 38 minutes at 0.1 mm / 2000k, now
~2.5. Every region is grouped with the islands it overlaps, so the
result is the same.
2026-10-02 18:12:41 -03:00
ExPikaPaka 84ec518f26 Slice fine texture relief without stalling
A colour texture baked at 0.1 mm / 2000k made the top layers thousands
of islands and slicing never finished. Colour segmentation runs per
island, the merge subtracts piece by piece, the support check tests only
nearby islands, and the travel ordering finds crossings through a grid.
2026-10-02 18:12:41 -03:00
ExPikaPaka 1fc153308f Generate walls and split solid infill in parallel
Same output, ~2.2 min for the LOTR map plate, was ~2.6.
2026-10-02 18:12:41 -03:00
ExPikaPaka 3167c3665a Run colour segmentation and vertical shells in parallel
Same output, ~2.6 min for the LOTR map plate, was ~2.9.
2026-10-02 18:12:40 -03:00
ExPikaPaka 6d34d83e78 Faster slicing of colour-painted layers
A layer split into ~1000 colour fragments (a colour texture baked over a
large top face) made several per-fragment loops redo whole-layer ClipperLib
work, so slicing took ~33 min; it now takes ~3 min with the same output.

- make_fills: clip the layer's no-overlap area to each expolygon's box
  before intersecting
- discover_vertical_shells: small-piece filter compares only against the
  nearby part of the layer
- bridge_over_infill: whole-layer union/diff/intersections restricted to the
  candidate's neighbourhood; fill boundary expanded once per spacing; anchor
  tree built only from lines crossing the scan range; bbox pre-check in the
  collision test; limiting outline taken directly instead of through
  expand(..., 0.3 * flow.spacing()), which offsets by 0.135 scaled units
  (flow.spacing() is in mm) and only cost a whole-layer pass per candidate
2026-10-02 18:11:17 -03:00
39 changed files with 1561 additions and 956 deletions
+17
View File
@@ -62,6 +62,23 @@ Clipper2 behaves differently by default, the wrapper adjusts it.
clip do not leave slivers. clip do not leave slivers.
- Open polylines are clipped with the non-zero rule and keep their direction. - 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 ### Offsets
- Before offsetting, input vertices closer than - Before offsetting, input vertices closer than
+69
View File
@@ -1,9 +1,12 @@
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <cmath>
#include <limits> #include <limits>
#include <numeric> #include <numeric>
#include <unordered_map> #include <unordered_map>
#include <tbb/parallel_for.h>
#include "ClipperUtils.hpp" #include "ClipperUtils.hpp"
#include "Geometry.hpp" #include "Geometry.hpp"
#include "ShortestPath.hpp" #include "ShortestPath.hpp"
@@ -785,6 +788,72 @@ Slic3r::ExPolygons intersection_ex(const Slic3r::Surfaces &subject, const Slic3r
{ return _clipper_ex(ctIntersection, ClipperUtils::SurfacesProvider(subject), ClipperUtils::SurfacesProvider(clip), do_safety_offset); } { 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) 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); } { return _clipper_ex(ctIntersection, ClipperUtils::SurfacesPtrProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); }
namespace ClipperUtils {
std::vector<ExPolygonsTile> tile_expolygons(const ExPolygons &expolygons, size_t per_tile)
{
BoundingBox extent;
std::vector<BoundingBox> 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<size_t>(per_tile, 1)))), 1, 32);
const Point size = extent.size();
const coord_t tile_w = std::max<coord_t>(1, size.x() / tiles + 1), tile_h = std::max<coord_t>(1, size.y() / tiles + 1);
std::vector<ExPolygonsTile> 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;
}
}
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<ClipperUtils::ExPolygonsTile> 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<BoundingBox> clip_bboxes;
clip_bboxes.reserve(clip.size());
for (const Polygon &polygon : clip)
clip_bboxes.emplace_back(get_extents(polygon));
std::vector<Slic3r::ExPolygons> 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). // 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) Slic3r::ExPolygons union_ex(const Slic3r::Polygons &subject, PolyFillType fill_type)
{ return _clipper_ex(ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ApplySafetyOffset::No, fill_type); } { return _clipper_ex(ctUnion, ClipperUtils::PolygonsProvider(subject), ClipperUtils::EmptyPathsProvider(), ApplySafetyOffset::No, fill_type); }
+15
View File
@@ -2,6 +2,7 @@
#define slic3r_ClipperUtils_hpp_ #define slic3r_ClipperUtils_hpp_
#include "libslic3r.h" #include "libslic3r.h"
#include "BoundingBox.hpp"
#include "ExPolygon.hpp" #include "ExPolygon.hpp"
#include "Polygon.hpp" #include "Polygon.hpp"
#include "Surface.hpp" #include "Surface.hpp"
@@ -330,6 +331,15 @@ 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 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); [[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<size_t> members;
};
[[nodiscard]] std::vector<ExPolygonsTile> tile_expolygons(const ExPolygons &expolygons, size_t per_tile);
} }
// offset Polygons // offset Polygons
@@ -525,6 +535,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::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::Surfaces &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::ExPolygons intersection_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::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::Polylines &subject, const Slic3r::Polygon &clip);
Slic3r::Polylines intersection_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygon &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); Slic3r::Polylines intersection_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygon &clip);
+31 -12
View File
@@ -9,6 +9,8 @@
#include "../PrintConfig.hpp" #include "../PrintConfig.hpp"
#include "../Surface.hpp" #include "../Surface.hpp"
#include <tbb/parallel_for.h>
#include "AABBTreeLines.hpp" #include "AABBTreeLines.hpp"
#include "ExtrusionEntity.hpp" #include "ExtrusionEntity.hpp"
#include "Fill.hpp" #include "Fill.hpp"
@@ -630,24 +632,28 @@ void split_solid_surface(size_t layer_id, const SurfaceFill &fill, ExPolygons &n
if (!line_based_pattern) { if (!line_based_pattern) {
const coord_t scaled_spacing = scaled<coord_t>(fill.params.spacing); const coord_t scaled_spacing = scaled<coord_t>(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<std::pair<ExPolygons, ExPolygons>> 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); Polygons filled_area = to_polygons(expolygon);
// "Core" area: open (erode+dilate) to drop thin features, then clamp back to the original polygon. // "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)); Polygons inner_area = intersection(filled_area, opening(filled_area, scaled_spacing, scaled_spacing));
if (inner_area.empty()) { if (inner_area.empty()) {
narrow_infill.emplace_back(expolygon); split_parts[idx].second.emplace_back(expolygon);
continue; return;
} }
ExPolygons inner_ex = union_ex(inner_area); ExPolygons inner_ex = union_ex(inner_area);
ExPolygons expolys{expolygon}; ExPolygons expolys{expolygon};
ExPolygons narrow_ex = diff_ex(expolys, inner_ex); split_parts[idx].second = diff_ex(expolys, inner_ex); // narrow infill area
ExPolygons normal_ex = intersection_ex(expolys, inner_ex); split_parts[idx].first = intersection_ex(expolys, inner_ex); // normal infill area
});
append(normal_infill, normal_ex); // normal infill area for (auto &[normal_ex, narrow_ex] : split_parts) {
append(narrow_infill, narrow_ex); // narrow infill area append(normal_infill, std::move(normal_ex));
append(narrow_infill, std::move(narrow_ex));
} }
return; return;
@@ -669,7 +675,10 @@ void split_solid_surface(size_t layer_id, const SurfaceFill &fill, ExPolygons &n
} }
const double aligning_angle = -base_angle + PI; 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<Polygons> 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 filled_area = to_polygons(expolygon);
polygons_rotate(filled_area, aligning_angle); polygons_rotate(filled_area, aligning_angle);
BoundingBox bb = get_extents(filled_area); BoundingBox bb = get_extents(filled_area);
@@ -800,8 +809,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); polygons_rotate(normal_fill_areas, -aligning_angle);
@@ -1409,7 +1420,15 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
// Orca: Reuse the body origin used for bridge anchoring, resetting it for each surface. // Orca: Reuse the body origin used for bridge anchoring, resetting it for each surface.
f->set_bounding_box(infill_bounding_box(*this, surface_fill, expoly, bbox)); f->set_bounding_box(infill_bounding_box(*this, surface_fill, expoly, bbox));
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) { if (params.symmetric_infill_y_axis) {
params.symmetric_y_axis = f->extended_object_bounding_box().center().x(); params.symmetric_y_axis = f->extended_object_bounding_box().center().x();
expoly.symmetric_y(params.symmetric_y_axis); expoly.symmetric_y(params.symmetric_y_axis);
+25 -33
View File
@@ -4334,9 +4334,9 @@ size_t GCode::get_extruder_id(unsigned int filament_id) const
size_t GCode::get_filament_config_index(int filament_id) const size_t GCode::get_filament_config_index(int filament_id) const
{ {
if (m_print) if (m_print) {
return m_filament_index_cache.get(filament_id, m_cur_layer_idx, m_print->config_index_generation(), return m_print->get_filament_config_indx(filament_id, m_cur_layer_idx);
[&] { return m_print->get_filament_config_indx(filament_id, m_cur_layer_idx); }); }
// Orca: without a Print the filament-indexed arrays are unexpanded, so the // Orca: without a Print the filament-indexed arrays are unexpanded, so the
// filament id itself is the only meaningful column. // filament id itself is the only meaningful column.
return filament_id; return filament_id;
@@ -4350,9 +4350,9 @@ size_t GCode::get_filament_config_index(int filament_id, size_t layer_id) const
size_t GCode::get_nozzle_config_index(int filament_id) const size_t GCode::get_nozzle_config_index(int filament_id) const
{ {
if (m_print) if (m_print) {
return m_nozzle_index_cache.get(filament_id, m_cur_layer_idx, m_print->config_index_generation(), return m_print->get_nozzle_config_index(filament_id, m_cur_layer_idx);
[&] { return m_print->get_nozzle_config_index(filament_id, m_cur_layer_idx); }); }
// Orca: same reasoning; degenerate to the filament's extruder column. // Orca: same reasoning; degenerate to the filament's extruder column.
return get_extruder_id(filament_id); return get_extruder_id(filament_id);
} }
@@ -8059,36 +8059,28 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
unsigned int acceleration_i = 0; unsigned int acceleration_i = 0;
double jerk = 0; double jerk = 0;
// adjust acceleration // adjust acceleration
const size_t nozzle = get_nozzle_config_index(m_writer.filament()->id()); if (NOZZLE_CONFIG(default_acceleration) > 0) {
if (m_config.default_acceleration.get_at(nozzle) > 0) {
const ExtrusionRole role = path.role();
const double bridge_acceleration = is_bridge(role) ?
m_config.bridge_acceleration.get_at(nozzle).get_abs_value(m_config.outer_wall_acceleration.get_at(nozzle)) : 0.;
const double sparse_infill_acceleration = role == erInternalInfill ?
m_config.sparse_infill_acceleration.get_at(nozzle).get_abs_value(m_config.default_acceleration.get_at(nozzle)) : 0.;
const double internal_solid_infill_acceleration = role == erSolidInfill ?
m_config.internal_solid_infill_acceleration.get_at(nozzle).get_abs_value(m_config.default_acceleration.get_at(nozzle)) : 0.;
double acceleration; double acceleration;
if (this->on_first_layer() && m_config.initial_layer_acceleration.get_at(nozzle) > 0) { if (this->on_first_layer() && NOZZLE_CONFIG(initial_layer_acceleration) > 0) {
acceleration = m_config.initial_layer_acceleration.get_at(nozzle); acceleration = NOZZLE_CONFIG(initial_layer_acceleration);
#if 0 #if 0
} else if (this->object_layer_over_raft() && m_config.first_layer_acceleration_over_raft.value > 0) { } else if (this->object_layer_over_raft() && m_config.first_layer_acceleration_over_raft.value > 0) {
acceleration = m_config.first_layer_acceleration_over_raft.value; acceleration = m_config.first_layer_acceleration_over_raft.value;
#endif #endif
} else if (bridge_acceleration > 0) { } else if (m_config.get_abs_value_at("bridge_acceleration", get_nozzle_config_index(m_writer.filament()->id())) > 0 && is_bridge(path.role())) {
acceleration = bridge_acceleration; acceleration = m_config.get_abs_value_at("bridge_acceleration", get_nozzle_config_index(m_writer.filament()->id()));
} else if (sparse_infill_acceleration > 0) { } else if (m_config.get_abs_value_at("sparse_infill_acceleration", get_nozzle_config_index(m_writer.filament()->id())) > 0 && (path.role() == erInternalInfill)) {
acceleration = sparse_infill_acceleration; acceleration = m_config.get_abs_value_at("sparse_infill_acceleration", get_nozzle_config_index(m_writer.filament()->id()));
} else if (internal_solid_infill_acceleration > 0) { } else if (m_config.get_abs_value_at("internal_solid_infill_acceleration", get_nozzle_config_index(m_writer.filament()->id())) > 0 && (path.role() == erSolidInfill)) {
acceleration = internal_solid_infill_acceleration; acceleration = m_config.get_abs_value_at("internal_solid_infill_acceleration", get_nozzle_config_index(m_writer.filament()->id()));
} else if (m_config.outer_wall_acceleration.get_at(nozzle) > 0 && is_external_perimeter(role)) { } else if (NOZZLE_CONFIG(outer_wall_acceleration) > 0 && is_external_perimeter(path.role())) {
acceleration = m_config.outer_wall_acceleration.get_at(nozzle); acceleration = NOZZLE_CONFIG(outer_wall_acceleration);
} else if (m_config.inner_wall_acceleration.get_at(nozzle) > 0 && is_internal_perimeter(role)) { } else if (NOZZLE_CONFIG(inner_wall_acceleration) > 0 && is_internal_perimeter(path.role())) {
acceleration = m_config.inner_wall_acceleration.get_at(nozzle); acceleration = NOZZLE_CONFIG(inner_wall_acceleration);
} else if (m_config.top_surface_acceleration.get_at(nozzle) > 0 && is_top_surface(role)) { } else if (NOZZLE_CONFIG(top_surface_acceleration) > 0 && is_top_surface(path.role())) {
acceleration = m_config.top_surface_acceleration.get_at(nozzle); acceleration = NOZZLE_CONFIG(top_surface_acceleration);
} else { } else {
acceleration = m_config.default_acceleration.get_at(nozzle); acceleration = NOZZLE_CONFIG(default_acceleration);
} }
acceleration_i = (unsigned int)floor(acceleration + 0.5); acceleration_i = (unsigned int)floor(acceleration + 0.5);
} }
@@ -8189,7 +8181,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
speed = std::min(speed, m_config.scarf_joint_speed.get_abs_value(speed)); speed = std::min(speed, m_config.scarf_joint_speed.get_abs_value(speed));
} }
} else if(path.role() == erInternalBridgeInfill) { } else if(path.role() == erInternalBridgeInfill) {
speed = m_config.internal_bridge_speed.get_at(nozzle).get_abs_value(m_config.bridge_speed.get_at(nozzle)); speed = m_config.get_abs_value_at("internal_bridge_speed", get_nozzle_config_index(m_writer.filament()->id()));
} else if (path.role() == erOverhangPerimeter || path.role() == erSupportTransition || path.role() == erBridgeInfill) { } else if (path.role() == erOverhangPerimeter || path.role() == erSupportTransition || path.role() == erBridgeInfill) {
speed = NOZZLE_CONFIG(bridge_speed); speed = NOZZLE_CONFIG(bridge_speed);
} else if (path.role() == erInternalInfill) { } else if (path.role() == erInternalInfill) {
@@ -8201,7 +8193,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
} else if (path.role() == erIroning) { } else if (path.role() == erIroning) {
const size_t filament_idx = get_filament_config_index(m_writer.filament()->id()); const size_t filament_idx = get_filament_config_index(m_writer.filament()->id());
speed = m_config.filament_ironing_speed.is_nil(filament_idx) speed = m_config.filament_ironing_speed.is_nil(filament_idx)
? m_config.ironing_speed.value ? m_config.get_abs_value("ironing_speed")
: m_config.filament_ironing_speed.get_at(filament_idx); : m_config.filament_ironing_speed.get_at(filament_idx);
} else if (path.role() == erBottomSurface) { } else if (path.role() == erBottomSurface) {
speed = NOZZLE_CONFIG(initial_layer_infill_speed); speed = NOZZLE_CONFIG(initial_layer_infill_speed);
@@ -8262,7 +8254,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
} }
// Override skirt speed if set // Override skirt speed if set
if (path.role() == erSkirt) { if (path.role() == erSkirt) {
const double skirt_speed = m_config.skirt_speed.value; const double skirt_speed = m_config.get_abs_value("skirt_speed");
if (skirt_speed > 0.0) if (skirt_speed > 0.0)
speed = skirt_speed; speed = skirt_speed;
} }
-20
View File
@@ -301,24 +301,6 @@ public:
size_t get_filament_config_index(int filament_id, size_t layer_id) const; size_t get_filament_config_index(int filament_id, size_t layer_id) const;
size_t get_nozzle_config_index(int filament_id) const; size_t get_nozzle_config_index(int filament_id) const;
// Holds the last slot a resolver returned without locking, so only the G-code generator may
// call the resolvers.
struct ConfigIndexCache
{
bool valid{false};
int filament_id{0};
size_t layer_idx{0};
size_t generation{0};
size_t index{0};
template<class Lookup> size_t get(int filament, size_t layer, size_t gen, Lookup &&lookup)
{
if (!valid || filament_id != filament || layer_idx != layer || generation != gen)
*this = {true, filament, layer, gen, size_t(lookup())};
return index;
}
};
// Object and support extrusions of the same PrintObject at the same print_z. // Object and support extrusions of the same PrintObject at the same print_z.
// public, so that it could be accessed by free helper functions from GCode.cpp // public, so that it could be accessed by free helper functions from GCode.cpp
struct LayerToPrint struct LayerToPrint
@@ -802,8 +784,6 @@ private:
// Object layer id of the layer being generated; keys the per-filament config-slot // Object layer id of the layer being generated; keys the per-filament config-slot
// resolvers. Distinct from m_layer_index (an export progress counter starting at -1). // resolvers. Distinct from m_layer_index (an export progress counter starting at -1).
size_t m_cur_layer_idx{0}; size_t m_cur_layer_idx{0};
mutable ConfigIndexCache m_filament_index_cache;
mutable ConfigIndexCache m_nozzle_index_cache;
std::set<unsigned int> m_initial_layer_extruders; std::set<unsigned int> m_initial_layer_extruders;
std::vector<std::vector<unsigned int>> m_sorted_layer_filaments; std::vector<std::vector<unsigned int>> m_sorted_layer_filaments;
+77 -5
View File
@@ -134,15 +134,87 @@ bool tsp_remove_crossings(std::vector<size_t>& path, const Points& centers)
return {std::numeric_limits<size_t>::max(), std::numeric_limits<size_t>::max()}; return {std::numeric_limits<size_t>::max(), std::numeric_limits<size_t>::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<coord_t>(1, (extent.max.x() - extent.min.x()) / grid_n + 1);
const coord_t cell_h = std::max<coord_t>(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<std::vector<size_t>> edge_cells(size_t(grid_n) * grid_n);
auto find_crossing_grid = [&]() -> std::pair<size_t, size_t> {
for (std::vector<size_t>& 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<size_t>::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<size_t>::max())
return {i, first_j};
}
return {std::numeric_limits<size_t>::max(), std::numeric_limits<size_t>::max()};
};
// Process crossings one at a time: find first, reverse it, restart scan. // Process crossings one at a time: find first, reverse it, restart scan.
// Cap iterations to prevent infinite loops on collinear/overlapping segments. // Cap iterations to prevent infinite loops on collinear/overlapping segments.
int max_iters = static_cast<int>(pn * pn); const int max_iters = static_cast<int>(pn * pn);
bool improved = false; bool improved = false;
while (max_iters-- > 0) { // Reversing between two segments that only touch or overlap along a line need not remove the intersection, so on
auto [ci, cj] = find_crossing(); // islands laid out on a regular grid (a tiled texture, an array of parts) the loop can cycle through the same
if (ci == std::numeric_limits<size_t>::max()) break; // orderings until the pn * pn cap. Once an ordering repeats the rest of the loop is periodic, so only the steps
improved = true; // to the ordering the capped loop would have stopped on are taken.
std::unordered_map<uint64_t, int> 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<size_t>::max())
return false;
std::reverse(path.begin() + ci + 1, path.begin() + cj + 1); 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; return improved;
} }
+17 -12
View File
@@ -8,6 +8,7 @@
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include <random> #include <random>
#include <algorithm> #include <algorithm>
#include <limits>
#include <queue> #include <queue>
#include <unordered_map> #include <unordered_map>
@@ -1178,21 +1179,21 @@ std::optional<std::pair<size_t, size_t>> SeamPlacer::find_next_seam_in_layer(
const size_t layer_idx, const float max_distance, const size_t layer_idx, const float max_distance,
const SeamPlacerImpl::SeamComparator &comparator) const { const SeamPlacerImpl::SeamComparator &comparator) const {
using namespace SeamPlacerImpl; using namespace SeamPlacerImpl;
std::vector<size_t> nearby_points_indices = find_nearby_points(*layers[layer_idx].points_tree, projected_position, // Find the best nearby point and the nearest one. A layer of a fine relief has tens of thousands of candidates within
max_distance); // 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<size_t>::max();
if (nearby_points_indices.empty()) { size_t best_nearby_point_index = none;
return {}; 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;
} }
size_t best_nearby_point_index = nearby_points_indices[0];
size_t nearest_point_index = nearby_points_indices[0];
// Now find best nearby point, nearest point, and corresponding indices
for (const size_t &nearby_point_index : nearby_points_indices) {
const SeamCandidate &point = layers[layer_idx].points[nearby_point_index]; const SeamCandidate &point = layers[layer_idx].points[nearby_point_index];
if (point.perimeter.finalized) { 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], if (comparator.is_first_better(point, layers[layer_idx].points[best_nearby_point_index],
projected_position.head<2>()) projected_position.head<2>())
@@ -1204,6 +1205,10 @@ std::optional<std::pair<size_t, size_t>> SeamPlacer::find_next_seam_in_layer(
|| layers[layer_idx].points[nearest_point_index].perimeter.finalized) { || layers[layer_idx].points[nearest_point_index].perimeter.finalized) {
nearest_point_index = nearby_point_index; 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]; const SeamCandidate &best_nearby_point = layers[layer_idx].points[best_nearby_point_index];
+30
View File
@@ -313,6 +313,36 @@ std::vector<size_t> find_nearby_points(const KDTreeIndirectType &kdtree, const P
return visitor.result; 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<typename KDTreeIndirectType, typename PointType, typename VisitorFn>
void visit_nearby_points(const KDTreeIndirectType &kdtree, const PointType &center,
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<typename KDTreeIndirectType, typename PointType> template<typename KDTreeIndirectType, typename PointType>
std::vector<size_t> find_nearby_points(const KDTreeIndirectType &kdtree, const PointType &center, std::vector<size_t> find_nearby_points(const KDTreeIndirectType &kdtree, const PointType &center,
const typename KDTreeIndirectType::CoordType& max_distance) const typename KDTreeIndirectType::CoordType& max_distance)
+2 -1
View File
@@ -72,10 +72,11 @@ void LayerRegion::slices_to_fill_surfaces_clipped()
by_surface[size_t(surface.surface_type)].emplace_back(&surface); by_surface[size_t(surface.surface_type)].emplace_back(&surface);
// Trim surfaces by the fill_boundaries. // Trim surfaces by the fill_boundaries.
this->fill_surfaces.surfaces.clear(); 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) { for (size_t surface_type = 0; surface_type < size_t(stCount); ++ surface_type) {
const SurfacesPtr &this_surfaces = by_surface[surface_type]; const SurfacesPtr &this_surfaces = by_surface[surface_type];
if (! this_surfaces.empty()) 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));
} }
} }
+268 -68
View File
@@ -8,6 +8,7 @@
#include "MutablePolygon.hpp" #include "MutablePolygon.hpp"
#include "format.hpp" #include "format.hpp"
#include <numeric>
#include <utility> #include <utility>
#include <unordered_set> #include <unordered_set>
@@ -1324,10 +1325,15 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
} }
#endif // MM_SEGMENTATION_DEBUG_TOP_BOTTOM #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) { const size_t occluded_pairs = num_facets_states * layers.size();
for (size_t layer_idx = 0; layer_idx < layers.size(); ++layer_idx) { tbb::parallel_for(tbb::blocked_range<size_t>(0, occluded_pairs), [&](const tbb::blocked_range<size_t> &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()) { 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]); top_raw[extruder_idx][layer_idx] = diff(top_raw[extruder_idx][layer_idx], input_expolygons[layer_idx + 1]);
} }
@@ -1335,7 +1341,7 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
bottom_raw[extruder_idx][layer_idx] = diff(bottom_raw[extruder_idx][layer_idx], input_expolygons[layer_idx - 1]); bottom_raw[extruder_idx][layer_idx] = diff(bottom_raw[extruder_idx][layer_idx], input_expolygons[layer_idx - 1]);
} }
} }
} });
} }
std::vector<std::vector<ExPolygons>> triangles_by_color_bottom(num_facets_states); std::vector<std::vector<ExPolygons>> triangles_by_color_bottom(num_facets_states);
@@ -1393,11 +1399,58 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
return out; 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<size_t> &shell_layers,
const LayerColorStat &stat, ShellProjections &dst) {
std::vector<float> 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<ClipperUtils::ExPolygonsTile> tiles = ClipperUtils::tile_expolygons(ex, 16);
// [shell layer][tile]
std::vector<std::vector<ExPolygons>> shells(shell_layers.size(), std::vector<ExPolygons>(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<size_t>(0, num_layers), [&num_layers, &num_facets_states, &layer_color_stat, &top_raw, &triangles_by_color_top, tbb::parallel_for(tbb::blocked_range<size_t>(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<size_t> &range) { &shell_triangles_by_color_top, &shell_triangles_by_color_bottom](const tbb::blocked_range<size_t> &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { 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(); throw_on_cancel_callback();
LayerColorStat stat = layer_color_stat(layer_idx, color_idx); LayerColorStat stat = layer_color_stat(layer_idx, color_idx);
if (std::vector<Polygons> &top = top_raw[color_idx]; ! top.empty() && ! top[layer_idx].empty()) if (std::vector<Polygons> &top = top_raw[color_idx]; ! top.empty() && ! top[layer_idx].empty())
@@ -1406,18 +1459,10 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
top_ex = opening_ex(top_ex, stat.small_region_threshold); top_ex = opening_ex(top_ex, stat.small_region_threshold);
if (! top_ex.empty()) { if (! top_ex.empty()) {
append(triangles_by_color_top[color_idx][layer_idx], top_ex); append(triangles_by_color_top[color_idx][layer_idx], top_ex);
float offset = 0.f; std::vector<size_t> shell_layers;
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)
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));
//BBS: offset width should be 2*spacing to avoid too narrow area which has overlap of wall line project_to_shells(top_ex, layer_idx, shell_layers, stat, shell_triangles_by_color_top[color_idx][layer_idx]);
//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));
}
} }
} }
if (std::vector<Polygons> &bottom = bottom_raw[color_idx]; ! bottom.empty() && ! bottom[layer_idx].empty()) if (std::vector<Polygons> &bottom = bottom_raw[color_idx]; ! bottom.empty() && ! bottom[layer_idx].empty())
@@ -1426,21 +1471,13 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
bottom_ex = opening_ex(bottom_ex, stat.small_region_threshold); bottom_ex = opening_ex(bottom_ex, stat.small_region_threshold);
if (! bottom_ex.empty()) { if (! bottom_ex.empty()) {
append(triangles_by_color_bottom[color_idx][layer_idx], bottom_ex); append(triangles_by_color_bottom[color_idx][layer_idx], bottom_ex);
float offset = 0.f; std::vector<size_t> shell_layers;
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)
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);
//BBS: offset width should be 2*spacing to avoid too narrow area which has overlap of wall line project_to_shells(bottom_ex, layer_idx, shell_layers, stat, shell_triangles_by_color_bottom[color_idx][layer_idx]);
//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));
}
}
} }
} }
});
} }
}); });
@@ -1461,20 +1498,23 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
&shell_top_by_layer, &shell_bottom_by_layer](const tbb::blocked_range<size_t> &range) { &shell_top_by_layer, &shell_bottom_by_layer](const tbb::blocked_range<size_t> &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
throw_on_cancel_callback(); throw_on_cancel_callback();
ExPolygons painted_exploys; // The per-colour unions below are independent of each other, so they run in parallel (a painted top or
for (size_t color_idx = 0; color_idx < triangles_by_color_merged.size(); ++color_idx) { // 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]; 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_bottom[color_idx][layer_idx]));
append(self, std::move(triangles_by_color_top[color_idx][layer_idx])); append(self, std::move(triangles_by_color_top[color_idx][layer_idx]));
self = union_ex(self); 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); painted_exploys = union_ex(painted_exploys);
//BBS: merge the top and bottom shell layers //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 &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); auto top_area = diff_ex(union_ex(shell_top_by_layer[color_idx][layer_idx]), painted_exploys);
@@ -1483,7 +1523,7 @@ static inline std::vector<std::vector<ExPolygons>> segmentation_top_and_bottom_l
append(self, top_area); append(self, top_area);
append(self, bottom_area); append(self, bottom_area);
self = union_ex(self); self = union_ex(self);
} });
// Trim one region by the other if some of the regions overlap. // Trim one region by the other if some of the regions overlap.
ExPolygons painted_regions; ExPolygons painted_regions;
for (size_t color_idx = 1; color_idx < triangles_by_color_merged.size(); ++color_idx) { for (size_t color_idx = 1; color_idx < triangles_by_color_merged.size(); ++color_idx) {
@@ -1850,7 +1890,69 @@ static void remove_multiple_edges_in_vertices(MMU_Graph &graph, const std::vecto
} }
} }
static std::vector<std::vector<ExPolygons>> merge_segmented_layers(const std::vector<std::vector<ExPolygons>> &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<coord_t>(1, size.x() / GRID + 1);
m_cell_h = std::max<coord_t>(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<size_t> &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<typename Fn> 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<BoundingBox> m_bboxes;
BoundingBox m_extent;
coord_t m_cell_w = 1, m_cell_h = 1;
std::vector<std::vector<size_t>> m_grid;
};
static std::vector<std::vector<ExPolygons>> merge_segmented_layers(const std::vector<ExPolygons> &input_expolygons,
const std::vector<std::vector<ExPolygons>> &segmented_regions,
std::vector<std::vector<ExPolygons>> &&top_and_bottom_layers, std::vector<std::vector<ExPolygons>> &&top_and_bottom_layers,
const size_t num_facets_states, const size_t num_facets_states,
const std::function<void()> &throw_on_cancel_callback) const std::function<void()> &throw_on_cancel_callback)
@@ -1861,33 +1963,91 @@ static std::vector<std::vector<ExPolygons>> merge_segmented_layers(const std::ve
assert(!top_and_bottom_layers.size() || num_facets_states == top_and_bottom_layers.size()); 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"; BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Merging segmented layers in parallel - Begin";
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&segmented_regions, &top_and_bottom_layers, &segmented_regions_merged, &num_facets_states, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &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<size_t>(0, num_layers), [&](const tbb::blocked_range<size_t> &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
assert(segmented_regions[layer_idx].size() == num_facets_states); assert(segmented_regions[layer_idx].size() == num_facets_states);
// Zero is skipped because it is the default color of the volume
for (size_t extruder_id = 1; extruder_id < num_facets_states; ++extruder_id) {
throw_on_cancel_callback(); throw_on_cancel_callback();
if (!segmented_regions[layer_idx][extruder_id].empty()) { // Group the islands joined by a region overlapping several of them; the last group takes the regions lying
ExPolygons segmented_regions_trimmed = segmented_regions[layer_idx][extruder_id]; // outside every island.
if (!top_and_bottom_layers.empty()) { const ExPolygons &islands = input_expolygons[layer_idx];
for (const std::vector<ExPolygons> &top_and_bottom_by_extruder : top_and_bottom_layers) { const IslandLocator locator(islands);
if (!top_and_bottom_by_extruder[layer_idx].empty() && !segmented_regions_trimmed.empty()) { std::vector<size_t> parent(islands.size() + 1);
segmented_regions_trimmed = diff_ex(segmented_regions_trimmed, top_and_bottom_by_extruder[layer_idx]); 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<const ExPolygon *> 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<std::vector<size_t>> overlapped(pieces.size());
tbb::parallel_for(size_t(0), pieces.size(), [&](size_t i) { locator.find(*pieces[i], overlapped[i]); });
std::vector<size_t> 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<size_t> 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++;
segmented_regions_merged[layer_idx][extruder_id - 1] = std::move(segmented_regions_trimmed); // [bucket][colour]
} std::vector<std::vector<ExPolygons>> sides(num_buckets, std::vector<ExPolygons>(num_facets_states));
std::vector<std::vector<ExPolygons>> tops(num_buckets, std::vector<ExPolygons>(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);
if (!top_and_bottom_layers.empty() && !top_and_bottom_layers[extruder_id][layer_idx].empty()) { // Side regions minus the top/bottom regions of every colour.
bool was_top_and_bottom_empty = segmented_regions_merged[layer_idx][extruder_id - 1].empty(); std::vector<std::vector<ExPolygons>> merged(num_buckets, std::vector<ExPolygons>(num_facets_states));
append(segmented_regions_merged[layer_idx][extruder_id - 1], top_and_bottom_layers[extruder_id][layer_idx]); 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);
});
// Remove dimples (#7235) appearing after merging side segmentation of the model with tops and bottoms painted layers. // Then this colour's top/bottom regions, with the dimples removed (#7235) when the layer has side regions left.
if (!was_top_and_bottom_empty) for (size_t extruder_id = 1; extruder_id < num_facets_states; ++extruder_id) {
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 &region = 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 }); // end of parallel_for
@@ -2174,16 +2334,56 @@ std::vector<std::vector<ExPolygons>> segmentation_by_painting(const PrintObject
assert(!color_poly.empty()); assert(!color_poly.empty());
assert(!color_poly.front().empty()); assert(!color_poly.front().empty());
if (has_layer_only_one_color(color_poly)) { // Each island (an ExPolygon with its holes) is segmented on its own. Any point of an island is closer to
// 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. // that island's contours than to any other island's - the way out crosses its own boundary first - so its
segmented_regions[layer_idx][size_t(color_poly.front().front().color)] = input_expolygons[layer_idx]; // Voronoi cells, and with them its colour regions, depend on nothing else. A layer cut through a fine relief
} else { // has thousands of islands, and one Voronoi diagram over all of them degenerated into overlapping regions
MMU_Graph graph = build_graph(layer_idx, color_poly); // that every boolean afterwards had to untangle. Per island the diagrams stay small and the islands run in
remove_multiple_edges_in_vertices(graph, color_poly); // parallel; an island in a single colour needs no diagram at all.
graph.remove_nodes_with_one_arc(); const ExPolygons &islands = input_expolygons[layer_idx];
segmented_regions[layer_idx] = extract_colored_segments(graph, num_facets_states); std::vector<std::pair<size_t, size_t>> island_contours(islands.size()); // [first, last) into color_poly
//segmented_regions[layer_idx] = extract_colored_segments(color_poly, num_extruders, layer_idx); {
// 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<std::vector<ExPolygons>> 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<ColoredLines> island_poly(color_poly.begin() + first, color_poly.begin() + last);
std::vector<ExPolygons> &regions = 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<ExPolygons> &regions : 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 #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]); 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]);
@@ -2206,7 +2406,7 @@ std::vector<std::vector<ExPolygons>> segmentation_by_painting(const PrintObject
throw_on_cancel_callback(); throw_on_cancel_callback();
} }
std::vector<std::vector<ExPolygons>> segmented_regions_merged = merge_segmented_layers(segmented_regions, std::move(top_and_bottom_layers), num_facets_states, throw_on_cancel_callback); std::vector<std::vector<ExPolygons>> 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(); throw_on_cancel_callback();
#ifdef MM_SEGMENTATION_DEBUG_REGIONS #ifdef MM_SEGMENTATION_DEBUG_REGIONS
+6 -2
View File
@@ -19,11 +19,15 @@ public:
MultiPoint() {} MultiPoint() {}
MultiPoint(const MultiPoint &other) : points(other.points) {} 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<Point> list) : points(list) {} MultiPoint(std::initializer_list<Point> list) : points(list) {}
explicit MultiPoint(const Points &_points) : points(_points) {} 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=(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; virtual ~MultiPoint() = default;
void scale(double factor); void scale(double factor);
void scale(double factor_x, double factor_y); void scale(double factor_x, double factor_y);
+33 -8
View File
@@ -18,6 +18,8 @@
#include <cassert> #include <cassert>
#include <unordered_set> #include <unordered_set>
#include <thread> #include <thread>
#include <tbb/blocked_range.h>
#include <tbb/parallel_for.h>
#include "libslic3r/AABBTreeLines.hpp" #include "libslic3r/AABBTreeLines.hpp"
#include "Print.hpp" #include "Print.hpp"
static const int overhang_sampling_number = 6; static const int overhang_sampling_number = 6;
@@ -2473,7 +2475,22 @@ void PerimeterGenerator::process_arachne()
const bool only_one_wall_first_layer = this->config->only_one_wall_first_layer && has_bottom_shell_layers(*this->config); 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 // we need to process each island separately because we might have different
// extra perimeters for each one // extra perimeters for each one
for (const Surface& surface : all_surfaces) { // 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<ArachneSurfaceResult> results(all_surfaces.size());
tbb::parallel_for(tbb::blocked_range<size_t>(0, all_surfaces.size()), [&](const tbb::blocked_range<size_t> &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; coord_t bead_width_0 = ext_perimeter_spacing;
// detect how many perimeters must be generated for this island // 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 loop_number = this->config->wall_loops + surface.extra_perimeters - 1; // 0-indexed loops
@@ -2737,10 +2754,10 @@ void PerimeterGenerator::process_arachne()
// 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 // 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. // rounding errors. When precise wall is enabled, the external perimeter full spacing is used.
coord_t threshold_external = (apply_precise_outer_wall) coord_t threshold_external = (apply_precise_outer_wall)
// Precise outer wall ⇒ use “full external spacing” // Precise outer wall: use the full external spacing
? ( this->ext_perimeter_flow.scaled_spacing() ? ( this->ext_perimeter_flow.scaled_spacing()
+ this->perimeter_flow.scaled_spacing()/2.0 ) + this->perimeter_flow.scaled_spacing()/2.0 )
// Normal ⇒ half ext spacing + half int spacing // Normal: half ext spacing plus half int spacing
: ( this->ext_perimeter_flow.scaled_spacing()/2.0 : ( this->ext_perimeter_flow.scaled_spacing()/2.0
+ this->perimeter_flow.scaled_spacing()/2.0 ); + this->perimeter_flow.scaled_spacing()/2.0 );
@@ -2830,7 +2847,8 @@ void PerimeterGenerator::process_arachne()
this->config->overhang_reverse_internal_only); this->config->overhang_reverse_internal_only);
} }
defer_unsupported_loops(*this, extrusion_coll); defer_unsupported_loops(*this, extrusion_coll);
this->loops->append(extrusion_coll); result.loops = std::move(extrusion_coll);
result.has_loops = true;
} }
const coord_t spacing = (perimeters.size() == 1) ? ext_perimeter_spacing2 : perimeter_spacing; const coord_t spacing = (perimeters.size() == 1) ? ext_perimeter_spacing2 : perimeter_spacing;
@@ -2873,9 +2891,7 @@ void PerimeterGenerator::process_arachne()
if (!top_expolygons.empty()) { if (!top_expolygons.empty()) {
infill_exp = union_ex(infill_exp, offset_ex(top_expolygons, double(top_inset))); infill_exp = union_ex(infill_exp, offset_ex(top_expolygons, double(top_inset)));
} }
this->fill_surfaces->append(infill_exp, stInternal); result.infill = std::move(infill_exp);
apply_extra_perimeters(infill_exp);
// BBS: get the no-overlap infill expolygons // BBS: get the no-overlap infill expolygons
{ {
@@ -2886,9 +2902,18 @@ void PerimeterGenerator::process_arachne()
float(+min_perimeter_infill_spacing / 2.)); float(+min_perimeter_infill_spacing / 2.));
if (!top_expolygons.empty()) if (!top_expolygons.empty())
polyWithoutOverlap = union_ex(polyWithoutOverlap, top_expolygons); polyWithoutOverlap = union_ex(polyWithoutOverlap, top_expolygons);
this->fill_no_overlap->insert(this->fill_no_overlap->end(), polyWithoutOverlap.begin(), polyWithoutOverlap.end()); 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));
}
} }
bool PerimeterGeneratorLoop::is_internal_contour() const bool PerimeterGeneratorLoop::is_internal_contour() const
+2 -2
View File
@@ -27,7 +27,7 @@ public:
explicit Polygon(const Points &points) : MultiPoint(points) {} explicit Polygon(const Points &points) : MultiPoint(points) {}
Polygon(std::initializer_list<Point> points) : MultiPoint(points) {} Polygon(std::initializer_list<Point> points) : MultiPoint(points) {}
Polygon(const Polygon &other) : MultiPoint(other.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<Vec2d> &points) { static Polygon new_scale(const std::vector<Vec2d> &points) {
Polygon pgn; Polygon pgn;
pgn.points.reserve(points.size()); pgn.points.reserve(points.size());
@@ -36,7 +36,7 @@ public:
return pgn; return pgn;
} }
Polygon& operator=(const Polygon &other) { points = other.points; return *this; } 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]; } Point& operator[](Points::size_type idx) { return this->points[idx]; }
const Point& operator[](Points::size_type idx) const { return this->points[idx]; } const Point& operator[](Points::size_type idx) const { return this->points[idx]; }
+2 -2
View File
@@ -20,7 +20,7 @@ class Polyline : public MultiPoint {
public: public:
Polyline() {}; Polyline() {};
Polyline(const Polyline& other) : MultiPoint(other.points), fitting_result(other.fitting_result) {} 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<Point> list) : MultiPoint(list) { Polyline(std::initializer_list<Point> list) : MultiPoint(list) {
fitting_result.clear(); fitting_result.clear();
} }
@@ -41,7 +41,7 @@ public:
fitting_result = other.fitting_result; fitting_result = other.fitting_result;
return *this; return *this;
} }
Polyline& operator=(Polyline&& other) { Polyline& operator=(Polyline&& other) noexcept {
points = std::move(other.points); points = std::move(other.points);
fitting_result = std::move(other.fitting_result); fitting_result = std::move(other.fitting_result);
return *this; return *this;
+5 -7
View File
@@ -4298,11 +4298,10 @@ Polygons Print::get_extruder_shared_printable_polygon() const
return shared_printable_polys; return shared_printable_polys;
} }
void Print::set_nozzle_group_result(std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> result) // Narrow the stored grouping result to the layer-aware type the slicing pipeline uses.
std::shared_ptr<MultiNozzleUtils::LayeredNozzleGroupResult> Print::get_layered_nozzle_group_result() const
{ {
m_nozzle_group_result = std::move(result); return std::dynamic_pointer_cast<MultiNozzleUtils::LayeredNozzleGroupResult>(m_nozzle_group_result);
m_layered_nozzle_group_result = std::dynamic_pointer_cast<MultiNozzleUtils::LayeredNozzleGroupResult>(m_nozzle_group_result);
++m_config_index_generation;
} }
// Dynamic (per-layer selector) regroup predicate. // Dynamic (per-layer selector) regroup predicate.
@@ -4338,7 +4337,6 @@ int Print::get_filament_config_indx(int filament_id, int layer_id, bool use_cach
void Print::update_filament_self_index_cache() void Print::update_filament_self_index_cache()
{ {
m_missing_nozzle_group_logged.clear(); // reset the per-slice get_config_index log dedupe m_missing_nozzle_group_logged.clear(); // reset the per-slice get_config_index log dedupe
++m_config_index_generation;
std::vector<int> values; std::vector<int> values;
if (m_full_print_config.has("filament_self_index")) { if (m_full_print_config.has("filament_self_index")) {
@@ -4378,7 +4376,7 @@ int Print::get_nozzle_config_index(int filament_id, int layer_id)
int Print::get_config_index(int filament_id, int layer_id, const std::vector<std::string> &variant_list, const std::vector<int>& self_index_list, FilamentIndexMap *index_map) int Print::get_config_index(int filament_id, int layer_id, const std::vector<std::string> &variant_list, const std::vector<int>& self_index_list, FilamentIndexMap *index_map)
{ {
const MultiNozzleUtils::LayeredNozzleGroupResult *group_result = m_layered_nozzle_group_result.get(); auto group_result = get_layered_nozzle_group_result();
// Orca: defensive — when no grouping producer has published a result yet, fall back to the // Orca: defensive — when no grouping producer has published a result yet, fall back to the
// static identity: one filament-variant column per filament. // static identity: one filament-variant column per filament.
if (!group_result) if (!group_result)
@@ -4413,7 +4411,7 @@ int Print::get_config_index(int filament_id, int layer_id, const std::vector<std
int Print::get_config_index(int filament_id, int layer_id, const std::vector<std::string> &variant_list, const std::vector<int>& self_index_list, PrintIndexMap &index_map) int Print::get_config_index(int filament_id, int layer_id, const std::vector<std::string> &variant_list, const std::vector<int>& self_index_list, PrintIndexMap &index_map)
{ {
const MultiNozzleUtils::LayeredNozzleGroupResult *group_result = m_layered_nozzle_group_result.get(); auto group_result = get_layered_nozzle_group_result();
// Orca: same static fallback as the filament overload; the slot degenerates to the filament's // Orca: same static fallback as the filament overload; the slot degenerates to the filament's
// extruder column (filament_map is 1 based, get_extruder_id guards the filament id range). // extruder column (filament_map is 1 based, get_extruder_id guards the filament id range).
if (!group_result) if (!group_result)
+2 -9
View File
@@ -1073,9 +1073,9 @@ public:
// Logical (extruder, nozzle) grouping result produced by ToolOrdering during reorder. // Logical (extruder, nozzle) grouping result produced by ToolOrdering during reorder.
// Consumed by GCode via get_layered_nozzle_group_result()->get_nozzle_id(filament, layer) etc. // Consumed by GCode via get_layered_nozzle_group_result()->get_nozzle_id(filament, layer) etc.
void set_nozzle_group_result(std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> result); void set_nozzle_group_result(std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> result) { m_nozzle_group_result = result; }
std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> get_nozzle_group_result() const { return m_nozzle_group_result; } std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> get_nozzle_group_result() const { return m_nozzle_group_result; }
std::shared_ptr<MultiNozzleUtils::LayeredNozzleGroupResult> get_layered_nozzle_group_result() const { return m_layered_nozzle_group_result; } std::shared_ptr<MultiNozzleUtils::LayeredNozzleGroupResult> get_layered_nozzle_group_result() const;
// True only when the project opts into the per-layer filament selector // True only when the project opts into the per-layer filament selector
// (enable_filament_dynamic_map) in auto-for-flush mode on a multi-extruder machine. Gates the // (enable_filament_dynamic_map) in auto-for-flush mode on a multi-extruder machine. Gates the
@@ -1226,9 +1226,6 @@ public:
// pipeline's cooling stage, which runs concurrently with the generator stage filling it. // pipeline's cooling stage, which runs concurrently with the generator stage filling it.
int get_filament_config_indx(int filament_id, int layer_id, bool use_cache = true); int get_filament_config_indx(int filament_id, int layer_id, bool use_cache = true);
int get_nozzle_config_index(int filament_id, int layer_id); int get_nozzle_config_index(int filament_id, int layer_id);
// Changes with the grouping result and the filament maps, so a caller may reuse a resolved slot
// until it changes.
size_t config_index_generation() const { return m_config_index_generation; }
// Orca: Implement prusa's filament shrink compensation approach // Orca: Implement prusa's filament shrink compensation approach
// Returns if all used filaments have same shrinkage compensations. // Returns if all used filaments have same shrinkage compensations.
@@ -1355,9 +1352,6 @@ private:
// Logical (extruder, nozzle) grouping result, set by ToolOrdering during reorder. // Logical (extruder, nozzle) grouping result, set by ToolOrdering during reorder.
std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> m_nozzle_group_result; std::shared_ptr<MultiNozzleUtils::NozzleGroupResultBase> m_nozzle_group_result;
// m_nozzle_group_result narrowed to the layer-aware type; only set_nozzle_group_result() assigns
// either.
std::shared_ptr<MultiNozzleUtils::LayeredNozzleGroupResult> m_layered_nozzle_group_result;
// Sequential (by-object) selector plans, keyed by object; see sequential_dynamic_orderings(). // Sequential (by-object) selector plans, keyed by object; see sequential_dynamic_orderings().
// Rebuilt (or cleared) on every process(). // Rebuilt (or cleared) on every process().
@@ -1367,7 +1361,6 @@ private:
FilamentIndexMap m_filament_index_map; FilamentIndexMap m_filament_index_map;
// Used to cache printer and process parameter information // Used to cache printer and process parameter information
PrintIndexMap m_nozzle_index_map; PrintIndexMap m_nozzle_index_map;
size_t m_config_index_generation{0};
// Orca: filament ids already reported as missing a nozzle-group entry this slice. get_config_index() // Orca: filament ids already reported as missing a nozzle-group entry this slice. get_config_index()
// falls back per-filament/per-layer in the g-code hot path, so this dedupes its log to once per // falls back per-filament/per-layer in the g-code hot path, so this dedupes its log to once per
// filament instead of flooding thousands of identical error lines. Cleared with the caches each slice. // filament instead of flooding thousands of identical error lines. Cleared with the caches each slice.
+1 -1
View File
@@ -1309,7 +1309,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
// Reproduce that exact expansion here so an unchanged config diffs empty — the expanded // Reproduce that exact expansion here so an unchanged config diffs empty — the expanded
// keys invalidate the wipe tower / g-code export, and the placeholder parser aliases // keys invalidate the wipe tower / g-code export, and the placeholder parser aliases
// the full config — instead of trimming back to one slot per filament. // the full config — instead of trimming back to one slot per filament.
auto group_result = this->get_layered_nozzle_group_result(); auto group_result = std::dynamic_pointer_cast<MultiNozzleUtils::LayeredNozzleGroupResult>(this->get_nozzle_group_result());
std::unordered_map<int, std::vector<FilamentVariantUse>> filament_variant_uses; std::unordered_map<int, std::vector<FilamentVariantUse>> filament_variant_uses;
if (group_result && group_result->is_support_dynamic_nozzle_map() if (group_result && group_result->is_support_dynamic_nozzle_map()
&& collect_filament_variant_uses(*group_result, m_ori_full_print_config, filament_variant_uses)) && collect_filament_variant_uses(*group_result, m_ori_full_print_config, filament_variant_uses))
+338 -168
View File
@@ -29,6 +29,7 @@
#include <cstddef> #include <cstddef>
#include <float.h> #include <float.h>
#include <array>
#include <iterator> #include <iterator>
#include <mutex> #include <mutex>
#include <string> #include <string>
@@ -41,6 +42,7 @@
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include <tbb/parallel_for.h> #include <tbb/parallel_for.h>
#include <tbb/parallel_invoke.h>
#include <tbb/spin_mutex.h> #include <tbb/spin_mutex.h>
#include <tbb/concurrent_unordered_set.h> #include <tbb/concurrent_unordered_set.h>
@@ -1663,7 +1665,9 @@ void PrintObject::detect_surfaces_type()
bool interface_shells = ! spiral_mode && m_config.interface_shells.value; 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(); 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"; BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << region_id << " in parallel - start";
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING #ifdef SLIC3R_DEBUG_SLICE_PROCESSING
for (Layer *layer : m_layers) for (Layer *layer : m_layers)
@@ -1721,7 +1725,7 @@ void PrintObject::detect_surfaces_type()
if (upper_layer) { if (upper_layer) {
ExPolygons upper_slices = interface_shells ? 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->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); surfaces_append(top, opening_ex(upper_slices, offset), stTop);
} else { } else {
// if no upper layer, all surfaces of this one are solid // if no upper layer, all surfaces of this one are solid
@@ -1747,7 +1751,7 @@ void PrintObject::detect_surfaces_type()
surfaces_append( surfaces_append(
bottom, bottom,
opening_ex( 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), offset),
surface_type_bottom_other); surface_type_bottom_other);
// if user requested internal shells, we need to identify surfaces // if user requested internal shells, we need to identify surfaces
@@ -1778,34 +1782,44 @@ void PrintObject::detect_surfaces_type()
// and top surfaces; let's do an intersection to discover them and consider them // and top surfaces; let's do an intersection to discover them and consider them
// as bottom surfaces (to allow for bridge detection) // as bottom surfaces (to allow for bridge detection)
if (! top.empty() && ! bottom.empty()) { 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 (!cracks.empty()) {
if (lower_layer) { // Only detect small cracks for non-first layer, because first layer should always be bottom 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; 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) { for (const auto& crack : cracks) {
if (offset_ex(crack, small_crack_threshold).empty()) { 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 // 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; 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 && se.area() > crack.area() * 2
&& !offset_ex(diff_ex(se, crack), small_crack_threshold).empty(); && !offset_ex(diff_ex(se, crack), small_crack_threshold).empty();
})) continue; })) continue;
// Crack too small, leave it as part of the top surface, remove it from bottom surfaces // 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; Surfaces bot_tmp;
for (auto& b : bottom) { 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); bottom = std::move(bot_tmp);
} }
} }
} }
Polygons top_polygons = to_polygons(std::move(top)); ExPolygons top_expolygons = to_expolygons(std::move(top));
top.clear(); top.clear();
surfaces_append(top, diff_ex(top_polygons, bottom), stTop); surfaces_append(top, diff_ex_by_piece(top_expolygons, to_polygons(bottom)), stTop);
} }
} }
@@ -1896,7 +1910,7 @@ void PrintObject::detect_surfaces_type()
{ {
Polygons topbottom = to_polygons(top); Polygons topbottom = to_polygons(top);
polygons_append(topbottom, to_polygons(bottom)); 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)); surfaces_append(surfaces_out, std::move(top));
@@ -2073,29 +2087,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<size_t>(0, m_layers.size()), [this, region_id](const tbb::blocked_range<size_t> &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 ======================================================= // === 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<size_t>(0, m_layers.size()), [this](const tbb::blocked_range<size_t> &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"; 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. // Fill in layerm->fill_surfaces by trimming the layerm->slices by the cummulative layerm->fill_surfaces.
tbb::parallel_for( tbb::parallel_for(
@@ -2112,7 +2128,7 @@ void PrintObject::detect_surfaces_type()
}); });
m_print->throw_if_canceled(); m_print->throw_if_canceled();
BOOST_LOG_TRIVIAL(debug) << "Detecting solid surfaces for region " << region_id << " - clipping in parallel - end"; 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.) // 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; m_typed_slices = true;
@@ -2179,8 +2195,10 @@ void PrintObject::process_external_surfaces()
BOOST_LOG_TRIVIAL(debug) << "Collecting surfaces covered with extrusions in parallel - end"; 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 in parallel - start";
BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces for region " << region_id << " 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::parallel_for(
tbb::blocked_range<size_t>(0, m_layers.size()), tbb::blocked_range<size_t>(0, m_layers.size()),
[this, &surfaces_covered, region_id](const tbb::blocked_range<size_t>& range) { [this, &surfaces_covered, region_id](const tbb::blocked_range<size_t>& range) {
@@ -2195,9 +2213,9 @@ void PrintObject::process_external_surfaces()
} }
} }
); );
});
m_print->throw_if_canceled(); m_print->throw_if_canceled();
BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces for region " << region_id << " in parallel - end"; BOOST_LOG_TRIVIAL(debug) << "Processing external surfaces in parallel - end";
}
} }
void PrintObject::discover_vertical_shells() void PrintObject::discover_vertical_shells()
@@ -2236,10 +2254,10 @@ void PrintObject::discover_vertical_shells()
// The "ensure vertical wall thickness" feature is not applicable to any of the regions. Quit. // The "ensure vertical wall thickness" feature is not applicable to any of the regions. Quit.
return; return;
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells in parallel - start : cache top / bottom"; BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells in parallel - start : cache top / bottom";
//FIXME Improve the heuristics for a grain size. // One layer per task: on a layer cut through a fine relief the unions below take far longer than elsewhere, and a
size_t grain_size = std::max(num_layers / 16, size_t(1)); // few such layers next to each other must not end up in one task.
tbb::parallel_for( tbb::parallel_for(
tbb::blocked_range<size_t>(0, num_layers, grain_size), tbb::blocked_range<size_t>(0, num_layers, 1),
[this, &cache_top_botom_regions](const tbb::blocked_range<size_t>& range) { [this, &cache_top_botom_regions](const tbb::blocked_range<size_t>& range) {
const std::initializer_list<SurfaceType> surfaces_bottom { stBottom, stBottomBridge }; const std::initializer_list<SurfaceType> surfaces_bottom { stBottom, stBottomBridge };
const size_t num_regions = this->num_printing_regions(); const size_t num_regions = this->num_printing_regions();
@@ -2247,26 +2265,38 @@ void PrintObject::discover_vertical_shells()
m_print->throw_if_canceled(); m_print->throw_if_canceled();
const Layer &layer = *m_layers[idx_layer]; const Layer &layer = *m_layers[idx_layer];
DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[idx_layer]; DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[idx_layer];
// Simulate single set of perimeters over all merged regions. const auto top_bottom_expansion = [&layer](size_t region_id) {
float perimeter_offset = 0.f; return float(layer.m_regions[region_id]->flow(frSolidInfill).scaled_spacing()) * top_bottom_expansion_coeff;
float perimeter_min_spacing = FLT_MAX; };
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING #ifdef SLIC3R_DEBUG_SLICE_PROCESSING
static size_t debug_idx = 0; static size_t debug_idx = 0;
++ debug_idx; ++ debug_idx;
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
for (size_t region_id = 0; region_id < num_regions; ++ region_id) { // The top surfaces, the bottom surfaces and the holes are independent of each other.
LayerRegion &layerm = *layer.m_regions[region_id]; tbb::parallel_invoke(
float top_bottom_expansion = float(layerm.flow(frSolidInfill).scaled_spacing()) * top_bottom_expansion_coeff; [&]() {
// Top surfaces. for (size_t region_id = 0; region_id < num_regions; ++ region_id)
append(cache.top_surfaces, offset(layerm.slices.filter_by_type(stTop), top_bottom_expansion)); 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)); // append(cache.top_surfaces, offset(layerm.fill_surfaces.filter_by_type(stTop), top_bottom_expansion));
// Bottom surfaces. // Save some computing time by reducing the number of polygons.
append(cache.bottom_surfaces, offset(layerm.slices.filter_by_types(surfaces_bottom), top_bottom_expansion)); 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)); // 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. // 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. // First find the maxium number of perimeters per region slice.
unsigned int perimeters = 0; unsigned int perimeters = 0;
for (Surface &s : layerm.slices.surfaces) for (const Surface &s : layerm.slices.surfaces)
perimeters = std::max<unsigned int>(perimeters, s.extra_perimeters); perimeters = std::max<unsigned int>(perimeters, s.extra_perimeters);
perimeters += layerm.region().config().wall_loops.value; perimeters += layerm.region().config().wall_loops.value;
// Then calculate the infill offset. // Then calculate the infill offset.
@@ -2279,9 +2309,6 @@ void PrintObject::discover_vertical_shells()
} }
polygons_append(cache.holes, to_polygons(layerm.fill_expolygons)); 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. // 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.) { if (perimeter_offset > 0.) {
// The layer.lslices are forced to merge by expanding them first. // The layer.lslices are forced to merge by expanding them first.
@@ -2297,106 +2324,32 @@ void PrintObject::discover_vertical_shells()
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
} }
cache.holes = union_(cache.holes); cache.holes = union_(cache.holes);
});
} }
}); });
m_print->throw_if_canceled(); m_print->throw_if_canceled();
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells in parallel - end : cache top / bottom"; 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
const PrintRegion &region = this->printing_region(region_id); // region-specific but the shell settings and the external perimeter spacing, so a region sharing them with an earlier
if (region.config().ensure_vertical_shell_thickness.value != evstAll ) // one reuses its result instead of repeating it: that accumulation is a union over several layers of top/bottom
// This region will be handled by discover_horizontal_shells(). // surfaces, and a multi-material print has a region per filament.
continue; using AccumulationKey = std::array<double, 5>;
struct ShellAccumulation
//FIXME Improve the heuristics for a grain size. {
size_t grain_size = std::max(num_layers / 16, size_t(1)); AccumulationKey key;
if (! top_bottom_surfaces_all_regions) {
// This is either a single material print, or a multi-material print and interface_shells are enabled, meaning that the vertical shell thickness
// is calculated over a single material.
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - start : cache top / bottom";
tbb::parallel_for(
tbb::blocked_range<size_t>(0, num_layers, grain_size),
[this, region_id, &cache_top_botom_regions](const tbb::blocked_range<size_t>& range) {
const std::initializer_list<SurfaceType> surfaces_bottom { stBottom, stBottomBridge };
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
m_print->throw_if_canceled();
Layer &layer = *m_layers[idx_layer];
LayerRegion &layerm = *layer.m_regions[region_id];
float top_bottom_expansion = float(layerm.flow(frSolidInfill).scaled_spacing()) * top_bottom_expansion_coeff;
// Top surfaces.
auto &cache = cache_top_botom_regions[idx_layer];
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.
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));
// Holes over all regions. Only collect them once, they are valid for all region_id iterations.
if (cache.holes.empty()) {
for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id)
polygons_append(cache.holes, to_polygons(layer.regions()[region_id]->fill_expolygons));
}
}
});
m_print->throw_if_canceled();
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - end : cache top / bottom";
}
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - start : ensure vertical wall thickness";
grain_size = 1;
tbb::parallel_for(
tbb::blocked_range<size_t>(0, num_layers, grain_size),
[this, region_id, &cache_top_botom_regions]
(const tbb::blocked_range<size_t>& range) {
// printf("discover_vertical_shells from %d to %d\n", range.begin(), range.end());
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
m_print->throw_if_canceled();
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
static size_t debug_idx = 0;
++ debug_idx;
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
Layer *layer = m_layers[idx_layer];
LayerRegion *layerm = layer->m_regions[region_id];
const PrintRegionConfig &region_config = layerm->region().config();
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
layerm->export_region_slices_to_svg_debug("3_discover_vertical_shells-initial");
layerm->export_region_fill_surfaces_to_svg_debug("3_discover_vertical_shells-initial");
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
Flow solid_infill_flow = layerm->flow(frSolidInfill);
coord_t infill_line_spacing = solid_infill_flow.scaled_spacing();
// Find a union of perimeters below / above this surface to guarantee a minimum shell thickness.
Polygons shell; Polygons shell;
Polygons holes; Polygons holes;
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING };
ExPolygons shell_ex; const auto accumulation_key = [](const PrintRegionConfig &region_config, const LayerRegion *layerm) {
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ return AccumulationKey{ double(region_config.top_shell_layers.value), region_config.top_shell_thickness.value,
float min_perimeter_infill_spacing = float(infill_line_spacing) * 1.05f; double(region_config.bottom_shell_layers.value), region_config.bottom_shell_thickness.value,
#if 0 double(layerm->flow(frExternalPerimeter).scaled_spacing()) };
// #ifdef SLIC3R_DEBUG_SLICE_PROCESSING };
{ const auto accumulate_shell = [this, &cache_top_botom_regions](size_t idx_layer, const PrintRegionConfig &region_config,
Slic3r::SVG svg_cummulative(debug_out_path("discover_vertical_shells-perimeters-before-union-run%d.svg", debug_idx), this->bounding_box()); const LayerRegion *layerm, Polygons &shell, Polygons &holes) {
for (int n = (int)idx_layer - n_extra_bottom_layers; n <= (int)idx_layer + n_extra_top_layers; ++ n) { const Layer *layer = m_layers[idx_layer];
if (n < 0 || n >= (int)m_layers.size())
continue;
ExPolygons &expolys = m_layers[n]->perimeter_expolygons;
for (size_t i = 0; i < expolys.size(); ++ i) {
Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-run%d-layer%d-expoly%d.svg", debug_idx, n, i), get_extents(expolys[i]));
svg.draw(expolys[i]);
svg.draw_outline(expolys[i].contour, "black", scale_(0.05));
svg.draw_outline(expolys[i].holes, "blue", scale_(0.05));
svg.Close();
svg_cummulative.draw(expolys[i]);
svg_cummulative.draw_outline(expolys[i].contour, "black", scale_(0.05));
svg_cummulative.draw_outline(expolys[i].holes, "blue", scale_(0.05));
}
}
}
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
polygons_append(holes, cache_top_botom_regions[idx_layer].holes); polygons_append(holes, cache_top_botom_regions[idx_layer].holes);
auto combine_holes = [&holes](const Polygons &holes2) { auto combine_holes = [&holes](const Polygons &holes2) {
if (holes.empty() || holes2.empty()) if (holes.empty() || holes2.empty())
@@ -2471,6 +2424,141 @@ void PrintObject::discover_vertical_shells()
(i > ibottom || bottom_z - m_layers[i]->print_z < region_config.bottom_shell_thickness - EPSILON)) (i > ibottom || bottom_z - m_layers[i]->print_z < region_config.bottom_shell_thickness - EPSILON))
combine_holes(cache_top_botom_regions[i].holes); combine_holes(cache_top_botom_regions[i].holes);
} }
};
std::vector<std::vector<ShellAccumulation>> 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<std::array<size_t, 3>> todo; // layer, its slot, a region holding the key
for (size_t idx_layer = 0; idx_layer < num_layers; ++ idx_layer) {
std::vector<ShellAccumulation> &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 &region = this->printing_region(region_id);
if (region.config().ensure_vertical_shell_thickness.value != evstAll )
// This region will be handled by discover_horizontal_shells().
return;
//FIXME Improve the heuristics for a grain size.
size_t grain_size = std::max(num_layers / 16, size_t(1));
if (! top_bottom_surfaces_all_regions) {
// This is either a single material print, or a multi-material print and interface_shells are enabled, meaning that the vertical shell thickness
// is calculated over a single material.
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - start : cache top / bottom";
tbb::parallel_for(
tbb::blocked_range<size_t>(0, num_layers, grain_size),
[this, region_id, &cache_top_botom_regions](const tbb::blocked_range<size_t>& range) {
const std::initializer_list<SurfaceType> surfaces_bottom { stBottom, stBottomBridge };
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
m_print->throw_if_canceled();
Layer &layer = *m_layers[idx_layer];
LayerRegion &layerm = *layer.m_regions[region_id];
float top_bottom_expansion = float(layerm.flow(frSolidInfill).scaled_spacing()) * top_bottom_expansion_coeff;
// Top surfaces.
auto &cache = cache_top_botom_regions[idx_layer];
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.
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));
// Holes over all regions. Only collect them once, they are valid for all region_id iterations.
if (cache.holes.empty()) {
for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id)
polygons_append(cache.holes, to_polygons(layer.regions()[region_id]->fill_expolygons));
}
}
});
m_print->throw_if_canceled();
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - end : cache top / bottom";
}
BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << region_id << " in parallel - start : ensure vertical wall thickness";
grain_size = 1;
tbb::parallel_for(
tbb::blocked_range<size_t>(0, num_layers, grain_size),
[this, region_id, &shell_accumulations, &accumulation_key, &accumulate_shell]
(const tbb::blocked_range<size_t>& range) {
// printf("discover_vertical_shells from %d to %d\n", range.begin(), range.end());
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
m_print->throw_if_canceled();
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
static size_t debug_idx = 0;
++ debug_idx;
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
Layer *layer = m_layers[idx_layer];
LayerRegion *layerm = layer->m_regions[region_id];
const PrintRegionConfig &region_config = layerm->region().config();
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
layerm->export_region_slices_to_svg_debug("3_discover_vertical_shells-initial");
layerm->export_region_fill_surfaces_to_svg_debug("3_discover_vertical_shells-initial");
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
Flow solid_infill_flow = layerm->flow(frSolidInfill);
coord_t infill_line_spacing = solid_infill_flow.scaled_spacing();
// Find a union of perimeters below / above this surface to guarantee a minimum shell thickness.
Polygons shell;
Polygons holes;
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
ExPolygons shell_ex;
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
float min_perimeter_infill_spacing = float(infill_line_spacing) * 1.05f;
#if 0
// #ifdef SLIC3R_DEBUG_SLICE_PROCESSING
{
Slic3r::SVG svg_cummulative(debug_out_path("discover_vertical_shells-perimeters-before-union-run%d.svg", debug_idx), this->bounding_box());
for (int n = (int)idx_layer - n_extra_bottom_layers; n <= (int)idx_layer + n_extra_top_layers; ++ n) {
if (n < 0 || n >= (int)m_layers.size())
continue;
ExPolygons &expolys = m_layers[n]->perimeter_expolygons;
for (size_t i = 0; i < expolys.size(); ++ i) {
Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-run%d-layer%d-expoly%d.svg", debug_idx, n, i), get_extents(expolys[i]));
svg.draw(expolys[i]);
svg.draw_outline(expolys[i].contour, "black", scale_(0.05));
svg.draw_outline(expolys[i].holes, "blue", scale_(0.05));
svg.Close();
svg_cummulative.draw(expolys[i]);
svg_cummulative.draw_outline(expolys[i].contour, "black", scale_(0.05));
svg_cummulative.draw_outline(expolys[i].holes, "blue", scale_(0.05));
}
}
}
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
const AccumulationKey key = accumulation_key(region_config, layerm);
const ShellAccumulation *reused = shell_accumulations.empty() ? nullptr :
[&]() -> const ShellAccumulation * {
for (const ShellAccumulation &a : shell_accumulations[idx_layer])
if (a.key == key)
return &a;
return nullptr;
}();
if (reused != nullptr) {
shell = reused->shell;
holes = reused->holes;
} else
accumulate_shell(idx_layer, region_config, layerm, shell, holes);
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING #ifdef SLIC3R_DEBUG_SLICE_PROCESSING
{ {
Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-%d.svg", debug_idx), get_extents(shell)); Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-%d.svg", debug_idx), get_extents(shell));
@@ -2564,11 +2652,8 @@ void PrintObject::discover_vertical_shells()
Polygons object_volume; Polygons object_volume;
Polygons internal_volume; Polygons internal_volume;
{ {
Polygons shrinked_bottom_slice = idx_layer > 0 ? to_polygons(m_layers[idx_layer - 1]->lslices) : Polygons{}; if (idx_layer > 0 && idx_layer + 1 < m_layers.size())
Polygons shrinked_upper_slice = (idx_layer + 1) < m_layers.size() ? object_volume = to_polygons(intersection_ex_by_piece(m_layers[idx_layer - 1]->lslices, to_polygons(m_layers[idx_layer + 1]->lslices)));
to_polygons(m_layers[idx_layer + 1]->lslices) :
Polygons{};
object_volume = intersection(shrinked_bottom_slice, shrinked_upper_slice);
internal_volume = closing(polygonsInternal, SCALED_EPSILON); internal_volume = closing(polygonsInternal, SCALED_EPSILON);
} }
@@ -2579,15 +2664,34 @@ void PrintObject::discover_vertical_shells()
// the in-model condition is there due to small sloping surfaces, e.g. top of the hull of the benchy // the in-model condition is there due to small sloping surfaces, e.g. top of the hull of the benchy
// 2. the area does not fully cover an internal polygon // 2. the area does not fully cover an internal polygon
// This is there mainly for a very thin parts, where the solid layers would be missing if the part area is quite small // This is there mainly for a very thin parts, where the solid layers would be missing if the part area is quite small
// Both tests below compare a small piece against the whole layer. Done literally, that is
// quadratic in the number of pieces, which is what a layer split up by colour painting has,
// so each is restricted to the part of the layer near the piece with an identical result:
// object_volume is clipped to the piece's box, and only the internal polygons whose box meets
// the expanded piece take part in the count, since the others pass through the difference
// unchanged and add the same number to both sides of it.
std::vector<BoundingBox> internal_bboxes;
internal_bboxes.reserve(internal_volume.size());
for (const Polygon &poly : internal_volume)
internal_bboxes.emplace_back(get_extents(poly));
regularized_shell.erase(std::remove_if(regularized_shell.begin(), regularized_shell.end(), regularized_shell.erase(std::remove_if(regularized_shell.begin(), regularized_shell.end(),
[&internal_volume, &min_perimeter_infill_spacing, [&internal_volume, &internal_bboxes, &min_perimeter_infill_spacing,
&object_volume](const ExPolygon &p) { &object_volume](const ExPolygon &p) {
return (p.area() < min_perimeter_infill_spacing * scaled(1.5) || const bool small = p.area() < min_perimeter_infill_spacing * scaled(1.5) ||
(p.area() < min_perimeter_infill_spacing * scaled(8.0) && (p.area() < min_perimeter_infill_spacing * scaled(8.0) &&
diff(to_polygons(p), object_volume).empty())) && diff(to_polygons(p),
diff(internal_volume, ClipperUtils::clip_clipper_polygons_with_subject_bbox(
expand(to_polygons(p), min_perimeter_infill_spacing)) object_volume, get_extents(p).inflated(SCALED_EPSILON)))
.size() >= internal_volume.size(); .empty());
if (!small)
return false;
const Polygons expanded = expand(to_polygons(p), min_perimeter_infill_spacing);
const BoundingBox bbox = get_extents(expanded);
Polygons nearby;
for (size_t i = 0; i < internal_volume.size(); ++i)
if (internal_bboxes[i].overlap(bbox))
nearby.emplace_back(internal_volume[i]);
return diff(nearby, expanded).size() >= nearby.size();
}), }),
regularized_shell.end()); regularized_shell.end());
} }
@@ -2609,8 +2713,9 @@ void PrintObject::discover_vertical_shells()
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
// Trim the internal & internalvoid by the shell. // Trim the internal & internalvoid by the shell.
Slic3r::ExPolygons new_internal = diff_ex(layerm->fill_surfaces.filter_by_type(stInternal), regularized_shell); const Polygons regularized_shell_polygons = to_polygons(regularized_shell);
Slic3r::ExPolygons new_internal_void = diff_ex(layerm->fill_surfaces.filter_by_type(stInternalVoid), regularized_shell); Slic3r::ExPolygons new_internal = diff_ex_by_piece(to_expolygons(layerm->fill_surfaces.filter_by_type(stInternal)), regularized_shell_polygons);
Slic3r::ExPolygons new_internal_void = diff_ex_by_piece(to_expolygons(layerm->fill_surfaces.filter_by_type(stInternalVoid)), regularized_shell_polygons);
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING #ifdef SLIC3R_DEBUG_SLICE_PROCESSING
{ {
@@ -2637,7 +2742,15 @@ void PrintObject::discover_vertical_shells()
layerm->export_region_fill_surfaces_to_svg_debug("3_discover_vertical_shells-final"); layerm->export_region_fill_surfaces_to_svg_debug("3_discover_vertical_shells-final");
} }
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
} // for each region }; // for each region
if (top_bottom_surfaces_all_regions)
// Nothing is shared between the regions, and a layer cut through a fine relief takes far longer than the others,
// so they run next to each other instead of one after another.
tbb::parallel_for(size_t(0), this->num_printing_regions(), process_region);
else
// Here every region fills the one top/bottom cache with its own surfaces first.
for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id)
process_region(region_id);
} // void PrintObject::discover_vertical_shells() } // void PrintObject::discover_vertical_shells()
// #define DEBUG_BRIDGE_OVER_INFILL // #define DEBUG_BRIDGE_OVER_INFILL
@@ -3158,6 +3271,16 @@ void PrintObject::bridge_over_infill()
vertical_lines[i].b = Point{x, y_max}; vertical_lines[i].b = Point{x, y_max};
} }
// The vertical lines only span the bridged area's x range, so anchors entirely outside it can never be
// hit. Leaving them out gives the same intersections without building a tree over the whole layer's
// boundary for every bridge.
const coord_t scan_x_min = bb_x.min.x();
const coord_t scan_x_max = bb_x.min.x() + coord_t(n_vlines) * scan_spacing;
anchors.erase(std::remove_if(anchors.begin(), anchors.end(),
[scan_x_min, scan_x_max](const Line &l) {
return std::max(l.a.x(), l.b.x()) < scan_x_min || std::min(l.a.x(), l.b.x()) > scan_x_max;
}),
anchors.end());
auto anchors_and_walls_tree = AABBTreeLines::LinesDistancer<Line>{std::move(anchors)}; auto anchors_and_walls_tree = AABBTreeLines::LinesDistancer<Line>{std::move(anchors)};
auto bridged_area_tree = AABBTreeLines::LinesDistancer<Line>{to_lines(bridged_area)}; auto bridged_area_tree = AABBTreeLines::LinesDistancer<Line>{to_lines(bridged_area)};
@@ -3402,28 +3525,62 @@ void PrintObject::bridge_over_infill()
std::vector<CandidateSurface> expanded_surfaces; std::vector<CandidateSurface> expanded_surfaces;
expanded_surfaces.reserve(surfaces_by_layer[lidx].size()); expanded_surfaces.reserve(surfaces_by_layer[lidx].size());
// The expanded fill boundary depends only on the bridging flow, and total_fill_area is not
// modified below, so build it once per spacing rather than once per candidate. A layer split
// into many candidates (e.g. by colour painting) otherwise repeats a layer-wide offset for each.
std::map<coord_t, Polylines> boundary_by_spacing;
// expansion_area is a clean, non-overlapping set, so uniting it with a bridge or cutting a bridge
// out of it only changes the polygons near that bridge. The rest are passed through untouched
// instead of being fed to Clipper with the whole layer again for every candidate.
// Not `near`/`far`: the Windows headers still define those as macros, and they expand to
// nothing, which turns the declaration below into an empty one.
const auto split_near = [](const Polygons &polys, const BoundingBox &bbox, Polygons &rest) {
Polygons nearby;
for (const Polygon &p : polys)
(get_extents(p).overlap(bbox) ? nearby : rest).emplace_back(p);
return nearby;
};
for (const CandidateSurface &candidate : surfaces_by_layer[lidx]) { for (const CandidateSurface &candidate : surfaces_by_layer[lidx]) {
const auto &region_config = candidate.region->region().config(); const auto &region_config = candidate.region->region().config();
const bool turning_pattern = region_config.sparse_infill_pattern == ipHilbertCurve || const bool turning_pattern = region_config.sparse_infill_pattern == ipHilbertCurve ||
region_config.sparse_infill_pattern == ipOctagramSpiral; region_config.sparse_infill_pattern == ipOctagramSpiral;
const Flow &flow = candidate.region->bridging_flow(frSolidInfill, true); const Flow &flow = candidate.region->bridging_flow(frSolidInfill, true);
Polygons area_to_be_bridge = expand(candidate.new_polys, flow.scaled_spacing()); Polygons area_to_be_bridge = expand(candidate.new_polys, flow.scaled_spacing());
area_to_be_bridge = intersection(area_to_be_bridge, deep_infill_area); // deep_infill_area and internal_unsupported_area cover the whole layer; only their part under
// this candidate can change the results, so they are clipped to its box first.
if (!area_to_be_bridge.empty())
area_to_be_bridge = intersection(area_to_be_bridge,
ClipperUtils::clip_clipper_polygons_with_subject_bbox(
deep_infill_area, get_extents(area_to_be_bridge).inflated(SCALED_EPSILON)));
area_to_be_bridge.erase(std::remove_if(area_to_be_bridge.begin(), area_to_be_bridge.end(), area_to_be_bridge.erase(std::remove_if(area_to_be_bridge.begin(), area_to_be_bridge.end(),
[internal_unsupported_area](const Polygon &p) { [&internal_unsupported_area](const Polygon &p) {
return intersection({p}, internal_unsupported_area).empty(); return intersection({p}, ClipperUtils::clip_clipper_polygons_with_subject_bbox(
internal_unsupported_area,
get_extents(p).inflated(SCALED_EPSILON)))
.empty();
}), }),
area_to_be_bridge.end()); area_to_be_bridge.end());
Polygons limiting_area = union_(area_to_be_bridge, expansion_area);
if (area_to_be_bridge.empty()) if (area_to_be_bridge.empty())
continue; continue;
Polylines boundary_plines = to_polylines(expand(total_fill_area, 1.3 * flow.scaled_spacing())); Polygons limiting_area;
const Polygons near_expansion = split_near(expansion_area, get_extents(area_to_be_bridge).inflated(SCALED_EPSILON),
limiting_area);
const size_t num_far = limiting_area.size();
append(limiting_area, union_(area_to_be_bridge, near_expansion));
auto boundary_it = boundary_by_spacing.find(flow.scaled_spacing());
if (boundary_it == boundary_by_spacing.end())
boundary_it = boundary_by_spacing
.emplace(flow.scaled_spacing(), to_polylines(expand(total_fill_area, 1.3 * flow.scaled_spacing())))
.first;
Polylines boundary_plines = boundary_it->second;
{ {
Polylines limiting_plines = to_polylines(expand(limiting_area, 0.3*flow.spacing())); // The sub-unit offset (spacing is in mm) still re-unites touching polygons by the bridge, which the anchors depend on.
Polylines limiting_plines = to_polylines(Polygons(limiting_area.begin(), limiting_area.begin() + num_far));
append(limiting_plines, to_polylines(expand(Polygons(limiting_area.begin() + num_far, limiting_area.end()), 0.3 * flow.spacing())));
boundary_plines.insert(boundary_plines.end(), limiting_plines.begin(), limiting_plines.end()); boundary_plines.insert(boundary_plines.end(), limiting_plines.begin(), limiting_plines.end());
} }
@@ -3498,8 +3655,11 @@ void PrintObject::bridge_over_infill()
{ {
bool reconstruct = false; bool reconstruct = false;
Polygons tmp_expanded_area = expand(bridging_area, 3.0 * flow.scaled_spacing()); Polygons tmp_expanded_area = expand(bridging_area, 3.0 * flow.scaled_spacing());
const BoundingBox tmp_expanded_bbox = get_extents(tmp_expanded_area);
for (const CandidateSurface &s : expanded_surfaces) { for (const CandidateSurface &s : expanded_surfaces) {
if (!intersection(s.new_polys, tmp_expanded_area).empty()) { // Surfaces whose boxes miss each other cannot intersect, which is most pairs on a busy layer.
if (get_extents(s.new_polys).overlap(tmp_expanded_bbox) &&
!intersection(s.new_polys, tmp_expanded_area).empty()) {
bridging_angle = s.bridge_angle; bridging_angle = s.bridge_angle;
reconstruct = true; reconstruct = true;
break; break;
@@ -3523,10 +3683,20 @@ void PrintObject::bridge_over_infill()
bridging_area = union_(bridging_area, construct_anchored_polygon(bridging_area, to_lines(boundary_plines), flow, bridging_area = union_(bridging_area, construct_anchored_polygon(bridging_area, to_lines(boundary_plines), flow,
bridging_angle, scan_spacing, true)); bridging_angle, scan_spacing, true));
} }
bridging_area = intersection(bridging_area, limiting_area); // Each of these meets one bridge with the whole layer, so the layer side is first cut down to the
bridging_area = intersection(bridging_area, total_fill_area); // bridge's box (and expansion_area split as above); the result is the same.
bridging_area = diff(bridging_area, total_top_area); if (!bridging_area.empty()) {
expansion_area = diff(expansion_area, bridging_area); const BoundingBox bridging_bbox = get_extents(bridging_area).inflated(SCALED_EPSILON);
bridging_area = intersection(bridging_area, ClipperUtils::clip_clipper_polygons_with_subject_bbox(limiting_area, bridging_bbox));
bridging_area = intersection(bridging_area, ClipperUtils::clip_clipper_polygons_with_subject_bbox(total_fill_area, bridging_bbox));
bridging_area = diff(bridging_area, ClipperUtils::clip_clipper_polygons_with_subject_bbox(total_top_area, bridging_bbox));
}
if (!bridging_area.empty()) {
Polygons kept;
const Polygons cut = split_near(expansion_area, get_extents(bridging_area).inflated(SCALED_EPSILON), kept);
append(kept, diff(cut, bridging_area));
expansion_area = std::move(kept);
}
#ifdef DEBUG_BRIDGE_OVER_INFILL #ifdef DEBUG_BRIDGE_OVER_INFILL
debug_draw(std::to_string(lidx) + "_" + std::to_string(cluster_idx) + "_" + std::to_string(job_idx) + "_" + "_expanded_bridging" + std::to_string(r), debug_draw(std::to_string(lidx) + "_" + std::to_string(cluster_idx) + "_" + std::to_string(job_idx) + "_" + "_expanded_bridging" + std::to_string(r),
+1 -1
View File
@@ -929,9 +929,9 @@ public:
::fread(&y, sizeof(coord_t), 1, file); ::fread(&y, sizeof(coord_t), 1, file);
poly.points.emplace_back(Point(x * scale, y * scale)); poly.points.emplace_back(Point(x * scale, y * scale));
} }
printf("Polygon %d, area: %lf\n", i, area(poly.points));
if (which == -1 || which == i) if (which == -1 || which == i)
m_support_polygons_deserialized.emplace_back(std::move(poly)); m_support_polygons_deserialized.emplace_back(std::move(poly));
printf("Polygon %d, area: %lf\n", i, area(poly.points));
} }
::fread(&n_polygons, 4, 1, file); ::fread(&n_polygons, 4, 1, file);
m_trimming_polygons_deserialized.reserve(n_polygons); m_trimming_polygons_deserialized.reserve(n_polygons);
+31 -1
View File
@@ -854,11 +854,41 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/)
if (is_auto(stype) && config_detect_sharp_tails) if (is_auto(stype) && config_detect_sharp_tails)
{ {
// BBS detect sharp tail // BBS detect sharp tail
// Each island is tested only against the lower islands whose box meets its own; overlaps() tries
// every pair, which is quadratic in the island counts of the two layers.
std::vector<BoundingBox> lower_bboxes;
lower_bboxes.reserve(lower_polys.size());
for (const ExPolygon &lower : lower_polys)
lower_bboxes.emplace_back(get_extents(lower));
for (const ExPolygon& expoly : curr_polys) { for (const ExPolygon& expoly : curr_polys) {
bool is_sharp_tail = false; bool is_sharp_tail = false;
// 1. nothing below // 1. nothing below
// this is a sharp tail region if it's floating and non-ignorable // this is a sharp tail region if it's floating and non-ignorable
if (!overlaps(offset_ex(expoly, 0.1 * extrusion_width_scaled), lower_polys)) { const ExPolygons expanded = offset_ex(expoly, 0.1 * extrusion_width_scaled);
const BoundingBox bbox = get_extents(expanded);
ExPolygons lower_nearby;
for (size_t i = 0; i < lower_polys.size(); ++i)
if (lower_bboxes[i].overlap(bbox))
lower_nearby.emplace_back(lower_polys[i]);
// As overlaps(expanded, lower_nearby), with each lower island cut to the island's box first:
// below a fine relief the lower layer is a few islands with thousands of holes, and the whole
// of that boundary would otherwise be intersected once per island above.
const auto overlaps_nearby = [&]() {
for (const ExPolygon &a : expanded) {
if (a.empty())
continue;
const BoundingBox a_bbox = get_extents(a);
for (const ExPolygon &b : lower_nearby) {
if (b.empty() || !get_extents(b).overlap(a_bbox))
continue;
const Polygons b_near = ClipperUtils::clip_clipper_polygons_with_subject_bbox(b, a_bbox.inflated(SCALED_EPSILON));
if (!intersection_pl(to_polylines(b_near), a).empty() || b.contains(a.contour.points.front()))
return true;
}
}
return false;
};
if (!overlaps_nearby()) {
is_sharp_tail = !offset_ex(expoly, -0.1 * extrusion_width_scaled).empty(); is_sharp_tail = !offset_ex(expoly, -0.1 * extrusion_width_scaled).empty();
} }
+2 -2
View File
@@ -61,7 +61,7 @@ public:
thickness(other.thickness), thickness_layers(other.thickness_layers), thickness(other.thickness), thickness_layers(other.thickness_layers),
bridge_angle(other.bridge_angle), extra_perimeters(other.extra_perimeters) bridge_angle(other.bridge_angle), extra_perimeters(other.extra_perimeters)
{}; {};
Surface(Surface &&rhs) Surface(Surface &&rhs) noexcept
: surface_type(rhs.surface_type), expolygon(std::move(rhs.expolygon)), : surface_type(rhs.surface_type), expolygon(std::move(rhs.expolygon)),
thickness(rhs.thickness), thickness_layers(rhs.thickness_layers), thickness(rhs.thickness), thickness_layers(rhs.thickness_layers),
bridge_angle(rhs.bridge_angle), extra_perimeters(rhs.extra_perimeters) bridge_angle(rhs.bridge_angle), extra_perimeters(rhs.extra_perimeters)
@@ -87,7 +87,7 @@ public:
return *this; return *this;
} }
Surface& operator=(Surface &&rhs) Surface& operator=(Surface &&rhs) noexcept
{ {
surface_type = rhs.surface_type; surface_type = rhs.surface_type;
expolygon = std::move(rhs.expolygon); expolygon = std::move(rhs.expolygon);
+4 -4
View File
@@ -162,10 +162,10 @@ inline void append(std::vector<T, Alloc> &dest, std::vector<T, Alloc> &&src)
{ {
if (dest.empty()) if (dest.empty())
dest = std::move(src); dest = std::move(src);
else { else
dest.reserve(dest.size() + src.size()); // insert() grows the capacity geometrically; reserving exactly the new size reallocated on every call, which
std::move(std::begin(src), std::end(src), std::back_inserter(dest)); // made appending piece by piece quadratic.
} dest.insert(dest.end(), std::make_move_iterator(src.begin()), std::make_move_iterator(src.end()));
src.clear(); src.clear();
src.shrink_to_fit(); src.shrink_to_fit();
} }
+1 -3
View File
@@ -6735,10 +6735,8 @@ void GUI_App::reload_settings()
tab->reload_config(); tab->reload_config();
tab->update_changed_ui(); tab->update_changed_ui();
} }
if (plater_) { if (plater_)
plater_->sidebar().update_all_preset_comboboxes(); plater_->sidebar().update_all_preset_comboboxes();
plater_->normalize_bed_types(false);
}
}; };
if (is_main_thread_active()) if (is_main_thread_active())
refresh_synced_ui(); refresh_synced_ui();
@@ -5151,7 +5151,7 @@ void GLGizmoTextureDisplacement::render_debug_stage_panel(ModelVolume *mv)
m_imgui->scaled(20.f)); m_imgui->scaled(20.f));
ImGui::SameLine(); ImGui::SameLine();
m_imgui->bbl_checkbox(_L("Check topology"), m_debug_check_topology); ImGui::Checkbox(_u8L("Check topology").c_str(), &m_debug_check_topology);
if (ImGui::IsItemHovered()) if (ImGui::IsItemHovered())
m_imgui->tooltip(_u8L("Count open and non-manifold edges after each stage, which is how a stage " m_imgui->tooltip(_u8L("Count open and non-manifold edges after each stage, which is how a stage "
"that tore the mesh is spotted. It scans every edge, so it adds noticeably " "that tore the mesh is spotted. It scans every edge, so it adds noticeably "
@@ -5341,8 +5341,6 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
GizmoImguiBegin(get_name(), flags); GizmoImguiBegin(get_name(), flags);
ensure_panel_icons(); ensure_panel_icons();
process_uv_commands(); // clicks from the UV editor pane, run here where the GL context is current process_uv_commands(); // clicks from the UV editor pane, run here where the GL context is current
const float previous_body_h = m_panel_body_h;
const float previous_footer_h = m_panel_footer_h;
// Pinned every frame while Standard is active, so what Preview shows is always what Bake will do. // Pinned every frame while Standard is active, so what Preview shows is always what Bake will do.
if (!pro_mode() && apply_standard_mode_presets(mv)) if (!pro_mode() && apply_standard_mode_presets(mv))
@@ -5364,16 +5362,20 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
const float card_pad = std::round(m_imgui->scaled(0.55f)); const float card_pad = std::round(m_imgui->scaled(0.55f));
const float wrap_w = m_imgui->scaled(20.f); const float wrap_w = m_imgui->scaled(20.f);
const ImVec4 orca = ImGuiWrapper::COL_ORCA; const ImVec4 orca = ImGuiWrapper::COL_ORCA;
const ImVec4 col_link = ImGuiWrapper::COL_ORCA; const ImVec4 col_link = dark ? ImVec4(0.30f, 0.71f, 0.67f, 1.f) : ImVec4(0.f, 0.47f, 0.42f, 1.f);
const ImVec4 col_frame = ImGui::GetStyleColorVec4(ImGuiCol_Separator); const ImVec4 col_frame = dark ? ImVec4(0.212f, 0.212f, 0.235f, 1.f) : ImVec4(0.808f, 0.808f, 0.808f, 1.f);
const ImU32 col_card = ImGui::GetColorU32(ImGuiCol_Text, dark ? 0.045f : 0.047f); const ImU32 col_card = dark ? IM_COL32(255, 255, 255, 10) : IM_COL32(0, 0, 0, 12);
const ImU32 col_line = ImGui::GetColorU32(ImGuiCol_Text, dark ? 0.08f : 0.09f); const ImU32 col_line = dark ? IM_COL32(255, 255, 255, 18) : IM_COL32(0, 0, 0, 23);
const ImU32 col_sep = ImGui::GetColorU32(ImGuiCol_Separator); const ImU32 col_sep = ImGui::GetColorU32(ImGuiCol_Separator);
// Combo drop-downs otherwise inherit ImGui's near-black default popup background; under the light
// theme that leaves the dark item text unreadable ("the dropbox is black"). Pushed only around each
// Combo below (never around a tooltip, whose own near-black default is what makes it readable).
const ImVec4 combo_popup_bg = dark ? ImVec4(0.18f, 0.18f, 0.19f, 1.f) : ImVec4(0.93f, 0.93f, 0.93f, 1.f);
const auto scoped_combo = [&](const char *id, int *v, const char *const items[], int n) { const auto scoped_combo = [&](const char *id, int *v, const char *const items[], int n) {
ImGuiWrapper::push_combo_style(m_parent.get_scale()); ImGui::PushStyleColor(ImGuiCol_PopupBg, combo_popup_bg);
const bool changed = ImGui::Combo(id, v, items, n); const bool changed = ImGui::Combo(id, v, items, n);
ImGuiWrapper::pop_combo_style(); ImGui::PopStyleColor();
return changed; return changed;
}; };
const auto hover_tip = [&](const auto &text) { const auto hover_tip = [&](const auto &text) {
@@ -5780,10 +5782,10 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
wf_toggle = true; wf_toggle = true;
const std::string auto_label = _u8L("Auto"); const std::string auto_label = _u8L("Auto");
const float auto_w = frame_h * 0.78f /*ratio from BBLCheckbox*/ + style.ItemInnerSpacing.x + ImGui::CalcTextSize(auto_label.c_str()).x; const float auto_w = frame_h + style.ItemInnerSpacing.x + ImGui::CalcTextSize(auto_label.c_str()).x;
ImGui::SameLine(); ImGui::SameLine();
ImGui::SetCursorPosX(std::max(ImGui::GetCursorPosX(), ImGui::GetWindowContentRegionMax().x - auto_w)); ImGui::SetCursorPosX(std::max(ImGui::GetCursorPosX(), ImGui::GetWindowContentRegionMax().x - auto_w));
if (m_imgui->bbl_checkbox(wxString::FromUTF8(auto_label) + "##auto_update", m_auto_update) && m_auto_update) if (ImGui::Checkbox((auto_label + "##auto_update").c_str(), &m_auto_update) && m_auto_update)
rebuild_preview(); // catch up anything that changed while it was off rebuild_preview(); // catch up anything that changed while it was off
hover_tip(_u8L("Rebuilds the preview as soon as anything changes. Turn it off on a heavy model if painting " hover_tip(_u8L("Rebuilds the preview as soon as anything changes. Turn it off on a heavy model if painting "
"or dragging a slider starts to stutter - the preview then waits until you let go.")); "or dragging a slider starts to stutter - the preview then waits until you let go."));
@@ -6018,7 +6020,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
"costs fine detail.")); "costs fine detail."));
// Edge fade: the relief flattens toward the boundary of the painted area. // Edge fade: the relief flattens toward the boundary of the painted area.
m_preview_params_dirty |= m_imgui->bbl_checkbox(_L("Edge fade") + "##edge_smoothing", layer.edge_smoothing); m_preview_params_dirty |= ImGui::Checkbox((_u8L("Edge fade") + "##edge_smoothing").c_str(), &layer.edge_smoothing);
hover_tip(_u8L("Flattens the relief as it approaches the edge of the painted area, so it " hover_tip(_u8L("Flattens the relief as it approaches the edge of the painted area, so it "
"blends into the bare surface instead of stopping at a step.")); "blends into the bare surface instead of stopping at a step."));
ImGui::SameLine(); ImGui::SameLine();
@@ -6033,7 +6035,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
// Invert and Colours share a row. // Invert and Colours share a row.
{ {
const float x0 = ImGui::GetCursorPosX(); const float x0 = ImGui::GetCursorPosX();
m_preview_params_dirty |= m_imgui->bbl_checkbox(_L("Invert"), layer.invert); m_preview_params_dirty |= ImGui::Checkbox(_u8L("Invert").c_str(), &layer.invert);
hover_tip(_u8L("Turns the relief inside out: what stood out is cut in, and the other way " hover_tip(_u8L("Turns the relief inside out: what stood out is cut in, and the other way "
"round. The same as using a negative of the image.")); "round. The same as using a negative of the image."));
ImGui::SameLine(); ImGui::SameLine();
@@ -6045,7 +6047,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
const bool has_color = decode_height_texture(layer).has_color(); const bool has_color = decode_height_texture(layer).has_color();
bool color_enabled = layer.color_enabled && has_color; bool color_enabled = layer.color_enabled && has_color;
m_imgui->disabled_begin(!has_color); m_imgui->disabled_begin(!has_color);
if (m_imgui->bbl_checkbox(_L("Colours"), color_enabled)) { if (ImGui::Checkbox(_u8L("Colours").c_str(), &color_enabled)) {
layer.color_enabled = color_enabled; layer.color_enabled = color_enabled;
m_preview_params_dirty = true; m_preview_params_dirty = true;
} }
@@ -6063,7 +6065,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
// under whichever layer turned colour on. // under whichever layer turned colour on.
if (color_enabled) { if (color_enabled) {
TextureDisplacementOptions &opts = mv->texture_displacement_options; TextureDisplacementOptions &opts = mv->texture_displacement_options;
if (m_imgui->bbl_checkbox(_L("Mix filaments"), opts.color_mix_enabled)) if (ImGui::Checkbox(_u8L("Mix filaments").c_str(), &opts.color_mix_enabled))
m_preview_params_dirty = true; m_preview_params_dirty = true;
hover_tip(_u8L("Interleaves two filaments to fake the colours in between, so a handful " hover_tip(_u8L("Interleaves two filaments to fake the colours in between, so a handful "
"of filaments can cover a photo or a gradient. An image of flat colours " "of filaments can cover a photo or a gradient. An image of flat colours "
@@ -6182,7 +6184,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
"a slide projector. Faces turned away from you stretch, so line the view up with " "a slide projector. Faces turned away from you stretch, so line the view up with "
"the surface you care about first.")); "the surface you care about first."));
if (m_imgui->bbl_checkbox(_L("Project only on visible"), m_project_only_visible)) { if (ImGui::Checkbox(_u8L("Project only on visible").c_str(), &m_project_only_visible)) {
if (m_project_only_visible && select_visible_faces() == 0) if (m_project_only_visible && select_visible_faces() == 0)
show_error(nullptr, _u8L("Nothing is visible from this angle - turn the model to face the " show_error(nullptr, _u8L("Nothing is visible from this angle - turn the model to face the "
"part you want to project onto.")); "part you want to project onto."));
@@ -6193,7 +6195,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
"behind anything - and projects onto those. Replaces what the layer had painted.")); "behind anything - and projects onto those. Replaces what the layer had painted."));
bool projector_open = m_projector_frame != nullptr && m_projector_frame->IsShown(); bool projector_open = m_projector_frame != nullptr && m_projector_frame->IsShown();
if (m_imgui->bbl_checkbox(_L("Projection frame"), projector_open)) if (ImGui::Checkbox(_u8L("Projection frame").c_str(), &projector_open))
show_projector(projector_open); show_projector(projector_open);
hover_tip(_u8L("Opens a window you drag over the model. Whatever you can see through it is what " hover_tip(_u8L("Opens a window you drag over the model. Whatever you can see through it is what "
"gets the texture, and its border becomes the edge of the projection.")); "gets the texture, and its border becomes the edge of the projection."));
@@ -6234,7 +6236,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
// Tile, and how it repeats. // Tile, and how it repeats.
{ {
const float x0 = ImGui::GetCursorPosX(); const float x0 = ImGui::GetCursorPosX();
m_preview_params_dirty |= m_imgui->bbl_checkbox(_L("Tile") + "##tile_enabled", layer.tile_enabled); m_preview_params_dirty |= ImGui::Checkbox((_u8L("Tile") + "##tile_enabled").c_str(), &layer.tile_enabled);
hover_tip(_u8L("Repeats the texture across the painted area. Off places one copy, like a decal, " hover_tip(_u8L("Repeats the texture across the painted area. Off places one copy, like a decal, "
"at the size set by Tile size.")); "at the size set by Tile size."));
ImGui::SameLine(); ImGui::SameLine();
@@ -6422,7 +6424,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
const ImVec2 mn(mouse.x - inner - 0.5f * grip_w, mouse.y - 0.5f * row_h); const ImVec2 mn(mouse.x - inner - 0.5f * grip_w, mouse.y - 0.5f * row_h);
const ImVec2 mx(mn.x + (content_rx - list_x), mn.y + row_h); const ImVec2 mx(mn.x + (content_rx - list_x), mn.y + row_h);
fg->AddRectFilled(ImVec2(mn.x + 2.f, mn.y + 4.f), ImVec2(mx.x + 2.f, mx.y + 4.f), IM_COL32(0, 0, 0, 70), rounding); fg->AddRectFilled(ImVec2(mn.x + 2.f, mn.y + 4.f), ImVec2(mx.x + 2.f, mx.y + 4.f), IM_COL32(0, 0, 0, 70), rounding);
fg->AddRectFilled(mn, mx, ImGui::GetColorU32(ImGuiCol_WindowBg, 245.f / 255.f), rounding); fg->AddRectFilled(mn, mx, dark ? IM_COL32(0x3a, 0x3a, 0x40, 245) : IM_COL32(255, 255, 255, 245), rounding);
fg->AddRect(mn, mx, ImGui::GetColorU32(orca), rounding, 0, 1.5f); fg->AddRect(mn, mx, ImGui::GetColorU32(orca), rounding, 0, 1.5f);
float x = mn.x + inner; float x = mn.x + inner;
@@ -6461,7 +6463,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
ImGui::Separator(); ImGui::Separator();
heading(_L("Subdivision")); heading(_L("Subdivision"));
if (m_imgui->bbl_checkbox(_L("Only painted area (adaptive)"), m_subdivide_adaptive)) { if (ImGui::Checkbox(_u8L("Only painted area (adaptive)").c_str(), &m_subdivide_adaptive)) {
if (m_subdivide_editing) if (m_subdivide_editing)
rebuild_subdivide_preview(); // switch the wireframe between the uniform and adaptive result rebuild_subdivide_preview(); // switch the wireframe between the uniform and adaptive result
m_parent.set_as_dirty(); m_parent.set_as_dirty();
@@ -6492,7 +6494,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
m_parent.set_as_dirty(); m_parent.set_as_dirty();
}; };
if (m_imgui->bbl_checkbox(_L("Follow texture detail"), m_subdivide_feature)) if (ImGui::Checkbox(_u8L("Follow texture detail").c_str(), &m_subdivide_feature))
preview_live(); preview_live();
hover_tip(_u8L("Spends the triangles where the texture actually bends - packed along ridges and edges, " hover_tip(_u8L("Spends the triangles where the texture actually bends - packed along ridges and edges, "
"sparse over flat ground - instead of spreading them evenly. The same detail for fewer " "sparse over flat ground - instead of spreading them evenly. The same detail for fewer "
@@ -6619,7 +6621,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
hover_tip(_u8L("Triangle size the whole model is rebuilt with, in millimetres. Displacement works best on an " hover_tip(_u8L("Triangle size the whole model is rebuilt with, in millimetres. Displacement works best on an "
"even mesh; this is what makes one out of an uneven import.")); "even mesh; this is what makes one out of an uneven import."));
m_imgui->bbl_checkbox(_L("Keep sharp edges") + "##remesh_sharp", m_remesh_keep_sharp_edges); ImGui::Checkbox((_u8L("Keep sharp edges") + "##remesh_sharp").c_str(), &m_remesh_keep_sharp_edges);
hover_tip(_u8L("Holds hard edges and open borders in place while the rest is remeshed. Without it " hover_tip(_u8L("Holds hard edges and open borders in place while the rest is remeshed. Without it "
"the remesher slides vertices along the surface and rounds every crisp edge off - " "the remesher slides vertices along the surface and rounds every crisp edge off - "
"a cube comes back with wobbly edges.")); "a cube comes back with wobbly edges."));
@@ -6646,12 +6648,12 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
ImGui::Separator(); ImGui::Separator();
heading(_L("Result")); heading(_L("Result"));
m_preview_params_dirty |= m_imgui->bbl_checkbox(_L("Displace up to the border"), opts.displace_border); m_preview_params_dirty |= ImGui::Checkbox(_u8L("Displace up to the border").c_str(), &opts.displace_border);
hover_tip(_u8L("Lets the relief run right to the edge of the painted area. Turn it off to hold that " hover_tip(_u8L("Lets the relief run right to the edge of the painted area. Turn it off to hold that "
"outer ring flat, which keeps the displacement strictly inside your paint but flattens " "outer ring flat, which keeps the displacement strictly inside your paint but flattens "
"the pattern at the border.")); "the pattern at the border."));
m_preview_params_dirty |= m_imgui->bbl_checkbox(_L("Smooth result"), opts.smooth_enabled); m_preview_params_dirty |= ImGui::Checkbox(_u8L("Smooth result").c_str(), &opts.smooth_enabled);
hover_tip(_u8L("Smooths the geometry after the texture has been applied, to take the hard steps out of a " hover_tip(_u8L("Smooths the geometry after the texture has been applied, to take the hard steps out of a "
"low-resolution image. Only what the displacement moved is touched. The Smoothing slider " "low-resolution image. Only what the displacement moved is touched. The Smoothing slider "
"on a layer is a different thing: it blurs the image before it is used.")); "on a layer is a different thing: it blurs the image before it is used."));
@@ -6668,7 +6670,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
hover_tip(_u8L("How many smoothing passes to run. More passes spread the smoothing further across " hover_tip(_u8L("How many smoothing passes to run. More passes spread the smoothing further across "
"the surface; Strength decides how much each one moves.")); "the surface; Strength decides how much each one moves."));
m_preview_params_dirty |= m_imgui->bbl_checkbox(_L("Ignore outer ring"), opts.smooth_skip_border); m_preview_params_dirty |= ImGui::Checkbox(_u8L("Ignore outer ring").c_str(), &opts.smooth_skip_border);
hover_tip(_u8L("Keeps the outer ring of the painted area out of the smoothing. Its neighbours " hover_tip(_u8L("Keeps the outer ring of the painted area out of the smoothing. Its neighbours "
"outside the paint never move, so smoothing it drags the relief down and leaves the " "outside the paint never move, so smoothing it drags the relief down and leaves the "
"pattern half-melted at the border.")); "pattern half-melted at the border."));
@@ -6693,7 +6695,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
// The classic path is the opt-in: the one-run pipeline is the default, and its resolution // The classic path is the opt-in: the one-run pipeline is the default, and its resolution
// control lives in the footer next to Bake (see below). // control lives in the footer next to Bake (see below).
bool classic = !opts.pipeline_v2; bool classic = !opts.pipeline_v2;
if (m_imgui->bbl_checkbox(_L("Experimental: classic bake pipeline"), classic)) { if (ImGui::Checkbox(_u8L("Experimental: classic bake pipeline").c_str(), &classic)) {
opts.pipeline_v2 = !classic; opts.pipeline_v2 = !classic;
m_preview_params_dirty = true; m_preview_params_dirty = true;
} }
@@ -6722,13 +6724,13 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
if (SHOW_PIPELINE_DEV_CONTROLS && opts.pipeline_v2) { if (SHOW_PIPELINE_DEV_CONTROLS && opts.pipeline_v2) {
// Keeping the relief above the plate is not a checkbox: it is unconditional, in both pipelines // Keeping the relief above the plate is not a checkbox: it is unconditional, in both pipelines
// (see build_texture_displacement()). // (see build_texture_displacement()).
m_preview_params_dirty |= m_imgui->bbl_checkbox(_L("Align mesh to texture edges"), opts.v2_relocate); m_preview_params_dirty |= ImGui::Checkbox(_u8L("Align mesh to texture edges").c_str(), &opts.v2_relocate);
hover_tip(_u8L("Slide vertices sideways onto the edges in the texture before displacing them. " hover_tip(_u8L("Slide vertices sideways onto the edges in the texture before displacing them. "
"Displacement can only move vertices up and down, so without this a sharp step " "Displacement can only move vertices up and down, so without this a sharp step "
"in the image lands wherever the triangles happen to be and comes out as a " "in the image lands wherever the triangles happen to be and comes out as a "
"staircase. Moving the vertices onto the step first gives a straight wall at the " "staircase. Moving the vertices onto the step first gives a straight wall at the "
"same triangle count.")); "same triangle count."));
m_preview_params_dirty |= m_imgui->bbl_checkbox(_L("Clean up slivers"), opts.v2_regularize); m_preview_params_dirty |= ImGui::Checkbox(_u8L("Clean up slivers").c_str(), &opts.v2_regularize);
hover_tip(_u8L("Collapse the thin triangles refinement inherits from the model's own " hover_tip(_u8L("Collapse the thin triangles refinement inherits from the model's own "
"tessellation, before displacement samples them. A sliver's three corners " "tessellation, before displacement samples them. A sliver's three corners "
"land on three unrelated parts of the texture, which is what makes the " "land on three unrelated parts of the texture, which is what makes the "
@@ -6808,7 +6810,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
// a fixed value, seeded with the recommendation so it starts from something sensible. // a fixed value, seeded with the recommendation so it starts from something sensible.
bool auto_res = opts.v2_refine_mm <= 0.f; bool auto_res = opts.v2_refine_mm <= 0.f;
m_imgui->disabled_begin(busy); m_imgui->disabled_begin(busy);
if (m_imgui->bbl_checkbox(wxString::FromUTF8("##v2auto"), auto_res)) { if (ImGui::Checkbox("##v2auto", &auto_res)) {
if (auto_res) { if (auto_res) {
opts.v2_refine_mm = 0.f; opts.v2_refine_mm = 0.f;
opts.v2_max_triangles_k = -1; opts.v2_max_triangles_k = -1;
@@ -6906,7 +6908,11 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
const std::string bake_label = m_prepare_in_progress ? _u8L("Preparing...") : const std::string bake_label = m_prepare_in_progress ? _u8L("Preparing...") :
m_bake_in_progress ? _u8L("Baking...") : m_bake_in_progress ? _u8L("Baking...") :
into_u8(m_desc.at("bake")); into_u8(m_desc.at("bake"));
GLGizmoUtils::push_orca_button_style(); ImGui::PushStyleColor(ImGuiCol_Button, orca);
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGuiWrapper::COL_ORCA_HOVER);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, orca);
ImGui::PushStyleColor(ImGuiCol_Border, orca);
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f));
m_imgui->push_bold_font(); m_imgui->push_bold_font();
m_imgui->disabled_begin(!can_bake); m_imgui->disabled_begin(!can_bake);
if (ImGui::Button((bake_label + "##bake").c_str(), ImVec2(x0 + panel_w - ImGui::GetCursorPosX(), button_h))) { if (ImGui::Button((bake_label + "##bake").c_str(), ImVec2(x0 + panel_w - ImGui::GetCursorPosX(), button_h))) {
@@ -6919,7 +6925,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
} }
m_imgui->disabled_end(); m_imgui->disabled_end();
m_imgui->pop_bold_font(); m_imgui->pop_bold_font();
GLGizmoUtils::pop_orca_button_style(); ImGui::PopStyleColor(5);
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
m_imgui->tooltip(mv != nullptr && !mv->is_texture_displacement_painted() ? m_imgui->tooltip(mv != nullptr && !mv->is_texture_displacement_painted() ?
(m_seam_edit_mode ? _u8L("Nothing is painted yet. The UV editor's seam tool is on, so " (m_seam_edit_mode ? _u8L("Nothing is painted yet. The UV editor's seam tool is on, so "
@@ -6958,17 +6964,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float
} }
} }
m_panel_footer_h = ImGui::GetCursorScreenPos().y - footer_top; m_panel_footer_h = ImGui::GetCursorScreenPos().y - footer_top;
constexpr float resize_epsilon = 0.5f;
const float content_bottom = ImGui::GetCursorPosY() - style.ItemSpacing.y;
const float window_bottom = ImGui::GetWindowContentRegionMax().y;
const bool outer_window_needs_fit = content_bottom < window_bottom - resize_epsilon ||
(content_bottom > window_bottom + resize_epsilon &&
ImGui::GetWindowHeight() < ImGui::GetMainViewport()->Size.y -
2.f * style.DisplaySafeAreaPadding.y - resize_epsilon);
if (std::abs(m_panel_body_h - previous_body_h) > resize_epsilon ||
std::abs(m_panel_footer_h - previous_footer_h) > resize_epsilon || outer_window_needs_fit) {
m_parent.request_extra_frame();
}
GizmoImguiEnd(); GizmoImguiEnd();
ImGuiWrapper::pop_toolbar_style(); ImGuiWrapper::pop_toolbar_style();
+5 -1
View File
@@ -2656,7 +2656,11 @@ void ImGuiWrapper::push_toolbar_style(const float scale)
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(238 / 255.0f, 238 / 255.0f, 238 / 255.0f, 1.00f)); // 10 ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(238 / 255.0f, 238 / 255.0f, 238 / 255.0f, 1.00f)); // 10
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(238 / 255.0f, 238 / 255.0f, 238 / 255.0f, 0.00f)); // 11 ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(238 / 255.0f, 238 / 255.0f, 238 / 255.0f, 0.00f)); // 11
ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, COL_GREEN_LIGHT); // 12 ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, COL_GREEN_LIGHT); // 12
ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(1.00f, 1.00f, 1.00f, 1.00f));//13 // The checkbox/radio frame behind this is drawn fully transparent (see FrameBg above,
// alpha 0), showing the light window background through it - a white check mark there is
// invisible. Dark mode doesn't have this problem (its window background is dark), so only
// this branch needs a check mark color with real contrast against a light background.
ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(0.f, 156 / 255.f, 136 / 255.f, 1.00f));//13
ImGui::PushStyleColor(ImGuiCol_ScrollbarGrab, ImVec4(0.42f, 0.42f, 0.42f, 1.00f)); ImGui::PushStyleColor(ImGuiCol_ScrollbarGrab, ImVec4(0.42f, 0.42f, 0.42f, 1.00f));
ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabHovered, ImVec4(0.93f, 0.93f, 0.93f, 1.00f)); ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabHovered, ImVec4(0.93f, 0.93f, 0.93f, 1.00f));
ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabActive, ImVec4(0.93f, 0.93f, 0.93f, 1.00f)); ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabActive, ImVec4(0.93f, 0.93f, 0.93f, 1.00f));
-2
View File
@@ -3870,8 +3870,6 @@ bool MainFrame::load_config_file(const std::string &path)
return false; return false;
} }
wxGetApp().load_current_presets(); wxGetApp().load_current_presets();
if (Plater *plater = wxGetApp().plater())
plater->normalize_bed_types(false);
return true; return true;
} }
+1 -22
View File
@@ -3,7 +3,6 @@
#include "ParamsPanel.hpp" #include "ParamsPanel.hpp"
#include "GUI_App.hpp" #include "GUI_App.hpp"
#include "MainFrame.hpp" #include "MainFrame.hpp"
#include "Plater.hpp"
#include "Tab.hpp" #include "Tab.hpp"
#include "libslic3r/Utils.hpp" #include "libslic3r/Utils.hpp"
@@ -71,18 +70,6 @@ ParamsDialog::ParamsDialog(wxWindow * parent)
} }
Hide(); Hide();
if (tab && tab->type() == Preset::TYPE_PRINTER) {
// Normalize only after the dialog closes, when the final capability is known.
auto &preset_bundle = *wxGetApp().preset_bundle;
const bool supports_multiple_bed_types = preset_bundle.is_bbl_vendor() ||
preset_bundle.printers.get_edited_preset().config.opt_bool("support_multi_bed_types");
if (m_initial_multi_bed_types != supports_multiple_bed_types) {
wxGetApp().plater()->normalize_bed_types(true);
if (auto *plate_tab = dynamic_cast<TabPrintPlate *>(wxGetApp().get_plate_tab()))
plate_tab->update_model_config();
}
}
if (!m_editing_filament_id.empty()) { if (!m_editing_filament_id.empty()) {
Filamentinformation *filament_info = new Filamentinformation(); Filamentinformation *filament_info = new Filamentinformation();
filament_info->filament_id = m_editing_filament_id; filament_info->filament_id = m_editing_filament_id;
@@ -106,15 +93,7 @@ void ParamsDialog::Popup()
if (m_panel && m_panel->get_current_tab()) { if (m_panel && m_panel->get_current_tab()) {
bool just_edit = false; bool just_edit = false;
if (!m_editing_filament_id.empty()) just_edit = true; if (!m_editing_filament_id.empty()) just_edit = true;
auto *tab = dynamic_cast<Tab *>(m_panel->get_current_tab()); dynamic_cast<Tab *>(m_panel->get_current_tab())->set_just_edit(just_edit);
tab->set_just_edit(just_edit);
if (tab->type() == Preset::TYPE_PRINTER) {
// Remember the initial capability and compare it when the dialog closes.
// Bambu profiles support multiple bed types even when this option is unset.
auto &preset_bundle = *wxGetApp().preset_bundle;
m_initial_multi_bed_types = preset_bundle.is_bbl_vendor() ||
preset_bundle.printers.get_edited_preset().config.opt_bool("support_multi_bed_types");
}
} }
Show(); Show();
} }
-1
View File
@@ -43,7 +43,6 @@ protected:
private: private:
std::string m_editing_filament_id; std::string m_editing_filament_id;
bool m_initial_multi_bed_types = false;
ParamsPanel * m_panel; ParamsPanel * m_panel;
wxWindowDisabler *m_winDisabler = nullptr; wxWindowDisabler *m_winDisabler = nullptr;
}; };
+2 -3
View File
@@ -431,9 +431,8 @@ PlateSettingsDialog::PlateSettingsDialog(wxWindow* parent, const wxString& title
} }
} }
auto &preset_bundle = *wxGetApp().preset_bundle; if (!wxGetApp().preset_bundle->is_bbl_vendor())
const auto &printer_config = preset_bundle.printers.get_edited_preset().config; m_bed_type_choice->Disable();
m_bed_type_choice->Enable(preset_bundle.is_bbl_vendor() || printer_config.opt_bool("support_multi_bed_types"));
wxStaticText* m_bed_type_txt = new wxStaticText(this, wxID_ANY, _L("Bed type")); wxStaticText* m_bed_type_txt = new wxStaticText(this, wxID_ANY, _L("Bed type"));
m_bed_type_txt->SetFont(Label::Body_14); m_bed_type_txt->SetFont(Label::Body_14);
+3 -51
View File
@@ -7401,8 +7401,7 @@ struct Plater::priv
std::vector<size_t> load_files(const std::vector<fs::path>& input_files, std::vector<size_t> load_files(const std::vector<fs::path>& input_files,
LoadStrategy strategy, LoadStrategy strategy,
bool ask_multi = false, bool ask_multi = false,
bool* published_out = nullptr, bool* published_out = nullptr);
bool* config_loaded_out = nullptr);
std::vector<size_t> load_model_objects(const ModelObjectPtrs& model_objects, bool allow_negative_z = false, bool split_object = false, bool auto_drop = true); std::vector<size_t> load_model_objects(const ModelObjectPtrs& model_objects, bool allow_negative_z = false, bool split_object = false, bool auto_drop = true);
// Texture-to-color import: a mesh loaded with UVs + a texture map gets its faces clustered // Texture-to-color import: a mesh loaded with UVs + a texture map gets its faces clustered
@@ -8873,12 +8872,8 @@ void read_binary_stl(const std::string& filename, std::string& model_id, std::st
std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_files, std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_files,
LoadStrategy strategy, LoadStrategy strategy,
bool ask_multi, bool ask_multi,
bool* published_out, bool* published_out)
bool* config_loaded_out)
{ {
if (config_loaded_out != nullptr)
*config_loaded_out = false;
std::vector<size_t> empty_result; std::vector<size_t> empty_result;
bool dlg_cont = true; bool dlg_cont = true;
bool is_user_cancel = false; bool is_user_cancel = false;
@@ -9563,8 +9558,6 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
id = agent->to_orca_filament_id(id); id = agent->to_orca_filament_id(id);
} }
preset_bundle->load_config_model(filename.string(), std::move(config), file_version, &published_config); preset_bundle->load_config_model(filename.string(), std::move(config), file_version, &published_config);
if (config_loaded_out != nullptr)
*config_loaded_out = true;
// Mixed-filament definitions that collided with one of the // Mixed-filament definitions that collided with one of the
// receiver's real slots were relocated during the preset load. // receiver's real slots were relocated during the preset load.
@@ -12787,8 +12780,6 @@ void Plater::priv::on_select_bed_type(wxCommandEvent &evt)
// update plater with new config // update plater with new config
q->on_config_change(wxGetApp().preset_bundle->full_config()); q->on_config_change(wxGetApp().preset_bundle->full_config());
if (auto *plate_tab = dynamic_cast<TabPrintPlate *>(wxGetApp().get_plate_tab()))
plate_tab->update_model_config();
// update app_config // update app_config
AppConfig* app_config = wxGetApp().app_config; AppConfig* app_config = wxGetApp().app_config;
@@ -15488,38 +15479,6 @@ void Plater::reset_project_dirty_initial_presets() { p->reset_project_dirty_init
void Plater::render_project_state_debug_window() const { p->render_project_state_debug_window(); } void Plater::render_project_state_debug_window() const { p->render_project_state_debug_window(); }
#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW #endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
void Plater::normalize_bed_types(bool printer_setting_changed)
{
if (only_gcode_mode() || is_gcode_3mf())
return;
auto &preset_bundle = *wxGetApp().preset_bundle;
// Keep FFF plate settings intact while an SLA printer is selected.
if (preset_bundle.printers.get_edited_preset().printer_technology() != ptFFF)
return;
const auto &printer_config = preset_bundle.printers.get_edited_preset().config;
const bool supports_multiple_bed_types =
preset_bundle.is_bbl_vendor() || printer_config.opt_bool("support_multi_bed_types");
// Clear local overrides for single-bed printers.
const bool overrides_reset = !supports_multiple_bed_types &&
!p->partplate_list.check_all_plate_local_bed_type({});
if (overrides_reset) {
set_plater_dirty(true);
show_info(this,
_L("The selected printer does not support multiple bed types.\nBed type overrides were reset to the global bed type."),
_L("Plate bed types reset"));
}
// Refresh the controls after a capability change, even when no override was reset.
if (printer_setting_changed || overrides_reset) {
sidebar().update_all_preset_comboboxes();
wxGetApp().obj_list()->update_and_show_object_settings_item();
}
}
std::vector<size_t> Plater::mixed_filament_config_indices() const std::vector<size_t> Plater::mixed_filament_config_indices() const
{ {
std::vector<size_t> indices; std::vector<size_t> indices;
@@ -16100,9 +16059,6 @@ void Plater::load_project(wxString const& filename2,
p->dirty_state.update_from_undo_redo_stack(true); p->dirty_state.update_from_undo_redo_stack(true);
up_to_date(true, true); up_to_date(true, true);
// Clear plate overrides that are incompatible with the selected printer.
normalize_bed_types(false);
wxGetApp().params_panel()->switch_to_object_if_has_object_configs(); wxGetApp().params_panel()->switch_to_object_if_has_object_configs();
auto has_modify = is_flush_config_modified(); auto has_modify = is_flush_config_modified();
@@ -17735,11 +17691,7 @@ std::vector<size_t> Plater::load_files(const std::vector<fs::path>& input_files,
p->m_slice_all_only_has_gcode = false; p->m_slice_all_only_has_gcode = false;
//BBS: wish to reset all plates stats item selected state when load a new file //BBS: wish to reset all plates stats item selected state when load a new file
p->preview->get_canvas3d()->reset_select_plate_toolbar_selection(); p->preview->get_canvas3d()->reset_select_plate_toolbar_selection();
bool config_loaded = false; return p->load_files(input_files, strategy, ask_multi, published_out);
std::vector<size_t> result = p->load_files(input_files, strategy, ask_multi, published_out, &config_loaded);
if (config_loaded && !is_loading_project())
normalize_bed_types(false);
return result;
} }
bool Plater::preview_zip_archive(const boost::filesystem::path& archive_path) bool Plater::preview_zip_archive(const boost::filesystem::path& archive_path)
-1
View File
@@ -343,7 +343,6 @@ public:
bool is_presets_dirty() const; bool is_presets_dirty() const;
void set_plater_dirty(bool is_dirty); void set_plater_dirty(bool is_dirty);
void update_project_dirty_from_presets(); void update_project_dirty_from_presets();
void normalize_bed_types(bool printer_setting_changed);
int save_project_if_dirty(const wxString& reason); int save_project_if_dirty(const wxString& reason);
void reset_project_dirty_after_save(); void reset_project_dirty_after_save();
void reset_project_dirty_initial_presets(); void reset_project_dirty_initial_presets();
-15
View File
@@ -87,7 +87,6 @@
#include "slic3r/plugin/PluginConfig.hpp" #include "slic3r/plugin/PluginConfig.hpp"
#include "slic3r/plugin/PluginManager.hpp" #include "slic3r/plugin/PluginManager.hpp"
#include "Plater.hpp" #include "Plater.hpp"
#include "ParamsDialog.hpp"
#include "MainFrame.hpp" #include "MainFrame.hpp"
#include "format.hpp" #include "format.hpp"
#include "UnsavedChangesDialog.hpp" #include "UnsavedChangesDialog.hpp"
@@ -3303,11 +3302,6 @@ void TabPrint::toggle_options()
m_config_manipulation.toggle_print_fff_options(m_config, int(intptr_t(m_extruder_switch->GetClientData())), m_type < Preset::TYPE_COUNT); m_config_manipulation.toggle_print_fff_options(m_config, int(intptr_t(m_extruder_switch->GetClientData())), m_type < Preset::TYPE_COUNT);
if (m_type == Preset::TYPE_PLATE) {
const auto &printer_config = m_preset_bundle->printers.get_edited_preset().config;
toggle_option("curr_bed_type", m_preset_bundle->is_bbl_vendor() || printer_config.opt_bool("support_multi_bed_types"));
}
Field *field = m_active_page->get_field("support_style"); Field *field = m_active_page->get_field("support_style");
auto support_type = m_config->opt_enum<SupportType>("support_type"); auto support_type = m_config->opt_enum<SupportType>("support_type");
if (auto choice = dynamic_cast<Choice*>(field)) { if (auto choice = dynamic_cast<Choice*>(field)) {
@@ -7030,15 +7024,6 @@ bool Tab::select_preset(
} }
load_current_preset(); load_current_preset();
// Wait for the settings dialog to close; its edits are still provisional.
if (printer_tab && is_selected) {
Plater *plater = wxGetApp().plater();
ParamsDialog *dialog = wxGetApp().params_dialog();
if (plater && !plater->is_loading_project() &&
(!dialog || !dialog->IsShown()))
plater->normalize_bed_types(false);
}
{ {
Slic3r::LifecycleEventContext ctx; Slic3r::LifecycleEventContext ctx;
ctx.name = preset_name; ctx.name = preset_name;
-3
View File
@@ -71,7 +71,6 @@
#include <unordered_map> #include <unordered_map>
#include "MainFrame.hpp" #include "MainFrame.hpp"
#include "Plater.hpp"
#include <boost/dll.hpp> #include <boost/dll.hpp>
#include <slic3r/GUI/Widgets/WebView.hpp> #include <slic3r/GUI/Widgets/WebView.hpp>
#include <slic3r/Utils/Http.hpp> #include <slic3r/Utils/Http.hpp>
@@ -1162,8 +1161,6 @@ bool GuideFrame::run()
app.app_config->set_legacy_datadir(false); app.app_config->set_legacy_datadir(false);
app.update_mode(); app.update_mode();
if (Plater *plater = app.plater())
plater->normalize_bed_types(false);
// BBS // BBS
//app.obj_manipul()->update_ui_from_settings(); //app.obj_manipul()->update_ui_from_settings();
BOOST_LOG_TRIVIAL(info) << "GuideFrame applied"; BOOST_LOG_TRIVIAL(info) << "GuideFrame applied";
-62
View File
@@ -4,21 +4,18 @@
#include <cstdlib> #include <cstdlib>
#include <map> #include <map>
#include <memory> #include <memory>
#include <set>
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <vector> #include <vector>
#include "nlohmann/json.hpp" #include "nlohmann/json.hpp"
#include "libslic3r/GCodeReader.hpp"
#include "libslic3r/GCodeWriter.hpp" #include "libslic3r/GCodeWriter.hpp"
#include "libslic3r/GCode.hpp" #include "libslic3r/GCode.hpp"
#include "libslic3r/Model.hpp" #include "libslic3r/Model.hpp"
#include "libslic3r/Print.hpp" #include "libslic3r/Print.hpp"
#include "libslic3r/ModelArrange.hpp" #include "libslic3r/ModelArrange.hpp"
#include <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include "test_helpers.hpp" #include "test_helpers.hpp"
@@ -100,22 +97,6 @@ SCENARIO("Origin manipulation", "[GCodeWriter]") {
} }
} }
TEST_CASE("A cached config slot is looked up again whenever its key changes", "[GCodeWriter]")
{
GCode::ConfigIndexCache cache;
int lookups = 0;
auto slot = [&](int filament, size_t layer, size_t generation) {
return cache.get(filament, layer, generation, [&] { ++lookups; return filament * 100 + int(layer) * 10 + int(generation); });
};
REQUIRE(slot(1, 2, 3) == 123);
REQUIRE(slot(1, 2, 3) == 123);
REQUIRE(lookups == 1);
REQUIRE(slot(4, 2, 3) == 423);
REQUIRE(slot(4, 5, 3) == 453);
REQUIRE(slot(4, 5, 6) == 456);
REQUIRE(lookups == 4);
}
// Verify that emit_machine_limits_to_gcode emits the correct max value across // Verify that emit_machine_limits_to_gcode emits the correct max value across
// used extruders (regression for commit b4ee665: "Emit max value of machine // used extruders (regression for commit b4ee665: "Emit max value of machine
// limit among used extruders"). // limit among used extruders").
@@ -891,46 +872,3 @@ TEST_CASE("Custom G-code motion limits are restored before generated moves", "[G
REQUIRE(gcode.find("M204 S6000 ; adjust acceleration", custom_gcode_pos) != std::string::npos); REQUIRE(gcode.find("M204 S6000 ; adjust acceleration", custom_gcode_pos) != std::string::npos);
REQUIRE(gcode.find("M205 X8 Y8 ; adjust jerk", custom_gcode_pos) != std::string::npos); REQUIRE(gcode.find("M205 X8 Y8 ; adjust jerk", custom_gcode_pos) != std::string::npos);
} }
TEST_CASE("Percent accelerations resolve against the option they are a percentage of", "[GCodeWriter]")
{
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
config.set_deserialize_strict({
{ "gcode_flavor", "marlin" },
{ "machine_max_acceleration_extruding", "20000,20000" },
{ "default_acceleration", "4000" },
{ "initial_layer_acceleration", "0" },
{ "outer_wall_acceleration", "3000" },
{ "bridge_acceleration", "50%" },
{ "sparse_infill_acceleration", "25%" },
{ "internal_solid_infill_acceleration", "60%" },
{ "sparse_infill_density", "20%" },
});
// get_abs_value_at() resolves each percentage through the ratio_over in the config definitions.
const std::map<std::string, int> expected = {
{ "Bridge", int(config.get_abs_value_at("bridge_acceleration", 0)) },
{ "Sparse infill", int(config.get_abs_value_at("sparse_infill_acceleration", 0)) },
{ "Internal solid infill", int(config.get_abs_value_at("internal_solid_infill_acceleration", 0)) },
};
REQUIRE(expected.at("Bridge") == 1500);
REQUIRE(expected.at("Sparse infill") == 1000);
REQUIRE(expected.at("Internal solid infill") == 2400);
std::map<std::string, std::set<int>> accelerations_by_role;
std::string role;
int acceleration = 0;
GCodeReader reader;
reader.parse_buffer(Slic3r::Test::slice({ TestMesh::bridge }, config), [&](GCodeReader &self, const GCodeReader::GCodeLine &line) {
float value;
if (boost::starts_with(line.raw(), ";TYPE:"))
role = line.raw().substr(6);
else if (line.cmd_is("M204") && line.has_value('S', value))
acceleration = int(value);
else if (line.extruding(self) && line.dist_XY(self) > 0)
accelerations_by_role[role].insert(acceleration);
});
for (const auto &[role_name, value] : expected) {
INFO(role_name);
REQUIRE(accelerations_by_role[role_name] == std::set<int>{ value });
}
}
+1
View File
@@ -30,6 +30,7 @@ add_executable(${_TEST_NAME}_tests
test_filament_mixer.cpp test_filament_mixer.cpp
test_fill_plane_path.cpp test_fill_plane_path.cpp
test_geometry.cpp test_geometry.cpp
test_kdtree.cpp
test_multimaterial_segmentation.cpp test_multimaterial_segmentation.cpp
test_placeholder_parser.cpp test_placeholder_parser.cpp
test_polygon.cpp test_polygon.cpp
+89
View File
@@ -1,7 +1,9 @@
#include <catch2/catch_all.hpp> #include <catch2/catch_all.hpp>
#include <algorithm>
#include <numeric> #include <numeric>
#include <iostream> #include <iostream>
#include <utility>
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include "libslic3r/ClipperUtils.hpp" #include "libslic3r/ClipperUtils.hpp"
@@ -291,3 +293,90 @@ TEST_CASE("Top level expolygons of an even-odd union", "[ClipperUtils]") {
REQUIRE(area_sum == Catch::Approx(area(top_level) + area(nested))); REQUIRE(area_sum == Catch::Approx(area(top_level) + area(nested)));
REQUIRE(top_level_expolygons(reference).size() == 1); REQUIRE(top_level_expolygons(reference).size() == 1);
} }
// Rings flattened to x,y,x,y... and sorted, with each ring rotated to start at its lowest point: two
// encodings of the same geometry compare equal however the pieces came back or wherever a ring started.
static std::vector<std::vector<coord_t>> canonical_rings(const ExPolygons &expolygons)
{
std::vector<std::vector<coord_t>> rings;
const auto add = [&rings](const Polygon &poly) {
if (poly.points.empty())
return;
Points pts = poly.points;
std::rotate(pts.begin(),
std::min_element(pts.begin(), pts.end(), [](const Point &a, const Point &b) {
return std::make_pair(a.x(), a.y()) < std::make_pair(b.x(), b.y());
}),
pts.end());
std::vector<coord_t> flat;
flat.reserve(pts.size() * 2);
for (const Point &p : pts) {
flat.emplace_back(p.x());
flat.emplace_back(p.y());
}
rings.emplace_back(std::move(flat));
};
for (const ExPolygon &expoly : expolygons) {
add(expoly.contour);
for (const Polygon &hole : expoly.holes)
add(hole);
}
std::sort(rings.begin(), rings.end());
return rings;
}
// The same rings, every coordinate within `tolerance`.
static bool same_rings(const ExPolygons &a, const ExPolygons &b, coord_t tolerance)
{
const std::vector<std::vector<coord_t>> ra = canonical_rings(a), rb = canonical_rings(b);
return std::equal(ra.begin(), ra.end(), rb.begin(), rb.end(), [tolerance](const std::vector<coord_t> &x, const std::vector<coord_t> &y) {
return std::equal(x.begin(), x.end(), y.begin(), y.end(), [tolerance](coord_t u, coord_t v) { return std::abs(u - v) <= tolerance; });
});
}
TEST_CASE("Tiled diff and intersection return the same polygons as the plain calls", "[ClipperUtils]") {
// A grid of disjoint framed squares, enough of them to be split into several tiles.
const int n = 40;
const coord_t cell = scaled<coord_t>(2.), side = scaled<coord_t>(1.5), frame = scaled<coord_t>(0.3);
ExPolygons subject;
for (int y = 0; y < n; ++ y)
for (int x = 0; x < n; ++ x) {
const Point o(x * cell, y * cell);
ExPolygon square(Polygon({ o, o + Point(side, 0), o + Point(side, side), o + Point(0, side) }));
Polygon hole({ o + Point(frame, frame), o + Point(frame, side - frame), o + Point(side - frame, side - frame), o + Point(side - frame, frame) });
square.holes.emplace_back(std::move(hole));
subject.emplace_back(std::move(square));
}
// Clip polygons crossing many squares, one of them large with holes of its own.
Polygons clip;
const coord_t span = n * cell;
for (int i = 0; i < 8; ++ i) {
const coord_t y0 = coord_t(i) * span / 8, y1 = y0 + scaled<coord_t>(0.9);
clip.emplace_back(Polygon({ Point(- cell, y0), Point(span, y0 + cell * 3), Point(span, y1 + cell * 3), Point(- cell, y1) }));
}
ExPolygon big(Polygon({ Point(span / 4, span / 4), Point(3 * span / 4, span / 4), Point(3 * span / 4, 3 * span / 4), Point(span / 4, 3 * span / 4) }));
for (int i = 0; i < 4; ++ i) {
const Point o(span / 4 + scaled<coord_t>(3.1) + i * scaled<coord_t>(9.7), span / 4 + scaled<coord_t>(5.3));
big.holes.emplace_back(Polygon({ o, o + Point(0, scaled<coord_t>(20.)), o + Point(scaled<coord_t>(5.), scaled<coord_t>(20.)), o + Point(scaled<coord_t>(5.), 0) }));
}
polygons_append(clip, to_polygons(big));
const ApplySafetyOffset safety = GENERATE(ApplySafetyOffset::No, ApplySafetyOffset::Yes);
// The point of the fixture: below 128 pieces the helpers fall back to a single tile and the tiled
// path under test is never taken.
REQUIRE(ClipperUtils::tile_expolygons(subject, 32).size() > 1);
// With the safety offset a tile unites fewer clip polygons, so Clipper2 can round a crossing 1 unit differently.
const coord_t tolerance = safety == ApplySafetyOffset::Yes ? 1 : 0;
const ExPolygons diff_plain = diff_ex(subject, clip, safety);
const ExPolygons diff_tiled = diff_ex_by_piece(subject, clip, safety);
REQUIRE(area(diff_plain) > 0.);
CHECK(same_rings(diff_tiled, diff_plain, tolerance));
const ExPolygons intersection_plain = intersection_ex(subject, clip, safety);
const ExPolygons intersection_tiled = intersection_ex_by_piece(subject, clip, safety);
REQUIRE(area(intersection_plain) > 0.);
CHECK(same_rings(intersection_tiled, intersection_plain, tolerance));
}
+67
View File
@@ -0,0 +1,67 @@
#include <catch2/catch_all.hpp>
#include <numeric>
#include <random>
#include <vector>
#include "libslic3r/KDTreeIndirect.hpp"
#include "libslic3r/Point.hpp"
using namespace Slic3r;
TEST_CASE("Visiting the nearby points gives what collecting them gives", "[KDTree]") {
std::mt19937 rng(19937);
std::uniform_real_distribution<float> coord(-50.f, 50.f);
// Points in a box, so that a radius search returns anything from none of them to all of them.
std::vector<Vec3f> points(2000);
for (Vec3f &p : points)
p = Vec3f(coord(rng), coord(rng), coord(rng));
auto coordinate = [&points](size_t idx, size_t dimension) { return points[idx](int(dimension)); };
KDTreeIndirect<3, float, decltype(coordinate)> tree(coordinate);
std::vector<size_t> indices(points.size());
std::iota(indices.begin(), indices.end(), 0);
tree.build(indices);
const float radius = GENERATE(0.5f, 5.f, 25.f, 200.f);
for (int i = 0; i < 20; ++ i) {
const Vec3f center(coord(rng), coord(rng), coord(rng));
const std::vector<size_t> collected = find_nearby_points(tree, center, radius);
std::vector<size_t> visited;
visit_nearby_points(tree, center, radius, [&visited](size_t idx) { visited.emplace_back(idx); });
// Same points, and in the same order: a caller that keeps the first of several equally good ones
// must get the same answer either way.
REQUIRE(visited == collected);
}
}
TEST_CASE("A radius search returns every point within the radius and no other", "[KDTree]") {
std::mt19937 rng(2024);
std::uniform_real_distribution<float> coord(-20.f, 20.f);
std::vector<Vec3f> points(500);
for (Vec3f &p : points)
p = Vec3f(coord(rng), coord(rng), coord(rng));
auto coordinate = [&points](size_t idx, size_t dimension) { return points[idx](int(dimension)); };
KDTreeIndirect<3, float, decltype(coordinate)> tree(coordinate);
std::vector<size_t> indices(points.size());
std::iota(indices.begin(), indices.end(), 0);
tree.build(indices);
const Vec3f center(1.f, -2.f, 3.f);
const float radius = 7.f;
std::vector<size_t> expected;
for (size_t i = 0; i < points.size(); ++ i)
if ((points[i] - center).squaredNorm() < radius * radius)
expected.emplace_back(i);
std::vector<size_t> visited;
visit_nearby_points(tree, center, radius, [&visited](size_t idx) { visited.emplace_back(idx); });
std::sort(visited.begin(), visited.end());
REQUIRE(! expected.empty());
REQUIRE(visited == expected);
}
@@ -510,22 +510,6 @@ TEST_CASE("Print config-index resolvers pick per-filament Hybrid slots", "[Print
} }
} }
TEST_CASE("Regrouping or rewriting the filament maps changes the config-index generation", "[Print][H2C]")
{
Model model;
model.add_object("cube", "", make_cube(20, 20, 20))->add_instance();
Print print;
print.apply(model, DynamicPrintConfig::full_print_config());
size_t generation = print.config_index_generation();
print.set_nozzle_group_result(nullptr);
REQUIRE(print.config_index_generation() != generation);
generation = print.config_index_generation();
print.update_filament_maps_to_config({1}, {(int) nvtStandard}, {0});
REQUIRE(print.config_index_generation() != generation);
}
TEST_CASE("Re-applying an unchanged config after slicing keeps the result valid", "[Print][H2C]") TEST_CASE("Re-applying an unchanged config after slicing keeps the result valid", "[Print][H2C]")
{ {
// apply() rebuilds m_config.filament_map_2 to the real per-filament slot map, while the // apply() rebuilds m_config.filament_map_2 to the real per-filament slot map, while the