WIP "ensure verticall wall thickness" rework:

1) New region expansion code to propagate wave from a boundary
   of a region inside of it.
2) get_extents() extended with a template attribute to work with
   zero area data sets.
3) ClipperZUtils.hpp for handling Clipper operation with Z coordinate
   (for source contour identification)
This commit is contained in:
Vojtech Bubnik 2022-12-20 09:09:10 +01:00
parent d3734aa5ae
commit 11c0e567a6
19 changed files with 964 additions and 85 deletions

View file

@ -0,0 +1,351 @@
#include "RegionExpansion.hpp"
#include <libslic3r/ClipperZUtils.hpp>
#include <libslic3r/ClipperUtils.hpp>
namespace Slic3r {
namespace Algorithm {
// similar to expolygons_to_zpaths(), but each contour is expanded before converted to zpath.
// The expanded contours are then opened (the first point is repeated at the end).
static ClipperLib_Z::Paths expolygons_to_zpaths_expanded_opened(
const ExPolygons &src, const float expansion, coord_t base_idx)
{
ClipperLib_Z::Paths out;
out.reserve(2 * std::accumulate(src.begin(), src.end(), size_t(0),
[](const size_t acc, const ExPolygon &expoly) { return acc + expoly.num_contours(); }));
coord_t z = base_idx;
ClipperLib::ClipperOffset offsetter;
offsetter.ShortestEdgeLength = expansion * ClipperOffsetShortestEdgeFactor;
ClipperLib::Paths expansion_cache;
for (const ExPolygon &expoly : src) {
for (size_t icontour = 0; icontour < expoly.num_contours(); ++ icontour) {
// Execute reorients the contours so that the outer most contour has a positive area. Thus the output
// contours will be CCW oriented even though the input paths are CW oriented.
// Offset is applied after contour reorientation, thus the signum of the offset value is reversed.
offsetter.Clear();
offsetter.AddPath(expoly.contour_or_hole(icontour).points, ClipperLib::jtSquare, ClipperLib::etClosedPolygon);
expansion_cache.clear();
offsetter.Execute(expansion_cache, icontour == 0 ? expansion : -expansion);
append(out, ClipperZUtils::to_zpaths<true>(expansion_cache, z));
}
++ z;
}
return out;
}
// Paths were created by splitting closed polygons into open paths and then by clipping them.
// Thus some pieces of the clipped polygons may now become split at the ends of the source polygons.
// Those ends are sorted lexicographically in "splits".
// Reconnect those split pieces.
static inline void merge_splits(ClipperLib_Z::Paths &paths, std::vector<std::pair<ClipperLib_Z::IntPoint, int>> &splits)
{
for (auto it_path = paths.begin(); it_path != paths.end(); ) {
ClipperLib_Z::Path &path = *it_path;
assert(path.size() >= 2);
bool merged = false;
if (path.size() >= 2) {
const ClipperLib_Z::IntPoint &front = path.front();
const ClipperLib_Z::IntPoint &back = path.back();
// The path before clipping was supposed to cross the clipping boundary or be fully out of it.
// Thus the clipped contour is supposed to become open.
assert(front.x() != back.x() || front.y() != back.y());
if (front.x() != back.x() || front.y() != back.y()) {
// Look up the ends in "splits", possibly join the contours.
// "splits" maps into the other piece connected to the same end point.
auto find_end = [&splits](const ClipperLib_Z::IntPoint &pt) -> std::pair<ClipperLib_Z::IntPoint, int>* {
auto it = std::lower_bound(splits.begin(), splits.end(), pt,
[](const auto &l, const auto &r){ return ClipperZUtils::zpoint_lower(l.first, r); });
return it != splits.end() && it->first == pt ? &(*it) : nullptr;
};
auto *end = find_end(front);
bool end_front = true;
if (! end) {
end_front = false;
end = find_end(back);
}
if (end) {
// This segment ends at a split point of the source closed contour before clipping.
if (end->second == -1) {
// Open end was found, not matched yet.
end->second = int(it_path - paths.begin());
} else {
// Open end was found and matched with end->second
ClipperLib_Z::Path &other_path = paths[end->second];
polylines_merge(other_path, other_path.front() == end->first, std::move(path), end_front);
if (std::next(it_path) == paths.end()) {
paths.pop_back();
break;
}
path = std::move(paths.back());
paths.pop_back();
merged = true;
}
}
}
}
if (! merged)
++ it_path;
}
}
static ClipperLib::Paths wavefront_initial(ClipperLib::ClipperOffset &co, const ClipperLib::Paths &polylines, float offset)
{
ClipperLib::Paths out;
out.reserve(polylines.size());
ClipperLib::Paths out_this;
for (const ClipperLib::Path &path : polylines) {
co.Clear();
co.AddPath(path, jtRound, ClipperLib::etOpenRound);
co.Execute(out_this, offset);
append(out, std::move(out_this));
}
return out;
}
// Input polygons may consist of multiple expolygons, even nested expolygons.
// After inflation some polygons may thus overlap, however the overlap is being resolved during the successive
// clipping operation, thus it is not being done here.
static ClipperLib::Paths wavefront_step(ClipperLib::ClipperOffset &co, const ClipperLib::Paths &polygons, float offset)
{
ClipperLib::Paths out;
out.reserve(polygons.size());
ClipperLib::Paths out_this;
for (const ClipperLib::Path &polygon : polygons) {
co.Clear();
// Execute reorients the contours so that the outer most contour has a positive area. Thus the output
// contours will be CCW oriented even though the input paths are CW oriented.
// Offset is applied after contour reorientation, thus the signum of the offset value is reversed.
co.AddPath(polygon, jtRound, ClipperLib::etClosedPolygon);
bool ccw = ClipperLib::Orientation(polygon);
co.Execute(out_this, ccw ? offset : - offset);
if (! ccw) {
// Reverse the resulting contours.
for (ClipperLib::Path &path : out_this)
std::reverse(path.begin(), path.end());
}
append(out, std::move(out_this));
}
return out;
}
static ClipperLib::Paths wavefront_clip(const ClipperLib::Paths &wavefront, const Polygons &clipping)
{
ClipperLib::Clipper clipper;
clipper.AddPaths(wavefront, ClipperLib::ptSubject, true);
clipper.AddPaths(ClipperUtils::PolygonsProvider(clipping), ClipperLib::ptClip, true);
ClipperLib::Paths out;
clipper.Execute(ClipperLib::ctIntersection, out, ClipperLib::pftPositive, ClipperLib::pftPositive);
return out;
}
static Polygons propagate_wave_from_boundary(
ClipperLib::ClipperOffset &co,
// Seed of the wave: Open polylines very close to the boundary.
const ClipperLib::Paths &seed,
// Boundary inside which the waveform will propagate.
const ExPolygon &boundary,
// How much to inflate the seed lines to produce the first wave area.
const float initial_step,
// How much to inflate the first wave area and the successive wave areas in each step.
const float other_step,
// Number of inflate steps after the initial step.
const size_t num_other_steps,
// Maximum inflation of seed contours over the boundary. Used to trim boundary to speed up
// clipping during wave propagation.
const float max_inflation)
{
assert(! seed.empty() && seed.front().size() >= 2);
Polygons clipping = ClipperUtils::clip_clipper_polygons_with_subject_bbox(boundary, get_extents<true>(seed).inflated(max_inflation));
ClipperLib::Paths polygons = wavefront_clip(wavefront_initial(co, seed, initial_step), clipping);
// Now offset the remaining
for (size_t ioffset = 0; ioffset < num_other_steps; ++ ioffset)
polygons = wavefront_clip(wavefront_step(co, polygons, other_step), clipping);
return to_polygons(polygons);
}
// Calculating radius discretization according to ClipperLib offsetter code, see void ClipperOffset::DoOffset(double delta)
inline double clipper_round_offset_error(double offset, double arc_tolerance)
{
static constexpr const double def_arc_tolerance = 0.25;
const double y =
arc_tolerance <= 0 ?
def_arc_tolerance :
arc_tolerance > offset * def_arc_tolerance ?
offset * def_arc_tolerance :
arc_tolerance;
double steps = std::min(M_PI / std::acos(1. - y / offset), offset * M_PI);
return offset * (1. - cos(M_PI / steps));
}
// Returns regions per source ExPolygon expanded into boundary.
std::vector<Polygons> expand_expolygons(
// Source regions that are supposed to touch the boundary.
// Boundaries of source regions touching the "boundary" regions will be expanded into the "boundary" region.
const ExPolygons &src,
const ExPolygons &boundary,
// Scaled expansion value
float full_expansion,
// Expand by waves of expansion_step size (expansion_step is scaled).
float expansion_step,
// Don't take more than max_nr_steps for small expansion_step.
size_t max_nr_expansion_steps)
{
assert(full_expansion > 0);
assert(expansion_step > 0);
assert(max_nr_expansion_steps > 0);
// Initial expansion of src to make the source regions intersect with boundary regions just a bit.
float tiny_expansion;
// How much to inflate the seed lines to produce the first wave area.
float initial_step;
// How much to inflate the first wave area and the successive wave areas in each step.
float other_step;
// Number of inflate steps after the initial step.
size_t num_other_steps;
// Maximum inflation of seed contours over the boundary. Used to trim boundary to speed up
// clipping during wave propagation.
float max_inflation;
// Offsetter to be applied for all inflation waves. Its accuracy is set with the block below.
ClipperLib::ClipperOffset co;
{
// Initial expansion of src to make the source regions intersect with boundary regions just a bit.
// The expansion should not be too tiny, but also small enough, so the following expansion will
// compensate for tiny_expansion and bring the wave back to the boundary without producing
// ugly cusps where it touches the boundary.
tiny_expansion = std::min(0.25f * full_expansion, scaled<float>(0.05f));
size_t nsteps = size_t(ceil((full_expansion - tiny_expansion) / expansion_step));
if (max_nr_expansion_steps > 0)
nsteps = std::min(nsteps, max_nr_expansion_steps);
assert(nsteps > 0);
initial_step = (full_expansion - tiny_expansion) / nsteps;
if (nsteps > 1 && 0.25 * initial_step < tiny_expansion) {
// Decrease the step size by lowering number of steps.
nsteps = std::max<size_t>(1, (floor((full_expansion - tiny_expansion) / (4. * tiny_expansion))));
initial_step = (full_expansion - tiny_expansion) / nsteps;
}
if (0.25 * initial_step < tiny_expansion || nsteps == 1) {
tiny_expansion = 0.2f * full_expansion;
initial_step = 0.8f * full_expansion;
}
other_step = initial_step;
num_other_steps = nsteps - 1;
// Accuracy of the offsetter for wave propagation.
co.ArcTolerance = float(scale_(0.1));
co.ShortestEdgeLength = std::abs(initial_step * ClipperOffsetShortestEdgeFactor);
// Maximum inflation of seed contours over the boundary. Used to trim boundary to speed up
// clipping during wave propagation. Needs to be in sync with the offsetter accuracy.
// Clipper positive round offset should rather offset less than more.
// Still a little bit of additional offset was added.
max_inflation = (tiny_expansion + nsteps * initial_step) * 1.1;
// (clipper_round_offset_error(tiny_expansion, co.ArcTolerance) + nsteps * clipper_round_offset_error(initial_step, co.ArcTolerance) * 1.5; // Account for uncertainty
}
using Intersection = ClipperZUtils::ClipperZIntersectionVisitor::Intersection;
using Intersections = ClipperZUtils::ClipperZIntersectionVisitor::Intersections;
ClipperLib_Z::Paths expansion_seeds;
Intersections intersections;
coord_t idx_boundary_begin = 1;
coord_t idx_boundary_end;
coord_t idx_src_end;
{
ClipperLib_Z::Clipper zclipper;
ClipperZUtils::ClipperZIntersectionVisitor visitor(intersections);
zclipper.ZFillFunction(visitor.clipper_callback());
// as closed contours
{
ClipperLib_Z::Paths zboundary = ClipperZUtils::expolygons_to_zpaths(boundary, idx_boundary_begin);
idx_boundary_end = idx_boundary_begin + coord_t(zboundary.size());
zclipper.AddPaths(zboundary, ClipperLib_Z::ptClip, true);
}
// as open contours
std::vector<std::pair<ClipperLib_Z::IntPoint, int>> zsrc_splits;
{
ClipperLib_Z::Paths zsrc = expolygons_to_zpaths_expanded_opened(src, tiny_expansion, idx_boundary_end);
zclipper.AddPaths(zsrc, ClipperLib_Z::ptSubject, false);
idx_src_end = idx_boundary_end + coord_t(zsrc.size());
zsrc_splits.reserve(zsrc.size());
for (const ClipperLib_Z::Path &path : zsrc) {
assert(path.size() >= 2);
assert(path.front() == path.back());
zsrc_splits.emplace_back(path.front(), -1);
}
std::sort(zsrc_splits.begin(), zsrc_splits.end(), [](const auto &l, const auto &r){ return ClipperZUtils::zpoint_lower(l.first, r.first); });
}
ClipperLib_Z::PolyTree polytree;
zclipper.Execute(ClipperLib_Z::ctIntersection, polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
ClipperLib_Z::PolyTreeToPaths(std::move(polytree), expansion_seeds);
merge_splits(expansion_seeds, zsrc_splits);
}
// Sort paths into their respective islands.
// Each src x boundary will be processed (wave expanded) independently.
// Multiple pieces of a single src may intersect the same boundary.
struct SeedOrigin {
int src;
int boundary;
int seed;
};
std::vector<SeedOrigin> map_seeds;
map_seeds.reserve(expansion_seeds.size());
int iseed = 0;
for (const ClipperLib_Z::Path &path : expansion_seeds) {
assert(path.size() >= 2);
const ClipperLib_Z::IntPoint &front = path.front();
const ClipperLib_Z::IntPoint &back = path.back();
// Both ends of a seed segment are supposed to be inside a single boundary expolygon.
assert(front.z() < 0);
assert(back.z() < 0);
const Intersection *intersection = nullptr;
auto intersection_point_valid = [idx_boundary_end, idx_src_end](const Intersection &is) {
return is.first >= 1 && is.first < idx_boundary_end &&
is.second >= idx_boundary_end && is.second < idx_src_end;
};
if (front.z() < 0) {
const Intersection &is = intersections[- front.z() - 1];
assert(intersection_point_valid(is));
if (intersection_point_valid(is))
intersection = &is;
}
if (! intersection && back.z() < 0) {
const Intersection &is = intersections[- back.z() - 1];
assert(intersection_point_valid(is));
if (intersection_point_valid(is))
intersection = &is;
}
if (intersection) {
// The path intersects the boundary contour at least at one side.
map_seeds.push_back({ intersection->second - idx_boundary_end, intersection->first - 1, iseed });
}
++ iseed;
}
// Sort the seeds by their intersection boundary and source contour.
std::sort(map_seeds.begin(), map_seeds.end(), [](const auto &l, const auto &r){
return l.boundary < r.boundary || (l.boundary == r.boundary && l.src < r.src);
});
std::vector<Polygons> out(src.size(), Polygons{});
ClipperLib::Paths paths;
for (auto it_seed = map_seeds.begin(); it_seed != map_seeds.end();) {
auto it = it_seed;
paths.clear();
for (; it != map_seeds.end() && it->boundary == it_seed->boundary && it->src == it_seed->src; ++ it)
paths.emplace_back(ClipperZUtils::from_zpath(expansion_seeds[it->seed]));
// Propagate the wavefront while clipping it with the trimmed boundary.
// Collect the expanded polygons, merge them with the source polygons.
append(out[it_seed->src], propagate_wave_from_boundary(co, paths, boundary[it_seed->boundary], initial_step, other_step, num_other_steps, max_inflation));
it_seed = it;
}
return out;
}
} // Algorithm
} // Slic3r

View file

@ -0,0 +1,26 @@
#ifndef SRC_LIBSLIC3R_ALGORITHM_REGION_EXPANSION_HPP_
#define SRC_LIBSLIC3R_ALGORITHM_REGION_EXPANSION_HPP_
#include <cstdint>
namespace Slic3r {
class Polygon;
using Polygons = std::vector<Polygon>;
class ExPolygon;
using ExPolygons = std::vector<ExPolygon>;
namespace Algorithm {
std::vector<Polygons> expand_expolygons(const ExPolygons &src, const ExPolygons &boundary,
// Scaled expansion value
float expansion,
// Expand by waves of expansion_step size (expansion_step is scaled).
float expansion_step,
// Don't take more than max_nr_steps for small expansion_step.
size_t max_nr_steps);
} // Algorithm
} // Slic3r
#endif /* SRC_LIBSLIC3R_ALGORITHM_REGION_EXPANSION_HPP_ */

View file

@ -22,24 +22,9 @@ public:
BoundingBoxBase(const PointClass &p1, const PointClass &p2, const PointClass &p3) :
min(p1), max(p1), defined(false) { merge(p2); merge(p3); }
template<class It, class = IteratorOnly<It> >
BoundingBoxBase(It from, It to) : min(PointClass::Zero()), max(PointClass::Zero())
{
if (from == to) {
this->defined = false;
// throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBoxBase constructor");
} else {
auto it = from;
this->min = it->template cast<typename PointClass::Scalar>();
this->max = this->min;
for (++ it; it != to; ++ it) {
auto vec = it->template cast<typename PointClass::Scalar>();
this->min = this->min.cwiseMin(vec);
this->max = this->max.cwiseMax(vec);
}
this->defined = (this->min.x() < this->max.x()) && (this->min.y() < this->max.y());
}
}
template<class It, class = IteratorOnly<It>>
BoundingBoxBase(It from, It to)
{ construct(*this, from, to); }
BoundingBoxBase(const std::vector<PointClass> &points)
: BoundingBoxBase(points.begin(), points.end())
@ -70,6 +55,30 @@ public:
}
bool operator==(const BoundingBoxBase<PointClass> &rhs) { return this->min == rhs.min && this->max == rhs.max; }
bool operator!=(const BoundingBoxBase<PointClass> &rhs) { return ! (*this == rhs); }
private:
// to access construct()
friend BoundingBox get_extents<false>(const Points &pts);
friend BoundingBox get_extents<true>(const Points &pts);
// if IncludeBoundary, then a bounding box is defined even for a single point.
// otherwise a bounding box is only defined if it has a positive area.
// The output bounding box is expected to be set to "undefined" initially.
template<bool IncludeBoundary = false, class BoundingBoxType, class It, class = IteratorOnly<It>>
static void construct(BoundingBoxType &out, It from, It to)
{
if (from != to) {
auto it = from;
out.min = it->template cast<typename PointClass::Scalar>();
out.max = out.min;
for (++ it; it != to; ++ it) {
auto vec = it->template cast<typename PointClass::Scalar>();
out.min = out.min.cwiseMin(vec);
out.max = out.max.cwiseMax(vec);
}
out.defined = IncludeBoundary || (out.min.x() < out.max.x() && out.min.y() < out.max.y());
}
}
};
template <class PointClass>

