diff --git a/src/libnest2d/include/libnest2d/backends/clipper/CMakeLists.txt b/src/libnest2d/include/libnest2d/backends/clipper/CMakeLists.txt index 995afcc76d..462d1dd068 100644 --- a/src/libnest2d/include/libnest2d/backends/clipper/CMakeLists.txt +++ b/src/libnest2d/include/libnest2d/backends/clipper/CMakeLists.txt @@ -64,6 +64,7 @@ endif() target_include_directories(ClipperBackend INTERFACE ${Boost_INCLUDE_DIRS} ) target_sources(ClipperBackend INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/geometries.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/clipper_polygon.hpp ${SRC_DIR}/libnest2d/utils/boost_alg.hpp ) target_compile_definitions(ClipperBackend INTERFACE LIBNEST2D_BACKEND_CLIPPER) diff --git a/src/libnest2d/include/libnest2d/backends/clipper/clipper_polygon.hpp b/src/libnest2d/include/libnest2d/backends/clipper/clipper_polygon.hpp new file mode 100644 index 0000000000..e9fbfbd18a --- /dev/null +++ b/src/libnest2d/include/libnest2d/backends/clipper/clipper_polygon.hpp @@ -0,0 +1,72 @@ +#ifndef CLIPPER_POLYGON_HPP +#define CLIPPER_POLYGON_HPP + +#include + +namespace ClipperLib { + +struct Polygon { + Path Contour; + Paths Holes; + + inline Polygon() = default; + + inline explicit Polygon(const Path& cont): Contour(cont) {} + inline explicit Polygon(const Paths& holes): + Holes(holes) {} + inline Polygon(const Path& cont, const Paths& holes): + Contour(cont), Holes(holes) {} + + inline explicit Polygon(Path&& cont): Contour(std::move(cont)) {} + inline explicit Polygon(Paths&& holes): Holes(std::move(holes)) {} + inline Polygon(Path&& cont, Paths&& holes): + Contour(std::move(cont)), Holes(std::move(holes)) {} +}; + +inline IntPoint& operator +=(IntPoint& p, const IntPoint& pa ) { + // This could be done with SIMD + p.X += pa.X; + p.Y += pa.Y; + return p; +} + +inline IntPoint operator+(const IntPoint& p1, const IntPoint& p2) { + IntPoint ret = p1; + ret += p2; + return ret; +} + +inline IntPoint& operator -=(IntPoint& p, const IntPoint& pa ) { + p.X -= pa.X; + p.Y -= pa.Y; + return p; +} + +inline IntPoint operator -(IntPoint& p ) { + IntPoint ret = p; + ret.X = -ret.X; + ret.Y = -ret.Y; + return ret; +} + +inline IntPoint operator-(const IntPoint& p1, const IntPoint& p2) { + IntPoint ret = p1; + ret -= p2; + return ret; +} + +inline IntPoint& operator *=(IntPoint& p, const IntPoint& pa ) { + p.X *= pa.X; + p.Y *= pa.Y; + return p; +} + +inline IntPoint operator*(const IntPoint& p1, const IntPoint& p2) { + IntPoint ret = p1; + ret *= p2; + return ret; +} + +} + +#endif // CLIPPER_POLYGON_HPP diff --git a/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp b/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp index 9f881e7e0d..232668f610 100644 --- a/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp +++ b/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp @@ -10,84 +10,15 @@ #include #include -#include - -namespace ClipperLib { -using PointImpl = IntPoint; -using PathImpl = Path; -using HoleStore = std::vector; - -struct PolygonImpl { - PathImpl Contour; - HoleStore Holes; - - inline PolygonImpl() = default; - - inline explicit PolygonImpl(const PathImpl& cont): Contour(cont) {} - inline explicit PolygonImpl(const HoleStore& holes): - Holes(holes) {} - inline PolygonImpl(const Path& cont, const HoleStore& holes): - Contour(cont), Holes(holes) {} - - inline explicit PolygonImpl(PathImpl&& cont): Contour(std::move(cont)) {} - inline explicit PolygonImpl(HoleStore&& holes): Holes(std::move(holes)) {} - inline PolygonImpl(Path&& cont, HoleStore&& holes): - Contour(std::move(cont)), Holes(std::move(holes)) {} -}; - -inline PointImpl& operator +=(PointImpl& p, const PointImpl& pa ) { - // This could be done with SIMD - p.X += pa.X; - p.Y += pa.Y; - return p; -} - -inline PointImpl operator+(const PointImpl& p1, const PointImpl& p2) { - PointImpl ret = p1; - ret += p2; - return ret; -} - -inline PointImpl& operator -=(PointImpl& p, const PointImpl& pa ) { - p.X -= pa.X; - p.Y -= pa.Y; - return p; -} - -inline PointImpl operator -(PointImpl& p ) { - PointImpl ret = p; - ret.X = -ret.X; - ret.Y = -ret.Y; - return ret; -} - -inline PointImpl operator-(const PointImpl& p1, const PointImpl& p2) { - PointImpl ret = p1; - ret -= p2; - return ret; -} - -inline PointImpl& operator *=(PointImpl& p, const PointImpl& pa ) { - p.X *= pa.X; - p.Y *= pa.Y; - return p; -} - -inline PointImpl operator*(const PointImpl& p1, const PointImpl& p2) { - PointImpl ret = p1; - ret *= p2; - return ret; -} - -} +#include "clipper_polygon.hpp" namespace libnest2d { // Aliases for convinience -using ClipperLib::PointImpl; -using ClipperLib::PathImpl; -using ClipperLib::PolygonImpl; -using ClipperLib::HoleStore; +using PointImpl = ClipperLib::IntPoint; +using PathImpl = ClipperLib::Path; +using HoleStore = ClipperLib::Paths; +using PolygonImpl = ClipperLib::Polygon; // Type of coordinate units used by Clipper template<> struct CoordType { @@ -158,33 +89,24 @@ template<> inline TCoord& y(PointImpl& p) #define DISABLE_BOOST_AREA namespace _smartarea { + template inline double area(const PolygonImpl& /*sh*/) { return std::nan(""); } template<> -inline double area(const PolygonImpl& sh) { - double a = 0; - - std::for_each(sh.Holes.begin(), sh.Holes.end(), [&a](const PathImpl& h) - { - a -= ClipperLib::Area(h); +inline double area(const PolygonImpl& sh) { + return std::accumulate(sh.Holes.begin(), sh.Holes.end(), + ClipperLib::Area(sh.Contour), + [](double a, const ClipperLib::Path& pt){ + return a + ClipperLib::Area(pt); }); - - return -ClipperLib::Area(sh.Contour) + a; } template<> -inline double area(const PolygonImpl& sh) { - double a = 0; - - std::for_each(sh.Holes.begin(), sh.Holes.end(), [&a](const PathImpl& h) - { - a += ClipperLib::Area(h); - }); - - return ClipperLib::Area(sh.Contour) + a; +inline double area(const PolygonImpl& sh) { + return -area(sh); } } @@ -228,9 +150,10 @@ template<> inline void offset(PolygonImpl& sh, TCoord distance) // but throwing would be an overkill. Instead, we should warn the // caller about the inability to create correct geometries if(!found_the_contour) { - sh.Contour = r; + sh.Contour = std::move(r); ClipperLib::ReversePath(sh.Contour); - sh.Contour.push_back(sh.Contour.front()); + auto front_p = sh.Contour.front(); + sh.Contour.emplace_back(std::move(front_p)); found_the_contour = true; } else { dout() << "Warning: offsetting result is invalid!"; @@ -240,9 +163,10 @@ template<> inline void offset(PolygonImpl& sh, TCoord distance) // TODO If there are multiple contours we can't be sure which hole // belongs to the first contour. (But in this case the situation is // bad enough to let it go...) - sh.Holes.push_back(r); + sh.Holes.emplace_back(std::move(r)); ClipperLib::ReversePath(sh.Holes.back()); - sh.Holes.back().push_back(sh.Holes.back().front()); + auto front_p = sh.Holes.back().front(); + sh.Holes.back().emplace_back(std::move(front_p)); } } } @@ -390,34 +314,53 @@ inline void rotate(PolygonImpl& sh, const Radians& rads) } // namespace shapelike #define DISABLE_BOOST_NFP_MERGE -inline std::vector _merge(ClipperLib::Clipper& clipper) { +inline std::vector clipper_execute( + ClipperLib::Clipper& clipper, + ClipperLib::ClipType clipType, + ClipperLib::PolyFillType subjFillType = ClipperLib::pftEvenOdd, + ClipperLib::PolyFillType clipFillType = ClipperLib::pftEvenOdd) +{ shapelike::Shapes retv; ClipperLib::PolyTree result; - clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNegative); + clipper.Execute(clipType, result, subjFillType, clipFillType); + retv.reserve(static_cast(result.Total())); std::function processHole; auto processPoly = [&retv, &processHole](ClipperLib::PolyNode *pptr) { - PolygonImpl poly(pptr->Contour); - poly.Contour.push_back(poly.Contour.front()); + PolygonImpl poly; + poly.Contour.swap(pptr->Contour); + + assert(!pptr->IsHole()); + + if(pptr->IsOpen()) { + auto front_p = poly.Contour.front(); + poly.Contour.emplace_back(front_p); + } + for(auto h : pptr->Childs) { processHole(h, poly); } retv.push_back(poly); }; processHole = [&processPoly](ClipperLib::PolyNode *pptr, PolygonImpl& poly) { - poly.Holes.push_back(pptr->Contour); - poly.Holes.back().push_back(poly.Holes.back().front()); + poly.Holes.emplace_back(std::move(pptr->Contour)); + + assert(pptr->IsHole()); + + if(pptr->IsOpen()) { + auto front_p = poly.Holes.back().front(); + poly.Holes.back().emplace_back(front_p); + } + for(auto c : pptr->Childs) processPoly(c); }; auto traverse = [&processPoly] (ClipperLib::PolyNode *node) { - for(auto ch : node->Childs) { - processPoly(ch); - } + for(auto ch : node->Childs) processPoly(ch); }; traverse(&result); @@ -438,14 +381,13 @@ merge(const std::vector& shapes) for(auto& path : shapes) { valid &= clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed); - for(auto& hole : path.Holes) { - valid &= clipper.AddPath(hole, ClipperLib::ptSubject, closed); - } + for(auto& h : path.Holes) + valid &= clipper.AddPath(h, ClipperLib::ptSubject, closed); } if(!valid) throw GeometryException(GeomErr::MERGE); - return _merge(clipper); + return clipper_execute(clipper, ClipperLib::ctUnion, ClipperLib::pftNegative); } } diff --git a/src/libnest2d/include/libnest2d/libnest2d.hpp b/src/libnest2d/include/libnest2d/libnest2d.hpp index 49baa65f25..c7b252e5d5 100644 --- a/src/libnest2d/include/libnest2d/libnest2d.hpp +++ b/src/libnest2d/include/libnest2d/libnest2d.hpp @@ -966,7 +966,7 @@ private: for(size_t i = 0; i < pckgrp.size(); i++) { auto items = pckgrp[i]; - pg.push_back({}); + pg.emplace_back(); pg[i].reserve(items.size()); for(Item& itemA : items) { diff --git a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp index 6fb717a7a6..91affe9786 100644 --- a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp +++ b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp @@ -261,7 +261,7 @@ template class EdgeCache { while(next != endit) { contour_.emap.emplace_back(*(first++), *(next++)); contour_.full_distance += contour_.emap.back().length(); - contour_.distances.push_back(contour_.full_distance); + contour_.distances.emplace_back(contour_.full_distance); } } @@ -276,10 +276,10 @@ template class EdgeCache { while(next != endit) { hc.emap.emplace_back(*(first++), *(next++)); hc.full_distance += hc.emap.back().length(); - hc.distances.push_back(hc.full_distance); + hc.distances.emplace_back(hc.full_distance); } - holes_.push_back(hc); + holes_.emplace_back(std::move(hc)); } } diff --git a/src/libnest2d/include/libnest2d/placers/placer_boilerplate.hpp b/src/libnest2d/include/libnest2d/placers/placer_boilerplate.hpp index 309a5007de..abcd861830 100644 --- a/src/libnest2d/include/libnest2d/placers/placer_boilerplate.hpp +++ b/src/libnest2d/include/libnest2d/placers/placer_boilerplate.hpp @@ -63,7 +63,7 @@ public: bool pack(Item& item, const Range& rem = Range()) { auto&& r = static_cast(this)->trypack(item, rem); if(r) { - items_.push_back(*(r.item_ptr_)); + items_.emplace_back(*(r.item_ptr_)); farea_valid_ = false; } return r; @@ -78,7 +78,7 @@ public: if(r) { r.item_ptr_->translation(r.move_); r.item_ptr_->rotation(r.rot_); - items_.push_back(*(r.item_ptr_)); + items_.emplace_back(*(r.item_ptr_)); farea_valid_ = false; } } diff --git a/src/libnest2d/include/libnest2d/selections/djd_heuristic.hpp b/src/libnest2d/include/libnest2d/selections/djd_heuristic.hpp index b03534dc46..25007e580e 100644 --- a/src/libnest2d/include/libnest2d/selections/djd_heuristic.hpp +++ b/src/libnest2d/include/libnest2d/selections/djd_heuristic.hpp @@ -667,7 +667,7 @@ public: addBin(); ItemList& not_packed = not_packeds[b]; for(unsigned idx = b; idx < store_.size(); idx+=bincount_guess) { - not_packed.push_back(store_[idx]); + not_packed.emplace_back(store_[idx]); } } diff --git a/src/libnest2d/include/libnest2d/utils/boost_alg.hpp b/src/libnest2d/include/libnest2d/utils/boost_alg.hpp index a6988ca007..baf1c6a105 100644 --- a/src/libnest2d/include/libnest2d/utils/boost_alg.hpp +++ b/src/libnest2d/include/libnest2d/utils/boost_alg.hpp @@ -463,7 +463,7 @@ template<> inline std::string serialize( auto& v = *it; hf.emplace_back(getX(v)*scale, getY(v)*scale); }; - holes.push_back(hf); + holes.emplace_back(std::move(hf)); } Polygonf poly; diff --git a/src/libnest2d/tests/printer_parts.h b/src/libnest2d/tests/printer_parts.h index b9a4eb8fab..1e65826bd3 100644 --- a/src/libnest2d/tests/printer_parts.h +++ b/src/libnest2d/tests/printer_parts.h @@ -2,36 +2,10 @@ #define PRINTER_PARTS_H #include -#include - -#ifndef CLIPPER_BACKEND_HPP -namespace ClipperLib { -using PointImpl = IntPoint; -using PathImpl = Path; -using HoleStore = std::vector; - -struct PolygonImpl { - PathImpl Contour; - HoleStore Holes; - - inline PolygonImpl() {} - - inline explicit PolygonImpl(const PathImpl& cont): Contour(cont) {} - inline explicit PolygonImpl(const HoleStore& holes): - Holes(holes) {} - inline PolygonImpl(const Path& cont, const HoleStore& holes): - Contour(cont), Holes(holes) {} - - inline explicit PolygonImpl(PathImpl&& cont): Contour(std::move(cont)) {} - inline explicit PolygonImpl(HoleStore&& holes): Holes(std::move(holes)) {} - inline PolygonImpl(Path&& cont, HoleStore&& holes): - Contour(std::move(cont)), Holes(std::move(holes)) {} -}; -} -#endif +#include using TestData = std::vector; -using TestDataEx = std::vector; +using TestDataEx = std::vector; extern const TestData PRINTER_PART_POLYGONS; extern const TestData STEGOSAUR_POLYGONS; diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index f9db9fea0b..b5b9a008d0 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -61,7 +61,7 @@ Model& Model::assign_copy(Model &&rhs) this->objects = std::move(rhs.objects); for (ModelObject *model_object : this->objects) model_object->set_model(this); - rhs.objects.clear(); + rhs.objects.clear(); return *this; } @@ -651,7 +651,7 @@ ModelObject& ModelObject::assign_copy(ModelObject &&rhs) for (ModelInstance *model_instance : this->instances) model_instance->set_model_object(this); - return *this; + return *this; } void ModelObject::assign_new_unique_ids_recursive() @@ -970,8 +970,8 @@ Polygon ModelObject::convex_hull_2d(const Transform3d &trafo_instance) } } } - std::sort(pts.begin(), pts.end(), [](const Point& a, const Point& b) { return a(0) < b(0) || (a(0) == b(0) && a(1) < b(1)); }); - pts.erase(std::unique(pts.begin(), pts.end(), [](const Point& a, const Point& b) { return a(0) == b(0) && a(1) == b(1); }), pts.end()); + std::sort(pts.begin(), pts.end(), [](const Point& a, const Point& b) { return a(0) < b(0) || (a(0) == b(0) && a(1) < b(1)); }); + pts.erase(std::unique(pts.begin(), pts.end(), [](const Point& a, const Point& b) { return a(0) == b(0) && a(1) == b(1); }), pts.end()); Polygon hull; int n = (int)pts.size(); @@ -1291,11 +1291,11 @@ void ModelObject::split(ModelObjectPtrs* new_objects) // XXX: this seems to be the only real usage of m_model, maybe refactor this so that it's not needed? ModelObject* new_object = m_model->add_object(); - new_object->name = this->name; - new_object->config = this->config; - new_object->instances.reserve(this->instances.size()); - for (const ModelInstance *model_instance : this->instances) - new_object->add_instance(*model_instance); + new_object->name = this->name; + new_object->config = this->config; + new_object->instances.reserve(this->instances.size()); + for (const ModelInstance *model_instance : this->instances) + new_object->add_instance(*model_instance); ModelVolume* new_vol = new_object->add_volume(*volume, std::move(*mesh)); #if !ENABLE_VOLUMES_CENTERING_FIXES new_vol->center_geometry(); @@ -1467,9 +1467,9 @@ int ModelVolume::extruder_id() const bool ModelVolume::is_splittable() const { - // the call mesh.has_multiple_patches() is expensive, so cache the value to calculate it only once + // the call mesh.is_splittable() is expensive, so cache the value to calculate it only once if (m_is_splittable == -1) - m_is_splittable = (int)mesh.has_multiple_patches(); + m_is_splittable = (int)mesh.is_splittable(); return m_is_splittable == 1; } @@ -1609,6 +1609,7 @@ void ModelVolume::rotate(double angle, Axis axis) case X: { rotate(angle, Vec3d::UnitX()); break; } case Y: { rotate(angle, Vec3d::UnitY()); break; } case Z: { rotate(angle, Vec3d::UnitZ()); break; } + default: break; } } @@ -1625,6 +1626,7 @@ void ModelVolume::mirror(Axis axis) case X: { mirror(0) *= -1.0; break; } case Y: { mirror(1) *= -1.0; break; } case Z: { mirror(2) *= -1.0; break; } + default: break; } set_mirror(mirror); } @@ -1711,7 +1713,6 @@ bool model_object_list_extended(const Model &model_old, const Model &model_new) bool model_volume_list_changed(const ModelObject &model_object_old, const ModelObject &model_object_new, const ModelVolumeType type) { - bool modifiers_differ = false; size_t i_old, i_new; for (i_old = 0, i_new = 0; i_old < model_object_old.volumes.size() && i_new < model_object_new.volumes.size();) { const ModelVolume &mv_old = *model_object_old.volumes[i_old]; diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp index 4942e35b60..54627ba86b 100644 --- a/src/libslic3r/ModelArrange.cpp +++ b/src/libslic3r/ModelArrange.cpp @@ -574,7 +574,7 @@ ShapeData2D projectModelFromTop(const Slic3r::Model &model) { for(ModelInstance* objinst : objptr->instances) { if(objinst) { - ClipperLib::PolygonImpl pn; + ClipperLib::Polygon pn; pn.Contour = clpath; // Efficient conversion to item. diff --git a/src/libslic3r/PrintExport.hpp b/src/libslic3r/PrintExport.hpp index cdb2cc0089..04b993a529 100644 --- a/src/libslic3r/PrintExport.hpp +++ b/src/libslic3r/PrintExport.hpp @@ -42,8 +42,9 @@ template class FilePrinter { public: - // Draw an ExPolygon which is a polygon inside a slice on the specified layer. + // Draw a polygon which is a polygon inside a slice on the specified layer. void draw_polygon(const ExPolygon& p, unsigned lyr); + void draw_polygon(const ClipperLib::Polygon& p, unsigned lyr); // Tell the printer how many layers should it consider. void layers(unsigned layernum); @@ -221,6 +222,11 @@ public: m_layers_rst[lyr].raster.draw(p); } + inline void draw_polygon(const ClipperLib::Polygon& p, unsigned lyr) { + assert(lyr < m_layers_rst.size()); + m_layers_rst[lyr].raster.draw(p); + } + inline void begin_layer(unsigned lyr) { if(m_layers_rst.size() <= lyr) m_layers_rst.resize(lyr+1); m_layers_rst[lyr].raster.reset(m_res, m_pxdim, m_o); diff --git a/src/libslic3r/Rasterizer/Rasterizer.cpp b/src/libslic3r/Rasterizer/Rasterizer.cpp index c28e113524..496a584a24 100644 --- a/src/libslic3r/Rasterizer/Rasterizer.cpp +++ b/src/libslic3r/Rasterizer/Rasterizer.cpp @@ -1,5 +1,6 @@ #include "Rasterizer.hpp" #include +#include // For rasterizing #include @@ -89,6 +90,25 @@ public: agg::render_scanlines(ras, scanlines, m_renderer); } + void draw(const ClipperLib::Polygon &poly) { + agg::rasterizer_scanline_aa<> ras; + agg::scanline_p8 scanlines; + + auto&& path = to_path(poly.Contour); + + if(m_o == Origin::TOP_LEFT) flipy(path); + + ras.add_path(path); + + for(auto h : poly.Holes) { + auto&& holepath = to_path(h); + if(m_o == Origin::TOP_LEFT) flipy(holepath); + ras.add_path(holepath); + } + + agg::render_scanlines(ras, scanlines, m_renderer); + } + inline void clear() { m_raw_renderer.clear(ColorBlack); } @@ -108,14 +128,36 @@ private: return p(1) * SCALING_FACTOR/m_pxdim.h_mm; } - agg::path_storage to_path(const Polygon& poly) { + agg::path_storage to_path(const Polygon& poly) + { agg::path_storage path; + auto it = poly.points.begin(); path.move_to(getPx(*it), getPy(*it)); - while(++it != poly.points.end()) + while(++it != poly.points.end()) path.line_to(getPx(*it), getPy(*it)); + path.line_to(getPx(poly.points.front()), getPy(poly.points.front())); + + return path; + } + + + double getPx(const ClipperLib::IntPoint& p) { + return p.X * SCALING_FACTOR/m_pxdim.w_mm; + } + + double getPy(const ClipperLib::IntPoint& p) { + return p.Y * SCALING_FACTOR/m_pxdim.h_mm; + } + + agg::path_storage to_path(const ClipperLib::Path& poly) + { + agg::path_storage path; + auto it = poly.begin(); + path.move_to(getPx(*it), getPy(*it)); + while(++it != poly.end()) path.line_to(getPx(*it), getPy(*it)); - path.line_to(getPx(poly.points.front()), getPy(poly.points.front())); + path.line_to(getPx(poly.front()), getPy(poly.front())); return path; } @@ -167,9 +209,13 @@ void Raster::clear() m_impl->clear(); } -void Raster::draw(const ExPolygon &poly) +void Raster::draw(const ExPolygon &expoly) +{ + m_impl->draw(expoly); +} + +void Raster::draw(const ClipperLib::Polygon &poly) { - assert(m_impl); m_impl->draw(poly); } diff --git a/src/libslic3r/Rasterizer/Rasterizer.hpp b/src/libslic3r/Rasterizer/Rasterizer.hpp index 768488adc5..a8c8e18664 100644 --- a/src/libslic3r/Rasterizer/Rasterizer.hpp +++ b/src/libslic3r/Rasterizer/Rasterizer.hpp @@ -6,6 +6,8 @@ #include #include +namespace ClipperLib { class Polygon; } + namespace Slic3r { class ExPolygon; @@ -123,6 +125,7 @@ public: /// Draw a polygon with holes. void draw(const ExPolygon& poly); + void draw(const ClipperLib::Polygon& poly); /// Save the raster on the specified stream. void save(std::ostream& stream, Compression comp = Compression::RAW); diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index ee24cf3ecf..4248814914 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -12,6 +12,9 @@ #include #include +// For geometry algorithms with native Clipper types (no copies and conversions) +#include + //#include //#include "tbb/mutex.h" #include "I18N.hpp" @@ -43,8 +46,7 @@ const std::array OBJ_STEP_LEVELS = 30, // slaposSupportPoints, 25, // slaposSupportTree, 25, // slaposBasePool, - 5, // slaposSliceSupports, - 5 // slaposIndexSlices + 10, // slaposSliceSupports, }; const std::array OBJ_STEP_LABELS = @@ -54,22 +56,19 @@ const std::array OBJ_STEP_LABELS = L("Generating support tree"), // slaposSupportTree, L("Generating pad"), // slaposBasePool, L("Slicing supports"), // slaposSliceSupports, - L("Slicing supports") // slaposIndexSlices, }; // Should also add up to 100 (%) const std::array PRINT_STEP_LEVELS = { 5, // slapsStats - 94, // slapsRasterize - 1, // slapsValidate + 95, // slapsRasterize }; const std::array PRINT_STEP_LABELS = { - L("Calculating statistics"), // slapsStats + L("Merging slices and calculating statistics"), // slapsStats L("Rasterizing layers"), // slapsRasterize - L("Validating"), // slapsValidate }; } @@ -206,7 +205,7 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf model_object_status.emplace(model_object->id(), ModelObjectStatus::Old); } else if (model_object_list_extended(m_model, model)) { // Add new objects. Their volumes and configs will be synchronized later. - update_apply_status(this->invalidate_step(slapsRasterize)); + update_apply_status(this->invalidate_step(slapsMergeSlicesAndEval)); for (const ModelObject *model_object : m_model.objects) model_object_status.emplace(model_object->id(), ModelObjectStatus::Old); for (size_t i = m_model.objects.size(); i < model.objects.size(); ++ i) { @@ -218,7 +217,7 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf // Reorder the objects, add new objects. // First stop background processing before shuffling or deleting the PrintObjects in the object list. this->call_cancel_callback(); - update_apply_status(this->invalidate_step(slapsRasterize)); + update_apply_status(this->invalidate_step(slapsMergeSlicesAndEval)); // Second create a new list of objects. std::vector model_objects_old(std::move(m_model.objects)); m_model.objects.clear(); @@ -390,7 +389,7 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf if (new_instances != it_print_object_status->print_object->instances()) { // Instances changed. it_print_object_status->print_object->set_instances(new_instances); - update_apply_status(this->invalidate_step(slapsRasterize)); + update_apply_status(this->invalidate_step(slapsMergeSlicesAndEval)); } print_objects_new.emplace_back(it_print_object_status->print_object); const_cast(*it_print_object_status).status = PrintObjectStatus::Reused; @@ -579,11 +578,6 @@ sla::PoolConfig make_pool_config(const SLAPrintObjectConfig& c) { return pcfg; } - -void swapXY(ExPolygon& expoly) { - for(auto& p : expoly.contour.points) std::swap(p(X), p(Y)); - for(auto& h : expoly.holes) for(auto& p : h.points) std::swap(p(X), p(Y)); -} } std::string SLAPrint::validate() const @@ -613,6 +607,18 @@ std::string SLAPrint::validate() const return ""; } +bool SLAPrint::invalidate_step(SLAPrintStep step) +{ + bool invalidated = Inherited::invalidate_step(step); + + // propagate to dependent steps + if (step == slapsMergeSlicesAndEval) { + invalidated |= this->invalidate_all_steps(); + } + + return invalidated; +} + template void report_status(SLAPrint& p, int st, const std::string& msg, Args&&...args) { @@ -639,7 +645,7 @@ void SLAPrint::process() const size_t objcount = m_objects.size(); const unsigned min_objstatus = 0; // where the per object operations start - const unsigned max_objstatus = PRINT_STEP_LEVELS[slapsRasterize]; // where the per object operations end + const unsigned max_objstatus = PRINT_STEP_LEVELS[slapsMergeSlicesAndEval]; // where the per object operations end // the coefficient that multiplies the per object status values which // are set up for <0, 100>. They need to be scaled into the whole process @@ -883,7 +889,7 @@ void SLAPrint::process() // Slicing the support geometries similarly to the model slicing procedure. // If the pad had been added previously (see step "base_pool" than it will // be part of the slices) - auto slice_supports = [](SLAPrintObject& po) { + auto slice_supports = [this](SLAPrintObject& po) { auto& sd = po.m_supportdata; if(sd) sd->support_slices.clear(); @@ -906,28 +912,14 @@ void SLAPrint::process() { po.m_slice_index[i].set_support_slice_idx(po, i); } - }; - // We have the layer polygon collection but we need to unite them into - // an index where the key is the height level in discrete levels (clipper) - auto index_slices = [this/*, ilhd*/](SLAPrintObject& /*po*/) { // Using RELOAD_SLA_PREVIEW to tell the Plater to pass the update status to the 3D preview to load the SLA slices. report_status(*this, -2, "", SlicingStatus::RELOAD_SLA_PREVIEW); }; - auto fillstats = [this]() { - - m_print_statistics.clear(); - - // Fill statistics - fill_statistics(); - - report_status(*this, -2, "", SlicingStatus::RELOAD_SLA_PREVIEW); - }; - - // Rasterizing the model objects, and their supports - auto rasterize = [this, max_objstatus, ilhs]() { - if(canceled()) return; + // Merging the slices from all the print objects into one slice grid and + // calculating print statistics from the merge result. + auto merge_slices_and_eval_stats = [this, ilhs]() { // clear the rasterizer input m_printer_input.clear(); @@ -962,6 +954,272 @@ void SLAPrint::process() } } + m_print_statistics.clear(); + + using ClipperPoint = ClipperLib::IntPoint; + using ClipperPolygon = ClipperLib::Polygon; // see clipper_polygon.hpp in libnest2d + using ClipperPolygons = std::vector; + namespace sl = libnest2d::shapelike; // For algorithms + + // If the raster has vertical orientation, we will flip the coordinates + bool flpXY = m_printer_config.display_orientation.getInt() == SLADisplayOrientation::sladoPortrait; + + // Set up custom union and diff functions for clipper polygons + auto polyunion = [] (const ClipperPolygons& subjects) + { + ClipperLib::Clipper clipper; + + bool closed = true; + + for(auto& path : subjects) { + clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed); + clipper.AddPaths(path.Holes, ClipperLib::ptSubject, closed); + } + + auto mode = ClipperLib::pftPositive; + + return libnest2d::clipper_execute(clipper, ClipperLib::ctUnion, mode, mode); + }; + + auto polydiff = [](const ClipperPolygons& subjects, const ClipperPolygons& clips) + { + ClipperLib::Clipper clipper; + + bool closed = true; + + for(auto& path : subjects) { + clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed); + clipper.AddPaths(path.Holes, ClipperLib::ptSubject, closed); + } + + for(auto& path : clips) { + clipper.AddPath(path.Contour, ClipperLib::ptClip, closed); + clipper.AddPaths(path.Holes, ClipperLib::ptClip, closed); + } + + auto mode = ClipperLib::pftPositive; + + return libnest2d::clipper_execute(clipper, ClipperLib::ctDifference, mode, mode); + }; + + // libnest calculates positive area for clockwise polygons, Slic3r is in counter-clockwise + auto areafn = [](const ClipperPolygon& poly) { return - sl::area(poly); }; + + const double area_fill = m_printer_config.area_fill.getFloat()*0.01;// 0.5 (50%); + const double fast_tilt = m_printer_config.fast_tilt_time.getFloat();// 5.0; + const double slow_tilt = m_printer_config.slow_tilt_time.getFloat();// 8.0; + + const double init_exp_time = m_material_config.initial_exposure_time.getFloat(); + const double exp_time = m_material_config.exposure_time.getFloat(); + + const int fade_layers_cnt = m_default_object_config.faded_layers.getInt();// 10 // [3;20] + + const double width = m_printer_config.display_width.getFloat() / SCALING_FACTOR; + const double height = m_printer_config.display_height.getFloat() / SCALING_FACTOR; + const double display_area = width*height; + + // get polygons for all instances in the object + auto get_all_polygons = + [flpXY](const ExPolygons& input_polygons, + const std::vector& instances) + { + ClipperPolygons polygons; + polygons.reserve(input_polygons.size() * instances.size()); + + for (const ExPolygon& polygon : input_polygons) { + if(polygon.contour.empty()) continue; + + for (size_t i = 0; i < instances.size(); ++i) + { + ClipperPolygon poly; + + // should be a move + poly.Contour.reserve(polygon.contour.size() + 1); + + for(auto& p : polygon.contour.points) + poly.Contour.emplace_back(p.x(), p.y()); + + auto pfirst = poly.Contour.front(); + poly.Contour.emplace_back(pfirst); + + for(auto& h : polygon.holes) { + poly.Holes.emplace_back(); + auto& hole = poly.Holes.back(); + hole.reserve(h.points.size() + 1); + + for(auto& p : h.points) hole.emplace_back(p.x(), p.y()); + auto pfirst = hole.front(); hole.emplace_back(pfirst); + } + + sl::rotate(poly, double(instances[i].rotation)); + sl::translate(poly, ClipperPoint{instances[i].shift(X), + instances[i].shift(Y)}); + if (flpXY) { + for(auto& p : poly.Contour) std::swap(p.X, p.Y); + std::reverse(poly.Contour.begin(), poly.Contour.end()); + + for(auto& h : poly.Holes) { + for(auto& p : h) std::swap(p.X, p.Y); + std::reverse(h.begin(), h.end()); + } + } + + polygons.emplace_back(std::move(poly)); + } + } + return polygons; + }; + + double supports_volume(0.0); + double models_volume(0.0); + + double estim_time(0.0); + + size_t slow_layers = 0; + size_t fast_layers = 0; + + const double delta_fade_time = (init_exp_time - exp_time) / (fade_layers_cnt + 1); + double fade_layer_time = init_exp_time; + + SpinMutex mutex; + using Lock = std::lock_guard; + + // Going to parallel: + auto printlayerfn = [this, + // functions and read only vars + get_all_polygons, polyunion, polydiff, areafn, + area_fill, display_area, exp_time, init_exp_time, fast_tilt, slow_tilt, delta_fade_time, + + // write vars + &mutex, &models_volume, &supports_volume, &estim_time, &slow_layers, + &fast_layers, &fade_layer_time](size_t sliced_layer_cnt) + { + PrintLayer& layer = m_printer_input[sliced_layer_cnt]; + + // vector of slice record references + auto& slicerecord_references = layer.slices(); + + if(slicerecord_references.empty()) return; + + // Layer height should match for all object slices for a given level. + const auto l_height = double(slicerecord_references.front().get().layer_height()); + + // Calculation of the consumed material + + ClipperPolygons model_polygons; + ClipperPolygons supports_polygons; + + size_t c = std::accumulate(layer.slices().begin(), layer.slices().end(), 0u, [](size_t a, const SliceRecord& sr) { + return a + sr.get_slice(soModel).size(); + }); + + model_polygons.reserve(c); + + c = std::accumulate(layer.slices().begin(), layer.slices().end(), 0u, [](size_t a, const SliceRecord& sr) { + return a + sr.get_slice(soModel).size(); + }); + + supports_polygons.reserve(c); + + for(const SliceRecord& record : layer.slices()) { + const SLAPrintObject *po = record.print_obj(); + + const ExPolygons &modelslices = record.get_slice(soModel); + if (!modelslices.empty()) { + ClipperPolygons v = get_all_polygons(modelslices, po->instances()); + for(ClipperPolygon& p_tmp : v) model_polygons.emplace_back(std::move(p_tmp)); + } + + const ExPolygons &supportslices = record.get_slice(soSupport); + if (!supportslices.empty()) { + ClipperPolygons v = get_all_polygons(supportslices, po->instances()); + for(ClipperPolygon& p_tmp : v) supports_polygons.emplace_back(std::move(p_tmp)); + } + } + + model_polygons = polyunion(model_polygons); + double layer_model_area = 0; + for (const ClipperPolygon& polygon : model_polygons) + layer_model_area += areafn(polygon); + + if (layer_model_area < 0 || layer_model_area > 0) { + Lock lck(mutex); models_volume += layer_model_area * l_height; + } + + if(!supports_polygons.empty()) { + if(model_polygons.empty()) supports_polygons = polyunion(supports_polygons); + else supports_polygons = polydiff(supports_polygons, model_polygons); + // allegedly, union of subject is done withing the diff according to the pftPositive polyFillType + } + + double layer_support_area = 0; + for (const ClipperPolygon& polygon : supports_polygons) + layer_support_area += areafn(polygon); + + if (layer_support_area < 0 || layer_support_area > 0) { + Lock lck(mutex); supports_volume += layer_support_area * l_height; + } + + // Here we can save the expensively calculated polygons for printing + ClipperPolygons trslices; + trslices.reserve(model_polygons.size() + supports_polygons.size()); + for(ClipperPolygon& poly : model_polygons) trslices.emplace_back(std::move(poly)); + for(ClipperPolygon& poly : supports_polygons) trslices.emplace_back(std::move(poly)); + + layer.transformed_slices(polyunion(trslices)); + + // Calculation of the slow and fast layers to the future controlling those values on FW + + const bool is_fast_layer = (layer_model_area + layer_support_area) <= display_area*area_fill; + const double tilt_time = is_fast_layer ? fast_tilt : slow_tilt; + + { Lock lck(mutex); + if (is_fast_layer) + fast_layers++; + else + slow_layers++; + + + // Calculation of the printing time + + if (sliced_layer_cnt < 3) + estim_time += init_exp_time; + else if (fade_layer_time > exp_time) + { + fade_layer_time -= delta_fade_time; + estim_time += fade_layer_time; + } + else + estim_time += exp_time; + + estim_time += tilt_time; + } + }; + + // sequential version for debugging: + // for(size_t i = 0; i < m_printer_input.size(); ++i) printlayerfn(i); + tbb::parallel_for(0, m_printer_input.size(), printlayerfn); + + m_print_statistics.support_used_material = supports_volume * SCALING_FACTOR * SCALING_FACTOR; + m_print_statistics.objects_used_material = models_volume * SCALING_FACTOR * SCALING_FACTOR; + + // Estimated printing time + // A layers count o the highest object + if (m_printer_input.size() == 0) + m_print_statistics.estimated_print_time = "N/A"; + else + m_print_statistics.estimated_print_time = get_time_dhms(float(estim_time)); + + m_print_statistics.fast_layers_count = fast_layers; + m_print_statistics.slow_layers_count = slow_layers; + + report_status(*this, -2, "", SlicingStatus::RELOAD_SLA_PREVIEW); + }; + + // Rasterizing the model objects, and their supports + auto rasterize = [this, max_objstatus]() { + if(canceled()) return; + // collect all the keys // If the raster has vertical orientation, we will flip the coordinates @@ -1005,7 +1263,7 @@ void SLAPrint::process() // procedure to process one height level. This will run in parallel auto lvlfn = - [this, &slck, &printer, slot, sd, ist, &pst, flpXY] + [this, &slck, &printer, slot, sd, ist, &pst] (unsigned level_id) { if(canceled()) return; @@ -1015,28 +1273,8 @@ void SLAPrint::process() // Switch to the appropriate layer in the printer printer.begin_layer(level_id); - using Instance = SLAPrintObject::Instance; - - auto draw = - [&printer, flpXY, level_id](ExPolygon& poly, const Instance& tr) - { - poly.rotate(double(tr.rotation)); - poly.translate(tr.shift(X), tr.shift(Y)); - if(flpXY) swapXY(poly); + for(const ClipperLib::Polygon& poly : printlayer.transformed_slices()) printer.draw_polygon(poly, level_id); - }; - - for(const SliceRecord& sr : printlayer.slices()) { - if(! sr.print_obj()) continue; - - for(const Instance& inst : sr.print_obj()->instances()) { - ExPolygons objsl = sr.get_slice(soModel); - for(ExPolygon& poly : objsl) draw(poly, inst); - - ExPolygons supsl = sr.get_slice(soSupport); - for(ExPolygon& poly : supsl) draw(poly, inst); - } - } // Finish the layer for later saving it. printer.finish_layer(level_id); @@ -1079,15 +1317,13 @@ void SLAPrint::process() support_points, support_tree, base_pool, - slice_supports, - index_slices + slice_supports }; std::array print_program = { - fillstats, - rasterize, - [](){} // validate + merge_slices_and_eval_stats, + rasterize }; unsigned st = min_objstatus; @@ -1127,7 +1363,7 @@ void SLAPrint::process() } std::array printsteps = { - slapsStats, slapsRasterize, slapsValidate + slapsMergeSlicesAndEval, slapsRasterize }; // this would disable the rasterization step @@ -1193,11 +1429,11 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector& instances) { - const size_t inst_cnt = instances.size(); - - size_t polygon_cnt = 0; - for (const ExPolygon& polygon : input_polygons) - polygon_cnt += polygon.holes.size() + 1; - - Polygons polygons; - polygons.reserve(polygon_cnt * inst_cnt); - for (const ExPolygon& polygon : input_polygons) { - for (size_t i = 0; i < inst_cnt; ++i) - { - ExPolygon tmp = polygon; - tmp.rotate(double(instances[i].rotation)); - tmp.translate(instances[i].shift.x(), instances[i].shift.y()); - polygons_append(polygons, to_polygons(std::move(tmp))); - } - } - return polygons; - }; - - double supports_volume = 0.0; - double models_volume = 0.0; - - double estim_time = 0.0; - - size_t slow_layers = 0; - size_t fast_layers = 0; - - // find highest object - // Which is a better bet? To compare by max_z or by number of layers in the index? - // float max_z = 0.; - size_t max_layers_cnt = 0; - size_t highest_obj_idx = 0; - for (SLAPrintObject *&po : m_objects) { - auto& slice_index = po->get_slice_index(); - if (! slice_index.empty()) { - // float z = (-- slice_index.end())->slice_level(); - size_t cnt = slice_index.size(); - //if (z > max_z) { - if (cnt > max_layers_cnt) { - max_layers_cnt = cnt; - // max_z = z; - highest_obj_idx = &po - &m_objects.front(); - } - } - } - - const SLAPrintObject * highest_obj = m_objects[highest_obj_idx]; - auto& highest_obj_slice_index = highest_obj->get_slice_index(); - - const double delta_fade_time = (init_exp_time - exp_time) / (fade_layers_cnt + 1); - double fade_layer_time = init_exp_time; - - int sliced_layer_cnt = 0; - for (const SliceRecord& layer : highest_obj_slice_index) - { - const auto l_height = double(layer.layer_height()); - - // Calculation of the consumed material - - Polygons model_polygons; - Polygons supports_polygons; - - for (SLAPrintObject * po : m_objects) - { - const SliceRecord *record = nullptr; - { - const SliceRecord& slr = po->closest_slice_to_slice_level(layer.slice_level(), float(EPSILON)); - if (!slr.is_valid()) continue; - record = &slr; - } - - const ExPolygons &modelslices = record->get_slice(soModel); - if (!modelslices.empty()) - append(model_polygons, get_all_polygons(modelslices, po->instances())); - - const ExPolygons &supportslices = record->get_slice(soSupport); - if (!supportslices.empty()) - append(supports_polygons, get_all_polygons(supportslices, po->instances())); - } - - model_polygons = union_(model_polygons); - double layer_model_area = 0; - for (const Polygon& polygon : model_polygons) - layer_model_area += polygon.area(); - - if (layer_model_area != 0) - models_volume += layer_model_area * l_height; - - if (!supports_polygons.empty() && !model_polygons.empty()) - supports_polygons = diff(supports_polygons, model_polygons); - double layer_support_area = 0; - for (const Polygon& polygon : supports_polygons) - layer_support_area += polygon.area(); - - if (layer_support_area != 0) - supports_volume += layer_support_area * l_height; - - // Calculation of the slow and fast layers to the future controlling those values on FW - - const bool is_fast_layer = (layer_model_area + layer_support_area) <= display_area*area_fill; - const double tilt_time = is_fast_layer ? fast_tilt : slow_tilt; - if (is_fast_layer) - fast_layers++; - else - slow_layers++; - - - // Calculation of the printing time - - if (sliced_layer_cnt < 3) - estim_time += init_exp_time; - else if (fade_layer_time > exp_time) - { - fade_layer_time -= delta_fade_time; - estim_time += fade_layer_time; - } - else - estim_time += exp_time; - - estim_time += tilt_time; - - sliced_layer_cnt++; - } - - m_print_statistics.support_used_material = supports_volume * SCALING_FACTOR * SCALING_FACTOR; - m_print_statistics.objects_used_material = models_volume * SCALING_FACTOR * SCALING_FACTOR; - - // Estimated printing time - // A layers count o the highest object - if (max_layers_cnt == 0) - m_print_statistics.estimated_print_time = "N/A"; - else - m_print_statistics.estimated_print_time = get_time_dhms(float(estim_time)); - - m_print_statistics.fast_layers_count = fast_layers; - m_print_statistics.slow_layers_count = slow_layers; -} - // Returns true if an object step is done on all objects and there's at least one object. bool SLAPrint::is_step_done(SLAPrintObjectStep step) const { @@ -1459,19 +1536,16 @@ bool SLAPrintObject::invalidate_step(SLAPrintObjectStep step) if (step == slaposObjectSlice) { invalidated |= this->invalidate_all_steps(); } else if (step == slaposSupportPoints) { - invalidated |= this->invalidate_steps({ slaposSupportTree, slaposBasePool, slaposSliceSupports, slaposIndexSlices }); - invalidated |= m_print->invalidate_step(slapsRasterize); + invalidated |= this->invalidate_steps({ slaposSupportTree, slaposBasePool, slaposSliceSupports }); + invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval); } else if (step == slaposSupportTree) { - invalidated |= this->invalidate_steps({ slaposBasePool, slaposSliceSupports, slaposIndexSlices }); - invalidated |= m_print->invalidate_step(slapsRasterize); + invalidated |= this->invalidate_steps({ slaposBasePool, slaposSliceSupports }); + invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval); } else if (step == slaposBasePool) { - invalidated |= this->invalidate_steps({slaposSliceSupports, slaposIndexSlices}); - invalidated |= m_print->invalidate_step(slapsRasterize); + invalidated |= this->invalidate_steps({slaposSliceSupports}); + invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval); } else if (step == slaposSliceSupports) { - invalidated |= this->invalidate_step(slaposIndexSlices); - invalidated |= m_print->invalidate_step(slapsRasterize); - } else if(step == slaposIndexSlices) { - invalidated |= m_print->invalidate_step(slapsRasterize); + invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval); } return invalidated; } @@ -1547,18 +1621,6 @@ const ExPolygons &SliceRecord::get_slice(SliceOrigin o) const return idx >= v.size() ? EMPTY_SLICE : v[idx]; } -const std::vector & SLAPrintObject::get_slice_index() const -{ - // assert(is_step_done(slaposIndexSlices)); - return m_slice_index; -} - -const std::vector &SLAPrintObject::get_model_slices() const -{ - // assert(is_step_done(slaposObjectSlice)); - return m_model_slices; -} - bool SLAPrintObject::has_mesh(SLAPrintObjectStep step) const { switch (step) { diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index d831908c7d..9cf826097e 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -6,14 +6,14 @@ #include "PrintExport.hpp" #include "Point.hpp" #include "MTUtils.hpp" +#include #include "Zipper.hpp" namespace Slic3r { enum SLAPrintStep : unsigned int { - slapsStats, - slapsRasterize, - slapsValidate, + slapsMergeSlicesAndEval, + slapsRasterize, slapsCount }; @@ -22,8 +22,7 @@ enum SLAPrintObjectStep : unsigned int { slaposSupportPoints, slaposSupportTree, slaposBasePool, - slaposSliceSupports, - slaposIndexSlices, + slaposSliceSupports, slaposCount }; @@ -127,7 +126,7 @@ public: bool is_valid() const { return ! std::isnan(m_slice_z); } - const SLAPrintObject* print_obj() const { return m_po; } + const SLAPrintObject* print_obj() const { assert(m_po); return m_po; } // Methods for setting the indices into the slice vectors. void set_model_slice_idx(const SLAPrintObject &po, size_t id) { @@ -190,7 +189,7 @@ private: return it; } - const std::vector& get_model_slices() const; + const std::vector& get_model_slices() const { return m_model_slices; } const std::vector& get_support_slices() const; public: @@ -205,7 +204,9 @@ public: // ///////////////////////////////////////////////////////////////////////// // Retrieve the slice index. - const std::vector& get_slice_index() const; + const std::vector& get_slice_index() const { + return m_slice_index; + } // Search slice index for the closest slice to given print_level. // max_epsilon gives the allowable deviation of the returned slice record's @@ -367,31 +368,6 @@ private: // Prevents erroneous use by other classes. public: - // An aggregation of SliceRecord-s from all the print objects for each - // occupied layer. Slice record levels dont have to match exactly. - // They are unified if the level difference is within +/- SCALED_EPSILON - class PrintLayer { - coord_t m_level; - - // The collection of slice records for the current level. - std::vector> m_slices; - - public: - - explicit PrintLayer(coord_t lvl) : m_level(lvl) {} - - // for being sorted in their container (see m_printer_input) - bool operator<(const PrintLayer& other) const { - return m_level < other.m_level; - } - - void add(const SliceRecord& sr) { m_slices.emplace_back(sr); } - - coord_t level() const { return m_level; } - - auto slices() const -> const decltype (m_slices)& { return m_slices; } - }; - SLAPrint(): m_stepmask(slapsCount, true) {} virtual ~SLAPrint() override { this->clear(); } @@ -407,7 +383,7 @@ public: // Returns true if an object step is done on all objects and there's at least one object. bool is_step_done(SLAPrintObjectStep step) const; // Returns true if the last step was finished with success. - bool finished() const override { return this->is_step_done(slaposIndexSlices) && this->Inherited::is_step_done(slapsRasterize); } + bool finished() const override { return this->is_step_done(slaposSliceSupports) && this->Inherited::is_step_done(slapsRasterize); } template void export_raster(const std::string& fname) { @@ -427,6 +403,43 @@ public: std::string validate() const override; + // An aggregation of SliceRecord-s from all the print objects for each + // occupied layer. Slice record levels dont have to match exactly. + // They are unified if the level difference is within +/- SCALED_EPSILON + class PrintLayer { + coord_t m_level; + + // The collection of slice records for the current level. + std::vector> m_slices; + + std::vector m_transformed_slices; + + template void transformed_slices(Container&& c) { + m_transformed_slices = std::forward(c); + } + + friend void SLAPrint::process(); + + public: + + explicit PrintLayer(coord_t lvl) : m_level(lvl) {} + + // for being sorted in their container (see m_printer_input) + bool operator<(const PrintLayer& other) const { + return m_level < other.m_level; + } + + void add(const SliceRecord& sr) { m_slices.emplace_back(sr); } + + coord_t level() const { return m_level; } + + auto slices() const -> const decltype (m_slices)& { return m_slices; } + + const std::vector & transformed_slices() const { + return m_transformed_slices; + } + }; + // The aggregated and leveled print records from various objects. // TODO: use this structure for the preview in the future. const std::vector& print_layers() const { return m_printer_input; } @@ -435,11 +448,12 @@ private: using SLAPrinter = FilePrinter; using SLAPrinterPtr = std::unique_ptr; + // Implement same logic as in SLAPrintObject + bool invalidate_step(SLAPrintStep st); + // Invalidate steps based on a set of parameters changed. bool invalidate_state_by_config_options(const std::vector &opt_keys); - void fill_statistics(); - SLAPrintConfig m_print_config; SLAPrinterConfig m_printer_config; SLAMaterialConfig m_material_config; diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index eaa11b7381..7595688603 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -55,7 +55,7 @@ TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector& f stl.stats.original_num_facets = stl.stats.number_of_facets; stl_allocate(&stl); - for (int i = 0; i < stl.stats.number_of_facets; i++) { + for (uint32_t i = 0; i < stl.stats.number_of_facets; i++) { stl_facet facet; facet.vertex[0] = points[facets[i](0)].cast(); facet.vertex[1] = points[facets[i](1)].cast(); @@ -125,9 +125,9 @@ void TriangleMesh::repair() float tolerance = stl.stats.shortest_edge; float increment = stl.stats.bounding_diameter / 10000.0; int iterations = 2; - if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) { + if (stl.stats.connected_facets_3_edge < (int)stl.stats.number_of_facets) { for (int i = 0; i < iterations; i++) { - if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) { + if (stl.stats.connected_facets_3_edge < (int)stl.stats.number_of_facets) { //printf("Checking nearby. Tolerance= %f Iteration=%d of %d...", tolerance, i + 1, iterations); #ifdef SLIC3R_TRACE_REPAIR BOOST_LOG_TRIVIAL(trace) << "\tstl_check_faces_nearby"; @@ -143,7 +143,7 @@ void TriangleMesh::repair() } // remove_unconnected - if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) { + if (stl.stats.connected_facets_3_edge < (int)stl.stats.number_of_facets) { #ifdef SLIC3R_TRACE_REPAIR BOOST_LOG_TRIVIAL(trace) << "\tstl_remove_unconnected_facets"; #endif /* SLIC3R_TRACE_REPAIR */ @@ -212,9 +212,9 @@ void TriangleMesh::check_topology() float tolerance = stl.stats.shortest_edge; float increment = stl.stats.bounding_diameter / 10000.0; int iterations = 2; - if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) { + if (stl.stats.connected_facets_3_edge < (int)stl.stats.number_of_facets) { for (int i = 0; i < iterations; i++) { - if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) { + if (stl.stats.connected_facets_3_edge < (int)stl.stats.number_of_facets) { //printf("Checking nearby. Tolerance= %f Iteration=%d of %d...", tolerance, i + 1, iterations); stl_check_facets_nearby(&stl, tolerance); //printf(" Fixed %d edges.\n", stl.stats.edges_fixed - last_edges_fixed); @@ -338,113 +338,78 @@ void TriangleMesh::rotate(double angle, Point* center) this->translate(c(0), c(1), 0); } -bool TriangleMesh::has_multiple_patches() const +/** + * Calculates whether or not the mesh is splittable. + */ +bool TriangleMesh::is_splittable() const { - // we need neighbors - if (!this->repaired) - throw std::runtime_error("split() requires repair()"); - - if (this->stl.stats.number_of_facets == 0) - return false; + std::vector visited; + find_unvisited_neighbors(visited); - std::vector facet_queue(this->stl.stats.number_of_facets, 0); - std::vector facet_visited(this->stl.stats.number_of_facets, false); - int facet_queue_cnt = 1; - facet_queue[0] = 0; - facet_visited[0] = true; - while (facet_queue_cnt > 0) { - int facet_idx = facet_queue[-- facet_queue_cnt]; - facet_visited[facet_idx] = true; - for (int j = 0; j < 3; ++ j) { - int neighbor_idx = this->stl.neighbors_start[facet_idx].neighbor[j]; - if (neighbor_idx != -1 && ! facet_visited[neighbor_idx]) - facet_queue[facet_queue_cnt ++] = neighbor_idx; - } - } - - // If any of the face was not visited at the first time, return "multiple bodies". - for (int facet_idx = 0; facet_idx < this->stl.stats.number_of_facets; ++ facet_idx) - if (! facet_visited[facet_idx]) - return true; - return false; + // Try finding an unvisited facet. If there are none, the mesh is not splittable. + auto it = std::find(visited.begin(), visited.end(), false); + return it != visited.end(); } -size_t TriangleMesh::number_of_patches() const +/** + * Visit all unvisited neighboring facets that are reachable from the first unvisited facet, + * and return them. + * + * @param facet_visited A reference to a vector of booleans. Contains whether or not a + * facet with the same index has been visited. + * @return A deque with all newly visited facets. + */ +std::deque TriangleMesh::find_unvisited_neighbors(std::vector &facet_visited) const { - // we need neighbors + // Make sure we're not operating on a broken mesh. if (!this->repaired) throw std::runtime_error("split() requires repair()"); - - if (this->stl.stats.number_of_facets == 0) - return false; - std::vector facet_queue(this->stl.stats.number_of_facets, 0); - std::vector facet_visited(this->stl.stats.number_of_facets, false); - int facet_queue_cnt = 0; - size_t num_bodies = 0; - for (;;) { - // Find a seeding triangle for a new body. - int facet_idx = 0; - for (; facet_idx < this->stl.stats.number_of_facets; ++ facet_idx) - if (! facet_visited[facet_idx]) { - // A seed triangle was found. - facet_queue[facet_queue_cnt ++] = facet_idx; - facet_visited[facet_idx] = true; - break; - } - if (facet_idx == this->stl.stats.number_of_facets) - // No seed found. - break; - ++ num_bodies; - while (facet_queue_cnt > 0) { - int facet_idx = facet_queue[-- facet_queue_cnt]; + // If the visited list is empty, populate it with false for every facet. + if (facet_visited.empty()) { + facet_visited = std::vector(this->stl.stats.number_of_facets, false); + } + + // Find the first unvisited facet. + std::queue facet_queue; + auto facet = std::find(facet_visited.begin(), facet_visited.end(), false); + if (facet != facet_visited.end()) + facet_queue.push(facet - facet_visited.begin()); + + // Traverse all reachable neighbors and mark them as visited. + std::deque facets; + while (!facet_queue.empty()) { + int facet_idx = facet_queue.front(); + facet_queue.pop(); + + if (facet_idx != -1 && !facet_visited[facet_idx]) { facet_visited[facet_idx] = true; - for (int j = 0; j < 3; ++ j) { - int neighbor_idx = this->stl.neighbors_start[facet_idx].neighbor[j]; - if (neighbor_idx != -1 && ! facet_visited[neighbor_idx]) - facet_queue[facet_queue_cnt ++] = neighbor_idx; - } + + facets.emplace_back(facet_idx); + for (int facet : this->stl.neighbors_start[facet_idx].neighbor) + facet_queue.push(facet); } } - return num_bodies; + return facets; } +/** + * Splits a mesh into multiple meshes when possible. + * + * @return A TriangleMeshPtrs with the newly created meshes. + */ TriangleMeshPtrs TriangleMesh::split() const { - TriangleMeshPtrs meshes; - std::vector facet_visited(this->stl.stats.number_of_facets, false); - - // we need neighbors - if (!this->repaired) - throw std::runtime_error("split() requires repair()"); - - // loop while we have remaining facets + // Loop while we have remaining facets. + std::vector facet_visited; + TriangleMeshPtrs meshes; for (;;) { - // get the first facet - std::queue facet_queue; - std::deque facets; - for (int facet_idx = 0; facet_idx < this->stl.stats.number_of_facets; ++ facet_idx) { - if (! facet_visited[facet_idx]) { - // if facet was not seen put it into queue and start searching - facet_queue.push(facet_idx); - break; - } - } - if (facet_queue.empty()) + std::deque facets = find_unvisited_neighbors(facet_visited); + if (facets.empty()) break; - while (! facet_queue.empty()) { - int facet_idx = facet_queue.front(); - facet_queue.pop(); - if (! facet_visited[facet_idx]) { - facets.emplace_back(facet_idx); - for (int j = 0; j < 3; ++ j) - facet_queue.push(this->stl.neighbors_start[facet_idx].neighbor[j]); - facet_visited[facet_idx] = true; - } - } - + // Create a new mesh for the part that was just split off. TriangleMesh* mesh = new TriangleMesh; meshes.emplace_back(mesh); mesh->stl.stats.type = inmemory; @@ -452,14 +417,15 @@ TriangleMeshPtrs TriangleMesh::split() const mesh->stl.stats.original_num_facets = mesh->stl.stats.number_of_facets; stl_clear_error(&mesh->stl); stl_allocate(&mesh->stl); - + + // Assign the facets to the new mesh. bool first = true; - for (std::deque::const_iterator facet = facets.begin(); facet != facets.end(); ++ facet) { + for (auto facet = facets.begin(); facet != facets.end(); ++ facet) { mesh->stl.facet_start[facet - facets.begin()] = this->stl.facet_start[*facet]; stl_facet_stats(&mesh->stl, this->stl.facet_start[*facet], first); } } - + return meshes; } @@ -476,7 +442,7 @@ void TriangleMesh::merge(const TriangleMesh &mesh) stl_reallocate(&this->stl); // copy facets - for (int i = 0; i < mesh.stl.stats.number_of_facets; ++ i) + for (uint32_t i = 0; i < mesh.stl.stats.number_of_facets; ++ i) this->stl.facet_start[number_of_facets + i] = mesh.stl.facet_start[i]; // update size @@ -489,7 +455,7 @@ ExPolygons TriangleMesh::horizontal_projection() const { Polygons pp; pp.reserve(this->stl.stats.number_of_facets); - for (int i = 0; i < this->stl.stats.number_of_facets; ++ i) { + for (uint32_t i = 0; i < this->stl.stats.number_of_facets; ++ i) { stl_facet* facet = &this->stl.facet_start[i]; Polygon p; p.points.resize(3); @@ -531,7 +497,7 @@ BoundingBoxf3 TriangleMesh::transformed_bounding_box(const Transform3d &trafo) c BoundingBoxf3 bbox; if (stl.v_shared == nullptr) { // Using the STL faces. - for (int i = 0; i < this->facets_count(); ++ i) { + for (size_t i = 0; i < this->facets_count(); ++ i) { const stl_facet &facet = this->stl.facet_start[i]; for (size_t j = 0; j < 3; ++ j) bbox.merge(trafo * facet.vertex[j].cast()); @@ -656,7 +622,7 @@ void TriangleMeshSlicer::init(TriangleMesh *_mesh, throw_on_cancel_callback_type }; std::vector edges_map; edges_map.assign(this->mesh->stl.stats.number_of_facets * 3, EdgeToFace()); - for (int facet_idx = 0; facet_idx < this->mesh->stl.stats.number_of_facets; ++ facet_idx) + for (uint32_t facet_idx = 0; facet_idx < this->mesh->stl.stats.number_of_facets; ++ facet_idx) for (int i = 0; i < 3; ++ i) { EdgeToFace &e2f = edges_map[facet_idx*3+i]; e2f.vertex_low = this->mesh->stl.v_indices[facet_idx].vertex[i]; @@ -905,7 +871,6 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( const stl_normal &normal = this->mesh->stl.facet_start[facet_idx].normal; // We may ignore this edge for slicing purposes, but we may still use it for object cutting. FacetSliceType result = Slicing; - const stl_neighbors &nbr = this->mesh->stl.neighbors_start[facet_idx]; if (min_z == max_z) { // All three vertices are aligned with slice_z. line_out->edge_type = feHorizontal; @@ -917,8 +882,6 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( } } else { // Two vertices are aligned with the cutting plane, the third vertex is below or above the cutting plane. - int nbr_idx = j % 3; - int nbr_face = nbr.neighbor[nbr_idx]; // Is the third vertex below the cutting plane? bool third_below = v0.z() < slice_z || v1.z() < slice_z || v2.z() < slice_z; // Two vertices on the cutting plane, the third vertex is below the plane. Consider the edge to be part of the slice @@ -1697,7 +1660,7 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - slicing object"; float scaled_z = scale_(z); - for (int facet_idx = 0; facet_idx < this->mesh->stl.stats.number_of_facets; ++ facet_idx) { + for (uint32_t facet_idx = 0; facet_idx < this->mesh->stl.stats.number_of_facets; ++ facet_idx) { stl_facet* facet = &this->mesh->stl.facet_start[facet_idx]; // find facet extents diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index a4387e5c1e..b204a9a3ec 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -68,12 +68,8 @@ public: size_t facets_count() const { return this->stl.stats.number_of_facets; } bool empty() const { return this->facets_count() == 0; } - // Returns true, if there are two and more connected patches in the mesh. - // Returns false, if one or zero connected patch is in the mesh. - bool has_multiple_patches() const; - - // Count disconnected triangle patches. - size_t number_of_patches() const; + bool is_splittable() const; + std::deque find_unvisited_neighbors(std::vector &facet_visited) const; stl_file stl; bool repaired; diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index c13df45462..dfd72b7a91 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -208,7 +208,7 @@ public: // Shorten the dhms time by removing the seconds, rounding the dhm to full minutes // and removing spaces. -static std::string short_time(const std::string &time) +inline std::string short_time(const std::string &time) { // Parse the dhms time format. int days = 0; @@ -247,7 +247,7 @@ static std::string short_time(const std::string &time) } // Returns the given time is seconds in format DDd HHh MMm SSs -static std::string get_time_dhms(float time_in_secs) +inline std::string get_time_dhms(float time_in_secs) { int days = (int)(time_in_secs / 86400.0f); time_in_secs -= (float)days * 86400.0f; diff --git a/src/slic3r.cpp b/src/slic3r.cpp index 5198eeaa32..958b663059 100644 --- a/src/slic3r.cpp +++ b/src/slic3r.cpp @@ -361,6 +361,14 @@ int CLI::run(int argc, char **argv) std::string outfile = m_config.opt_string("output"); Print fff_print; SLAPrint sla_print; + + sla_print.set_status_callback( + [](const PrintBase::SlicingStatus& s) + { + if(s.percent >= 0) // FIXME: is this sufficient? + printf("%3d%s %s\n", s.percent, "% =>", s.text.c_str()); + }); + PrintBase *print = (printer_technology == ptFFF) ? static_cast(&fff_print) : static_cast(&sla_print); if (! m_config.opt_bool("dont_arrange")) { //FIXME make the min_object_distance configurable. diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 931c3d2fff..7c983e1575 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3902,7 +3902,7 @@ void GLCanvas3D::_render_sla_slices() const } if ((bottom_obj_triangles.empty() || bottom_sup_triangles.empty() || top_obj_triangles.empty() || top_sup_triangles.empty()) && - obj->is_step_done(slaposIndexSlices) && !obj->get_slice_index().empty()) + obj->is_step_done(slaposSliceSupports) && !obj->get_slice_index().empty()) { double layer_height = print->default_object_config().layer_height.value; double initial_layer_height = print->material_config().initial_layer_height.value; @@ -5053,7 +5053,7 @@ void GLCanvas3D::_load_shells_sla() int obj_idx = 0; for (const SLAPrintObject* obj : print->objects()) { - if (!obj->is_step_done(slaposIndexSlices)) + if (!obj->is_step_done(slaposSliceSupports)) continue; unsigned int initial_volumes_count = (unsigned int)m_volumes.volumes.size(); diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index f41d148de4..14d19e2514 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -773,7 +773,7 @@ void Preview::load_print_as_sla() std::vector zs; double initial_layer_height = print->material_config().initial_layer_height.value; for (const SLAPrintObject* obj : print->objects()) - if (obj->is_step_done(slaposIndexSlices) && !obj->get_slice_index().empty()) + if (obj->is_step_done(slaposSliceSupports) && !obj->get_slice_index().empty()) { auto low_coord = obj->get_slice_index().front().print_level(); for (auto& rec : obj->get_slice_index()) diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index c568b41a8b..2de4ece47f 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -119,17 +119,17 @@ void Selection::add(unsigned int volume_idx, bool as_single_selection) if (needs_reset) clear(); - if (volume->is_modifier) - m_mode = Volume; - else if (!contains_volume(volume_idx)) - m_mode = Instance; - // else -> keep current mode + if (!contains_volume(volume_idx)) + m_mode = volume->is_modifier ? Volume : Instance; + else + // keep current mode + return; switch (m_mode) { case Volume: { - if (volume->volume_idx() >= 0 && (is_empty() || (volume->instance_idx() == get_instance_idx()))) + if ((volume->volume_idx() >= 0) && (is_empty() || (volume->instance_idx() == get_instance_idx()))) do_add_volume(volume_idx); break; @@ -440,6 +440,8 @@ void Selection::translate(const Vec3d& displacement, bool local) if (!m_valid) return; + EMode translation_type = m_mode; + for (unsigned int i : m_list) { if ((m_mode == Volume) || (*m_volumes)[i]->is_wipe_tower) @@ -453,13 +455,22 @@ void Selection::translate(const Vec3d& displacement, bool local) } } else if (m_mode == Instance) - (*m_volumes)[i]->set_instance_offset(m_cache.volumes_data[i].get_instance_position() + displacement); + { + if (is_from_fully_selected_instance(i)) + (*m_volumes)[i]->set_instance_offset(m_cache.volumes_data[i].get_instance_position() + displacement); + else + { + Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement; + (*m_volumes)[i]->set_volume_offset(m_cache.volumes_data[i].get_volume_position() + local_displacement); + translation_type = Volume; + } + } } #if !DISABLE_INSTANCES_SYNCH - if (m_mode == Instance) + if (translation_type == Instance) synchronize_unselected_instances(SYNC_ROTATION_NONE); - else if (m_mode == Volume) + else if (translation_type == Volume) synchronize_unselected_volumes(); #endif // !DISABLE_INSTANCES_SYNCH @@ -1684,5 +1695,29 @@ void Selection::ensure_on_bed() } } +bool Selection::is_from_fully_selected_instance(unsigned int volume_idx) const +{ + struct SameInstance + { + int obj_idx; + int inst_idx; + GLVolumePtrs& volumes; + + SameInstance(int obj_idx, int inst_idx, GLVolumePtrs& volumes) : obj_idx(obj_idx), inst_idx(inst_idx), volumes(volumes) {} + bool operator () (unsigned int i) { return (volumes[i]->object_idx() == obj_idx) && (volumes[i]->instance_idx() == inst_idx); } + }; + + if ((unsigned int)m_volumes->size() <= volume_idx) + return false; + + GLVolume* volume = (*m_volumes)[volume_idx]; + int object_idx = volume->object_idx(); + if ((int)m_model->objects.size() <= object_idx) + return false; + + unsigned int count = (unsigned int)std::count_if(m_list.begin(), m_list.end(), SameInstance(object_idx, volume->instance_idx(), *m_volumes)); + return count == (unsigned int)m_model->objects[object_idx]->volumes.size(); +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 3f7631f386..5058c72f8f 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -297,6 +297,7 @@ private: void synchronize_unselected_instances(SyncRotationType sync_rotation_type); void synchronize_unselected_volumes(); void ensure_on_bed(); + bool is_from_fully_selected_instance(unsigned int volume_idx) const; }; } // namespace GUI