Merge pull request #27 from Prusa-Development/pm_anchor_bridges_on_sparse_infill

Ensuring + anchoring bridges over sparse infill
This commit is contained in:
Pavel Mikuš 2023-03-02 17:02:16 +01:00 committed by GitHub
commit 1a0d8f5130
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 3150 additions and 889 deletions

View File

@ -4109,19 +4109,40 @@ void AddPolyNodeToPaths(const PolyNode& polynode, NodeType nodetype, Paths& path
for (int i = 0; i < polynode.ChildCount(); ++i)
AddPolyNodeToPaths(*polynode.Childs[i], nodetype, paths);
}
void AddPolyNodeToPaths(PolyNode&& polynode, NodeType nodetype, Paths& paths)
{
bool match = true;
if (nodetype == ntClosed) match = !polynode.IsOpen();
else if (nodetype == ntOpen) return;
if (!polynode.Contour.empty() && match)
paths.push_back(std::move(polynode.Contour));
for (int i = 0; i < polynode.ChildCount(); ++i)
AddPolyNodeToPaths(std::move(*polynode.Childs[i]), nodetype, paths);
}
//------------------------------------------------------------------------------
void PolyTreeToPaths(const PolyTree& polytree, Paths& paths)
{
paths.resize(0);
paths.clear();
paths.reserve(polytree.Total());
AddPolyNodeToPaths(polytree, ntAny, paths);
}
void PolyTreeToPaths(PolyTree&& polytree, Paths& paths)
{
paths.clear();
paths.reserve(polytree.Total());
AddPolyNodeToPaths(std::move(polytree), ntAny, paths);
}
//------------------------------------------------------------------------------
void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths)
{
paths.resize(0);
paths.clear();
paths.reserve(polytree.Total());
AddPolyNodeToPaths(polytree, ntClosed, paths);
}
@ -4129,7 +4150,7 @@ void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths)
void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths)
{
paths.resize(0);
paths.clear();
paths.reserve(polytree.Total());
//Open paths are top level only, so ...
for (int i = 0; i < polytree.ChildCount(); ++i)

View File

@ -206,6 +206,7 @@ void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, bool
void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution);
void PolyTreeToPaths(const PolyTree& polytree, Paths& paths);
void PolyTreeToPaths(PolyTree&& polytree, Paths& paths);
void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths);
void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths);

View File

@ -13,6 +13,7 @@
#include <Eigen/Geometry>
#include "BoundingBox.hpp"
#include "Utils.hpp" // for next_highest_power_of_2()
// Definition of the ray intersection hit structure.
@ -217,6 +218,23 @@ using Tree3f = Tree<3, float>;
using Tree2d = Tree<2, double>;
using Tree3d = Tree<3, double>;
// Wrap a 2D Slic3r own BoundingBox to be passed to Tree::build() and similar
// to build an AABBTree over coord_t 2D bounding boxes.
class BoundingBoxWrapper {
public:
using BoundingBox = Eigen::AlignedBox<coord_t, 2>;
BoundingBoxWrapper(const size_t idx, const Slic3r::BoundingBox &bbox) :
m_idx(idx),
// Inflate the bounding box a bit to account for numerical issues.
m_bbox(bbox.min - Point(SCALED_EPSILON, SCALED_EPSILON), bbox.max + Point(SCALED_EPSILON, SCALED_EPSILON)) {}
size_t idx() const { return m_idx; }
const BoundingBox& bbox() const { return m_bbox; }
Point centroid() const { return ((m_bbox.min().cast<int64_t>() + m_bbox.max().cast<int64_t>()) / 2).cast<int32_t>(); }
private:
size_t m_idx;
BoundingBox m_bbox;
};
namespace detail {
template<typename AVertexType, typename AIndexedFaceType, typename ATreeType, typename AVectorType>
struct RayIntersector {

View File

@ -0,0 +1,539 @@
#include "RegionExpansion.hpp"
#include <libslic3r/AABBTreeIndirect.hpp>
#include <libslic3r/ClipperZUtils.hpp>
#include <libslic3r/ClipperUtils.hpp>
#include <libslic3r/Utils.hpp>
#include <numeric>
namespace Slic3r {
namespace Algorithm {
// 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));
}
RegionExpansionParameters RegionExpansionParameters::build(
// 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);
RegionExpansionParameters out;
// 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.
out.tiny_expansion = std::min(0.25f * full_expansion, scaled<float>(0.05f));
size_t nsteps = size_t(ceil((full_expansion - out.tiny_expansion) / expansion_step));
if (max_nr_expansion_steps > 0)
nsteps = std::min(nsteps, max_nr_expansion_steps);
assert(nsteps > 0);
out.initial_step = (full_expansion - out.tiny_expansion) / nsteps;
if (nsteps > 1 && 0.25 * out.initial_step < out.tiny_expansion) {
// Decrease the step size by lowering number of steps.
nsteps = std::max<size_t>(1, (floor((full_expansion - out.tiny_expansion) / (4. * out.tiny_expansion))));
out.initial_step = (full_expansion - out.tiny_expansion) / nsteps;
}
if (0.25 * out.initial_step < out.tiny_expansion || nsteps == 1) {
out.tiny_expansion = 0.2f * full_expansion;
out.initial_step = 0.8f * full_expansion;
}
out.other_step = out.initial_step;
out.num_other_steps = nsteps - 1;
// Accuracy of the offsetter for wave propagation.
out.arc_tolerance = scaled<double>(0.1);
out.shortest_edge_length = out.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.
out.max_inflation = (out.tiny_expansion + nsteps * out.initial_step) * 1.1;
// (clipper_round_offset_error(out.tiny_expansion, co.ArcTolerance) + nsteps * clipper_round_offset_error(out.initial_step, co.ArcTolerance) * 1.5; // Account for uncertainty
return out;
}
// 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(); }));
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, base_idx));
}
++ base_idx;
}
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, with one exception: The anchor expands into a closed hole.
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;
}
}
using AABBTreeBBoxes = AABBTreeIndirect::Tree<2, coord_t>;
static AABBTreeBBoxes build_aabb_tree_over_expolygons(const ExPolygons &expolygons)
{
// Calculate bounding boxes of internal slices.
std::vector<AABBTreeIndirect::BoundingBoxWrapper> bboxes;
bboxes.reserve(expolygons.size());
for (size_t i = 0; i < expolygons.size(); ++ i)
bboxes.emplace_back(i, get_extents(expolygons[i].contour));
// Build AABB tree over bounding boxes of boundary expolygons.
AABBTreeBBoxes out;
out.build_modify_input(bboxes);
return out;
}
static int sample_in_expolygons(
// AABB tree over boundary expolygons
const AABBTreeBBoxes &aabb_tree,
const ExPolygons &expolygons,
const Point &sample)
{
int out = -1;
AABBTreeIndirect::traverse(aabb_tree,
[&sample](const AABBTreeBBoxes::Node &node) {
return node.bbox.contains(sample);
},
[&expolygons, &sample, &out](const AABBTreeBBoxes::Node &node) {
assert(node.is_leaf());
assert(node.is_valid());
if (expolygons[node.idx].contains(sample)) {
out = int(node.idx);
// Stop traversal.
return false;
}
// Continue traversal.
return true;
});
return out;
}
std::vector<WaveSeed> wave_seeds(
// Source regions that are supposed to touch the boundary.
const ExPolygons &src,
// Boundaries of source regions touching the "boundary" regions will be expanded into the "boundary" region.
const ExPolygons &boundary,
// Initial expansion of src to make the source regions intersect with boundary regions just a bit.
float tiny_expansion,
// Sort output by boundary ID and source ID.
bool sorted)
{
assert(tiny_expansion > 0);
if (src.empty())
return {};
using Intersection = ClipperZUtils::ClipperZIntersectionVisitor::Intersection;
using Intersections = ClipperZUtils::ClipperZIntersectionVisitor::Intersections;
ClipperLib_Z::Paths segments;
Intersections intersections;
coord_t idx_boundary_begin = 1;
coord_t idx_boundary_end = idx_boundary_begin;
coord_t idx_src_end;
{
ClipperLib_Z::Clipper zclipper;
ClipperZUtils::ClipperZIntersectionVisitor visitor(intersections);
zclipper.ZFillFunction(visitor.clipper_callback());
// as closed contours
zclipper.AddPaths(ClipperZUtils::expolygons_to_zpaths(boundary, idx_boundary_end), ClipperLib_Z::ptClip, true);
// as open contours
std::vector<std::pair<ClipperLib_Z::IntPoint, int>> zsrc_splits;
{
idx_src_end = idx_boundary_end;
ClipperLib_Z::Paths zsrc = expolygons_to_zpaths_expanded_opened(src, tiny_expansion, idx_src_end);
zclipper.AddPaths(zsrc, ClipperLib_Z::ptSubject, false);
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), segments);
merge_splits(segments, zsrc_splits);
}
// AABBTree over bounding boxes of boundaries.
// Only built if necessary, that is if any of the seed contours is closed, thus there is no intersection point
// with the boundary and all Z coordinates of the closed contour point to the source contour.
AABBTreeBBoxes aabb_tree;
// 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.
WaveSeeds out;
out.reserve(segments.size());
int iseed = 0;
for (const ClipperLib_Z::Path &path : segments) {
assert(path.size() >= 2);
const ClipperLib_Z::IntPoint &front = path.front();
const ClipperLib_Z::IntPoint &back = path.back();
// Both ends of a seed segment are supposed to be inside a single boundary expolygon.
// Thus as long as the seed contour is not closed, it should be open at a boundary point.
assert((front == back && front.z() >= idx_boundary_end && front.z() < idx_src_end) ||
//(front.z() < 0 && back.z() < 0));
// Hope that at least one end of an open polyline is clipped by the boundary, thus an intersection point is created.
(front.z() < 0 || back.z() < 0));
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.
out.push_back({ uint32_t(intersection->second - idx_boundary_end), uint32_t(intersection->first - 1), ClipperZUtils::from_zpath(path) });
} else {
// This should be a closed contour.
assert(front == back && front.z() >= idx_boundary_end && front.z() < idx_src_end);
// Find a source boundary expolygon of one sample of this closed path.
if (aabb_tree.empty())
aabb_tree = build_aabb_tree_over_expolygons(boundary);
int boundary_id = sample_in_expolygons(aabb_tree, boundary, Point(front.x(), front.y()));
// Boundary that contains the sample point was found.
assert(boundary_id >= 0);
if (boundary_id >= 0)
out.push_back({ uint32_t(front.z() - idx_boundary_end), uint32_t(boundary_id), ClipperZUtils::from_zpath(path) });
}
++ iseed;
}
if (sorted)
// Sort the seeds by their intersection boundary and source contour.
std::sort(out.begin(), out.end(), lower_by_boundary_and_src);
return out;
}
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) {
assert(path.size() >= 2);
co.Clear();
co.AddPath(path, jtRound, path.front() == path.back() ? ClipperLib::etClosedLine : 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);
}
// Resulting regions are sorted by boundary id and source id.
std::vector<RegionExpansion> propagate_waves(const WaveSeeds &seeds, const ExPolygons &boundary, const RegionExpansionParameters &params)
{
std::vector<RegionExpansion> out;
ClipperLib::Paths paths;
ClipperLib::ClipperOffset co;
co.ArcTolerance = params.arc_tolerance;
co.ShortestEdgeLength = params.shortest_edge_length;
for (auto it_seed = seeds.begin(); it_seed != seeds.end();) {
auto it = it_seed;
paths.clear();
for (; it != seeds.end() && it->boundary == it_seed->boundary && it->src == it_seed->src; ++ it)
paths.emplace_back(it->path);
// Propagate the wavefront while clipping it with the trimmed boundary.
// Collect the expanded polygons, merge them with the source polygons.
RegionExpansion re;
for (Polygon &polygon : propagate_wave_from_boundary(co, paths, boundary[it_seed->boundary], params.initial_step, params.other_step, params.num_other_steps, params.max_inflation))
out.push_back({ std::move(polygon), it_seed->src, it_seed->boundary });
it_seed = it;
}
return out;
}
std::vector<RegionExpansion> propagate_waves(const ExPolygons &src, const ExPolygons &boundary, const RegionExpansionParameters &params)
{
return propagate_waves(wave_seeds(src, boundary, params.tiny_expansion, true), boundary, params);
}
std::vector<RegionExpansion> propagate_waves(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)
{
return propagate_waves(src, boundary, RegionExpansionParameters::build(expansion, expansion_step, max_nr_steps));
}
// Returns regions per source ExPolygon expanded into boundary.
std::vector<RegionExpansionEx> propagate_waves_ex(const WaveSeeds &seeds, const ExPolygons &boundary, const RegionExpansionParameters &params)
{
std::vector<RegionExpansion> expanded = propagate_waves(seeds, boundary, params);
assert(std::is_sorted(seeds.begin(), seeds.end(), [](const auto &l, const auto &r){ return l.boundary < r.boundary || (l.boundary == r.boundary && l.src < r.src); }));
Polygons acc;
std::vector<RegionExpansionEx> out;
for (auto it = expanded.begin(); it != expanded.end(); ) {
auto it2 = it;
acc.clear();
for (; it2 != expanded.end() && it2->boundary_id == it->boundary_id && it2->src_id == it->src_id; ++ it2)
acc.emplace_back(std::move(it2->polygon));
size_t size = it2 - it;
if (size == 1)
out.push_back({ ExPolygon{std::move(acc.front())}, it->src_id, it->boundary_id });
else {
ExPolygons expolys = union_ex(acc);
reserve_more_power_of_2(out, expolys.size());
for (ExPolygon &ex : expolys)
out.push_back({ std::move(ex), it->src_id, it->boundary_id });
}
it = it2;
}
return out;
}
// Returns regions per source ExPolygon expanded into boundary.
std::vector<RegionExpansionEx> propagate_waves_ex(
// 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)
{
auto params = RegionExpansionParameters::build(full_expansion, expansion_step, max_nr_expansion_steps);
return propagate_waves_ex(wave_seeds(src, boundary, params.tiny_expansion, true), boundary, params);
}
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)
{
std::vector<Polygons> out(src.size(), Polygons{});
for (RegionExpansion &r : propagate_waves(src, boundary, expansion, expansion_step, max_nr_steps))
out[r.src_id].emplace_back(std::move(r.polygon));
return out;
}
std::vector<ExPolygon> expand_merge_expolygons(ExPolygons &&src, const ExPolygons &boundary, const RegionExpansionParameters &params)
{
// expanded regions are sorted by boundary id and source id
std::vector<RegionExpansion> expanded = propagate_waves(src, boundary, params);
// expanded regions will be merged into source regions, thus they will be re-sorted by source id.
std::sort(expanded.begin(), expanded.end(), [](const auto &l, const auto &r) { return l.src_id < r.src_id; });
uint32_t last = 0;
Polygons acc;
ExPolygons out;
out.reserve(src.size());
for (auto it = expanded.begin(); it != expanded.end();) {
for (; last < it->src_id; ++ last)
out.emplace_back(std::move(src[last]));
acc.clear();
assert(it->src_id == last);
for (; it != expanded.end() && it->src_id == last; ++ it)
acc.emplace_back(std::move(it->polygon));
//FIXME offset & merging could be more efficient, for example one does not need to copy the source expolygon
ExPolygon &src_ex = src[last ++];
assert(! src_ex.contour.empty());
#if 0
{
static int iRun = 0;
BoundingBox bbox = get_extents(acc);
bbox.merge(get_extents(src_ex));
SVG svg(debug_out_path("expand_merge_expolygons-failed-union=%d.svg", iRun ++).c_str(), bbox);
svg.draw(acc);
svg.draw_outline(acc, "black", scale_(0.05));
svg.draw(src_ex, "red");
svg.Close();
}
#endif
Point sample = src_ex.contour.front();
append(acc, to_polygons(std::move(src_ex)));
ExPolygons merged = union_safety_offset_ex(acc);
// Expanding one expolygon by waves should not change connectivity of the source expolygon:
// Single expolygon should be produced possibly with increased number of holes.
if (merged.size() > 1) {
// assert(merged.size() == 1);
// There is something wrong with the initial waves. Most likely the bridge was not valid at all
// or the boundary region was very close to some bridge edge, but not really touching.
// Pick only a single merged expolygon, which contains one sample point of the source expolygon.
auto aabb_tree = build_aabb_tree_over_expolygons(merged);
int id = sample_in_expolygons(aabb_tree, merged, sample);
assert(id != -1);
if (id != -1)
out.emplace_back(std::move(merged[id]));
} else if (merged.size() == 1)
out.emplace_back(std::move(merged.front()));
}
for (; last < uint32_t(src.size()); ++ last)
out.emplace_back(std::move(src[last]));
return out;
}
} // Algorithm
} // Slic3r

View File

