2014-05-06 08:07:18 +00:00
|
|
|
#include "Layer.hpp"
|
2014-08-03 16:41:09 +00:00
|
|
|
#include "ClipperUtils.hpp"
|
|
|
|
#include "Print.hpp"
|
2016-11-02 09:47:00 +00:00
|
|
|
#include "Fill/Fill.hpp"
|
2019-09-26 15:30:03 +00:00
|
|
|
#include "ShortestPath.hpp"
|
2016-09-26 11:44:23 +00:00
|
|
|
#include "SVG.hpp"
|
2021-07-29 08:21:50 +00:00
|
|
|
#include "BoundingBox.hpp"
|
2014-05-06 08:07:18 +00:00
|
|
|
|
2017-03-03 11:53:05 +00:00
|
|
|
#include <boost/log/trivial.hpp>
|
|
|
|
|
2014-05-06 08:07:18 +00:00
|
|
|
namespace Slic3r {
|
|
|
|
|
|
|
|
Layer::~Layer()
|
|
|
|
{
|
2017-05-30 16:33:17 +00:00
|
|
|
this->lower_layer = this->upper_layer = nullptr;
|
2018-09-11 12:04:47 +00:00
|
|
|
for (LayerRegion *region : m_regions)
|
2017-05-30 16:33:17 +00:00
|
|
|
delete region;
|
2018-09-11 12:04:47 +00:00
|
|
|
m_regions.clear();
|
2014-05-06 08:07:18 +00:00
|
|
|
}
|
|
|
|
|
2018-12-14 16:17:51 +00:00
|
|
|
// Test whether whether there are any slices assigned to this layer.
|
|
|
|
bool Layer::empty() const
|
|
|
|
{
|
|
|
|
for (const LayerRegion *layerm : m_regions)
|
2022-10-26 16:41:39 +00:00
|
|
|
if (layerm != nullptr && ! layerm->slices().empty())
|
2018-12-14 16:17:51 +00:00
|
|
|
// Non empty layer.
|
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-05-06 13:08:57 +00:00
|
|
|
LayerRegion* Layer::add_region(const PrintRegion *print_region)
|
2014-05-06 08:07:18 +00:00
|
|
|
{
|
2018-09-11 12:04:47 +00:00
|
|
|
m_regions.emplace_back(new LayerRegion(this, print_region));
|
|
|
|
return m_regions.back();
|
2014-05-06 08:07:18 +00:00
|
|
|
}
|
|
|
|
|
2014-08-03 16:41:09 +00:00
|
|
|
// merge all regions' slices to get islands
|
2017-05-30 16:33:17 +00:00
|
|
|
void Layer::make_slices()
|
2014-08-03 16:41:09 +00:00
|
|
|
{
|
|
|
|
ExPolygons slices;
|
2018-09-11 12:04:47 +00:00
|
|
|
if (m_regions.size() == 1) {
|
2014-08-03 16:41:09 +00:00
|
|
|
// optimization: if we only have one region, take its slices
|
2022-10-26 16:41:39 +00:00
|
|
|
slices = to_expolygons(m_regions.front()->slices().surfaces);
|
2014-08-03 16:41:09 +00:00
|
|
|
} else {
|
|
|
|
Polygons slices_p;
|
2018-11-02 19:41:49 +00:00
|
|
|
for (LayerRegion *layerm : m_regions)
|
2022-10-26 16:41:39 +00:00
|
|
|
polygons_append(slices_p, to_polygons(layerm->slices().surfaces));
|
2021-12-02 17:18:26 +00:00
|
|
|
slices = union_safety_offset_ex(slices_p);
|
2014-08-03 16:41:09 +00:00
|
|
|
}
|
|
|
|
|
2020-01-03 13:05:56 +00:00
|
|
|
this->lslices.clear();
|
|
|
|
this->lslices.reserve(slices.size());
|
2014-08-03 16:41:09 +00:00
|
|
|
|
|
|
|
// prepare ordering points
|
|
|
|
Points ordering_points;
|
|
|
|
ordering_points.reserve(slices.size());
|
2017-04-05 06:59:03 +00:00
|
|
|
for (const ExPolygon &ex : slices)
|
|
|
|
ordering_points.push_back(ex.contour.first_point());
|
2014-08-03 16:41:09 +00:00
|
|
|
|
|
|
|
// sort slices
|
2019-09-26 15:30:03 +00:00
|
|
|
std::vector<Points::size_type> order = chain_points(ordering_points);
|
2014-08-03 16:41:09 +00:00
|
|
|
|
|
|
|
// populate slices vector
|
2017-04-05 06:59:03 +00:00
|
|
|
for (size_t i : order)
|
2020-01-03 13:05:56 +00:00
|
|
|
this->lslices.emplace_back(std::move(slices[i]));
|
2014-08-03 16:41:09 +00:00
|
|
|
}
|
|
|
|
|
2022-10-26 16:41:39 +00:00
|
|
|
// used by Layer::build_up_down_graph()
|
|
|
|
[[nodiscard]] static ClipperLib_Z::Paths expolygons_to_zpaths(const ExPolygons &expolygons, coord_t isrc)
|
|
|
|
{
|
|
|
|
size_t num_paths = 0;
|
|
|
|
for (const ExPolygon &expolygon : expolygons)
|
|
|
|
num_paths += expolygon.num_contours();
|
|
|
|
|
|
|
|
ClipperLib_Z::Paths out;
|
|
|
|
out.reserve(num_paths);
|
|
|
|
|
|
|
|
for (const ExPolygon &expolygon : expolygons) {
|
|
|
|
for (size_t icontour = 0; icontour < expolygon.num_contours(); ++ icontour) {
|
|
|
|
const Polygon &contour = expolygon.contour_or_hole(icontour);
|
|
|
|
out.emplace_back();
|
|
|
|
ClipperLib_Z::Path &path = out.back();
|
|
|
|
path.reserve(contour.size());
|
|
|
|
for (const Point &p : contour.points)
|
|
|
|
path.push_back({ p.x(), p.y(), isrc });
|
|
|
|
}
|
|
|
|
++ isrc;
|
|
|
|
}
|
|
|
|
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
|
|
|
|
// used by Layer::build_up_down_graph()
|
|
|
|
static void connect_layer_slices(
|
|
|
|
Layer &below,
|
|
|
|
Layer &above,
|
|
|
|
const ClipperLib_Z::PolyTree &polytree,
|
|
|
|
const std::vector<std::pair<coord_t, coord_t>> &intersections,
|
|
|
|
const coord_t offset_below,
|
|
|
|
const coord_t offset_above,
|
|
|
|
const coord_t offset_end)
|
|
|
|
{
|
|
|
|
class Visitor {
|
|
|
|
public:
|
|
|
|
Visitor(const std::vector<std::pair<coord_t, coord_t>> &intersections,
|
|
|
|
Layer &below, Layer &above, const coord_t offset_below, const coord_t offset_above, const coord_t offset_end) :
|
|
|
|
m_intersections(intersections), m_below(below), m_above(above), m_offset_below(offset_below), m_offset_above(offset_above), m_offset_end(offset_end) {}
|
|
|
|
|
|
|
|
void visit(const ClipperLib_Z::PolyNode &polynode)
|
|
|
|
{
|
|
|
|
if (polynode.Contour.size() >= 3) {
|
|
|
|
int32_t i = 0, j = 0;
|
|
|
|
double area = 0;
|
|
|
|
for (int icontour = 0; icontour <= polynode.ChildCount(); ++ icontour) {
|
|
|
|
const ClipperLib_Z::Path &contour = icontour == 0 ? polynode.Contour : polynode.Childs[icontour - 1]->Contour;
|
|
|
|
if (contour.size() >= 3) {
|
|
|
|
area = ClipperLib_Z::Area(contour);
|
|
|
|
int32_t i = contour.front().z();
|
|
|
|
int32_t j = i;
|
|
|
|
if (i < 0) {
|
|
|
|
std::tie(i, j) = m_intersections[-i - 1];
|
|
|
|
} else {
|
|
|
|
for (const ClipperLib_Z::IntPoint& pt : contour) {
|
|
|
|
j = pt.z();
|
|
|
|
if (j < 0) {
|
|
|
|
std::tie(i, j) = m_intersections[-j - 1];
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (i != j)
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
end:
|
|
|
|
bool found = false;
|
|
|
|
if (i == j) {
|
|
|
|
// The contour is completely inside another contour.
|
|
|
|
Point pt(polynode.Contour.front().x(), polynode.Contour.front().y());
|
|
|
|
if (i < m_offset_above) {
|
|
|
|
// Index of an island below. Look-it up in the island above.
|
|
|
|
assert(i >= m_offset_below);
|
|
|
|
i -= m_offset_below;
|
|
|
|
for (int l = int(m_above.lslices_ex.size()) - 1; l >= 0; -- l) {
|
|
|
|
LayerSlice &lslice = m_above.lslices_ex[l];
|
|
|
|
if (lslice.bbox.contains(pt) && m_above.lslices[l].contains(pt)) {
|
|
|
|
found = true;
|
|
|
|
j = l;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Index of an island above. Look-it up in the island below.
|
|
|
|
assert(j < m_offset_end);
|
|
|
|
j -= m_offset_below;
|
|
|
|
for (int l = int(m_below.lslices_ex.size()) - 1; l >= 0; -- l) {
|
|
|
|
LayerSlice &lslice = m_below.lslices_ex[l];
|
|
|
|
if (lslice.bbox.contains(pt) && m_below.lslices[l].contains(pt)) {
|
|
|
|
found = true;
|
|
|
|
i = l;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (i > j)
|
|
|
|
std::swap(i, j);
|
|
|
|
assert(i >= m_offset_below);
|
|
|
|
assert(i < m_offset_above);
|
|
|
|
i -= m_offset_below;
|
|
|
|
assert(j >= m_offset_above);
|
|
|
|
assert(j < m_offset_end);
|
|
|
|
j -= m_offset_above;
|
|
|
|
found = true;
|
|
|
|
}
|
|
|
|
if (found) {
|
|
|
|
// Subtract area of holes from the area of outer contour.
|
|
|
|
for (int icontour = 0; icontour < polynode.ChildCount(); ++ icontour)
|
|
|
|
area -= ClipperLib_Z::Area(polynode.Childs[icontour]->Contour);
|
|
|
|
// Store the links and area into the contours.
|
|
|
|
LayerSlice::Links &links_below = m_below.lslices_ex[i].overlaps_above;
|
|
|
|
LayerSlice::Links &links_above = m_above.lslices_ex[i].overlaps_below;
|
|
|
|
LayerSlice::Link key{ j };
|
|
|
|
auto it_below = std::lower_bound(links_below.begin(), links_below.end(), key, [](auto &l, auto &r){ return l.slice_idx < r.slice_idx; });
|
|
|
|
if (it_below != links_below.end() && it_below->slice_idx == j) {
|
|
|
|
it_below->area += area;
|
|
|
|
} else {
|
|
|
|
auto it_above = std::lower_bound(links_above.begin(), links_above.end(), key, [](auto &l, auto &r){ return l.slice_idx < r.slice_idx; });
|
|
|
|
if (it_above != links_above.end() && it_above->slice_idx == i) {
|
|
|
|
it_above->area += area;
|
|
|
|
} else {
|
|
|
|
// Insert into one of the two vectors.
|
|
|
|
bool take_below = false;
|
|
|
|
if (links_below.size() < LayerSlice::LinksStaticSize)
|
|
|
|
take_below = false;
|
|
|
|
else if (links_above.size() >= LayerSlice::LinksStaticSize) {
|
|
|
|
size_t shift_below = links_below.end() - it_below;
|
|
|
|
size_t shift_above = links_above.end() - it_above;
|
|
|
|
take_below = shift_below < shift_above;
|
|
|
|
}
|
|
|
|
if (take_below)
|
|
|
|
links_below.insert(it_below, { j, float(area) });
|
|
|
|
else
|
|
|
|
links_above.insert(it_above, { i, float(area) });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (int i = 0; i < polynode.ChildCount(); ++ i)
|
|
|
|
for (int j = 0; j < polynode.Childs[i]->ChildCount(); ++ j)
|
|
|
|
this->visit(*polynode.Childs[i]->Childs[j]);
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
const std::vector<std::pair<coord_t, coord_t>> &m_intersections;
|
|
|
|
Layer &m_below;
|
|
|
|
Layer &m_above;
|
|
|
|
const coord_t m_offset_below;
|
|
|
|
const coord_t m_offset_above;
|
|
|
|
const coord_t m_offset_end;
|
|
|
|
} visitor(intersections, below, above, offset_below, offset_above, offset_end);
|
|
|
|
|
|
|
|
for (int i = 0; i < polytree.ChildCount(); ++ i)
|
|
|
|
visitor.visit(*polytree.Childs[i]);
|
|
|
|
|
|
|
|
#ifndef NDEBUG
|
|
|
|
// Verify that only one directional link is stored: either from bottom slice up or from upper slice down.
|
|
|
|
for (int32_t islice = 0; islice < below.lslices_ex.size(); ++ islice) {
|
|
|
|
LayerSlice::Links &links1 = below.lslices_ex[islice].overlaps_above;
|
|
|
|
for (LayerSlice::Link &link1 : links1) {
|
|
|
|
LayerSlice::Links &links2 = above.lslices_ex[link1.slice_idx].overlaps_below;
|
|
|
|
assert(! std::binary_search(links2.begin(), links2.end(), link1, [](auto &l, auto &r){ return l.slice_idx < r.slice_idx; }));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (int32_t islice = 0; islice < above.lslices_ex.size(); ++ islice) {
|
|
|
|
LayerSlice::Links &links1 = above.lslices_ex[islice].overlaps_above;
|
|
|
|
for (LayerSlice::Link &link1 : links1) {
|
|
|
|
LayerSlice::Links &links2 = below.lslices_ex[link1.slice_idx].overlaps_below;
|
|
|
|
assert(! std::binary_search(links2.begin(), links2.end(), link1, [](auto &l, auto &r){ return l.slice_idx < r.slice_idx; }));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif // NDEBUG
|
|
|
|
|
|
|
|
// Scatter the links, but don't sort them yet.
|
|
|
|
for (int32_t islice = 0; islice < below.lslices_ex.size(); ++ islice)
|
|
|
|
for (LayerSlice::Link &link : below.lslices_ex[islice].overlaps_above)
|
|
|
|
above.lslices_ex[link.slice_idx].overlaps_below.push_back({ islice, link.area });
|
|
|
|
for (int32_t islice = 0; islice < above.lslices_ex.size(); ++ islice)
|
|
|
|
for (LayerSlice::Link &link : above.lslices_ex[islice].overlaps_below)
|
|
|
|
below.lslices_ex[link.slice_idx].overlaps_above.push_back({ islice, link.area });
|
|
|
|
// Sort the links.
|
|
|
|
for (LayerSlice &lslice : below.lslices_ex)
|
|
|
|
std::sort(lslice.overlaps_above.begin(), lslice.overlaps_above.end(), [](const LayerSlice::Link &l, const LayerSlice::Link &r){ return l.slice_idx < r.slice_idx; });
|
|
|
|
for (LayerSlice &lslice : above.lslices_ex)
|
|
|
|
std::sort(lslice.overlaps_below.begin(), lslice.overlaps_below.end(), [](const LayerSlice::Link &l, const LayerSlice::Link &r){ return l.slice_idx < r.slice_idx; });
|
|
|
|
}
|
|
|
|
|
|
|
|
void Layer::build_up_down_graph(Layer& below, Layer& above)
|
|
|
|
{
|
|
|
|
coord_t paths_below_offset = 0;
|
|
|
|
ClipperLib_Z::Paths paths_below = expolygons_to_zpaths(below.lslices, paths_below_offset);
|
|
|
|
coord_t paths_above_offset = paths_below_offset + coord_t(below.lslices.size());
|
|
|
|
ClipperLib_Z::Paths paths_above = expolygons_to_zpaths(above.lslices, paths_above_offset);
|
|
|
|
coord_t paths_end = paths_above_offset + coord_t(above.lslices.size());
|
|
|
|
|
|
|
|
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);
|
|
|
|
assert(begin + 2 == end);
|
|
|
|
if (begin + 1 == end)
|
|
|
|
pt.z() = *begin;
|
|
|
|
else 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); });
|
|
|
|
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, paths_end);
|
|
|
|
}
|
|
|
|
|
2020-12-11 11:21:07 +00:00
|
|
|
static inline bool layer_needs_raw_backup(const Layer *layer)
|
2015-04-16 18:44:55 +00:00
|
|
|
{
|
2020-12-11 11:21:07 +00:00
|
|
|
return ! (layer->regions().size() == 1 && (layer->id() > 0 || layer->object()->config().elefant_foot_compensation.value == 0));
|
|
|
|
}
|
|
|
|
|
|
|
|
void Layer::backup_untyped_slices()
|
|
|
|
{
|
|
|
|
if (layer_needs_raw_backup(this)) {
|
|
|
|
for (LayerRegion *layerm : m_regions)
|
2022-10-26 16:41:39 +00:00
|
|
|
layerm->m_raw_slices = to_expolygons(layerm->slices().surfaces);
|
2016-11-20 11:38:59 +00:00
|
|
|
} else {
|
2020-12-11 11:21:07 +00:00
|
|
|
assert(m_regions.size() == 1);
|
2022-10-26 16:41:39 +00:00
|
|
|
m_regions.front()->m_raw_slices.clear();
|
2020-12-11 11:21:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Layer::restore_untyped_slices()
|
|
|
|
{
|
|
|
|
if (layer_needs_raw_backup(this)) {
|
2018-11-02 19:41:49 +00:00
|
|
|
for (LayerRegion *layerm : m_regions)
|
2022-10-26 16:41:39 +00:00
|
|
|
layerm->m_slices.set(layerm->m_raw_slices, stInternal);
|
2020-12-11 11:21:07 +00:00
|
|
|
} else {
|
|
|
|
assert(m_regions.size() == 1);
|
2022-10-26 16:41:39 +00:00
|
|
|
m_regions.front()->m_slices.set(this->lslices, stInternal);
|
2015-04-16 18:44:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-08 08:45:48 +00:00
|
|
|
// Similar to Layer::restore_untyped_slices()
|
|
|
|
// To improve robustness of detect_surfaces_type() when reslicing (working with typed slices), see GH issue #7442.
|
|
|
|
// Only resetting layerm->slices if Slice::extra_perimeters is always zero or it will not be used anymore
|
|
|
|
// after the perimeter generator.
|
|
|
|
void Layer::restore_untyped_slices_no_extra_perimeters()
|
|
|
|
{
|
|
|
|
if (layer_needs_raw_backup(this)) {
|
|
|
|
for (LayerRegion *layerm : m_regions)
|
2021-12-08 10:22:43 +00:00
|
|
|
if (! layerm->region().config().extra_perimeters.value)
|
2022-10-26 16:41:39 +00:00
|
|
|
layerm->m_slices.set(layerm->m_raw_slices, stInternal);
|
2021-12-08 08:45:48 +00:00
|
|
|
} else {
|
|
|
|
assert(m_regions.size() == 1);
|
|
|
|
LayerRegion *layerm = m_regions.front();
|
|
|
|
// This optimization is correct, as extra_perimeters are only reused by prepare_infill() with multi-regions.
|
2021-12-08 10:22:43 +00:00
|
|
|
//if (! layerm->region().config().extra_perimeters.value)
|
2022-10-26 16:41:39 +00:00
|
|
|
layerm->m_slices.set(this->lslices, stInternal);
|
2021-12-08 08:45:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-05 10:54:04 +00:00
|
|
|
ExPolygons Layer::merged(float offset_scaled) const
|
|
|
|
{
|
|
|
|
assert(offset_scaled >= 0.f);
|
|
|
|
// If no offset is set, apply EPSILON offset before union, and revert it afterwards.
|
|
|
|
float offset_scaled2 = 0;
|
|
|
|
if (offset_scaled == 0.f) {
|
|
|
|
offset_scaled = float( EPSILON);
|
|
|
|
offset_scaled2 = float(- EPSILON);
|
|
|
|
}
|
|
|
|
Polygons polygons;
|
2019-11-01 18:59:09 +00:00
|
|
|
for (LayerRegion *layerm : m_regions) {
|
2021-05-05 16:13:58 +00:00
|
|
|
const PrintRegionConfig &config = layerm->region().config();
|
2019-11-01 18:59:09 +00:00
|
|
|
// Our users learned to bend Slic3r to produce empty volumes to act as subtracters. Only add the region if it is non-empty.
|
|
|
|
if (config.bottom_solid_layers > 0 || config.top_solid_layers > 0 || config.fill_density > 0. || config.perimeters > 0)
|
2022-10-26 16:41:39 +00:00
|
|
|
append(polygons, offset(layerm->slices().surfaces, offset_scaled));
|
2019-11-01 18:59:09 +00:00
|
|
|
}
|
2019-03-05 10:54:04 +00:00
|
|
|
ExPolygons out = union_ex(polygons);
|
|
|
|
if (offset_scaled2 != 0.f)
|
|
|
|
out = offset_ex(out, offset_scaled2);
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
|
2016-09-13 11:30:00 +00:00
|
|
|
// Here the perimeters are created cummulatively for all layer regions sharing the same parameters influencing the perimeters.
|
|
|
|
// The perimeter paths and the thin fills (ExtrusionEntityCollection) are assigned to the first compatible layer region.
|
|
|
|
// The resulting fill surface is split back among the originating regions.
|
2017-05-30 16:33:17 +00:00
|
|
|
void Layer::make_perimeters()
|
2015-12-02 18:32:57 +00:00
|
|
|
{
|
2017-03-03 11:53:05 +00:00
|
|
|
BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id();
|
2015-12-02 18:32:57 +00:00
|
|
|
|
|
|
|
// keep track of regions whose perimeters we have already generated
|
2019-03-06 09:21:10 +00:00
|
|
|
std::vector<unsigned char> done(m_regions.size(), false);
|
2015-12-02 18:32:57 +00:00
|
|
|
|
2022-10-26 16:41:39 +00:00
|
|
|
LayerRegionPtrs layerms;
|
2020-03-20 12:37:13 +00:00
|
|
|
for (LayerRegionPtrs::iterator layerm = m_regions.begin(); layerm != m_regions.end(); ++ layerm)
|
2022-10-26 16:41:39 +00:00
|
|
|
if ((*layerm)->slices().empty()) {
|
|
|
|
(*layerm)->m_perimeters.clear();
|
|
|
|
(*layerm)->m_fills.clear();
|
|
|
|
(*layerm)->m_thin_fills.clear();
|
2020-03-20 12:37:13 +00:00
|
|
|
} else {
|
|
|
|
size_t region_id = layerm - m_regions.begin();
|
|
|
|
if (done[region_id])
|
|
|
|
continue;
|
|
|
|
BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << ", region " << region_id;
|
|
|
|
done[region_id] = true;
|
2021-05-05 16:13:58 +00:00
|
|
|
const PrintRegionConfig &config = (*layerm)->region().config();
|
2020-03-20 12:37:13 +00:00
|
|
|
|
|
|
|
// find compatible regions
|
2022-10-26 16:41:39 +00:00
|
|
|
layerms.clear();
|
2020-03-20 12:37:13 +00:00
|
|
|
layerms.push_back(*layerm);
|
|
|
|
for (LayerRegionPtrs::const_iterator it = layerm + 1; it != m_regions.end(); ++it)
|
2022-10-26 16:41:39 +00:00
|
|
|
if (! (*it)->slices().empty()) {
|
2020-03-20 12:37:13 +00:00
|
|
|
LayerRegion* other_layerm = *it;
|
2021-05-05 16:13:58 +00:00
|
|
|
const PrintRegionConfig &other_config = other_layerm->region().config();
|
2021-02-10 15:02:32 +00:00
|
|
|
if (config.perimeter_extruder == other_config.perimeter_extruder
|
|
|
|
&& config.perimeters == other_config.perimeters
|
|
|
|
&& config.perimeter_speed == other_config.perimeter_speed
|
|
|
|
&& config.external_perimeter_speed == other_config.external_perimeter_speed
|
2021-02-10 16:28:56 +00:00
|
|
|
&& (config.gap_fill_enabled ? config.gap_fill_speed.value : 0.) ==
|
|
|
|
(other_config.gap_fill_enabled ? other_config.gap_fill_speed.value : 0.)
|
2021-02-10 15:02:32 +00:00
|
|
|
&& config.overhangs == other_config.overhangs
|
2020-03-20 12:37:13 +00:00
|
|
|
&& config.opt_serialize("perimeter_extrusion_width") == other_config.opt_serialize("perimeter_extrusion_width")
|
2021-02-10 15:02:32 +00:00
|
|
|
&& config.thin_walls == other_config.thin_walls
|
|
|
|
&& config.external_perimeters_first == other_config.external_perimeters_first
|
|
|
|
&& config.infill_overlap == other_config.infill_overlap
|
|
|
|
&& config.fuzzy_skin == other_config.fuzzy_skin
|
|
|
|
&& config.fuzzy_skin_thickness == other_config.fuzzy_skin_thickness
|
|
|
|
&& config.fuzzy_skin_point_dist == other_config.fuzzy_skin_point_dist)
|
2020-03-20 12:37:13 +00:00
|
|
|
{
|
2022-10-26 16:41:39 +00:00
|
|
|
other_layerm->m_perimeters.clear();
|
|
|
|
other_layerm->m_fills.clear();
|
|
|
|
other_layerm->m_thin_fills.clear();
|
2020-03-20 12:37:13 +00:00
|
|
|
layerms.push_back(other_layerm);
|
|
|
|
done[it - m_regions.begin()] = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-27 17:08:43 +00:00
|
|
|
ExPolygons fill_expolygons;
|
2020-03-20 12:37:13 +00:00
|
|
|
if (layerms.size() == 1) { // optimization
|
2022-10-26 16:41:39 +00:00
|
|
|
(*layerm)->m_fill_expolygons.clear();
|
|
|
|
(*layerm)->m_fill_surfaces.clear();
|
2022-10-27 17:08:43 +00:00
|
|
|
(*layerm)->make_perimeters((*layerm)->slices(), fill_expolygons);
|
|
|
|
(*layerm)->m_fill_expolygons = std::move(fill_expolygons);
|
2020-03-20 12:37:13 +00:00
|
|
|
} else {
|
|
|
|
SurfaceCollection new_slices;
|
|
|
|
// Use the region with highest infill rate, as the make_perimeters() function below decides on the gap fill based on the infill existence.
|
|
|
|
LayerRegion *layerm_config = layerms.front();
|
|
|
|
{
|
|
|
|
// group slices (surfaces) according to number of extra perimeters
|
|
|
|
std::map<unsigned short, Surfaces> slices; // extra_perimeters => [ surface, surface... ]
|
|
|
|
for (LayerRegion *layerm : layerms) {
|
2022-10-26 16:41:39 +00:00
|
|
|
for (const Surface &surface : layerm->slices())
|
2020-03-20 12:37:13 +00:00
|
|
|
slices[surface.extra_perimeters].emplace_back(surface);
|
2021-05-05 16:13:58 +00:00
|
|
|
if (layerm->region().config().fill_density > layerm_config->region().config().fill_density)
|
2020-03-20 12:37:13 +00:00
|
|
|
layerm_config = layerm;
|
2022-10-26 16:41:39 +00:00
|
|
|
layerm->m_fill_surfaces.clear();
|
|
|
|
layerm->m_fill_expolygons.clear();
|
2020-03-20 12:37:13 +00:00
|
|
|
}
|
|
|
|
// merge the surfaces assigned to each group
|
|
|
|
for (std::pair<const unsigned short,Surfaces> &surfaces_with_extra_perimeters : slices)
|
2021-05-03 09:39:53 +00:00
|
|
|
new_slices.append(offset_ex(surfaces_with_extra_perimeters.second, ClipperSafetyOffset), surfaces_with_extra_perimeters.second.front());
|
2020-03-20 12:37:13 +00:00
|
|
|
}
|
|
|
|
// make perimeters
|
2022-10-27 17:08:43 +00:00
|
|
|
layerm_config->make_perimeters(new_slices, fill_expolygons);
|
2020-03-20 12:37:13 +00:00
|
|
|
// assign fill_surfaces to each layer
|
2022-10-27 17:08:43 +00:00
|
|
|
if (! fill_expolygons.empty()) {
|
2022-10-26 16:41:39 +00:00
|
|
|
// Separate the fill surfaces.
|
|
|
|
for (LayerRegion *l : layerms)
|
2022-10-27 17:08:43 +00:00
|
|
|
l->m_fill_expolygons = intersection_ex(l->slices().surfaces, fill_expolygons);
|
2022-10-26 16:41:39 +00:00
|
|
|
}
|
2020-03-20 12:37:13 +00:00
|
|
|
}
|
|
|
|
}
|
2017-03-03 11:53:05 +00:00
|
|
|
BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << " - Done";
|
2015-12-02 18:32:57 +00:00
|
|
|
}
|
|
|
|
|
2017-09-14 11:15:32 +00:00
|
|
|
void Layer::export_region_slices_to_svg(const char *path) const
|
2016-09-26 11:44:23 +00:00
|
|
|
{
|
|
|
|
BoundingBox bbox;
|
2018-09-11 12:04:47 +00:00
|
|
|
for (const auto *region : m_regions)
|
2022-10-26 16:41:39 +00:00
|
|
|
for (const auto &surface : region->slices())
|
2018-09-11 12:04:47 +00:00
|
|
|
bbox.merge(get_extents(surface.expolygon));
|
2016-09-26 11:44:23 +00:00
|
|
|
Point legend_size = export_surface_type_legend_to_svg_box_size();
|
2018-08-17 13:53:43 +00:00
|
|
|
Point legend_pos(bbox.min(0), bbox.max(1));
|
|
|
|
bbox.merge(Point(std::max(bbox.min(0) + legend_size(0), bbox.max(0)), bbox.max(1) + legend_size(1)));
|
2016-09-26 11:44:23 +00:00
|
|
|
|
|
|
|
SVG svg(path, bbox);
|
|
|
|
const float transparency = 0.5f;
|
2018-09-11 12:04:47 +00:00
|
|
|
for (const auto *region : m_regions)
|
2022-10-26 16:41:39 +00:00
|
|
|
for (const auto &surface : region->slices())
|
2018-09-11 12:04:47 +00:00
|
|
|
svg.draw(surface.expolygon, surface_type_to_color_name(surface.surface_type), transparency);
|
2016-09-26 11:44:23 +00:00
|
|
|
export_surface_type_legend_to_svg(svg, legend_pos);
|
|
|
|
svg.Close();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Export to "out/LayerRegion-name-%d.svg" with an increasing index with every export.
|
2017-09-14 11:15:32 +00:00
|
|
|
void Layer::export_region_slices_to_svg_debug(const char *name) const
|
2016-09-26 11:44:23 +00:00
|
|
|
{
|
|
|
|
static size_t idx = 0;
|
2016-10-21 08:18:01 +00:00
|
|
|
this->export_region_slices_to_svg(debug_out_path("Layer-slices-%s-%d.svg", name, idx ++).c_str());
|
2016-09-26 11:44:23 +00:00
|
|
|
}
|
|
|
|
|
2017-09-14 11:15:32 +00:00
|
|
|
void Layer::export_region_fill_surfaces_to_svg(const char *path) const
|
2016-09-26 11:44:23 +00:00
|
|
|
{
|
|
|
|
BoundingBox bbox;
|
2018-09-11 12:04:47 +00:00
|
|
|
for (const auto *region : m_regions)
|
2022-10-26 16:41:39 +00:00
|
|
|
for (const auto &surface : region->slices())
|
2018-09-11 12:04:47 +00:00
|
|
|
bbox.merge(get_extents(surface.expolygon));
|
2016-09-26 11:44:23 +00:00
|
|
|
Point legend_size = export_surface_type_legend_to_svg_box_size();
|
2018-08-17 13:53:43 +00:00
|
|
|
Point legend_pos(bbox.min(0), bbox.max(1));
|
|
|
|
bbox.merge(Point(std::max(bbox.min(0) + legend_size(0), bbox.max(0)), bbox.max(1) + legend_size(1)));
|
2016-09-26 11:44:23 +00:00
|
|
|
|
|
|
|
SVG svg(path, bbox);
|
|
|
|
const float transparency = 0.5f;
|
2018-09-11 12:04:47 +00:00
|
|
|
for (const auto *region : m_regions)
|
2022-10-26 16:41:39 +00:00
|
|
|
for (const auto &surface : region->slices())
|
2018-09-11 12:04:47 +00:00
|
|
|
svg.draw(surface.expolygon, surface_type_to_color_name(surface.surface_type), transparency);
|
2016-09-26 11:44:23 +00:00
|
|
|
export_surface_type_legend_to_svg(svg, legend_pos);
|
|
|
|
svg.Close();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Export to "out/LayerRegion-name-%d.svg" with an increasing index with every export.
|
2017-09-14 11:15:32 +00:00
|
|
|
void Layer::export_region_fill_surfaces_to_svg_debug(const char *name) const
|
2016-09-26 11:44:23 +00:00
|
|
|
{
|
|
|
|
static size_t idx = 0;
|
2016-10-21 08:18:01 +00:00
|
|
|
this->export_region_fill_surfaces_to_svg(debug_out_path("Layer-fill_surfaces-%s-%d.svg", name, idx ++).c_str());
|
2016-09-26 11:44:23 +00:00
|
|
|
}
|
2014-05-06 08:07:18 +00:00
|
|
|
|
2021-07-29 08:21:50 +00:00
|
|
|
BoundingBox get_extents(const LayerRegion &layer_region)
|
|
|
|
{
|
|
|
|
BoundingBox bbox;
|
2022-10-26 16:41:39 +00:00
|
|
|
if (! layer_region.slices().empty()) {
|
|
|
|
bbox = get_extents(layer_region.slices().surfaces.front());
|
|
|
|
for (auto it = layer_region.slices().surfaces.cbegin() + 1; it != layer_region.slices().surfaces.cend(); ++ it)
|
2021-07-29 08:21:50 +00:00
|
|
|
bbox.merge(get_extents(*it));
|
|
|
|
}
|
|
|
|
return bbox;
|
|
|
|
}
|
|
|
|
|
|
|
|
BoundingBox get_extents(const LayerRegionPtrs &layer_regions)
|
|
|
|
{
|
|
|
|
BoundingBox bbox;
|
|
|
|
if (!layer_regions.empty()) {
|
|
|
|
bbox = get_extents(*layer_regions.front());
|
|
|
|
for (auto it = layer_regions.begin() + 1; it != layer_regions.end(); ++it)
|
|
|
|
bbox.merge(get_extents(**it));
|
|
|
|
}
|
|
|
|
return bbox;
|
|
|
|
}
|
|
|
|
|
2014-05-06 08:07:18 +00:00
|
|
|
}
|