Merge branch 'master' into fs_emboss

This commit is contained in:
Filip Sykala - NTB T15p 2023-03-03 08:20:26 +01:00
commit 1dcc96a607
61 changed files with 3461 additions and 1035 deletions

View File

@ -4109,19 +4109,40 @@ void AddPolyNodeToPaths(const PolyNode& polynode, NodeType nodetype, Paths& path
for (int i = 0; i < polynode.ChildCount(); ++i) for (int i = 0; i < polynode.ChildCount(); ++i)
AddPolyNodeToPaths(*polynode.Childs[i], nodetype, paths); 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) void PolyTreeToPaths(const PolyTree& polytree, Paths& paths)
{ {
paths.resize(0); paths.clear();
paths.reserve(polytree.Total()); paths.reserve(polytree.Total());
AddPolyNodeToPaths(polytree, ntAny, paths); 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) void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths)
{ {
paths.resize(0); paths.clear();
paths.reserve(polytree.Total()); paths.reserve(polytree.Total());
AddPolyNodeToPaths(polytree, ntClosed, paths); AddPolyNodeToPaths(polytree, ntClosed, paths);
} }
@ -4129,7 +4150,7 @@ void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths)
void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths) void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths)
{ {
paths.resize(0); paths.clear();
paths.reserve(polytree.Total()); paths.reserve(polytree.Total());
//Open paths are top level only, so ... //Open paths are top level only, so ...
for (int i = 0; i < polytree.ChildCount(); ++i) 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 MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution);
void PolyTreeToPaths(const PolyTree& polytree, Paths& paths); void PolyTreeToPaths(const PolyTree& polytree, Paths& paths);
void PolyTreeToPaths(PolyTree&& polytree, Paths& paths);
void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths); void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths);
void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths); void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths);

View File

@ -13,6 +13,7 @@
#include <Eigen/Geometry> #include <Eigen/Geometry>
#include "BoundingBox.hpp"
#include "Utils.hpp" // for next_highest_power_of_2() #include "Utils.hpp" // for next_highest_power_of_2()
// Definition of the ray intersection hit structure. // Definition of the ray intersection hit structure.
@ -217,6 +218,23 @@ using Tree3f = Tree<3, float>;
using Tree2d = Tree<2, double>; using Tree2d = Tree<2, double>;
using Tree3d = Tree<3, 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 { namespace detail {
template<typename AVertexType, typename AIndexedFaceType, typename ATreeType, typename AVectorType> template<typename AVertexType, typename AIndexedFaceType, typename ATreeType, typename AVectorType>
struct RayIntersector { 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) : BoundingBoxBase(const PointClass &p1, const PointClass &p2, const PointClass &p3) :
min(p1), max(p1), defined(false) { merge(p2); merge(p3); } min(p1), max(p1), defined(false) { merge(p2); merge(p3); }
template<class It, class = IteratorOnly<It> > template<class It, class = IteratorOnly<It>>
BoundingBoxBase(It from, It to) : min(PointClass::Zero()), max(PointClass::Zero()) BoundingBoxBase(It from, It to)
{ { construct(*this, from, to); }
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());
}
}
BoundingBoxBase(const std::vector<PointClass> &points) BoundingBoxBase(const std::vector<PointClass> &points)
: BoundingBoxBase(points.begin(), points.end()) : 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->min == rhs.min && this->max == rhs.max; }
bool operator!=(const BoundingBoxBase<PointClass> &rhs) { return ! (*this == rhs); } 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> template <class PointClass>

View File

@ -75,12 +75,9 @@ private:
//return ideal bridge direction and unsupported bridge endpoints distance. //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); if (floating_edges.empty()) {
Polylines floating_polylines = diff_pl(to_polylines(overhang_area), expand(anchors_area, float(SCALED_EPSILON)));
if (floating_polylines.empty()) {
// consider this area anchored from all sides, pick bridging direction that will likely yield shortest bridges // consider this area anchored from all sides, pick bridging direction that will likely yield shortest bridges
auto [pc1, pc2] = compute_principal_components(overhang_area); 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 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 // 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{}; std::unordered_map<double, Vec2d> directions{};
for (const Line &l : floating_edges) { for (const Line &l : floating_edges) {
Vec2d normal = l.normal().cast<double>().normalized(); 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 {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 // perform operation
ClipperLib_Z::PolyTree loops_trimmed_tree; ClipperLib_Z::PolyTree loops_trimmed_tree;
clipper.Execute(ClipperLib_Z::ctDifference, loops_trimmed_tree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero); 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. // Third, produce the extrusions, sorted by the source loop indices.

View File

@ -22,6 +22,8 @@ set(SLIC3R_SOURCES
AABBTreeLines.hpp AABBTreeLines.hpp
AABBMesh.hpp AABBMesh.hpp
AABBMesh.cpp AABBMesh.cpp
Algorithm/RegionExpansion.cpp
Algorithm/RegionExpansion.hpp
AnyPtr.hpp AnyPtr.hpp
BoundingBox.cpp BoundingBox.cpp
BoundingBox.hpp BoundingBox.hpp
@ -36,6 +38,7 @@ set(SLIC3R_SOURCES
clipper.hpp clipper.hpp
ClipperUtils.cpp ClipperUtils.cpp
ClipperUtils.hpp ClipperUtils.hpp
ClipperZUtils.hpp
Color.cpp Color.cpp
Color.hpp Color.hpp
Config.cpp Config.cpp
@ -79,6 +82,8 @@ set(SLIC3R_SOURCES
Fill/FillBase.hpp Fill/FillBase.hpp
Fill/FillConcentric.cpp Fill/FillConcentric.cpp
Fill/FillConcentric.hpp Fill/FillConcentric.hpp
Fill/FillEnsuring.cpp
Fill/FillEnsuring.hpp
Fill/FillHoneycomb.cpp Fill/FillHoneycomb.cpp
Fill/FillHoneycomb.hpp Fill/FillHoneycomb.hpp
Fill/FillGyroid.cpp Fill/FillGyroid.cpp

View File

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

View File

@ -39,6 +39,9 @@ static constexpr const Slic3r::ClipperLib::JoinType DefaultLineJoinType = Sl
// Miter limit is ignored for jtSquare. // Miter limit is ignored for jtSquare.
static constexpr const double DefaultLineMiterLimit = 0.; 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 { enum class ApplySafetyOffset {
No, No,
Yes 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); } { 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) 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); } { 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. // 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. // 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::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::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::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::Polyline &subject, const Slic3r::Polygons &clip);
Slic3r::Polylines diff_pl(const Slic3r::Polylines &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); 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_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.); 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 { class ConfigOptionSingle : public ConfigOption {
public: public:
T value; T value;
explicit ConfigOptionSingle(T value) : value(value) {} explicit ConfigOptionSingle(T value) : value(std::move(value)) {}
operator T() const { return this->value; } operator T() const { return this->value; }
void set(const ConfigOption *rhs) override void set(const ConfigOption *rhs) override
@ -847,8 +847,8 @@ using ConfigOptionIntsNullable = ConfigOptionIntsTempl<true>;
class ConfigOptionString : public ConfigOptionSingle<std::string> class ConfigOptionString : public ConfigOptionSingle<std::string>
{ {
public: public:
ConfigOptionString() : ConfigOptionSingle<std::string>("") {} ConfigOptionString() : ConfigOptionSingle<std::string>(std::string{}) {}
explicit ConfigOptionString(const std::string &value) : ConfigOptionSingle<std::string>(value) {} explicit ConfigOptionString(std::string value) : ConfigOptionSingle<std::string>(std::move(value)) {}
static ConfigOptionType static_type() { return coString; } static ConfigOptionType static_type() { return coString; }
ConfigOptionType type() const override { return static_type(); } ConfigOptionType type() const override { return static_type(); }

View File

@ -15,10 +15,12 @@
#include "FillRectilinear.hpp" #include "FillRectilinear.hpp"
#include "FillLightning.hpp" #include "FillLightning.hpp"
#include "FillConcentric.hpp" #include "FillConcentric.hpp"
#include "FillEnsuring.hpp"
namespace Slic3r { namespace Slic3r {
static constexpr const float NarrowInfillAreaThresholdMM = 3.f;
struct SurfaceFillParams struct SurfaceFillParams
{ {
// Zero based extruder ID. // 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. // Calculate the actual flow we'll be using for this infill.
params.bridge = is_bridge || Fill::use_bridge_flow(params.pattern); params.bridge = is_bridge || Fill::use_bridge_flow(params.pattern);
params.flow = params.bridge ? 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); layerm.flow(extrusion_role, (surface.thickness == -1) ? layer.height : surface.thickness);
// Calculate flow spacing for infill pattern generation. // 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; return surface_fills;
} }
@ -441,14 +484,28 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
} }
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
size_t first_object_layer_id = this->object()->get_layer(0)->id();
for (SurfaceFill &surface_fill : surface_fills) { 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. // Create the filler object.
std::unique_ptr<Fill> f = std::unique_ptr<Fill>(Fill::new_from_type(surface_fill.params.pattern)); std::unique_ptr<Fill> f = std::unique_ptr<Fill>(Fill::new_from_type(surface_fill.params.pattern));
f->set_bounding_box(bbox); f->set_bounding_box(bbox);
f->layer_id = this->id(); // Layer ID is used for orienting the infill in alternating directions.
// Layer::id() returns layer ID including raft layers, subtract them to make the infill direction independent
// from raft.
f->layer_id = this->id() - first_object_layer_id;
f->z = this->print_z; f->z = this->print_z;
f->angle = surface_fill.params.angle; 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) { if (surface_fill.params.pattern == ipLightning) {
auto *lf = dynamic_cast<FillLightning::Filler*>(f.get()); auto *lf = dynamic_cast<FillLightning::Filler*>(f.get());
@ -456,11 +513,10 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
lf->num_raft_layers = this->object()->slicing_parameters().raft_layers(); lf->num_raft_layers = this->object()->slicing_parameters().raft_layers();
} }
if (perimeter_generator.value == PerimeterGeneratorType::Arachne && surface_fill.params.pattern == ipConcentric) { if (surface_fill.params.pattern == ipEnsuring) {
FillConcentric *fill_concentric = dynamic_cast<FillConcentric *>(f.get()); auto *fill_bounded_rectilinear = dynamic_cast<FillEnsuring *>(f.get());
assert(fill_concentric != nullptr); assert(fill_bounded_rectilinear != nullptr);
fill_concentric->print_config = &this->object()->print()->config(); fill_bounded_rectilinear->print_region_config = &m_regions[surface_fill.region_id]->region().config();
fill_concentric->print_object_config = &this->object()->config();
} }
// calculate flow spacing for infill pattern generation // calculate flow spacing for infill pattern generation
@ -490,7 +546,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
params.anchor_length = surface_fill.params.anchor_length; params.anchor_length = surface_fill.params.anchor_length;
params.anchor_length_max = surface_fill.params.anchor_length_max; params.anchor_length_max = surface_fill.params.anchor_length_max;
params.resolution = resolution; 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; params.layer_height = layerm.layer()->height;
for (ExPolygon &expoly : surface_fill.expolygons) { for (ExPolygon &expoly : surface_fill.expolygons) {
@ -591,6 +647,94 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
#endif #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. // Create ironing extrusions over top surfaces.
void Layer::make_ironing() void Layer::make_ironing()
{ {
@ -698,7 +842,11 @@ void Layer::make_ironing()
FillRectilinear fill; FillRectilinear fill;
FillParams fill_params; FillParams fill_params;
fill.set_bounding_box(this->object()->bounding_box()); fill.set_bounding_box(this->object()->bounding_box());
fill.layer_id = this->id(); // Layer ID is used for orienting the infill in alternating directions.
// Layer::id() returns layer ID including raft layers, subtract them to make the infill direction independent
// from raft.
//FIXME ironing does not take fill angle into account. Shall it? Does it matter?
fill.layer_id = this->id() - this->object()->get_layer(0)->id();
fill.z = this->print_z; fill.z = this->print_z;
fill.overlap = 0; fill.overlap = 0;
fill_params.density = 1.; fill_params.density = 1.;

View File

@ -20,6 +20,7 @@
#include "FillRectilinear.hpp" #include "FillRectilinear.hpp"
#include "FillAdaptive.hpp" #include "FillAdaptive.hpp"
#include "FillLightning.hpp" #include "FillLightning.hpp"
#include "FillEnsuring.hpp"
#include <boost/log/trivial.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 ipSupportCubic: return new FillAdaptive::Filler();
case ipSupportBase: return new FillSupportBase(); case ipSupportBase: return new FillSupportBase();
case ipLightning: return new FillLightning::Filler(); case ipLightning: return new FillLightning::Filler();
case ipEnsuring: return new FillEnsuring();
default: throw Slic3r::InvalidArgument("unknown type"); default: throw Slic3r::InvalidArgument("unknown type");
} }
} }

View File

@ -91,6 +91,10 @@ public:
// Octree builds on mesh for usage in the adaptive cubic infill // Octree builds on mesh for usage in the adaptive cubic infill
FillAdaptive::Octree* adapt_fill_octree = nullptr; 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: public:
virtual ~Fill() {} virtual ~Fill() {}
virtual Fill* clone() const = 0; virtual Fill* clone() const = 0;

View File

@ -97,14 +97,8 @@ void FillConcentric::_fill_surface_single(const FillParams &params,
continue; continue;
ThickPolyline thick_polyline = Arachne::to_thick_polyline(*extrusion); 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()) { if (extrusion->is_closed)
thick_polyline.points.pop_back(); thick_polyline.start_at_index(nearest_point_index(thick_polyline.points, last_pos));
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());
}
thick_polylines_out.emplace_back(std::move(thick_polyline)); thick_polylines_out.emplace_back(std::move(thick_polyline));
last_pos = thick_polylines_out.back().last_point(); last_pos = thick_polylines_out.back().last_point();
} }

View File

@ -26,11 +26,6 @@ protected:
ThickPolylines &thick_polylines_out) override; ThickPolylines &thick_polylines_out) override;
bool no_sort() const override { return true; } bool no_sort() const override { return true; }
const PrintConfig *print_config = nullptr;
const PrintObjectConfig *print_object_config = nullptr;
friend class Layer;
}; };
} // namespace Slic3r } // 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 { namespace Slic3r {
class PrintRegionConfig;
class Surface; class Surface;
class FillRectilinear : public Fill class FillRectilinear : public Fill

