Performance optimizations and some cleanup.

Optional heavy parallelism which is disabled by default. Would like to test it further in a next release cycle.
This commit is contained in:
tamasmeszaros 2019-07-30 17:57:07 +02:00
parent ca1f3dc6af
commit 1ab3268d55
2 changed files with 320 additions and 245 deletions

View File

@ -83,6 +83,42 @@ const unsigned SupportConfig::max_bridges_on_pillar = 3;
using Coordf = double;
using Portion = std::tuple<double, double>;
// Set this to true to enable full parallelism in this module.
// Only the well tested parts will be concurrent if this is set to false.
const constexpr bool USE_FULL_CONCURRENCY = false;
template<bool> struct _ccr {};
template<> struct _ccr<true>
{
using Mutex = SpinMutex;
template<class It, class Fn>
static inline void enumerate(It from, It to, Fn fn)
{
using TN = size_t;
auto iN = to - from;
TN N = iN < 0 ? 0 : TN(iN);
tbb::parallel_for(TN(0), N, [from, fn](TN n) { fn(*(from + n), n); });
}
};
template<> struct _ccr<false>
{
struct Mutex { inline void lock() {} inline void unlock() {} };
template<class It, class Fn>
static inline void enumerate(It from, It to, Fn fn)
{
for (auto it = from; it != to; ++it) fn(*it, it - from);
}
};
using ccr = _ccr<USE_FULL_CONCURRENCY>;
using ccr_seq = _ccr<false>;
using ccr_par = _ccr<true>;
inline Portion make_portion(double a, double b) {
return std::make_tuple(a, b);
}
@ -677,6 +713,7 @@ struct Pad {
}
tmesh.translate(0, 0, float(zlevel));
tmesh.require_shared_vertices();
}
bool empty() const { return tmesh.facets_count() == 0; }
@ -735,7 +772,10 @@ ClusteredPoints cluster(
// The support pad is considered an auxiliary geometry and is not part of the
// merged mesh. It can be retrieved using a dedicated method (pad())
class SLASupportTree::Impl {
std::map<unsigned, Head> m_heads;
// For heads it is beneficial to use the same IDs as for the support points.
std::vector<Head> m_heads;
std::vector<size_t> m_head_indices;
std::vector<Pillar> m_pillars;
std::vector<Junction> m_junctions;
std::vector<Bridge> m_bridges;
@ -743,8 +783,13 @@ class SLASupportTree::Impl {
Controller m_ctl;
Pad m_pad;
using Mutex = ccr::Mutex;
mutable Mutex m_mutex;
mutable TriangleMesh meshcache; mutable bool meshcache_valid = false;
mutable double model_height = 0; // the full height of the model
public:
double ground_level = 0;
@ -753,37 +798,49 @@ public:
const Controller& ctl() const { return m_ctl; }
template<class...Args> Head& add_head(unsigned id, Args&&... args) {
auto el = m_heads.emplace(std::piecewise_construct,
std::forward_as_tuple(id),
std::forward_as_tuple(std::forward<Args>(args)...));
el.first->second.id = id;
template<class...Args> Head& add_head(unsigned id, Args&&... args)
{
std::lock_guard<Mutex> lk(m_mutex);
m_heads.emplace_back(std::forward<Args>(args)...);
m_heads.back().id = id;
if (id >= m_head_indices.size()) m_head_indices.resize(id + 1);
m_head_indices[id] = m_heads.size() - 1;
meshcache_valid = false;
return el.first->second;
return m_heads.back();
}
template<class...Args> Pillar& add_pillar(unsigned headid, Args&&... args) {
auto it = m_heads.find(headid);
assert(it != m_heads.end());
Head& head = it->second;
template<class...Args> Pillar& add_pillar(unsigned headid, Args&&... args)
{
std::lock_guard<Mutex> lk(m_mutex);
assert(headid < m_head_indices.size());
Head &head = m_heads[m_head_indices[headid]];
m_pillars.emplace_back(head, std::forward<Args>(args)...);
Pillar& pillar = m_pillars.back();
pillar.id = long(m_pillars.size() - 1);
head.pillar_id = pillar.id;
pillar.start_junction_id = head.id;
pillar.starts_from_head = true;
meshcache_valid = false;
return m_pillars.back();
}
void increment_bridges(const Pillar& pillar) {
void increment_bridges(const Pillar& pillar)
{
std::lock_guard<Mutex> lk(m_mutex);
assert(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size());
if(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size())
m_pillars[size_t(pillar.id)].bridges++;
}
void increment_links(const Pillar& pillar) {
void increment_links(const Pillar& pillar)
{
std::lock_guard<Mutex> lk(m_mutex);
assert(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size());
if(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size())
@ -792,6 +849,7 @@ public:
template<class...Args> Pillar& add_pillar(Args&&...args)
{
std::lock_guard<Mutex> lk(m_mutex);
m_pillars.emplace_back(std::forward<Args>(args)...);
Pillar& pillar = m_pillars.back();
pillar.id = long(m_pillars.size() - 1);
@ -800,113 +858,125 @@ public:
return m_pillars.back();
}
const Head& pillar_head(long pillar_id) const {
const Head& pillar_head(long pillar_id) const
{
std::lock_guard<Mutex> lk(m_mutex);
assert(pillar_id >= 0 && pillar_id < long(m_pillars.size()));
const Pillar& p = m_pillars[size_t(pillar_id)];
assert(p.starts_from_head && p.start_junction_id >= 0);
auto it = m_heads.find(unsigned(p.start_junction_id));
assert(it != m_heads.end());
return it->second;
assert(size_t(p.start_junction_id) < m_head_indices.size());
return m_heads[m_head_indices[p.start_junction_id]];
}
const Pillar& head_pillar(unsigned headid) const {
auto it = m_heads.find(headid);
assert(it != m_heads.end());
const Head& h = it->second;
const Pillar& head_pillar(unsigned headid) const
{
std::lock_guard<Mutex> lk(m_mutex);
assert(headid < m_head_indices.size());
const Head& h = m_heads[m_head_indices[headid]];
assert(h.pillar_id >= 0 && h.pillar_id < long(m_pillars.size()));
return pillar(h.pillar_id);
return m_pillars[size_t(h.pillar_id)];
}
template<class...Args> const Junction& add_junction(Args&&... args) {
template<class...Args> const Junction& add_junction(Args&&... args)
{
std::lock_guard<Mutex> lk(m_mutex);
m_junctions.emplace_back(std::forward<Args>(args)...);
m_junctions.back().id = long(m_junctions.size() - 1);
meshcache_valid = false;
return m_junctions.back();
}
template<class...Args> const Bridge& add_bridge(Args&&... args) {
template<class...Args> const Bridge& add_bridge(Args&&... args)
{
std::lock_guard<Mutex> lk(m_mutex);
m_bridges.emplace_back(std::forward<Args>(args)...);
m_bridges.back().id = long(m_bridges.size() - 1);
meshcache_valid = false;
return m_bridges.back();
}
template<class...Args>
const CompactBridge& add_compact_bridge(Args&&...args) {
template<class...Args> const CompactBridge& add_compact_bridge(Args&&...args)
{
std::lock_guard<Mutex> lk(m_mutex);
m_compact_bridges.emplace_back(std::forward<Args>(args)...);
m_compact_bridges.back().id = long(m_compact_bridges.size() - 1);
meshcache_valid = false;
return m_compact_bridges.back();
}
const std::map<unsigned, Head>& heads() const { return m_heads; }
Head& head(unsigned idx) {
Head &head(unsigned id)
{
std::lock_guard<Mutex> lk(m_mutex);
assert(id < m_head_indices.size());
meshcache_valid = false;
auto it = m_heads.find(idx);
assert(it != m_heads.end());
return it->second;
}
const std::vector<Pillar>& pillars() const { return m_pillars; }
const std::vector<Bridge>& bridges() const { return m_bridges; }
const std::vector<Junction>& junctions() const { return m_junctions; }
const std::vector<CompactBridge>& compact_bridges() const {
return m_compact_bridges;
return m_heads[m_head_indices[id]];
}
template<class T> inline const Pillar& pillar(T id) const {
static_assert(std::is_integral<T>::value, "Invalid index type");
inline size_t pillarcount() const {
std::lock_guard<Mutex> lk(m_mutex);
return m_pillars.size();
}
template<class T> inline IntegerOnly<T, const Pillar&> pillar(T id) const
{
std::lock_guard<Mutex> lk(m_mutex);
assert(id >= 0 && size_t(id) < m_pillars.size() &&
size_t(id) < std::numeric_limits<size_t>::max());
return m_pillars[size_t(id)];
}
const Pad& create_pad(const TriangleMesh& object_supports,
const ExPolygons& modelbase,
const PoolConfig& cfg) {
const Pad &create_pad(const TriangleMesh &object_supports,
const ExPolygons & modelbase,
const PoolConfig & cfg)
{
m_pad = Pad(object_supports, modelbase, ground_level, cfg);
return m_pad;
}
void remove_pad() {
m_pad = Pad();
}
void remove_pad() { m_pad = Pad(); }
const Pad& pad() const { return m_pad; }
// WITHOUT THE PAD!!!
const TriangleMesh& merged_mesh() const {
if(meshcache_valid) return meshcache;
const TriangleMesh &merged_mesh() const
{
if (meshcache_valid) return meshcache;
Contour3D merged;
for(auto& headel : heads()) {
if(m_ctl.stopcondition()) break;
if(headel.second.is_valid())
merged.merge(headel.second.mesh);
for (auto &head : m_heads) {
if (m_ctl.stopcondition()) break;
if (head.is_valid()) merged.merge(head.mesh);
}
for(auto& stick : pillars()) {
if(m_ctl.stopcondition()) break;
for (auto &stick : m_pillars) {
if (m_ctl.stopcondition()) break;
merged.merge(stick.mesh);
merged.merge(stick.base);
}
for(auto& j : junctions()) {
if(m_ctl.stopcondition()) break;
for (auto &j : m_junctions) {
if (m_ctl.stopcondition()) break;
merged.merge(j.mesh);
}
for(auto& cb : compact_bridges()) {
if(m_ctl.stopcondition()) break;
for (auto &cb : m_compact_bridges) {
if (m_ctl.stopcondition()) break;
merged.merge(cb.mesh);
}
for(auto& bs : bridges()) {
if(m_ctl.stopcondition()) break;
for (auto &bs : m_bridges) {
if (m_ctl.stopcondition()) break;
merged.merge(bs.mesh);
}
if(m_ctl.stopcondition()) {
if (m_ctl.stopcondition()) {
// In case of failure we have to return an empty mesh
meshcache = TriangleMesh();
return meshcache;
@ -918,42 +988,44 @@ public:
// which will need this.
if (!meshcache.empty()) meshcache.require_shared_vertices();
// TODO: Is this necessary?
//meshcache.repair();
BoundingBoxf3&& bb = meshcache.bounding_box();
model_height = bb.max(Z) - bb.min(Z);
BoundingBoxf3 &&bb = meshcache.bounding_box();
model_height = bb.max(Z) - bb.min(Z);
meshcache_valid = true;
return meshcache;
}
// WITH THE PAD
double full_height() const {
if(merged_mesh().empty() && !pad().empty())
double full_height() const
{
if (merged_mesh().empty() && !pad().empty())
return get_pad_fullheight(pad().cfg);
double h = mesh_height();
if(!pad().empty()) h += sla::get_pad_elevation(pad().cfg);
if (!pad().empty()) h += sla::get_pad_elevation(pad().cfg);
return h;
}
// WITHOUT THE PAD!!!
double mesh_height() const {
if(!meshcache_valid) merged_mesh();
double mesh_height() const
{
if (!meshcache_valid) merged_mesh();
return model_height;
}
// Intended to be called after the generation is fully complete
void clear_support_data() {
void merge_and_cleanup()
{
merged_mesh(); // in case the mesh is not generated, it should be...
m_heads.clear();
m_pillars.clear();
m_junctions.clear();
m_bridges.clear();
m_compact_bridges.clear();
}
// Doing clear() does not garantee to release the memory.
m_heads = {};
m_head_indices = {};
m_pillars = {};
m_junctions = {};
m_bridges = {};
m_compact_bridges = {};
}
};
// This function returns the position of the centroid in the input 'clust'
@ -1122,11 +1194,10 @@ class SLASupportTree::Algorithm {
// Now a and b vectors are perpendicular to v and to each other.
// Together they define the plane where we have to iterate with the
// given angles in the 'phis' vector
tbb::parallel_for(size_t(0), phis.size(),
[&phis, &hits, &m, sd, r_pin, r_back, s, a, b, c]
(size_t i)
ccr_par::enumerate(phis.begin(), phis.end(),
[&hits, &m, sd, r_pin, r_back, s, a, b, c]
(double phi, size_t i)
{
double& phi = phis[i];
double sinphi = std::sin(phi);
double cosphi = std::cos(phi);
@ -1226,11 +1297,10 @@ class SLASupportTree::Algorithm {
// Hit results
std::array<HitResult, SAMPLES> hits;
tbb::parallel_for(size_t(0), phis.size(),
[&m, &phis, a, b, sd, dir, r, s, ins_check, &hits]
(size_t i)
ccr_par::enumerate(phis.begin(), phis.end(),
[&m, a, b, sd, dir, r, s, ins_check, &hits]
(double phi, size_t i)
{
double& phi = phis[i];
double sinphi = std::sin(phi);
double cosphi = std::cos(phi);
@ -1458,7 +1528,7 @@ class SLASupportTree::Algorithm {
if(nearest_id >= 0) {
auto nearpillarID = unsigned(nearest_id);
if(nearpillarID < m_result.pillars().size()) {
if(nearpillarID < m_result.pillarcount()) {
if(!connect_to_nearpillar(head, nearpillarID)) {
nearest_id = -1; // continue searching
spindex.remove(ne); // without the current pillar
@ -1647,11 +1717,17 @@ public:
using libnest2d::opt::GeneticOptimizer;
using libnest2d::opt::StopCriteria;
for(unsigned i = 0, fidx = 0; i < filtered_indices.size(); ++i)
ccr::Mutex mutex;
auto addfn = [&mutex](PtIndices &container, unsigned val) {
std::lock_guard<ccr::Mutex> lk(mutex);
container.emplace_back(val);
};
ccr::enumerate(filtered_indices.begin(), filtered_indices.end(),
[this, &nmls, addfn](unsigned fidx, size_t i)
{
m_thr();
fidx = filtered_indices[i];
auto n = nmls.row(i);
// for all normals we generate the spherical coordinates and
@ -1709,20 +1785,20 @@ public:
auto oresult = solver.optimize_max(
[this, pin_r, w, hp](double plr, double azm)
{
auto n = Vec3d(std::cos(azm) * std::sin(plr),
std::sin(azm) * std::sin(plr),
std::cos(plr)).normalized();
{
auto n = Vec3d(std::cos(azm) * std::sin(plr),
std::sin(azm) * std::sin(plr),
std::cos(plr)).normalized();
double score = pinhead_mesh_intersect( hp, n, pin_r,
m_cfg.head_back_radius_mm, w);
double score = pinhead_mesh_intersect(
hp, n, pin_r, m_cfg.head_back_radius_mm, w);
return score;
},
initvals(polar, azimuth), // start with what we have
bound(3*PI/4, PI), // Must not exceed the tilt limit
bound(-PI, PI) // azimuth can be a full search
);
return score;
},
initvals(polar, azimuth), // start with what we have
bound(3*PI/4, PI), // Must not exceed the tilt limit
bound(-PI, PI) // azimuth can be a full search
);
if(oresult.score > w) {
polar = std::get<0>(oresult.optimum);
@ -1740,18 +1816,18 @@ public:
if (t.distance() > w) {
// Check distance from ground, we might have zero elevation.
if (hp(Z) + w * nn(Z) < m_result.ground_level) {
m_iheadless.emplace_back(fidx);
addfn(m_iheadless, fidx);
} else {
// mark the point for needing a head.
m_iheads.emplace_back(fidx);
addfn(m_iheads, fidx);
}
} else if (polar >= 3 * PI / 4) {
// Headless supports do not tilt like the headed ones
// so the normal should point almost to the ground.
m_iheadless.emplace_back(fidx);
addfn(m_iheadless, fidx);
}
}
}
});
m_thr();
}
@ -1939,11 +2015,17 @@ public:
};
std::vector<unsigned> modelpillars;
ccr::Mutex mutex;
// TODO: connect these to the ground pillars if possible
for(auto item : m_iheads_onmodel) { m_thr();
unsigned idx = item.first;
EigenMesh3D::hit_result hit = item.second;
ccr::enumerate(m_iheads_onmodel.begin(), m_iheads_onmodel.end(),
[this, routedown, &modelpillars, &mutex]
(const std::pair<unsigned, EigenMesh3D::hit_result> &el,
size_t)
{
m_thr();
unsigned idx = el.first;
EigenMesh3D::hit_result hit = el.second;
auto& head = m_result.head(idx);
Vec3d hjp = head.junction_point();
@ -1952,7 +2034,7 @@ public:
// Search nearby pillar
// /////////////////////////////////////////////////////////////////
if(search_pillar_and_connect(head)) { head.transform(); continue; }
if(search_pillar_and_connect(head)) { head.transform(); return; }
// /////////////////////////////////////////////////////////////////
// Try straight path
@ -1974,7 +2056,7 @@ public:
}
if(std::isinf(tdown)) { // we heave found a route to the ground
routedown(head, head.dir, d); continue;
routedown(head, head.dir, d); return;
}
// /////////////////////////////////////////////////////////////////
@ -2036,7 +2118,7 @@ public:
}
if(std::isinf(tdown)) { // we heave found a route to the ground
routedown(head, bridgedir, d); continue;
routedown(head, bridgedir, d); return;
}
// /////////////////////////////////////////////////////////////////
@ -2079,8 +2161,9 @@ public:
pill.base = tailhead.mesh;
// Experimental: add the pillar to the index for cascading
std::lock_guard<ccr::Mutex> lk(mutex);
modelpillars.emplace_back(unsigned(pill.id));
continue;
return;
}
// We have failed to route this head.
@ -2088,7 +2171,7 @@ public:
<< "Failed to route model facing support point."
<< " ID: " << idx;
head.invalidate();
}
});
for(auto pillid : modelpillars) {
auto& pillar = m_result.pillar(pillid);
@ -2176,7 +2259,7 @@ public:
// Search for the pair amongst the remembered pairs
if(pairs.find(hashval) != pairs.end()) continue;
const Pillar& neighborpillar = m_result.pillars()[re.second];
const Pillar& neighborpillar = m_result.pillar(re.second);
// this neighbor is occupied, skip
if(neighborpillar.links >= neighbors) continue;
@ -2212,7 +2295,7 @@ public:
// lonely pillars. One or even two additional pillar might get inserted
// depending on the length of the lonely pillar.
size_t pillarcount = m_result.pillars().size();
size_t pillarcount = m_result.pillarcount();
// Again, go through all pillars, this time in the whole support tree
// not just the index.
@ -2364,6 +2447,8 @@ public:
m_result.add_compact_bridge(sp, ej, n, R, !std::isinf(dist));
}
}
void merge_result() { m_result.merge_and_cleanup(); }
};
bool SLASupportTree::generate(const std::vector<SupportPoint> &support_points,
@ -2386,6 +2471,7 @@ bool SLASupportTree::generate(const std::vector<SupportPoint> &support_points,
ROUTING_NONGROUND,
CASCADE_PILLARS,
HEADLESS,
MERGE_RESULT,
DONE,
ABORT,
NUM_STEPS
@ -2413,6 +2499,8 @@ bool SLASupportTree::generate(const std::vector<SupportPoint> &support_points,
std::bind(&Algorithm::routing_headless, &alg),
std::bind(&Algorithm::merge_result, &alg),
[] () {
// Done
},
@ -2427,7 +2515,7 @@ bool SLASupportTree::generate(const std::vector<SupportPoint> &support_points,
if(cfg.ground_facing_only) {
program[ROUTING_NONGROUND] = []() {
BOOST_LOG_TRIVIAL(info)
<< "Skipping model-facing supports as requested.";
<< "Skipping model-facing supports as requested.";
};
program[HEADLESS] = []() {
BOOST_LOG_TRIVIAL(info) << "Skipping headless stick generation as"
@ -2446,6 +2534,7 @@ bool SLASupportTree::generate(const std::vector<SupportPoint> &support_points,
"Routing supports to model surface",
"Interconnecting pillars",
"Processing small holes",
"Merging support mesh",
"Done",
"Abort"
};
@ -2458,7 +2547,8 @@ bool SLASupportTree::generate(const std::vector<SupportPoint> &support_points,
60,
70,
80,
90,
85,
99,
100,
0
};
@ -2473,11 +2563,13 @@ bool SLASupportTree::generate(const std::vector<SupportPoint> &support_points,
case ROUTING_GROUND: pc = ROUTING_NONGROUND; break;
case ROUTING_NONGROUND: pc = CASCADE_PILLARS; break;
case CASCADE_PILLARS: pc = HEADLESS; break;
case HEADLESS: pc = DONE; break;
case HEADLESS: pc = MERGE_RESULT; break;
case MERGE_RESULT: pc = DONE; break;
case DONE:
case ABORT: break;
default: ;
}
ctl.statuscb(stepstate[pc], stepstr[pc]);
};
@ -2504,44 +2596,40 @@ void SLASupportTree::merged_mesh_with_pad(TriangleMesh &outmesh) const {
outmesh.merge(get_pad());
}
std::vector<ExPolygons> SLASupportTree::slice(float layerh, float init_layerh) const
std::vector<ExPolygons> SLASupportTree::slice(
const std::vector<float> &heights, float cr) const
{
if(init_layerh < 0) init_layerh = layerh;
auto& stree = get();
const TriangleMesh &sup_mesh = m_impl->merged_mesh();
const TriangleMesh &pad_mesh = get_pad();
const auto modelh = float(stree.full_height());
auto gndlvl = float(this->m_impl->ground_level);
const Pad& pad = m_impl->pad();
if(!pad.empty()) gndlvl -= float(get_pad_elevation(pad.cfg));
std::vector<float> heights;
heights.reserve(size_t(modelh/layerh) + 1);
for(float h = gndlvl + init_layerh; h < gndlvl + modelh; h += layerh) {
heights.emplace_back(h);
std::vector<ExPolygons> sup_slices;
if (!sup_mesh.empty()) {
TriangleMeshSlicer sup_slicer(&sup_mesh);
sup_slicer.slice(heights, cr, &sup_slices, m_impl->ctl().cancelfn);
}
TriangleMesh fullmesh = m_impl->merged_mesh();
fullmesh.merge(get_pad());
if (!fullmesh.empty()) fullmesh.require_shared_vertices();
TriangleMeshSlicer slicer(&fullmesh);
std::vector<ExPolygons> ret;
slicer.slice(heights, 0.f, &ret, get().ctl().cancelfn);
auto bb = pad_mesh.bounding_box();
auto maxzit = std::upper_bound(heights.begin(), heights.end(), bb.max.z());
return ret;
}
auto padgrid = reserve_vector<float>(heights.end() - maxzit);
std::copy(heights.begin(), maxzit, std::back_inserter(padgrid));
std::vector<ExPolygons> SLASupportTree::slice(const std::vector<float> &heights,
float cr) const
{
TriangleMesh fullmesh = m_impl->merged_mesh();
fullmesh.merge(get_pad());
if (!fullmesh.empty()) fullmesh.require_shared_vertices();
TriangleMeshSlicer slicer(&fullmesh);
std::vector<ExPolygons> ret;
slicer.slice(heights, cr, &ret, get().ctl().cancelfn);
std::vector<ExPolygons> pad_slices;
if (!pad_mesh.empty()) {
TriangleMeshSlicer pad_slicer(&pad_mesh);
pad_slicer.slice(padgrid, cr, &pad_slices, m_impl->ctl().cancelfn);
}
return ret;
size_t len = std::min(heights.size(), pad_slices.size());
len = std::min(len, sup_slices.size());
for (size_t i = 0; i < len; ++i) {
std::copy(pad_slices[i].begin(), pad_slices[i].end(),
std::back_inserter(sup_slices[i]));
pad_slices[i] = {};
}
return sup_slices;
}
const TriangleMesh &SLASupportTree::add_pad(const ExPolygons& modelbase,
@ -2568,16 +2656,6 @@ SLASupportTree::SLASupportTree(const std::vector<SupportPoint> &points,
{
m_impl->ground_level = emesh.ground_level() - cfg.object_elevation_mm;
generate(points, emesh, cfg, ctl);
m_impl->clear_support_data();
}
SLASupportTree::SLASupportTree(const SLASupportTree &c):
m_impl(new Impl(*c.m_impl)) {}
SLASupportTree &SLASupportTree::operator=(const SLASupportTree &c)
{
m_impl = make_unique<Impl>(*c.m_impl);
return *this;
}
SLASupportTree::~SLASupportTree() {}

View File

@ -172,8 +172,8 @@ public:
const SupportConfig& cfg = {},
const Controller& ctl = {});
SLASupportTree(const SLASupportTree&);
SLASupportTree& operator=(const SLASupportTree&);
SLASupportTree(const SLASupportTree&) = delete;
SLASupportTree& operator=(const SLASupportTree&) = delete;
~SLASupportTree();
@ -183,9 +183,6 @@ public:
void merged_mesh_with_pad(TriangleMesh&) const;
/// Get the sliced 2d layers of the support geometry.
std::vector<ExPolygons> slice(float layerh, float init_layerh = -1.0) const;
std::vector<ExPolygons> slice(const std::vector<float> &,
float closing_radius) const;