Merge pull request #27 from Prusa-Development/pm_anchor_bridges_on_sparse_infill
Ensuring + anchoring bridges over sparse infill
This commit is contained in:
commit
1a0d8f5130
@ -4109,19 +4109,40 @@ void AddPolyNodeToPaths(const PolyNode& polynode, NodeType nodetype, Paths& path
|
||||
for (int i = 0; i < polynode.ChildCount(); ++i)
|
||||
AddPolyNodeToPaths(*polynode.Childs[i], nodetype, paths);
|
||||
}
|
||||
|
||||
void AddPolyNodeToPaths(PolyNode&& polynode, NodeType nodetype, Paths& paths)
|
||||
{
|
||||
bool match = true;
|
||||
if (nodetype == ntClosed) match = !polynode.IsOpen();
|
||||
else if (nodetype == ntOpen) return;
|
||||
|
||||
if (!polynode.Contour.empty() && match)
|
||||
paths.push_back(std::move(polynode.Contour));
|
||||
for (int i = 0; i < polynode.ChildCount(); ++i)
|
||||
AddPolyNodeToPaths(std::move(*polynode.Childs[i]), nodetype, paths);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void PolyTreeToPaths(const PolyTree& polytree, Paths& paths)
|
||||
{
|
||||
paths.resize(0);
|
||||
paths.clear();
|
||||
paths.reserve(polytree.Total());
|
||||
AddPolyNodeToPaths(polytree, ntAny, paths);
|
||||
}
|
||||
|
||||
void PolyTreeToPaths(PolyTree&& polytree, Paths& paths)
|
||||
{
|
||||
paths.clear();
|
||||
paths.reserve(polytree.Total());
|
||||
AddPolyNodeToPaths(std::move(polytree), ntAny, paths);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths)
|
||||
{
|
||||
paths.resize(0);
|
||||
paths.clear();
|
||||
paths.reserve(polytree.Total());
|
||||
AddPolyNodeToPaths(polytree, ntClosed, paths);
|
||||
}
|
||||
@ -4129,7 +4150,7 @@ void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths)
|
||||
|
||||
void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths)
|
||||
{
|
||||
paths.resize(0);
|
||||
paths.clear();
|
||||
paths.reserve(polytree.Total());
|
||||
//Open paths are top level only, so ...
|
||||
for (int i = 0; i < polytree.ChildCount(); ++i)
|
||||
|
@ -206,6 +206,7 @@ void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, bool
|
||||
void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution);
|
||||
|
||||
void PolyTreeToPaths(const PolyTree& polytree, Paths& paths);
|
||||
void PolyTreeToPaths(PolyTree&& polytree, Paths& paths);
|
||||
void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths);
|
||||
void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths);
|
||||
|
||||
|
@ -13,6 +13,7 @@
|
||||
|
||||
#include <Eigen/Geometry>
|
||||
|
||||
#include "BoundingBox.hpp"
|
||||
#include "Utils.hpp" // for next_highest_power_of_2()
|
||||
|
||||
// Definition of the ray intersection hit structure.
|
||||
@ -217,6 +218,23 @@ using Tree3f = Tree<3, float>;
|
||||
using Tree2d = Tree<2, double>;
|
||||
using Tree3d = Tree<3, double>;
|
||||
|
||||
// Wrap a 2D Slic3r own BoundingBox to be passed to Tree::build() and similar
|
||||
// to build an AABBTree over coord_t 2D bounding boxes.
|
||||
class BoundingBoxWrapper {
|
||||
public:
|
||||
using BoundingBox = Eigen::AlignedBox<coord_t, 2>;
|
||||
BoundingBoxWrapper(const size_t idx, const Slic3r::BoundingBox &bbox) :
|
||||
m_idx(idx),
|
||||
// Inflate the bounding box a bit to account for numerical issues.
|
||||
m_bbox(bbox.min - Point(SCALED_EPSILON, SCALED_EPSILON), bbox.max + Point(SCALED_EPSILON, SCALED_EPSILON)) {}
|
||||
size_t idx() const { return m_idx; }
|
||||
const BoundingBox& bbox() const { return m_bbox; }
|
||||
Point centroid() const { return ((m_bbox.min().cast<int64_t>() + m_bbox.max().cast<int64_t>()) / 2).cast<int32_t>(); }
|
||||
private:
|
||||
size_t m_idx;
|
||||
BoundingBox m_bbox;
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
template<typename AVertexType, typename AIndexedFaceType, typename ATreeType, typename AVectorType>
|
||||
struct RayIntersector {
|
||||
|
539
src/libslic3r/Algorithm/RegionExpansion.cpp
Normal file
539
src/libslic3r/Algorithm/RegionExpansion.cpp
Normal 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 ¶ms)
|
||||
{
|
||||
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 ¶ms)
|
||||
{
|
||||
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 ¶ms)
|
||||
{
|
||||
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 ¶ms)
|
||||
{
|
||||
// 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
|
114
src/libslic3r/Algorithm/RegionExpansion.hpp
Normal file
114
src/libslic3r/Algorithm/RegionExpansion.hpp
Normal 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 ¶ms);
|
||||
|
||||
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 ¶ms);
|
||||
|
||||
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 ¶ms);
|
||||
|
||||
} // Algorithm
|
||||
} // Slic3r
|
||||
|
||||
#endif /* SRC_LIBSLIC3R_ALGORITHM_REGION_EXPANSION_HPP_ */
|
@ -22,24 +22,9 @@ public:
|
||||
BoundingBoxBase(const PointClass &p1, const PointClass &p2, const PointClass &p3) :
|
||||
min(p1), max(p1), defined(false) { merge(p2); merge(p3); }
|
||||
|
||||
template<class It, class = IteratorOnly<It> >
|
||||
BoundingBoxBase(It from, It to) : min(PointClass::Zero()), max(PointClass::Zero())
|
||||
{
|
||||
if (from == to) {
|
||||
this->defined = false;
|
||||
// throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBoxBase constructor");
|
||||
} else {
|
||||
auto it = from;
|
||||
this->min = it->template cast<typename PointClass::Scalar>();
|
||||
this->max = this->min;
|
||||
for (++ it; it != to; ++ it) {
|
||||
auto vec = it->template cast<typename PointClass::Scalar>();
|
||||
this->min = this->min.cwiseMin(vec);
|
||||
this->max = this->max.cwiseMax(vec);
|
||||
}
|
||||
this->defined = (this->min.x() < this->max.x()) && (this->min.y() < this->max.y());
|
||||
}
|
||||
}
|
||||
template<class It, class = IteratorOnly<It>>
|
||||
BoundingBoxBase(It from, It to)
|
||||
{ construct(*this, from, to); }
|
||||
|
||||
BoundingBoxBase(const std::vector<PointClass> &points)
|
||||
: BoundingBoxBase(points.begin(), points.end())
|
||||
@ -70,6 +55,30 @@ public:
|
||||
}
|
||||
bool operator==(const BoundingBoxBase<PointClass> &rhs) { return this->min == rhs.min && this->max == rhs.max; }
|
||||
bool operator!=(const BoundingBoxBase<PointClass> &rhs) { return ! (*this == rhs); }
|
||||
|
||||
private:
|
||||
// to access construct()
|
||||
friend BoundingBox get_extents<false>(const Points &pts);
|
||||
friend BoundingBox get_extents<true>(const Points &pts);
|
||||
|
||||
// if IncludeBoundary, then a bounding box is defined even for a single point.
|
||||
// otherwise a bounding box is only defined if it has a positive area.
|
||||
// The output bounding box is expected to be set to "undefined" initially.
|
||||
template<bool IncludeBoundary = false, class BoundingBoxType, class It, class = IteratorOnly<It>>
|
||||
static void construct(BoundingBoxType &out, It from, It to)
|
||||
{
|
||||
if (from != to) {
|
||||
auto it = from;
|
||||
out.min = it->template cast<typename PointClass::Scalar>();
|
||||
out.max = out.min;
|
||||
for (++ it; it != to; ++ it) {
|
||||
auto vec = it->template cast<typename PointClass::Scalar>();
|
||||
out.min = out.min.cwiseMin(vec);
|
||||
out.max = out.max.cwiseMax(vec);
|
||||
}
|
||||
out.defined = IncludeBoundary || (out.min.x() < out.max.x() && out.min.y() < out.max.y());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class PointClass>
|
||||
|
@ -75,12 +75,9 @@ private:
|
||||
|
||||
|
||||
//return ideal bridge direction and unsupported bridge endpoints distance.
|
||||
inline std::tuple<Vec2d, double> detect_bridging_direction(const Polygons &to_cover, const Polygons &anchors_area)
|
||||
inline std::tuple<Vec2d, double> detect_bridging_direction(const Lines &floating_edges, const Polygons &overhang_area)
|
||||
{
|
||||
Polygons overhang_area = diff(to_cover, anchors_area);
|
||||
Polylines floating_polylines = diff_pl(to_polylines(overhang_area), expand(anchors_area, float(SCALED_EPSILON)));
|
||||
|
||||
if (floating_polylines.empty()) {
|
||||
if (floating_edges.empty()) {
|
||||
// consider this area anchored from all sides, pick bridging direction that will likely yield shortest bridges
|
||||
auto [pc1, pc2] = compute_principal_components(overhang_area);
|
||||
if (pc2 == Vec2f::Zero()) { // overhang may be smaller than resolution. In this case, any direction is ok
|
||||
@ -91,7 +88,6 @@ inline std::tuple<Vec2d, double> detect_bridging_direction(const Polygons &to_co
|
||||
}
|
||||
|
||||
// Overhang is not fully surrounded by anchors, in that case, find such direction that will minimize the number of bridge ends/180turns in the air
|
||||
Lines floating_edges = to_lines(floating_polylines);
|
||||
std::unordered_map<double, Vec2d> directions{};
|
||||
for (const Line &l : floating_edges) {
|
||||
Vec2d normal = l.normal().cast<double>().normalized();
|
||||
@ -125,6 +121,13 @@ inline std::tuple<Vec2d, double> detect_bridging_direction(const Polygons &to_co
|
||||
return {result_dir, min_cost};
|
||||
};
|
||||
|
||||
//return ideal bridge direction and unsupported bridge endpoints distance.
|
||||
inline std::tuple<Vec2d, double> detect_bridging_direction(const Polygons &to_cover, const Polygons &anchors_area)
|
||||
{
|
||||
Polygons overhang_area = diff(to_cover, anchors_area);
|
||||
Lines floating_edges = to_lines(diff_pl(to_polylines(overhang_area), expand(anchors_area, float(SCALED_EPSILON))));
|
||||
return detect_bridging_direction(floating_edges, overhang_area);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -641,7 +641,7 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance
|
||||
// perform operation
|
||||
ClipperLib_Z::PolyTree loops_trimmed_tree;
|
||||
clipper.Execute(ClipperLib_Z::ctDifference, loops_trimmed_tree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
|
||||
ClipperLib_Z::PolyTreeToPaths(loops_trimmed_tree, loops_trimmed);
|
||||
ClipperLib_Z::PolyTreeToPaths(std::move(loops_trimmed_tree), loops_trimmed);
|
||||
}
|
||||
|
||||
// Third, produce the extrusions, sorted by the source loop indices.
|
||||
|
@ -22,6 +22,8 @@ set(SLIC3R_SOURCES
|
||||
AABBTreeLines.hpp
|
||||
AABBMesh.hpp
|
||||
AABBMesh.cpp
|
||||
Algorithm/RegionExpansion.cpp
|
||||
Algorithm/RegionExpansion.hpp
|
||||
AnyPtr.hpp
|
||||
BoundingBox.cpp
|
||||
BoundingBox.hpp
|
||||
@ -36,6 +38,7 @@ set(SLIC3R_SOURCES
|
||||
clipper.hpp
|
||||
ClipperUtils.cpp
|
||||
ClipperUtils.hpp
|
||||
ClipperZUtils.hpp
|
||||
Color.cpp
|
||||
Color.hpp
|
||||
Config.cpp
|
||||
@ -79,6 +82,8 @@ set(SLIC3R_SOURCES
|
||||
Fill/FillBase.hpp
|
||||
Fill/FillConcentric.cpp
|
||||
Fill/FillConcentric.hpp
|
||||
Fill/FillEnsuring.cpp
|
||||
Fill/FillEnsuring.hpp
|
||||
Fill/FillHoneycomb.cpp
|
||||
Fill/FillHoneycomb.hpp
|
||||
Fill/FillGyroid.cpp
|
||||
|
@ -8,8 +8,6 @@
|
||||
#include "SVG.hpp"
|
||||
#endif /* CLIPPER_UTILS_DEBUG */
|
||||
|
||||
#define CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR (0.005f)
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
#ifdef CLIPPER_UTILS_DEBUG
|
||||
@ -267,7 +265,7 @@ static ClipperLib::Paths raw_offset(PathsProvider &&paths, float offset, Clipper
|
||||
co.ArcTolerance = miterLimit;
|
||||
else
|
||||
co.MiterLimit = miterLimit;
|
||||
co.ShortestEdgeLength = double(std::abs(offset * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR));
|
||||
co.ShortestEdgeLength = std::abs(offset * ClipperOffsetShortestEdgeFactor);
|
||||
for (const ClipperLib::Path &path : paths) {
|
||||
co.Clear();
|
||||
// Execute reorients the contours so that the outer most contour has a positive area. Thus the output
|
||||
@ -414,7 +412,7 @@ static int offset_expolygon_inner(const Slic3r::ExPolygon &expoly, const float d
|
||||
co.ArcTolerance = miterLimit;
|
||||
else
|
||||
co.MiterLimit = miterLimit;
|
||||
co.ShortestEdgeLength = double(std::abs(delta * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR));
|
||||
co.ShortestEdgeLength = std::abs(delta * ClipperOffsetShortestEdgeFactor);
|
||||
co.AddPath(expoly.contour.points, joinType, ClipperLib::etClosedPolygon);
|
||||
co.Execute(contours, delta);
|
||||
}
|
||||
@ -435,7 +433,7 @@ static int offset_expolygon_inner(const Slic3r::ExPolygon &expoly, const float d
|
||||
co.ArcTolerance = miterLimit;
|
||||
else
|
||||
co.MiterLimit = miterLimit;
|
||||
co.ShortestEdgeLength = double(std::abs(delta * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR));
|
||||
co.ShortestEdgeLength = std::abs(delta * ClipperOffsetShortestEdgeFactor);
|
||||
co.AddPath(hole.points, joinType, ClipperLib::etClosedPolygon);
|
||||
ClipperLib::Paths out2;
|
||||
// Execute reorients the contours so that the outer most contour has a positive area. Thus the output
|
||||
@ -720,6 +718,8 @@ Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfac
|
||||
{ return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::SurfacesProvider(subject), ClipperUtils::SurfacesProvider(clip), do_safety_offset); }
|
||||
Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
|
||||
{ return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::SurfacesPtrProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); }
|
||||
Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset)
|
||||
{ return _clipper_ex(ClipperLib::ctDifference, ClipperUtils::SurfacesPtrProvider(subject), ClipperUtils::ExPolygonsProvider(clip), do_safety_offset); }
|
||||
|
||||
Slic3r::ExPolygons intersection_ex(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset)
|
||||
{ return _clipper_ex(ClipperLib::ctIntersection, ClipperUtils::PolygonsProvider(subject), ClipperUtils::PolygonsProvider(clip), do_safety_offset); }
|
||||
@ -1062,7 +1062,7 @@ ClipperLib::Path mittered_offset_path_scaled(const Points &contour, const std::v
|
||||
};
|
||||
|
||||
// Minimum edge length, squared.
|
||||
double lmin = *std::max_element(deltas.begin(), deltas.end()) * CLIPPER_OFFSET_SHORTEST_EDGE_FACTOR;
|
||||
double lmin = *std::max_element(deltas.begin(), deltas.end()) * ClipperOffsetShortestEdgeFactor;
|
||||
double l2min = lmin * lmin;
|
||||
// Minimum angle to consider two edges to be parallel.
|
||||
// Vojtech's estimate.
|
||||
|
@ -39,6 +39,9 @@ static constexpr const Slic3r::ClipperLib::JoinType DefaultLineJoinType = Sl
|
||||
// Miter limit is ignored for jtSquare.
|
||||
static constexpr const double DefaultLineMiterLimit = 0.;
|
||||
|
||||
// Decimation factor applied on input contour when doing offset, multiplied by the offset distance.
|
||||
static constexpr const double ClipperOffsetShortestEdgeFactor = 0.005;
|
||||
|
||||
enum class ApplySafetyOffset {
|
||||
No,
|
||||
Yes
|
||||
@ -370,6 +373,8 @@ inline Slic3r::Polygons shrink(const Slic3r::Polygons &polygons, const float d
|
||||
{ assert(delta > 0); return offset(polygons, -delta, joinType, miterLimit); }
|
||||
inline Slic3r::ExPolygons shrink_ex(const Slic3r::Polygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ assert(delta > 0); return offset_ex(polygons, -delta, joinType, miterLimit); }
|
||||
inline Slic3r::ExPolygons shrink_ex(const Slic3r::ExPolygons &polygons, const float delta, ClipperLib::JoinType joinType = DefaultJoinType, double miterLimit = DefaultMiterLimit)
|
||||
{ assert(delta > 0); return offset_ex(polygons, -delta, joinType, miterLimit); }
|
||||
|
||||
// Wherever applicable, please use the opening() / closing() variants instead, they convey their purpose better.
|
||||
// Input polygons for negative offset shall be "normalized": There must be no overlap / intersections between the input polygons.
|
||||
@ -430,6 +435,7 @@ Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::ExPoly
|
||||
Slic3r::ExPolygons diff_ex(const Slic3r::ExPolygons &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfaces &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::ExPolygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No);
|
||||
Slic3r::Polylines diff_pl(const Slic3r::Polyline &subject, const Slic3r::Polygons &clip);
|
||||
Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip);
|
||||
Slic3r::Polylines diff_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygon &clip);
|
||||
@ -601,6 +607,6 @@ Polygons variable_offset_outer(const ExPolygon &expoly, const std::vector<std::
|
||||
ExPolygons variable_offset_outer_ex(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit = 2.);
|
||||
ExPolygons variable_offset_inner_ex(const ExPolygon &expoly, const std::vector<std::vector<float>> &deltas, double miter_limit = 2.);
|
||||
|
||||
}
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif
|
||||
#endif // slic3r_ClipperUtils_hpp_
|
||||
|
143
src/libslic3r/ClipperZUtils.hpp
Normal file
143
src/libslic3r/ClipperZUtils.hpp
Normal 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_
|
@ -303,7 +303,7 @@ template <class T>
|
||||
class ConfigOptionSingle : public ConfigOption {
|
||||
public:
|
||||
T value;
|
||||
explicit ConfigOptionSingle(T value) : value(value) {}
|
||||
explicit ConfigOptionSingle(T value) : value(std::move(value)) {}
|
||||
operator T() const { return this->value; }
|
||||
|
||||
void set(const ConfigOption *rhs) override
|
||||
@ -847,9 +847,9 @@ using ConfigOptionIntsNullable = ConfigOptionIntsTempl<true>;
|
||||
class ConfigOptionString : public ConfigOptionSingle<std::string>
|
||||
{
|
||||
public:
|
||||
ConfigOptionString() : ConfigOptionSingle<std::string>("") {}
|
||||
explicit ConfigOptionString(const std::string &value) : ConfigOptionSingle<std::string>(value) {}
|
||||
|
||||
ConfigOptionString() : ConfigOptionSingle<std::string>(std::string{}) {}
|
||||
explicit ConfigOptionString(std::string value) : ConfigOptionSingle<std::string>(std::move(value)) {}
|
||||
|
||||
static ConfigOptionType static_type() { return coString; }
|
||||
ConfigOptionType type() const override { return static_type(); }
|
||||
ConfigOption* clone() const override { return new ConfigOptionString(*this); }
|
||||
|
@ -15,10 +15,12 @@
|
||||
#include "FillRectilinear.hpp"
|
||||
#include "FillLightning.hpp"
|
||||
#include "FillConcentric.hpp"
|
||||
|
||||
#include "FillEnsuring.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
static constexpr const float NarrowInfillAreaThresholdMM = 3.f;
|
||||
|
||||
struct SurfaceFillParams
|
||||
{
|
||||
// Zero based extruder ID.
|
||||
@ -159,7 +161,8 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
|
||||
// Calculate the actual flow we'll be using for this infill.
|
||||
params.bridge = is_bridge || Fill::use_bridge_flow(params.pattern);
|
||||
params.flow = params.bridge ?
|
||||
layerm.bridging_flow(extrusion_role) :
|
||||
// Always enable thick bridges for internal bridges.
|
||||
layerm.bridging_flow(extrusion_role, surface.is_bridge() && ! surface.is_external()) :
|
||||
layerm.flow(extrusion_role, (surface.thickness == -1) ? layer.height : surface.thickness);
|
||||
|
||||
// Calculate flow spacing for infill pattern generation.
|
||||
@ -302,6 +305,46 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
|
||||
}
|
||||
}
|
||||
|
||||
// Detect narrow internal solid infill area and use ipEnsuring pattern instead.
|
||||
{
|
||||
std::vector<char> narrow_expolygons;
|
||||
static constexpr const auto narrow_pattern = ipEnsuring;
|
||||
for (size_t surface_fill_id = 0, num_old_fills = surface_fills.size(); surface_fill_id < num_old_fills; ++ surface_fill_id)
|
||||
if (SurfaceFill &fill = surface_fills[surface_fill_id]; fill.surface.surface_type == stInternalSolid) {
|
||||
size_t num_expolygons = fill.expolygons.size();
|
||||
narrow_expolygons.clear();
|
||||
narrow_expolygons.reserve(num_expolygons);
|
||||
// Detect narrow expolygons.
|
||||
int num_narrow = 0;
|
||||
for (const ExPolygon &ex : fill.expolygons) {
|
||||
bool narrow = offset_ex(ex, -scaled<float>(NarrowInfillAreaThresholdMM)).empty();
|
||||
num_narrow += int(narrow);
|
||||
narrow_expolygons.emplace_back(narrow);
|
||||
}
|
||||
if (num_narrow == num_expolygons) {
|
||||
// All expolygons are narrow, change the fill pattern.
|
||||
fill.params.pattern = narrow_pattern;
|
||||
} else if (num_narrow > 0) {
|
||||
// Some expolygons are narrow, split the fills.
|
||||
params = fill.params;
|
||||
params.pattern = narrow_pattern;
|
||||
surface_fills.emplace_back(params);
|
||||
SurfaceFill &old_fill = surface_fills[surface_fill_id];
|
||||
SurfaceFill &new_fill = surface_fills.back();
|
||||
new_fill.region_id = old_fill.region_id;
|
||||
new_fill.surface.surface_type = stInternalSolid;
|
||||
new_fill.surface.thickness = old_fill.surface.thickness;
|
||||
new_fill.expolygons.reserve(num_narrow);
|
||||
for (size_t i = 0; i < narrow_expolygons.size(); ++ i)
|
||||
if (narrow_expolygons[i])
|
||||
new_fill.expolygons.emplace_back(std::move(old_fill.expolygons[i]));
|
||||
old_fill.expolygons.erase(std::remove_if(old_fill.expolygons.begin(), old_fill.expolygons.end(),
|
||||
[&narrow_expolygons, ex_first = old_fill.expolygons.data()](const ExPolygon& ex) { return narrow_expolygons[&ex - ex_first]; }),
|
||||
old_fill.expolygons.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return surface_fills;
|
||||
}
|
||||
|
||||
@ -443,6 +486,14 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
||||
|
||||
size_t first_object_layer_id = this->object()->get_layer(0)->id();
|
||||
for (SurfaceFill &surface_fill : surface_fills) {
|
||||
//skip patterns for which additional input is nullptr
|
||||
switch (surface_fill.params.pattern) {
|
||||
case ipLightning: if (lightning_generator == nullptr) continue; break;
|
||||
case ipAdaptiveCubic: if (adaptive_fill_octree == nullptr) continue; break;
|
||||
case ipSupportCubic: if (support_fill_octree == nullptr) continue; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
// Create the filler object.
|
||||
std::unique_ptr<Fill> f = std::unique_ptr<Fill>(Fill::new_from_type(surface_fill.params.pattern));
|
||||
f->set_bounding_box(bbox);
|
||||
@ -452,7 +503,9 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
||||
f->layer_id = this->id() - first_object_layer_id;
|
||||
f->z = this->print_z;
|
||||
f->angle = surface_fill.params.angle;
|
||||
f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree;
|
||||
f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree;
|
||||
f->print_config = &this->object()->print()->config();
|
||||
f->print_object_config = &this->object()->config();
|
||||
|
||||
if (surface_fill.params.pattern == ipLightning) {
|
||||
auto *lf = dynamic_cast<FillLightning::Filler*>(f.get());
|
||||
@ -460,11 +513,10 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
||||
lf->num_raft_layers = this->object()->slicing_parameters().raft_layers();
|
||||
}
|
||||
|
||||
if (perimeter_generator.value == PerimeterGeneratorType::Arachne && surface_fill.params.pattern == ipConcentric) {
|
||||
FillConcentric *fill_concentric = dynamic_cast<FillConcentric *>(f.get());
|
||||
assert(fill_concentric != nullptr);
|
||||
fill_concentric->print_config = &this->object()->print()->config();
|
||||
fill_concentric->print_object_config = &this->object()->config();
|
||||
if (surface_fill.params.pattern == ipEnsuring) {
|
||||
auto *fill_bounded_rectilinear = dynamic_cast<FillEnsuring *>(f.get());
|
||||
assert(fill_bounded_rectilinear != nullptr);
|
||||
fill_bounded_rectilinear->print_region_config = &m_regions[surface_fill.region_id]->region().config();
|
||||
}
|
||||
|
||||
// calculate flow spacing for infill pattern generation
|
||||
@ -494,7 +546,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
||||
params.anchor_length = surface_fill.params.anchor_length;
|
||||
params.anchor_length_max = surface_fill.params.anchor_length_max;
|
||||
params.resolution = resolution;
|
||||
params.use_arachne = perimeter_generator == PerimeterGeneratorType::Arachne && surface_fill.params.pattern == ipConcentric;
|
||||
params.use_arachne = (perimeter_generator == PerimeterGeneratorType::Arachne && surface_fill.params.pattern == ipConcentric) || surface_fill.params.pattern == ipEnsuring;
|
||||
params.layer_height = layerm.layer()->height;
|
||||
|
||||
for (ExPolygon &expoly : surface_fill.expolygons) {
|
||||
@ -595,6 +647,94 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
||||
#endif
|
||||
}
|
||||
|
||||
Polylines Layer::generate_sparse_infill_polylines_for_anchoring() const
|
||||
{
|
||||
std::vector<SurfaceFill> surface_fills = group_fills(*this);
|
||||
const Slic3r::BoundingBox bbox = this->object()->bounding_box();
|
||||
const auto resolution = this->object()->print()->config().gcode_resolution.value;
|
||||
|
||||
Polylines sparse_infill_polylines{};
|
||||
|
||||
for (SurfaceFill &surface_fill : surface_fills) {
|
||||
// skip patterns for which additional input is nullptr
|
||||
switch (surface_fill.params.pattern) {
|
||||
case ipLightning: continue; break;
|
||||
case ipAdaptiveCubic: continue; break;
|
||||
case ipSupportCubic: continue; break;
|
||||
case ipCount: continue; break;
|
||||
case ipSupportBase: continue; break;
|
||||
case ipEnsuring: continue; break;
|
||||
case ipRectilinear:
|
||||
case ipMonotonic:
|
||||
case ipMonotonicLines:
|
||||
case ipAlignedRectilinear:
|
||||
case ipGrid:
|
||||
case ipTriangles:
|
||||
case ipStars:
|
||||
case ipCubic:
|
||||
case ipLine:
|
||||
case ipConcentric:
|
||||
case ipHoneycomb:
|
||||
case ip3DHoneycomb:
|
||||
case ipGyroid:
|
||||
case ipHilbertCurve:
|
||||
case ipArchimedeanChords:
|
||||
case ipOctagramSpiral: break;
|
||||
}
|
||||
|
||||
// Create the filler object.
|
||||
std::unique_ptr<Fill> f = std::unique_ptr<Fill>(Fill::new_from_type(surface_fill.params.pattern));
|
||||
f->set_bounding_box(bbox);
|
||||
f->layer_id = this->id();
|
||||
f->z = this->print_z;
|
||||
f->angle = surface_fill.params.angle;
|
||||
// f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree;
|
||||
f->print_config = &this->object()->print()->config();
|
||||
f->print_object_config = &this->object()->config();
|
||||
|
||||
// calculate flow spacing for infill pattern generation
|
||||
double link_max_length = 0.;
|
||||
if (!surface_fill.params.bridge) {
|
||||
#if 0
|
||||
link_max_length = layerm.region()->config().get_abs_value(surface.is_external() ? "external_fill_link_max_length" : "fill_link_max_length", flow.spacing());
|
||||
// printf("flow spacing: %f, is_external: %d, link_max_length: %lf\n", flow.spacing(), int(surface.is_external()), link_max_length);
|
||||
#else
|
||||
if (surface_fill.params.density > 80.) // 80%
|
||||
link_max_length = 3. * f->spacing;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Maximum length of the perimeter segment linking two infill lines.
|
||||
f->link_max_length = (coord_t) scale_(link_max_length);
|
||||
// Used by the concentric infill pattern to clip the loops to create extrusion paths.
|
||||
f->loop_clipping = coord_t(scale_(surface_fill.params.flow.nozzle_diameter()) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER);
|
||||
|
||||
LayerRegion &layerm = *m_regions[surface_fill.region_id];
|
||||
|
||||
// apply half spacing using this flow's own spacing and generate infill
|
||||
FillParams params;
|
||||
params.density = float(0.01 * surface_fill.params.density);
|
||||
params.dont_adjust = false; // surface_fill.params.dont_adjust;
|
||||
params.anchor_length = surface_fill.params.anchor_length;
|
||||
params.anchor_length_max = surface_fill.params.anchor_length_max;
|
||||
params.resolution = resolution;
|
||||
params.use_arachne = false;
|
||||
params.layer_height = layerm.layer()->height;
|
||||
|
||||
for (ExPolygon &expoly : surface_fill.expolygons) {
|
||||
// Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon.
|
||||
f->spacing = surface_fill.params.spacing;
|
||||
surface_fill.surface.expolygon = std::move(expoly);
|
||||
try {
|
||||
Polylines polylines = f->fill_surface(&surface_fill.surface, params);
|
||||
sparse_infill_polylines.insert(sparse_infill_polylines.end(), polylines.begin(), polylines.end());
|
||||
} catch (InfillFailedException &) {}
|
||||
}
|
||||
}
|
||||
|
||||
return sparse_infill_polylines;
|
||||
}
|
||||
|
||||
// Create ironing extrusions over top surfaces.
|
||||
void Layer::make_ironing()
|
||||
{
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "FillRectilinear.hpp"
|
||||
#include "FillAdaptive.hpp"
|
||||
#include "FillLightning.hpp"
|
||||
#include "FillEnsuring.hpp"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
@ -50,6 +51,7 @@ Fill* Fill::new_from_type(const InfillPattern type)
|
||||
case ipSupportCubic: return new FillAdaptive::Filler();
|
||||
case ipSupportBase: return new FillSupportBase();
|
||||
case ipLightning: return new FillLightning::Filler();
|
||||
case ipEnsuring: return new FillEnsuring();
|
||||
default: throw Slic3r::InvalidArgument("unknown type");
|
||||
}
|
||||
}
|
||||
|
@ -91,6 +91,10 @@ public:
|
||||
// Octree builds on mesh for usage in the adaptive cubic infill
|
||||
FillAdaptive::Octree* adapt_fill_octree = nullptr;
|
||||
|
||||
// PrintConfig and PrintObjectConfig are used by infills that use Arachne (Concentric and FillEnsuring).
|
||||
const PrintConfig *print_config = nullptr;
|
||||
const PrintObjectConfig *print_object_config = nullptr;
|
||||
|
||||
public:
|
||||
virtual ~Fill() {}
|
||||
virtual Fill* clone() const = 0;
|
||||
|
@ -97,14 +97,8 @@ void FillConcentric::_fill_surface_single(const FillParams ¶ms,
|
||||
continue;
|
||||
|
||||
ThickPolyline thick_polyline = Arachne::to_thick_polyline(*extrusion);
|
||||
if (extrusion->is_closed && thick_polyline.points.front() == thick_polyline.points.back() && thick_polyline.width.front() == thick_polyline.width.back()) {
|
||||
thick_polyline.points.pop_back();
|
||||
assert(thick_polyline.points.size() * 2 == thick_polyline.width.size());
|
||||
int nearest_idx = nearest_point_index(thick_polyline.points, last_pos);
|
||||
std::rotate(thick_polyline.points.begin(), thick_polyline.points.begin() + nearest_idx, thick_polyline.points.end());
|
||||
std::rotate(thick_polyline.width.begin(), thick_polyline.width.begin() + 2 * nearest_idx, thick_polyline.width.end());
|
||||
thick_polyline.points.emplace_back(thick_polyline.points.front());
|
||||
}
|
||||
if (extrusion->is_closed)
|
||||
thick_polyline.start_at_index(nearest_point_index(thick_polyline.points, last_pos));
|
||||
thick_polylines_out.emplace_back(std::move(thick_polyline));
|
||||
last_pos = thick_polylines_out.back().last_point();
|
||||
}
|
||||
|
@ -26,11 +26,6 @@ protected:
|
||||
ThickPolylines &thick_polylines_out) override;
|
||||
|
||||
bool no_sort() const override { return true; }
|
||||
|
||||
const PrintConfig *print_config = nullptr;
|
||||
const PrintObjectConfig *print_object_config = nullptr;
|
||||
|
||||
friend class Layer;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
83
src/libslic3r/Fill/FillEnsuring.cpp
Normal file
83
src/libslic3r/Fill/FillEnsuring.cpp
Normal 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 ¶ms)
|
||||
{
|
||||
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
|
30
src/libslic3r/Fill/FillEnsuring.hpp
Normal file
30
src/libslic3r/Fill/FillEnsuring.hpp
Normal 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 ¶ms) override { return {}; };
|
||||
ThickPolylines fill_surface_arachne(const Surface *surface, const FillParams ¶ms) override;
|
||||
|
||||
protected:
|
||||
void fill_surface_single_arachne(const Surface &surface, const FillParams ¶ms, 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_
|
@ -7,6 +7,7 @@
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class PrintRegionConfig;
|
||||
class Surface;
|
||||
|
||||
class FillRectilinear : public Fill
|
||||
|
@ -378,7 +378,8 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
|
||||
size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') :
|
||||
(*c == extrusion_axis) ? 3 : (*c == 'F') ? 4 : size_t(-1);
|
||||
if (axis != size_t(-1)) {
|
||||
auto [pend, ec] = fast_float::from_chars(&*(++ c), sline.data() + sline.size(), new_pos[axis]);
|
||||
//auto [pend, ec] =
|
||||
fast_float::from_chars(&*(++ c), sline.data() + sline.size(), new_pos[axis]);
|
||||
if (axis == 4) {
|
||||
// Convert mm/min to mm/sec.
|
||||
new_pos[4] /= 60.f;
|
||||
@ -497,7 +498,8 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
|
||||
bool has_S = pos_S > 0;
|
||||
bool has_P = pos_P > 0;
|
||||
if (has_S || has_P) {
|
||||
auto [pend, ec] = fast_float::from_chars(sline.data() + (has_S ? pos_S : pos_P) + 1, sline.data() + sline.size(), line.time);
|
||||
//auto [pend, ec] =
|
||||
fast_float::from_chars(sline.data() + (has_S ? pos_S : pos_P) + 1, sline.data() + sline.size(), line.time);
|
||||
if (has_P)
|
||||
line.time *= 0.001f;
|
||||
} else
|
||||
@ -846,7 +848,8 @@ std::string CoolingBuffer::apply_layer_cooldown(
|
||||
if (line->slowdown)
|
||||
new_feedrate = int(floor(60. * line->feedrate + 0.5));
|
||||
else
|
||||
auto res = std::from_chars(fpos, line_end, new_feedrate);
|
||||
//auto res =
|
||||
std::from_chars(fpos, line_end, new_feedrate);
|
||||
if (new_feedrate == current_feedrate) {
|
||||
// No need to change the F value.
|
||||
if ((line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_ADJUSTABLE_EMPTY | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) || line->length == 0.)
|
||||
|
@ -19,20 +19,7 @@ bool RetractWhenCrossingPerimeters::travel_inside_internal_regions(const Layer &
|
||||
if (surface.is_internal())
|
||||
m_internal_islands.emplace_back(&surface.expolygon);
|
||||
// Calculate bounding boxes of internal slices.
|
||||
class BBoxWrapper {
|
||||
public:
|
||||
BBoxWrapper(const size_t idx, const BoundingBox &bbox) :
|
||||
m_idx(idx),
|
||||
// Inflate the bounding box a bit to account for numerical issues.
|
||||
m_bbox(bbox.min - Point(SCALED_EPSILON, SCALED_EPSILON), bbox.max + Point(SCALED_EPSILON, SCALED_EPSILON)) {}
|
||||
size_t idx() const { return m_idx; }
|
||||
const AABBTree::BoundingBox& bbox() const { return m_bbox; }
|
||||
Point centroid() const { return ((m_bbox.min().cast<int64_t>() + m_bbox.max().cast<int64_t>()) / 2).cast<int32_t>(); }
|
||||
private:
|
||||
size_t m_idx;
|
||||
AABBTree::BoundingBox m_bbox;
|
||||
};
|
||||
std::vector<BBoxWrapper> bboxes;
|
||||
std::vector<AABBTreeIndirect::BoundingBoxWrapper> bboxes;
|
||||
bboxes.reserve(m_internal_islands.size());
|
||||
for (size_t i = 0; i < m_internal_islands.size(); ++ i)
|
||||
bboxes.emplace_back(i, get_extents(*m_internal_islands[i]));
|
||||
|
@ -468,9 +468,6 @@ void MedialAxis::build(ThickPolylines* polylines)
|
||||
}
|
||||
*/
|
||||
|
||||
//typedef const VD::vertex_type vert_t;
|
||||
using edge_t = const VD::edge_type;
|
||||
|
||||
// collect valid edges (i.e. prune those not belonging to MAT)
|
||||
// note: this keeps twins, so it inserts twice the number of the valid edges
|
||||
m_edge_data.assign(m_vd.edges().size() / 2, EdgeData{});
|
||||
|
@ -1,5 +1,5 @@
|
||||
#include "Layer.hpp"
|
||||
#include <clipper/clipper_z.hpp>
|
||||
#include "ClipperZUtils.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Print.hpp"
|
||||
#include "Fill/Fill.hpp"
|
||||
@ -302,48 +302,16 @@ void Layer::build_up_down_graph(Layer& below, Layer& above)
|
||||
coord_t paths_end = paths_above_offset + coord_t(above.lslices.size());
|
||||
#endif // NDEBUG
|
||||
|
||||
class ZFill {
|
||||
public:
|
||||
ZFill() = default;
|
||||
void reset() { m_intersections.clear(); }
|
||||
void operator()(
|
||||
const ClipperLib_Z::IntPoint& e1bot, const ClipperLib_Z::IntPoint& e1top,
|
||||
const ClipperLib_Z::IntPoint& e2bot, const ClipperLib_Z::IntPoint& e2top,
|
||||
ClipperLib_Z::IntPoint& pt) {
|
||||
coord_t srcs[4]{ e1bot.z(), e1top.z(), e2bot.z(), e2top.z() };
|
||||
coord_t* begin = srcs;
|
||||
coord_t* end = srcs + 4;
|
||||
std::sort(begin, end);
|
||||
end = std::unique(begin, end);
|
||||
if (begin + 1 == end) {
|
||||
// Self intersection may happen on source contour. Just copy the Z value.
|
||||
pt.z() = *begin;
|
||||
} else {
|
||||
assert(begin + 2 == end);
|
||||
if (begin + 2 <= end) {
|
||||
// store a -1 based negative index into the "intersections" vector here.
|
||||
m_intersections.emplace_back(srcs[0], srcs[1]);
|
||||
pt.z() = -coord_t(m_intersections.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
const std::vector<std::pair<coord_t, coord_t>>& intersections() const { return m_intersections; }
|
||||
|
||||
private:
|
||||
std::vector<std::pair<coord_t, coord_t>> m_intersections;
|
||||
} zfill;
|
||||
|
||||
ClipperLib_Z::Clipper clipper;
|
||||
ClipperLib_Z::PolyTree result;
|
||||
clipper.ZFillFunction(
|
||||
[&zfill](const ClipperLib_Z::IntPoint &e1bot, const ClipperLib_Z::IntPoint &e1top,
|
||||
const ClipperLib_Z::IntPoint &e2bot, const ClipperLib_Z::IntPoint &e2top, ClipperLib_Z::IntPoint &pt)
|
||||
{ return zfill(e1bot, e1top, e2bot, e2top, pt); });
|
||||
ClipperZUtils::ClipperZIntersectionVisitor::Intersections intersections;
|
||||
ClipperZUtils::ClipperZIntersectionVisitor visitor(intersections);
|
||||
clipper.ZFillFunction(visitor.clipper_callback());
|
||||
clipper.AddPaths(paths_below, ClipperLib_Z::ptSubject, true);
|
||||
clipper.AddPaths(paths_above, ClipperLib_Z::ptClip, true);
|
||||
clipper.Execute(ClipperLib_Z::ctIntersection, result, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
|
||||
|
||||
connect_layer_slices(below, above, result, zfill.intersections(), paths_below_offset, paths_above_offset
|
||||
connect_layer_slices(below, above, result, intersections, paths_below_offset, paths_above_offset
|
||||
#ifndef NDEBUG
|
||||
, paths_end
|
||||
#endif // NDEBUG
|
||||
|
@ -134,7 +134,7 @@ public:
|
||||
|
||||
Flow flow(FlowRole role) const;
|
||||
Flow flow(FlowRole role, double layer_height) const;
|
||||
Flow bridging_flow(FlowRole role) const;
|
||||
Flow bridging_flow(FlowRole role, bool force_thick_bridges = false) const;
|
||||
|
||||
void slices_to_fill_surfaces_clipped();
|
||||
void prepare_fill_surfaces();
|
||||
@ -369,6 +369,7 @@ public:
|
||||
// Phony version of make_fills() without parameters for Perl integration only.
|
||||
void make_fills() { this->make_fills(nullptr, nullptr, nullptr); }
|
||||
void make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator);
|
||||
Polylines generate_sparse_infill_polylines_for_anchoring() const;
|
||||
void make_ironing();
|
||||
|
||||
void export_region_slices_to_svg(const char *path) const;
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "Surface.hpp"
|
||||
#include "BoundingBox.hpp"
|
||||
#include "SVG.hpp"
|
||||
#include "Algorithm/RegionExpansion.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
@ -26,12 +27,12 @@ Flow LayerRegion::flow(FlowRole role, double layer_height) const
|
||||
return m_region->flow(*m_layer->object(), role, layer_height, m_layer->id() == 0);
|
||||
}
|
||||
|
||||
Flow LayerRegion::bridging_flow(FlowRole role) const
|
||||
Flow LayerRegion::bridging_flow(FlowRole role, bool force_thick_bridges) const
|
||||
{
|
||||
const PrintRegion ®ion = this->region();
|
||||
const PrintRegionConfig ®ion_config = region.config();
|
||||
const PrintObject &print_object = *this->layer()->object();
|
||||
if (print_object.config().thick_bridges) {
|
||||
if (print_object.config().thick_bridges || force_thick_bridges) {
|
||||
// The old Slic3r way (different from all other slicers): Use rounded extrusions.
|
||||
// Get the configured nozzle_diameter for the extruder associated to the flow role requested.
|
||||
// Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will follback to zero'th element, so everything is all right.
|
||||
@ -139,6 +140,273 @@ void LayerRegion::make_perimeters(
|
||||
}
|
||||
}
|
||||
|
||||
#if 1
|
||||
|
||||
// Extract surfaces of given type from surfaces, extract fill (layer) thickness of one of the surfaces.
|
||||
static ExPolygons fill_surfaces_extract_expolygons(Surfaces &surfaces, SurfaceType surface_type, double &thickness)
|
||||
{
|
||||
size_t cnt = 0;
|
||||
for (const Surface &surface : surfaces)
|
||||
if (surface.surface_type == surface_type) {
|
||||
++ cnt;
|
||||
thickness = surface.thickness;
|
||||
}
|
||||
if (cnt == 0)
|
||||
return {};
|
||||
|
||||
ExPolygons out;
|
||||
out.reserve(cnt);
|
||||
for (Surface &surface : surfaces)
|
||||
if (surface.surface_type == surface_type)
|
||||
out.emplace_back(std::move(surface.expolygon));
|
||||
return out;
|
||||
}
|
||||
|
||||
// Extract bridging surfaces from "surfaces", expand them into "shells" using expansion_params,
|
||||
// detect bridges.
|
||||
// Trim "shells" by the expanded bridges.
|
||||
Surfaces expand_bridges_detect_orientations(
|
||||
Surfaces &surfaces,
|
||||
ExPolygons &shells,
|
||||
const Algorithm::RegionExpansionParameters &expansion_params)
|
||||
{
|
||||
using namespace Slic3r::Algorithm;
|
||||
|
||||
double thickness;
|
||||
ExPolygons bridges_ex = fill_surfaces_extract_expolygons(surfaces, stBottomBridge, thickness);
|
||||
if (bridges_ex.empty())
|
||||
return {};
|
||||
|
||||
// Calculate bridge anchors and their expansions in their respective shell region.
|
||||
WaveSeeds bridge_anchors = wave_seeds(bridges_ex, shells, expansion_params.tiny_expansion, true);
|
||||
std::vector<RegionExpansionEx> bridge_expansions = propagate_waves_ex(bridge_anchors, shells, expansion_params);
|
||||
|
||||
// Cache for detecting bridge orientation and merging regions with overlapping expansions.
|
||||
struct Bridge {
|
||||
ExPolygon expolygon;
|
||||
uint32_t group_id;
|
||||
std::vector<RegionExpansionEx>::const_iterator bridge_expansion_begin;
|
||||
double angle = -1;
|
||||
};
|
||||
std::vector<Bridge> bridges;
|
||||
{
|
||||
bridges.reserve(bridges_ex.size());
|
||||
uint32_t group_id = 0;
|
||||
for (ExPolygon &ex : bridges_ex)
|
||||
bridges.push_back({ std::move(ex), group_id ++, bridge_expansions.end() });
|
||||
bridges_ex.clear();
|
||||
}
|
||||
|
||||
// Group the bridge surfaces by overlaps.
|
||||
auto group_id = [&bridges](uint32_t src_id) {
|
||||
uint32_t group_id = bridges[src_id].group_id;
|
||||
while (group_id != src_id) {
|
||||
src_id = group_id;
|
||||
group_id = bridges[src_id].group_id;
|
||||
}
|
||||
bridges[src_id].group_id = group_id;
|
||||
return group_id;
|
||||
};
|
||||
|
||||
{
|
||||
// Cache of bboxes per expansion boundary.
|
||||
std::vector<BoundingBox> bboxes;
|
||||
// Detect overlaps of bridge anchors inside their respective shell regions.
|
||||
// bridge_expansions are sorted by boundary id and source id.
|
||||
for (auto it = bridge_expansions.begin(); it != bridge_expansions.end();) {
|
||||
// For each boundary region:
|
||||
auto it_begin = it;
|
||||
auto it_end = std::next(it_begin);
|
||||
for (; it_end != bridge_expansions.end() && it_end->boundary_id == it_begin->boundary_id; ++ it_end) ;
|
||||
bboxes.clear();
|
||||
bboxes.reserve(it_end - it_begin);
|
||||
for (auto it2 = it_begin; it2 != it_end; ++ it2)
|
||||
bboxes.emplace_back(get_extents(it2->expolygon.contour));
|
||||
// For each bridge anchor of the current source:
|
||||
for (; it != it_end; ++ it) {
|
||||
// A grup id for this bridge.
|
||||
for (auto it2 = std::next(it); it2 != it_end; ++ it2)
|
||||
if (it->src_id != it2->src_id &&
|
||||
bboxes[it - it_begin].overlap(bboxes[it2 - it_begin]) &&
|
||||
// One may ignore holes, they are irrelevant for intersection test.
|
||||
! intersection(it->expolygon.contour, it2->expolygon.contour).empty()) {
|
||||
// The two bridge regions intersect. Give them the same group id.
|
||||
uint32_t id = group_id(it->src_id);
|
||||
uint32_t id2 = group_id(it2->src_id);
|
||||
bridges[it->src_id].group_id = bridges[it2->src_id].group_id = std::min(id, id2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Detect bridge directions.
|
||||
{
|
||||
std::sort(bridge_anchors.begin(), bridge_anchors.end(), Algorithm::lower_by_src_and_boundary);
|
||||
auto it_bridge_anchor = bridge_anchors.begin();
|
||||
Lines lines;
|
||||
Polygons anchor_areas;
|
||||
for (uint32_t bridge_id = 0; bridge_id < uint32_t(bridges.size()); ++ bridge_id) {
|
||||
Bridge &bridge = bridges[bridge_id];
|
||||
// lines.clear();
|
||||
anchor_areas.clear();
|
||||
int32_t last_anchor_id = -1;
|
||||
for (; it_bridge_anchor != bridge_anchors.end() && it_bridge_anchor->src == bridge_id; ++ it_bridge_anchor) {
|
||||
if (last_anchor_id != int(it_bridge_anchor->boundary)) {
|
||||
last_anchor_id = int(it_bridge_anchor->boundary);
|
||||
append(anchor_areas, to_polygons(shells[last_anchor_id]));
|
||||
}
|
||||
// if (Points &polyline = it_bridge_anchor->path; polyline.size() >= 2) {
|
||||
// reserve_more_power_of_2(lines, polyline.size() - 1);
|
||||
// for (size_t i = 1; i < polyline.size(); ++ i)
|
||||
// lines.push_back({ polyline[i - 1], polyline[1] });
|
||||
// }
|
||||
}
|
||||
lines = to_lines(diff_pl(to_polylines(bridge.expolygon), expand(anchor_areas, float(SCALED_EPSILON))));
|
||||
auto [bridging_dir, unsupported_dist] = detect_bridging_direction(lines, to_polygons(bridge.expolygon));
|
||||
bridge.angle = M_PI + std::atan2(bridging_dir.y(), bridging_dir.x());
|
||||
// #if 1
|
||||
// coordf_t stroke_width = scale_(0.06);
|
||||
// BoundingBox bbox = get_extents(initial);
|
||||
// bbox.offset(scale_(1.));
|
||||
// ::Slic3r::SVG
|
||||
// svg(debug_out_path(("bridge"+std::to_string(bridges[idx_last].bridge_angle)+"_"+std::to_string(this->layer()->bottom_z())).c_str()),
|
||||
// bbox);
|
||||
|
||||
// svg.draw(initial, "cyan");
|
||||
// svg.draw(to_lines(lower_layer->lslices), "green", stroke_width);
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
|
||||
// Merge the groups with the same group id, produce surfaces by merging source overhangs with their newly expanded anchors.
|
||||
Surfaces out;
|
||||
{
|
||||
Polygons acc;
|
||||
Surface templ{ stBottomBridge, {} };
|
||||
std::sort(bridge_expansions.begin(), bridge_expansions.end(), [](auto &l, auto &r) {
|
||||
return l.src_id < r.src_id || (l.src_id == r.src_id && l.boundary_id < r.boundary_id);
|
||||
});
|
||||
for (auto it = bridge_expansions.begin(); it != bridge_expansions.end(); ) {
|
||||
bridges[it->src_id].bridge_expansion_begin = it;
|
||||
uint32_t src_id = it->src_id;
|
||||
for (++ it; it != bridge_expansions.end() && it->src_id == src_id; ++ it) ;
|
||||
}
|
||||
for (uint32_t bridge_id = 0; bridge_id < uint32_t(bridges.size()); ++ bridge_id) {
|
||||
acc.clear();
|
||||
for (uint32_t bridge_id2 = bridge_id; bridge_id2 < uint32_t(bridges.size()); ++ bridge_id2)
|
||||
if (group_id(bridge_id) == bridge_id) {
|
||||
append(acc, to_polygons(std::move(bridges[bridge_id2].expolygon)));
|
||||
auto it_bridge_expansion = bridges[bridge_id2].bridge_expansion_begin;
|
||||
assert(it_bridge_expansion == bridge_expansions.end() || it_bridge_expansion->src_id == bridge_id2);
|
||||
for (; it_bridge_expansion != bridge_expansions.end() && it_bridge_expansion->src_id == bridge_id2; ++ it_bridge_expansion)
|
||||
append(acc, to_polygons(std::move(it_bridge_expansion->expolygon)));
|
||||
}
|
||||
//FIXME try to be smart and pick the best bridging angle for all?
|
||||
templ.bridge_angle = bridges[bridge_id].angle;
|
||||
// without safety offset, artifacts are generated (GH #2494)
|
||||
for (ExPolygon &ex : union_safety_offset_ex(acc))
|
||||
out.emplace_back(templ, std::move(ex));
|
||||
}
|
||||
}
|
||||
|
||||
// Clip the shells by the expanded bridges.
|
||||
shells = diff_ex(shells, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Extract bridging surfaces from "surfaces", expand them into "shells" using expansion_params.
|
||||
// Trim "shells" by the expanded bridges.
|
||||
static Surfaces expand_merge_surfaces(
|
||||
Surfaces &surfaces,
|
||||
SurfaceType surface_type,
|
||||
ExPolygons &shells,
|
||||
const Algorithm::RegionExpansionParameters ¶ms,
|
||||
const double bridge_angle = -1.)
|
||||
{
|
||||
double thickness;
|
||||
ExPolygons src = fill_surfaces_extract_expolygons(surfaces, surface_type, thickness);
|
||||
if (src.empty())
|
||||
return {};
|
||||
|
||||
std::vector<ExPolygon> expanded = expand_merge_expolygons(std::move(src), shells, params);
|
||||
// Trim the shells by the expanded expolygons.
|
||||
shells = diff_ex(shells, expanded);
|
||||
|
||||
Surface templ{ surface_type, {} };
|
||||
templ.bridge_angle = bridge_angle;
|
||||
Surfaces out;
|
||||
out.reserve(expanded.size());
|
||||
for (auto &expoly : expanded)
|
||||
out.emplace_back(templ, std::move(expoly));
|
||||
return out;
|
||||
}
|
||||
|
||||
void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered)
|
||||
{
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-initial");
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
|
||||
// Width of the perimeters.
|
||||
float shell_width = 0;
|
||||
if (int num_perimeters = this->region().config().perimeters; num_perimeters > 0) {
|
||||
Flow external_perimeter_flow = this->flow(frExternalPerimeter);
|
||||
Flow perimeter_flow = this->flow(frPerimeter);
|
||||
shell_width += 0.5f * external_perimeter_flow.scaled_width() + external_perimeter_flow.scaled_spacing();
|
||||
shell_width += perimeter_flow.scaled_spacing() * (num_perimeters - 1);
|
||||
} else {
|
||||
}
|
||||
|
||||
// Scaled expansions of the respective external surfaces.
|
||||
float expansion_top = shell_width * sqrt(2.);
|
||||
float expansion_bottom = expansion_top;
|
||||
float expansion_bottom_bridge = expansion_top;
|
||||
// Expand by waves of expansion_step size (expansion_step is scaled), but with no more steps than max_nr_expansion_steps.
|
||||
static constexpr const float expansion_step = scaled<float>(0.1);
|
||||
// Don't take more than max_nr_steps for small expansion_step.
|
||||
static constexpr const size_t max_nr_expansion_steps = 5;
|
||||
|
||||
// Expand the top / bottom / bridge surfaces into the shell thickness solid infills.
|
||||
double layer_thickness;
|
||||
ExPolygons shells = union_ex(fill_surfaces_extract_expolygons(m_fill_surfaces.surfaces, stInternalSolid, layer_thickness));
|
||||
|
||||
SurfaceCollection bridges;
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges. layer" << this->layer()->print_z;
|
||||
const double custom_angle = this->region().config().bridge_angle.value;
|
||||
const auto params = Algorithm::RegionExpansionParameters::build(expansion_bottom_bridge, expansion_step, max_nr_expansion_steps);
|
||||
bridges.surfaces = custom_angle > 0 ?
|
||||
expand_merge_surfaces(m_fill_surfaces.surfaces, stBottomBridge, shells, params, custom_angle) :
|
||||
expand_bridges_detect_orientations(m_fill_surfaces.surfaces, shells, params);
|
||||
BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges - done";
|
||||
#if 0
|
||||
{
|
||||
static int iRun = 0;
|
||||
bridges.export_to_svg(debug_out_path("bridges-after-grouping-%d.svg", iRun++), true);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
Surfaces bottoms = expand_merge_surfaces(m_fill_surfaces.surfaces, stBottom, shells,
|
||||
Algorithm::RegionExpansionParameters::build(expansion_bottom, expansion_step, max_nr_expansion_steps));
|
||||
Surfaces tops = expand_merge_surfaces(m_fill_surfaces.surfaces, stTop, shells,
|
||||
Algorithm::RegionExpansionParameters::build(expansion_top, expansion_step, max_nr_expansion_steps));
|
||||
|
||||
m_fill_surfaces.remove_types({ stBottomBridge, stBottom, stTop, stInternalSolid });
|
||||
reserve_more(m_fill_surfaces.surfaces, shells.size() + bridges.size() + bottoms.size() + tops.size());
|
||||
Surface solid_templ(stInternalSolid, {});
|
||||
solid_templ.thickness = layer_thickness;
|
||||
m_fill_surfaces.append(std::move(shells), solid_templ);
|
||||
m_fill_surfaces.append(std::move(bridges.surfaces));
|
||||
m_fill_surfaces.append(std::move(bottoms));
|
||||
m_fill_surfaces.append(std::move(tops));
|
||||
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-final");
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
}
|
||||
#else
|
||||
|
||||
//#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 3.
|
||||
//#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtMiter, 1.5
|
||||
#define EXTERNAL_SURFACES_OFFSET_PARAMETERS ClipperLib::jtSquare, 0.
|
||||
@ -146,10 +414,11 @@ void LayerRegion::make_perimeters(
|
||||
void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Polygons *lower_layer_covered)
|
||||
{
|
||||
const bool has_infill = this->region().config().fill_density.value > 0.;
|
||||
const float margin = float(scale_(EXTERNAL_INFILL_MARGIN));
|
||||
// const float margin = scaled<float>(0.1); // float(scale_(EXTERNAL_INFILL_MARGIN));
|
||||
const float margin = float(scale_(EXTERNAL_INFILL_MARGIN));
|
||||
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-initial");
|
||||
export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-initial");
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
|
||||
// 1) Collect bottom and bridge surfaces, each of them grown by a fixed 3mm offset
|
||||
@ -164,7 +433,6 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
|
||||
Surfaces internal;
|
||||
// Areas, where an infill of various types (top, bottom, bottom bride, sparse, void) could be placed.
|
||||
Polygons fill_boundaries = to_polygons(this->fill_expolygons());
|
||||
Polygons lower_layer_covered_tmp;
|
||||
|
||||
// Collect top surfaces and internal surfaces.
|
||||
// Collect fill_boundaries: If we're slicing with no infill, we can't extend external surfaces over non-existent infill.
|
||||
@ -174,33 +442,42 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
|
||||
// Voids are sparse infills if infill rate is zero.
|
||||
Polygons voids;
|
||||
for (const Surface &surface : this->fill_surfaces()) {
|
||||
if (surface.is_top()) {
|
||||
// Collect the top surfaces, inflate them and trim them by the bottom surfaces.
|
||||
// This gives the priority to bottom surfaces.
|
||||
surfaces_append(top, offset_ex(surface.expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface);
|
||||
} else if (surface.surface_type == stBottom || (surface.surface_type == stBottomBridge && lower_layer == nullptr)) {
|
||||
// Grown by 3mm.
|
||||
surfaces_append(bottom, offset_ex(surface.expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface);
|
||||
} else if (surface.surface_type == stBottomBridge) {
|
||||
if (! surface.empty())
|
||||
assert(! surface.empty());
|
||||
if (! surface.empty()) {
|
||||
if (surface.is_top()) {
|
||||
// Collect the top surfaces, inflate them and trim them by the bottom surfaces.
|
||||
// This gives the priority to bottom surfaces.
|
||||
surfaces_append(top, offset_ex(surface.expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface);
|
||||
} else if (surface.surface_type == stBottom || (surface.surface_type == stBottomBridge && lower_layer == nullptr)) {
|
||||
// Grown by 3mm.
|
||||
surfaces_append(bottom, offset_ex(surface.expolygon, margin, EXTERNAL_SURFACES_OFFSET_PARAMETERS), surface);
|
||||
} else if (surface.surface_type == stBottomBridge) {
|
||||
bridges.emplace_back(surface);
|
||||
}
|
||||
if (surface.is_internal()) {
|
||||
assert(surface.surface_type == stInternal || surface.surface_type == stInternalSolid);
|
||||
if (! has_infill && lower_layer != nullptr)
|
||||
polygons_append(voids, surface.expolygon);
|
||||
internal.emplace_back(std::move(surface));
|
||||
} else {
|
||||
assert(surface.is_internal());
|
||||
assert(surface.surface_type == stInternal || surface.surface_type == stInternalSolid);
|
||||
if (! has_infill && lower_layer != nullptr)
|
||||
polygons_append(voids, surface.expolygon);
|
||||
internal.emplace_back(std::move(surface));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (! has_infill && lower_layer != nullptr && ! voids.empty()) {
|
||||
if (! voids.empty()) {
|
||||
// There are some voids (empty infill regions) on this layer. Usually one does not want to expand
|
||||
// any infill into these voids, with the exception the expanded infills are supported by layers below
|
||||
// with nonzero inill.
|
||||
assert(! has_infill && lower_layer != nullptr);
|
||||
// Remove voids from fill_boundaries, that are not supported by the layer below.
|
||||
Polygons lower_layer_covered_tmp;
|
||||
if (lower_layer_covered == nullptr) {
|
||||
lower_layer_covered = &lower_layer_covered_tmp;
|
||||
lower_layer_covered_tmp = to_polygons(lower_layer->lslices);
|
||||
}
|
||||
if (! lower_layer_covered->empty())
|
||||
// Allow the top / bottom surfaces to expand into the voids of this layer if supported by the layer below.
|
||||
voids = diff(voids, *lower_layer_covered);
|
||||
fill_boundaries = diff(fill_boundaries, voids);
|
||||
if (! voids.empty())
|
||||
fill_boundaries = diff(fill_boundaries, voids);
|
||||
}
|
||||
}
|
||||
|
||||
@ -224,13 +501,12 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
{
|
||||
static int iRun = 0;
|
||||
SVG svg(debug_out_path("3_process_external_surfaces-fill_regions-%d.svg", iRun ++).c_str(), get_extents(fill_boundaries_ex));
|
||||
SVG svg(debug_out_path("4_process_external_surfaces-fill_regions-%d.svg", iRun ++).c_str(), get_extents(fill_boundaries_ex));
|
||||
svg.draw(fill_boundaries_ex);
|
||||
svg.draw_outline(fill_boundaries_ex, "black", "blue", scale_(0.05));
|
||||
svg.Close();
|
||||
}
|
||||
|
||||
// export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-initial");
|
||||
// export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-initial");
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
|
||||
{
|
||||
@ -253,7 +529,8 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
|
||||
if (idx_island == -1) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "Bridge did not fall into the source region!";
|
||||
} else {
|
||||
// Found an island, to which this bridge region belongs. Trim it,
|
||||
// Found an island, to which this bridge region belongs. Trim the expanded bridging region
|
||||
// with its source region, so it does not overflow into a neighbor region.
|
||||
polys = intersection(polys, fill_boundaries_ex[idx_island]);
|
||||
}
|
||||
bridge_bboxes.push_back(get_extents(polys));
|
||||
@ -371,10 +648,10 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
|
||||
|
||||
Surfaces new_surfaces;
|
||||
{
|
||||
// Merge top and bottom in a single collection.
|
||||
surfaces_append(top, std::move(bottom));
|
||||
// Intersect the grown surfaces with the actual fill boundaries.
|
||||
Polygons bottom_polygons = to_polygons(bottom);
|
||||
// Merge top and bottom in a single collection.
|
||||
surfaces_append(top, std::move(bottom));
|
||||
for (size_t i = 0; i < top.size(); ++ i) {
|
||||
Surface &s1 = top[i];
|
||||
if (s1.empty())
|
||||
@ -422,9 +699,10 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
|
||||
m_fill_surfaces.surfaces = std::move(new_surfaces);
|
||||
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
export_region_fill_surfaces_to_svg_debug("3_process_external_surfaces-final");
|
||||
export_region_fill_surfaces_to_svg_debug("4_process_external_surfaces-final");
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
}
|
||||
#endif
|
||||
|
||||
void LayerRegion::prepare_fill_surfaces()
|
||||
{
|
||||
|
@ -433,10 +433,12 @@ static ClipperLib_Z::Paths clip_extrusion(const ClipperLib_Z::Path &subject, con
|
||||
clipper.AddPath(subject, ClipperLib_Z::ptSubject, false);
|
||||
clipper.AddPaths(clip, ClipperLib_Z::ptClip, true);
|
||||
|
||||
ClipperLib_Z::PolyTree clipped_polytree;
|
||||
ClipperLib_Z::Paths clipped_paths;
|
||||
clipper.Execute(clipType, clipped_polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
|
||||
ClipperLib_Z::PolyTreeToPaths(clipped_polytree, clipped_paths);
|
||||
{
|
||||
ClipperLib_Z::PolyTree clipped_polytree;
|
||||
clipper.Execute(clipType, clipped_polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
|
||||
ClipperLib_Z::PolyTreeToPaths(std::move(clipped_polytree), clipped_paths);
|
||||
}
|
||||
|
||||
// Clipped path could contain vertices from the clip with a Z coordinate equal to zero.
|
||||
// For those vertices, we must assign value based on the subject.
|
||||
|
@ -185,75 +185,107 @@ namespace client
|
||||
template<typename Iterator>
|
||||
struct expr
|
||||
{
|
||||
expr() : type(TYPE_EMPTY) {}
|
||||
explicit expr(bool b) : type(TYPE_BOOL) { data.b = b; }
|
||||
explicit expr(bool b, const Iterator &it_begin, const Iterator &it_end) : type(TYPE_BOOL), it_range(it_begin, it_end) { data.b = b; }
|
||||
explicit expr(int i) : type(TYPE_INT) { data.i = i; }
|
||||
explicit expr(int i, const Iterator &it_begin, const Iterator &it_end) : type(TYPE_INT), it_range(it_begin, it_end) { data.i = i; }
|
||||
explicit expr(double d) : type(TYPE_DOUBLE) { data.d = d; }
|
||||
explicit expr(double d, const Iterator &it_begin, const Iterator &it_end) : type(TYPE_DOUBLE), it_range(it_begin, it_end) { data.d = d; }
|
||||
explicit expr(const char *s) : type(TYPE_STRING) { data.s = new std::string(s); }
|
||||
explicit expr(const std::string &s) : type(TYPE_STRING) { data.s = new std::string(s); }
|
||||
expr() {}
|
||||
explicit expr(bool b) : m_type(TYPE_BOOL) { m_data.b = b; }
|
||||
explicit expr(bool b, const Iterator &it_begin, const Iterator &it_end) : m_type(TYPE_BOOL), it_range(it_begin, it_end) { m_data.b = b; }
|
||||
explicit expr(int i) : m_type(TYPE_INT) { m_data.i = i; }
|
||||
explicit expr(int i, const Iterator &it_begin, const Iterator &it_end) : m_type(TYPE_INT), it_range(it_begin, it_end) { m_data.i = i; }
|
||||
explicit expr(double d) : m_type(TYPE_DOUBLE) { m_data.d = d; }
|
||||
explicit expr(double d, const Iterator &it_begin, const Iterator &it_end) : m_type(TYPE_DOUBLE), it_range(it_begin, it_end) { m_data.d = d; }
|
||||
explicit expr(const char *s) : m_type(TYPE_STRING) { m_data.s = new std::string(s); }
|
||||
explicit expr(const std::string &s) : m_type(TYPE_STRING) { m_data.s = new std::string(s); }
|
||||
explicit expr(const std::string &s, const Iterator &it_begin, const Iterator &it_end) :
|
||||
type(TYPE_STRING), it_range(it_begin, it_end) { data.s = new std::string(s); }
|
||||
expr(const expr &rhs) : type(rhs.type), it_range(rhs.it_range)
|
||||
{ if (rhs.type == TYPE_STRING) data.s = new std::string(*rhs.data.s); else data.set(rhs.data); }
|
||||
explicit expr(expr &&rhs) : type(rhs.type), it_range(rhs.it_range)
|
||||
{ data.set(rhs.data); rhs.type = TYPE_EMPTY; }
|
||||
explicit expr(expr &&rhs, const Iterator &it_begin, const Iterator &it_end) : type(rhs.type), it_range(it_begin, it_end)
|
||||
{ data.set(rhs.data); rhs.type = TYPE_EMPTY; }
|
||||
~expr() { this->reset(); }
|
||||
|
||||
expr &operator=(const expr &rhs)
|
||||
m_type(TYPE_STRING), it_range(it_begin, it_end) { m_data.s = new std::string(s); }
|
||||
expr(const expr &rhs) : m_type(rhs.type()), it_range(rhs.it_range)
|
||||
{ if (rhs.type() == TYPE_STRING) m_data.s = new std::string(*rhs.m_data.s); else m_data.set(rhs.m_data); }
|
||||
explicit expr(expr &&rhs) : expr(rhs, rhs.it_range.begin(), rhs.it_range.end()) {}
|
||||
explicit expr(expr &&rhs, const Iterator &it_begin, const Iterator &it_end) : m_type(rhs.type()), it_range{ it_begin, it_end }
|
||||
{
|
||||
m_data.set(rhs.m_data);
|
||||
rhs.m_type = TYPE_EMPTY;
|
||||
}
|
||||
expr &operator=(const expr &rhs)
|
||||
{
|
||||
this->type = rhs.type;
|
||||
this->it_range = rhs.it_range;
|
||||
if (rhs.type == TYPE_STRING)
|
||||
this->data.s = new std::string(*rhs.data.s);
|
||||
else
|
||||
this->data.set(rhs.data);
|
||||
return *this;
|
||||
if (rhs.type() == TYPE_STRING) {
|
||||
this->set_s(rhs.s());
|
||||
} else {
|
||||
m_type = rhs.type();
|
||||
m_data.set(rhs.m_data);
|
||||
}
|
||||
this->it_range = rhs.it_range;
|
||||
return *this;
|
||||
}
|
||||
|
||||
expr &operator=(expr &&rhs)
|
||||
{
|
||||
type = rhs.type;
|
||||
this->it_range = rhs.it_range;
|
||||
data.set(rhs.data);
|
||||
rhs.type = TYPE_EMPTY;
|
||||
if (this != &rhs) {
|
||||
this->reset();
|
||||
m_type = rhs.type();
|
||||
this->it_range = rhs.it_range;
|
||||
m_data.set(rhs.m_data);
|
||||
rhs.m_type = TYPE_EMPTY;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
if (this->type == TYPE_STRING)
|
||||
delete data.s;
|
||||
this->type = TYPE_EMPTY;
|
||||
if (this->type() == TYPE_STRING)
|
||||
delete m_data.s;
|
||||
m_type = TYPE_EMPTY;
|
||||
}
|
||||
|
||||
bool& b() { return data.b; }
|
||||
bool b() const { return data.b; }
|
||||
void set_b(bool v) { this->reset(); this->data.b = v; this->type = TYPE_BOOL; }
|
||||
int& i() { return data.i; }
|
||||
int i() const { return data.i; }
|
||||
void set_i(int v) { this->reset(); this->data.i = v; this->type = TYPE_INT; }
|
||||
int as_i() const { return (this->type == TYPE_INT) ? this->i() : int(this->d()); }
|
||||
int as_i_rounded() const { return (this->type == TYPE_INT) ? this->i() : int(std::round(this->d())); }
|
||||
double& d() { return data.d; }
|
||||
double d() const { return data.d; }
|
||||
void set_d(double v) { this->reset(); this->data.d = v; this->type = TYPE_DOUBLE; }
|
||||
double as_d() const { return (this->type == TYPE_DOUBLE) ? this->d() : double(this->i()); }
|
||||
std::string& s() { return *data.s; }
|
||||
const std::string& s() const { return *data.s; }
|
||||
void set_s(const std::string &s) { this->reset(); this->data.s = new std::string(s); this->type = TYPE_STRING; }
|
||||
void set_s(std::string &&s) { this->reset(); this->data.s = new std::string(std::move(s)); this->type = TYPE_STRING; }
|
||||
enum Type {
|
||||
TYPE_EMPTY = 0,
|
||||
TYPE_BOOL,
|
||||
TYPE_INT,
|
||||
TYPE_DOUBLE,
|
||||
TYPE_STRING,
|
||||
};
|
||||
Type type() const { return m_type; }
|
||||
|
||||
bool& b() { return m_data.b; }
|
||||
bool b() const { return m_data.b; }
|
||||
void set_b(bool v) { this->reset(); this->set_b_lite(v); }
|
||||
void set_b_lite(bool v) { assert(this->type() != TYPE_STRING); Data tmp; tmp.b = v; m_data.set(tmp); m_type = TYPE_BOOL; }
|
||||
int& i() { return m_data.i; }
|
||||
int i() const { return m_data.i; }
|
||||
void set_i(int v) { this->reset(); set_i_lite(v); }
|
||||
void set_i_lite(int v) { assert(this->type() != TYPE_STRING); Data tmp; tmp.i = v; m_data.set(tmp); m_type = TYPE_INT; }
|
||||
int as_i() const { return this->type() == TYPE_INT ? this->i() : int(this->d()); }
|
||||
int as_i_rounded() const { return this->type() == TYPE_INT ? this->i() : int(std::round(this->d())); }
|
||||
double& d() { return m_data.d; }
|
||||
double d() const { return m_data.d; }
|
||||
void set_d(double v) { this->reset(); this->set_d_lite(v); }
|
||||
void set_d_lite(double v) { assert(this->type() != TYPE_STRING); Data tmp; tmp.d = v; m_data.set(tmp); m_type = TYPE_DOUBLE; }
|
||||
double as_d() const { return this->type() == TYPE_DOUBLE ? this->d() : double(this->i()); }
|
||||
std::string& s() { return *m_data.s; }
|
||||
const std::string& s() const { return *m_data.s; }
|
||||
void set_s(const std::string &s) {
|
||||
if (this->type() == TYPE_STRING)
|
||||
*m_data.s = s;
|
||||
else
|
||||
this->set_s_take_ownership(new std::string(s));
|
||||
}
|
||||
void set_s(std::string &&s) {
|
||||
if (this->type() == TYPE_STRING)
|
||||
*m_data.s = std::move(s);
|
||||
else
|
||||
this->set_s_take_ownership(new std::string(std::move(s)));
|
||||
}
|
||||
void set_s(const char *s) {
|
||||
if (this->type() == TYPE_STRING)
|
||||
*m_data.s = s;
|
||||
else
|
||||
this->set_s_take_ownership(new std::string(s));
|
||||
}
|
||||
|
||||
std::string to_string() const
|
||||
{
|
||||
std::string out;
|
||||
switch (type) {
|
||||
case TYPE_BOOL: out = data.b ? "true" : "false"; break;
|
||||
case TYPE_INT: out = std::to_string(data.i); break;
|
||||
switch (this->type()) {
|
||||
case TYPE_BOOL: out = this->b() ? "true" : "false"; break;
|
||||
case TYPE_INT: out = std::to_string(this->i()); break;
|
||||
case TYPE_DOUBLE:
|
||||
#if 0
|
||||
// The default converter produces trailing zeros after the decimal point.
|
||||
@ -263,48 +295,24 @@ namespace client
|
||||
// It seems to be doing what the old boost::to_string() did.
|
||||
{
|
||||
std::ostringstream ss;
|
||||
ss << data.d;
|
||||
ss << this->d();
|
||||
out = ss.str();
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case TYPE_STRING: out = *data.s; break;
|
||||
case TYPE_STRING: out = this->s(); break;
|
||||
default: break;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
union Data {
|
||||
// Raw image of the other data members.
|
||||
// The C++ compiler will consider a possible aliasing of char* with any other union member,
|
||||
// therefore copying the raw data is safe.
|
||||
char raw[8];
|
||||
bool b;
|
||||
int i;
|
||||
double d;
|
||||
std::string *s;
|
||||
|
||||
// Copy the largest member variable through char*, which will alias with all other union members by default.
|
||||
void set(const Data &rhs) { memcpy(this->raw, rhs.raw, sizeof(rhs.raw)); }
|
||||
} data;
|
||||
|
||||
enum Type {
|
||||
TYPE_EMPTY = 0,
|
||||
TYPE_BOOL,
|
||||
TYPE_INT,
|
||||
TYPE_DOUBLE,
|
||||
TYPE_STRING,
|
||||
};
|
||||
|
||||
Type type;
|
||||
|
||||
// Range of input iterators covering this expression.
|
||||
// Used for throwing parse exceptions.
|
||||
boost::iterator_range<Iterator> it_range;
|
||||
|
||||
expr unary_minus(const Iterator start_pos) const
|
||||
{
|
||||
switch (this->type) {
|
||||
switch (this->type()) {
|
||||
case TYPE_INT :
|
||||
return expr<Iterator>(- this->i(), start_pos, this->it_range.end());
|
||||
case TYPE_DOUBLE:
|
||||
@ -319,7 +327,7 @@ namespace client
|
||||
|
||||
expr unary_integer(const Iterator start_pos) const
|
||||
{
|
||||
switch (this->type) {
|
||||
switch (this->type()) {
|
||||
case TYPE_INT:
|
||||
return expr<Iterator>(this->i(), start_pos, this->it_range.end());
|
||||
case TYPE_DOUBLE:
|
||||
@ -334,7 +342,7 @@ namespace client
|
||||
|
||||
expr round(const Iterator start_pos) const
|
||||
{
|
||||
switch (this->type) {
|
||||
switch (this->type()) {
|
||||
case TYPE_INT:
|
||||
return expr<Iterator>(this->i(), start_pos, this->it_range.end());
|
||||
case TYPE_DOUBLE:
|
||||
@ -349,7 +357,7 @@ namespace client
|
||||
|
||||
expr unary_not(const Iterator start_pos) const
|
||||
{
|
||||
switch (this->type) {
|
||||
switch (this->type()) {
|
||||
case TYPE_BOOL:
|
||||
return expr<Iterator>(! this->b(), start_pos, this->it_range.end());
|
||||
default:
|
||||
@ -362,23 +370,20 @@ namespace client
|
||||
|
||||
expr &operator+=(const expr &rhs)
|
||||
{
|
||||
if (this->type == TYPE_STRING) {
|
||||
if (this->type() == TYPE_STRING) {
|
||||
// Convert the right hand side to string and append.
|
||||
*this->data.s += rhs.to_string();
|
||||
} else if (rhs.type == TYPE_STRING) {
|
||||
*m_data.s += rhs.to_string();
|
||||
} else if (rhs.type() == TYPE_STRING) {
|
||||
// Conver the left hand side to string, append rhs.
|
||||
this->data.s = new std::string(this->to_string() + rhs.s());
|
||||
this->type = TYPE_STRING;
|
||||
this->set_s(this->to_string() + rhs.s());
|
||||
} else {
|
||||
const char *err_msg = "Cannot add non-numeric types.";
|
||||
this->throw_if_not_numeric(err_msg);
|
||||
rhs.throw_if_not_numeric(err_msg);
|
||||
if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) {
|
||||
double d = this->as_d() + rhs.as_d();
|
||||
this->data.d = d;
|
||||
this->type = TYPE_DOUBLE;
|
||||
} else
|
||||
this->data.i += rhs.i();
|
||||
if (this->type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE)
|
||||
this->set_d_lite(this->as_d() + rhs.as_d());
|
||||
else
|
||||
m_data.i += rhs.i();
|
||||
}
|
||||
this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end());
|
||||
return *this;
|
||||
@ -389,12 +394,10 @@ namespace client
|
||||
const char *err_msg = "Cannot subtract non-numeric types.";
|
||||
this->throw_if_not_numeric(err_msg);
|
||||
rhs.throw_if_not_numeric(err_msg);
|
||||
if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) {
|
||||
double d = this->as_d() - rhs.as_d();
|
||||
this->data.d = d;
|
||||
this->type = TYPE_DOUBLE;
|
||||
} else
|
||||
this->data.i -= rhs.i();
|
||||
if (this->type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE)
|
||||
this->set_d_lite(this->as_d() - rhs.as_d());
|
||||
else
|
||||
m_data.i -= rhs.i();
|
||||
this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end());
|
||||
return *this;
|
||||
}
|
||||
@ -404,12 +407,10 @@ namespace client
|
||||
const char *err_msg = "Cannot multiply with non-numeric type.";
|
||||
this->throw_if_not_numeric(err_msg);
|
||||
rhs.throw_if_not_numeric(err_msg);
|
||||
if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) {
|
||||
double d = this->as_d() * rhs.as_d();
|
||||
this->data.d = d;
|
||||
this->type = TYPE_DOUBLE;
|
||||
} else
|
||||
this->data.i *= rhs.i();
|
||||
if (this->type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE)
|
||||
this->set_d_lite(this->as_d() * rhs.as_d());
|
||||
else
|
||||
m_data.i *= rhs.i();
|
||||
this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end());
|
||||
return *this;
|
||||
}
|
||||
@ -418,14 +419,12 @@ namespace client
|
||||
{
|
||||
this->throw_if_not_numeric("Cannot divide a non-numeric type.");
|
||||
rhs.throw_if_not_numeric("Cannot divide with a non-numeric type.");
|
||||
if ((rhs.type == TYPE_INT) ? (rhs.i() == 0) : (rhs.d() == 0.))
|
||||
if (rhs.type() == TYPE_INT ? (rhs.i() == 0) : (rhs.d() == 0.))
|
||||
rhs.throw_exception("Division by zero");
|
||||
if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) {
|
||||
double d = this->as_d() / rhs.as_d();
|
||||
this->data.d = d;
|
||||
this->type = TYPE_DOUBLE;
|
||||
} else
|
||||
this->data.i /= rhs.i();
|
||||
if (this->type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE)
|
||||
this->set_d_lite(this->as_d() / rhs.as_d());
|
||||
else
|
||||
m_data.i /= rhs.i();
|
||||
this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end());
|
||||
return *this;
|
||||
}
|
||||
@ -434,14 +433,12 @@ namespace client
|
||||
{
|
||||
this->throw_if_not_numeric("Cannot divide a non-numeric type.");
|
||||
rhs.throw_if_not_numeric("Cannot divide with a non-numeric type.");
|
||||
if ((rhs.type == TYPE_INT) ? (rhs.i() == 0) : (rhs.d() == 0.))
|
||||
if (rhs.type() == TYPE_INT ? (rhs.i() == 0) : (rhs.d() == 0.))
|
||||
rhs.throw_exception("Division by zero");
|
||||
if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) {
|
||||
double d = std::fmod(this->as_d(), rhs.as_d());
|
||||
this->data.d = d;
|
||||
this->type = TYPE_DOUBLE;
|
||||
} else
|
||||
this->data.i %= rhs.i();
|
||||
if (this->type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE)
|
||||
this->set_d_lite(std::fmod(this->as_d(), rhs.as_d()));
|
||||
else
|
||||
m_data.i %= rhs.i();
|
||||
this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end());
|
||||
return *this;
|
||||
}
|
||||
@ -453,14 +450,14 @@ namespace client
|
||||
|
||||
static void evaluate_boolean(expr &self, bool &out)
|
||||
{
|
||||
if (self.type != TYPE_BOOL)
|
||||
if (self.type() != TYPE_BOOL)
|
||||
self.throw_exception("Not a boolean expression");
|
||||
out = self.b();
|
||||
}
|
||||
|
||||
static void evaluate_boolean_to_string(expr &self, std::string &out)
|
||||
{
|
||||
if (self.type != TYPE_BOOL)
|
||||
if (self.type() != TYPE_BOOL)
|
||||
self.throw_exception("Not a boolean expression");
|
||||
out = self.b() ? "true" : "false";
|
||||
}
|
||||
@ -469,31 +466,31 @@ namespace client
|
||||
static void compare_op(expr &lhs, expr &rhs, char op, bool invert)
|
||||
{
|
||||
bool value = false;
|
||||
if ((lhs.type == TYPE_INT || lhs.type == TYPE_DOUBLE) &&
|
||||
(rhs.type == TYPE_INT || rhs.type == TYPE_DOUBLE)) {
|
||||
if ((lhs.type() == TYPE_INT || lhs.type() == TYPE_DOUBLE) &&
|
||||
(rhs.type() == TYPE_INT || rhs.type() == TYPE_DOUBLE)) {
|
||||
// Both types are numeric.
|
||||
switch (op) {
|
||||
case '=':
|
||||
value = (lhs.type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) ?
|
||||
value = (lhs.type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE) ?
|
||||
(std::abs(lhs.as_d() - rhs.as_d()) < 1e-8) : (lhs.i() == rhs.i());
|
||||
break;
|
||||
case '<':
|
||||
value = (lhs.type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) ?
|
||||
value = (lhs.type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE) ?
|
||||
(lhs.as_d() < rhs.as_d()) : (lhs.i() < rhs.i());
|
||||
break;
|
||||
case '>':
|
||||
default:
|
||||
value = (lhs.type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) ?
|
||||
value = (lhs.type() == TYPE_DOUBLE || rhs.type() == TYPE_DOUBLE) ?
|
||||
(lhs.as_d() > rhs.as_d()) : (lhs.i() > rhs.i());
|
||||
break;
|
||||
}
|
||||
} else if (lhs.type == TYPE_BOOL && rhs.type == TYPE_BOOL) {
|
||||
} else if (lhs.type() == TYPE_BOOL && rhs.type() == TYPE_BOOL) {
|
||||
// Both type are bool.
|
||||
if (op != '=')
|
||||
boost::throw_exception(qi::expectation_failure<Iterator>(
|
||||
lhs.it_range.begin(), rhs.it_range.end(), spirit::info("*Cannot compare the types.")));
|
||||
value = lhs.b() == rhs.b();
|
||||
} else if (lhs.type == TYPE_STRING || rhs.type == TYPE_STRING) {
|
||||
} else if (lhs.type() == TYPE_STRING || rhs.type() == TYPE_STRING) {
|
||||
// One type is string, the other could be converted to string.
|
||||
value = (op == '=') ? (lhs.to_string() == rhs.to_string()) :
|
||||
(op == '<') ? (lhs.to_string() < rhs.to_string()) : (lhs.to_string() > rhs.to_string());
|
||||
@ -502,8 +499,7 @@ namespace client
|
||||
lhs.it_range.begin(), rhs.it_range.end(), spirit::info("*Cannot compare the types.")));
|
||||
}
|
||||
lhs.reset();
|
||||
lhs.type = TYPE_BOOL;
|
||||
lhs.data.b = invert ? ! value : value;
|
||||
lhs.set_b_lite(invert ? ! value : value);
|
||||
}
|
||||
// Compare operators, store the result into lhs.
|
||||
static void equal (expr &lhs, expr &rhs) { compare_op(lhs, rhs, '=', false); }
|
||||
@ -528,15 +524,14 @@ namespace client
|
||||
{
|
||||
throw_if_not_numeric(param1);
|
||||
throw_if_not_numeric(param2);
|
||||
if (param1.type == TYPE_DOUBLE || param2.type == TYPE_DOUBLE) {
|
||||
if (param1.type() == TYPE_DOUBLE || param2.type() == TYPE_DOUBLE) {
|
||||
double d = 0.;
|
||||
switch (fun) {
|
||||
case FUNCTION_MIN: d = std::min(param1.as_d(), param2.as_d()); break;
|
||||
case FUNCTION_MAX: d = std::max(param1.as_d(), param2.as_d()); break;
|
||||
default: param1.throw_exception("Internal error: invalid function");
|
||||
}
|
||||
param1.data.d = d;
|
||||
param1.type = TYPE_DOUBLE;
|
||||
param1.set_d_lite(d);
|
||||
} else {
|
||||
int i = 0;
|
||||
switch (fun) {
|
||||
@ -544,8 +539,7 @@ namespace client
|
||||
case FUNCTION_MAX: i = std::max(param1.as_i(), param2.as_i()); break;
|
||||
default: param1.throw_exception("Internal error: invalid function");
|
||||
}
|
||||
param1.data.i = i;
|
||||
param1.type = TYPE_INT;
|
||||
param1.set_i_lite(i);
|
||||
}
|
||||
}
|
||||
// Store the result into param1.
|
||||
@ -557,13 +551,10 @@ namespace client
|
||||
{
|
||||
throw_if_not_numeric(param1);
|
||||
throw_if_not_numeric(param2);
|
||||
if (param1.type == TYPE_DOUBLE || param2.type == TYPE_DOUBLE) {
|
||||
param1.data.d = std::uniform_real_distribution<>(param1.as_d(), param2.as_d())(rng);
|
||||
param1.type = TYPE_DOUBLE;
|
||||
} else {
|
||||
param1.data.i = std::uniform_int_distribution<>(param1.as_i(), param2.as_i())(rng);
|
||||
param1.type = TYPE_INT;
|
||||
}
|
||||
if (param1.type() == TYPE_DOUBLE || param2.type() == TYPE_DOUBLE)
|
||||
param1.set_d_lite(std::uniform_real_distribution<>(param1.as_d(), param2.as_d())(rng));
|
||||
else
|
||||
param1.set_i_lite(std::uniform_int_distribution<>(param1.as_i(), param2.as_i())(rng));
|
||||
}
|
||||
|
||||
// Store the result into param1.
|
||||
@ -572,10 +563,10 @@ namespace client
|
||||
static void digits(expr ¶m1, expr ¶m2, expr ¶m3)
|
||||
{
|
||||
throw_if_not_numeric(param1);
|
||||
if (param2.type != TYPE_INT)
|
||||
if (param2.type() != TYPE_INT)
|
||||
param2.throw_exception("digits: second parameter must be integer");
|
||||
bool has_decimals = param3.type != TYPE_EMPTY;
|
||||
if (has_decimals && param3.type != TYPE_INT)
|
||||
bool has_decimals = param3.type() != TYPE_EMPTY;
|
||||
if (has_decimals && param3.type() != TYPE_INT)
|
||||
param3.throw_exception("digits: third parameter must be integer");
|
||||
|
||||
char buf[256];
|
||||
@ -593,7 +584,7 @@ namespace client
|
||||
static void regex_op(expr &lhs, boost::iterator_range<Iterator> &rhs, char op)
|
||||
{
|
||||
const std::string *subject = nullptr;
|
||||
if (lhs.type == TYPE_STRING) {
|
||||
if (lhs.type() == TYPE_STRING) {
|
||||
// One type is string, the other could be converted to string.
|
||||
subject = &lhs.s();
|
||||
} else {
|
||||
@ -604,9 +595,7 @@ namespace client
|
||||
bool result = SLIC3R_REGEX_NAMESPACE::regex_match(*subject, SLIC3R_REGEX_NAMESPACE::regex(pattern));
|
||||
if (op == '!')
|
||||
result = ! result;
|
||||
lhs.reset();
|
||||
lhs.type = TYPE_BOOL;
|
||||
lhs.data.b = result;
|
||||
lhs.set_b(result);
|
||||
} catch (SLIC3R_REGEX_NAMESPACE::regex_error &ex) {
|
||||
// Syntax error in the regular expression
|
||||
boost::throw_exception(qi::expectation_failure<Iterator>(
|
||||
@ -620,21 +609,20 @@ namespace client
|
||||
static void logical_op(expr &lhs, expr &rhs, char op)
|
||||
{
|
||||
bool value = false;
|
||||
if (lhs.type == TYPE_BOOL && rhs.type == TYPE_BOOL) {
|
||||
if (lhs.type() == TYPE_BOOL && rhs.type() == TYPE_BOOL) {
|
||||
value = (op == '|') ? (lhs.b() || rhs.b()) : (lhs.b() && rhs.b());
|
||||
} else {
|
||||
boost::throw_exception(qi::expectation_failure<Iterator>(
|
||||
lhs.it_range.begin(), rhs.it_range.end(), spirit::info("*Cannot apply logical operation to non-boolean operators.")));
|
||||
}
|
||||
lhs.type = TYPE_BOOL;
|
||||
lhs.data.b = value;
|
||||
lhs.set_b_lite(value);
|
||||
}
|
||||
static void logical_or (expr &lhs, expr &rhs) { logical_op(lhs, rhs, '|'); }
|
||||
static void logical_and(expr &lhs, expr &rhs) { logical_op(lhs, rhs, '&'); }
|
||||
|
||||
static void ternary_op(expr &lhs, expr &rhs1, expr &rhs2)
|
||||
{
|
||||
if (lhs.type != TYPE_BOOL)
|
||||
if (lhs.type() != TYPE_BOOL)
|
||||
lhs.throw_exception("Not a boolean expression");
|
||||
if (lhs.b())
|
||||
lhs = std::move(rhs1);
|
||||
@ -658,9 +646,25 @@ namespace client
|
||||
|
||||
void throw_if_not_numeric(const char *message) const
|
||||
{
|
||||
if (this->type != TYPE_INT && this->type != TYPE_DOUBLE)
|
||||
if (this->type() != TYPE_INT && this->type() != TYPE_DOUBLE)
|
||||
this->throw_exception(message);
|
||||
}
|
||||
|
||||
private:
|
||||
// This object will take ownership of the parameter string object "s".
|
||||
void set_s_take_ownership(std::string* s) { assert(this->type() != TYPE_STRING); Data tmp; tmp.s = s; m_data.set(tmp); m_type = TYPE_STRING; }
|
||||
|
||||
Type m_type = TYPE_EMPTY;
|
||||
|
||||
union Data {
|
||||
bool b;
|
||||
int i;
|
||||
double d;
|
||||
std::string *s;
|
||||
|
||||
// Copy the largest member variable through char*, which will alias with all other union members by default.
|
||||
void set(const Data &rhs) { memcpy(this, &rhs, sizeof(rhs)); }
|
||||
} m_data;
|
||||
};
|
||||
|
||||
template<typename ITERATOR>
|
||||
@ -668,7 +672,7 @@ namespace client
|
||||
{
|
||||
typedef expr<ITERATOR> Expr;
|
||||
os << std::string(expression.it_range.begin(), expression.it_range.end()) << " - ";
|
||||
switch (expression.type) {
|
||||
switch (expression.type()) {
|
||||
case Expr::TYPE_EMPTY: os << "empty"; break;
|
||||
case Expr::TYPE_BOOL: os << "bool (" << expression.b() << ")"; break;
|
||||
case Expr::TYPE_INT: os << "int (" << expression.i() << ")"; break;
|
||||
@ -802,6 +806,7 @@ namespace client
|
||||
case coInt: output.set_i(opt.opt->getInt()); break;
|
||||
case coString: output.set_s(static_cast<const ConfigOptionString*>(opt.opt)->value); break;
|
||||
case coPercent: output.set_d(opt.opt->getFloat()); break;
|
||||
case coEnum:
|
||||
case coPoint: output.set_s(opt.opt->serialize()); break;
|
||||
case coBool: output.set_b(opt.opt->getBool()); break;
|
||||
case coFloatOrPercent:
|
||||
@ -869,6 +874,7 @@ namespace client
|
||||
case coPercents: output.set_d(static_cast<const ConfigOptionPercents*>(opt.opt)->values[idx]); break;
|
||||
case coPoints: output.set_s(to_string(static_cast<const ConfigOptionPoints *>(opt.opt)->values[idx])); break;
|
||||
case coBools: output.set_b(static_cast<const ConfigOptionBools *>(opt.opt)->values[idx] != 0); break;
|
||||
//case coEnums: output.set_s(opt.opt->vserialize()[idx]); break;
|
||||
default:
|
||||
ctx->throw_exception("Unknown vector variable type", opt.it_range);
|
||||
}
|
||||
@ -912,7 +918,7 @@ namespace client
|
||||
template <typename Iterator>
|
||||
static void evaluate_index(expr<Iterator> &expr_index, int &output)
|
||||
{
|
||||
if (expr_index.type != expr<Iterator>::TYPE_INT)
|
||||
if (expr_index.type() != expr<Iterator>::TYPE_INT)
|
||||
expr_index.throw_exception("Non-integer index is not allowed to address a vector variable.");
|
||||
output = expr_index.i();
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include <map>
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
#include "PrintConfig.hpp"
|
||||
|
||||
@ -38,6 +39,8 @@ public:
|
||||
|
||||
// Add new ConfigOption values to m_config.
|
||||
void set(const std::string &key, const std::string &value) { this->set(key, new ConfigOptionString(value)); }
|
||||
void set(const std::string &key, std::string_view value) { this->set(key, new ConfigOptionString(std::string(value))); }
|
||||
void set(const std::string &key, const char *value) { this->set(key, new ConfigOptionString(value)); }
|
||||
void set(const std::string &key, int value) { this->set(key, new ConfigOptionInt(value)); }
|
||||
void set(const std::string &key, unsigned int value) { this->set(key, int(value)); }
|
||||
void set(const std::string &key, bool value) { this->set(key, new ConfigOptionBool(value)); }
|
||||
|
@ -84,18 +84,28 @@ Points collect_duplicates(Points pts /* Copy */)
|
||||
return duplicits;
|
||||
}
|
||||
|
||||
template<bool IncludeBoundary>
|
||||
BoundingBox get_extents(const Points &pts)
|
||||
{
|
||||
return BoundingBox(pts);
|
||||
BoundingBox out;
|
||||
BoundingBox::construct<IncludeBoundary>(out, pts.begin(), pts.end());
|
||||
return out;
|
||||
}
|
||||
template BoundingBox get_extents<false>(const Points &pts);
|
||||
template BoundingBox get_extents<true>(const Points &pts);
|
||||
|
||||
// if IncludeBoundary, then a bounding box is defined even for a single point.
|
||||
// otherwise a bounding box is only defined if it has a positive area.
|
||||
template<bool IncludeBoundary>
|
||||
BoundingBox get_extents(const std::vector<Points> &pts)
|
||||
{
|
||||
BoundingBox bbox;
|
||||
for (const Points &p : pts)
|
||||
bbox.merge(get_extents(p));
|
||||
bbox.merge(get_extents<IncludeBoundary>(p));
|
||||
return bbox;
|
||||
}
|
||||
template BoundingBox get_extents<false>(const std::vector<Points> &pts);
|
||||
template BoundingBox get_extents<true>(const std::vector<Points> &pts);
|
||||
|
||||
BoundingBoxf get_extents(const std::vector<Vec2d> &pts)
|
||||
{
|
||||
|
@ -237,8 +237,20 @@ inline Point lerp(const Point &a, const Point &b, double t)
|
||||
return ((1. - t) * a.cast<double>() + t * b.cast<double>()).cast<coord_t>();
|
||||
}
|
||||
|
||||
// if IncludeBoundary, then a bounding box is defined even for a single point.
|
||||
// otherwise a bounding box is only defined if it has a positive area.
|
||||
template<bool IncludeBoundary = false>
|
||||
BoundingBox get_extents(const Points &pts);
|
||||
extern template BoundingBox get_extents<false>(const Points &pts);
|
||||
extern template BoundingBox get_extents<true>(const Points &pts);
|
||||
|
||||
// if IncludeBoundary, then a bounding box is defined even for a single point.
|
||||
// otherwise a bounding box is only defined if it has a positive area.
|
||||
template<bool IncludeBoundary = false>
|
||||
BoundingBox get_extents(const std::vector<Points> &pts);
|
||||
extern template BoundingBox get_extents<false>(const std::vector<Points> &pts);
|
||||
extern template BoundingBox get_extents<true>(const std::vector<Points> &pts);
|
||||
|
||||
BoundingBoxf get_extents(const std::vector<Vec2d> &pts);
|
||||
|
||||
int nearest_point_index(const Points &points, const Point &pt);
|
||||
|
@ -292,6 +292,19 @@ void ThickPolyline::clip_end(double distance)
|
||||
assert(this->width.size() == (this->points.size() - 1) * 2);
|
||||
}
|
||||
|
||||
void ThickPolyline::start_at_index(int index)
|
||||
{
|
||||
assert(index >= 0 && index < this->points.size());
|
||||
assert(this->points.front() == this->points.back() && this->width.front() == this->width.back());
|
||||
if (index != 0 && index != (this->points.size() - 1) && this->points.front() == this->points.back() && this->width.front() == this->width.back()) {
|
||||
this->points.pop_back();
|
||||
assert(this->points.size() * 2 == this->width.size());
|
||||
std::rotate(this->points.begin(), this->points.begin() + index, this->points.end());
|
||||
std::rotate(this->width.begin(), this->width.begin() + 2 * index, this->width.end());
|
||||
this->points.emplace_back(this->points.front());
|
||||
}
|
||||
}
|
||||
|
||||
double Polyline3::length() const
|
||||
{
|
||||
double l = 0;
|
||||
|
@ -153,6 +153,25 @@ inline void polylines_append(Polylines &dst, Polylines &&src)
|
||||
}
|
||||
}
|
||||
|
||||
// Merge polylines at their respective end points.
|
||||
// dst_first: the merge point is at dst.begin() or dst.end()?
|
||||
// src_first: the merge point is at src.begin() or src.end()?
|
||||
// The orientation of the resulting polyline is unknown, the output polyline may start
|
||||
// either with src piece or dst piece.
|
||||
template<typename PointType>
|
||||
inline void polylines_merge(std::vector<PointType> &dst, bool dst_first, std::vector<PointType> &&src, bool src_first)
|
||||
{
|
||||
if (dst_first) {
|
||||
if (src_first)
|
||||
std::reverse(dst.begin(), dst.end());
|
||||
else
|
||||
std::swap(dst, src);
|
||||
} else if (! src_first)
|
||||
std::reverse(src.begin(), src.end());
|
||||
// Merge src into dst.
|
||||
append(dst, std::move(src));
|
||||
}
|
||||
|
||||
const Point& leftmost_point(const Polylines &polylines);
|
||||
|
||||
bool remove_degenerate(Polylines &polylines);
|
||||
@ -166,6 +185,7 @@ struct ThickPolyline {
|
||||
|
||||
const Point& first_point() const { return this->points.front(); }
|
||||
const Point& last_point() const { return this->points.back(); }
|
||||
size_t size() const { return this->points.size(); }
|
||||
bool is_valid() const { return this->points.size() >= 2; }
|
||||
double length() const { return Slic3r::length(this->points); }
|
||||
|
||||
@ -179,6 +199,11 @@ struct ThickPolyline {
|
||||
|
||||
void clip_end(double distance);
|
||||
|
||||
// Make this closed ThickPolyline starting in the specified index.
|
||||
// Be aware that this method can be applicable just for closed ThickPolyline.
|
||||
// On open ThickPolyline make no effect.
|
||||
void start_at_index(int index);
|
||||
|
||||
Points points;
|
||||
std::vector<coordf_t> width;
|
||||
std::pair<bool,bool> endpoints { false, false };
|
||||
|
@ -786,7 +786,7 @@ std::pair<Preset*, bool> PresetCollection::load_external_preset(
|
||||
{
|
||||
// Load the preset over a default preset, so that the missing fields are filled in from the default preset.
|
||||
DynamicPrintConfig cfg(this->default_preset_for(combined_config).config);
|
||||
t_config_option_keys keys = std::move(cfg.keys());
|
||||
t_config_option_keys keys = cfg.keys();
|
||||
cfg.apply_only(combined_config, keys, true);
|
||||
std::string &inherits = Preset::inherits(cfg);
|
||||
if (select == LoadAndSelect::Never) {
|
||||
|
@ -591,7 +591,7 @@ void PrintConfigDef::init_fff_params()
|
||||
"Fan speeds for overhang sizes in between are calculated via linear interpolation. ");
|
||||
|
||||
def = this->add("overhang_fan_speed_0", coInts);
|
||||
def->label = L("speed for 0\% overlap (bridge)");
|
||||
def->label = L("speed for 0% overlap (bridge)");
|
||||
def->tooltip = fan_speed_setting_description;
|
||||
def->sidetext = L("%");
|
||||
def->min = 0;
|
||||
@ -600,7 +600,7 @@ void PrintConfigDef::init_fff_params()
|
||||
def->set_default_value(new ConfigOptionInts{0});
|
||||
|
||||
def = this->add("overhang_fan_speed_1", coInts);
|
||||
def->label = L("speed for 25\% overlap");
|
||||
def->label = L("speed for 25% overlap");
|
||||
def->tooltip = fan_speed_setting_description;
|
||||
def->sidetext = L("%");
|
||||
def->min = 0;
|
||||
@ -609,7 +609,7 @@ void PrintConfigDef::init_fff_params()
|
||||
def->set_default_value(new ConfigOptionInts{0});
|
||||
|
||||
def = this->add("overhang_fan_speed_2", coInts);
|
||||
def->label = L("speed for 50\% overlap");
|
||||
def->label = L("speed for 50% overlap");
|
||||
def->tooltip = fan_speed_setting_description;
|
||||
def->sidetext = L("%");
|
||||
def->min = 0;
|
||||
@ -618,7 +618,7 @@ void PrintConfigDef::init_fff_params()
|
||||
def->set_default_value(new ConfigOptionInts{0});
|
||||
|
||||
def = this->add("overhang_fan_speed_3", coInts);
|
||||
def->label = L("speed for 75\% overlap");
|
||||
def->label = L("speed for 75% overlap");
|
||||
def->tooltip = fan_speed_setting_description;
|
||||
def->sidetext = L("%");
|
||||
def->min = 0;
|
||||
|
@ -60,6 +60,7 @@ enum InfillPattern : int {
|
||||
ipRectilinear, ipMonotonic, ipMonotonicLines, ipAlignedRectilinear, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb,
|
||||
ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipAdaptiveCubic, ipSupportCubic, ipSupportBase,
|
||||
ipLightning,
|
||||
ipEnsuring,
|
||||
ipCount,
|
||||
};
|
||||
|
||||
@ -162,6 +163,7 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(GCodeThumbnailsFormat)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PerimeterGeneratorType)
|
||||
|
||||
|
||||
#undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS
|
||||
|
||||
// Defines each and every confiuration option of Slic3r, including the properties of the GUI dialogs.
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -4755,4 +4755,3 @@ sub clip_with_shape {
|
||||
*/
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
|
@ -901,7 +901,7 @@ std::tuple<SupportPoints, PartialObjects> check_stability(const PrintObject *po,
|
||||
params)) {
|
||||
if (bridge.support_point_generated.has_value()) {
|
||||
reckon_new_support_point(*bridge.support_point_generated, create_support_point_position(bridge.b),
|
||||
-EPSILON, Vec2f::Zero());
|
||||
float(-EPSILON), Vec2f::Zero());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -916,7 +916,7 @@ std::tuple<SupportPoints, PartialObjects> check_stability(const PrintObject *po,
|
||||
params);
|
||||
for (const ExtrusionLine &perim : perims) {
|
||||
if (perim.support_point_generated.has_value()) {
|
||||
reckon_new_support_point(*perim.support_point_generated, create_support_point_position(perim.b), -EPSILON,
|
||||
reckon_new_support_point(*perim.support_point_generated, create_support_point_position(perim.b), float(-EPSILON),
|
||||
Vec2f::Zero());
|
||||
}
|
||||
if (perim.is_external_perimeter()) {
|
||||
|
@ -145,6 +145,7 @@ struct PartialObject
|
||||
|
||||
using PartialObjects = std::vector<PartialObject>;
|
||||
|
||||
// Both support points and partial objects are sorted from the lowest z to the highest
|
||||
std::tuple<SupportPoints, PartialObjects> full_search(const PrintObject *po, const PrintTryCancel& cancel_func, const Params ¶ms);
|
||||
|
||||
void estimate_supports_malformations(std::vector<SupportLayer *> &layers, float supports_flow_width, const Params ¶ms);
|
||||
|
@ -33,40 +33,31 @@ class Surface
|
||||
public:
|
||||
SurfaceType surface_type;
|
||||
ExPolygon expolygon;
|
||||
double thickness; // in mm
|
||||
unsigned short thickness_layers; // in layers
|
||||
double bridge_angle; // in radians, ccw, 0 = East, only 0+ (negative means undefined)
|
||||
unsigned short extra_perimeters;
|
||||
double thickness { -1 }; // in mm
|
||||
unsigned short thickness_layers { 1 }; // in layers
|
||||
double bridge_angle { -1. }; // in radians, ccw, 0 = East, only 0+ (negative means undefined)
|
||||
unsigned short extra_perimeters { 0 };
|
||||
|
||||
Surface(const Slic3r::Surface &rhs)
|
||||
: surface_type(rhs.surface_type), expolygon(rhs.expolygon),
|
||||
thickness(rhs.thickness), thickness_layers(rhs.thickness_layers),
|
||||
bridge_angle(rhs.bridge_angle), extra_perimeters(rhs.extra_perimeters)
|
||||
{};
|
||||
|
||||
Surface(SurfaceType _surface_type, const ExPolygon &_expolygon)
|
||||
: surface_type(_surface_type), expolygon(_expolygon),
|
||||
thickness(-1), thickness_layers(1), bridge_angle(-1), extra_perimeters(0)
|
||||
{};
|
||||
Surface(const Surface &other, const ExPolygon &_expolygon)
|
||||
: surface_type(other.surface_type), expolygon(_expolygon),
|
||||
thickness(other.thickness), thickness_layers(other.thickness_layers),
|
||||
bridge_angle(other.bridge_angle), extra_perimeters(other.extra_perimeters)
|
||||
{};
|
||||
Surface(Surface &&rhs)
|
||||
: surface_type(rhs.surface_type), expolygon(std::move(rhs.expolygon)),
|
||||
thickness(rhs.thickness), thickness_layers(rhs.thickness_layers),
|
||||
bridge_angle(rhs.bridge_angle), extra_perimeters(rhs.extra_perimeters)
|
||||
{};
|
||||
Surface(SurfaceType _surface_type, ExPolygon &&_expolygon)
|
||||
: surface_type(_surface_type), expolygon(std::move(_expolygon)),
|
||||
thickness(-1), thickness_layers(1), bridge_angle(-1), extra_perimeters(0)
|
||||
{};
|
||||
Surface(const Surface &other, ExPolygon &&_expolygon)
|
||||
: surface_type(other.surface_type), expolygon(std::move(_expolygon)),
|
||||
thickness(other.thickness), thickness_layers(other.thickness_layers),
|
||||
bridge_angle(other.bridge_angle), extra_perimeters(other.extra_perimeters)
|
||||
{};
|
||||
Surface(const Slic3r::Surface &rhs) :
|
||||
surface_type(rhs.surface_type), expolygon(rhs.expolygon),
|
||||
thickness(rhs.thickness), thickness_layers(rhs.thickness_layers),
|
||||
bridge_angle(rhs.bridge_angle), extra_perimeters(rhs.extra_perimeters) {}
|
||||
Surface(SurfaceType surface_type, const ExPolygon &expolygon) :
|
||||
surface_type(surface_type), expolygon(expolygon) {}
|
||||
Surface(const Surface &templ, const ExPolygon &expolygon) :
|
||||
surface_type(templ.surface_type), expolygon(expolygon),
|
||||
thickness(templ.thickness), thickness_layers(templ.thickness_layers),
|
||||
bridge_angle(templ.bridge_angle), extra_perimeters(templ.extra_perimeters) {}
|
||||
Surface(Surface &&rhs) :
|
||||
surface_type(rhs.surface_type), expolygon(std::move(rhs.expolygon)),
|
||||
thickness(rhs.thickness), thickness_layers(rhs.thickness_layers),
|
||||
bridge_angle(rhs.bridge_angle), extra_perimeters(rhs.extra_perimeters) {}
|
||||
Surface(SurfaceType surface_type, ExPolygon &&expolygon) :
|
||||
surface_type(surface_type), expolygon(std::move(expolygon)) {}
|
||||
Surface(const Surface &templ, ExPolygon &&expolygon) :
|
||||
surface_type(templ.surface_type), expolygon(std::move(expolygon)),
|
||||
thickness(templ.thickness), thickness_layers(templ.thickness_layers),
|
||||
bridge_angle(templ.bridge_angle), extra_perimeters(templ.extra_perimeters) {}
|
||||
|
||||
Surface& operator=(const Surface &rhs)
|
||||
{
|
||||
|
@ -51,16 +51,12 @@ SurfacesPtr SurfaceCollection::filter_by_type(const SurfaceType type) const
|
||||
return ss;
|
||||
}
|
||||
|
||||
SurfacesPtr SurfaceCollection::filter_by_types(const SurfaceType *types, int ntypes) const
|
||||
SurfacesPtr SurfaceCollection::filter_by_types(std::initializer_list<SurfaceType> types) const
|
||||
{
|
||||
SurfacesPtr ss;
|
||||
for (const Surface &surface : this->surfaces)
|
||||
for (int i = 0; i < ntypes; ++ i) {
|
||||
if (surface.surface_type == types[i]) {
|
||||
ss.push_back(&surface);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (std::find(types.begin(), types.end(), surface.surface_type) != types.end())
|
||||
ss.push_back(&surface);
|
||||
return ss;
|
||||
}
|
||||
|
||||
@ -85,23 +81,15 @@ void SurfaceCollection::keep_type(const SurfaceType type)
|
||||
surfaces.erase(surfaces.begin() + j, surfaces.end());
|
||||
}
|
||||
|
||||
void SurfaceCollection::keep_types(const SurfaceType *types, int ntypes)
|
||||
void SurfaceCollection::keep_types(std::initializer_list<SurfaceType> types)
|
||||
{
|
||||
size_t j = 0;
|
||||
for (size_t i = 0; i < surfaces.size(); ++ i) {
|
||||
bool keep = false;
|
||||
for (int k = 0; k < ntypes; ++ k) {
|
||||
if (surfaces[i].surface_type == types[k]) {
|
||||
keep = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (keep) {
|
||||
for (size_t i = 0; i < surfaces.size(); ++ i)
|
||||
if (std::find(types.begin(), types.end(), surfaces[i].surface_type) != types.end()) {
|
||||
if (j < i)
|
||||
std::swap(surfaces[i], surfaces[j]);
|
||||
++ j;
|
||||
}
|
||||
}
|
||||
if (j < surfaces.size())
|
||||
surfaces.erase(surfaces.begin() + j, surfaces.end());
|
||||
}
|
||||
@ -136,23 +124,15 @@ void SurfaceCollection::remove_type(const SurfaceType type, ExPolygons *polygons
|
||||
surfaces.erase(surfaces.begin() + j, surfaces.end());
|
||||
}
|
||||
|
||||
void SurfaceCollection::remove_types(const SurfaceType *types, int ntypes)
|
||||
void SurfaceCollection::remove_types(std::initializer_list<SurfaceType> types)
|
||||
{
|
||||
size_t j = 0;
|
||||
for (size_t i = 0; i < surfaces.size(); ++ i) {
|
||||
bool remove = false;
|
||||
for (int k = 0; k < ntypes; ++ k) {
|
||||
if (surfaces[i].surface_type == types[k]) {
|
||||
remove = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (! remove) {
|
||||
for (size_t i = 0; i < surfaces.size(); ++ i)
|
||||
if (std::find(types.begin(), types.end(), surfaces[i].surface_type) == types.end()) {
|
||||
if (j < i)
|
||||
std::swap(surfaces[i], surfaces[j]);
|
||||
++ j;
|
||||
}
|
||||
}
|
||||
if (j < surfaces.size())
|
||||
surfaces.erase(surfaces.begin() + j, surfaces.end());
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#include "libslic3r.h"
|
||||
#include "Surface.hpp"
|
||||
#include <initializer_list>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r {
|
||||
@ -27,11 +28,11 @@ public:
|
||||
return false;
|
||||
}
|
||||
SurfacesPtr filter_by_type(const SurfaceType type) const;
|
||||
SurfacesPtr filter_by_types(const SurfaceType *types, int ntypes) const;
|
||||
SurfacesPtr filter_by_types(std::initializer_list<SurfaceType> types) const;
|
||||
void keep_type(const SurfaceType type);
|
||||
void keep_types(const SurfaceType *types, int ntypes);
|
||||
void keep_types(std::initializer_list<SurfaceType> types);
|
||||
void remove_type(const SurfaceType type);
|
||||
void remove_types(const SurfaceType *types, int ntypes);
|
||||
void remove_types(std::initializer_list<SurfaceType> types);
|
||||
void filter_by_type(SurfaceType type, Polygons *polygons) const;
|
||||
void remove_type(const SurfaceType type, ExPolygons *polygons);
|
||||
void set_type(SurfaceType type) {
|
||||
|
@ -2421,7 +2421,6 @@ static void merge_influence_areas(
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_buckets_initial),
|
||||
[&](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t idx = range.begin(); idx < range.end(); ++ idx) {
|
||||
const size_t bucket_pair_idx = idx * 2;
|
||||
// Merge bucket_count adjacent to each other, merging uneven bucket numbers into even buckets
|
||||
buckets[idx].second = merge_influence_areas_leaves(volumes, config, layer_idx, buckets[idx].first, buckets[idx].second);
|
||||
throw_on_cancel();
|
||||
@ -4012,8 +4011,12 @@ static indexed_triangle_set draw_branches(
|
||||
// Only one link points to a node above from below.
|
||||
assert(!(++it != map_downwards_old.end() && it->first == &elem));
|
||||
}
|
||||
const SupportElement *pchild = child == -1 ? nullptr : &move_bounds[layer_idx - 1][child];
|
||||
assert(pchild ? pchild->state.result_on_layer_is_set() : elem.state.target_height > layer_idx);
|
||||
#ifndef NDEBUG
|
||||
{
|
||||
const SupportElement *pchild = child == -1 ? nullptr : &move_bounds[layer_idx - 1][child];
|
||||
assert(pchild ? pchild->state.result_on_layer_is_set() : elem.state.target_height > layer_idx);
|
||||
}
|
||||
#endif // NDEBUG
|
||||
}
|
||||
for (int32_t parent_idx : elem.parents) {
|
||||
SupportElement &parent = (*layer_above)[parent_idx];
|
||||
@ -4078,19 +4081,22 @@ static indexed_triangle_set draw_branches(
|
||||
partial_mesh.clear();
|
||||
extrude_branch(path, config, slicing_params, move_bounds, partial_mesh);
|
||||
#if 0
|
||||
char fname[2048];
|
||||
sprintf(fname, "d:\\temp\\meshes\\tree-raw-%d.obj", ++ irun);
|
||||
its_write_obj(partial_mesh, fname);
|
||||
#if 0
|
||||
temp_mesh.clear();
|
||||
cut_mesh(partial_mesh, layer_z(slicing_params, path.back()->state.layer_idx) + EPSILON, nullptr, &temp_mesh, false);
|
||||
sprintf(fname, "d:\\temp\\meshes\\tree-trimmed1-%d.obj", irun);
|
||||
its_write_obj(temp_mesh, fname);
|
||||
partial_mesh.clear();
|
||||
cut_mesh(temp_mesh, layer_z(slicing_params, path.front()->state.layer_idx) - EPSILON, &partial_mesh, nullptr, false);
|
||||
sprintf(fname, "d:\\temp\\meshes\\tree-trimmed2-%d.obj", irun);
|
||||
#endif
|
||||
its_write_obj(partial_mesh, fname);
|
||||
{
|
||||
char fname[2048];
|
||||
static int irun = 0;
|
||||
sprintf(fname, "d:\\temp\\meshes\\tree-raw-%d.obj", ++ irun);
|
||||
its_write_obj(partial_mesh, fname);
|
||||
#if 0
|
||||
temp_mesh.clear();
|
||||
cut_mesh(partial_mesh, layer_z(slicing_params, path.back()->state.layer_idx) + EPSILON, nullptr, &temp_mesh, false);
|
||||
sprintf(fname, "d:\\temp\\meshes\\tree-trimmed1-%d.obj", irun);
|
||||
its_write_obj(temp_mesh, fname);
|
||||
partial_mesh.clear();
|
||||
cut_mesh(temp_mesh, layer_z(slicing_params, path.front()->state.layer_idx) - EPSILON, &partial_mesh, nullptr, false);
|
||||
sprintf(fname, "d:\\temp\\meshes\\tree-trimmed2-%d.obj", irun);
|
||||
#endif
|
||||
its_write_obj(partial_mesh, fname);
|
||||
}
|
||||
#endif
|
||||
its_merge(cummulative_mesh, partial_mesh);
|
||||
}
|
||||
|
@ -62,6 +62,8 @@ public:
|
||||
// Inherits coord_t x, y
|
||||
};
|
||||
|
||||
#define DEBUG_INTERSECTIONLINE (! defined(NDEBUG) || defined(SLIC3R_DEBUG_SLICE_PROCESSING))
|
||||
|
||||
class IntersectionLine : public Line
|
||||
{
|
||||
public:
|
||||
@ -119,14 +121,14 @@ public:
|
||||
};
|
||||
uint32_t flags { 0 };
|
||||
|
||||
#ifndef NDEBUG
|
||||
#if DEBUG_INTERSECTIONLINE
|
||||
enum class Source {
|
||||
BottomPlane,
|
||||
TopPlane,
|
||||
Slab,
|
||||
};
|
||||
Source source { Source::BottomPlane };
|
||||
#endif // NDEBUG
|
||||
#endif
|
||||
};
|
||||
|
||||
using IntersectionLines = std::vector<IntersectionLine>;
|
||||
@ -1440,24 +1442,24 @@ static std::vector<Polygons> make_slab_loops(
|
||||
for (const IntersectionLine &l : lines.at_slice[slice_below])
|
||||
if (l.edge_type != IntersectionLine::FacetEdgeType::Top) {
|
||||
in.emplace_back(l);
|
||||
#ifndef NDEBUG
|
||||
#if DEBUG_INTERSECTIONLINE
|
||||
in.back().source = IntersectionLine::Source::BottomPlane;
|
||||
#endif // NDEBUG
|
||||
#endif // DEBUG_INTERSECTIONLINE
|
||||
}
|
||||
}
|
||||
{
|
||||
// Edges in between slice_below and slice_above.
|
||||
#ifndef NDEBUG
|
||||
#if DEBUG_INTERSECTIONLINE
|
||||
size_t old_size = in.size();
|
||||
#endif // NDEBUG
|
||||
#endif // DEBUG_INTERSECTIONLINE
|
||||
// Edge IDs of end points on in-between lines that touch the layer above are already increased with num_edges.
|
||||
append(in, lines.between_slices[line_idx]);
|
||||
#ifndef NDEBUG
|
||||
#if DEBUG_INTERSECTIONLINE
|
||||
for (auto it = in.begin() + old_size; it != in.end(); ++ it) {
|
||||
assert(it->edge_type == IntersectionLine::FacetEdgeType::Slab);
|
||||
it->source = IntersectionLine::Source::Slab;
|
||||
}
|
||||
#endif // NDEBUG
|
||||
#endif // DEBUG_INTERSECTIONLINE
|
||||
}
|
||||
if (has_slice_above) {
|
||||
for (const IntersectionLine &lsrc : lines.at_slice[slice_above])
|
||||
@ -1470,9 +1472,9 @@ static std::vector<Polygons> make_slab_loops(
|
||||
l.edge_a_id += num_edges;
|
||||
if (l.edge_b_id >= 0)
|
||||
l.edge_b_id += num_edges;
|
||||
#ifndef NDEBUG
|
||||
#if DEBUG_INTERSECTIONLINE
|
||||
l.source = IntersectionLine::Source::TopPlane;
|
||||
#endif // NDEBUG
|
||||
#endif // DEBUG_INTERSECTIONLINE
|
||||
}
|
||||
}
|
||||
if (! in.empty()) {
|
||||
|
@ -124,8 +124,7 @@ inline void append(std::vector<T>& dest, std::vector<T>&& src)
|
||||
dest.insert(dest.end(),
|
||||
std::make_move_iterator(src.begin()),
|
||||
std::make_move_iterator(src.end()));
|
||||
|
||||
// Vojta wants back compatibility
|
||||
// Release memory of the source contour now.
|
||||
src.clear();
|
||||
src.shrink_to_fit();
|
||||
}
|
||||
@ -161,8 +160,7 @@ inline void append_reversed(std::vector<T>& dest, std::vector<T>&& src)
|
||||
dest.insert(dest.end(),
|
||||
std::make_move_iterator(src.rbegin()),
|
||||
std::make_move_iterator(src.rend()));
|
||||
|
||||
// Vojta wants back compatibility
|
||||
// Release memory of the source contour now.
|
||||
src.clear();
|
||||
src.shrink_to_fit();
|
||||
}
|
||||
|
268
t/shells.t
268
t/shells.t
@ -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__
|
@ -1,7 +1,7 @@
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include "test_data.hpp"
|
||||
#include "clipper/clipper_z.hpp"
|
||||
#include "libslic3r/ClipperZUtils.hpp"
|
||||
#include "libslic3r/clipper.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
@ -132,3 +132,72 @@ SCENARIO("Clipper Z", "[ClipperZ]")
|
||||
REQUIRE(pt.z() == 1);
|
||||
}
|
||||
|
||||
SCENARIO("Intersection with multiple polylines", "[ClipperZ]")
|
||||
{
|
||||
// 1000x1000 CCQ square
|
||||
ClipperLib_Z::Path clip { { 0, 0, 1 }, { 1000, 0, 1 }, { 1000, 1000, 1 }, { 0, 1000, 1 } };
|
||||
// Two lines interseting inside the square above, crossing the bottom edge of the square.
|
||||
ClipperLib_Z::Path line1 { { +100, -100, 2 }, { +900, +900, 2 } };
|
||||
ClipperLib_Z::Path line2 { { +100, +900, 3 }, { +900, -100, 3 } };
|
||||
|
||||
ClipperLib_Z::Clipper clipper;
|
||||
ClipperZUtils::ClipperZIntersectionVisitor::Intersections intersections;
|
||||
ClipperZUtils::ClipperZIntersectionVisitor visitor(intersections);
|
||||
clipper.ZFillFunction(visitor.clipper_callback());
|
||||
clipper.AddPath(line1, ClipperLib_Z::ptSubject, false);
|
||||
clipper.AddPath(line2, ClipperLib_Z::ptSubject, false);
|
||||
clipper.AddPath(clip, ClipperLib_Z::ptClip, true);
|
||||
|
||||
ClipperLib_Z::PolyTree polytree;
|
||||
ClipperLib_Z::Paths paths;
|
||||
clipper.Execute(ClipperLib_Z::ctIntersection, polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
|
||||
ClipperLib_Z::PolyTreeToPaths(polytree, paths);
|
||||
|
||||
REQUIRE(paths.size() == 2);
|
||||
|
||||
THEN("First output polyline is a trimmed 2nd line") {
|
||||
// Intermediate point (intersection) was removed)
|
||||
REQUIRE(paths.front().size() == 2);
|
||||
REQUIRE(paths.front().front().z() == 3);
|
||||
REQUIRE(paths.front().back().z() < 0);
|
||||
REQUIRE(intersections[- paths.front().back().z() - 1] == std::pair<coord_t, coord_t>(1, 3));
|
||||
}
|
||||
|
||||
THEN("Second output polyline is a trimmed 1st line") {
|
||||
// Intermediate point (intersection) was removed)
|
||||
REQUIRE(paths[1].size() == 2);
|
||||
REQUIRE(paths[1].front().z() < 0);
|
||||
REQUIRE(paths[1].back().z() == 2);
|
||||
REQUIRE(intersections[- paths[1].front().z() - 1] == std::pair<coord_t, coord_t>(1, 2));
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("Interseting a closed loop as an open polyline", "[ClipperZ]")
|
||||
{
|
||||
// 1000x1000 CCQ square
|
||||
ClipperLib_Z::Path clip{ { 0, 0, 1 }, { 1000, 0, 1 }, { 1000, 1000, 1 }, { 0, 1000, 1 } };
|
||||
// Two lines interseting inside the square above, crossing the bottom edge of the square.
|
||||
ClipperLib_Z::Path rect{ { 500, 500, 2}, { 500, 1500, 2 }, { 1500, 1500, 2}, { 500, 1500, 2}, { 500, 500, 2 } };
|
||||
|
||||
ClipperLib_Z::Clipper clipper;
|
||||
clipper.AddPath(rect, ClipperLib_Z::ptSubject, false);
|
||||
clipper.AddPath(clip, ClipperLib_Z::ptClip, true);
|
||||
|
||||
ClipperLib_Z::PolyTree polytree;
|
||||
ClipperLib_Z::Paths paths;
|
||||
ClipperZUtils::ClipperZIntersectionVisitor::Intersections intersections;
|
||||
ClipperZUtils::ClipperZIntersectionVisitor visitor(intersections);
|
||||
clipper.ZFillFunction(visitor.clipper_callback());
|
||||
clipper.Execute(ClipperLib_Z::ctIntersection, polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
|
||||
ClipperLib_Z::PolyTreeToPaths(std::move(polytree), paths);
|
||||
|
||||
THEN("Open polyline is clipped into two pieces") {
|
||||
REQUIRE(paths.size() == 2);
|
||||
REQUIRE(paths.front().size() == 2);
|
||||
REQUIRE(paths.back().size() == 2);
|
||||
REQUIRE(paths.front().front().z() == 2);
|
||||
REQUIRE(paths.back().back().z() == 2);
|
||||
REQUIRE(paths.front().front().x() == paths.back().back().x());
|
||||
REQUIRE(paths.front().front().y() == paths.back().back().y());
|
||||
}
|
||||
}
|
||||
|
@ -9,16 +9,14 @@ using namespace Slic3r;
|
||||
|
||||
SCENARIO("Shells", "[Shells]") {
|
||||
GIVEN("20mm box") {
|
||||
auto test = [](const DynamicPrintConfig &config){
|
||||
std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config);
|
||||
|
||||
auto test = [](const DynamicPrintConfig &config){
|
||||
std::vector<coord_t> zs;
|
||||
std::set<coord_t> layers_with_solid_infill;
|
||||
std::set<coord_t> layers_with_bridge_infill;
|
||||
const double solid_infill_speed = config.opt_float("solid_infill_speed") * 60;
|
||||
const double bridge_speed = config.opt_float("bridge_speed") * 60;
|
||||
GCodeReader parser;
|
||||
parser.parse_buffer(gcode,
|
||||
parser.parse_buffer(Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config),
|
||||
[&zs, &layers_with_solid_infill, &layers_with_bridge_infill, solid_infill_speed, bridge_speed]
|
||||
(Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line)
|
||||
{
|
||||
@ -110,3 +108,294 @@ SCENARIO("Shells", "[Shells]") {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static std::set<double> layers_with_speed(const std::string &gcode, int speed)
|
||||
{
|
||||
std::set<double> out;
|
||||
GCodeReader parser;
|
||||
parser.parse_buffer(gcode, [&out, speed](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) {
|
||||
if (line.extruding(self) && is_approx<double>(line.new_F(self), speed * 60.))
|
||||
out.insert(self.z());
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
SCENARIO("Shells (from Perl)", "[Shells]") {
|
||||
GIVEN("V shape, Slic3r GH #1161") {
|
||||
int solid_speed = 99;
|
||||
auto config = Slic3r::DynamicPrintConfig::full_print_config_with({
|
||||
{ "layer_height", 0.3 },
|
||||
{ "first_layer_height", 0.3 },
|
||||
{ "bottom_solid_layers", 0 },
|
||||
{ "top_solid_layers", 3 },
|
||||
// to prevent speeds from being altered
|
||||
{ "cooling", "0" },
|
||||
{ "bridge_speed", solid_speed },
|
||||
{ "solid_infill_speed", solid_speed },
|
||||
{ "top_solid_infill_speed", solid_speed },
|
||||
// to prevent speeds from being altered
|
||||
{ "first_layer_speed", "100%" },
|
||||
// prevent speed alteration
|
||||
{ "enable_dynamic_overhang_speeds", 0 }
|
||||
});
|
||||
|
||||
THEN("correct number of top solid shells is generated in V-shaped object") {
|
||||
size_t n = 0;
|
||||
for (auto z : layers_with_speed(Slic3r::Test::slice({TestMesh::V}, config), solid_speed))
|
||||
if (z <= 7.2)
|
||||
++ n;
|
||||
REQUIRE(n == 3);
|
||||
}
|
||||
}
|
||||
GIVEN("V shape") {
|
||||
// we need to check against one perimeter because this test is calibrated
|
||||
// (shape, extrusion_width) so that perimeters cover the bottom surfaces of
|
||||
// their lower layer - the test checks that shells are not generated on the
|
||||
// above layers (thus 'across' the shadow perimeter)
|
||||
// the test is actually calibrated to leave a narrow bottom region for each
|
||||
// layer - we test that in case of fill_density = 0 such narrow shells are
|
||||
// discarded instead of grown
|
||||
int bottom_solid_layers = 3;
|
||||
auto config = Slic3r::DynamicPrintConfig::full_print_config_with({
|
||||
{ "perimeters", 1 },
|
||||
{ "fill_density", 0 },
|
||||
// to prevent speeds from being altered
|
||||
{ "cooling", "0" },
|
||||
// to prevent speeds from being altered
|
||||
{ "first_layer_speed", "100%" },
|
||||
// prevent speed alteration
|
||||
{ "enable_dynamic_overhang_speeds", 0 },
|
||||
{ "layer_height", 0.4 },
|
||||
{ "first_layer_height", 0.4 },
|
||||
{ "extrusion_width", 0.55 },
|
||||
{ "bottom_solid_layers", bottom_solid_layers },
|
||||
{ "top_solid_layers", 0 },
|
||||
{ "solid_infill_speed", 99 }
|
||||
});
|
||||
THEN("shells are not propagated across perimeters of the neighbor layer") {
|
||||
std::string gcode = Slic3r::Test::slice({TestMesh::V}, config);
|
||||
REQUIRE(layers_with_speed(gcode, 99).size() == bottom_solid_layers);
|
||||
}
|
||||
}
|
||||
GIVEN("sloping_hole") {
|
||||
int bottom_solid_layers = 3;
|
||||
int top_solid_layers = 3;
|
||||
int solid_speed = 99;
|
||||
auto config = Slic3r::DynamicPrintConfig::full_print_config_with({
|
||||
{ "perimeters", 3 },
|
||||
// to prevent speeds from being altered
|
||||
{ "cooling", "0" },
|
||||
// to prevent speeds from being altered
|
||||
{ "first_layer_speed", "100%" },
|
||||
// prevent speed alteration
|
||||
{ "enable_dynamic_overhang_speeds", 0 },
|
||||
{ "layer_height", 0.4 },
|
||||
{ "first_layer_height", 0.4 },
|
||||
{ "bottom_solid_layers", bottom_solid_layers },
|
||||
{ "top_solid_layers", top_solid_layers },
|
||||
{ "solid_infill_speed", solid_speed },
|
||||
{ "top_solid_infill_speed", solid_speed },
|
||||
{ "bridge_speed", solid_speed },
|
||||
{ "filament_diameter", 3. },
|
||||
{ "nozzle_diameter", 0.5 }
|
||||
});
|
||||
THEN("no superfluous shells are generated") {
|
||||
std::string gcode = Slic3r::Test::slice({TestMesh::sloping_hole}, config);
|
||||
REQUIRE(layers_with_speed(gcode, solid_speed).size() == bottom_solid_layers + top_solid_layers);
|
||||
}
|
||||
}
|
||||
GIVEN("20mm_cube, spiral vase") {
|
||||
double layer_height = 0.3;
|
||||
auto config = Slic3r::DynamicPrintConfig::full_print_config_with({
|
||||
{ "perimeters", 1 },
|
||||
{ "fill_density", 0 },
|
||||
{ "layer_height", layer_height },
|
||||
{ "first_layer_height", layer_height },
|
||||
{ "top_solid_layers", 0 },
|
||||
{ "spiral_vase", 1 },
|
||||
{ "bottom_solid_layers", 0 },
|
||||
{ "skirts", 0 },
|
||||
{ "start_gcode", "" },
|
||||
{ "temperature", 200 },
|
||||
{ "first_layer_temperature", 205}
|
||||
});
|
||||
|
||||
// TODO: this needs to be tested with a model with sloping edges, where starting
|
||||
// points of each layer are not aligned - in that case we would test that no
|
||||
// travel moves are left to move to the new starting point - in a cube, end
|
||||
// points coincide with next layer starting points (provided there's no clipping)
|
||||
auto test = [layer_height](const DynamicPrintConfig &config) {
|
||||
size_t travel_moves_after_first_extrusion = 0;
|
||||
bool started_extruding = false;
|
||||
bool first_layer_temperature_set = false;
|
||||
bool temperature_set = false;
|
||||
std::vector<double> z_steps;
|
||||
GCodeReader parser;
|
||||
parser.parse_buffer(Slic3r::Test::slice({TestMesh::cube_20x20x20}, config),
|
||||
[&](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) {
|
||||
if (line.cmd_is("G1")) {
|
||||
if (line.extruding(self))
|
||||
started_extruding = true;
|
||||
if (started_extruding) {
|
||||
if (double dz = line.dist_Z(self); dz > 0)
|
||||
z_steps.emplace_back(dz);
|
||||
if (line.travel() && line.dist_XY(self) > 0 && ! line.has(Z))
|
||||
++ travel_moves_after_first_extrusion;
|
||||
}
|
||||
} else if (line.cmd_is("M104")) {
|
||||
int s;
|
||||
if (line.has_value('S', s)) {
|
||||
if (s == 205)
|
||||
first_layer_temperature_set = true;
|
||||
else if (s == 200)
|
||||
temperature_set = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
THEN("first layer temperature is set") {
|
||||
REQUIRE(first_layer_temperature_set);
|
||||
}
|
||||
THEN("temperature is set") {
|
||||
REQUIRE(temperature_set);
|
||||
}
|
||||
// we allow one travel move after first extrusion: i.e. when moving to the first
|
||||
// spiral point after moving to second layer (bottom layer had loop clipping, so
|
||||
// we're slightly distant from the starting point of the loop)
|
||||
THEN("no gaps in spiral vase") {
|
||||
REQUIRE(travel_moves_after_first_extrusion <= 1);
|
||||
}
|
||||
THEN("no gaps in Z") {
|
||||
REQUIRE(std::count_if(z_steps.begin(), z_steps.end(),
|
||||
[&layer_height](auto z_step) { return z_step > layer_height + EPSILON; }) == 0);
|
||||
}
|
||||
};
|
||||
WHEN("solid model") {
|
||||
test(config);
|
||||
}
|
||||
WHEN("solid model with negative z-offset") {
|
||||
config.set_deserialize_strict("z_offset", "-10");
|
||||
test(config);
|
||||
}
|
||||
// Disabled because the current unreliable medial axis code doesn't always produce valid loops.
|
||||
// $test->('40x10', 'hollow model with negative z-offset');
|
||||
}
|
||||
GIVEN("20mm_cube, spiral vase") {
|
||||
double layer_height = 0.4;
|
||||
auto config = Slic3r::DynamicPrintConfig::full_print_config_with({
|
||||
{ "spiral_vase", 1 },
|
||||
{ "perimeters", 1 },
|
||||
{ "fill_density", 0 },
|
||||
{ "top_solid_layers", 0 },
|
||||
{ "bottom_solid_layers", 0 },
|
||||
{ "retract_layer_change", 0 },
|
||||
{ "skirts", 0 },
|
||||
{ "layer_height", layer_height },
|
||||
{ "first_layer_height", layer_height },
|
||||
{ "start_gcode", "" },
|
||||
// { "use_relative_e_distances", 1}
|
||||
});
|
||||
config.validate();
|
||||
|
||||
std::vector<std::pair<double, double>> this_layer; // [ dist_Z, dist_XY ], ...
|
||||
int z_moves = 0;
|
||||
bool bottom_layer_not_flat = false;
|
||||
bool null_z_moves_not_layer_changes = false;
|
||||
bool null_z_moves_not_multiples_of_layer_height = false;
|
||||
bool sum_of_partial_z_equals_to_layer_height = false;
|
||||
bool all_layer_segments_have_same_slope = false;
|
||||
bool horizontal_extrusions = false;
|
||||
GCodeReader parser;
|
||||
parser.parse_buffer(Slic3r::Test::slice({TestMesh::cube_20x20x20}, config),
|
||||
[&](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) {
|
||||
if (line.cmd_is("G1")) {
|
||||
if (z_moves < 2) {
|
||||
// skip everything up to the second Z move
|
||||
// (i.e. start of second layer)
|
||||
if (line.has(Z)) {
|
||||
++ z_moves;
|
||||
if (double dz = line.dist_Z(self); dz > 0 && ! is_approx<double>(dz, layer_height))
|
||||
bottom_layer_not_flat = true;
|
||||
}
|
||||
} else if (line.dist_Z(self) == 0 && line.has(Z)) {
|
||||
if (line.dist_XY(self) != 0)
|
||||
null_z_moves_not_layer_changes = true;
|
||||
double z = line.new_Z(self);
|
||||
if (fmod(z + EPSILON, layer_height) > 2 * EPSILON)
|
||||
null_z_moves_not_multiples_of_layer_height = true;
|
||||
double total_dist_XY = 0;
|
||||
double total_dist_Z = 0;
|
||||
for (auto &seg : this_layer) {
|
||||
total_dist_Z += seg.first;
|
||||
total_dist_XY += seg.second;
|
||||
}
|
||||
if (std::abs(total_dist_Z - layer_height) >
|
||||
// The first segment on the 2nd layer has extrusion interpolated from zero
|
||||
// and the 1st segment has such a low extrusion assigned, that it is effectively zero, thus the move
|
||||
// is considered non-extruding and a higher epsilon is required.
|
||||
(z_moves == 2 ? 0.0021 : EPSILON))
|
||||
sum_of_partial_z_equals_to_layer_height = true;
|
||||
//printf("Total height: %f, layer height: %f, good: %d\n", sum(map $_->[0], @this_layer), $config->layer_height, $sum_of_partial_z_equals_to_layer_height);
|
||||
for (auto &seg : this_layer)
|
||||
// check that segment's dist_Z is proportioned to its dist_XY
|
||||
if (std::abs(seg.first * total_dist_XY / layer_height - seg.second) > 0.2)
|
||||
all_layer_segments_have_same_slope = true;
|
||||
this_layer.clear();
|
||||
} else if (line.extruding(self) && line.dist_XY(self) > 0) {
|
||||
if (line.dist_Z(self) == 0)
|
||||
horizontal_extrusions = true;
|
||||
//printf("Pushing dist_z: %f, dist_xy: %f\n", $info->{dist_Z}, $info->{dist_XY});
|
||||
this_layer.emplace_back(line.dist_Z(self), line.dist_XY(self));
|
||||
}
|
||||
}
|
||||
});
|
||||
THEN("bottom layer is flat when using spiral vase") {
|
||||
REQUIRE(! bottom_layer_not_flat);
|
||||
}
|
||||
THEN("null Z moves are layer changes") {
|
||||
REQUIRE(! null_z_moves_not_layer_changes);
|
||||
}
|
||||
THEN("null Z moves are multiples of layer height") {
|
||||
REQUIRE(! null_z_moves_not_multiples_of_layer_height);
|
||||
}
|
||||
THEN("sum of partial Z increments equals to a full layer height") {
|
||||
REQUIRE(! sum_of_partial_z_equals_to_layer_height);
|
||||
}
|
||||
THEN("all layer segments have the same slope") {
|
||||
REQUIRE(! all_layer_segments_have_same_slope);
|
||||
}
|
||||
THEN("no horizontal extrusions") {
|
||||
REQUIRE(! horizontal_extrusions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
// The current Spiral Vase slicing code removes the holes and all but the largest contours from each slice,
|
||||
// therefore the following test is no more valid.
|
||||
{
|
||||
my $config = Slic3r::Config::new_from_defaults;
|
||||
$config->set('perimeters', 1);
|
||||
$config->set('fill_density', 0);
|
||||
$config->set('top_solid_layers', 0);
|
||||
$config->set('spiral_vase', 1);
|
||||
$config->set('bottom_solid_layers', 0);
|
||||
$config->set('skirts', 0);
|
||||
$config->set('first_layer_height', $config->layer_height);
|
||||
$config->set('start_gcode', '');
|
||||
|
||||
my $print = Slic3r::Test::init_print('two_hollow_squares', config => $config);
|
||||
my $diagonal_moves = 0;
|
||||
Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
|
||||
my ($self, $cmd, $args, $info) = @_;
|
||||
|
||||
if ($cmd eq 'G1') {
|
||||
if ($info->{extruding} && $info->{dist_XY} > 0) {
|
||||
if ($info->{dist_Z} > 0) {
|
||||
$diagonal_moves++;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
is $diagonal_moves, 0, 'no spiral moves on two-island object';
|
||||
}
|
||||
#endif
|
||||
|
@ -23,6 +23,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_stl.cpp
|
||||
test_meshboolean.cpp
|
||||
test_marchingsquares.cpp
|
||||
test_region_expansion.cpp
|
||||
test_timeutils.cpp
|
||||
test_utils.cpp
|
||||
test_voronoi.cpp
|
||||
|
@ -31,6 +31,7 @@ SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") {
|
||||
parser.set("foo", 0);
|
||||
parser.set("bar", 2);
|
||||
parser.set("num_extruders", 4);
|
||||
parser.set("gcode_flavor", "marlin");
|
||||
|
||||
SECTION("nested config options (legacy syntax)") { REQUIRE(parser.process("[temperature_[foo]]") == "357"); }
|
||||
SECTION("array reference") { REQUIRE(parser.process("{temperature[foo]}") == "357"); }
|
||||
@ -115,4 +116,5 @@ SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") {
|
||||
SECTION("complex expression") { REQUIRE(boolean_expression("printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 and num_extruders>1")); }
|
||||
SECTION("complex expression2") { REQUIRE(boolean_expression("printer_notes=~/.*PRINTER_VEwerfNDOR_PRUSA3D.*/ or printer_notes=~/.*PRINTertER_MODEL_MK2.*/ or (nozzle_diameter[0]==0.6 and num_extruders>1)")); }
|
||||
SECTION("complex expression3") { REQUIRE(! boolean_expression("printer_notes=~/.*PRINTER_VEwerfNDOR_PRUSA3D.*/ or printer_notes=~/.*PRINTertER_MODEL_MK2.*/ or (nozzle_diameter[0]==0.3 and num_extruders>1)")); }
|
||||
SECTION("enum expression") { REQUIRE(boolean_expression("gcode_flavor == \"marlin\"")); }
|
||||
}
|
||||
|
@ -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"
|
||||
|
284
tests/libslic3r/test_region_expansion.cpp
Normal file
284
tests/libslic3r/test_region_expansion.cpp
Normal 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))));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user