View File

@ -1,3 +1,4 @@
#include "Config.hpp"
#include "libslic3r.h" #include "libslic3r.h"
#include "GCode/ExtrusionProcessor.hpp" #include "GCode/ExtrusionProcessor.hpp"
#include "I18N.hpp" #include "I18N.hpp"
@ -24,6 +25,7 @@
#include <cstdlib> #include <cstdlib>
#include <chrono> #include <chrono>
#include <math.h> #include <math.h>
#include <string>
#include <string_view> #include <string_view>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
@ -2867,16 +2869,36 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de
); );
} }
bool variable_speed = false; bool variable_speed_or_fan_speed = false;
std::vector<ProcessedPoint> new_points{}; std::vector<ProcessedPoint> new_points{};
if (this->m_config.enable_dynamic_overhang_speeds && !this->on_first_layer() && path.role().is_perimeter()) { if ((this->m_config.enable_dynamic_overhang_speeds || this->config().enable_dynamic_fan_speeds.get_at(m_writer.extruder()->id())) &&
!this->on_first_layer() && path.role().is_perimeter()) {
std::vector<std::pair<int, ConfigOptionFloatOrPercent>> overhangs_with_speeds = {{100, ConfigOptionFloatOrPercent{speed, false}}};
if (this->m_config.enable_dynamic_overhang_speeds) {
overhangs_with_speeds = {{0, m_config.overhang_speed_0},
{25, m_config.overhang_speed_1},
{50, m_config.overhang_speed_2},
{75, m_config.overhang_speed_3},
{100, ConfigOptionFloatOrPercent{speed, false}}};
}
std::vector<std::pair<int, ConfigOptionInts>> overhang_w_fan_speeds = {{100, ConfigOptionInts{0}}};
if (this->m_config.enable_dynamic_fan_speeds.get_at(m_writer.extruder()->id())) {
overhang_w_fan_speeds = {{0, m_config.overhang_fan_speed_0},
{25, m_config.overhang_fan_speed_1},
{50, m_config.overhang_fan_speed_2},
{75, m_config.overhang_fan_speed_3},
{100, ConfigOptionInts{0}}};
}
double external_perim_reference_speed = std::min(m_config.get_abs_value("external_perimeter_speed"), double external_perim_reference_speed = std::min(m_config.get_abs_value("external_perimeter_speed"),
std::min(EXTRUDER_CONFIG(filament_max_volumetric_speed) / path.mm3_per_mm, std::min(EXTRUDER_CONFIG(filament_max_volumetric_speed) / path.mm3_per_mm,
m_config.max_volumetric_speed.value / path.mm3_per_mm)); m_config.max_volumetric_speed.value / path.mm3_per_mm));
new_points = m_extrusion_quality_estimator.estimate_extrusion_quality(path, m_config.overhang_overlap_levels, new_points = m_extrusion_quality_estimator.estimate_extrusion_quality(path, overhangs_with_speeds, overhang_w_fan_speeds,
m_config.dynamic_overhang_speeds, m_writer.extruder()->id(), external_perim_reference_speed,
external_perim_reference_speed, speed); speed);
variable_speed = std::any_of(new_points.begin(), new_points.end(), [speed](const ProcessedPoint &p) { return p.speed != speed; }); variable_speed_or_fan_speed = std::any_of(new_points.begin(), new_points.end(),
[speed](const ProcessedPoint &p) { return p.speed != speed || p.fan_speed != 0; });
} }
double F = speed * 60; // convert mm/sec to mm/min double F = speed * 60; // convert mm/sec to mm/min
@ -2932,7 +2954,8 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de
std::string cooling_marker_setspeed_comments; std::string cooling_marker_setspeed_comments;
if (m_enable_cooling_markers) { if (m_enable_cooling_markers) {
if (path.role().is_bridge()) if (path.role().is_bridge() &&
(!path.role().is_perimeter() || !this->config().enable_dynamic_fan_speeds.get_at(m_writer.extruder()->id())))
gcode += ";_BRIDGE_FAN_START\n"; gcode += ";_BRIDGE_FAN_START\n";
else else
cooling_marker_setspeed_comments = ";_EXTRUDE_SET_SPEED"; cooling_marker_setspeed_comments = ";_EXTRUDE_SET_SPEED";
@ -2940,7 +2963,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de
cooling_marker_setspeed_comments += ";_EXTERNAL_PERIMETER"; cooling_marker_setspeed_comments += ";_EXTERNAL_PERIMETER";
} }
if (!variable_speed) { if (!variable_speed_or_fan_speed) {
// F is mm per minute. // F is mm per minute.
gcode += m_writer.set_speed(F, "", cooling_marker_setspeed_comments); gcode += m_writer.set_speed(F, "", cooling_marker_setspeed_comments);
double path_length = 0.; double path_length = 0.;
@ -2966,10 +2989,12 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de
marked_comment += description_bridge; marked_comment += description_bridge;
} }
double last_set_speed = new_points[0].speed * 60.0; double last_set_speed = new_points[0].speed * 60.0;
double last_set_fan_speed = new_points[0].fan_speed;
gcode += m_writer.set_speed(last_set_speed, "", cooling_marker_setspeed_comments); gcode += m_writer.set_speed(last_set_speed, "", cooling_marker_setspeed_comments);
gcode += ";_SET_FAN_SPEED" + std::to_string(int(last_set_fan_speed)) + "\n";
Vec2d prev = this->point_to_gcode_quantized(new_points[0].p); Vec2d prev = this->point_to_gcode_quantized(new_points[0].p);
for (size_t i = 1; i < new_points.size(); i++) { for (size_t i = 1; i < new_points.size(); i++) {
const ProcessedPoint& processed_point = new_points[i]; const ProcessedPoint &processed_point = new_points[i];
Vec2d p = this->point_to_gcode_quantized(processed_point.p); Vec2d p = this->point_to_gcode_quantized(processed_point.p);
const double line_length = (p - prev).norm(); const double line_length = (p - prev).norm();
gcode += m_writer.extrude_to_xy(p, e_per_mm * line_length, marked_comment); gcode += m_writer.extrude_to_xy(p, e_per_mm * line_length, marked_comment);
@ -2979,8 +3004,13 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string_view de
gcode += m_writer.set_speed(new_speed, "", cooling_marker_setspeed_comments); gcode += m_writer.set_speed(new_speed, "", cooling_marker_setspeed_comments);
last_set_speed = new_speed; last_set_speed = new_speed;
} }
if (last_set_fan_speed != processed_point.fan_speed) {
last_set_fan_speed = processed_point.fan_speed;
gcode += ";_SET_FAN_SPEED" + std::to_string(int(last_set_fan_speed)) + "\n";
} }
} }
gcode += ";_RESET_FAN_SPEED\n";
}
if (m_enable_cooling_markers) if (m_enable_cooling_markers)
gcode += path.role().is_bridge() ? ";_BRIDGE_FAN_END\n" : ";_EXTRUDE_END\n"; gcode += path.role().is_bridge() ? ";_BRIDGE_FAN_END\n" : ";_EXTRUDE_END\n";

View File