@ -0,0 +1,114 @@
#ifndef SRC_LIBSLIC3R_ALGORITHM_REGION_EXPANSION_HPP_
#define SRC_LIBSLIC3R_ALGORITHM_REGION_EXPANSION_HPP_
#include <cstdint>
#include <libslic3r/Point.hpp>
#include <libslic3r/Polygon.hpp>
#include <libslic3r/ExPolygon.hpp>
namespace Slic3r {
namespace Algorithm {
struct RegionExpansionParameters
{
// 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;
// Accuracy of the offsetter for wave propagation.
double arc_tolerance;
double shortest_edge_length;
static RegionExpansionParameters build(
// 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);
};
struct WaveSeed {
uint32_t src;
uint32_t boundary;
Points path;
};
using WaveSeeds = std::vector<WaveSeed>;
inline bool lower_by_boundary_and_src(const WaveSeed &l, const WaveSeed &r)
{
return l.boundary < r.boundary || (l.boundary == r.boundary && l.src < r.src);
}
inline bool lower_by_src_and_boundary(const WaveSeed &l, const WaveSeed &r)
{
return l.src < r.src || (l.src == r.src && l.boundary < r.boundary);
}
// Expand src slightly outwards to intersect boundaries, trim the offsetted src polylines by the boundaries.
// Return the trimmed paths annotated with their origin (source of the path, index of the boundary region).
WaveSeeds wave_seeds(
// Source regions that are supposed to touch the boundary.
const ExPolygons &src,
// Boundaries of source regions touching the "boundary" regions will be expanded into the "boundary" region.
const ExPolygons &boundary,
// Initial expansion of src to make the source regions intersect with boundary regions just a bit.
float tiny_expansion,
bool sorted);
struct RegionExpansion
{
Polygon polygon;
uint32_t src_id;
uint32_t boundary_id;
};
std::vector<RegionExpansion> propagate_waves(const WaveSeeds &seeds, const ExPolygons &boundary, const RegionExpansionParameters &params);
std::vector<RegionExpansion> propagate_waves(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);
struct RegionExpansionEx
{
ExPolygon expolygon;
uint32_t src_id;
uint32_t boundary_id;
};
std::vector<RegionExpansionEx> propagate_waves_ex(const WaveSeeds &seeds, const ExPolygons &boundary, const RegionExpansionParameters &params);
std::vector<RegionExpansionEx> propagate_waves_ex(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);
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);
std::vector<ExPolygon> expand_merge_expolygons(ExPolygons &&src, const ExPolygons &boundary, const RegionExpansionParameters &params);
} // 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

@ -75,12 +75,9 @@ private:
//return ideal bridge direction and unsupported bridge endpoints distance.
inline std::tuple<Vec2d, double> detect_bridging_direction(const Polygons &to_cover, const Polygons &anchors_area)
inline std::tuple<Vec2d, double> detect_bridging_direction(const Lines &floating_edges, const Polygons &overhang_area)
{
Polygons overhang_area = diff(to_cover, anchors_area);
Polylines floating_polylines = diff_pl(to_polylines(overhang_area), expand(anchors_area, float(SCALED_EPSILON)));
if (floating_polylines.empty()) {
if (floating_edges.empty()) {
// consider this area anchored from all sides, pick bridging direction that will likely yield shortest bridges
auto [pc1, pc2] = compute_principal_components(overhang_area);
if (pc2 == Vec2f::Zero()) { // overhang may be smaller than resolution. In this case, any direction is ok
@ -91,7 +88,6 @@ inline std::tuple<Vec2d, double> detect_bridging_direction(const Polygons &to_co
}
// Overhang is not fully surrounded by anchors, in that case, find such direction that will minimize the number of bridge ends/180turns in the air
Lines floating_edges = to_lines(floating_polylines);
std::unordered_map<double, Vec2d> directions{};
for (const Line &l : floating_edges) {
Vec2d normal = l.normal().cast<double>().normalized();
@ -125,6 +121,13 @@ inline std::tuple<Vec2d, double> detect_bridging_direction(const Polygons &to_co
return {result_dir, min_cost};
};
//return ideal bridge direction and unsupported bridge endpoints distance.
inline std::tuple<Vec2d, double> detect_bridging_direction(const Polygons &to_cover, const Polygons &anchors_area)
{
Polygons overhang_area = diff(to_cover, anchors_area);
Lines floating_edges = to_lines(diff_pl(to_polylines(overhang_area), expand(anchors_area, float(SCALED_EPSILON))));
return detect_bridging_direction(floating_edges, overhang_area);
}
}

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
AnyPtr.hpp
BoundingBox.cpp
BoundingBox.hpp
@ -36,6 +38,7 @@ set(SLIC3R_SOURCES
clipper.hpp
ClipperUtils.cpp
ClipperUtils.hpp
ClipperZUtils.hpp
Color.cpp
Color.hpp
Config.cpp
@ -79,6 +82,8 @@ set(SLIC3R_SOURCES
Fill/FillBase.hpp
Fill/FillConcentric.cpp
Fill/FillConcentric.hpp
Fill/FillEnsuring.cpp
Fill/FillEnsuring.hpp
Fill/FillHoneycomb.cpp
Fill/FillHoneycomb.hpp
Fill/FillGyroid.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
@ -720,6 +718,8 @@ Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfac
{ return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::SurfacesProvider(subject), ClipperUtils::SurfacesProvider(clip), do_safety_offset); }
Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
{ return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::SurfacesPtrProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); }
Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset)
{ return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::SurfacesPtrProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); }
Slic3r::ExPolygons intersection_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
{ return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); }
@ -1062,7 +1062,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
@ -370,6 +373,8 @@ inline Slic3r::Polygons shrink(const Slic3r::Polygons &polygons, const float d
{ assert(delta > 0); return offset(polygons, -delta, joinType, miterLimit); }
inline Slic3r::ExPolygons shrink_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ assert(delta > 0); return offset_ex(polygons, -delta, joinType, miterLimit); }
inline Slic3r::ExPolygons shrink_ex(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
{ assert(delta > 0); return offset_ex(polygons, -delta, joinType, miterLimit); }
// Wherever applicable, please use the opening() / closing() variants instead, they convey their purpose better.
// Input polygons for negative offset shall be "normalized": There must be no overlap / intersections between the input polygons.
@ -430,6 +435,7 @@ Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::ExPoly
Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
Slic3r::Polylines diff_pl(const Slic3r::Polyline &subject, const Slic3r::Polygons &clip);
Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip);
Slic3r::Polylines diff_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygon &clip);
@ -601,6 +607,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 <numeric>
#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(); }));
for (const ExPolygon &expoly : src) {
out.emplace_back(to_zpath<Open>(expoly.contour.points, base_idx));
for (const Polygon &hole : expoly.holes)
out.emplace_back(to_zpath<Open>(hole.points, base_idx));
++ base_idx;
}
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

@ -303,7 +303,7 @@ template <class T>
class ConfigOptionSingle : public ConfigOption {
public:
T value;
explicit ConfigOptionSingle(T value) : value(value) {}
explicit ConfigOptionSingle(T value) : value(std::move(value)) {}
operator T() const { return this->value; }
void set(const ConfigOption *rhs) override
@ -847,9 +847,9 @@ using ConfigOptionIntsNullable = ConfigOptionIntsTempl<true>;
class ConfigOptionString : public ConfigOptionSingle<std::string>
{
public:
ConfigOptionString() : ConfigOptionSingle<std::string>("") {}
explicit ConfigOptionString(const std::string &value) : ConfigOptionSingle<std::string>(value) {}
ConfigOptionString() : ConfigOptionSingle<std::string>(std::string{}) {}
explicit ConfigOptionString(std::string value) : ConfigOptionSingle<std::string>(std::move(value)) {}
static ConfigOptionType static_type() { return coString; }
ConfigOptionType type() const override { return static_type(); }
ConfigOption* clone() const override { return new ConfigOptionString(*this); }

View File

@ -15,10 +15,12 @@
#include "FillRectilinear.hpp"
#include "FillLightning.hpp"
#include "FillConcentric.hpp"
#include "FillEnsuring.hpp"
namespace Slic3r {
static constexpr const float NarrowInfillAreaThresholdMM = 3.f;
struct SurfaceFillParams
{
// Zero based extruder ID.
@ -159,7 +161,8 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
// Calculate the actual flow we'll be using for this infill.
params.bridge = is_bridge || Fill::use_bridge_flow(params.pattern);
params.flow = params.bridge ?
layerm.bridging_flow(extrusion_role) :
// Always enable thick bridges for internal bridges.
layerm.bridging_flow(extrusion_role, surface.is_bridge() && ! surface.is_external()) :
layerm.flow(extrusion_role, (surface.thickness == -1) ? layer.height : surface.thickness);
// Calculate flow spacing for infill pattern generation.
@ -302,6 +305,46 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
}
}
// Detect narrow internal solid infill area and use ipEnsuring pattern instead.
{
std::vector<char> narrow_expolygons;
static constexpr const auto narrow_pattern = ipEnsuring;
for (size_t surface_fill_id = 0, num_old_fills = surface_fills.size(); surface_fill_id < num_old_fills; ++ surface_fill_id)
if (SurfaceFill &fill = surface_fills[surface_fill_id]; fill.surface.surface_type == stInternalSolid) {
size_t num_expolygons = fill.expolygons.size();
narrow_expolygons.clear();
narrow_expolygons.reserve(num_expolygons);
// Detect narrow expolygons.
int num_narrow = 0;
for (const ExPolygon &ex : fill.expolygons) {
bool narrow = offset_ex(ex, -scaled<float>(NarrowInfillAreaThresholdMM)).empty();
num_narrow += int(narrow);
narrow_expolygons.emplace_back(narrow);
}
if (num_narrow == num_expolygons) {
// All expolygons are narrow, change the fill pattern.
fill.params.pattern = narrow_pattern;
} else if (num_narrow > 0) {
// Some expolygons are narrow, split the fills.
params = fill.params;
params.pattern = narrow_pattern;
surface_fills.emplace_back(params);
SurfaceFill &old_fill = surface_fills[surface_fill_id];
SurfaceFill &new_fill = surface_fills.back();
new_fill.region_id = old_fill.region_id;
new_fill.surface.surface_type = stInternalSolid;
new_fill.surface.thickness = old_fill.surface.thickness;
new_fill.expolygons.reserve(num_narrow);
for (size_t i = 0; i < narrow_expolygons.size(); ++ i)
if (narrow_expolygons[i])
new_fill.expolygons.emplace_back(std::move(old_fill.expolygons[i]));
old_fill.expolygons.erase(std::remove_if(old_fill.expolygons.begin(), old_fill.expolygons.end(),
[&narrow_expolygons, ex_first = old_fill.expolygons.data()](const ExPolygon& ex) { return narrow_expolygons[&ex - ex_first]; }),
old_fill.expolygons.end());
}
}
}
return surface_fills;
}
@ -443,6 +486,14 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
size_t first_object_layer_id = this->object()->get_layer(0)->id();
for (SurfaceFill &surface_fill : surface_fills) {
//skip patterns for which additional input is nullptr
switch (surface_fill.params.pattern) {
case ipLightning: if (lightning_generator == nullptr) continue; break;
case ipAdaptiveCubic: if (adaptive_fill_octree == nullptr) continue; break;
case ipSupportCubic: if (support_fill_octree == nullptr) continue; break;
default: break;
}
// Create the filler object.
std::unique_ptr<Fill> f = std::unique_ptr<Fill>(Fill::new_from_type(surface_fill.params.pattern));
f->set_bounding_box(bbox);
@ -452,7 +503,9 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
f->layer_id = this->id() - first_object_layer_id;
f->z = this->print_z;
f->angle = surface_fill.params.angle;
f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree;
f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree;
f->print_config = &this->object()->print()->config();
f->print_object_config = &this->object()->config();
if (surface_fill.params.pattern == ipLightning) {
auto *lf = dynamic_cast<FillLightning::Filler*>(f.get());
@ -460,11 +513,10 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
lf->num_raft_layers = this->object()->slicing_parameters().raft_layers();
}
if (perimeter_generator.value == PerimeterGeneratorType::Arachne && surface_fill.params.pattern == ipConcentric) {
FillConcentric *fill_concentric = dynamic_cast<FillConcentric *>(f.get());
assert(fill_concentric != nullptr);
fill_concentric->print_config = &this->object()->print()->config();
fill_concentric->print_object_config = &this->object()->config();
if (surface_fill.params.pattern == ipEnsuring) {
auto *fill_bounded_rectilinear = dynamic_cast<FillEnsuring *>(f.get());
assert(fill_bounded_rectilinear != nullptr);
fill_bounded_rectilinear->print_region_config = &m_regions[surface_fill.region_id]->region().config();
}
// calculate flow spacing for infill pattern generation
@ -494,7 +546,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
params.anchor_length = surface_fill.params.anchor_length;
params.anchor_length_max = surface_fill.params.anchor_length_max;
params.resolution = resolution;
params.use_arachne = perimeter_generator == PerimeterGeneratorType::Arachne && surface_fill.params.pattern == ipConcentric;
params.use_arachne = (perimeter_generator == PerimeterGeneratorType::Arachne && surface_fill.params.pattern == ipConcentric) || surface_fill.params.pattern == ipEnsuring;
params.layer_height = layerm.layer()->height;
for (ExPolygon &expoly : surface_fill.expolygons) {
@ -595,6 +647,94 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
#endif
}
Polylines Layer::generate_sparse_infill_polylines_for_anchoring() const
{
std::vector<SurfaceFill> surface_fills = group_fills(*this);
const Slic3r::BoundingBox bbox = this->object()->bounding_box();
const auto resolution = this->object()->print()->config().gcode_resolution.value;
Polylines sparse_infill_polylines{};
for (SurfaceFill &surface_fill : surface_fills) {
// skip patterns for which additional input is nullptr
switch (surface_fill.params.pattern) {
case ipLightning: continue; break;
case ipAdaptiveCubic: continue; break;
case ipSupportCubic: continue; break;
case ipCount: continue; break;
case ipSupportBase: continue; break;
case ipEnsuring: continue; break;
case ipRectilinear:
case ipMonotonic:
case ipMonotonicLines:
case ipAlignedRectilinear:
case ipGrid:
case ipTriangles:
case ipStars:
case ipCubic:
case ipLine:
case ipConcentric:
case ipHoneycomb:
case ip3DHoneycomb:
case ipGyroid:
case ipHilbertCurve:
case ipArchimedeanChords:
case ipOctagramSpiral: break;
}
// Create the filler object.
std::unique_ptr<Fill> f = std::unique_ptr<Fill>(Fill::new_from_type(surface_fill.params.pattern));
f->set_bounding_box(bbox);
f->layer_id = this->id();
f->z = this->print_z;
f->angle = surface_fill.params.angle;
// f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree;
f->print_config = &this->object()->print()->config();
f->print_object_config = &this->object()->config();
// calculate flow spacing for infill pattern generation
double link_max_length = 0.;
if (!surface_fill.params.bridge) {
#if 0
link_max_length = layerm.region()->config().get_abs_value(surface.is_external() ? "external_fill_link_max_length" : "fill_link_max_length", flow.spacing());
// printf("flow spacing: %f, is_external: %d, link_max_length: %lf\n", flow.spacing(), int(surface.is_external()), link_max_length);
#else
if (surface_fill.params.density > 80.) // 80%
link_max_length = 3. * f->spacing;
#endif
}
// Maximum length of the perimeter segment linking two infill lines.
f->link_max_length = (coord_t) scale_(link_max_length);
// Used by the concentric infill pattern to clip the loops to create extrusion paths.
f->loop_clipping = coord_t(scale_(surface_fill.params.flow.nozzle_diameter()) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER);
LayerRegion &layerm = *m_regions[surface_fill.region_id];
// apply half spacing using this flow's own spacing and generate infill
FillParams params;
params.density = float(0.01 * surface_fill.params.density);
params.dont_adjust = false; // surface_fill.params.dont_adjust;
params.anchor_length = surface_fill.params.anchor_length;
params.anchor_length_max = surface_fill.params.anchor_length_max;
params.resolution = resolution;
params.use_arachne = false;
params.layer_height = layerm.layer()->height;
for (ExPolygon &expoly : surface_fill.expolygons) {
// Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon.
f->spacing = surface_fill.params.spacing;
surface_fill.surface.expolygon = std::move(expoly);
try {
Polylines polylines = f->fill_surface(&surface_fill.surface, params);
sparse_infill_polylines.insert(sparse_infill_polylines.end(), polylines.begin(), polylines.end());
} catch (InfillFailedException &) {}
}
}
return sparse_infill_polylines;
}
// Create ironing extrusions over top surfaces.
void Layer::make_ironing()
{

View File

@ -20,6 +20,7 @@
#include "FillRectilinear.hpp"
#include "FillAdaptive.hpp"
#include "FillLightning.hpp"
#include "FillEnsuring.hpp"
#include <boost/log/trivial.hpp>
@ -50,6 +51,7 @@ Fill* Fill::new_from_type(const InfillPattern type)
case ipSupportCubic: return new FillAdaptive::Filler();
case ipSupportBase: return new FillSupportBase();
case ipLightning: return new FillLightning::Filler();
case ipEnsuring: return new FillEnsuring();
default: throw Slic3r::InvalidArgument("unknown type");
}
}

View File

@ -91,6 +91,10 @@ public:
// Octree builds on mesh for usage in the adaptive cubic infill
FillAdaptive::Octree* adapt_fill_octree = nullptr;
// PrintConfig and PrintObjectConfig are used by infills that use Arachne (Concentric and FillEnsuring).
const PrintConfig *print_config = nullptr;
const PrintObjectConfig *print_object_config = nullptr;
public:
virtual ~Fill() {}
virtual Fill* clone() const = 0;

View File

@ -97,14 +97,8 @@ void FillConcentric::_fill_surface_single(const FillParams &params,
continue;
ThickPolyline thick_polyline = Arachne::to_thick_polyline(*extrusion);
if (extrusion->is_closed && thick_polyline.points.front() == thick_polyline.points.back() && thick_polyline.width.front() == thick_polyline.width.back()) {
thick_polyline.points.pop_back();
assert(thick_polyline.points.size() * 2 == thick_polyline.width.size());
int nearest_idx = nearest_point_index(thick_polyline.points, last_pos);
std::rotate(thick_polyline.points.begin(), thick_polyline.points.begin() + nearest_idx, thick_polyline.points.end());
std::rotate(thick_polyline.width.begin(), thick_polyline.width.begin() + 2 * nearest_idx, thick_polyline.width.end());
thick_polyline.points.emplace_back(thick_polyline.points.front());
}
if (extrusion->is_closed)
thick_polyline.start_at_index(nearest_point_index(thick_polyline.points, last_pos));
thick_polylines_out.emplace_back(std::move(thick_polyline));
last_pos = thick_polylines_out.back().last_point();
}

View File

@ -26,11 +26,6 @@ protected:
ThickPolylines &thick_polylines_out) override;
bool no_sort() const override { return true; }
const PrintConfig *print_config = nullptr;
const PrintObjectConfig *print_object_config = nullptr;
friend class Layer;
};
} // namespace Slic3r

View File

@ -0,0 +1,83 @@
#include "../ClipperUtils.hpp"
#include "../ShortestPath.hpp"
#include "../Arachne/WallToolPaths.hpp"
#include "FillEnsuring.hpp"
#include <boost/log/trivial.hpp>
namespace Slic3r {
ThickPolylines FillEnsuring::fill_surface_arachne(const Surface *surface, const FillParams &params)
{
assert(params.use_arachne);
assert(this->print_config != nullptr && this->print_object_config != nullptr && this->print_region_config != nullptr);
const coord_t scaled_spacing = scaled<coord_t>(this->spacing);
// Perform offset.
Slic3r::ExPolygons expp = this->overlap != 0. ? offset_ex(surface->expolygon, scaled<float>(this->overlap)) : ExPolygons{surface->expolygon};
// Create the infills for each of the regions.
ThickPolylines thick_polylines_out;
for (ExPolygon &ex_poly : expp) {
Point bbox_size = ex_poly.contour.bounding_box().size();
coord_t loops_count = std::max(bbox_size.x(), bbox_size.y()) / scaled_spacing + 1;
Polygons polygons = to_polygons(ex_poly);
Arachne::WallToolPaths wall_tool_paths(polygons, scaled_spacing, scaled_spacing, loops_count, 0, params.layer_height, *this->print_object_config, *this->print_config);
if (std::vector<Arachne::VariableWidthLines> loops = wall_tool_paths.getToolPaths(); !loops.empty()) {
assert(!is_bounded_rectilinear || loops.size() == 1);
std::vector<const Arachne::ExtrusionLine *> all_extrusions;
for (Arachne::VariableWidthLines &loop : loops) {
if (loop.empty())
continue;
for (const Arachne::ExtrusionLine &wall : loop)
all_extrusions.emplace_back(&wall);
}
// Split paths using a nearest neighbor search.
size_t firts_poly_idx = thick_polylines_out.size();
Point last_pos(0, 0);
for (const Arachne::ExtrusionLine *extrusion : all_extrusions) {
if (extrusion->empty())
continue;
ThickPolyline thick_polyline = Arachne::to_thick_polyline(*extrusion);
if (thick_polyline.length() == 0.)
//FIXME this should not happen.
continue;
assert(thick_polyline.size() > 1);
assert(thick_polyline.length() > 0.);
//assert(thick_polyline.points.size() == thick_polyline.width.size());
if (extrusion->is_closed)
thick_polyline.start_at_index(nearest_point_index(thick_polyline.points, last_pos));
assert(thick_polyline.size() > 1);
//assert(thick_polyline.points.size() == thick_polyline.width.size());
thick_polylines_out.emplace_back(std::move(thick_polyline));
last_pos = thick_polylines_out.back().last_point();
}
// clip the paths to prevent the extruder from getting exactly on the first point of the loop
// Keep valid paths only.
size_t j = firts_poly_idx;
for (size_t i = firts_poly_idx; i < thick_polylines_out.size(); ++i) {
assert(thick_polylines_out[i].size() > 1);
assert(thick_polylines_out[i].length() > 0.);
//assert(thick_polylines_out[i].points.size() == thick_polylines_out[i].width.size());
thick_polylines_out[i].clip_end(this->loop_clipping);
assert(thick_polylines_out[i].size() > 1);
if (thick_polylines_out[i].is_valid()) {
if (j < i)
thick_polylines_out[j] = std::move(thick_polylines_out[i]);
++j;
}
}
if (j < thick_polylines_out.size())
thick_polylines_out.erase(thick_polylines_out.begin() + int(j), thick_polylines_out.end());
}
}
return thick_polylines_out;
}
} // namespace Slic3r