View file

@ -641,7 +641,7 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance
// perform operation
ClipperLib_Z::PolyTree loops_trimmed_tree;
clipper.Execute(ClipperLib_Z::ctDifference, loops_trimmed_tree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
ClipperLib_Z::PolyTreeToPaths(loops_trimmed_tree, loops_trimmed);
ClipperLib_Z::PolyTreeToPaths(std::move(loops_trimmed_tree), loops_trimmed);
}
// Third, produce the extrusions, sorted by the source loop indices.

View file

@ -22,6 +22,8 @@ set(SLIC3R_SOURCES
AABBTreeLines.hpp
AABBMesh.hpp
AABBMesh.cpp
Algorithm/RegionExpansion.cpp
Algorithm/RegionExpansion.hpp
BoundingBox.cpp
BoundingBox.hpp
BridgeDetector.cpp
@ -35,6 +37,7 @@ set(SLIC3R_SOURCES
clipper.hpp
ClipperUtils.cpp
ClipperUtils.hpp
ClipperZUtils.hpp
Color.cpp
Color.hpp
Config.cpp

View file

@ -8,8 +8,6 @@
#include "SVG.hpp"
#endif /* CLIPPER_UTILS_DEBUG */
#define CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR (0.005f)
namespace Slic3r {
#ifdef CLIPPER_UTILS_DEBUG
@ -267,7 +265,7 @@ static ClipperLib::Paths raw_offset(PathsProvider &&paths, float offset, Clipper
co.ArcTolerance = miterLimit;
else
co.MiterLimit = miterLimit;
co.ShortestEdgeLength = double(std::abs(offset * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR));
co.ShortestEdgeLength = std::abs(offset * ClipperOffsetShortestEdgeFactor);
for (const ClipperLib::Path &path : paths) {
co.Clear();
// Execute reorients the contours so that the outer most contour has a positive area. Thus the output
@ -414,7 +412,7 @@ static int offset_expolygon_inner(const Slic3r::ExPolygon &expoly, const float d
co.ArcTolerance = miterLimit;
else
co.MiterLimit = miterLimit;
co.ShortestEdgeLength = double(std::abs(delta * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR));
co.ShortestEdgeLength = std::abs(delta * ClipperOffsetShortestEdgeFactor);
co.AddPath(expoly.contour.points, joinType, ClipperLib::etClosedPolygon);
co.Execute(contours, delta);
}
@ -435,7 +433,7 @@ static int offset_expolygon_inner(const Slic3r::ExPolygon &expoly, const float d
co.ArcTolerance = miterLimit;
else
co.MiterLimit = miterLimit;
co.ShortestEdgeLength = double(std::abs(delta * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR));
co.ShortestEdgeLength = std::abs(delta * ClipperOffsetShortestEdgeFactor);
co.AddPath(hole.points, joinType, ClipperLib::etClosedPolygon);
ClipperLib::Paths out2;
// Execute reorients the contours so that the outer most contour has a positive area. Thus the output
@ -1055,7 +1053,7 @@ ClipperLib::Path mittered_offset_path_scaled(const Points &contour, const std::v
};
// Minimum edge length, squared.
double lmin = *std::max_element(deltas.begin(), deltas.end()) * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR;
double lmin = *std::max_element(deltas.begin(), deltas.end()) * ClipperOffsetShortestEdgeFactor;
double l2min = lmin * lmin;
// Minimum angle to consider two edges to be parallel.
// Vojtech's estimate.

View file

@ -39,6 +39,9 @@ static constexpr const Slic3r::ClipperLib::JoinType DefaultLineJoinType = Sl
// Miter limit is ignored for jtSquare.
static constexpr const double DefaultLineMiterLimit = 0.;
// Decimation factor applied on input contour when doing offset, multiplied by the offset distance.
static constexpr const double ClipperOffsetShortestEdgeFactor = 0.005;
enum class ApplySafetyOffset {
No,
Yes
@ -599,6 +602,6 @@ Polygons variable_offset_outer(const ExPolygon &expoly, const std::vector<std::
ExPolygons variable_offset_outer_ex(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit = 2.);
ExPolygons variable_offset_inner_ex(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit = 2.);
}
} // namespace Slic3r
#endif
#endif // slic3r_ClipperUtils_hpp_

View file

@ -0,0 +1,143 @@
#ifndef slic3r_ClipperZUtils_hpp_
#define slic3r_ClipperZUtils_hpp_
#include <vector>
#include <clipper/clipper_z.hpp>
#include <libslic3r/Point.hpp>
namespace Slic3r {
namespace ClipperZUtils {
using ZPoint = ClipperLib_Z::IntPoint;
using ZPoints = ClipperLib_Z::Path;
using ZPath = ClipperLib_Z::Path;
using ZPaths = ClipperLib_Z::Paths;
inline bool zpoint_lower(const ZPoint &l, const ZPoint &r)
{
return l.x() < r.x() || (l.x() == r.x() && (l.y() < r.y() || (l.y() == r.y() && l.z() < r.z())));
}
// Convert a single path to path with a given Z coordinate.
// If Open, then duplicate the first point at the end.
template<bool Open = false>
inline ZPath to_zpath(const Points &path, coord_t z)
{
ZPath out;
if (! path.empty()) {
out.reserve(path.size() + Open ? 1 : 0);
for (const Point &p : path)
out.emplace_back(p.x(), p.y(), z);
if (Open)
out.emplace_back(out.front());
}
return out;
}
// Convert multiple paths to paths with a given Z coordinate.
// If Open, then duplicate the first point of each path at its end.
template<bool Open = false>
inline ZPaths to_zpaths(const std::vector<Points> &paths, coord_t z)
{
ZPaths out;
out.reserve(paths.size());
for (const Points &path : paths)
out.emplace_back(to_zpath<Open>(path, z));
return out;
}
// Convert multiple expolygons into z-paths with Z specified by an index of the source expolygon
// offsetted by base_index.
// If Open, then duplicate the first point of each path at its end.
template<bool Open = false>
inline ZPaths expolygons_to_zpaths(const ExPolygons &src, coord_t base_idx)
{
ZPaths out;
out.reserve(std::accumulate(src.begin(), src.end(), size_t(0),
[](const size_t acc, const ExPolygon &expoly) { return acc + expoly.num_contours(); }));
coord_t z = base_idx;
for (const ExPolygon &expoly : src) {
out.emplace_back(to_zpath<Open>(expoly.contour.points, z));
for (const Polygon &hole : expoly.holes)
out.emplace_back(to_zpath<Open>(hole.points, z));
++ z;
}
return out;
}
// Convert a single path to path with a given Z coordinate.
// If Open, then duplicate the first point at the end.
template<bool Open = false>
inline Points from_zpath(const ZPoints &path)
{
Points out;
if (! path.empty()) {
out.reserve(path.size() + Open ? 1 : 0);
for (const ZPoint &p : path)
out.emplace_back(p.x(), p.y());
if (Open)
out.emplace_back(out.front());
}
return out;
}
// Convert multiple paths to paths with a given Z coordinate.
// If Open, then duplicate the first point of each path at its end.
template<bool Open = false>
inline void from_zpaths(const ZPaths &paths, std::vector<Points> &out)
{
out.reserve(out.size() + paths.size());
for (const ZPoints &path : paths)
out.emplace_back(from_zpath<Open>(path));
}
template<bool Open = false>
inline std::vector<Points> from_zpaths(const ZPaths &paths)
{
std::vector<Points> out;
from_zpaths(paths, out);
return out;
}
class ClipperZIntersectionVisitor {
public:
using Intersection = std::pair<coord_t, coord_t>;
using Intersections = std::vector<Intersection>;
ClipperZIntersectionVisitor(Intersections &intersections) : m_intersections(intersections) {}
void reset() { m_intersections.clear(); }
void operator()(const ZPoint &e1bot, const ZPoint &e1top, const ZPoint &e2bot, const ZPoint &e2top, ZPoint &pt) {
coord_t srcs[4]{ e1bot.z(), e1top.z(), e2bot.z(), e2top.z() };
coord_t *begin = srcs;
coord_t *end = srcs + 4;
//FIXME bubble sort manually?
std::sort(begin, end);
end = std::unique(begin, end);
if (begin + 1 == end) {
// Self intersection may happen on source contour. Just copy the Z value.
pt.z() = *begin;
} else {
assert(begin + 2 == end);
if (begin + 2 <= end) {
// store a -1 based negative index into the "intersections" vector here.
m_intersections.emplace_back(srcs[0], srcs[1]);
pt.z() = -coord_t(m_intersections.size());
}
}
}
ClipperLib_Z::ZFillCallback clipper_callback() {
return [this](const ZPoint &e1bot, const ZPoint &e1top,
const ZPoint &e2bot, const ZPoint &e2top, ZPoint &pt)
{ return (*this)(e1bot, e1top, e2bot, e2top, pt); };
}
const std::vector<std::pair<coord_t, coord_t>>& intersections() const { return m_intersections; }
private:
std::vector<std::pair<coord_t, coord_t>> &m_intersections;
};
} // namespace ClipperZUtils
} // namespace Slic3r
#endif // slic3r_ClipperZUtils_hpp_

View file

@ -1,5 +1,5 @@
#include "Layer.hpp"
#include <clipper/clipper_z.hpp>
#include "ClipperZUtils.hpp"
#include "ClipperUtils.hpp"
#include "Print.hpp"
#include "Fill/Fill.hpp"
@ -304,48 +304,16 @@ void Layer::build_up_down_graph(Layer& below, Layer& above)
coord_t paths_end = paths_above_offset + coord_t(above.lslices.size());
#endif // NDEBUG
class ZFill {
public:
ZFill() = default;
void reset() { m_intersections.clear(); }
void operator()(
const ClipperLib_Z::IntPoint& e1bot, const ClipperLib_Z::IntPoint& e1top,
const ClipperLib_Z::IntPoint& e2bot, const ClipperLib_Z::IntPoint& e2top,
ClipperLib_Z::IntPoint& pt) {
coord_t srcs[4]{ e1bot.z(), e1top.z(), e2bot.z(), e2top.z() };
coord_t* begin = srcs;
coord_t* end = srcs + 4;
std::sort(begin, end);
end = std::unique(begin, end);
if (begin + 1 == end) {
// Self intersection may happen on source contour. Just copy the Z value.
pt.z() = *begin;
} else {
assert(begin + 2 == end);
if (begin + 2 <= end) {
// store a -1 based negative index into the "intersections" vector here.
m_intersections.emplace_back(srcs[0], srcs[1]);
pt.z() = -coord_t(m_intersections.size());
}
}
}
const std::vector<std::pair<coord_t, coord_t>>& intersections() const { return m_intersections; }
private:
std::vector<std::pair<coord_t, coord_t>> m_intersections;
} zfill;
ClipperLib_Z::Clipper clipper;
ClipperLib_Z::PolyTree result;
clipper.ZFillFunction(
[&zfill](const ClipperLib_Z::IntPoint &e1bot, const ClipperLib_Z::IntPoint &e1top,
const ClipperLib_Z::IntPoint &e2bot, const ClipperLib_Z::IntPoint &e2top, ClipperLib_Z::IntPoint &pt)
{ return zfill(e1bot, e1top, e2bot, e2top, pt); });
ClipperZUtils::ClipperZIntersectionVisitor::Intersections intersections;
ClipperZUtils::ClipperZIntersectionVisitor visitor(intersections);
clipper.ZFillFunction(visitor.clipper_callback());
clipper.AddPaths(paths_below, ClipperLib_Z::ptSubject, true);
clipper.AddPaths(paths_above, ClipperLib_Z::ptClip, true);
clipper.Execute(ClipperLib_Z::ctIntersection, result, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
connect_layer_slices(below, above, result, zfill.intersections(), paths_below_offset, paths_above_offset
connect_layer_slices(below, above, result, intersections, paths_below_offset, paths_above_offset
#ifndef NDEBUG
, paths_end
#endif // NDEBUG

View file

@ -433,10 +433,12 @@ static ClipperLib_Z::Paths clip_extrusion(const ClipperLib_Z::Path &subject, con
clipper.AddPath(subject, ClipperLib_Z::ptSubject, false);
clipper.AddPaths(clip, ClipperLib_Z::ptClip, true);
ClipperLib_Z::PolyTree clipped_polytree;
ClipperLib_Z::Paths clipped_paths;
clipper.Execute(clipType, clipped_polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
ClipperLib_Z::PolyTreeToPaths(clipped_polytree, clipped_paths);
{
ClipperLib_Z::PolyTree clipped_polytree;
clipper.Execute(clipType, clipped_polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
ClipperLib_Z::PolyTreeToPaths(std::move(clipped_polytree), clipped_paths);
}
// Clipped path could contain vertices from the clip with a Z coordinate equal to zero.
// For those vertices, we must assign value based on the subject.

View file

@ -84,18 +84,15 @@ Points collect_duplicates(Points pts /* Copy */)
return duplicits;
}
template<bool IncludeBoundary>
BoundingBox get_extents(const Points &pts)
{
return BoundingBox(pts);
}
BoundingBox get_extents(const std::vector<Points> &pts)
{
BoundingBox bbox;
for (const Points &p : pts)
bbox.merge(get_extents(p));
return bbox;
BoundingBox out;
BoundingBox::construct<IncludeBoundary>(out, pts.begin(), pts.end());
return out;
}
template BoundingBox get_extents<false>(const Points &pts);
template BoundingBox get_extents<true>(const Points &pts);
BoundingBoxf get_extents(const std::vector<Vec2d> &pts)
{

View file

@ -229,8 +229,24 @@ inline Point lerp(const Point &a, const Point &b, double t)
return ((1. - t) * a.cast<double>() + t * b.cast<double>()).cast<coord_t>();
}
// if IncludeBoundary, then a bounding box is defined even for a single point.
// otherwise a bounding box is only defined if it has a positive area.
template<bool IncludeBoundary = false>
BoundingBox get_extents(const Points &pts);
BoundingBox get_extents(const std::vector<Points> &pts);
extern template BoundingBox get_extents<false>(const Points& pts);
extern template BoundingBox get_extents<true>(const Points& pts);
// if IncludeBoundary, then a bounding box is defined even for a single point.
// otherwise a bounding box is only defined if it has a positive area.
template<bool IncludeBoundary = false>
BoundingBox get_extents(const std::vector<Points> &pts)
{
BoundingBox bbox;
for (const Points &p : pts)
bbox.merge(get_extents<IncludeBoundary>(p));
return bbox;
}
BoundingBoxf get_extents(const std::vector<Vec2d> &pts);
int nearest_point_index(const Points &points, const Point &pt);

View file

@ -153,6 +153,25 @@ inline void polylines_append(Polylines &dst, Polylines &&src)
}
}
// Merge polylines at their respective end points.
// dst_first: the merge point is at dst.begin() or dst.end()?
// src_first: the merge point is at src.begin() or src.end()?
// The orientation of the resulting polyline is unknown, the output polyline may start
// either with src piece or dst piece.
template<typename PointType>
inline void polylines_merge(std::vector<PointType> &dst, bool dst_first, std::vector<PointType> &&src, bool src_first)
{
if (dst_first) {
if (src_first)
std::reverse(dst.begin(), dst.end());
else
std::swap(dst, src);
} else if (! src_first)
std::reverse(src.begin(), src.end());
// Merge src into dst.
append(dst, std::move(src));
}
const Point& leftmost_point(const Polylines &polylines);
bool remove_degenerate(Polylines &polylines);

View file

@ -124,8 +124,7 @@ inline void append(std::vector<T>& dest, std::vector<T>&& src)
dest.insert(dest.end(),
std::make_move_iterator(src.begin()),
std::make_move_iterator(src.end()));
// Vojta wants back compatibility
// Release memory of the source contour now.
src.clear();
src.shrink_to_fit();
}
@ -161,8 +160,7 @@ inline void append_reversed(std::vector<T>& dest, std::vector<T>&& src)
dest.insert(dest.end(),
std::make_move_iterator(src.rbegin()),
std::make_move_iterator(src.rend()));
// Vojta wants back compatibility
// Release memory of the source contour now.
src.clear();
src.shrink_to_fit();
}