@ -1,5 +1,6 @@
#include "../GCode.hpp" #include "../GCode.hpp"
#include "CoolingBuffer.hpp" #include "CoolingBuffer.hpp"
#include <algorithm>
#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/replace.hpp> #include <boost/algorithm/string/replace.hpp>
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
@ -59,6 +60,9 @@ struct CoolingLine
// Would be TYPE_ADJUSTABLE, but the block of G-code lines has zero extrusion length, thus the block // Would be TYPE_ADJUSTABLE, but the block of G-code lines has zero extrusion length, thus the block
// cannot have its speed adjusted. This should not happen (sic!). // cannot have its speed adjusted. This should not happen (sic!).
TYPE_ADJUSTABLE_EMPTY = 1 << 12, TYPE_ADJUSTABLE_EMPTY = 1 << 12,
// Custom fan speed (introduced for overhang fan speed)
TYPE_SET_FAN_SPEED = 1 << 13,
TYPE_RESET_FAN_SPEED = 1 << 14,
}; };
CoolingLine(unsigned int type, size_t line_start, size_t line_end) : CoolingLine(unsigned int type, size_t line_start, size_t line_end) :
@ -88,6 +92,8 @@ struct CoolingLine
float time; float time;
// Maximum duration of this segment. // Maximum duration of this segment.
float time_max; float time_max;
// Requested fan speed
int fan_speed;
// If marked with the "slowdown" flag, the line has been slowed down. // If marked with the "slowdown" flag, the line has been slowed down.
bool slowdown; bool slowdown;
}; };
@ -372,7 +378,8 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') : size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') :
(*c == extrusion_axis) ? 3 : (*c == 'F') ? 4 : size_t(-1); (*c == extrusion_axis) ? 3 : (*c == 'F') ? 4 : size_t(-1);
if (axis != 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) { if (axis == 4) {
// Convert mm/min to mm/sec. // Convert mm/min to mm/sec.
new_pos[4] /= 60.f; new_pos[4] /= 60.f;
@ -491,13 +498,25 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
bool has_S = pos_S > 0; bool has_S = pos_S > 0;
bool has_P = pos_P > 0; bool has_P = pos_P > 0;
if (has_S || has_P) { 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) if (has_P)
line.time *= 0.001f; line.time *= 0.001f;
} else } else
line.time = 0; line.time = 0;
line.time_max = line.time; line.time_max = line.time;
} else if (boost::contains(sline, ";_SET_FAN_SPEED")) {
auto speed_start = sline.find_last_of('D');
int speed = 0;
for (char num : sline.substr(speed_start + 1)) {
speed = speed * 10 + (num - '0');
} }
line.type = CoolingLine::TYPE_SET_FAN_SPEED;
line.fan_speed = speed;
} else if (boost::contains(sline, ";_RESET_FAN_SPEED")) {
line.type = CoolingLine::TYPE_RESET_FAN_SPEED;
}
if (line.type != 0) if (line.type != 0)
adjustment->lines.emplace_back(std::move(line)); adjustment->lines.emplace_back(std::move(line));
} }
@ -730,10 +749,11 @@ std::string CoolingBuffer::apply_layer_cooldown(
new_gcode.reserve(gcode.size() * 2); new_gcode.reserve(gcode.size() * 2);
bool bridge_fan_control = false; bool bridge_fan_control = false;
int bridge_fan_speed = 0; int bridge_fan_speed = 0;
auto change_extruder_set_fan = [ this, layer_id, layer_time, &new_gcode, &bridge_fan_control, &bridge_fan_speed ]() { auto change_extruder_set_fan = [this, layer_id, layer_time, &new_gcode, &bridge_fan_control, &bridge_fan_speed]() {
#define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_current_extruder) #define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_current_extruder)
int min_fan_speed = EXTRUDER_CONFIG(min_fan_speed); int min_fan_speed = EXTRUDER_CONFIG(min_fan_speed);
int fan_speed_new = EXTRUDER_CONFIG(fan_always_on) ? min_fan_speed : 0; int fan_speed_new = EXTRUDER_CONFIG(fan_always_on) ? min_fan_speed : 0;
std::pair<int, int> custom_fan_speed_limits{fan_speed_new, 100 };
int disable_fan_first_layers = EXTRUDER_CONFIG(disable_fan_first_layers); int disable_fan_first_layers = EXTRUDER_CONFIG(disable_fan_first_layers);
// Is the fan speed ramp enabled? // Is the fan speed ramp enabled?
int full_fan_speed_layer = EXTRUDER_CONFIG(full_fan_speed_layer); int full_fan_speed_layer = EXTRUDER_CONFIG(full_fan_speed_layer);
@ -750,11 +770,13 @@ std::string CoolingBuffer::apply_layer_cooldown(
if (layer_time < slowdown_below_layer_time) { if (layer_time < slowdown_below_layer_time) {
// Layer time very short. Enable the fan to a full throttle. // Layer time very short. Enable the fan to a full throttle.
fan_speed_new = max_fan_speed; fan_speed_new = max_fan_speed;
custom_fan_speed_limits.first = fan_speed_new;
} else if (layer_time < fan_below_layer_time) { } else if (layer_time < fan_below_layer_time) {
// Layer time quite short. Enable the fan proportionally according to the current layer time. // Layer time quite short. Enable the fan proportionally according to the current layer time.
assert(layer_time >= slowdown_below_layer_time); assert(layer_time >= slowdown_below_layer_time);
double t = (layer_time - slowdown_below_layer_time) / (fan_below_layer_time - slowdown_below_layer_time); double t = (layer_time - slowdown_below_layer_time) / (fan_below_layer_time - slowdown_below_layer_time);
fan_speed_new = int(floor(t * min_fan_speed + (1. - t) * max_fan_speed) + 0.5); fan_speed_new = int(floor(t * min_fan_speed + (1. - t) * max_fan_speed) + 0.5);
custom_fan_speed_limits.first = fan_speed_new;
} }
} }
bridge_fan_speed = EXTRUDER_CONFIG(bridge_fan_speed); bridge_fan_speed = EXTRUDER_CONFIG(bridge_fan_speed);
@ -763,6 +785,7 @@ std::string CoolingBuffer::apply_layer_cooldown(
float factor = float(int(layer_id + 1) - disable_fan_first_layers) / float(full_fan_speed_layer - disable_fan_first_layers); float factor = float(int(layer_id + 1) - disable_fan_first_layers) / float(full_fan_speed_layer - disable_fan_first_layers);
fan_speed_new = std::clamp(int(float(fan_speed_new) * factor + 0.5f), 0, 255); fan_speed_new = std::clamp(int(float(fan_speed_new) * factor + 0.5f), 0, 255);
bridge_fan_speed = std::clamp(int(float(bridge_fan_speed) * factor + 0.5f), 0, 255); bridge_fan_speed = std::clamp(int(float(bridge_fan_speed) * factor + 0.5f), 0, 255);
custom_fan_speed_limits.second = fan_speed_new;
} }
#undef EXTRUDER_CONFIG #undef EXTRUDER_CONFIG
bridge_fan_control = bridge_fan_speed > fan_speed_new; bridge_fan_control = bridge_fan_speed > fan_speed_new;
@ -775,11 +798,12 @@ std::string CoolingBuffer::apply_layer_cooldown(
m_fan_speed = fan_speed_new; m_fan_speed = fan_speed_new;
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, m_fan_speed); new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, m_fan_speed);
} }
return custom_fan_speed_limits;
}; };
const char *pos = gcode.c_str(); const char *pos = gcode.c_str();
int current_feedrate = 0; int current_feedrate = 0;
change_extruder_set_fan(); std::pair<int,int> fan_speed_limits = change_extruder_set_fan();
for (const CoolingLine *line : lines) { for (const CoolingLine *line : lines) {
const char *line_start = gcode.c_str() + line->line_start; const char *line_start = gcode.c_str() + line->line_start;
const char *line_end = gcode.c_str() + line->line_end; const char *line_end = gcode.c_str() + line->line_end;
@ -790,9 +814,17 @@ std::string CoolingBuffer::apply_layer_cooldown(
auto res = std::from_chars(line_start + m_toolchange_prefix.size(), line_end, new_extruder); auto res = std::from_chars(line_start + m_toolchange_prefix.size(), line_end, new_extruder);
if (res.ec != std::errc::invalid_argument && new_extruder != m_current_extruder) { if (res.ec != std::errc::invalid_argument && new_extruder != m_current_extruder) {
m_current_extruder = new_extruder; m_current_extruder = new_extruder;
change_extruder_set_fan(); fan_speed_limits = change_extruder_set_fan();
} }
new_gcode.append(line_start, line_end - line_start); new_gcode.append(line_start, line_end - line_start);
} else if (line->type & CoolingLine::TYPE_SET_FAN_SPEED) {
int new_speed = std::clamp(line->fan_speed, fan_speed_limits.first, fan_speed_limits.second);
if (m_fan_speed != new_speed) {
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, new_speed);
m_fan_speed = new_speed;
}
} else if (line->type & CoolingLine::TYPE_RESET_FAN_SPEED){
fan_speed_limits = change_extruder_set_fan();
} else if (line->type & CoolingLine::TYPE_BRIDGE_FAN_START) { } else if (line->type & CoolingLine::TYPE_BRIDGE_FAN_START) {
if (bridge_fan_control) if (bridge_fan_control)
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, bridge_fan_speed); new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, bridge_fan_speed);
@ -816,7 +848,8 @@ std::string CoolingBuffer::apply_layer_cooldown(
if (line->slowdown) if (line->slowdown)
new_feedrate = int(floor(60. * line->feedrate + 0.5)); new_feedrate = int(floor(60. * line->feedrate + 0.5));
else 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) { if (new_feedrate == current_feedrate) {
// No need to change the F value. // 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.) if ((line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_ADJUSTABLE_EMPTY | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) || line->length == 0.)

View File

@ -17,6 +17,7 @@
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <cstddef> #include <cstddef>
#include <iterator>
#include <limits> #include <limits>
#include <numeric> #include <numeric>
#include <unordered_map> #include <unordered_map>
@ -238,6 +239,7 @@ struct ProcessedPoint
{ {
Point p; Point p;
float speed = 1.0f; float speed = 1.0f;
int fan_speed = 0;
}; };
class ExtrusionQualityEstimator class ExtrusionQualityEstimator
@ -258,33 +260,26 @@ public:
} }
std::vector<ProcessedPoint> estimate_extrusion_quality(const ExtrusionPath &path, std::vector<ProcessedPoint> estimate_extrusion_quality(const ExtrusionPath &path,
const ConfigOptionPercents &overlaps, const std::vector<std::pair<int, ConfigOptionFloatOrPercent>> overhangs_w_speeds,
const ConfigOptionFloatsOrPercents &speeds, const std::vector<std::pair<int, ConfigOptionInts>> overhangs_w_fan_speeds,
size_t extruder_id,
float ext_perimeter_speed, float ext_perimeter_speed,
float original_speed) float original_speed)
{ {
size_t speed_sections_count = std::min(overlaps.values.size(), speeds.values.size());
float speed_base = ext_perimeter_speed > 0 ? ext_perimeter_speed : original_speed; float speed_base = ext_perimeter_speed > 0 ? ext_perimeter_speed : original_speed;
std::vector<std::pair<float, float>> speed_sections; std::map<float, float> speed_sections;
for (size_t i = 0; i < speed_sections_count; i++) { for (size_t i = 0; i < overhangs_w_speeds.size(); i++) {
float distance = path.width * (1.0 - (overlaps.get_at(i) / 100.0)); float distance = path.width * (1.0 - (overhangs_w_speeds[i].first / 100.0));
float speed = speeds.get_at(i).percent ? (speed_base * speeds.get_at(i).value / 100.0) : speeds.get_at(i).value; float speed = overhangs_w_speeds[i].second.percent ? (speed_base * overhangs_w_speeds[i].second.value / 100.0) :
speed_sections.push_back({distance, speed}); overhangs_w_speeds[i].second.value;
speed_sections[distance] = speed;
} }
std::sort(speed_sections.begin(), speed_sections.end(),
[](const std::pair<float, float> &a, const std::pair<float, float> &b) {
if (a.first == b.first) {
return a.second > b.second;
}
return a.first < b.first; });
std::pair<float, float> last_section{INFINITY, 0}; std::map<float, float> fan_speed_sections;
for (auto &section : speed_sections) { for (size_t i = 0; i < overhangs_w_fan_speeds.size(); i++) {
if (section.first == last_section.first) { float distance = path.width * (1.0 - (overhangs_w_fan_speeds[i].first / 100.0));
section.second = last_section.second; float fan_speed = overhangs_w_fan_speeds[i].second.get_at(extruder_id);
} else { fan_speed_sections[distance] = fan_speed;
last_section = section;
}
} }
std::vector<ExtendedPoint> extended_points = std::vector<ExtendedPoint> extended_points =
@ -296,28 +291,26 @@ public:
const ExtendedPoint &curr = extended_points[i]; const ExtendedPoint &curr = extended_points[i];
const ExtendedPoint &next = extended_points[i + 1 < extended_points.size() ? i + 1 : i]; const ExtendedPoint &next = extended_points[i + 1 < extended_points.size() ? i + 1 : i];
auto calculate_speed = [&speed_sections, &original_speed](float distance) { auto interpolate_speed = [](const std::map<float, float> &values, float distance) {
float final_speed; auto upper_dist = values.lower_bound(distance);
if (distance <= speed_sections.front().first) { if (upper_dist == values.end()) {
final_speed = original_speed; return values.rbegin()->second;
} else if (distance >= speed_sections.back().first) {
final_speed = speed_sections.back().second;
} else {
size_t section_idx = 0;
while (distance > speed_sections[section_idx + 1].first) {
section_idx++;
} }
float t = (distance - speed_sections[section_idx].first) / if (upper_dist == values.begin()) {
(speed_sections[section_idx + 1].first - speed_sections[section_idx].first); return upper_dist->second;
t = std::clamp(t, 0.0f, 1.0f);
final_speed = (1.0f - t) * speed_sections[section_idx].second + t * speed_sections[section_idx + 1].second;
} }
return final_speed;
auto lower_dist = std::prev(upper_dist);
float t = (distance - lower_dist->first) / (upper_dist->first - lower_dist->first);
return (1.0f - t) * lower_dist->second + t * upper_dist->second;
}; };
float extrusion_speed = std::min(calculate_speed(curr.distance), calculate_speed(next.distance)); float extrusion_speed = std::min(interpolate_speed(speed_sections, curr.distance),
interpolate_speed(speed_sections, next.distance));
float fan_speed = std::min(interpolate_speed(fan_speed_sections, curr.distance),
interpolate_speed(fan_speed_sections, next.distance));
processed_points.push_back({scaled(curr.position), extrusion_speed}); processed_points.push_back({scaled(curr.position), extrusion_speed, int(fan_speed)});
} }
return processed_points; return processed_points;
} }