View File

@ -0,0 +1,30 @@
#ifndef slic3r_FillEnsuring_hpp_
#define slic3r_FillEnsuring_hpp_
#include "FillBase.hpp"
#include "FillRectilinear.hpp"
namespace Slic3r {
class FillEnsuring : public FillRectilinear
{
public:
Fill *clone() const override { return new FillEnsuring(*this); }
~FillEnsuring() override = default;
Polylines fill_surface(const Surface *surface, const FillParams &params) override { return {}; };
ThickPolylines fill_surface_arachne(const Surface *surface, const FillParams &params) override;
protected:
void fill_surface_single_arachne(const Surface &surface, const FillParams &params, ThickPolylines &thick_polylines_out);
bool no_sort() const override { return true; }
// PrintRegionConfig is used for computing overlap between boundary contour and inner Rectilinear infill.
const PrintRegionConfig *print_region_config = nullptr;
friend class Layer;
};
} // namespace Slic3r
#endif // slic3r_FillEnsuring_hpp_

View File

@ -7,6 +7,7 @@
namespace Slic3r {
class PrintRegionConfig;
class Surface;
class FillRectilinear : public Fill

View File

@ -378,7 +378,8 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') :
(*c == extrusion_axis) ? 3 : (*c == 'F') ? 4 : size_t(-1);
if (axis != size_t(-1)) {
auto [pend, ec] = fast_float::from_chars(&*(++ c), sline.data() + sline.size(), new_pos[axis]);
//auto [pend, ec] =
fast_float::from_chars(&*(++ c), sline.data() + sline.size(), new_pos[axis]);
if (axis == 4) {
// Convert mm/min to mm/sec.
new_pos[4] /= 60.f;
@ -497,7 +498,8 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
bool has_S = pos_S > 0;
bool has_P = pos_P > 0;
if (has_S || has_P) {
auto [pend, ec] = fast_float::from_chars(sline.data() + (has_S ? pos_S : pos_P) + 1, sline.data() + sline.size(), line.time);
//auto [pend, ec] =
fast_float::from_chars(sline.data() + (has_S ? pos_S : pos_P) + 1, sline.data() + sline.size(), line.time);
if (has_P)
line.time *= 0.001f;
} else
@ -846,7 +848,8 @@ std::string CoolingBuffer::apply_layer_cooldown(
if (line->slowdown)
new_feedrate = int(floor(60. * line->feedrate + 0.5));
else
auto res = std::from_chars(fpos, line_end, new_feedrate);
//auto res =
std::from_chars(fpos, line_end, new_feedrate);
if (new_feedrate == current_feedrate) {
// No need to change the F value.
if ((line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_ADJUSTABLE_EMPTY | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) || line->length == 0.)

View File

@ -19,20 +19,7 @@ bool RetractWhenCrossingPerimeters::travel_inside_internal_regions(const Layer &
if (surface.is_internal())
m_internal_islands.emplace_back(&surface.expolygon);
// Calculate bounding boxes of internal slices.
class BBoxWrapper {
public:
BBoxWrapper(const size_t idx, const BoundingBox &bbox) :
m_idx(idx),
// Inflate the bounding box a bit to account for numerical issues.
m_bbox(bbox.min - Point(SCALED_EPSILON, SCALED_EPSILON), bbox.max + Point(SCALED_EPSILON, SCALED_EPSILON)) {}
size_t idx() const { return m_idx; }
const AABBTree::BoundingBox& bbox() const { return m_bbox; }
Point centroid() const { return ((m_bbox.min().cast<int64_t>() + m_bbox.max().cast<int64_t>()) / 2).cast<int32_t>(); }
private:
size_t m_idx;
AABBTree::BoundingBox m_bbox;
};
std::vector<BBoxWrapper> bboxes;
std::vector<AABBTreeIndirect::BoundingBoxWrapper> bboxes;
bboxes.reserve(m_internal_islands.size());
for (size_t i = 0; i < m_internal_islands.size(); ++ i)
bboxes.emplace_back(i, get_extents(*m_internal_islands[i]));

View File

@ -468,9 +468,6 @@ void MedialAxis::build(ThickPolylines* polylines)
}
*/
//typedef const VD::vertex_type vert_t;
using edge_t = const VD::edge_type;
// collect valid edges (i.e. prune those not belonging to MAT)
// note: this keeps twins, so it inserts twice the number of the valid edges
m_edge_data.assign(m_vd.edges().size() / 2, EdgeData{});

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"
@ -302,48 +302,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

@ -134,7 +134,7 @@ public:
Flow flow(FlowRole role) const;
Flow flow(FlowRole role, double layer_height) const;
Flow bridging_flow(FlowRole role) const;
Flow bridging_flow(FlowRole role, bool force_thick_bridges = false) const;
void slices_to_fill_surfaces_clipped();
void prepare_fill_surfaces();
@ -369,6 +369,7 @@ public:
// Phony version of make_fills() without parameters for Perl integration only.
void make_fills() { this->make_fills(nullptr, nullptr, nullptr); }
void make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator);
Polylines generate_sparse_infill_polylines_for_anchoring() const;
void make_ironing();
void export_region_slices_to_svg(const char *path) const;

View File

@ -8,6 +8,7 @@
#include "Surface.hpp"
#include "BoundingBox.hpp"
#include "SVG.hpp"
#include "Algorithm/RegionExpansion.hpp"
#include <string>
#include <map>
@ -26,12 +27,12 @@ Flow LayerRegion::flow(FlowRole role, double layer_height) const
return m_region->flow(*m_layer->object(), role, layer_height, m_layer->id() == 0);
}
Flow LayerRegion::bridging_flow(FlowRole role) const
Flow LayerRegion::bridging_flow(FlowRole role, bool force_thick_bridges) const
{
const PrintRegion &region = this->region();
const PrintRegionConfig &region_config = region.config();
const PrintObject &print_object = *this->layer()->object();
if (print_object.config().thick_bridges) {
if (print_object.config().thick_bridges || force_thick_bridges) {
// The old Slic3r way (different from all other slicers): Use rounded extrusions.
// Get the configured nozzle_diameter for the extruder associated to the flow role requested.
// Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will follback to zero'th element, so everything is all right.
@ -139,6 +140,273 @@ void LayerRegion::make_perimeters(
}
}
#if 1
// Extract surfaces of given type from surfaces, extract fill (layer) thickness of one of the surfaces.
static ExPolygons fill_surfaces_extract_expolygons(Surfaces &surfaces, SurfaceType surface_type, double &thickness)
{
size_t cnt = 0;
for (const Surface &surface : surfaces)
if (surface.surface_type == surface_type) {
++ cnt;
thickness = surface.thickness;
}
if (cnt == 0)
return {};
ExPolygons out;
out.reserve(cnt);
for (Surface &surface : surfaces)
if (surface.surface_type == surface_type)
out.emplace_back(std::move(surface.expolygon));
return out;
}
// Extract bridging surfaces from "surfaces", expand them into "shells" using expansion_params,
// detect bridges.
// Trim "shells" by the expanded bridges.
Surfaces expand_bridges_detect_orientations(
Surfaces &surfaces,
ExPolygons &shells,
const Algorithm::RegionExpansionParameters &expansion_params)
{
using namespace Slic3r::Algorithm;
double thickness;
ExPolygons bridges_ex = fill_surfaces_extract_expolygons(surfaces, stBottomBridge, thickness);
if (bridges_ex.empty())
return {};
// Calculate bridge anchors and their expansions in their respective shell region.
WaveSeeds bridge_anchors = wave_seeds(bridges_ex, shells, expansion_params.tiny_expansion, true);
std::vector<RegionExpansionEx> bridge_expansions = propagate_waves_ex(bridge_anchors, shells, expansion_params);
// Cache for detecting bridge orientation and merging regions with overlapping expansions.
struct Bridge {
ExPolygon expolygon;
uint32_t group_id;
std::vector<RegionExpansionEx>::const_iterator bridge_expansion_begin;
double angle = -1;
};
std::vector<Bridge> bridges;
{
bridges.reserve(bridges_ex.size());
uint32_t group_id = 0;
for (ExPolygon &ex : bridges_ex)
bridges.push_back({ std::move(ex), group_id ++, bridge_expansions.end() });
bridges_ex.clear();
}
// Group the bridge surfaces by overlaps.
auto group_id = [&bridges](uint32_t src_id) {
uint32_t group_id = bridges[src_id].group_id;
while (group_id != src_id) {
src_id = group_id;
group_id = bridges[src_id].group_id;
}
bridges[src_id].group_id = group_id;
return group_id;
};
{
// Cache of bboxes per expansion boundary.
std::vector<BoundingBox> bboxes;
// Detect overlaps of bridge anchors inside their respective shell regions.
// bridge_expansions are sorted by boundary id and source id.
for (auto it = bridge_expansions.begin(); it != bridge_expansions.end();) {
// For each boundary region:
auto it_begin = it;
auto it_end = std::next(it_begin);
for (; it_end != bridge_expansions.end() && it_end->boundary_id == it_begin->boundary_id; ++ it_end) ;
bboxes.clear();
bboxes.reserve(it_end - it_begin);
for (auto it2 = it_begin; it2 != it_end; ++ it2)
bboxes.emplace_back(get_extents(it2->expolygon.contour));
// For each bridge anchor of the current source:
for (; it != it_end; ++ it) {
// A grup id for this bridge.
for (auto it2 = std::next(it); it2 != it_end; ++ it2)
if (it->src_id != it2->src_id &&
bboxes[it - it_begin].overlap(bboxes[it2 - it_begin]) &&
// One may ignore holes, they are irrelevant for intersection test.
! intersection(it->expolygon.contour, it2->expolygon.contour).empty()) {
// The two bridge regions intersect. Give them the same group id.
uint32_t id = group_id(it->src_id);
uint32_t id2 = group_id(it2->src_id);
bridges[it->src_id].group_id = bridges[it2->src_id].group_id = std::min(id, id2);
}
}
}
}
// Detect bridge directions.
{
std::sort(bridge_anchors.begin(), bridge_anchors.end(), Algorithm::lower_by_src_and_boundary);
auto it_bridge_anchor = bridge_anchors.begin();
Lines lines;
Polygons anchor_areas;
for (uint32_t bridge_id = 0; bridge_id < uint32_t(bridges.size()); ++ bridge_id) {
Bridge &bridge = bridges[bridge_id];
// lines.clear();
anchor_areas.clear();
int32_t last_anchor_id = -1;
for (; it_bridge_anchor != bridge_anchors.end() && it_bridge_anchor->src == bridge_id; ++ it_bridge_anchor) {
if (last_anchor_id != int(it_bridge_anchor->boundary)) {
last_anchor_id = int(it_bridge_anchor->boundary);
append(anchor_areas, to_polygons(shells[last_anchor_id]));
}
// if (Points &polyline = it_bridge_anchor->path; polyline.size() >= 2) {
// reserve_more_power_of_2(lines, polyline.size() - 1);
// for (size_t i = 1; i < polyline.size(); ++ i)
// lines.push_back({ polyline[i - 1], polyline[1] });
// }
}
lines = to_lines(diff_pl(to_polylines(bridge.expolygon), expand(anchor_areas, float(SCALED_EPSILON))));
auto [bridging_dir, unsupported_dist] = detect_bridging_direction(lines, to_polygons(bridge.expolygon));
bridge.angle = M_PI + std::atan2(bridging_dir.y(), bridging_dir.x());
// #if 1
// coordf_t stroke_width = scale_(0.06);
// BoundingBox bbox = get_extents(initial);
// bbox.offset(scale_(1.));
// ::Slic3r::SVG
// svg(debug_out_path(("bridge"+std::to_string(bridges[idx_last].bridge_angle)+"_"+std::to_string(this->layer()->bottom_z())).c_str()),
// bbox);
// svg.draw(initial, "cyan");
// svg.draw(to_lines(lower_layer->lslices), "green", stroke_width);
// #endif
}
}
// Merge the groups with the same group id, produce surfaces by merging source overhangs with their newly expanded anchors.
Surfaces out;
{
Polygons acc;
Surface templ{ stBottomBridge, {} };
std::sort(bridge_expansions.begin(), bridge_expansions.end(), [](auto &l, auto &r) {
return l.src_id < r.src_id || (l.src_id == r.src_id && l.boundary_id < r.boundary_id);
});
for (auto it = bridge_expansions.begin(); it != bridge_expansions.end(); ) {
bridges[it->src_id].bridge_expansion_begin = it;
uint32_t src_id = it->src_id;
for (++ it; it != bridge_expansions.end() && it->src_id == src_id; ++ it) ;
}
for (uint32_t bridge_id = 0; bridge_id < uint32_t(bridges.size()); ++ bridge_id) {
acc.clear();
for (uint32_t bridge_id2 = bridge_id; bridge_id2 < uint32_t(bridges.size()); ++ bridge_id2)
if (group_id(bridge_id) == bridge_id) {
append(acc, to_polygons(std::move(bridges[bridge_id2].expolygon)));
auto it_bridge_expansion = bridges[bridge_id2].bridge_expansion_begin;
assert(it_bridge_expansion == bridge_expansions.end() || it_bridge_expansion->src_id == bridge_id2);
for (; it_bridge_expansion != bridge_expansions.end() && it_bridge_expansion->src_id == bridge_id2; ++ it_bridge_expansion)
append(acc, to_polygons(std::move(it_bridge_expansion->expolygon)));
}
//FIXME try to be smart and pick the best bridging angle for all?
templ.bridge_angle = bridges[bridge_id].angle;
// without safety offset, artifacts are generated (GH #2494)
for (ExPolygon &ex : union_safety_offset_ex(acc))
out.emplace_back(templ, std::move(ex));
}
}
// Clip the shells by the expanded bridges.
shells = diff_ex(shells, out);
return out;
}
// Extract bridging surfaces from "surfaces", expand them into "shells" using expansion_params.
// Trim "shells" by the expanded bridges.
static Surfaces expand_merge_surfaces(
Surfaces &surfaces,
SurfaceType surface_type,
ExPolygons &shells,
const Algorithm::RegionExpansionParameters &params,
const double bridge_angle = -1.)
{
double thickness;
ExPolygons src = fill_surfaces_extract_expolygons(surfaces, surface_type, thickness);
if (src.empty())
return {};
std::vector<ExPolygon> expanded = expand_merge_expolygons(std::move(src), shells, params);
// Trim the shells by the expanded expolygons.
shells = diff_ex(shells, expanded);
Surface templ{ surface_type, {} };
templ.bridge_angle = bridge_angle;
Surfaces out;
out.reserve(expanded.size());
for (auto &expoly : expanded)
out.emplace_back(templ, std::move(expoly));
return out;
}
void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered)
{
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-initial");
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
// Width of the perimeters.
float shell_width = 0;
if (int num_perimeters = this->region().config().perimeters; num_perimeters > 0) {
Flow external_perimeter_flow = this->flow(frExternalPerimeter);
Flow perimeter_flow = this->flow(frPerimeter);
shell_width += 0.5f * external_perimeter_flow.scaled_width() + external_perimeter_flow.scaled_spacing();
shell_width += perimeter_flow.scaled_spacing() * (num_perimeters - 1);
} else {
}
// Scaled expansions of the respective external surfaces.
float expansion_top = shell_width * sqrt(2.);
float expansion_bottom = expansion_top;
float expansion_bottom_bridge = expansion_top;
// Expand by waves of expansion_step size (expansion_step is scaled), but with no more steps than max_nr_expansion_steps.
static constexpr const float expansion_step = scaled<float>(0.1);
// Don't take more than max_nr_steps for small expansion_step.
static constexpr const size_t max_nr_expansion_steps = 5;
// Expand the top / bottom / bridge surfaces into the shell thickness solid infills.
double layer_thickness;
ExPolygons shells = union_ex(fill_surfaces_extract_expolygons(m_fill_surfaces.surfaces, stInternalSolid, layer_thickness));
SurfaceCollection bridges;
{
BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges. layer" << this->layer()->print_z;
const double custom_angle = this->region().config().bridge_angle.value;
const auto params = Algorithm::RegionExpansionParameters::build(expansion_bottom_bridge, expansion_step, max_nr_expansion_steps);
bridges.surfaces = custom_angle > 0 ?
expand_merge_surfaces(m_fill_surfaces.surfaces, stBottomBridge, shells, params, custom_angle) :
expand_bridges_detect_orientations(m_fill_surfaces.surfaces, shells, params);
BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges - done";
#if 0
{
static int iRun = 0;
bridges.export_to_svg(debug_out_path("bridges-after-grouping-%d.svg", iRun++), true);
}
#endif
}
Surfaces bottoms = expand_merge_surfaces(m_fill_surfaces.surfaces, stBottom, shells,
Algorithm::RegionExpansionParameters::build(expansion_bottom, expansion_step, max_nr_expansion_steps));
Surfaces tops = expand_merge_surfaces(m_fill_surfaces.surfaces, stTop, shells,
Algorithm::RegionExpansionParameters::build(expansion_top, expansion_step, max_nr_expansion_steps));
m_fill_surfaces.remove_types({ stBottomBridge, stBottom, stTop, stInternalSolid });
reserve_more(m_fill_surfaces.surfaces, shells.size() + bridges.size() + bottoms.size() + tops.size());
Surface solid_templ(stInternalSolid, {});
solid_templ.thickness = layer_thickness;
m_fill_surfaces.append(std::move(shells), solid_templ);
m_fill_surfaces.append(std::move(bridges.surfaces));
m_fill_surfaces.append(std::move(bottoms));
m_fill_surfaces.append(std::move(tops));
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-final");
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
}
#else
//#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 3.
//#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 1.5
#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0.
@ -146,10 +414,11 @@ void LayerRegion::make_perimeters(
void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered)
{
const bool has_infill = this->region().config().fill_density.value > 0.;
const float margin = float(scale_(EXTERNAL_INFILL_MARGIN));
// const float margin = scaled<float>(0.1); // float(scale_(EXTERNAL_INFILL_MARGIN));
const float margin = float(scale_(EXTERNAL_INFILL_MARGIN));
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-initial");
export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-initial");
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
// 1) Collect bottom and bridge surfaces, each of them grown by a fixed 3mm offset
@ -164,7 +433,6 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
Surfaces internal;
// Areas, where an infill of various types (top, bottom, bottom bride, sparse, void) could be placed.
Polygons fill_boundaries = to_polygons(this->fill_expolygons());
Polygons lower_layer_covered_tmp;
// Collect top surfaces and internal surfaces.
// Collect fill_boundaries: If we're slicing with no infill, we can't extend external surfaces over non-existent infill.
@ -174,33 +442,42 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
// Voids are sparse infills if infill rate is zero.
Polygons voids;
for (const Surface &surface : this->fill_surfaces()) {
if (surface.is_top()) {
// Collect the top surfaces, inflate them and trim them by the bottom surfaces.
// This gives the priority to bottom surfaces.
surfaces_append(top, offset_ex(surface.expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface);
} else if (surface.surface_type == stBottom || (surface.surface_type == stBottomBridge && lower_layer == nullptr)) {
// Grown by 3mm.
surfaces_append(bottom, offset_ex(surface.expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface);
} else if (surface.surface_type == stBottomBridge) {
if (! surface.empty())
assert(! surface.empty());
if (! surface.empty()) {
if (surface.is_top()) {
// Collect the top surfaces, inflate them and trim them by the bottom surfaces.
// This gives the priority to bottom surfaces.
surfaces_append(top, offset_ex(surface.expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface);
} else if (surface.surface_type == stBottom || (surface.surface_type == stBottomBridge && lower_layer == nullptr)) {
// Grown by 3mm.
surfaces_append(bottom, offset_ex(surface.expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface);
} else if (surface.surface_type == stBottomBridge) {
bridges.emplace_back(surface);
}
if (surface.is_internal()) {
assert(surface.surface_type == stInternal || surface.surface_type == stInternalSolid);
if (! has_infill && lower_layer != nullptr)
polygons_append(voids, surface.expolygon);
internal.emplace_back(std::move(surface));
} else {
assert(surface.is_internal());
assert(surface.surface_type == stInternal || surface.surface_type == stInternalSolid);
if (! has_infill && lower_layer != nullptr)
polygons_append(voids, surface.expolygon);
internal.emplace_back(std::move(surface));
}
}
}
if (! has_infill && lower_layer != nullptr && ! voids.empty()) {
if (! voids.empty()) {
// There are some voids (empty infill regions) on this layer. Usually one does not want to expand
// any infill into these voids, with the exception the expanded infills are supported by layers below
// with nonzero inill.
assert(! has_infill && lower_layer != nullptr);
// Remove voids from fill_boundaries, that are not supported by the layer below.
Polygons lower_layer_covered_tmp;
if (lower_layer_covered == nullptr) {
lower_layer_covered = &lower_layer_covered_tmp;
lower_layer_covered_tmp = to_polygons(lower_layer->lslices);
}
if (! lower_layer_covered->empty())
// Allow the top / bottom surfaces to expand into the voids of this layer if supported by the layer below.
voids = diff(voids, *lower_layer_covered);
fill_boundaries = diff(fill_boundaries, voids);
if (! voids.empty())
fill_boundaries = diff(fill_boundaries, voids);
}
}
@ -224,13 +501,12 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
{
static int iRun = 0;
SVG svg(debug_out_path("3_process_external_surfaces-fill_regions-%d.svg", iRun ++).c_str(), get_extents(fill_boundaries_ex));
SVG svg(debug_out_path("4_process_external_surfaces-fill_regions-%d.svg", iRun ++).c_str(), get_extents(fill_boundaries_ex));
svg.draw(fill_boundaries_ex);
svg.draw_outline(fill_boundaries_ex, "black", "blue", scale_(0.05));
svg.Close();
}
// export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-initial");
// export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-initial");
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
{
@ -253,7 +529,8 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
if (idx_island == -1) {
BOOST_LOG_TRIVIAL(trace) << "Bridge did not fall into the source region!";
} else {
// Found an island, to which this bridge region belongs. Trim it,
// Found an island, to which this bridge region belongs. Trim the expanded bridging region
// with its source region, so it does not overflow into a neighbor region.
polys = intersection(polys, fill_boundaries_ex[idx_island]);
}
bridge_bboxes.push_back(get_extents(polys));
@ -371,10 +648,10 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
Surfaces new_surfaces;
{
// Merge top and bottom in a single collection.
surfaces_append(top, std::move(bottom));
// Intersect the grown surfaces with the actual fill boundaries.
Polygons bottom_polygons = to_polygons(bottom);
// Merge top and bottom in a single collection.
surfaces_append(top, std::move(bottom));
for (size_t i = 0; i < top.size(); ++ i) {
Surface &s1 = top[i];
if (s1.empty())
@ -422,9 +699,10 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
m_fill_surfaces.surfaces = std::move(new_surfaces);
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-final");
export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-final");
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
}
#endif
void LayerRegion::prepare_fill_surfaces()
{

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

@ -185,75 +185,107 @@ namespace client
template<typename Iterator>
struct expr
{
expr() : type(TYPE_EMPTY) {}
explicit expr(bool b) : type(TYPE_BOOL) { data.b = b; }
explicit expr(bool b, const Iterator &it_begin, const Iterator &it_end) : type(TYPE_BOOL), it_range(it_begin, it_end) { data.b = b; }
explicit expr(int i) : type(TYPE_INT) { data.i = i; }
explicit expr(int i, const Iterator &it_begin, const Iterator &it_end) : type(TYPE_INT), it_range(it_begin, it_end) { data.i = i; }
explicit expr(double d) : type(TYPE_DOUBLE) { data.d = d; }
explicit expr(double d, const Iterator &it_begin, const Iterator &it_end) : type(TYPE_DOUBLE), it_range(it_begin, it_end) { data.d = d; }
explicit expr(const char *s) : type(TYPE_STRING) { data.s = new std::string(s); }
explicit expr(const std::string &s) : type(TYPE_STRING) { data.s = new std::string(s); }
expr() {}
explicit expr(bool b) : m_type(TYPE_BOOL) { m_data.b = b; }
explicit expr(bool b, const Iterator &it_begin, const Iterator &it_end) : m_type(TYPE_BOOL), it_range(it_begin, it_end) { m_data.b = b; }
explicit expr(int i) : m_type(TYPE_INT) { m_data.i = i; }
explicit expr(int i, const Iterator &it_begin, const Iterator &it_end) : m_type(TYPE_INT), it_range(it_begin, it_end) { m_data.i = i; }
explicit expr(double d) : m_type(TYPE_DOUBLE) { m_data.d = d; }
explicit expr(double d, const Iterator &it_begin, const Iterator &it_end) : m_type(TYPE_DOUBLE), it_range(it_begin, it_end) { m_data.d = d; }
explicit expr(const char *s) : m_type(TYPE_STRING) { m_data.s = new std::string(s); }
explicit expr(const std::string &s) : m_type(TYPE_STRING) { m_data.s = new std::string(s); }
explicit expr(const std::string &s, const Iterator &it_begin, const Iterator &it_end) :
type(TYPE_STRING), it_range(it_begin, it_end) { data.s = new std::string(s); }
expr(const expr &rhs) : type(rhs.type), it_range(rhs.it_range)
{ if (rhs.type == TYPE_STRING) data.s = new std::string(*rhs.data.s); else data.set(rhs.data); }
explicit expr(expr &&rhs) : type(rhs.type), it_range(rhs.it_range)
{ data.set(rhs.data); rhs.type = TYPE_EMPTY; }
explicit expr(expr &&rhs, const Iterator &it_begin, const Iterator &it_end) : type(rhs.type), it_range(it_begin, it_end)
{ data.set(rhs.data); rhs.type = TYPE_EMPTY; }
~expr() { this->reset(); }
expr &operator=(const expr &rhs)
m_type(TYPE_STRING), it_range(it_begin, it_end) { m_data.s = new std::string(s); }
expr(const expr &rhs) : m_type(rhs.type()), it_range(rhs.it_range)
{ if (rhs.type() == TYPE_STRING) m_data.s = new std::string(*rhs.m_data.s); else m_data.set(rhs.m_data); }
explicit expr(expr &&rhs) : expr(rhs, rhs.it_range.begin(), rhs.it_range.end()) {}
explicit expr(expr &&rhs, const Iterator &it_begin, const Iterator &it_end) : m_type(rhs.type()), it_range{ it_begin, it_end }
{
m_data.set(rhs.m_data);
rhs.m_type = TYPE_EMPTY;
}
expr &operator=(const expr &rhs)
{
this->type = rhs.type;
this->it_range = rhs.it_range;
if (rhs.type == TYPE_STRING)
this->data.s = new std::string(*rhs.data.s);
else
this->data.set(rhs.data);
return *this;
if (rhs.type() == TYPE_STRING) {
this->set_s(rhs.s());
} else {
m_type = rhs.type();
m_data.set(rhs.m_data);
}
this->it_range = rhs.it_range;
return *this;
}
expr &operator=(expr &&rhs)
{
type = rhs.type;
this->it_range = rhs.it_range;
data.set(rhs.data);
rhs.type = TYPE_EMPTY;
if (this != &rhs) {
this->reset();
m_type = rhs.type();
this->it_range = rhs.it_range;
m_data.set(rhs.m_data);
rhs.m_type = TYPE_EMPTY;
}
return *this;
}
void reset()
{
if (this->type == TYPE_STRING)
delete data.s;
this->type = TYPE_EMPTY;
if (this->type() == TYPE_STRING)
delete m_data.s;
m_type = TYPE_EMPTY;
}
bool& b() { return data.b; }
bool b() const { return data.b; }
void set_b(bool v) { this->reset(); this->data.b = v; this->type = TYPE_BOOL; }
int& i() { return data.i; }
int i() const { return data.i; }
void set_i(int v) { this->reset(); this->data.i = v; this->type = TYPE_INT; }
int as_i() const { return (this->type == TYPE_INT) ? this->i() : int(this->d()); }
int as_i_rounded() const { return (this->type == TYPE_INT) ? this->i() : int(std::round(this->d())); }
double& d() { return data.d; }
double d() const { return data.d; }
void set_d(double v) { this->reset(); this->data.d = v; this->type = TYPE_DOUBLE; }
double as_d() const { return (this->type == TYPE_DOUBLE) ? this->d() : double(this->i()); }
std::string& s() { return *data.s; }
const std::string& s() const { return *data.s; }
void set_s(const std::string &s) { this->reset(); this->data.s = new std::string(s); this->type = TYPE_STRING; }
void set_s(std::string &&s) { this->reset(); this->data.s = new std::string(std::move(s)); this->type = TYPE_STRING; }
enum Type {
TYPE_EMPTY = 0,
TYPE_BOOL,
TYPE_INT,
TYPE_DOUBLE,
TYPE_STRING,
};
Type type() const { return m_type; }
bool& b() { return m_data.b; }
bool b() const { return m_data.b; }
void set_b(bool v) { this->reset(); this->set_b_lite(v); }
void set_b_lite(bool v) { assert(this->type() != TYPE_STRING); Data tmp; tmp.b = v; m_data.set(tmp); m_type = TYPE_BOOL; }
int& i() { return m_data.i; }
int i() const { return m_data.i; }
void set_i(int v) { this->reset(); set_i_lite(v); }
void set_i_lite(int v) { assert(this->type() != TYPE_STRING); Data tmp; tmp.i = v; m_data.set(tmp); m_type = TYPE_INT; }
int as_i() const { return this->type() == TYPE_INT ? this->i() : int(this->d()); }
int as_i_rounded() const { return this->type() == TYPE_INT ? this->i() : int(std::round(this->d())); }
double& d() { return m_data.d; }
double d() const { return m_data.d; }
void set_d(double v) { this->reset(); this->set_d_lite(v); }
void set_d_lite(double v) { assert(this->type() != TYPE_STRING); Data tmp; tmp.d = v; m_data.set(tmp); m_type = TYPE_DOUBLE; }
double as_d() const { return this->type() == TYPE_DOUBLE ? this->d() : double(this->i()); }
std::string& s() { return *m_data.s; }
const std::string& s() const { return *m_data.s; }
void set_s(const std::string &s) {
if (this->type() == TYPE_STRING)
*m_data.s = s;
else
this->set_s_take_ownership(new std::string(s));
}
void set_s(std::string &&s) {
if (this->type() == TYPE_STRING)
*m_data.s = std::move(s);
else
this->set_s_take_ownership(new std::string(std::move(s)));
}
void set_s(const char *s) {
if (this->type() == TYPE_STRING)
*m_data.s = s;
else
this->set_s_take_ownership(new std::string(s));
}
std::string to_string() const
{
std::string out;
switch (type) {
case TYPE_BOOL: out = data.b ? "true" : "false"; break;
case TYPE_INT: out = std::to_string(data.i); break;
switch (this->type()) {
case TYPE_BOOL: out = this->b() ? "true" : "false"; break;
case TYPE_INT: out = std::to_string(this->i()); break;
case TYPE_DOUBLE:
#if 0
// The default converter produces trailing zeros after the decimal point.
@ -263,48 +295,24 @@ namespace client
// It seems to be doing what the old boost::to_string() did.
{
std::ostringstream ss;
ss << data.d;
ss << this->d();
out = ss.str();
}
#endif
break;
case TYPE_STRING: out = *data.s; break;
case TYPE_STRING: out = this->s(); break;
default: break;
}
return out;
}
union Data {
// Raw image of the other data members.
// The C++ compiler will consider a possible aliasing of char* with any other union member,
// therefore copying the raw data is safe.
char raw[8];
bool b;
int i;
double d;
std::string *s;
// Copy the largest member variable through char*, which will alias with all other union members by default.
void set(const Data &rhs) { memcpy(this->raw, rhs.raw, sizeof(rhs.raw)); }
} data;
enum Type {
TYPE_EMPTY = 0,
TYPE_BOOL,
TYPE_INT,
TYPE_DOUBLE,
TYPE_STRING,
};
Type type;
// Range of input iterators covering this expression.
// Used for throwing parse exceptions.
boost::iterator_range<Iterator> it_range;
expr unary_minus(const Iterator start_pos) const
{
switch (this->type) {
switch (this->type()) {
case TYPE_INT :
return expr<Iterator>(- this->i(), start_pos, this->it_range.end());
case TYPE_DOUBLE:
@ -319,7 +327,7 @@ namespace client
expr unary_integer(const Iterator start_pos) const
{
switch (this->type) {
switch (this->type()) {
case TYPE_INT:
return expr<Iterator>(this->i(), start_pos, this->it_range.end());
case TYPE_DOUBLE:
@ -334,7 +342,7 @@ namespace client
expr round(const Iterator start_pos) const
{
switch (this->type) {
switch (this->type()) {
case TYPE_INT:
return expr<Iterator>(this->i(), start_pos, this->it_range.end());
case TYPE_DOUBLE:
@ -349,7 +357,7 @@ namespace client
expr unary_not(const Iterator start_pos) const
{
switch (this->type) {
switch (this->type()) {
case TYPE_BOOL:
return expr<Iterator>(! this->b(), start_pos, this->it_range.end());
default:
@ -362,23 +370,20 @@ namespace client
expr &operator+=(const expr &rhs)
{
if (this->type == TYPE_STRING) {
if (this->type() == TYPE_STRING) {
// Convert the right hand side to string and append.
*this->data.s += rhs.to_string();
} else if (rhs.type == TYPE_STRING) {
*m_data.s += rhs.to_string();
} else if (rhs.type() == TYPE_STRING) {
// Conver the left hand side to string, append rhs.
this->data.s = new std::string(this->to_string() + rhs.s());
this->type = TYPE_STRING;
this->set_s(this->to_string() + rhs.s());
} else {
const char *err_msg = "Cannot add non-numeric types.";
this->throw_if_not_numeric(err_msg);
rhs.throw_if_not_numeric(err_msg);
if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) {
double d = this->as_d() + rhs.as_d();
this->data.d = d;
this->type = TYPE_DOUBLE;
} else
this->data.i += rhs.i();
if (this->type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE)
this->set_d_lite(this->as_d() + rhs.as_d());
else
m_data.i += rhs.i();
}
this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end());
return *this;
@ -389,12 +394,10 @@ namespace client
const char *err_msg = "Cannot subtract non-numeric types.";
this->throw_if_not_numeric(err_msg);
rhs.throw_if_not_numeric(err_msg);
if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) {
double d = this->as_d() - rhs.as_d();
this->data.d = d;
this->type = TYPE_DOUBLE;
} else
this->data.i -= rhs.i();
if (this->type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE)
this->set_d_lite(this->as_d() - rhs.as_d());
else
m_data.i -= rhs.i();
this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end());
return *this;
}
@ -404,12 +407,10 @@ namespace client
const char *err_msg = "Cannot multiply with non-numeric type.";
this->throw_if_not_numeric(err_msg);
rhs.throw_if_not_numeric(err_msg);
if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) {
double d = this->as_d() * rhs.as_d();
this->data.d = d;
this->type = TYPE_DOUBLE;
} else
this->data.i *= rhs.i();
if (this->type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE)
this->set_d_lite(this->as_d() * rhs.as_d());
else
m_data.i *= rhs.i();
this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end());
return *this;
}
@ -418,14 +419,12 @@ namespace client
{
this->throw_if_not_numeric("Cannot divide a non-numeric type.");
rhs.throw_if_not_numeric("Cannot divide with a non-numeric type.");
if ((rhs.type == TYPE_INT) ? (rhs.i() == 0) : (rhs.d() == 0.))
if (rhs.type() == TYPE_INT ? (rhs.i() == 0) : (rhs.d() == 0.))
rhs.throw_exception("Division by zero");
if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) {
double d = this->as_d() / rhs.as_d();
this->data.d = d;
this->type = TYPE_DOUBLE;
} else
this->data.i /= rhs.i();
if (this->type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE)
this->set_d_lite(this->as_d() / rhs.as_d());
else
m_data.i /= rhs.i();
this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end());
return *this;
}
@ -434,14 +433,12 @@ namespace client
{
this->throw_if_not_numeric("Cannot divide a non-numeric type.");
rhs.throw_if_not_numeric("Cannot divide with a non-numeric type.");
if ((rhs.type == TYPE_INT) ? (rhs.i() == 0) : (rhs.d() == 0.))
if (rhs.type() == TYPE_INT ? (rhs.i() == 0) : (rhs.d() == 0.))
rhs.throw_exception("Division by zero");
if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) {
double d = std::fmod(this->as_d(), rhs.as_d());
this->data.d = d;
this->type = TYPE_DOUBLE;
} else
this->data.i %= rhs.i();
if (this->type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE)
this->set_d_lite(std::fmod(this->as_d(), rhs.as_d()));
else
m_data.i %= rhs.i();
this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end());
return *this;
}
@ -453,14 +450,14 @@ namespace client
static void evaluate_boolean(expr &self, bool &out)
{
if (self.type != TYPE_BOOL)
if (self.type() != TYPE_BOOL)
self.throw_exception("Not a boolean expression");
out = self.b();
}
static void evaluate_boolean_to_string(expr &self, std::string &out)
{
if (self.type != TYPE_BOOL)
if (self.type() != TYPE_BOOL)
self.throw_exception("Not a boolean expression");
out = self.b() ? "true" : "false";
}
@ -469,31 +466,31 @@ namespace client
static void compare_op(expr &lhs, expr &rhs, char op, bool invert)
{
bool value = false;
if ((lhs.type == TYPE_INT || lhs.type == TYPE_DOUBLE) &&
(rhs.type == TYPE_INT || rhs.type == TYPE_DOUBLE)) {
if ((lhs.type() == TYPE_INT || lhs.type() == TYPE_DOUBLE) &&
(rhs.type() == TYPE_INT || rhs.type() == TYPE_DOUBLE)) {
// Both types are numeric.
switch (op) {
case '=':
value = (lhs.type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) ?
value = (lhs.type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE) ?
(std::abs(lhs.as_d() - rhs.as_d()) < 1e-8) : (lhs.i() == rhs.i());
break;
case '<':
value = (lhs.type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) ?
value = (lhs.type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE) ?
(lhs.as_d() < rhs.as_d()) : (lhs.i() < rhs.i());
break;
case '>':
default:
value = (lhs.type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) ?
value = (lhs.type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE) ?
(lhs.as_d() > rhs.as_d()) : (lhs.i() > rhs.i());
break;
}
} else if (lhs.type == TYPE_BOOL && rhs.type == TYPE_BOOL) {
} else if (lhs.type() == TYPE_BOOL && rhs.type() == TYPE_BOOL) {
// Both type are bool.
if (op != '=')
boost::throw_exception(qi::expectation_failure<Iterator>(
lhs.it_range.begin(), rhs.it_range.end(), spirit::info("*Cannot compare the types.")));
value = lhs.b() == rhs.b();
} else if (lhs.type == TYPE_STRING || rhs.type == TYPE_STRING) {
} else if (lhs.type() == TYPE_STRING || rhs.type() == TYPE_STRING) {
// One type is string, the other could be converted to string.
value = (op == '=') ? (lhs.to_string() == rhs.to_string()) :
(op == '<') ? (lhs.to_string() < rhs.to_string()) : (lhs.to_string() > rhs.to_string());
@ -502,8 +499,7 @@ namespace client
lhs.it_range.begin(), rhs.it_range.end(), spirit::info("*Cannot compare the types.")));
}
lhs.reset();
lhs.type = TYPE_BOOL;
lhs.data.b = invert ? ! value : value;
lhs.set_b_lite(invert ? ! value : value);
}
// Compare operators, store the result into lhs.
static void equal (expr &lhs, expr &rhs) { compare_op(lhs, rhs, '=', false); }
@ -528,15 +524,14 @@ namespace client
{
throw_if_not_numeric(param1);
throw_if_not_numeric(param2);
if (param1.type == TYPE_DOUBLE || param2.type == TYPE_DOUBLE) {
if (param1.type() == TYPE_DOUBLE || param2.type() == TYPE_DOUBLE) {
double d = 0.;
switch (fun) {
case FUNCTION_MIN: d = std::min(param1.as_d(), param2.as_d()); break;
case FUNCTION_MAX: d = std::max(param1.as_d(), param2.as_d()); break;
default: param1.throw_exception("Internal error: invalid function");
}
param1.data.d = d;
param1.type = TYPE_DOUBLE;
param1.set_d_lite(d);
} else {
int i = 0;
switch (fun) {
@ -544,8 +539,7 @@ namespace client
case FUNCTION_MAX: i = std::max(param1.as_i(), param2.as_i()); break;
default: param1.throw_exception("Internal error: invalid function");
}
param1.data.i = i;
param1.type = TYPE_INT;
param1.set_i_lite(i);
}
}
// Store the result into param1.
@ -557,13 +551,10 @@ namespace client
{
throw_if_not_numeric(param1);
throw_if_not_numeric(param2);
if (param1.type == TYPE_DOUBLE || param2.type == TYPE_DOUBLE) {
param1.data.d = std::uniform_real_distribution<>(param1.as_d(), param2.as_d())(rng);
param1.type = TYPE_DOUBLE;
} else {
param1.data.i = std::uniform_int_distribution<>(param1.as_i(), param2.as_i())(rng);
param1.type = TYPE_INT;
}
if (param1.type() == TYPE_DOUBLE || param2.type() == TYPE_DOUBLE)
param1.set_d_lite(std::uniform_real_distribution<>(param1.as_d(), param2.as_d())(rng));
else
param1.set_i_lite(std::uniform_int_distribution<>(param1.as_i(), param2.as_i())(rng));
}
// Store the result into param1.
@ -572,10 +563,10 @@ namespace client
static void digits(expr &param1, expr &param2, expr &param3)
{
throw_if_not_numeric(param1);
if (param2.type != TYPE_INT)
if (param2.type() != TYPE_INT)
param2.throw_exception("digits: second parameter must be integer");
bool has_decimals = param3.type != TYPE_EMPTY;
if (has_decimals && param3.type != TYPE_INT)
bool has_decimals = param3.type() != TYPE_EMPTY;
if (has_decimals && param3.type() != TYPE_INT)
param3.throw_exception("digits: third parameter must be integer");
char buf[256];
@ -593,7 +584,7 @@ namespace client
static void regex_op(expr &lhs, boost::iterator_range<Iterator> &rhs, char op)
{
const std::string *subject = nullptr;
if (lhs.type == TYPE_STRING) {
if (lhs.type() == TYPE_STRING) {
// One type is string, the other could be converted to string.
subject = &lhs.s();
} else {
@ -604,9 +595,7 @@ namespace client
bool result = SLIC3R_REGEX_NAMESPACE::regex_match(*subject, SLIC3R_REGEX_NAMESPACE::regex(pattern));
if (op == '!')
result = ! result;
lhs.reset();
lhs.type = TYPE_BOOL;
lhs.data.b = result;
lhs.set_b(result);
} catch (SLIC3R_REGEX_NAMESPACE::regex_error &ex) {
// Syntax error in the regular expression
boost::throw_exception(qi::expectation_failure<Iterator>(
@ -620,21 +609,20 @@ namespace client
static void logical_op(expr &lhs, expr &rhs, char op)
{
bool value = false;
if (lhs.type == TYPE_BOOL && rhs.type == TYPE_BOOL) {
if (lhs.type() == TYPE_BOOL && rhs.type() == TYPE_BOOL) {
value = (op == '|') ? (lhs.b() || rhs.b()) : (lhs.b() && rhs.b());
} else {
boost::throw_exception(qi::expectation_failure<Iterator>(
lhs.it_range.begin(), rhs.it_range.end(), spirit::info("*Cannot apply logical operation to non-boolean operators.")));
}
lhs.type = TYPE_BOOL;
lhs.data.b = value;
lhs.set_b_lite(value);
}
static void logical_or (expr &lhs, expr &rhs) { logical_op(lhs, rhs, '|'); }
static void logical_and(expr &lhs, expr &rhs) { logical_op(lhs, rhs, '&'); }
static void ternary_op(expr &lhs, expr &rhs1, expr &rhs2)
{
if (lhs.type != TYPE_BOOL)
if (lhs.type() != TYPE_BOOL)
lhs.throw_exception("Not a boolean expression");
if (lhs.b())
lhs = std::move(rhs1);
@ -658,9 +646,25 @@ namespace client
void throw_if_not_numeric(const char *message) const
{
if (this->type != TYPE_INT && this->type != TYPE_DOUBLE)
if (this->type() != TYPE_INT && this->type() != TYPE_DOUBLE)
this->throw_exception(message);
}
private:
// This object will take ownership of the parameter string object "s".
void set_s_take_ownership(std::string* s) { assert(this->type() != TYPE_STRING); Data tmp; tmp.s = s; m_data.set(tmp); m_type = TYPE_STRING; }
Type m_type = TYPE_EMPTY;
union Data {
bool b;
int i;
double d;
std::string *s;
// Copy the largest member variable through char*, which will alias with all other union members by default.
void set(const Data &rhs) { memcpy(this, &rhs, sizeof(rhs)); }
} m_data;
};
template<typename ITERATOR>
@ -668,7 +672,7 @@ namespace client
{
typedef expr<ITERATOR> Expr;
os << std::string(expression.it_range.begin(), expression.it_range.end()) << " - ";
switch (expression.type) {
switch (expression.type()) {
case Expr::TYPE_EMPTY: os << "empty"; break;
case Expr::TYPE_BOOL: os << "bool (" << expression.b() << ")"; break;
case Expr::TYPE_INT: os << "int (" << expression.i() << ")"; break;
@ -802,6 +806,7 @@ namespace client
case coInt: output.set_i(opt.opt->getInt()); break;
case coString: output.set_s(static_cast<const ConfigOptionString*>(opt.opt)->value); break;
case coPercent: output.set_d(opt.opt->getFloat()); break;
case coEnum:
case coPoint: output.set_s(opt.opt->serialize()); break;
case coBool: output.set_b(opt.opt->getBool()); break;
case coFloatOrPercent:
@ -869,6 +874,7 @@ namespace client
case coPercents: output.set_d(static_cast<const ConfigOptionPercents*>(opt.opt)->values[idx]); break;
case coPoints: output.set_s(to_string(static_cast<const ConfigOptionPoints *>(opt.opt)->values[idx])); break;
case coBools: output.set_b(static_cast<const ConfigOptionBools *>(opt.opt)->values[idx] != 0); break;
//case coEnums: output.set_s(opt.opt->vserialize()[idx]); break;
default:
ctx->throw_exception("Unknown vector variable type", opt.it_range);
}
@ -912,7 +918,7 @@ namespace client
template <typename Iterator>
static void evaluate_index(expr<Iterator> &expr_index, int &output)
{
if (expr_index.type != expr<Iterator>::TYPE_INT)
if (expr_index.type() != expr<Iterator>::TYPE_INT)
expr_index.throw_exception("Non-integer index is not allowed to address a vector variable.");
output = expr_index.i();
}

View File

@ -5,6 +5,7 @@
#include <map>
#include <random>
#include <string>
#include <string_view>
#include <vector>
#include "PrintConfig.hpp"
@ -38,6 +39,8 @@ public:
// Add new ConfigOption values to m_config.
void set(const std::string &key, const std::string &value) { this->set(key, new ConfigOptionString(value)); }
void set(const std::string &key, std::string_view value) { this->set(key, new ConfigOptionString(std::string(value))); }
void set(const std::string &key, const char *value) { this->set(key, new ConfigOptionString(value)); }
void set(const std::string &key, int value) { this->set(key, new ConfigOptionInt(value)); }
void set(const std::string &key, unsigned int value) { this->set(key, int(value)); }
void set(const std::string &key, bool value) { this->set(key, new ConfigOptionBool(value)); }

View File

@ -84,18 +84,28 @@ Points collect_duplicates(Points pts /* Copy */)
return duplicits;
}
template<bool IncludeBoundary>
BoundingBox get_extents(const Points &pts)
{
return BoundingBox(pts);
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);
// 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>
BoundingBox get_extents(const std::vector<Points> &pts)
{
BoundingBox bbox;
for (const Points &p : pts)
bbox.merge(get_extents(p));
bbox.merge(get_extents<IncludeBoundary>(p));
return bbox;
}
template BoundingBox get_extents<false>(const std::vector<Points> &pts);
template BoundingBox get_extents<true>(const std::vector<Points> &pts);
BoundingBoxf get_extents(const std::vector<Vec2d> &pts)
{

View File

@ -237,8 +237,20 @@ 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);
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);
extern template BoundingBox get_extents<false>(const std::vector<Points> &pts);
extern template BoundingBox get_extents<true>(const std::vector<Points> &pts);
BoundingBoxf get_extents(const std::vector<Vec2d> &pts);
int nearest_point_index(const Points &points, const Point &pt);

View File

@ -292,6 +292,19 @@ void ThickPolyline::clip_end(double distance)
assert(this->width.size() == (this->points.size() - 1) * 2);
}
void ThickPolyline::start_at_index(int index)
{
assert(index >= 0 && index < this->points.size());
assert(this->points.front() == this->points.back() && this->width.front() == this->width.back());
if (index != 0 && index != (this->points.size() - 1) && this->points.front() == this->points.back() && this->width.front() == this->width.back()) {
this->points.pop_back();
assert(this->points.size() * 2 == this->width.size());
std::rotate(this->points.begin(), this->points.begin() + index, this->points.end());
std::rotate(this->width.begin(), this->width.begin() + 2 * index, this->width.end());
this->points.emplace_back(this->points.front());
}
}
double Polyline3::length() const
{
double l = 0;

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);
@ -166,6 +185,7 @@ struct ThickPolyline {
const Point& first_point() const { return this->points.front(); }
const Point& last_point() const { return this->points.back(); }
size_t size() const { return this->points.size(); }
bool is_valid() const { return this->points.size() >= 2; }
double length() const { return Slic3r::length(this->points); }
@ -179,6 +199,11 @@ struct ThickPolyline {
void clip_end(double distance);
// Make this closed ThickPolyline starting in the specified index.
// Be aware that this method can be applicable just for closed ThickPolyline.
// On open ThickPolyline make no effect.
void start_at_index(int index);
Points points;
std::vector<coordf_t> width;
std::pair<bool,bool> endpoints { false, false };

View File

@ -786,7 +786,7 @@ std::pair<Preset*, bool> PresetCollection::load_external_preset(
{
// Load the preset over a default preset, so that the missing fields are filled in from the default preset.
DynamicPrintConfig cfg(this->default_preset_for(combined_config).config);
t_config_option_keys keys = std::move(cfg.keys());
t_config_option_keys keys = cfg.keys();
cfg.apply_only(combined_config, keys, true);
std::string &inherits = Preset::inherits(cfg);
if (select == LoadAndSelect::Never) {

View File

@ -591,7 +591,7 @@ void PrintConfigDef::init_fff_params()
"Fan speeds for overhang sizes in between are calculated via linear interpolation. ");
def = this->add("overhang_fan_speed_0", coInts);
def->label = L("speed for 0\% overlap (bridge)");
def->label = L("speed for 0% overlap (bridge)");
def->tooltip = fan_speed_setting_description;
def->sidetext = L("%");
def->min = 0;
@ -600,7 +600,7 @@ void PrintConfigDef::init_fff_params()
def->set_default_value(new ConfigOptionInts{0});
def = this->add("overhang_fan_speed_1", coInts);
def->label = L("speed for 25\% overlap");
def->label = L("speed for 25% overlap");
def->tooltip = fan_speed_setting_description;
def->sidetext = L("%");
def->min = 0;
@ -609,7 +609,7 @@ void PrintConfigDef::init_fff_params()
def->set_default_value(new ConfigOptionInts{0});
def = this->add("overhang_fan_speed_2", coInts);
def->label = L("speed for 50\% overlap");
def->label = L("speed for 50% overlap");
def->tooltip = fan_speed_setting_description;
def->sidetext = L("%");
def->min = 0;
@ -618,7 +618,7 @@ void PrintConfigDef::init_fff_params()
def->set_default_value(new ConfigOptionInts{0});
def = this->add("overhang_fan_speed_3", coInts);
def->label = L("speed for 75\% overlap");
def->label = L("speed for 75% overlap");
def->tooltip = fan_speed_setting_description;
def->sidetext = L("%");
def->min = 0;

View File

@ -60,6 +60,7 @@ enum InfillPattern : int {
ipRectilinear, ipMonotonic, ipMonotonicLines, ipAlignedRectilinear, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb,
ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipAdaptiveCubic, ipSupportCubic, ipSupportBase,
ipLightning,
ipEnsuring,
ipCount,
};
@ -162,6 +163,7 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(GCodeThumbnailsFormat)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PerimeterGeneratorType)
#undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS
// Defines each and every confiuration option of Slic3r, including the properties of the GUI dialogs.

File diff suppressed because it is too large Load Diff

View File

@ -4755,4 +4755,3 @@ sub clip_with_shape {
*/
} // namespace Slic3r

View File

@ -901,7 +901,7 @@ std::tuple<SupportPoints, PartialObjects> check_stability(const PrintObject *po,
params)) {
if (bridge.support_point_generated.has_value()) {
reckon_new_support_point(*bridge.support_point_generated, create_support_point_position(bridge.b),
-EPSILON, Vec2f::Zero());
float(-EPSILON), Vec2f::Zero());
}
}
}
@ -916,7 +916,7 @@ std::tuple<SupportPoints, PartialObjects> check_stability(const PrintObject *po,
params);
for (const ExtrusionLine &perim : perims) {
if (perim.support_point_generated.has_value()) {
reckon_new_support_point(*perim.support_point_generated, create_support_point_position(perim.b), -EPSILON,
reckon_new_support_point(*perim.support_point_generated, create_support_point_position(perim.b), float(-EPSILON),
Vec2f::Zero());
}
if (perim.is_external_perimeter()) {

View File

@ -145,6 +145,7 @@ struct PartialObject
using PartialObjects = std::vector<PartialObject>;
// Both support points and partial objects are sorted from the lowest z to the highest
std::tuple<SupportPoints, PartialObjects> full_search(const PrintObject *po, const PrintTryCancel& cancel_func, const Params &params);
void estimate_supports_malformations(std::vector<SupportLayer *> &layers, float supports_flow_width, const Params &params);

View File

@ -33,40 +33,31 @@ class Surface
public:
SurfaceType surface_type;
ExPolygon expolygon;
double thickness; // in mm
unsigned short thickness_layers; // in layers
double bridge_angle; // in radians, ccw, 0 = East, only 0+ (negative means undefined)
unsigned short extra_perimeters;
double thickness { -1 }; // in mm
unsigned short thickness_layers { 1 }; // in layers
double bridge_angle { -1. }; // in radians, ccw, 0 = East, only 0+ (negative means undefined)
unsigned short extra_perimeters { 0 };
Surface(const Slic3r::Surface &rhs)
: surface_type(rhs.surface_type), expolygon(rhs.expolygon),
thickness(rhs.thickness), thickness_layers(rhs.thickness_layers),
bridge_angle(rhs.bridge_angle), extra_perimeters(rhs.extra_perimeters)
{};
Surface(SurfaceType _surface_type, const ExPolygon &_expolygon)
: surface_type(_surface_type), expolygon(_expolygon),
thickness(-1), thickness_layers(1), bridge_angle(-1), extra_perimeters(0)
{};
Surface(const Surface &other, const ExPolygon &_expolygon)
: surface_type(other.surface_type), expolygon(_expolygon),
thickness(other.thickness), thickness_layers(other.thickness_layers),
bridge_angle(other.bridge_angle), extra_perimeters(other.extra_perimeters)
{};
Surface(Surface &&rhs)
: surface_type(rhs.surface_type), expolygon(std::move(rhs.expolygon)),
thickness(rhs.thickness), thickness_layers(rhs.thickness_layers),
bridge_angle(rhs.bridge_angle), extra_perimeters(rhs.extra_perimeters)
{};
Surface(SurfaceType _surface_type, ExPolygon &&_expolygon)
: surface_type(_surface_type), expolygon(std::move(_expolygon)),
thickness(-1), thickness_layers(1), bridge_angle(-1), extra_perimeters(0)
{};
Surface(const Surface &other, ExPolygon &&_expolygon)
: surface_type(other.surface_type), expolygon(std::move(_expolygon)),
thickness(other.thickness), thickness_layers(other.thickness_layers),
bridge_angle(other.bridge_angle), extra_perimeters(other.extra_perimeters)
{};
Surface(const Slic3r::Surface &rhs) :
surface_type(rhs.surface_type), expolygon(rhs.expolygon),
thickness(rhs.thickness), thickness_layers(rhs.thickness_layers),
bridge_angle(rhs.bridge_angle), extra_perimeters(rhs.extra_perimeters) {}
Surface(SurfaceType surface_type, const ExPolygon &expolygon) :
surface_type(surface_type), expolygon(expolygon) {}
Surface(const Surface &templ, const ExPolygon &expolygon) :
surface_type(templ.surface_type), expolygon(expolygon),
thickness(templ.thickness), thickness_layers(templ.thickness_layers),
bridge_angle(templ.bridge_angle), extra_perimeters(templ.extra_perimeters) {}
Surface(Surface &&rhs) :
surface_type(rhs.surface_type), expolygon(std::move(rhs.expolygon)),
thickness(rhs.thickness), thickness_layers(rhs.thickness_layers),
bridge_angle(rhs.bridge_angle), extra_perimeters(rhs.extra_perimeters) {}
Surface(SurfaceType surface_type, ExPolygon &&expolygon) :
surface_type(surface_type), expolygon(std::move(expolygon)) {}
Surface(const Surface &templ, ExPolygon &&expolygon) :
surface_type(templ.surface_type), expolygon(std::move(expolygon)),
thickness(templ.thickness), thickness_layers(templ.thickness_layers),
bridge_angle(templ.bridge_angle), extra_perimeters(templ.extra_perimeters) {}
Surface& operator=(const Surface &rhs)
{

View File

@ -51,16 +51,12 @@ SurfacesPtr SurfaceCollection::filter_by_type(const SurfaceType type) const
return ss;
}
SurfacesPtr SurfaceCollection::filter_by_types(const SurfaceType *types, int ntypes) const
SurfacesPtr SurfaceCollection::filter_by_types(std::initializer_list<SurfaceType> types) const
{
SurfacesPtr ss;
for (const Surface &surface : this->surfaces)
for (int i = 0; i < ntypes; ++ i) {
if (surface.surface_type == types[i]) {
ss.push_back(&surface);
break;
}
}
if (std::find(types.begin(), types.end(), surface.surface_type) != types.end())
ss.push_back(&surface);
return ss;
}
@ -85,23 +81,15 @@ void SurfaceCollection::keep_type(const SurfaceType type)
surfaces.erase(surfaces.begin() + j, surfaces.end());
}
void SurfaceCollection::keep_types(const SurfaceType *types, int ntypes)
void SurfaceCollection::keep_types(std::initializer_list<SurfaceType> types)
{
size_t j = 0;
for (size_t i = 0; i < surfaces.size(); ++ i) {
bool keep = false;
for (int k = 0; k < ntypes; ++ k) {
if (surfaces[i].surface_type == types[k]) {
keep = true;
break;
}
}
if (keep) {
for (size_t i = 0; i < surfaces.size(); ++ i)
if (std::find(types.begin(), types.end(), surfaces[i].surface_type) != types.end()) {
if (j < i)
std::swap(surfaces[i], surfaces[j]);
++ j;
}
}
if (j < surfaces.size())
surfaces.erase(surfaces.begin() + j, surfaces.end());
}
@ -136,23 +124,15 @@ void SurfaceCollection::remove_type(const SurfaceType type, ExPolygons *polygons
surfaces.erase(surfaces.begin() + j, surfaces.end());
}
void SurfaceCollection::remove_types(const SurfaceType *types, int ntypes)
void SurfaceCollection::remove_types(std::initializer_list<SurfaceType> types)
{
size_t j = 0;
for (size_t i = 0; i < surfaces.size(); ++ i) {
bool remove = false;
for (int k = 0; k < ntypes; ++ k) {
if (surfaces[i].surface_type == types[k]) {
remove = true;
break;
}
}
if (! remove) {
for (size_t i = 0; i < surfaces.size(); ++ i)
if (std::find(types.begin(), types.end(), surfaces[i].surface_type) == types.end()) {
if (j < i)
std::swap(surfaces[i], surfaces[j]);
++ j;
}
}
if (j < surfaces.size())
surfaces.erase(surfaces.begin() + j, surfaces.end());
}

View File

@ -3,6 +3,7 @@
#include "libslic3r.h"
#include "Surface.hpp"
#include <initializer_list>
#include <vector>
namespace Slic3r {
@ -27,11 +28,11 @@ public:
return false;
}
SurfacesPtr filter_by_type(const SurfaceType type) const;
SurfacesPtr filter_by_types(const SurfaceType *types, int ntypes) const;
SurfacesPtr filter_by_types(std::initializer_list<SurfaceType> types) const;
void keep_type(const SurfaceType type);
void keep_types(const SurfaceType *types, int ntypes);
void keep_types(std::initializer_list<SurfaceType> types);
void remove_type(const SurfaceType type);
void remove_types(const SurfaceType *types, int ntypes);
void remove_types(std::initializer_list<SurfaceType> types);
void filter_by_type(SurfaceType type, Polygons *polygons) const;
void remove_type(const SurfaceType type, ExPolygons *polygons);
void set_type(SurfaceType type) {

View File

@ -2421,7 +2421,6 @@ static void merge_influence_areas(
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_buckets_initial),
[&](const tbb::blocked_range<size_t> &range) {
for (size_t idx = range.begin(); idx < range.end(); ++ idx) {
const size_t bucket_pair_idx = idx * 2;
// Merge bucket_count adjacent to each other, merging uneven bucket numbers into even buckets
buckets[idx].second = merge_influence_areas_leaves(volumes, config, layer_idx, buckets[idx].first, buckets[idx].second);
throw_on_cancel();
@ -4012,8 +4011,12 @@ static indexed_triangle_set draw_branches(
// Only one link points to a node above from below.
assert(!(++it != map_downwards_old.end() && it->first == &elem));
}
const SupportElement *pchild = child == -1 ? nullptr : &move_bounds[layer_idx - 1][child];
assert(pchild ? pchild->state.result_on_layer_is_set() : elem.state.target_height > layer_idx);
#ifndef NDEBUG
{
const SupportElement *pchild = child == -1 ? nullptr : &move_bounds[layer_idx - 1][child];
assert(pchild ? pchild->state.result_on_layer_is_set() : elem.state.target_height > layer_idx);
}
#endif // NDEBUG
}
for (int32_t parent_idx : elem.parents) {
SupportElement &parent = (*layer_above)[parent_idx];
@ -4078,19 +4081,22 @@ static indexed_triangle_set draw_branches(
partial_mesh.clear();
extrude_branch(path, config, slicing_params, move_bounds, partial_mesh);
#if 0
char fname[2048];
sprintf(fname, "d:\\temp\\meshes\\tree-raw-%d.obj", ++ irun);
its_write_obj(partial_mesh, fname);
#if 0
temp_mesh.clear();
cut_mesh(partial_mesh, layer_z(slicing_params, path.back()->state.layer_idx) + EPSILON, nullptr, &temp_mesh, false);
sprintf(fname, "d:\\temp\\meshes\\tree-trimmed1-%d.obj", irun);
its_write_obj(temp_mesh, fname);
partial_mesh.clear();
cut_mesh(temp_mesh, layer_z(slicing_params, path.front()->state.layer_idx) - EPSILON, &partial_mesh, nullptr, false);
sprintf(fname, "d:\\temp\\meshes\\tree-trimmed2-%d.obj", irun);
#endif
its_write_obj(partial_mesh, fname);
{
char fname[2048];
static int irun = 0;
sprintf(fname, "d:\\temp\\meshes\\tree-raw-%d.obj", ++ irun);
its_write_obj(partial_mesh, fname);
#if 0
temp_mesh.clear();
cut_mesh(partial_mesh, layer_z(slicing_params, path.back()->state.layer_idx) + EPSILON, nullptr, &temp_mesh, false);
sprintf(fname, "d:\\temp\\meshes\\tree-trimmed1-%d.obj", irun);
its_write_obj(temp_mesh, fname);
partial_mesh.clear();
cut_mesh(temp_mesh, layer_z(slicing_params, path.front()->state.layer_idx) - EPSILON, &partial_mesh, nullptr, false);
sprintf(fname, "d:\\temp\\meshes\\tree-trimmed2-%d.obj", irun);
#endif
its_write_obj(partial_mesh, fname);
}
#endif
its_merge(cummulative_mesh, partial_mesh);
}

View File

@ -62,6 +62,8 @@ public:
// Inherits coord_t x, y
};
#define DEBUG_INTERSECTIONLINE (! defined(NDEBUG) || defined(SLIC3R_DEBUG_SLICE_PROCESSING))
class IntersectionLine : public Line
{
public:
@ -119,14 +121,14 @@ public:
};
uint32_t flags { 0 };
#ifndef NDEBUG
#if DEBUG_INTERSECTIONLINE
enum class Source {
BottomPlane,
TopPlane,
Slab,
};
Source source { Source::BottomPlane };
#endif // NDEBUG
#endif
};
using IntersectionLines = std::vector<IntersectionLine>;
@ -1440,24 +1442,24 @@ static std::vector<Polygons> make_slab_loops(
for (const IntersectionLine &l : lines.at_slice[slice_below])
if (l.edge_type != IntersectionLine::FacetEdgeType::Top) {
in.emplace_back(l);
#ifndef NDEBUG
#if DEBUG_INTERSECTIONLINE
in.back().source = IntersectionLine::Source::BottomPlane;
#endif // NDEBUG
#endif // DEBUG_INTERSECTIONLINE
}
}
{
// Edges in between slice_below and slice_above.
#ifndef NDEBUG
#if DEBUG_INTERSECTIONLINE
size_t old_size = in.size();
#endif // NDEBUG
#endif // DEBUG_INTERSECTIONLINE
// Edge IDs of end points on in-between lines that touch the layer above are already increased with num_edges.
append(in, lines.between_slices[line_idx]);
#ifndef NDEBUG
#if DEBUG_INTERSECTIONLINE
for (auto it = in.begin() + old_size; it != in.end(); ++ it) {
assert(it->edge_type == IntersectionLine::FacetEdgeType::Slab);
it->source = IntersectionLine::Source::Slab;
}
#endif // NDEBUG
#endif // DEBUG_INTERSECTIONLINE
}
if (has_slice_above) {
for (const IntersectionLine &lsrc : lines.at_slice[slice_above])
@ -1470,9 +1472,9 @@ static std::vector<Polygons> make_slab_loops(
l.edge_a_id += num_edges;
if (l.edge_b_id >= 0)
l.edge_b_id += num_edges;
#ifndef NDEBUG
#if DEBUG_INTERSECTIONLINE
l.source = IntersectionLine::Source::TopPlane;
#endif // NDEBUG
#endif // DEBUG_INTERSECTIONLINE
}
}
if (! in.empty()) {

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();
}

View File

@ -1,268 +0,0 @@
use Test::More tests => 17;
use strict;
use warnings;
BEGIN {
use FindBin;
use lib "$FindBin::Bin/../lib";
use local::lib "$FindBin::Bin/../local-lib";
}
use List::Util qw(first sum);
use Slic3r;
use Slic3r::Geometry qw(epsilon);
use Slic3r::Test;
# issue #1161
{
my $config = Slic3r::Config::new_from_defaults;
$config->set('layer_height', 0.3);
$config->set('first_layer_height', $config->layer_height);
$config->set('bottom_solid_layers', 0);
$config->set('top_solid_layers', 3);
$config->set('cooling', [ 0 ]);
$config->set('bridge_speed', 99);
$config->set('solid_infill_speed', 99);
$config->set('top_solid_infill_speed', 99);
$config->set('first_layer_speed', '100%');
$config->set('enable_dynamic_overhang_speeds', 0); # prevent speed alteration
my $print = Slic3r::Test::init_print('V', config => $config);
my %layers_with_solid_infill = (); # Z => 1
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
my ($self, $cmd, $args, $info) = @_;
$layers_with_solid_infill{$self->Z} = 1
if $info->{extruding} && ($args->{F} // $self->F) == $config->solid_infill_speed*60;
});
is scalar(map $layers_with_solid_infill{$_}, grep $_ <= 7.2, keys %layers_with_solid_infill), 3,
"correct number of top solid shells is generated in V-shaped object";
}
{
my $config = Slic3r::Config::new_from_defaults;
# we need to check against one perimeter because this test is calibrated
# (shape, extrusion_width) so that perimeters cover the bottom surfaces of
# their lower layer - the test checks that shells are not generated on the
# above layers (thus 'across' the shadow perimeter)
# the test is actually calibrated to leave a narrow bottom region for each
# layer - we test that in case of fill_density = 0 such narrow shells are
# discarded instead of grown
$config->set('perimeters', 1);
$config->set('fill_density', 0);
$config->set('cooling', [ 0 ]); # prevent speed alteration
$config->set('first_layer_speed', '100%'); # prevent speed alteration
$config->set('enable_dynamic_overhang_speeds', 0); # prevent speed alteration
$config->set('layer_height', 0.4);
$config->set('first_layer_height', $config->layer_height);
$config->set('extrusion_width', 0.55);
$config->set('bottom_solid_layers', 3);
$config->set('top_solid_layers', 0);
$config->set('solid_infill_speed', 99);
my $print = Slic3r::Test::init_print('V', config => $config);
my %layers = (); # Z => 1
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
my ($self, $cmd, $args, $info) = @_;
$layers{$self->Z} = 1
if $info->{extruding} && ($args->{F} // $self->F) == $config->solid_infill_speed*60;
});
is scalar(keys %layers), $config->bottom_solid_layers,
"shells are not propagated across perimeters of the neighbor layer";
}
{
my $config = Slic3r::Config::new_from_defaults;
$config->set('perimeters', 3);
$config->set('cooling', [ 0 ]); # prevent speed alteration
$config->set('first_layer_speed', '100%'); # prevent speed alteration
$config->set('enable_dynamic_overhang_speeds', 0); # prevent speed alteration
$config->set('layer_height', 0.4);
$config->set('first_layer_height', $config->layer_height);
$config->set('bottom_solid_layers', 3);
$config->set('top_solid_layers', 3);
$config->set('solid_infill_speed', 99);
$config->set('top_solid_infill_speed', 99);
$config->set('bridge_speed', 99);
$config->set('filament_diameter', [ 3.0 ]);
$config->set('nozzle_diameter', [ 0.5 ]);
my $print = Slic3r::Test::init_print('sloping_hole', config => $config);
my %solid_layers = (); # Z => 1
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
my ($self, $cmd, $args, $info) = @_;
$solid_layers{$self->Z} = 1
if $info->{extruding} && ($args->{F} // $self->F) == $config->solid_infill_speed*60;
});
is scalar(keys %solid_layers), $config->bottom_solid_layers + $config->top_solid_layers,
"no superfluous shells are generated";
}
{
my $config = Slic3r::Config::new_from_defaults;
$config->set('perimeters', 1);
$config->set('fill_density', 0);
$config->set('top_solid_layers', 0);
$config->set('spiral_vase', 1);
$config->set('bottom_solid_layers', 0);
$config->set('skirts', 0);
$config->set('first_layer_height', $config->layer_height);
$config->set('start_gcode', '');
$config->set('temperature', [200]);
$config->set('first_layer_temperature', [205]);
# TODO: this needs to be tested with a model with sloping edges, where starting
# points of each layer are not aligned - in that case we would test that no
# travel moves are left to move to the new starting point - in a cube, end
# points coincide with next layer starting points (provided there's no clipping)
my $test = sub {
my ($model_name, $description) = @_;
my $print = Slic3r::Test::init_print($model_name, config => $config);
my $travel_moves_after_first_extrusion = 0;
my $started_extruding = 0;
my $first_layer_temperature_set = 0;
my $temperature_set = 0;
my @z_steps = ();
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
my ($self, $cmd, $args, $info) = @_;
if ($cmd eq 'G1') {
$started_extruding = 1 if $info->{extruding};
push @z_steps, $info->{dist_Z}
if $started_extruding && $info->{dist_Z} > 0;
$travel_moves_after_first_extrusion++
if $info->{travel} && $info->{dist_XY} > 0 && $started_extruding && !exists $args->{Z};
} elsif ($cmd eq 'M104') {
$first_layer_temperature_set = 1 if $args->{S} == 205;
$temperature_set = 1 if $args->{S} == 200;
}
});
ok $first_layer_temperature_set, 'first layer temperature is preserved';
ok $temperature_set, 'temperature is preserved';
# we allow one travel move after first extrusion: i.e. when moving to the first
# spiral point after moving to second layer (bottom layer had loop clipping, so
# we're slightly distant from the starting point of the loop)
ok $travel_moves_after_first_extrusion <= 1, "no gaps in spiral vase ($description)";
ok !(grep { $_ > $config->layer_height + epsilon } @z_steps), "no gaps in Z ($description)";
};
$test->('20mm_cube', 'solid model');
$config->set('z_offset', -10);
$test->('20mm_cube', 'solid model with negative z-offset');
### Disabled because the current unreliable medial axis code doesn't
### always produce valid loops.
###$test->('40x10', 'hollow model with negative z-offset');
}
{
my $config = Slic3r::Config::new_from_defaults;
$config->set('spiral_vase', 1);
$config->set('perimeters', 1);
$config->set('fill_density', 0);
$config->set('top_solid_layers', 0);
$config->set('bottom_solid_layers', 0);
$config->set('retract_layer_change', [0]);
$config->set('skirts', 0);
$config->set('layer_height', 0.4);
$config->set('first_layer_height', $config->layer_height);
$config->set('start_gcode', '');
# $config->set('use_relative_e_distances', 1);
$config->validate;
my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
my $z_moves = 0;
my @this_layer = (); # [ dist_Z, dist_XY ], ...
my $bottom_layer_not_flat = 0;
my $null_z_moves_not_layer_changes = 0;
my $null_z_moves_not_multiples_of_layer_height = 0;
my $sum_of_partial_z_equals_to_layer_height = 0;
my $all_layer_segments_have_same_slope = 0;
my $horizontal_extrusions = 0;
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
my ($self, $cmd, $args, $info) = @_;
if ($cmd eq 'G1') {
if ($z_moves < 2) {
# skip everything up to the second Z move
# (i.e. start of second layer)
if (exists $args->{Z}) {
$z_moves++;
$bottom_layer_not_flat = 1
if $info->{dist_Z} > 0 && $info->{dist_Z} != $config->layer_height;
}
} elsif ($info->{dist_Z} == 0 && $args->{Z}) {
$null_z_moves_not_layer_changes = 1
if $info->{dist_XY} != 0;
# % doesn't work easily with floats
$null_z_moves_not_multiples_of_layer_height = 1
if abs(($args->{Z} / $config->layer_height) * $config->layer_height - $args->{Z}) > epsilon;
my $total_dist_XY = sum(map $_->[1], @this_layer);
$sum_of_partial_z_equals_to_layer_height = 1
if abs(sum(map $_->[0], @this_layer) - $config->layer_height) >
# The first segment on the 2nd layer has extrusion interpolated from zero
# and the 1st segment has such a low extrusion assigned, that it is effectively zero, thus the move
# is considered non-extruding and a higher epsilon is required.
($z_moves == 2 ? 0.0021 : epsilon);
#printf ("Total height: %f, layer height: %f, good: %d\n", sum(map $_->[0], @this_layer), $config->layer_height, $sum_of_partial_z_equals_to_layer_height);
foreach my $segment (@this_layer) {
# check that segment's dist_Z is proportioned to its dist_XY
$all_layer_segments_have_same_slope = 1
if abs($segment->[0]*$total_dist_XY/$config->layer_height - $segment->[1]) > 0.2;
}
@this_layer = ();
} elsif ($info->{extruding} && $info->{dist_XY} > 0) {
$horizontal_extrusions = 1
if $info->{dist_Z} == 0;
#printf("Pushing dist_z: %f, dist_xy: %f\n", $info->{dist_Z}, $info->{dist_XY});
push @this_layer, [ $info->{dist_Z}, $info->{dist_XY} ];
}
}
});
ok !$bottom_layer_not_flat, 'bottom layer is flat when using spiral vase';
ok !$null_z_moves_not_layer_changes, 'null Z moves are layer changes';
ok !$null_z_moves_not_multiples_of_layer_height, 'null Z moves are multiples of layer height';
ok !$sum_of_partial_z_equals_to_layer_height, 'sum of partial Z increments equals to a full layer height';
ok !$all_layer_segments_have_same_slope, 'all layer segments have the same slope';
ok !$horizontal_extrusions, 'no horizontal extrusions';
}
# The current Spiral Vase slicing code removes the holes and all but the largest contours from each slice,
# therefore the following test is no more valid.
#{
# my $config = Slic3r::Config::new_from_defaults;
# $config->set('perimeters', 1);
# $config->set('fill_density', 0);
# $config->set('top_solid_layers', 0);
# $config->set('spiral_vase', 1);
# $config->set('bottom_solid_layers', 0);
# $config->set('skirts', 0);
# $config->set('first_layer_height', $config->layer_height);
# $config->set('start_gcode', '');
#
# my $print = Slic3r::Test::init_print('two_hollow_squares', config => $config);
# my $diagonal_moves = 0;
# Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
# my ($self, $cmd, $args, $info) = @_;
#
# if ($cmd eq 'G1') {
# if ($info->{extruding} && $info->{dist_XY} > 0) {
# if ($info->{dist_Z} > 0) {
# $diagonal_moves++;
# }
# }
# }
# });
# is $diagonal_moves, 0, 'no spiral moves on two-island object';
#}
__END__

View File

@ -1,7 +1,7 @@
#include <catch2/catch.hpp>
#include "test_data.hpp"
#include "clipper/clipper_z.hpp"
#include "libslic3r/ClipperZUtils.hpp"
#include "libslic3r/clipper.hpp"
using namespace Slic3r;
@ -132,3 +132,72 @@ SCENARIO("Clipper Z", "[ClipperZ]")
REQUIRE(pt.z() == 1);
}
SCENARIO("Intersection with multiple polylines", "[ClipperZ]")
{
// 1000x1000 CCQ square
ClipperLib_Z::Path clip { { 0, 0, 1 }, { 1000, 0, 1 }, { 1000, 1000, 1 }, { 0, 1000, 1 } };
// Two lines interseting inside the square above, crossing the bottom edge of the square.
ClipperLib_Z::Path line1 { { +100, -100, 2 }, { +900, +900, 2 } };
ClipperLib_Z::Path line2 { { +100, +900, 3 }, { +900, -100, 3 } };
ClipperLib_Z::Clipper clipper;
ClipperZUtils::ClipperZIntersectionVisitor::Intersections intersections;
ClipperZUtils::ClipperZIntersectionVisitor visitor(intersections);
clipper.ZFillFunction(visitor.clipper_callback());
clipper.AddPath(line1, ClipperLib_Z::ptSubject, false);
clipper.AddPath(line2, ClipperLib_Z::ptSubject, false);
clipper.AddPath(clip, ClipperLib_Z::ptClip, true);
ClipperLib_Z::PolyTree polytree;
ClipperLib_Z::Paths paths;
clipper.Execute(ClipperLib_Z::ctIntersection, polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
ClipperLib_Z::PolyTreeToPaths(polytree, paths);
REQUIRE(paths.size() == 2);
THEN("First output polyline is a trimmed 2nd line") {
// Intermediate point (intersection) was removed)
REQUIRE(paths.front().size() == 2);
REQUIRE(paths.front().front().z() == 3);
REQUIRE(paths.front().back().z() < 0);
REQUIRE(intersections[- paths.front().back().z() - 1] == std::pair<coord_t, coord_t>(1, 3));
}
THEN("Second output polyline is a trimmed 1st line") {
// Intermediate point (intersection) was removed)
REQUIRE(paths[1].size() == 2);
REQUIRE(paths[1].front().z() < 0);
REQUIRE(paths[1].back().z() == 2);
REQUIRE(intersections[- paths[1].front().z() - 1] == std::pair<coord_t, coord_t>(1, 2));
}
}
SCENARIO("Interseting a closed loop as an open polyline", "[ClipperZ]")
{
// 1000x1000 CCQ square
ClipperLib_Z::Path clip{ { 0, 0, 1 }, { 1000, 0, 1 }, { 1000, 1000, 1 }, { 0, 1000, 1 } };
// Two lines interseting inside the square above, crossing the bottom edge of the square.
ClipperLib_Z::Path rect{ { 500, 500, 2}, { 500, 1500, 2 }, { 1500, 1500, 2}, { 500, 1500, 2}, { 500, 500, 2 } };
ClipperLib_Z::Clipper clipper;
clipper.AddPath(rect, ClipperLib_Z::ptSubject, false);
clipper.AddPath(clip, ClipperLib_Z::ptClip, true);
ClipperLib_Z::PolyTree polytree;
ClipperLib_Z::Paths paths;
ClipperZUtils::ClipperZIntersectionVisitor::Intersections intersections;
ClipperZUtils::ClipperZIntersectionVisitor visitor(intersections);
clipper.ZFillFunction(visitor.clipper_callback());
clipper.Execute(ClipperLib_Z::ctIntersection, polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
ClipperLib_Z::PolyTreeToPaths(std::move(polytree), paths);
THEN("Open polyline is clipped into two pieces") {
REQUIRE(paths.size() == 2);
REQUIRE(paths.front().size() == 2);
REQUIRE(paths.back().size() == 2);
REQUIRE(paths.front().front().z() == 2);
REQUIRE(paths.back().back().z() == 2);
REQUIRE(paths.front().front().x() == paths.back().back().x());
REQUIRE(paths.front().front().y() == paths.back().back().y());
}
}

View File

@ -9,16 +9,14 @@ using namespace Slic3r;
SCENARIO("Shells", "[Shells]") {
GIVEN("20mm box") {
auto test = [](const DynamicPrintConfig &config){
std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config);
auto test = [](const DynamicPrintConfig &config){
std::vector<coord_t> zs;
std::set<coord_t> layers_with_solid_infill;
std::set<coord_t> layers_with_bridge_infill;
const double solid_infill_speed = config.opt_float("solid_infill_speed") * 60;
const double bridge_speed = config.opt_float("bridge_speed") * 60;
GCodeReader parser;
parser.parse_buffer(gcode,
parser.parse_buffer(Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config),
[&zs, &layers_with_solid_infill, &layers_with_bridge_infill, solid_infill_speed, bridge_speed]
(Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line)
{
@ -110,3 +108,294 @@ SCENARIO("Shells", "[Shells]") {
}
}
}
static std::set<double> layers_with_speed(const std::string &gcode, int speed)
{
std::set<double> out;
GCodeReader parser;
parser.parse_buffer(gcode, [&out, speed](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) {
if (line.extruding(self) && is_approx<double>(line.new_F(self), speed * 60.))
out.insert(self.z());
});
return out;
}
SCENARIO("Shells (from Perl)", "[Shells]") {
GIVEN("V shape, Slic3r GH #1161") {
int solid_speed = 99;
auto config = Slic3r::DynamicPrintConfig::full_print_config_with({
{ "layer_height", 0.3 },
{ "first_layer_height", 0.3 },
{ "bottom_solid_layers", 0 },
{ "top_solid_layers", 3 },
// to prevent speeds from being altered
{ "cooling", "0" },
{ "bridge_speed", solid_speed },
{ "solid_infill_speed", solid_speed },
{ "top_solid_infill_speed", solid_speed },
// to prevent speeds from being altered
{ "first_layer_speed", "100%" },
// prevent speed alteration
{ "enable_dynamic_overhang_speeds", 0 }
});
THEN("correct number of top solid shells is generated in V-shaped object") {
size_t n = 0;
for (auto z : layers_with_speed(Slic3r::Test::slice({TestMesh::V}, config), solid_speed))
if (z <= 7.2)
++ n;
REQUIRE(n == 3);
}
}
GIVEN("V shape") {
// we need to check against one perimeter because this test is calibrated
// (shape, extrusion_width) so that perimeters cover the bottom surfaces of
// their lower layer - the test checks that shells are not generated on the
// above layers (thus 'across' the shadow perimeter)
// the test is actually calibrated to leave a narrow bottom region for each
// layer - we test that in case of fill_density = 0 such narrow shells are
// discarded instead of grown
int bottom_solid_layers = 3;
auto config = Slic3r::DynamicPrintConfig::full_print_config_with({
{ "perimeters", 1 },
{ "fill_density", 0 },
// to prevent speeds from being altered
{ "cooling", "0" },
// to prevent speeds from being altered
{ "first_layer_speed", "100%" },
// prevent speed alteration
{ "enable_dynamic_overhang_speeds", 0 },
{ "layer_height", 0.4 },
{ "first_layer_height", 0.4 },
{ "extrusion_width", 0.55 },
{ "bottom_solid_layers", bottom_solid_layers },
{ "top_solid_layers", 0 },
{ "solid_infill_speed", 99 }
});
THEN("shells are not propagated across perimeters of the neighbor layer") {
std::string gcode = Slic3r::Test::slice({TestMesh::V}, config);
REQUIRE(layers_with_speed(gcode, 99).size() == bottom_solid_layers);
}
}
GIVEN("sloping_hole") {
int bottom_solid_layers = 3;
int top_solid_layers = 3;
int solid_speed = 99;
auto config = Slic3r::DynamicPrintConfig::full_print_config_with({
{ "perimeters", 3 },
// to prevent speeds from being altered
{ "cooling", "0" },
// to prevent speeds from being altered
{ "first_layer_speed", "100%" },
// prevent speed alteration
{ "enable_dynamic_overhang_speeds", 0 },
{ "layer_height", 0.4 },
{ "first_layer_height", 0.4 },
{ "bottom_solid_layers", bottom_solid_layers },
{ "top_solid_layers", top_solid_layers },
{ "solid_infill_speed", solid_speed },
{ "top_solid_infill_speed", solid_speed },
{ "bridge_speed", solid_speed },
{ "filament_diameter", 3. },
{ "nozzle_diameter", 0.5 }
});
THEN("no superfluous shells are generated") {
std::string gcode = Slic3r::Test::slice({TestMesh::sloping_hole}, config);
REQUIRE(layers_with_speed(gcode, solid_speed).size() == bottom_solid_layers + top_solid_layers);
}
}
GIVEN("20mm_cube, spiral vase") {
double layer_height = 0.3;
auto config = Slic3r::DynamicPrintConfig::full_print_config_with({
{ "perimeters", 1 },
{ "fill_density", 0 },
{ "layer_height", layer_height },
{ "first_layer_height", layer_height },
{ "top_solid_layers", 0 },
{ "spiral_vase", 1 },
{ "bottom_solid_layers", 0 },
{ "skirts", 0 },
{ "start_gcode", "" },
{ "temperature", 200 },
{ "first_layer_temperature", 205}
});
// TODO: this needs to be tested with a model with sloping edges, where starting
// points of each layer are not aligned - in that case we would test that no
// travel moves are left to move to the new starting point - in a cube, end
// points coincide with next layer starting points (provided there's no clipping)
auto test = [layer_height](const DynamicPrintConfig &config) {
size_t travel_moves_after_first_extrusion = 0;
bool started_extruding = false;
bool first_layer_temperature_set = false;
bool temperature_set = false;
std::vector<double> z_steps;
GCodeReader parser;
parser.parse_buffer(Slic3r::Test::slice({TestMesh::cube_20x20x20}, config),
[&](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) {
if (line.cmd_is("G1")) {
if (line.extruding(self))
started_extruding = true;
if (started_extruding) {
if (double dz = line.dist_Z(self); dz > 0)
z_steps.emplace_back(dz);
if (line.travel() && line.dist_XY(self) > 0 && ! line.has(Z))
++ travel_moves_after_first_extrusion;
}
} else if (line.cmd_is("M104")) {
int s;
if (line.has_value('S', s)) {
if (s == 205)
first_layer_temperature_set = true;
else if (s == 200)
temperature_set = true;
}
}
});
THEN("first layer temperature is set") {
REQUIRE(first_layer_temperature_set);
}
THEN("temperature is set") {
REQUIRE(temperature_set);
}
// we allow one travel move after first extrusion: i.e. when moving to the first
// spiral point after moving to second layer (bottom layer had loop clipping, so
// we're slightly distant from the starting point of the loop)
THEN("no gaps in spiral vase") {
REQUIRE(travel_moves_after_first_extrusion <= 1);
}
THEN("no gaps in Z") {
REQUIRE(std::count_if(z_steps.begin(), z_steps.end(),
[&layer_height](auto z_step) { return z_step > layer_height + EPSILON; }) == 0);
}
};
WHEN("solid model") {
test(config);
}
WHEN("solid model with negative z-offset") {
config.set_deserialize_strict("z_offset", "-10");
test(config);
}
// Disabled because the current unreliable medial axis code doesn't always produce valid loops.
// $test->('40x10', 'hollow model with negative z-offset');
}
GIVEN("20mm_cube, spiral vase") {
double layer_height = 0.4;
auto config = Slic3r::DynamicPrintConfig::full_print_config_with({
{ "spiral_vase", 1 },
{ "perimeters", 1 },
{ "fill_density", 0 },
{ "top_solid_layers", 0 },
{ "bottom_solid_layers", 0 },
{ "retract_layer_change", 0 },
{ "skirts", 0 },
{ "layer_height", layer_height },
{ "first_layer_height", layer_height },
{ "start_gcode", "" },
// { "use_relative_e_distances", 1}
});
config.validate();
std::vector<std::pair<double, double>> this_layer; // [ dist_Z, dist_XY ], ...
int z_moves = 0;
bool bottom_layer_not_flat = false;
bool null_z_moves_not_layer_changes = false;
bool null_z_moves_not_multiples_of_layer_height = false;
bool sum_of_partial_z_equals_to_layer_height = false;
bool all_layer_segments_have_same_slope = false;
bool horizontal_extrusions = false;
GCodeReader parser;
parser.parse_buffer(Slic3r::Test::slice({TestMesh::cube_20x20x20}, config),
[&](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) {
if (line.cmd_is("G1")) {
if (z_moves < 2) {
// skip everything up to the second Z move
// (i.e. start of second layer)
if (line.has(Z)) {
++ z_moves;
if (double dz = line.dist_Z(self); dz > 0 && ! is_approx<double>(dz, layer_height))
bottom_layer_not_flat = true;
}
} else if (line.dist_Z(self) == 0 && line.has(Z)) {
if (line.dist_XY(self) != 0)
null_z_moves_not_layer_changes = true;
double z = line.new_Z(self);
if (fmod(z + EPSILON, layer_height) > 2 * EPSILON)
null_z_moves_not_multiples_of_layer_height = true;
double total_dist_XY = 0;
double total_dist_Z = 0;
for (auto &seg : this_layer) {
total_dist_Z += seg.first;
total_dist_XY += seg.second;
}
if (std::abs(total_dist_Z - layer_height) >
// The first segment on the 2nd layer has extrusion interpolated from zero
// and the 1st segment has such a low extrusion assigned, that it is effectively zero, thus the move
// is considered non-extruding and a higher epsilon is required.
(z_moves == 2 ? 0.0021 : EPSILON))
sum_of_partial_z_equals_to_layer_height = true;
//printf("Total height: %f, layer height: %f, good: %d\n", sum(map $_->[0], @this_layer), $config->layer_height, $sum_of_partial_z_equals_to_layer_height);
for (auto &seg : this_layer)
// check that segment's dist_Z is proportioned to its dist_XY
if (std::abs(seg.first * total_dist_XY / layer_height - seg.second) > 0.2)
all_layer_segments_have_same_slope = true;
this_layer.clear();
} else if (line.extruding(self) && line.dist_XY(self) > 0) {
if (line.dist_Z(self) == 0)
horizontal_extrusions = true;
//printf("Pushing dist_z: %f, dist_xy: %f\n", $info->{dist_Z}, $info->{dist_XY});
this_layer.emplace_back(line.dist_Z(self), line.dist_XY(self));
}
}
});
THEN("bottom layer is flat when using spiral vase") {
REQUIRE(! bottom_layer_not_flat);
}
THEN("null Z moves are layer changes") {
REQUIRE(! null_z_moves_not_layer_changes);
}
THEN("null Z moves are multiples of layer height") {
REQUIRE(! null_z_moves_not_multiples_of_layer_height);
}
THEN("sum of partial Z increments equals to a full layer height") {
REQUIRE(! sum_of_partial_z_equals_to_layer_height);
}
THEN("all layer segments have the same slope") {
REQUIRE(! all_layer_segments_have_same_slope);
}
THEN("no horizontal extrusions") {
REQUIRE(! horizontal_extrusions);
}
}
}
#if 0
// The current Spiral Vase slicing code removes the holes and all but the largest contours from each slice,
// therefore the following test is no more valid.
{
my $config = Slic3r::Config::new_from_defaults;
$config->set('perimeters', 1);
$config->set('fill_density', 0);
$config->set('top_solid_layers', 0);
$config->set('spiral_vase', 1);
$config->set('bottom_solid_layers', 0);
$config->set('skirts', 0);
$config->set('first_layer_height', $config->layer_height);
$config->set('start_gcode', '');
my $print = Slic3r::Test::init_print('two_hollow_squares', config => $config);
my $diagonal_moves = 0;
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
my ($self, $cmd, $args, $info) = @_;
if ($cmd eq 'G1') {
if ($info->{extruding} && $info->{dist_XY} > 0) {
if ($info->{dist_Z} > 0) {
$diagonal_moves++;
}
}
}
});
is $diagonal_moves, 0, 'no spiral moves on two-island object';
}
#endif

View File

@ -23,6 +23,7 @@ add_executable(${_TEST_NAME}_tests
test_stl.cpp
test_meshboolean.cpp
test_marchingsquares.cpp
test_region_expansion.cpp
test_timeutils.cpp
test_utils.cpp
test_voronoi.cpp

View File

@ -31,6 +31,7 @@ SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") {
parser.set("foo", 0);
parser.set("bar", 2);
parser.set("num_extruders", 4);
parser.set("gcode_flavor", "marlin");
SECTION("nested config options (legacy syntax)") { REQUIRE(parser.process("[temperature_[foo]]") == "357"); }
SECTION("array reference") { REQUIRE(parser.process("{temperature[foo]}") == "357"); }
@ -115,4 +116,5 @@ SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") {
SECTION("complex expression") { REQUIRE(boolean_expression("printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 and num_extruders>1")); }
SECTION("complex expression2") { REQUIRE(boolean_expression("printer_notes=~/.*PRINTER_VEwerfNDOR_PRUSA3D.*/ or printer_notes=~/.*PRINTertER_MODEL_MK2.*/ or (nozzle_diameter[0]==0.6 and num_extruders>1)")); }
SECTION("complex expression3") { REQUIRE(! boolean_expression("printer_notes=~/.*PRINTER_VEwerfNDOR_PRUSA3D.*/ or printer_notes=~/.*PRINTertER_MODEL_MK2.*/ or (nozzle_diameter[0]==0.3 and num_extruders>1)")); }
SECTION("enum expression") { REQUIRE(boolean_expression("gcode_flavor == \"marlin\"")); }
}

View File

@ -196,19 +196,6 @@ SCENARIO("Simplify polygon", "[Polygon]")
}
}
}
GIVEN("hole in square") {
// CW oriented
auto hole_in_square = Polygon{ {140, 140}, {140, 160}, {160, 160}, {160, 140} };
WHEN("simplified") {
Polygons simplified = hole_in_square.simplify(2.);
THEN("hole simplification returns one polygon") {
REQUIRE(simplified.size() == 1);
}
THEN("hole simplification turns cw polygon into ccw polygon") {
REQUIRE(simplified.front().is_counter_clockwise());
}
}
}
}
#include "libslic3r/ExPolygon.hpp"

View File

@ -0,0 +1,284 @@
#include <catch2/catch.hpp>
#include <libslic3r/libslic3r.h>
#include <libslic3r/Algorithm/RegionExpansion.hpp>
#include <libslic3r/ClipperUtils.hpp>
#include <libslic3r/ExPolygon.hpp>
#include <libslic3r/Polygon.hpp>
#include <libslic3r/SVG.cpp>
using namespace Slic3r;
//#define DEBUG_TEMP_DIR "d:\\temp\\"
SCENARIO("Region expansion basics", "[RegionExpansion]") {
static constexpr const coord_t ten = scaled<coord_t>(10.);
GIVEN("two touching squares") {
Polygon square1{ { 1 * ten, 1 * ten }, { 2 * ten, 1 * ten }, { 2 * ten, 2 * ten }, { 1 * ten, 2 * ten } };
Polygon square2{ { 2 * ten, 1 * ten }, { 3 * ten, 1 * ten }, { 3 * ten, 2 * ten }, { 2 * ten, 2 * ten } };
Polygon square3{ { 1 * ten, 2 * ten }, { 2 * ten, 2 * ten }, { 2 * ten, 3 * ten }, { 1 * ten, 3 * ten } };
static constexpr const float expansion = scaled<float>(1.);
auto test_expansion = [](const Polygon &src, const Polygon &boundary) {
std::vector<Polygons> expanded = Algorithm::expand_expolygons({ ExPolygon{src} }, { ExPolygon{boundary} },
expansion,
scaled<float>(0.3), // expansion step
5); // max num steps
THEN("Single anchor is produced") {
REQUIRE(expanded.size() == 1);
}
THEN("The area of the anchor is 10mm2") {
REQUIRE(area(expanded.front()) == Approx(expansion * ten));
}
};
WHEN("second square expanded into the first square (to left)") {
test_expansion(square2, square1);
}
WHEN("first square expanded into the second square (to right)") {
test_expansion(square1, square2);
}
WHEN("third square expanded into the first square (down)") {
test_expansion(square3, square1);
}
WHEN("first square expanded into the third square (up)") {
test_expansion(square1, square3);
}
}
GIVEN("simple bridge") {
Polygon square1{ { 1 * ten, 1 * ten }, { 2 * ten, 1 * ten }, { 2 * ten, 2 * ten }, { 1 * ten, 2 * ten } };
Polygon square2{ { 2 * ten, 1 * ten }, { 3 * ten, 1 * ten }, { 3 * ten, 2 * ten }, { 2 * ten, 2 * ten } };
Polygon square3{ { 3 * ten, 1 * ten }, { 4 * ten, 1 * ten }, { 4 * ten, 2 * ten }, { 3 * ten, 2 * ten } };
WHEN("expanded") {
static constexpr const float expansion = scaled<float>(1.);
std::vector<Polygons> expanded = Algorithm::expand_expolygons({ ExPolygon{square2} }, { ExPolygon{square1}, ExPolygon{square3} },
expansion,
scaled<float>(0.3), // expansion step
5); // max num steps
THEN("Two anchors are produced") {
REQUIRE(expanded.size() == 1);
REQUIRE(expanded.front().size() == 2);
}
THEN("The area of each anchor is 10mm2") {
REQUIRE(area(expanded.front().front()) == Approx(expansion * ten));
REQUIRE(area(expanded.front().back()) == Approx(expansion * ten));
}
}
WHEN("fully expanded") {
static constexpr const float expansion = scaled<float>(10.1);
std::vector<Polygons> expanded = Algorithm::expand_expolygons({ ExPolygon{square2} }, { ExPolygon{square1}, ExPolygon{square3} },
expansion,
scaled<float>(2.3), // expansion step
5); // max num steps
THEN("Two anchors are produced") {
REQUIRE(expanded.size() == 1);
REQUIRE(expanded.front().size() == 2);
}
THEN("The area of each anchor is 100mm2") {
REQUIRE(area(expanded.front().front()) == Approx(sqr<double>(ten)));
REQUIRE(area(expanded.front().back()) == Approx(sqr<double>(ten)));
}
}
}
GIVEN("two bridges") {
Polygon left_support { { 1 * ten, 1 * ten }, { 2 * ten, 1 * ten }, { 2 * ten, 4 * ten }, { 1 * ten, 4 * ten } };
Polygon right_support { { 3 * ten, 1 * ten }, { 4 * ten, 1 * ten }, { 4 * ten, 4 * ten }, { 3 * ten, 4 * ten } };
Polygon bottom_bridge { { 2 * ten, 1 * ten }, { 3 * ten, 1 * ten }, { 3 * ten, 2 * ten }, { 2 * ten, 2 * ten } };
Polygon top_bridge { { 2 * ten, 3 * ten }, { 3 * ten, 3 * ten }, { 3 * ten, 4 * ten }, { 2 * ten, 4 * ten } };
WHEN("expanded") {
static constexpr const float expansion = scaled<float>(1.);
std::vector<Polygons> expanded = Algorithm::expand_expolygons({ ExPolygon{bottom_bridge}, ExPolygon{top_bridge} }, { ExPolygon{left_support}, ExPolygon{right_support} },
expansion,
scaled<float>(0.3), // expansion step
5); // max num steps
#if 0
SVG::export_expolygons(DEBUG_TEMP_DIR "two_bridges-out.svg",
{ { { { ExPolygon{left_support}, ExPolygon{right_support} } }, { "supports", "orange", 0.5f } },
{ { { ExPolygon{bottom_bridge}, ExPolygon{top_bridge} } }, { "bridges", "blue", 0.5f } },
{ { union_ex(union_(expanded.front(), expanded.back())) }, { "expanded", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
#endif
THEN("Two anchors are produced for each bridge") {
REQUIRE(expanded.size() == 2);
REQUIRE(expanded.front().size() == 2);
REQUIRE(expanded.back().size() == 2);
}
THEN("The area of each anchor is 10mm2") {
double a = expansion * ten + M_PI * sqr(expansion) / 4;
double eps = sqr(scaled<double>(0.1));
REQUIRE(is_approx(area(expanded.front().front()), a, eps));
REQUIRE(is_approx(area(expanded.front().back()), a, eps));
REQUIRE(is_approx(area(expanded.back().front()), a, eps));
REQUIRE(is_approx(area(expanded.back().back()), a, eps));
}
}
}
GIVEN("rectangle with rhombic cut-out") {
double diag = 1 * ten * sqrt(2.) / 4.;
Polygon square_with_rhombic_cutout{ { 0, 0 }, { 1 * ten, 0 }, { ten / 2, ten / 2 }, { 1 * ten, 1 * ten }, { 0, 1 * ten } };
Polygon rhombic { { ten / 2, ten / 2 }, { 3 * ten / 4, ten / 4 }, { 1 * ten, ten / 2 }, { 3 * ten / 4, 3 * ten / 4 } };
WHEN("expanded") {
static constexpr const float expansion = scaled<float>(1.);
std::vector<Polygons> expanded = Algorithm::expand_expolygons({ ExPolygon{rhombic} }, { ExPolygon{square_with_rhombic_cutout} },
expansion,
scaled<float>(0.1), // expansion step
11); // max num steps
#if 0
SVG::export_expolygons(DEBUG_TEMP_DIR "rectangle_with_rhombic_cut-out.svg",
{ { { { ExPolygon{square_with_rhombic_cutout} } }, { "square_with_rhombic_cutout", "orange", 0.5f } },
{ { { ExPolygon{rhombic} } }, { "rhombic", "blue", 0.5f } },
{ { union_ex(expanded.front()) }, { "bridges", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
#endif
THEN("Single anchor is produced") {
REQUIRE(expanded.size() == 1);
}
THEN("The area of anchor is correct") {
double area_calculated = area(expanded.front());
double area_expected = 2. * diag * expansion + M_PI * sqr(expansion) * 0.75;
REQUIRE(is_approx(area_expected, area_calculated, sqr(scaled<double>(0.2))));
}
}
WHEN("extra expanded") {
static constexpr const float expansion = scaled<float>(2.5);
std::vector<Polygons> expanded = Algorithm::expand_expolygons({ ExPolygon{rhombic} }, { ExPolygon{square_with_rhombic_cutout} },
expansion,
scaled<float>(0.25), // expansion step
11); // max num steps
#if 0
SVG::export_expolygons(DEBUG_TEMP_DIR "rectangle_with_rhombic_cut-out2.svg",
{ { { { ExPolygon{square_with_rhombic_cutout} } }, { "square_with_rhombic_cutout", "orange", 0.5f } },
{ { { ExPolygon{rhombic} } }, { "rhombic", "blue", 0.5f } },
{ { union_ex(expanded.front()) }, { "bridges", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
#endif
THEN("Single anchor is produced") {
REQUIRE(expanded.size() == 1);
}
THEN("The area of anchor is correct") {
double area_calculated = area(expanded.front());
double area_expected = 2. * diag * expansion + M_PI * sqr(expansion) * 0.75;
REQUIRE(is_approx(area_expected, area_calculated, sqr(scaled<double>(0.3))));
}
}
}
GIVEN("square with two holes") {
Polygon outer{ { 0, 0 }, { 3 * ten, 0 }, { 3 * ten, 5 * ten }, { 0, 5 * ten } };
Polygon hole1{ { 1 * ten, 1 * ten }, { 1 * ten, 2 * ten }, { 2 * ten, 2 * ten }, { 2 * ten, 1 * ten } };
Polygon hole2{ { 1 * ten, 3 * ten }, { 1 * ten, 4 * ten }, { 2 * ten, 4 * ten }, { 2 * ten, 3 * ten } };
ExPolygon boundary(outer);
boundary.holes = { hole1, hole2 };
Polygon anchor{ { -1 * ten, coord_t(1.5 * ten) }, { 0 * ten, coord_t(1.5 * ten) }, { 0, coord_t(3.5 * ten) }, { -1 * ten, coord_t(3.5 * ten) } };
WHEN("expanded") {
static constexpr const float expansion = scaled<float>(5.);
std::vector<Polygons> expanded = Algorithm::expand_expolygons({ ExPolygon{anchor} }, { boundary },
expansion,
scaled<float>(0.4), // expansion step
15); // max num steps
#if 0
SVG::export_expolygons(DEBUG_TEMP_DIR "square_with_two_holes-out.svg",
{ { { { ExPolygon{anchor} } }, { "anchor", "orange", 0.5f } },
{ { { boundary } }, { "boundary", "blue", 0.5f } },
{ { union_ex(expanded.front()) }, { "expanded", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
#endif
THEN("The anchor expands into a single region") {
REQUIRE(expanded.size() == 1);
REQUIRE(expanded.front().size() == 1);
}
THEN("The area of anchor is correct") {
double area_calculated = area(expanded.front());
double area_expected = double(expansion) * 2. * double(ten) + M_PI * sqr(expansion) * 0.5;
REQUIRE(is_approx(area_expected, area_calculated, sqr(scaled<double>(0.45))));
}
}
WHEN("expanded even more") {
static constexpr const float expansion = scaled<float>(25.);
std::vector<Polygons> expanded = Algorithm::expand_expolygons({ ExPolygon{anchor} }, { boundary },
expansion,
scaled<float>(2.), // expansion step
15); // max num steps
#if 0
SVG::export_expolygons(DEBUG_TEMP_DIR "square_with_two_holes-expanded2-out.svg",
{ { { { ExPolygon{anchor} } }, { "anchor", "orange", 0.5f } },
{ { { boundary } }, { "boundary", "blue", 0.5f } },
{ { union_ex(expanded.front()) }, { "expanded", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
#endif
THEN("The anchor expands into a single region") {
REQUIRE(expanded.size() == 1);
REQUIRE(expanded.front().size() == 1);
}
}
WHEN("expanded yet even more") {
static constexpr const float expansion = scaled<float>(28.);
std::vector<Polygons> expanded = Algorithm::expand_expolygons({ ExPolygon{anchor} }, { boundary },
expansion,
scaled<float>(2.), // expansion step
20); // max num steps
#if 0
SVG::export_expolygons(DEBUG_TEMP_DIR "square_with_two_holes-expanded3-out.svg",
{ { { { ExPolygon{anchor} } }, { "anchor", "orange", 0.5f } },
{ { { boundary } }, { "boundary", "blue", 0.5f } },
{ { union_ex(expanded.front()) }, { "expanded", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
#endif
THEN("The anchor expands into a single region with two holes") {
REQUIRE(expanded.size() == 1);
REQUIRE(expanded.front().size() == 3);
}
}
WHEN("expanded fully") {
static constexpr const float expansion = scaled<float>(35.);
std::vector<Polygons> expanded = Algorithm::expand_expolygons({ ExPolygon{anchor} }, { boundary },
expansion,
scaled<float>(2.), // expansion step
25); // max num steps
#if 0
SVG::export_expolygons(DEBUG_TEMP_DIR "square_with_two_holes-expanded_fully-out.svg",
{ { { { ExPolygon{anchor} } }, { "anchor", "orange", 0.5f } },
{ { { boundary } }, { "boundary", "blue", 0.5f } },
{ { union_ex(expanded.front()) }, { "expanded", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
#endif
THEN("The anchor expands into a single region with two holes, fully covering the boundary") {
REQUIRE(expanded.size() == 1);
REQUIRE(expanded.front().size() == 3);
REQUIRE(area(expanded.front()) == Approx(area(boundary)));
}
}
}
GIVEN("square with hole, hole edge anchored") {
Polygon outer{ { -1 * ten, -1 * ten }, { 2 * ten, -1 * ten }, { 2 * ten, 2 * ten }, { -1 * ten, 2 * ten } };
Polygon hole { { 0, ten }, { ten, ten }, { ten, 0 }, { 0, 0 } };
Polygon anchor{ { 0, 0 }, { ten, 0 }, { ten, ten }, { 0, ten } };
ExPolygon boundary(outer);
boundary.holes = { hole };
WHEN("expanded") {
static constexpr const float expansion = scaled<float>(5.);
std::vector<Polygons> expanded = Algorithm::expand_expolygons({ ExPolygon{anchor} }, { boundary },
expansion,
scaled<float>(0.4), // expansion step
15); // max num steps
#if 0
SVG::export_expolygons(DEBUG_TEMP_DIR "square_with_hole_anchored-out.svg",
{ { { { ExPolygon{anchor} } }, { "anchor", "orange", 0.5f } },
{ { { boundary } }, { "boundary", "blue", 0.5f } },
{ { union_ex(expanded.front()) }, { "expanded", "red", "black", "", scaled<coord_t>(0.1f), 0.5f } } });
#endif
THEN("The anchor expands into a single region with a hole") {
REQUIRE(expanded.size() == 1);
REQUIRE(expanded.front().size() == 2);
}
THEN("The area of anchor is correct") {
double area_calculated = area(expanded.front());
double area_expected = double(expansion) * 4. * double(ten) + M_PI * sqr(expansion);
REQUIRE(is_approx(area_expected, area_calculated, sqr(scaled<double>(0.6))));
}
}
}
}