View File

@ -19,20 +19,7 @@ bool RetractWhenCrossingPerimeters::travel_inside_internal_regions(const Layer &
if (surface.is_internal()) if (surface.is_internal())
m_internal_islands.emplace_back(&surface.expolygon); m_internal_islands.emplace_back(&surface.expolygon);
// Calculate bounding boxes of internal slices. // Calculate bounding boxes of internal slices.
class BBoxWrapper { std::vector<AABBTreeIndirect::BoundingBoxWrapper> bboxes;
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;
bboxes.reserve(m_internal_islands.size()); bboxes.reserve(m_internal_islands.size());
for (size_t i = 0; i < m_internal_islands.size(); ++ i) for (size_t i = 0; i < m_internal_islands.size(); ++ i)
bboxes.emplace_back(i, get_extents(*m_internal_islands[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) // 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 // note: this keeps twins, so it inserts twice the number of the valid edges
m_edge_data.assign(m_vd.edges().size() / 2, EdgeData{}); m_edge_data.assign(m_vd.edges().size() / 2, EdgeData{});

View File

@ -1,5 +1,5 @@
#include "Layer.hpp" #include "Layer.hpp"
#include <clipper/clipper_z.hpp> #include "ClipperZUtils.hpp"
#include "ClipperUtils.hpp" #include "ClipperUtils.hpp"
#include "Print.hpp" #include "Print.hpp"
#include "Fill/Fill.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()); coord_t paths_end = paths_above_offset + coord_t(above.lslices.size());
#endif // NDEBUG #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::Clipper clipper;
ClipperLib_Z::PolyTree result; ClipperLib_Z::PolyTree result;
clipper.ZFillFunction( ClipperZUtils::ClipperZIntersectionVisitor::Intersections intersections;
[&zfill](const ClipperLib_Z::IntPoint &e1bot, const ClipperLib_Z::IntPoint &e1top, ClipperZUtils::ClipperZIntersectionVisitor visitor(intersections);
const ClipperLib_Z::IntPoint &e2bot, const ClipperLib_Z::IntPoint &e2top, ClipperLib_Z::IntPoint &pt) clipper.ZFillFunction(visitor.clipper_callback());
{ return zfill(e1bot, e1top, e2bot, e2top, pt); });
clipper.AddPaths(paths_below, ClipperLib_Z::ptSubject, true); clipper.AddPaths(paths_below, ClipperLib_Z::ptSubject, true);
clipper.AddPaths(paths_above, ClipperLib_Z::ptClip, true); clipper.AddPaths(paths_above, ClipperLib_Z::ptClip, true);
clipper.Execute(ClipperLib_Z::ctIntersection, result, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero); 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 #ifndef NDEBUG
, paths_end , paths_end
#endif // NDEBUG #endif // NDEBUG

View File

@ -134,7 +134,7 @@ public:
Flow flow(FlowRole role) const; Flow flow(FlowRole role) const;
Flow flow(FlowRole role, double layer_height) 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 slices_to_fill_surfaces_clipped();
void prepare_fill_surfaces(); void prepare_fill_surfaces();
@ -369,6 +369,7 @@ public:
// Phony version of make_fills() without parameters for Perl integration only. // Phony version of make_fills() without parameters for Perl integration only.
void make_fills() { this->make_fills(nullptr, nullptr, nullptr); } 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); 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 make_ironing();
void export_region_slices_to_svg(const char *path) const; void export_region_slices_to_svg(const char *path) const;

View File

@ -8,6 +8,7 @@
#include "Surface.hpp" #include "Surface.hpp"
#include "BoundingBox.hpp" #include "BoundingBox.hpp"
#include "SVG.hpp" #include "SVG.hpp"
#include "Algorithm/RegionExpansion.hpp"
#include <string> #include <string>
#include <map> #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); 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 PrintRegion &region = this->region();
const PrintRegionConfig &region_config = region.config(); const PrintRegionConfig &region_config = region.config();
const PrintObject &print_object = *this->layer()->object(); 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. // 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. // 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. // 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, 3.
//#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 1.5 //#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 1.5
#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0. #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) 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 bool has_infill = this->region().config().fill_density.value > 0.;
// const float margin = scaled<float>(0.1); // float(scale_(EXTERNAL_INFILL_MARGIN));
const float margin = float(scale_(EXTERNAL_INFILL_MARGIN)); const float margin = float(scale_(EXTERNAL_INFILL_MARGIN));
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING #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 */ #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
// 1) Collect bottom and bridge surfaces, each of them grown by a fixed 3mm offset // 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; Surfaces internal;
// Areas, where an infill of various types (top, bottom, bottom bride, sparse, void) could be placed. // 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 fill_boundaries = to_polygons(this->fill_expolygons());
Polygons lower_layer_covered_tmp;
// Collect top surfaces and internal surfaces. // 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. // Collect fill_boundaries: If we're slicing with no infill, we can't extend external surfaces over non-existent infill.
@ -174,6 +442,8 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
// Voids are sparse infills if infill rate is zero. // Voids are sparse infills if infill rate is zero.
Polygons voids; Polygons voids;
for (const Surface &surface : this->fill_surfaces()) { for (const Surface &surface : this->fill_surfaces()) {
assert(! surface.empty());
if (! surface.empty()) {
if (surface.is_top()) { if (surface.is_top()) {
// Collect the top surfaces, inflate them and trim them by the bottom surfaces. // Collect the top surfaces, inflate them and trim them by the bottom surfaces.
// This gives the priority to bottom surfaces. // This gives the priority to bottom surfaces.
@ -182,24 +452,31 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
// Grown by 3mm. // Grown by 3mm.
surfaces_append(bottom, offset_ex(surface.expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface); surfaces_append(bottom, offset_ex(surface.expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface);
} else if (surface.surface_type == stBottomBridge) { } else if (surface.surface_type == stBottomBridge) {
if (! surface.empty())
bridges.emplace_back(surface); bridges.emplace_back(surface);
} } else {
if (surface.is_internal()) { assert(surface.is_internal());
assert(surface.surface_type == stInternal || surface.surface_type == stInternalSolid); assert(surface.surface_type == stInternal || surface.surface_type == stInternalSolid);
if (! has_infill && lower_layer != nullptr) if (! has_infill && lower_layer != nullptr)
polygons_append(voids, surface.expolygon); polygons_append(voids, surface.expolygon);
internal.emplace_back(std::move(surface)); 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. // Remove voids from fill_boundaries, that are not supported by the layer below.
Polygons lower_layer_covered_tmp;
if (lower_layer_covered == nullptr) { if (lower_layer_covered == nullptr) {
lower_layer_covered = &lower_layer_covered_tmp; lower_layer_covered = &lower_layer_covered_tmp;
lower_layer_covered_tmp = to_polygons(lower_layer->lslices); lower_layer_covered_tmp = to_polygons(lower_layer->lslices);
} }
if (! lower_layer_covered->empty()) 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); voids = diff(voids, *lower_layer_covered);
if (! voids.empty())
fill_boundaries = diff(fill_boundaries, voids); 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 #ifdef SLIC3R_DEBUG_SLICE_PROCESSING
{ {
static int iRun = 0; 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(fill_boundaries_ex);
svg.draw_outline(fill_boundaries_ex, "black", "blue", scale_(0.05)); svg.draw_outline(fill_boundaries_ex, "black", "blue", scale_(0.05));
svg.Close(); svg.Close();
} }
// export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-initial");
// export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-initial");
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
{ {
@ -253,7 +529,8 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
if (idx_island == -1) { if (idx_island == -1) {
BOOST_LOG_TRIVIAL(trace) << "Bridge did not fall into the source region!"; BOOST_LOG_TRIVIAL(trace) << "Bridge did not fall into the source region!";
} else { } 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]); polys = intersection(polys, fill_boundaries_ex[idx_island]);
} }
bridge_bboxes.push_back(get_extents(polys)); 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; 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. // Intersect the grown surfaces with the actual fill boundaries.
Polygons bottom_polygons = to_polygons(bottom); 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) { for (size_t i = 0; i < top.size(); ++ i) {
Surface &s1 = top[i]; Surface &s1 = top[i];
if (s1.empty()) 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); m_fill_surfaces.surfaces = std::move(new_surfaces);
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING #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 /* SLIC3R_DEBUG_SLICE_PROCESSING */
} }
#endif
void LayerRegion::prepare_fill_surfaces() 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.AddPath(subject, ClipperLib_Z::ptSubject, false);
clipper.AddPaths(clip, ClipperLib_Z::ptClip, true); clipper.AddPaths(clip, ClipperLib_Z::ptClip, true);
ClipperLib_Z::PolyTree clipped_polytree;
ClipperLib_Z::Paths clipped_paths; ClipperLib_Z::Paths clipped_paths;
{
ClipperLib_Z::PolyTree clipped_polytree;
clipper.Execute(clipType, clipped_polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero); clipper.Execute(clipType, clipped_polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
ClipperLib_Z::PolyTreeToPaths(clipped_polytree, clipped_paths); ClipperLib_Z::PolyTreeToPaths(std::move(clipped_polytree), clipped_paths);
}
// Clipped path could contain vertices from the clip with a Z coordinate equal to zero. // 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. // For those vertices, we must assign value based on the subject.

View File

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

View File

@ -5,6 +5,7 @@
#include <map> #include <map>
#include <random> #include <random>
#include <string> #include <string>
#include <string_view>
#include <vector> #include <vector>
#include "PrintConfig.hpp" #include "PrintConfig.hpp"
@ -38,6 +39,8 @@ public:
// Add new ConfigOption values to m_config. // 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, 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, 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, unsigned int value) { this->set(key, int(value)); }
void set(const std::string &key, bool value) { this->set(key, new ConfigOptionBool(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; return duplicits;
} }
template<bool IncludeBoundary>
BoundingBox get_extents(const Points &pts) 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 get_extents(const std::vector<Points> &pts)
{ {
BoundingBox bbox; BoundingBox bbox;
for (const Points &p : pts) for (const Points &p : pts)
bbox.merge(get_extents(p)); bbox.merge(get_extents<IncludeBoundary>(p));
return bbox; 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) 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>(); return ((1. - t) * a.cast<double>() + t * b.cast<double>()).cast<coord_t>();
} }
// if IncludeBoundary, then a bounding box is defined even for a single point.
// otherwise a bounding box is only defined if it has a positive area.
template<bool IncludeBoundary = false>
BoundingBox get_extents(const Points &pts); BoundingBox get_extents(const Points &pts);
extern template BoundingBox get_extents<false>(const Points &pts);
extern template BoundingBox get_extents<true>(const Points &pts);
// if IncludeBoundary, then a bounding box is defined even for a single point.
// otherwise a bounding box is only defined if it has a positive area.
template<bool IncludeBoundary = false>
BoundingBox get_extents(const std::vector<Points> &pts); BoundingBox 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); BoundingBoxf get_extents(const std::vector<Vec2d> &pts);
int nearest_point_index(const Points &points, const Point &pt); 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); 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 Polyline3::length() const
{ {
double l = 0; 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); const Point& leftmost_point(const Polylines &polylines);
bool remove_degenerate(Polylines &polylines); bool remove_degenerate(Polylines &polylines);
@ -166,6 +185,7 @@ struct ThickPolyline {
const Point& first_point() const { return this->points.front(); } const Point& first_point() const { return this->points.front(); }
const Point& last_point() const { return this->points.back(); } 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; } bool is_valid() const { return this->points.size() >= 2; }
double length() const { return Slic3r::length(this->points); } double length() const { return Slic3r::length(this->points); }
@ -179,6 +199,11 @@ struct ThickPolyline {
void clip_end(double distance); 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; Points points;
std::vector<coordf_t> width; std::vector<coordf_t> width;
std::pair<bool,bool> endpoints { false, false }; std::pair<bool,bool> endpoints { false, false };

View File

@ -440,7 +440,7 @@ static std::vector<std::string> s_Preset_print_options {
"fuzzy_skin", "fuzzy_skin_thickness", "fuzzy_skin_point_dist", "fuzzy_skin", "fuzzy_skin_thickness", "fuzzy_skin_point_dist",
"max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative", "max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative",
"perimeter_speed", "small_perimeter_speed", "external_perimeter_speed", "infill_speed", "solid_infill_speed", "perimeter_speed", "small_perimeter_speed", "external_perimeter_speed", "infill_speed", "solid_infill_speed",
"enable_dynamic_overhang_speeds", "dynamic_overhang_speeds", "overhang_overlap_levels", "enable_dynamic_overhang_speeds", "overhang_speed_0", "overhang_speed_1", "overhang_speed_2", "overhang_speed_3",
"top_solid_infill_speed", "support_material_speed", "support_material_xy_spacing", "support_material_interface_speed", "top_solid_infill_speed", "support_material_speed", "support_material_xy_spacing", "support_material_interface_speed",
"bridge_speed", "gap_fill_speed", "gap_fill_enabled", "travel_speed", "travel_speed_z", "first_layer_speed", "first_layer_speed_over_raft", "perimeter_acceleration", "infill_acceleration", "bridge_speed", "gap_fill_speed", "gap_fill_enabled", "travel_speed", "travel_speed_z", "first_layer_speed", "first_layer_speed_over_raft", "perimeter_acceleration", "infill_acceleration",
"external_perimeter_acceleration", "top_solid_infill_acceleration", "solid_infill_acceleration", "external_perimeter_acceleration", "top_solid_infill_acceleration", "solid_infill_acceleration",
@ -473,7 +473,8 @@ static std::vector<std::string> s_Preset_filament_options {
"filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters", "filament_minimal_purge_on_wipe_tower", "filament_cooling_initial_speed", "filament_cooling_final_speed", "filament_ramming_parameters", "filament_minimal_purge_on_wipe_tower",
"temperature", "idle_temperature", "first_layer_temperature", "bed_temperature", "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed", "temperature", "idle_temperature", "first_layer_temperature", "bed_temperature", "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed",
"max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers", "full_fan_speed_layer", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed", "max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers", "full_fan_speed_layer", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed",
"start_filament_gcode", "end_filament_gcode", "start_filament_gcode", "end_filament_gcode", "enable_dynamic_fan_speeds",
"overhang_fan_speed_0", "overhang_fan_speed_1", "overhang_fan_speed_2", "overhang_fan_speed_3",
// Retract overrides // Retract overrides
"filament_retract_length", "filament_retract_lift", "filament_retract_lift_above", "filament_retract_lift_below", "filament_retract_speed", "filament_deretract_speed", "filament_retract_restart_extra", "filament_retract_before_travel", "filament_retract_length", "filament_retract_lift", "filament_retract_lift_above", "filament_retract_lift_below", "filament_retract_speed", "filament_deretract_speed", "filament_retract_restart_extra", "filament_retract_before_travel",
"filament_retract_layer_change", "filament_wipe", "filament_retract_before_wipe", "filament_retract_layer_change", "filament_wipe", "filament_retract_before_wipe",
@ -785,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. // 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); 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); cfg.apply_only(combined_config, keys, true);
std::string &inherits = Preset::inherits(cfg); std::string &inherits = Preset::inherits(cfg);
if (select == LoadAndSelect::Never) { if (select == LoadAndSelect::Never) {

View File

@ -66,6 +66,11 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
"between_objects_gcode", "between_objects_gcode",
"bridge_acceleration", "bridge_acceleration",
"bridge_fan_speed", "bridge_fan_speed",
"enable_dynamic_fan_speeds",
"overhang_fan_speed_0",
"overhang_fan_speed_1",
"overhang_fan_speed_2",
"overhang_fan_speed_3",
"colorprint_heights", "colorprint_heights",
"cooling", "cooling",
"default_acceleration", "default_acceleration",
@ -514,7 +519,7 @@ std::string Print::validate(std::string* warning) const
const PrintObject &print_object = *m_objects[print_object_idx]; const PrintObject &print_object = *m_objects[print_object_idx];
//FIXME It is quite expensive to generate object layers just to get the print height! //FIXME It is quite expensive to generate object layers just to get the print height!
if (auto layers = generate_object_layers(print_object.slicing_parameters(), layer_height_profile(print_object_idx)); if (auto layers = generate_object_layers(print_object.slicing_parameters(), layer_height_profile(print_object_idx));
! layers.empty() && layers.back() > this->config().max_print_height) { ! layers.empty() && layers.back() > this->config().max_print_height + EPSILON) {
return L("The print is taller than the maximum allowed height. You might want to reduce the size of your model" return L("The print is taller than the maximum allowed height. You might want to reduce the size of your model"
" or change current print settings and retry."); " or change current print settings and retry.");
} }

View File

@ -535,35 +535,96 @@ void PrintConfigDef::init_fff_params()
def->label = L("Enable dynamic overhang speeds"); def->label = L("Enable dynamic overhang speeds");
def->category = L("Speed"); def->category = L("Speed");
def->tooltip = L("This setting enables dynamic speed control on overhangs."); def->tooltip = L("This setting enables dynamic speed control on overhangs.");
def->mode = comAdvanced; def->mode = comExpert;
def->set_default_value(new ConfigOptionBool(false)); def->set_default_value(new ConfigOptionBool(false));
def = this->add("overhang_overlap_levels", coPercents); auto overhang_speed_setting_description = L("Overhang size is expressed as a percentage of overlap of the extrusion with the previous layer: "
def->full_label = L("Overhang overlap levels"); "100% would be full overlap (no overhang), while 0% represents full overhang (floating extrusion, bridge). "
"Speeds for overhang sizes in between are calculated via linear interpolation. "
"If set as percentage, the speed is calculated over the external perimeter speed.");
def = this->add("overhang_speed_0", coFloatOrPercent);
def->label = L("speed for 0\% overlap (bridge)");
def->category = L("Speed"); def->category = L("Speed");
def->tooltip = L("Controls overhang levels, expressed as a percentage of overlap of the extrusion with the previous layer - " def->tooltip = overhang_speed_setting_description;
"100% represents full overlap - no overhang is present, while 0% represents full overhang (floating extrusion). " def->sidetext = L("mm/s or %");
"Each overhang level then corresponds with the overhang speed below. Speeds for overhang levels in between are " def->min = 0;
"calculated via linear interpolation." def->mode = comExpert;
"If you set multiple different speeds for the same overhang level, only the largest speed is used. " def->set_default_value(new ConfigOptionFloatOrPercent(15, false));
);
def = this->add("overhang_speed_1", coFloatOrPercent);
def->label = L("speed for 25\% overlap");
def->category = L("Speed");
def->tooltip = overhang_speed_setting_description;
def->sidetext = L("mm/s or %");
def->min = 0;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloatOrPercent(15, false));
def = this->add("overhang_speed_2", coFloatOrPercent);
def->label = L("speed for 50\% overlap");
def->category = L("Speed");
def->tooltip = overhang_speed_setting_description;
def->sidetext = L("mm/s or %");
def->min = 0;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloatOrPercent(20, false));
def = this->add("overhang_speed_3", coFloatOrPercent);
def->label = L("speed for 75\% overlap");
def->category = L("Speed");
def->tooltip = overhang_speed_setting_description;
def->sidetext = L("mm/s or %");
def->min = 0;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloatOrPercent(25, false));
def = this->add("enable_dynamic_fan_speeds", coBools);
def->label = L("Enable dynamic fan speeds");
def->tooltip = L("This setting enables dynamic fan speed control on overhangs.");
def->mode = comExpert;
def->set_default_value(new ConfigOptionBools{false});
auto fan_speed_setting_description = L(
"Overhang size is expressed as a percentage of overlap of the extrusion with the previous layer: "
"100% would be full overlap (no overhang), while 0% represents full overhang (floating extrusion, bridge). "
"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->tooltip = fan_speed_setting_description;
def->sidetext = L("%"); def->sidetext = L("%");
def->min = 0; def->min = 0;
def->max = 100; def->max = 100;
def->mode = comAdvanced; def->mode = comExpert;
def->set_default_value(new ConfigOptionPercents({60, 40, 20, 0})); def->set_default_value(new ConfigOptionInts{0});
def = this->add("dynamic_overhang_speeds", coFloatsOrPercents); def = this->add("overhang_fan_speed_1", coInts);
def->full_label = L("Dynamic speed on overhangs"); def->label = L("speed for 25% overlap");
def->category = L("Speed"); def->tooltip = fan_speed_setting_description;
def->tooltip = L("This setting controls the speed on the overhang with the overlap value set above. " def->sidetext = L("%");
"The speed of the extrusion is calculated as a linear interpolation of the speeds for higher and lower overlap. "
"If set as percentage, the speed is calculated over the external perimeter speed."
);
def->sidetext = L("mm/s or %");
def->min = 0; def->min = 0;
def->mode = comAdvanced; def->max = 100;
def->set_default_value(new ConfigOptionFloatsOrPercents({{25, false}, {20, false}, {15, false}, {15, false}})); def->mode = comExpert;
def->set_default_value(new ConfigOptionInts{0});
def = this->add("overhang_fan_speed_2", coInts);
def->label = L("speed for 50% overlap");
def->tooltip = fan_speed_setting_description;
def->sidetext = L("%");
def->min = 0;
def->max = 100;
def->mode = comExpert;
def->set_default_value(new ConfigOptionInts{0});
def = this->add("overhang_fan_speed_3", coInts);
def->label = L("speed for 75% overlap");
def->tooltip = fan_speed_setting_description;
def->sidetext = L("%");
def->min = 0;
def->max = 100;
def->mode = comExpert;
def->set_default_value(new ConfigOptionInts{0});
def = this->add("brim_width", coFloat); def = this->add("brim_width", coFloat);
def->label = L("Brim width"); def->label = L("Brim width");

View File

@ -60,6 +60,7 @@ enum InfillPattern : int {
ipRectilinear, ipMonotonic, ipMonotonicLines, ipAlignedRectilinear, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb, ipRectilinear, ipMonotonic, ipMonotonicLines, ipAlignedRectilinear, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb,
ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipAdaptiveCubic, ipSupportCubic, ipSupportBase, ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipAdaptiveCubic, ipSupportCubic, ipSupportBase,
ipLightning, ipLightning,
ipEnsuring,
ipCount, 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(ForwardCompatibilitySubstitutionRule)
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PerimeterGeneratorType) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PerimeterGeneratorType)
#undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS #undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS
// Defines each and every confiuration option of Slic3r, including the properties of the GUI dialogs. // Defines each and every confiuration option of Slic3r, including the properties of the GUI dialogs.
@ -573,8 +575,10 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionFloatOrPercent, external_perimeter_extrusion_width)) ((ConfigOptionFloatOrPercent, external_perimeter_extrusion_width))
((ConfigOptionFloatOrPercent, external_perimeter_speed)) ((ConfigOptionFloatOrPercent, external_perimeter_speed))
((ConfigOptionBool, enable_dynamic_overhang_speeds)) ((ConfigOptionBool, enable_dynamic_overhang_speeds))
((ConfigOptionPercents, overhang_overlap_levels)) ((ConfigOptionFloatOrPercent, overhang_speed_0))
((ConfigOptionFloatsOrPercents, dynamic_overhang_speeds)) ((ConfigOptionFloatOrPercent, overhang_speed_1))
((ConfigOptionFloatOrPercent, overhang_speed_2))
((ConfigOptionFloatOrPercent, overhang_speed_3))
((ConfigOptionBool, external_perimeters_first)) ((ConfigOptionBool, external_perimeters_first))
((ConfigOptionBool, extra_perimeters)) ((ConfigOptionBool, extra_perimeters))
((ConfigOptionBool, extra_perimeters_on_overhangs)) ((ConfigOptionBool, extra_perimeters_on_overhangs))
@ -747,6 +751,11 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
((ConfigOptionInts, bed_temperature)) ((ConfigOptionInts, bed_temperature))
((ConfigOptionFloat, bridge_acceleration)) ((ConfigOptionFloat, bridge_acceleration))
((ConfigOptionInts, bridge_fan_speed)) ((ConfigOptionInts, bridge_fan_speed))
((ConfigOptionBools, enable_dynamic_fan_speeds))
((ConfigOptionInts, overhang_fan_speed_0))
((ConfigOptionInts, overhang_fan_speed_1))
((ConfigOptionInts, overhang_fan_speed_2))
((ConfigOptionInts, overhang_fan_speed_3))
((ConfigOptionBool, complete_objects)) ((ConfigOptionBool, complete_objects))
((ConfigOptionFloats, colorprint_heights)) ((ConfigOptionFloats, colorprint_heights))
((ConfigOptionBools, cooling)) ((ConfigOptionBools, cooling))

File diff suppressed because it is too large Load Diff

View File

@ -376,8 +376,6 @@ SupportParameters::SupportParameters(const PrintObject &object)
} }
this->base_angle = Geometry::deg2rad(float(object_config.support_material_angle.value));
this->interface_angle = Geometry::deg2rad(float(object_config.support_material_angle.value + 90.));
double interface_spacing = object_config.support_material_interface_spacing.value + this->support_material_interface_flow.spacing(); double interface_spacing = object_config.support_material_interface_spacing.value + this->support_material_interface_flow.spacing();
this->interface_density = std::min(1., this->support_material_interface_flow.spacing() / interface_spacing); this->interface_density = std::min(1., this->support_material_interface_flow.spacing() / interface_spacing);
double raft_interface_spacing = object_config.support_material_interface_spacing.value + this->raft_interface_flow.spacing(); double raft_interface_spacing = object_config.support_material_interface_spacing.value + this->raft_interface_flow.spacing();
@ -401,6 +399,39 @@ SupportParameters::SupportParameters(const PrintObject &object)
object_config.support_material_interface_pattern == smipConcentric ? object_config.support_material_interface_pattern == smipConcentric ?
ipConcentric : ipConcentric :
(this->interface_density > 0.95 ? ipRectilinear : ipSupportBase); (this->interface_density > 0.95 ? ipRectilinear : ipSupportBase);
this->base_angle = Geometry::deg2rad(float(object_config.support_material_angle.value));
this->interface_angle = Geometry::deg2rad(float(object_config.support_material_angle.value + 90.));
this->raft_angle_1st_layer = 0.f;
this->raft_angle_base = 0.f;
this->raft_angle_interface = 0.f;
if (slicing_params.base_raft_layers > 1) {
assert(slicing_params.raft_layers() >= 4);
// There are all raft layer types (1st layer, base, interface & contact layers) available.
this->raft_angle_1st_layer = this->interface_angle;
this->raft_angle_base = this->base_angle;
this->raft_angle_interface = this->interface_angle;
if ((slicing_params.interface_raft_layers & 1) == 0)
// Allign the 1st raft interface layer so that the object 1st layer is hatched perpendicularly to the raft contact interface.
this->raft_angle_interface += float(0.5 * M_PI);
} else if (slicing_params.base_raft_layers == 1 || slicing_params.interface_raft_layers > 1) {
assert(slicing_params.raft_layers() == 2 || slicing_params.raft_layers() == 3);
// 1st layer, interface & contact layers available.
this->raft_angle_1st_layer = this->base_angle;
this->raft_angle_interface = this->interface_angle + 0.5 * M_PI;
} else if (slicing_params.interface_raft_layers == 1) {
// Only the contact raft layer is non-empty, which will be printed as the 1st layer.
assert(slicing_params.base_raft_layers == 0);
assert(slicing_params.interface_raft_layers == 1);
assert(slicing_params.raft_layers() == 1);
this->raft_angle_1st_layer = float(0.5 * M_PI);
this->raft_angle_interface = this->raft_angle_1st_layer;
} else {
// No raft.
assert(slicing_params.base_raft_layers == 0);
assert(slicing_params.interface_raft_layers == 0);
assert(slicing_params.raft_layers() == 0);
}
} }
PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object, const SlicingParameters &slicing_params) : PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object, const SlicingParameters &slicing_params) :
@ -4207,38 +4238,12 @@ void generate_support_toolpaths(
// const coordf_t link_max_length_factor = 3.; // const coordf_t link_max_length_factor = 3.;
const coordf_t link_max_length_factor = 0.; const coordf_t link_max_length_factor = 0.;
float raft_angle_1st_layer = 0.f;
float raft_angle_base = 0.f;
float raft_angle_interface = 0.f;
if (slicing_params.base_raft_layers > 1) {
// There are all raft layer types (1st layer, base, interface & contact layers) available.
raft_angle_1st_layer = support_params.interface_angle;
raft_angle_base = support_params.base_angle;
raft_angle_interface = support_params.interface_angle;
} else if (slicing_params.base_raft_layers == 1 || slicing_params.interface_raft_layers > 1) {
// 1st layer, interface & contact layers available.
raft_angle_1st_layer = support_params.base_angle;
if (config.support_material || config.support_material_enforce_layers > 0)
// Print 1st layer at 45 degrees from both the interface and base angles as both can land on the 1st layer.
raft_angle_1st_layer += 0.7854f;
raft_angle_interface = support_params.interface_angle;
} else if (slicing_params.interface_raft_layers == 1) {
// Only the contact raft layer is non-empty, which will be printed as the 1st layer.
assert(slicing_params.base_raft_layers == 0);
assert(slicing_params.interface_raft_layers == 1);
assert(slicing_params.raft_layers() == 1 && raft_layers.size() == 0);
} else {
// No raft.
assert(slicing_params.base_raft_layers == 0);
assert(slicing_params.interface_raft_layers == 0);
assert(slicing_params.raft_layers() == 0 && raft_layers.size() == 0);
}
// Insert the raft base layers. // Insert the raft base layers.
auto n_raft_layers = std::min<size_t>(support_layers.size(), std::max(0, int(slicing_params.raft_layers()) - 1)); auto n_raft_layers = std::min<size_t>(support_layers.size(), std::max(0, int(slicing_params.raft_layers()) - 1));
tbb::parallel_for(tbb::blocked_range<size_t>(0, n_raft_layers), tbb::parallel_for(tbb::blocked_range<size_t>(0, n_raft_layers),
[&support_layers, &raft_layers, &intermediate_layers, &config, &support_params, &slicing_params, [&support_layers, &raft_layers, &intermediate_layers, &config, &support_params, &slicing_params,
&bbox_object, raft_angle_1st_layer, raft_angle_base, raft_angle_interface, link_max_length_factor] &bbox_object, link_max_length_factor]
(const tbb::blocked_range<size_t>& range) { (const tbb::blocked_range<size_t>& range) {
for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id)
{ {
@ -4270,7 +4275,7 @@ void generate_support_toolpaths(
assert(!raft_layer.bridging); assert(!raft_layer.bridging);
if (! to_infill_polygons.empty()) { if (! to_infill_polygons.empty()) {
Fill *filler = filler_support.get(); Fill *filler = filler_support.get();
filler->angle = raft_angle_base; filler->angle = support_params.raft_angle_base;
filler->spacing = support_params.support_material_flow.spacing(); filler->spacing = support_params.support_material_flow.spacing();
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.support_density)); filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.support_density));
fill_expolygons_with_sheath_generate_paths( fill_expolygons_with_sheath_generate_paths(
@ -4293,11 +4298,11 @@ void generate_support_toolpaths(
float density = 0.f; float density = 0.f;
if (support_layer_id == 0) { if (support_layer_id == 0) {
// Base flange. // Base flange.
filler->angle = raft_angle_1st_layer; filler->angle = support_params.raft_angle_1st_layer;
filler->spacing = support_params.first_layer_flow.spacing(); filler->spacing = support_params.first_layer_flow.spacing();
density = float(config.raft_first_layer_density.value * 0.01); density = float(config.raft_first_layer_density.value * 0.01);
} else if (support_layer_id >= slicing_params.base_raft_layers) { } else if (support_layer_id >= slicing_params.base_raft_layers) {
filler->angle = raft_angle_interface; filler->angle = support_params.raft_interface_angle(support_layer.interface_id());
// We don't use $base_flow->spacing because we need a constant spacing // We don't use $base_flow->spacing because we need a constant spacing
// value that guarantees that all layers are correctly aligned. // value that guarantees that all layers are correctly aligned.
filler->spacing = support_params.support_material_flow.spacing(); filler->spacing = support_params.support_material_flow.spacing();
@ -4345,7 +4350,7 @@ void generate_support_toolpaths(
std::vector<LayerCache> layer_caches(support_layers.size()); std::vector<LayerCache> layer_caches(support_layers.size());
tbb::parallel_for(tbb::blocked_range<size_t>(n_raft_layers, support_layers.size()), tbb::parallel_for(tbb::blocked_range<size_t>(n_raft_layers, support_layers.size()),
[&config, &support_params, &support_layers, &bottom_contacts, &top_contacts, &intermediate_layers, &interface_layers, &base_interface_layers, &layer_caches, &loop_interface_processor, [&config, &slicing_params, &support_params, &support_layers, &bottom_contacts, &top_contacts, &intermediate_layers, &interface_layers, &base_interface_layers, &layer_caches, &loop_interface_processor,
&bbox_object, &angles, n_raft_layers, link_max_length_factor] &bbox_object, &angles, n_raft_layers, link_max_length_factor]
(const tbb::blocked_range<size_t>& range) { (const tbb::blocked_range<size_t>& range) {
// Indices of the 1st layer in their respective container at the support layer height. // Indices of the 1st layer in their respective container at the support layer height.
@ -4381,9 +4386,8 @@ void generate_support_toolpaths(
{ {
SupportLayer &support_layer = *support_layers[support_layer_id]; SupportLayer &support_layer = *support_layers[support_layer_id];
LayerCache &layer_cache = layer_caches[support_layer_id]; LayerCache &layer_cache = layer_caches[support_layer_id];
float interface_angle_delta = config.support_material_style.value != smsGrid ? const float support_interface_angle = config.support_material_style.value == smsGrid ?
(support_layer.interface_id() & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.) : support_params.interface_angle : support_params.raft_interface_angle(support_layer.interface_id());
0;
// Find polygons with the same print_z. // Find polygons with the same print_z.
SupportGeneratorLayerExtruded &bottom_contact_layer = layer_cache.bottom_contact_layer; SupportGeneratorLayerExtruded &bottom_contact_layer = layer_cache.bottom_contact_layer;
@ -4412,7 +4416,8 @@ void generate_support_toolpaths(
if (idx_layer_intermediate < intermediate_layers.size() && intermediate_layers[idx_layer_intermediate]->print_z < support_layer.print_z + EPSILON) if (idx_layer_intermediate < intermediate_layers.size() && intermediate_layers[idx_layer_intermediate]->print_z < support_layer.print_z + EPSILON)
base_layer.layer = intermediate_layers[idx_layer_intermediate]; base_layer.layer = intermediate_layers[idx_layer_intermediate];
bool raft_layer = support_layer_id == n_raft_layers; // This layer is a raft contact layer. Any contact polygons at this layer are raft contacts.
bool raft_layer = slicing_params.interface_raft_layers && top_contact_layer.layer && is_approx(top_contact_layer.layer->print_z, slicing_params.raft_contact_top_z);
if (config.support_material_interface_layers == 0) { if (config.support_material_interface_layers == 0) {
// If no top interface layers were requested, we treat the contact layer exactly as a generic base layer. // If no top interface layers were requested, we treat the contact layer exactly as a generic base layer.
// Don't merge the raft contact layer though. // Don't merge the raft contact layer though.
@ -4470,7 +4475,9 @@ void generate_support_toolpaths(
// If zero interface layers are configured, use the same angle as for the base layers. // If zero interface layers are configured, use the same angle as for the base layers.
angles[support_layer_id % angles.size()] : angles[support_layer_id % angles.size()] :
// Use interface angle for the interface layers. // Use interface angle for the interface layers.
support_params.interface_angle + interface_angle_delta; raft_contact ?
support_params.raft_interface_angle(support_layer.interface_id()) :
support_interface_angle;
double density = raft_contact ? support_params.raft_interface_density : interface_as_base ? support_params.support_density : support_params.interface_density; double density = raft_contact ? support_params.raft_interface_density : interface_as_base ? support_params.support_density : support_params.interface_density;
filler->spacing = raft_contact ? support_params.raft_interface_flow.spacing() : filler->spacing = raft_contact ? support_params.raft_interface_flow.spacing() :
interface_as_base ? support_params.support_material_flow.spacing() : support_params.support_material_interface_flow.spacing(); interface_as_base ? support_params.support_material_flow.spacing() : support_params.support_material_interface_flow.spacing();
@ -4499,7 +4506,7 @@ void generate_support_toolpaths(
// the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b) // the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b)
assert(! base_interface_layer.layer->bridging); assert(! base_interface_layer.layer->bridging);
Flow interface_flow = support_params.support_material_flow.with_height(float(base_interface_layer.layer->height)); Flow interface_flow = support_params.support_material_flow.with_height(float(base_interface_layer.layer->height));
filler->angle = support_params.interface_angle + interface_angle_delta; filler->angle = support_interface_angle;
filler->spacing = support_params.support_material_interface_flow.spacing(); filler->spacing = support_params.support_material_interface_flow.spacing();
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.interface_density)); filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_params.interface_density));
fill_expolygons_generate_paths( fill_expolygons_generate_paths(
@ -4748,4 +4755,3 @@ sub clip_with_shape {
*/ */
} // namespace Slic3r } // namespace Slic3r

View File

@ -161,6 +161,14 @@ struct SupportParameters {
InfillPattern contact_fill_pattern; InfillPattern contact_fill_pattern;
// Shall the sparse (base) layers be printed with a single perimeter line (sheath) for robustness? // Shall the sparse (base) layers be printed with a single perimeter line (sheath) for robustness?
bool with_sheath; bool with_sheath;
float raft_angle_1st_layer;
float raft_angle_base;
float raft_angle_interface;
// Produce a raft interface angle for a given SupportLayer::interface_id()
float raft_interface_angle(size_t interface_id) const
{ return this->raft_angle_interface + ((interface_id & 1) ? float(- M_PI / 4.) : float(+ M_PI / 4.)); }
}; };
// Remove bridges from support contact areas. // Remove bridges from support contact areas.

View File

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

View File

@ -145,6 +145,7 @@ struct PartialObject
using PartialObjects = std::vector<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); 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); void estimate_supports_malformations(std::vector<SupportLayer *> &layers, float supports_flow_width, const Params &params);

View File

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

View File

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

View File

@ -3,6 +3,7 @@
#include "libslic3r.h" #include "libslic3r.h"
#include "Surface.hpp" #include "Surface.hpp"
#include <initializer_list>
#include <vector> #include <vector>
namespace Slic3r { namespace Slic3r {
@ -27,11 +28,11 @@ public:
return false; return false;
} }
SurfacesPtr filter_by_type(const SurfaceType type) const; 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_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_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 filter_by_type(SurfaceType type, Polygons *polygons) const;
void remove_type(const SurfaceType type, ExPolygons *polygons); void remove_type(const SurfaceType type, ExPolygons *polygons);
void set_type(SurfaceType type) { void set_type(SurfaceType type) {

View File

@ -491,9 +491,8 @@ void TreeModelVolumes::calculateCollision(const coord_t radius, const LayerIndex
if (const std::vector<Polygons> &outlines = m_layer_outlines[outline_idx].second; ! outlines.empty()) { if (const std::vector<Polygons> &outlines = m_layer_outlines[outline_idx].second; ! outlines.empty()) {
const TreeSupportMeshGroupSettings &settings = m_layer_outlines[outline_idx].first; const TreeSupportMeshGroupSettings &settings = m_layer_outlines[outline_idx].first;
const coord_t layer_height = settings.layer_height; const coord_t layer_height = settings.layer_height;
const coord_t z_distance_bottom = settings.support_bottom_distance; const int z_distance_bottom_layers = int(round(double(settings.support_bottom_distance) / double(layer_height)));
const int z_distance_bottom_layers = round_up_divide<int>(z_distance_bottom, layer_height); const int z_distance_top_layers = int(round(double(settings.support_top_distance) / double(layer_height)));
const int z_distance_top_layers = round_up_divide<int>(settings.support_top_distance, layer_height);
const LayerIndex max_required_layer = std::min<LayerIndex>(outlines.size(), max_layer_idx + std::max(coord_t(1), z_distance_top_layers)); const LayerIndex max_required_layer = std::min<LayerIndex>(outlines.size(), max_layer_idx + std::max(coord_t(1), z_distance_top_layers));
const LayerIndex min_layer_bottom = std::max<LayerIndex>(0, min_layer_last - int(z_distance_bottom_layers)); const LayerIndex min_layer_bottom = std::max<LayerIndex>(0, min_layer_last - int(z_distance_bottom_layers));
const coord_t xy_distance = outline_idx == m_current_outline_idx ? m_current_min_xy_dist : const coord_t xy_distance = outline_idx == m_current_outline_idx ? m_current_min_xy_dist :

View File

@ -80,8 +80,8 @@ TreeSupportSettings::TreeSupportSettings(const TreeSupportMeshGroupSettings& mes
xy_min_distance(std::min(mesh_group_settings.support_xy_distance, mesh_group_settings.support_xy_distance_overhang)), xy_min_distance(std::min(mesh_group_settings.support_xy_distance, mesh_group_settings.support_xy_distance_overhang)),
bp_radius(mesh_group_settings.support_tree_bp_diameter / 2), bp_radius(mesh_group_settings.support_tree_bp_diameter / 2),
diameter_scale_bp_radius(std::min(sin(0.7) * layer_height / branch_radius, 1.0 / (branch_radius / (support_line_width / 2.0)))), // Either 40? or as much as possible so that 2 lines will overlap by at least 50%, whichever is smaller. diameter_scale_bp_radius(std::min(sin(0.7) * layer_height / branch_radius, 1.0 / (branch_radius / (support_line_width / 2.0)))), // Either 40? or as much as possible so that 2 lines will overlap by at least 50%, whichever is smaller.
z_distance_top_layers(round_up_divide(mesh_group_settings.support_top_distance, layer_height)), z_distance_bottom_layers(size_t(round(double(mesh_group_settings.support_bottom_distance) / double(layer_height)))),
z_distance_bottom_layers(round_up_divide(mesh_group_settings.support_bottom_distance, layer_height)), z_distance_top_layers(size_t(round(double(mesh_group_settings.support_top_distance) / double(layer_height)))),
performance_interface_skip_layers(round_up_divide(mesh_group_settings.support_interface_skip_height, layer_height)), performance_interface_skip_layers(round_up_divide(mesh_group_settings.support_interface_skip_height, layer_height)),
// support_infill_angles(mesh_group_settings.support_infill_angles), // support_infill_angles(mesh_group_settings.support_infill_angles),
support_roof_angles(mesh_group_settings.support_roof_angles), support_roof_angles(mesh_group_settings.support_roof_angles),
@ -2421,7 +2421,6 @@ static void merge_influence_areas(
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_buckets_initial), tbb::parallel_for(tbb::blocked_range<size_t>(0, num_buckets_initial),
[&](const tbb::blocked_range<size_t> &range) { [&](const tbb::blocked_range<size_t> &range) {
for (size_t idx = range.begin(); idx < range.end(); ++ idx) { 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 // 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); buckets[idx].second = merge_influence_areas_leaves(volumes, config, layer_idx, buckets[idx].first, buckets[idx].second);
throw_on_cancel(); throw_on_cancel();
@ -4012,9 +4011,13 @@ static indexed_triangle_set draw_branches(
// Only one link points to a node above from below. // Only one link points to a node above from below.
assert(!(++it != map_downwards_old.end() && it->first == &elem)); assert(!(++it != map_downwards_old.end() && it->first == &elem));
} }
#ifndef NDEBUG
{
const SupportElement *pchild = child == -1 ? nullptr : &move_bounds[layer_idx - 1][child]; 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); assert(pchild ? pchild->state.result_on_layer_is_set() : elem.state.target_height > layer_idx);
} }
#endif // NDEBUG
}
for (int32_t parent_idx : elem.parents) { for (int32_t parent_idx : elem.parents) {
SupportElement &parent = (*layer_above)[parent_idx]; SupportElement &parent = (*layer_above)[parent_idx];
if (parent.state.result_on_layer_is_set()) if (parent.state.result_on_layer_is_set())
@ -4078,10 +4081,12 @@ static indexed_triangle_set draw_branches(
partial_mesh.clear(); partial_mesh.clear();
extrude_branch(path, config, slicing_params, move_bounds, partial_mesh); extrude_branch(path, config, slicing_params, move_bounds, partial_mesh);
#if 0 #if 0
{
char fname[2048]; char fname[2048];
static int irun = 0;
sprintf(fname, "d:\\temp\\meshes\\tree-raw-%d.obj", ++ irun); sprintf(fname, "d:\\temp\\meshes\\tree-raw-%d.obj", ++ irun);
its_write_obj(partial_mesh, fname); its_write_obj(partial_mesh, fname);
#if 0 #if 0
temp_mesh.clear(); temp_mesh.clear();
cut_mesh(partial_mesh, layer_z(slicing_params, path.back()->state.layer_idx) + EPSILON, nullptr, &temp_mesh, false); 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); sprintf(fname, "d:\\temp\\meshes\\tree-trimmed1-%d.obj", irun);
@ -4089,8 +4094,9 @@ static indexed_triangle_set draw_branches(
partial_mesh.clear(); partial_mesh.clear();
cut_mesh(temp_mesh, layer_z(slicing_params, path.front()->state.layer_idx) - EPSILON, &partial_mesh, nullptr, false); 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); sprintf(fname, "d:\\temp\\meshes\\tree-trimmed2-%d.obj", irun);
#endif #endif
its_write_obj(partial_mesh, fname); its_write_obj(partial_mesh, fname);
}
#endif #endif
its_merge(cummulative_mesh, partial_mesh); its_merge(cummulative_mesh, partial_mesh);
} }

View File

@ -62,6 +62,8 @@ public:
// Inherits coord_t x, y // Inherits coord_t x, y
}; };
#define DEBUG_INTERSECTIONLINE (! defined(NDEBUG) || defined(SLIC3R_DEBUG_SLICE_PROCESSING))
class IntersectionLine : public Line class IntersectionLine : public Line
{ {
public: public:
@ -119,14 +121,14 @@ public:
}; };
uint32_t flags { 0 }; uint32_t flags { 0 };
#ifndef NDEBUG #if DEBUG_INTERSECTIONLINE
enum class Source { enum class Source {
BottomPlane, BottomPlane,
TopPlane, TopPlane,
Slab, Slab,
}; };
Source source { Source::BottomPlane }; Source source { Source::BottomPlane };
#endif // NDEBUG #endif
}; };
using IntersectionLines = std::vector<IntersectionLine>; 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]) for (const IntersectionLine &l : lines.at_slice[slice_below])
if (l.edge_type != IntersectionLine::FacetEdgeType::Top) { if (l.edge_type != IntersectionLine::FacetEdgeType::Top) {
in.emplace_back(l); in.emplace_back(l);
#ifndef NDEBUG #if DEBUG_INTERSECTIONLINE
in.back().source = IntersectionLine::Source::BottomPlane; in.back().source = IntersectionLine::Source::BottomPlane;
#endif // NDEBUG #endif // DEBUG_INTERSECTIONLINE
} }
} }
{ {
// Edges in between slice_below and slice_above. // Edges in between slice_below and slice_above.
#ifndef NDEBUG #if DEBUG_INTERSECTIONLINE
size_t old_size = in.size(); 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. // 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]); append(in, lines.between_slices[line_idx]);
#ifndef NDEBUG #if DEBUG_INTERSECTIONLINE
for (auto it = in.begin() + old_size; it != in.end(); ++ it) { for (auto it = in.begin() + old_size; it != in.end(); ++ it) {
assert(it->edge_type == IntersectionLine::FacetEdgeType::Slab); assert(it->edge_type == IntersectionLine::FacetEdgeType::Slab);
it->source = IntersectionLine::Source::Slab; it->source = IntersectionLine::Source::Slab;
} }
#endif // NDEBUG #endif // DEBUG_INTERSECTIONLINE
} }
if (has_slice_above) { if (has_slice_above) {
for (const IntersectionLine &lsrc : lines.at_slice[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; l.edge_a_id += num_edges;
if (l.edge_b_id >= 0) if (l.edge_b_id >= 0)
l.edge_b_id += num_edges; l.edge_b_id += num_edges;
#ifndef NDEBUG #if DEBUG_INTERSECTIONLINE
l.source = IntersectionLine::Source::TopPlane; l.source = IntersectionLine::Source::TopPlane;
#endif // NDEBUG #endif // DEBUG_INTERSECTIONLINE
} }
} }
if (! in.empty()) { if (! in.empty()) {

View File

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

View File

@ -221,12 +221,11 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config)
bool have_perimeters = config->opt_int("perimeters") > 0; bool have_perimeters = config->opt_int("perimeters") > 0;
for (auto el : { "extra_perimeters","extra_perimeters_on_overhangs", "ensure_vertical_shell_thickness", "thin_walls", "overhangs", for (auto el : { "extra_perimeters","extra_perimeters_on_overhangs", "ensure_vertical_shell_thickness", "thin_walls", "overhangs",
"seam_position","staggered_inner_seams", "external_perimeters_first", "external_perimeter_extrusion_width", "seam_position","staggered_inner_seams", "external_perimeters_first", "external_perimeter_extrusion_width",
"perimeter_speed", "small_perimeter_speed", "external_perimeter_speed", "enable_dynamic_overhang_speeds", "overhang_overlap_levels", "dynamic_overhang_speeds" }) "perimeter_speed", "small_perimeter_speed", "external_perimeter_speed", "enable_dynamic_overhang_speeds"})
toggle_field(el, have_perimeters); toggle_field(el, have_perimeters);
for (size_t i = 0; i < 4; i++) { for (size_t i = 0; i < 4; i++) {
toggle_field("overhang_overlap_levels#" + std::to_string(i), config->opt_bool("enable_dynamic_overhang_speeds")); toggle_field("overhang_speed_" + std::to_string(i), config->opt_bool("enable_dynamic_overhang_speeds"));
toggle_field("dynamic_overhang_speeds#" + std::to_string(i), config->opt_bool("enable_dynamic_overhang_speeds"));
} }
bool have_infill = config->option<ConfigOptionPercent>("fill_density")->value > 0; bool have_infill = config->option<ConfigOptionPercent>("fill_density")->value > 0;

View File

@ -1549,18 +1549,11 @@ void TabPrint::build()
optgroup->append_single_option_line("ironing_speed"); optgroup->append_single_option_line("ironing_speed");
optgroup = page->new_optgroup(L("Dynamic overhang speed")); optgroup = page->new_optgroup(L("Dynamic overhang speed"));
auto append_option_line = [](ConfigOptionsGroupShp optgroup, std::string opt_key) {
auto option = optgroup->get_option(opt_key, 0);
auto line = Line{option.opt.full_label, ""};
line.append_option(option);
line.append_option(optgroup->get_option(opt_key, 1));
line.append_option(optgroup->get_option(opt_key, 2));
line.append_option(optgroup->get_option(opt_key, 3));
optgroup->append_line(line);
};
optgroup->append_single_option_line("enable_dynamic_overhang_speeds"); optgroup->append_single_option_line("enable_dynamic_overhang_speeds");
append_option_line(optgroup,"overhang_overlap_levels"); optgroup->append_single_option_line("overhang_speed_0");
append_option_line(optgroup,"dynamic_overhang_speeds"); optgroup->append_single_option_line("overhang_speed_1");
optgroup->append_single_option_line("overhang_speed_2");
optgroup->append_single_option_line("overhang_speed_3");
optgroup = page->new_optgroup(L("Speed for non-print moves")); optgroup = page->new_optgroup(L("Speed for non-print moves"));
optgroup->append_single_option_line("travel_speed"); optgroup->append_single_option_line("travel_speed");
@ -1994,6 +1987,13 @@ void TabFilament::build()
optgroup->append_single_option_line("disable_fan_first_layers", category_path + "fan-settings"); optgroup->append_single_option_line("disable_fan_first_layers", category_path + "fan-settings");
optgroup->append_single_option_line("full_fan_speed_layer", category_path + "fan-settings"); optgroup->append_single_option_line("full_fan_speed_layer", category_path + "fan-settings");
optgroup = page->new_optgroup(L("Dynamic fan speeds"), 25);
optgroup->append_single_option_line("enable_dynamic_fan_speeds", category_path + "dynamic-fan-speeds");
optgroup->append_single_option_line("overhang_fan_speed_0", category_path + "dynamic-fan-speeds");
optgroup->append_single_option_line("overhang_fan_speed_1", category_path + "dynamic-fan-speeds");
optgroup->append_single_option_line("overhang_fan_speed_2", category_path + "dynamic-fan-speeds");
optgroup->append_single_option_line("overhang_fan_speed_3", category_path + "dynamic-fan-speeds");
optgroup = page->new_optgroup(L("Cooling thresholds"), 25); optgroup = page->new_optgroup(L("Cooling thresholds"), 25);
optgroup->append_single_option_line("fan_below_layer_time", category_path + "cooling-thresholds"); optgroup->append_single_option_line("fan_below_layer_time", category_path + "cooling-thresholds");
optgroup->append_single_option_line("slowdown_below_layer_time", category_path + "cooling-thresholds"); optgroup->append_single_option_line("slowdown_below_layer_time", category_path + "cooling-thresholds");
@ -2146,6 +2146,11 @@ void TabFilament::toggle_options()
for (auto el : { "min_fan_speed", "disable_fan_first_layers", "full_fan_speed_layer" }) for (auto el : { "min_fan_speed", "disable_fan_first_layers", "full_fan_speed_layer" })
toggle_option(el, fan_always_on); toggle_option(el, fan_always_on);
bool dynamic_fan_speeds = m_config->opt_bool("enable_dynamic_fan_speeds", 0);
for (int i = 0; i < 4; i++) {
toggle_option("overhang_fan_speed_"+std::to_string(i),dynamic_fan_speeds);
}
} }
if (m_active_page->title() == "Filament Overrides") if (m_active_page->title() == "Filament Overrides")

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 <catch2/catch.hpp>
#include "test_data.hpp" #include "test_data.hpp"
#include "clipper/clipper_z.hpp" #include "libslic3r/ClipperZUtils.hpp"
#include "libslic3r/clipper.hpp" #include "libslic3r/clipper.hpp"
using namespace Slic3r; using namespace Slic3r;
@ -132,3 +132,72 @@ SCENARIO("Clipper Z", "[ClipperZ]")
REQUIRE(pt.z() == 1); 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

@ -10,15 +10,13 @@ using namespace Slic3r;
SCENARIO("Shells", "[Shells]") { SCENARIO("Shells", "[Shells]") {
GIVEN("20mm box") { GIVEN("20mm box") {
auto test = [](const DynamicPrintConfig &config){ auto test = [](const DynamicPrintConfig &config){
std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config);
std::vector<coord_t> zs; std::vector<coord_t> zs;
std::set<coord_t> layers_with_solid_infill; std::set<coord_t> layers_with_solid_infill;
std::set<coord_t> layers_with_bridge_infill; std::set<coord_t> layers_with_bridge_infill;
const double solid_infill_speed = config.opt_float("solid_infill_speed") * 60; const double solid_infill_speed = config.opt_float("solid_infill_speed") * 60;
const double bridge_speed = config.opt_float("bridge_speed") * 60; const double bridge_speed = config.opt_float("bridge_speed") * 60;
GCodeReader parser; 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] [&zs, &layers_with_solid_infill, &layers_with_bridge_infill, solid_infill_speed, bridge_speed]
(Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) (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_stl.cpp
test_meshboolean.cpp test_meshboolean.cpp
test_marchingsquares.cpp test_marchingsquares.cpp
test_region_expansion.cpp
test_timeutils.cpp test_timeutils.cpp
test_utils.cpp test_utils.cpp
test_voronoi.cpp test_voronoi.cpp

View File

@ -31,6 +31,7 @@ SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") {
parser.set("foo", 0); parser.set("foo", 0);
parser.set("bar", 2); parser.set("bar", 2);
parser.set("num_extruders", 4); parser.set("num_extruders", 4);
parser.set("gcode_flavor", "marlin");
SECTION("nested config options (legacy syntax)") { REQUIRE(parser.process("[temperature_[foo]]") == "357"); } SECTION("nested config options (legacy syntax)") { REQUIRE(parser.process("[temperature_[foo]]") == "357"); }
SECTION("array reference") { 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 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 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("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" #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))));
}
}
}
}