diff --git a/src/libnest2d/include/libnest2d/backends/clipper/CMakeLists.txt b/src/libnest2d/include/libnest2d/backends/clipper/CMakeLists.txt index 995afcc76..462d1dd06 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 000000000..e9fbfbd18 --- /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 9f881e7e0..232668f61 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 49baa65f2..c7b252e5d 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 6fb717a7a..91affe978 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 309a5007d..abcd86183 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 b03534dc4..25007e580 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 a6988ca00..baf1c6a10 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 b9a4eb8fa..1e65826bd 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/Geometry.hpp b/src/libslic3r/Geometry.hpp index 380245b5f..d556f664c 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -246,6 +246,7 @@ public: const Vec3d& get_mirror() const { return m_mirror; } double get_mirror(Axis axis) const { return m_mirror(axis); } + bool is_left_handed() const { return m_mirror.x() * m_mirror.y() * m_mirror.z() < 0.; } void set_mirror(const Vec3d& mirror); void set_mirror(Axis axis, double mirror); diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 913ba76a6..19fbb3bb6 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -258,13 +258,18 @@ void LayerRegion::process_external_surfaces(const Layer* lower_layer) #ifdef SLIC3R_DEBUG printf("Processing bridge at layer " PRINTF_ZU ":\n", this->layer()->id()); #endif - if (bd.detect_angle(Geometry::deg2rad(this->region()->config().bridge_angle.value))) { + double custom_angle = Geometry::deg2rad(this->region()->config().bridge_angle.value); + if (bd.detect_angle(custom_angle)) { bridges[idx_last].bridge_angle = bd.angle; if (this->layer()->object()->config().support_material) { polygons_append(this->bridged, bd.coverage()); this->unsupported_bridge_edges.append(bd.unsupported_edges()); } - } + } else if (custom_angle > 0) { + // Bridge was not detected (likely it is only supported at one side). Still it is a surface filled in + // using a bridging flow, therefore it makes sense to respect the custom bridging direction. + bridges[idx_last].bridge_angle = custom_angle; + } // without safety offset, artifacts are generated (GH #2494) surfaces_append(bottom, union_ex(grown, true), bridges[idx_last]); } diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index f9db9fea0..b5b9a008d 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/Model.hpp b/src/libslic3r/Model.hpp index 8a48f8ee9..5cf7f49ca 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -394,6 +394,7 @@ public: const Vec3d& get_mirror() const { return m_transformation.get_mirror(); } double get_mirror(Axis axis) const { return m_transformation.get_mirror(axis); } + bool is_left_handed() const { return m_transformation.is_left_handed(); } void set_mirror(const Vec3d& mirror) { m_transformation.set_mirror(mirror); } void set_mirror(Axis axis, double mirror) { m_transformation.set_mirror(axis, mirror); } @@ -498,6 +499,7 @@ public: const Vec3d& get_mirror() const { return m_transformation.get_mirror(); } double get_mirror(Axis axis) const { return m_transformation.get_mirror(axis); } + bool is_left_handed() const { return m_transformation.is_left_handed(); } void set_mirror(const Vec3d& mirror) { m_transformation.set_mirror(mirror); } void set_mirror(Axis axis, double mirror) { m_transformation.set_mirror(axis, mirror); } diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp index 4942e35b6..54627ba86 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 cdb2cc008..04b993a52 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 c28e11352..496a584a2 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 768488adc..671dcbb3d 100644 --- a/src/libslic3r/Rasterizer/Rasterizer.hpp +++ b/src/libslic3r/Rasterizer/Rasterizer.hpp @@ -6,6 +6,8 @@ #include #include +namespace ClipperLib { struct 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/SLA/SLAAutoSupports.cpp b/src/libslic3r/SLA/SLAAutoSupports.cpp index 9e9f07b6c..0a2537b91 100644 --- a/src/libslic3r/SLA/SLAAutoSupports.cpp +++ b/src/libslic3r/SLA/SLAAutoSupports.cpp @@ -49,8 +49,8 @@ float SLAAutoSupports::distance_limit(float angle) const }*/ SLAAutoSupports::SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3D& emesh, const std::vector& slices, const std::vector& heights, - const Config& config, std::function throw_on_cancel) -: m_config(config), m_emesh(emesh), m_throw_on_cancel(throw_on_cancel) + const Config& config, std::function throw_on_cancel, std::function statusfn) +: m_config(config), m_emesh(emesh), m_throw_on_cancel(throw_on_cancel), m_statusfn(statusfn) { process(slices, heights); project_onto_mesh(m_output); @@ -197,6 +197,9 @@ void SLAAutoSupports::process(const std::vector& slices, const std:: PointGrid3D point_grid; point_grid.cell_size = Vec3f(10.f, 10.f, 10.f); + double increment = 100.0 / layers.size(); + double status = 0; + for (unsigned int layer_id = 0; layer_id < layers.size(); ++ layer_id) { SLAAutoSupports::MyLayer *layer_top = &layers[layer_id]; SLAAutoSupports::MyLayer *layer_bottom = (layer_id > 0) ? &layers[layer_id - 1] : nullptr; @@ -252,6 +255,9 @@ void SLAAutoSupports::process(const std::vector& slices, const std:: m_throw_on_cancel(); + status += increment; + m_statusfn(int(std::round(status))); + #ifdef SLA_AUTOSUPPORTS_DEBUG /*std::string layer_num_str = std::string((i<10 ? "0" : "")) + std::string((i<100 ? "0" : "")) + std::to_string(i); output_expolygons(expolys_top, "top" + layer_num_str + ".svg"); diff --git a/src/libslic3r/SLA/SLAAutoSupports.hpp b/src/libslic3r/SLA/SLAAutoSupports.hpp index 038b505da..d32d182bc 100644 --- a/src/libslic3r/SLA/SLAAutoSupports.hpp +++ b/src/libslic3r/SLA/SLAAutoSupports.hpp @@ -24,7 +24,7 @@ public: }; SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3D& emesh, const std::vector& slices, - const std::vector& heights, const Config& config, std::function throw_on_cancel); + const std::vector& heights, const Config& config, std::function throw_on_cancel, std::function statusfn); const std::vector& output() { return m_output; } struct MyLayer; @@ -196,12 +196,13 @@ private: static void output_structures(const std::vector &structures); #endif // SLA_AUTOSUPPORTS_DEBUG - std::function m_throw_on_cancel; const sla::EigenMesh3D& m_emesh; + std::function m_throw_on_cancel; + std::function m_statusfn; }; } // namespace Slic3r -#endif // SLAAUTOSUPPORTS_HPP_ \ No newline at end of file +#endif // SLAAUTOSUPPORTS_HPP_ diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index ee24cf3ec..7dc920517 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" @@ -39,37 +42,33 @@ namespace { // should add up to 100 (%) const std::array OBJ_STEP_LEVELS = { - 10, // slaposObjectSlice, - 30, // slaposSupportPoints, - 25, // slaposSupportTree, - 25, // slaposBasePool, - 5, // slaposSliceSupports, - 5 // slaposIndexSlices + 30, // slaposObjectSlice, + 20, // slaposSupportPoints, + 10, // slaposSupportTree, + 10, // slaposBasePool, + 30, // slaposSliceSupports, }; const std::array OBJ_STEP_LABELS = { L("Slicing model"), // slaposObjectSlice, - L("Generating support points"), // slaposSupportPoints, + L("Generating support points"), // slaposSupportPoints, 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 + 10, // slapsMergeSlicesAndEval + 90, // 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 }; } @@ -94,7 +93,10 @@ static Transform3d sla_trafo(const ModelObject &model_object) offset(0) = 0.; offset(1) = 0.; rotation(2) = 0.; - return Geometry::assemble_transform(offset, rotation, model_instance.get_scaling_factor(), model_instance.get_mirror()); + Transform3d trafo = Geometry::assemble_transform(offset, rotation, model_instance.get_scaling_factor(), model_instance.get_mirror()); + if (model_instance.is_left_handed()) + trafo = Eigen::Scaling(Vec3d(-1., 1., 1.)) * trafo; + return trafo; } // List of instances, where the ModelInstance transformation is a composite of sla_trafo and the transformation defined by SLAPrintObject::Instance. @@ -103,10 +105,10 @@ static std::vector sla_instances(const ModelObject &mo std::vector instances; for (ModelInstance *model_instance : model_object.instances) if (model_instance->is_printable()) { - instances.emplace_back(SLAPrintObject::Instance( + instances.emplace_back( model_instance->id(), Point::new_scale(model_instance->get_offset(X), model_instance->get_offset(Y)), - float(model_instance->get_rotation(Z)))); + float(model_instance->get_rotation(Z))); } return instances; } @@ -206,7 +208,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 +220,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(); @@ -315,8 +317,11 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf it_print_object_status = print_object_status.end(); // Check whether a model part volume was added or removed, their transformations or order changed. bool model_parts_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::MODEL_PART); - bool sla_trafo_differs = model_object.instances.empty() != model_object_new.instances.empty() || - (! model_object.instances.empty() && ! sla_trafo(model_object).isApprox(sla_trafo(model_object_new))); + bool sla_trafo_differs = + model_object.instances.empty() != model_object_new.instances.empty() || + (! model_object.instances.empty() && + (! sla_trafo(model_object).isApprox(sla_trafo(model_object_new)) || + model_object.instances.front()->is_left_handed() != model_object_new.instances.front()->is_left_handed())); if (model_parts_differ || sla_trafo_differs) { // The very first step (the slicing step) is invalidated. One may freely remove all associated PrintObjects. if (it_print_object_status != print_object_status.end()) { @@ -390,7 +395,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; @@ -400,9 +405,9 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf // FIXME: this invalidates the transformed mesh in SLAPrintObject // which is expensive to calculate (especially the raw_mesh() call) - print_object->set_trafo(sla_trafo(model_object)); + print_object->set_trafo(sla_trafo(model_object), model_object.instances.front()->is_left_handed()); - print_object->set_instances(new_instances); + print_object->set_instances(std::move(new_instances)); print_object->config_apply(config, true); print_objects_new.emplace_back(print_object); new_objects = true; @@ -579,11 +584,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,13 +613,17 @@ std::string SLAPrint::validate() const return ""; } -template -void report_status(SLAPrint& p, int st, const std::string& msg, Args&&...args) +bool SLAPrint::invalidate_step(SLAPrintStep step) { - BOOST_LOG_TRIVIAL(info) << st << "% " << msg; - p.set_status(st, msg, std::forward(args)...); -} + bool invalidated = Inherited::invalidate_step(step); + // propagate to dependent steps + if (step == slapsMergeSlicesAndEval) { + invalidated |= this->invalidate_all_steps(); + } + + return invalidated; +} void SLAPrint::process() { @@ -639,7 +643,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 = 50; // 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 @@ -714,7 +718,7 @@ void SLAPrint::process() // In this step we check the slices, identify island and cover them with // support points. Then we sprinkle the rest of the mesh. - auto support_points = [this](SLAPrintObject& po) { + auto support_points = [this, ostepd](SLAPrintObject& po) { const ModelObject& mo = *po.m_model_object; po.m_supportdata.reset( new SLAPrintObject::SupportData(po.transformed_mesh()) ); @@ -748,6 +752,19 @@ void SLAPrint::process() config.minimal_distance = float(cfg.support_points_minimal_distance); config.head_diameter = float(cfg.support_head_front_diameter); + // scaling for the sub operations + double d = ostepd * OBJ_STEP_LEVELS[slaposSupportPoints] / 100.0; + double init = m_report_status.status(); + + auto statuscb = [this, d, init](unsigned st) + { + double current = init + st * d; + if(std::round(m_report_status.status()) < std::round(current)) + m_report_status(*this, current, + OBJ_STEP_LABELS[slaposSupportPoints]); + + }; + // Construction of this object does the calculation. this->throw_if_canceled(); SLAAutoSupports auto_supports(po.transformed_mesh(), @@ -755,7 +772,8 @@ void SLAPrint::process() po.get_model_slices(), heights, config, - [this]() { throw_if_canceled(); }); + [this]() { throw_if_canceled(); }, + statuscb); // Now let's extract the result. const std::vector& points = auto_supports.output(); @@ -766,7 +784,7 @@ void SLAPrint::process() << po.m_supportdata->support_points.size(); // Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass the update status to GLGizmoSlaSupports - report_status(*this, -1, L("Generating support points"), SlicingStatus::RELOAD_SLA_SUPPORT_POINTS); + m_report_status(*this, -1, L("Generating support points"), SlicingStatus::RELOAD_SLA_SUPPORT_POINTS); } else { // There are either some points on the front-end, or the user removed them on purpose. No calculation will be done. @@ -775,7 +793,8 @@ void SLAPrint::process() }; // In this step we create the supports - auto support_tree = [this, objcount, ostepd](SLAPrintObject& po) { + auto support_tree = [this, ostepd](SLAPrintObject& po) + { if(!po.m_supportdata) return; if(!po.m_config.supports_enable.getBool()) { @@ -787,22 +806,17 @@ void SLAPrint::process() sla::SupportConfig scfg = make_support_cfg(po.m_config); sla::Controller ctl; - // some magic to scale the status values coming from the support - // tree creation into the whole print process - auto stfirst = OBJ_STEP_LEVELS.begin(); - auto stthis = stfirst + slaposSupportTree; - // we need to add up the status portions until this operation - int init = std::accumulate(stfirst, stthis, 0); - init = int(init * ostepd); // scale the init portion - // scaling for the sub operations - double d = *stthis / (objcount * 100.0); + double d = ostepd * OBJ_STEP_LEVELS[slaposSupportTree] / 100.0; + double init = m_report_status.status(); - ctl.statuscb = [this, init, d](unsigned st, const std::string& msg) + ctl.statuscb = [this, d, init](unsigned st, const std::string&) { - //FIXME this status line scaling does not seem to be correct. - // How does it account for an increasing object index? - report_status(*this, int(init + st*d), msg); + double current = init + st * d; + if(std::round(m_report_status.status()) < std::round(current)) + m_report_status(*this, current, + OBJ_STEP_LABELS[slaposSupportTree]); + }; ctl.stopcondition = [this](){ return canceled(); }; @@ -818,7 +832,7 @@ void SLAPrint::process() auto rc = SlicingStatus::RELOAD_SCENE; // This is to prevent "Done." being displayed during merged_mesh() - report_status(*this, -1, L("Visualizing supports")); + m_report_status(*this, -1, L("Visualizing supports")); po.m_supportdata->support_tree_ptr->merged_mesh(); BOOST_LOG_TRIVIAL(debug) << "Processed support point count " @@ -828,8 +842,7 @@ void SLAPrint::process() if(po.support_mesh().empty()) BOOST_LOG_TRIVIAL(warning) << "Support mesh is empty"; - report_status(*this, -1, L("Visualizing supports"), rc); - + m_report_status(*this, -1, L("Visualizing supports"), rc); }; // This step generates the sla base pad @@ -877,13 +890,13 @@ void SLAPrint::process() po.throw_if_canceled(); auto rc = SlicingStatus::RELOAD_SCENE; - report_status(*this, -1, L("Visualizing supports"), rc); + m_report_status(*this, -1, L("Visualizing supports"), rc); }; // 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 +919,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); + m_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 +961,284 @@ 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, + bool is_lefthanded) + { + 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); + } + + if(is_lefthanded) { + for(auto& p : poly.Contour) p.X = -p.X; + std::reverse(poly.Contour.begin(), poly.Contour.end()); + for(auto& h : poly.Holes) { + for(auto& p : h) p.X = -p.X; + std::reverse(h.begin(), h.end()); + } + } + + 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); + bool is_lefth = record.print_obj()->is_left_handed(); + if (!modelslices.empty()) { + ClipperPolygons v = get_all_polygons(modelslices, po->instances(), is_lefth); + 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(), is_lefth); + 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; + + m_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 @@ -994,18 +1271,24 @@ void SLAPrint::process() auto lvlcnt = unsigned(m_printer_input.size()); printer.layers(lvlcnt); - // slot is the portion of 100% that is realted to rasterization - unsigned slot = PRINT_STEP_LEVELS[slapsRasterize]; - // ist: initial state; pst: previous state - unsigned ist = max_objstatus, pst = ist; // coefficient to map the rasterization state (0-99) to the allocated // portion (slot) of the process state - double sd = (100 - ist) / 100.0; + double sd = (100 - max_objstatus) / 100.0; + + // slot is the portion of 100% that is realted to rasterization + unsigned slot = PRINT_STEP_LEVELS[slapsRasterize]; + + // pst: previous state + double pst = m_report_status.status(); + + double increment = (slot * sd) / m_printer_input.size(); + double dstatus = m_report_status.status(); + SpinMutex slck; // procedure to process one height level. This will run in parallel auto lvlfn = - [this, &slck, &printer, slot, sd, ist, &pst, flpXY] + [this, &slck, &printer, increment, &dstatus, &pst] (unsigned level_id) { if(canceled()) return; @@ -1015,39 +1298,20 @@ 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); // Status indication guarded with the spinlock - auto st = ist + unsigned(sd*level_id*slot/m_printer_input.size()); { std::lock_guard lck(slck); - if( st > pst) { - report_status(*this, int(st), - PRINT_STEP_LABELS[slapsRasterize]); + dstatus += increment; + double st = std::round(dstatus); + if(st > pst) { + m_report_status(*this, st, + PRINT_STEP_LABELS[slapsRasterize]); pst = st; } } @@ -1079,18 +1343,16 @@ 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; + double st = min_objstatus; unsigned incr = 0; BOOST_LOG_TRIVIAL(info) << "Start slicing process."; @@ -1104,18 +1366,18 @@ void SLAPrint::process() BOOST_LOG_TRIVIAL(info) << "Slicing object " << po->model_object()->name; - for (int s = (int)step_ranges[idx_range]; s < (int)step_ranges[idx_range + 1]; ++s) { - auto currentstep = (SLAPrintObjectStep)s; + for (int s = int(step_ranges[idx_range]); s < int(step_ranges[idx_range + 1]); ++s) { + auto currentstep = static_cast(s); // Cancellation checking. Each step will check for cancellation // on its own and return earlier gracefully. Just after it returns // execution gets to this point and throws the canceled signal. throw_if_canceled(); - st += unsigned(incr * ostepd); + st += incr * ostepd; if(po->m_stepmask[currentstep] && po->set_started(currentstep)) { - report_status(*this, int(st), OBJ_STEP_LABELS[currentstep]); + m_report_status(*this, st, OBJ_STEP_LABELS[currentstep]); pobj_program[currentstep](*po); throw_if_canceled(); po->set_done(currentstep); @@ -1127,7 +1389,7 @@ void SLAPrint::process() } std::array printsteps = { - slapsStats, slapsRasterize, slapsValidate + slapsMergeSlicesAndEval, slapsRasterize }; // this would disable the rasterization step @@ -1142,17 +1404,17 @@ void SLAPrint::process() if(m_stepmask[currentstep] && set_started(currentstep)) { - report_status(*this, int(st), PRINT_STEP_LABELS[currentstep]); + m_report_status(*this, st, PRINT_STEP_LABELS[currentstep]); print_program[currentstep](); throw_if_canceled(); set_done(currentstep); } - st += unsigned(PRINT_STEP_LEVELS[currentstep] * pstd); + st += PRINT_STEP_LEVELS[currentstep] * pstd; } // If everything vent well - report_status(*this, 100, L("Slicing done")); + m_report_status(*this, 100, L("Slicing done")); } bool SLAPrint::invalidate_state_by_config_options(const std::vector &opt_keys) @@ -1193,11 +1455,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 +1562,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 +1647,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) { @@ -1650,7 +1738,8 @@ DynamicConfig SLAPrintStatistics::placeholders() "print_time", "total_cost", "total_weight", "objects_used_material", "support_used_material" }) config.set_key_value(key, new ConfigOptionString(std::string("{") + key + "}")); - return config; + + return config; } std::string SLAPrintStatistics::finalize_output_path(const std::string &path_in) const @@ -1670,4 +1759,12 @@ std::string SLAPrintStatistics::finalize_output_path(const std::string &path_in) return final_path; } +void SLAPrint::StatusReporter::operator()( + SLAPrint &p, double st, const std::string &msg, unsigned flags) +{ + m_st = st; + BOOST_LOG_TRIVIAL(info) << st << "% " << msg; + p.set_status(int(std::round(st)), msg, flags); +} + } // namespace Slic3r diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index d831908c7..a1e382acb 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 }; @@ -52,6 +51,7 @@ public: const SLAPrintObjectConfig& config() const { return m_config; } const Transform3d& trafo() const { return m_trafo; } + bool is_left_handed() const { return m_left_handed; } struct Instance { Instance(ModelID instance_id, const Point &shift, float rotation) : instance_id(instance_id), shift(shift), rotation(rotation) {} @@ -127,7 +127,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 +190,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 +205,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 @@ -240,11 +242,12 @@ protected: void config_apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false) { this->m_config.apply_only(other, keys, ignore_nonexistent); } - void set_trafo(const Transform3d& trafo) { - m_transformed_rmesh.invalidate([this, &trafo](){ m_trafo = trafo; }); + void set_trafo(const Transform3d& trafo, bool left_handed) { + m_transformed_rmesh.invalidate([this, &trafo, left_handed](){ m_trafo = trafo; m_left_handed = left_handed; }); } - void set_instances(const std::vector &instances) { m_instances = instances; } + template inline void set_instances(InstVec&& instances) { m_instances = std::forward(instances); } + // Invalidates the step, and its depending steps in SLAPrintObject and SLAPrint. bool invalidate_step(SLAPrintObjectStep step); bool invalidate_all_steps(); @@ -261,6 +264,8 @@ private: // Translation in Z + Rotation by Y and Z + Scaling / Mirroring. Transform3d m_trafo = Transform3d::Identity(); + // m_trafo is left handed -> 3x3 affine transformation has negative determinant. + bool m_left_handed = false; std::vector m_instances; @@ -367,31 +372,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 +387,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 +407,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 +452,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; @@ -457,6 +475,15 @@ private: // Estimated print time, material consumed. SLAPrintStatistics m_print_statistics; + class StatusReporter { + double m_st = 0; + public: + void operator() (SLAPrint& p, double st, const std::string& msg, + unsigned flags = SlicingStatus::DEFAULT); + double status() const { return m_st; } + } m_report_status; + + friend SLAPrintObject; }; diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index eaa11b738..c145380c9 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,79 @@ 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; + throw std::runtime_error("find_unvisited_neighbors() requires repair()"); - 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]; - 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 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; + std::deque facets; + auto facet = std::find(facet_visited.begin(), facet_visited.end(), false); + if (facet != facet_visited.end()) { + uint32_t idx = uint32_t(facet - facet_visited.begin()); + facet_queue.push(idx); + facet_visited[idx] = true; + facets.emplace_back(idx); } - return num_bodies; + // Traverse all reachable neighbors and mark them as visited. + while (! facet_queue.empty()) { + uint32_t facet_idx = facet_queue.front(); + facet_queue.pop(); + for (int neighbor_idx : this->stl.neighbors_start[facet_idx].neighbor) + if (neighbor_idx != -1 && ! facet_visited[neighbor_idx]) { + facet_queue.push(uint32_t(neighbor_idx)); + facet_visited[neighbor_idx] = true; + facets.emplace_back(uint32_t(neighbor_idx)); + } + } + + 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 +418,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 +443,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 +456,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 +498,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 +623,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 +872,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 +883,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 +1661,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 a4387e5c1..a65a4be75 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -68,18 +68,14 @@ 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; stl_file stl; bool repaired; private: void require_shared_vertices(); + std::deque find_unvisited_neighbors(std::vector &facet_visited) const; friend class TriangleMeshSlicer; }; diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index c13df4546..dfd72b7a9 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 5198eeaa3..958b66305 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/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 58011730b..7ba61bdb6 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -333,6 +333,13 @@ Transform3d GLVolume::world_matrix() const return m; } +bool GLVolume::is_left_handed() const +{ + const Vec3d &m1 = m_instance_transformation.get_mirror(); + const Vec3d &m2 = m_volume_transformation.get_mirror(); + return m1.x() * m1.y() * m1.z() * m2.x() * m2.y() * m2.z() < 0.; +} + const BoundingBoxf3& GLVolume::transformed_bounding_box() const { assert(bounding_box.defined || bounding_box.min(0) >= bounding_box.max(0) || bounding_box.min(1) >= bounding_box.max(1) || bounding_box.min(2) >= bounding_box.max(2)); @@ -401,6 +408,8 @@ void GLVolume::render() const if (!is_active) return; + if (this->is_left_handed()) + glFrontFace(GL_CW); glsafe(::glCullFace(GL_BACK)); glsafe(::glPushMatrix()); @@ -410,6 +419,8 @@ void GLVolume::render() const else this->indexed_vertex_array.render(); glsafe(::glPopMatrix()); + if (this->is_left_handed()) + glFrontFace(GL_CCW); } void GLVolume::render_VBOs(int color_id, int detection_id, int worldmatrix_id) const @@ -420,6 +431,9 @@ void GLVolume::render_VBOs(int color_id, int detection_id, int worldmatrix_id) c if (!indexed_vertex_array.vertices_and_normals_interleaved_VBO_id) return; + if (this->is_left_handed()) + glFrontFace(GL_CW); + GLsizei n_triangles = GLsizei(std::min(indexed_vertex_array.triangle_indices_size, tverts_range.second - tverts_range.first)); GLsizei n_quads = GLsizei(std::min(indexed_vertex_array.quad_indices_size, qverts_range.second - qverts_range.first)); if (n_triangles + n_quads == 0) @@ -481,6 +495,9 @@ void GLVolume::render_VBOs(int color_id, int detection_id, int worldmatrix_id) c } glsafe(::glPopMatrix()); + + if (this->is_left_handed()) + glFrontFace(GL_CCW); } void GLVolume::render_legacy() const @@ -489,6 +506,9 @@ void GLVolume::render_legacy() const if (!is_active) return; + if (this->is_left_handed()) + glFrontFace(GL_CW); + GLsizei n_triangles = GLsizei(std::min(indexed_vertex_array.triangle_indices_size, tverts_range.second - tverts_range.first)); GLsizei n_quads = GLsizei(std::min(indexed_vertex_array.quad_indices_size, qverts_range.second - qverts_range.first)); if (n_triangles + n_quads == 0) @@ -520,6 +540,9 @@ void GLVolume::render_legacy() const glsafe(::glDrawElements(GL_QUADS, n_quads, GL_UNSIGNED_INT, indexed_vertex_array.quad_indices.data() + qverts_range.first)); glsafe(::glPopMatrix()); + + if (this->is_left_handed()) + glFrontFace(GL_CCW); } std::vector GLVolumeCollection::load_object( diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index e421997e5..5cc301a39 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -388,6 +388,7 @@ public: int instance_idx() const { return this->composite_id.instance_id; } Transform3d world_matrix() const; + bool is_left_handed() const; const BoundingBoxf3& transformed_bounding_box() const; const BoundingBoxf3& transformed_convex_hull_bounding_box() const; diff --git a/src/slic3r/GUI/FirmwareDialog.cpp b/src/slic3r/GUI/FirmwareDialog.cpp index 1a294d210..8095a3237 100644 --- a/src/slic3r/GUI/FirmwareDialog.cpp +++ b/src/slic3r/GUI/FirmwareDialog.cpp @@ -59,6 +59,8 @@ enum { USB_PID_MK3 = 2, USB_PID_MMU_BOOT = 3, USB_PID_MMU_APP = 4, + USB_PID_CW1_BOOT = 7, + USB_PID_CW1_APP = 8, }; // This enum discriminates the kind of information in EVT_AVRDUDE, @@ -77,6 +79,13 @@ wxDEFINE_EVENT(EVT_AVRDUDE, wxCommandEvent); wxDECLARE_EVENT(EVT_ASYNC_DIALOG, wxCommandEvent); wxDEFINE_EVENT(EVT_ASYNC_DIALOG, wxCommandEvent); +struct Avr109Pid +{ + unsigned boot; + unsigned app; + + Avr109Pid(unsigned boot, unsigned app) : boot(boot), app(app) {} +}; // Private @@ -146,24 +155,40 @@ struct FirmwareDialog::priv void flashing_done(AvrDudeComplete complete); void enable_port_picker(bool enable); void load_hex_file(const wxString &path); - void queue_status(wxString message); - void queue_error(const wxString &message); + void queue_event(AvrdudeEvent aevt, wxString message); bool ask_model_id_mismatch(const std::string &printer_model); bool check_model_id(); - void wait_for_mmu_bootloader(unsigned retries); - void mmu_reboot(const SerialPortInfo &port); - void lookup_port_mmu(); + void avr109_wait_for_bootloader(Avr109Pid usb_pid, unsigned retries); + void avr109_reboot(const SerialPortInfo &port); + void avr109_lookup_port(Avr109Pid usb_pid); void prepare_common(); void prepare_mk2(); void prepare_mk3(); - void prepare_mm_control(); + void prepare_avr109(Avr109Pid usb_pid); void perform_upload(); void user_cancel(); void on_avrdude(const wxCommandEvent &evt); void on_async_dialog(const wxCommandEvent &evt); void ensure_joined(); + + void queue_status(wxString message) { queue_event(AE_STATUS, std::move(message)); } + + template void queue_message(const wxString &format, Args... args) { + auto message = wxString::Format(format, args...); + BOOST_LOG_TRIVIAL(info) << message; + message.Append('\n'); + queue_event(AE_MESSAGE, std::move(message)); + } + + template void queue_error(const wxString &format, Args... args) { + queue_message(format, args...); + queue_event(AE_STATUS, _(L("Flashing failed: ")) + wxString::Format(format, args...)); + avrdude->cancel(); + } + + static const char* avr109_dev_name(Avr109Pid usb_pid); }; void FirmwareDialog::priv::find_serial_ports() @@ -259,26 +284,18 @@ void FirmwareDialog::priv::enable_port_picker(bool enable) void FirmwareDialog::priv::load_hex_file(const wxString &path) { hex_file = HexFile(path.wx_str()); - enable_port_picker(hex_file.device != HexFile::DEV_MM_CONTROL); + const bool auto_lookup = hex_file.device == HexFile::DEV_MM_CONTROL || hex_file.device == HexFile::DEV_CW1; + enable_port_picker(! auto_lookup); } -void FirmwareDialog::priv::queue_status(wxString message) +void FirmwareDialog::priv::queue_event(AvrdudeEvent aevt, wxString message) { auto evt = new wxCommandEvent(EVT_AVRDUDE, this->q->GetId()); - evt->SetExtraLong(AE_STATUS); + evt->SetExtraLong(aevt); evt->SetString(std::move(message)); wxQueueEvent(this->q, evt); } -void FirmwareDialog::priv::queue_error(const wxString &message) -{ - auto evt = new wxCommandEvent(EVT_AVRDUDE, this->q->GetId()); - evt->SetExtraLong(AE_STATUS); - evt->SetString(wxString::Format(_(L("Flashing failed: %s")), message)); - - wxQueueEvent(this->q, evt); avrdude->cancel(); -} - bool FirmwareDialog::priv::ask_model_id_mismatch(const std::string &printer_model) { // model_id in the hex file doesn't match what the printer repoted. @@ -356,7 +373,7 @@ bool FirmwareDialog::priv::check_model_id() // return false; } -void FirmwareDialog::priv::wait_for_mmu_bootloader(unsigned retries) +void FirmwareDialog::priv::avr109_wait_for_bootloader(Avr109Pid usb_pid, unsigned retries) { enum { SLEEP_MS = 500, @@ -367,61 +384,63 @@ void FirmwareDialog::priv::wait_for_mmu_bootloader(unsigned retries) auto ports = Utils::scan_serial_ports_extended(); ports.erase(std::remove_if(ports.begin(), ports.end(), [=](const SerialPortInfo &port ) { - return port.id_vendor != USB_VID_PRUSA || port.id_product != USB_PID_MMU_BOOT; + return port.id_vendor != USB_VID_PRUSA || port.id_product != usb_pid.boot; }), ports.end()); if (ports.size() == 1) { port = ports[0]; return; } else if (ports.size() > 1) { - BOOST_LOG_TRIVIAL(error) << "Several VID/PID 0x2c99/3 devices found"; - queue_error(_(L("Multiple Original Prusa i3 MMU 2.0 devices found. Please only connect one at a time for flashing."))); + queue_message("Several VID/PID 0x2c99/%u devices found", usb_pid.boot); + queue_error(_(L("Multiple %s devices found. Please only connect one at a time for flashing.")), avr109_dev_name(usb_pid)); return; } } } -void FirmwareDialog::priv::mmu_reboot(const SerialPortInfo &port) +void FirmwareDialog::priv::avr109_reboot(const SerialPortInfo &port) { asio::io_service io; Serial serial(io, port.port, 1200); std::this_thread::sleep_for(std::chrono::milliseconds(50)); } -void FirmwareDialog::priv::lookup_port_mmu() +void FirmwareDialog::priv::avr109_lookup_port(Avr109Pid usb_pid) { - static const auto msg_not_found = - "The Multi Material Control device was not found.\n" - "If the device is connected, please press the Reset button next to the USB connector ..."; + const char *dev_name = avr109_dev_name(usb_pid); + const wxString msg_not_found = wxString::Format( + _(L("The %s device was not found.\n" + "If the device is connected, please press the Reset button next to the USB connector ...")), + dev_name); - BOOST_LOG_TRIVIAL(info) << "Flashing MMU 2.0, looking for VID/PID 0x2c99/3 or 0x2c99/4 ..."; + queue_message("Flashing %s, looking for VID/PID 0x2c99/%u or 0x2c99/%u ...", dev_name, usb_pid.boot, usb_pid.app); auto ports = Utils::scan_serial_ports_extended(); ports.erase(std::remove_if(ports.begin(), ports.end(), [=](const SerialPortInfo &port ) { return port.id_vendor != USB_VID_PRUSA || - port.id_product != USB_PID_MMU_BOOT && - port.id_product != USB_PID_MMU_APP; + port.id_product != usb_pid.boot && + port.id_product != usb_pid.app; }), ports.end()); if (ports.size() == 0) { - BOOST_LOG_TRIVIAL(info) << "MMU 2.0 device not found, asking the user to press Reset and waiting for the device to show up ..."; - queue_status(_(L(msg_not_found))); - wait_for_mmu_bootloader(30); + queue_message("The %s device was not found.", dev_name); + queue_status(msg_not_found); + avr109_wait_for_bootloader(usb_pid, 30); } else if (ports.size() > 1) { - BOOST_LOG_TRIVIAL(error) << "Several VID/PID 0x2c99/3 devices found"; - queue_error(_(L("Multiple Original Prusa i3 MMU 2.0 devices found. Please only connect one at a time for flashing."))); + queue_message("Several VID/PID 0x2c99/%u devices found", usb_pid.boot); + queue_error(_(L("Multiple %s devices found. Please only connect one at a time for flashing.")), dev_name); } else { - if (ports[0].id_product == USB_PID_MMU_APP) { + if (ports[0].id_product == usb_pid.app) { // The device needs to be rebooted into the bootloader mode - BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/4 at `%1%`, rebooting the device ...") % ports[0].port; - mmu_reboot(ports[0]); - wait_for_mmu_bootloader(10); + queue_message("Found VID/PID 0x2c99/%u at `%s`, rebooting the device ...", usb_pid.app, ports[0].port); + avr109_reboot(ports[0]); + avr109_wait_for_bootloader(usb_pid, 10); if (! port) { // The device in bootloader mode was not found, inform the user and wait some more... - BOOST_LOG_TRIVIAL(info) << "MMU 2.0 bootloader device not found after reboot, asking the user to press Reset and waiting for the device to show up ..."; - queue_status(_(L(msg_not_found))); - wait_for_mmu_bootloader(30); + queue_message("%s device not found after reboot", dev_name); + queue_status(msg_not_found); + avr109_wait_for_bootloader(usb_pid, 30); } } else { port = ports[0]; @@ -498,16 +517,16 @@ void FirmwareDialog::priv::prepare_mk3() avrdude->push_args(std::move(args)); } -void FirmwareDialog::priv::prepare_mm_control() +void FirmwareDialog::priv::prepare_avr109(Avr109Pid usb_pid) { port = boost::none; - lookup_port_mmu(); + avr109_lookup_port(usb_pid); if (! port) { - queue_error(_(L("The device could not have been found"))); + queue_error(_(L("The %s device could not have been found")), avr109_dev_name(usb_pid)); return; } - BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/3 at `%1%`, flashing ...") % port->port; + queue_message("Found VID/PID 0x2c99/%u at `%s`, flashing ...", usb_pid.boot, port->port); queue_status(label_status_flashing); std::vector args {{ @@ -568,7 +587,11 @@ void FirmwareDialog::priv::perform_upload() break; case HexFile::DEV_MM_CONTROL: - this->prepare_mm_control(); + this->prepare_avr109(Avr109Pid(USB_PID_MMU_BOOT, USB_PID_MMU_APP)); + break; + + case HexFile::DEV_CW1: + this->prepare_avr109(Avr109Pid(USB_PID_CW1_BOOT, USB_PID_CW1_APP)); break; default: @@ -576,7 +599,11 @@ void FirmwareDialog::priv::perform_upload() break; } } catch (const std::exception &ex) { - queue_error(wxString::Format(_(L("Error accessing port at %s: %s")), port->port, ex.what())); + if (port) { + queue_error(_(L("Error accessing port at %s: %s")), port->port, ex.what()); + } else { + queue_error(_(L("Error: %s")), ex.what()); + } } }) .on_message(std::move([q, extra_verbose](const char *msg, unsigned /* size */) { @@ -688,6 +715,19 @@ void FirmwareDialog::priv::ensure_joined() avrdude.reset(); } +const char* FirmwareDialog::priv::avr109_dev_name(Avr109Pid usb_pid) { + switch (usb_pid.boot) { + case USB_PID_MMU_BOOT: + return "Prusa MMU 2.0 Control"; + break; + case USB_PID_CW1_BOOT: + return "Prusa CurWa"; + break; + + default: throw std::runtime_error((boost::format("Invalid avr109 device USB PID: %1%") % usb_pid.boot).str()); + } +} + // Public @@ -757,7 +797,7 @@ FirmwareDialog::FirmwareDialog(wxWindow *parent) : vsizer->Add(grid, 0, wxEXPAND | wxTOP | wxBOTTOM, SPACING); - p->spoiler = new wxCollapsiblePane(panel, wxID_ANY, _(L("Advanced: avrdude output log")), wxDefaultPosition, wxDefaultSize, wxCP_DEFAULT_STYLE | wxCP_NO_TLW_RESIZE); + p->spoiler = new wxCollapsiblePane(panel, wxID_ANY, _(L("Advanced: Output log")), wxDefaultPosition, wxDefaultSize, wxCP_DEFAULT_STYLE | wxCP_NO_TLW_RESIZE); auto *spoiler_pane = p->spoiler->GetPane(); auto *spoiler_sizer = new wxBoxSizer(wxVERTICAL); p->txt_stdout = new wxTextCtrl(spoiler_pane, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE | wxTE_READONLY); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index a25b7e7c1..79a3684ef 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -380,7 +380,7 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const float GLCanvas3D::LayersEditing::get_cursor_z_relative(const GLCanvas3D& canvas) { - const Point& mouse_pos = canvas.get_local_mouse_position(); + const Vec2d mouse_pos = canvas.get_local_mouse_position(); const Rect& rect = get_bar_rect_screen(canvas); float x = (float)mouse_pos(0); float y = (float)mouse_pos(1); @@ -3915,19 +3915,25 @@ Size GLCanvas3D::get_canvas_size() const w *= factor; h *= factor; #else - const float factor = 1.0; + const float factor = 1.0f; #endif return Size(w, h, factor); } -Point GLCanvas3D::get_local_mouse_position() const +Vec2d GLCanvas3D::get_local_mouse_position() const { if (m_canvas == nullptr) - return Point(); + return Vec2d::Zero(); wxPoint mouse_pos = m_canvas->ScreenToClient(wxGetMousePosition()); - return Point(mouse_pos.x, mouse_pos.y); + const double factor = +#if ENABLE_RETINA_GL + m_retina_helper->get_scale_factor(); +#else + 1.0; +#endif + return Vec2d(factor * mouse_pos.x, factor * mouse_pos.y); } void GLCanvas3D::reset_legend_texture() @@ -4397,11 +4403,13 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h) if ((m_canvas == nullptr) && (m_context == nullptr)) return; - wxGetApp().imgui()->set_display_size((float)w, (float)h); + auto *imgui = wxGetApp().imgui(); + imgui->set_display_size((float)w, (float)h); + const float font_size = 1.5f * wxGetApp().em_unit(); #if ENABLE_RETINA_GL - wxGetApp().imgui()->set_style_scaling(m_retina_helper->get_scale_factor()); + imgui->set_scaling(font_size, 1.0f, m_retina_helper->get_scale_factor()); #else - wxGetApp().imgui()->set_style_scaling(m_canvas->GetContentScaleFactor()); + imgui->set_scaling(font_size, m_canvas->GetContentScaleFactor(), 1.0f); #endif // ensures that this canvas is current @@ -4979,6 +4987,9 @@ void GLCanvas3D::_render_sla_slices() const { const SLAPrintObject* obj = print_objects[i]; + if (!obj->is_step_done(slaposSliceSupports)) + continue; + SlaCap::ObjectIdToTrianglesMap::iterator it_caps_bottom = m_sla_caps[0].triangles.find(i); SlaCap::ObjectIdToTrianglesMap::iterator it_caps_top = m_sla_caps[1].triangles.find(i); { @@ -5002,24 +5013,12 @@ void GLCanvas3D::_render_sla_slices() const Pointf3s &top_obj_triangles = it_caps_top->second.object; Pointf3s &top_sup_triangles = it_caps_top->second.supports; - const std::vector& instances = obj->instances(); - struct InstanceTransform - { - Vec3d offset; - float rotation; - }; - - std::vector instance_transforms; - for (const SLAPrintObject::Instance& inst : instances) - { - instance_transforms.push_back({ to_3d(unscale(inst.shift), 0.), Geometry::rad2deg(inst.rotation) }); - } - 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->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; + bool left_handed = obj->is_left_handed(); coord_t key_zero = obj->get_slice_index().front().print_level(); // Slice at the center of the slab starting at clip_min_z will be rendered for the lower plane. @@ -5038,10 +5037,10 @@ void GLCanvas3D::_render_sla_slices() const const ExPolygons& sup_bottom = slice_low.get_slice(soSupport); // calculate model bottom cap if (bottom_obj_triangles.empty() && !obj_bottom.empty()) - bottom_obj_triangles = triangulate_expolygons_3d(obj_bottom, clip_min_z - plane_shift_z, true); + bottom_obj_triangles = triangulate_expolygons_3d(obj_bottom, clip_min_z - plane_shift_z, ! left_handed); // calculate support bottom cap if (bottom_sup_triangles.empty() && !sup_bottom.empty()) - bottom_sup_triangles = triangulate_expolygons_3d(sup_bottom, clip_min_z - plane_shift_z, true); + bottom_sup_triangles = triangulate_expolygons_3d(sup_bottom, clip_min_z - plane_shift_z, ! left_handed); } if (slice_high.is_valid()) { @@ -5049,49 +5048,43 @@ void GLCanvas3D::_render_sla_slices() const const ExPolygons& sup_top = slice_high.get_slice(soSupport); // calculate model top cap if (top_obj_triangles.empty() && !obj_top.empty()) - top_obj_triangles = triangulate_expolygons_3d(obj_top, clip_max_z + plane_shift_z, false); + top_obj_triangles = triangulate_expolygons_3d(obj_top, clip_max_z + plane_shift_z, left_handed); // calculate support top cap if (top_sup_triangles.empty() && !sup_top.empty()) - top_sup_triangles = triangulate_expolygons_3d(sup_top, clip_max_z + plane_shift_z, false); + top_sup_triangles = triangulate_expolygons_3d(sup_top, clip_max_z + plane_shift_z, left_handed); } } if (!bottom_obj_triangles.empty() || !top_obj_triangles.empty() || !bottom_sup_triangles.empty() || !top_sup_triangles.empty()) { - for (const InstanceTransform& inst : instance_transforms) + for (const SLAPrintObject::Instance& inst : obj->instances()) { ::glPushMatrix(); - ::glTranslated(inst.offset(0), inst.offset(1), inst.offset(2)); - ::glRotatef(inst.rotation, 0.0, 0.0, 1.0); - - ::glBegin(GL_TRIANGLES); - - ::glColor3f(1.0f, 0.37f, 0.0f); - - for (const Vec3d& v : bottom_obj_triangles) - { - ::glVertex3dv((GLdouble*)v.data()); - } - - for (const Vec3d& v : top_obj_triangles) - { - ::glVertex3dv((GLdouble*)v.data()); - } - - ::glColor3f(1.0f, 0.0f, 0.37f); - - for (const Vec3d& v : bottom_sup_triangles) - { - ::glVertex3dv((GLdouble*)v.data()); - } - - for (const Vec3d& v : top_sup_triangles) - { - ::glVertex3dv((GLdouble*)v.data()); - } - - ::glEnd(); - + ::glTranslated(unscale(inst.shift.x()), unscale(inst.shift.y()), 0); + ::glRotatef(Geometry::rad2deg(inst.rotation), 0.0, 0.0, 1.0); + if (obj->is_left_handed()) + // The polygons are mirrored by X. + ::glScalef(-1.0, 1.0, 1.0); + ::glEnableClientState(GL_VERTEX_ARRAY); + ::glColor3f(1.0f, 0.37f, 0.0f); + if (!bottom_obj_triangles.empty()) { + ::glVertexPointer(3, GL_DOUBLE, 0, (GLdouble*)bottom_obj_triangles.front().data()); + ::glDrawArrays(GL_TRIANGLES, 0, bottom_obj_triangles.size()); + } + if (! top_obj_triangles.empty()) { + ::glVertexPointer(3, GL_DOUBLE, 0, (GLdouble*)top_obj_triangles.front().data()); + ::glDrawArrays(GL_TRIANGLES, 0, top_obj_triangles.size()); + } + ::glColor3f(1.0f, 0.0f, 0.37f); + if (! bottom_sup_triangles.empty()) { + ::glVertexPointer(3, GL_DOUBLE, 0, (GLdouble*)bottom_sup_triangles.front().data()); + ::glDrawArrays(GL_TRIANGLES, 0, bottom_sup_triangles.size()); + } + if (! top_sup_triangles.empty()) { + ::glVertexPointer(3, GL_DOUBLE, 0, (GLdouble*)top_sup_triangles.front().data()); + ::glDrawArrays(GL_TRIANGLES, 0, top_sup_triangles.size()); + } + ::glDisableClientState(GL_VERTEX_ARRAY); ::glPopMatrix(); } } @@ -6209,6 +6202,8 @@ void GLCanvas3D::_load_shells_fff() void GLCanvas3D::_load_shells_sla() { + //FIXME use reload_scene +#if 1 const SLAPrint* print = this->sla_print(); if (print->objects().empty()) // nothing to render, return @@ -6218,23 +6213,30 @@ 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(); + // selects only instances which were sliced const ModelObject* model_obj = obj->model_object(); - std::vector instance_idxs(model_obj->instances.size()); - for (int i = 0; i < (int)model_obj->instances.size(); ++i) + const std::vector& sla_instances = obj->instances(); + std::vector instances_model_idxs(sla_instances.size()); + for (int i = 0; i < (int)sla_instances.size(); ++i) { - instance_idxs[i] = i; + instances_model_idxs[i] = (int)sla_instances[i].instance_id.id; } - m_volumes.load_object(model_obj, obj_idx, instance_idxs, "object", m_use_VBOs && m_initialized); + std::vector sliced_instance_idxs; + for (int i = 0; i < (int)model_obj->instances.size(); ++i) + { + if (std::find(instances_model_idxs.begin(), instances_model_idxs.end(), (int)model_obj->instances[i]->id().id) != instances_model_idxs.end()) + sliced_instance_idxs.push_back(i); + } - const std::vector& instances = obj->instances(); + m_volumes.load_object(model_obj, obj_idx, sliced_instance_idxs, "object", m_use_VBOs && m_initialized); - for (const SLAPrintObject::Instance& instance : instances) + for (const SLAPrintObject::Instance& instance : sla_instances) { Vec3d offset = unscale(instance.shift(0), instance.shift(1), 0); Vec3d rotation(0.0, 0.0, (double)instance.rotation); @@ -6257,6 +6259,7 @@ void GLCanvas3D::_load_shells_sla() v.composite_id.volume_id = -1; v.set_instance_offset(offset); v.set_instance_rotation(rotation); + v.set_instance_mirror(X, obj->is_left_handed() ? -1. : 1.); } // add pad @@ -6275,6 +6278,7 @@ void GLCanvas3D::_load_shells_sla() v.composite_id.volume_id = -1; v.set_instance_offset(offset); v.set_instance_rotation(rotation); + v.set_instance_mirror(X, obj->is_left_handed() ? -1. : 1.); } // finalize volumes and sends geometry to gpu @@ -6297,6 +6301,9 @@ void GLCanvas3D::_load_shells_sla() } update_volumes_colors_by_extruder(); +#else + this->reload_scene(true, true); +#endif } void GLCanvas3D::_update_gcode_volumes_visibility(const GCodePreviewData& preview_data) diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index cc69117bf..7e414d52a 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -634,6 +634,7 @@ public: m_sla_caps[id].reset(); } } + void reset_clipping_planes_cache() { m_sla_caps[0].triangles.clear(); m_sla_caps[1].triangles.clear(); } void set_use_clipping_planes(bool use) { m_use_clipping_planes = use; } void set_color_by(const std::string& value); @@ -699,7 +700,7 @@ public: void on_paint(wxPaintEvent& evt); Size get_canvas_size() const; - Point get_local_mouse_position() const; + Vec2d get_local_mouse_position() const; void reset_legend_texture(); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index bdfd2fc26..f1a7d9263 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -90,7 +90,6 @@ bool GUI_App::OnInit() const wxString resources_dir = from_u8(Slic3r::resources_dir()); wxCHECK_MSG(wxDirExists(resources_dir), false, wxString::Format("Resources path does not exist or is not a directory: %s", resources_dir)); - wxCHECK_MSG(m_imgui->init(), false, "Failed to initialize ImGui"); SetAppName("Slic3rPE-beta"); SetAppDisplayName("Slic3r Prusa Edition"); @@ -136,6 +135,10 @@ bool GUI_App::OnInit() app_config->save(); }); + // initialize label colors and fonts + init_label_colours(); + init_fonts(); + load_language(); // Suppress the '- default -' presets. @@ -148,9 +151,6 @@ bool GUI_App::OnInit() // Let the libslic3r know the callback, which will translate messages on demand. Slic3r::I18N::set_translate_callback(libslic3r_translate_callback); - // initialize label colors and fonts - init_label_colours(); - init_fonts(); // application frame if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr) diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index f41d148de..438e9d236 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -27,7 +27,7 @@ namespace Slic3r { namespace GUI { - View3D::View3D(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process) +View3D::View3D(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process) : m_canvas_widget(nullptr) , m_canvas(nullptr) { @@ -155,7 +155,9 @@ void View3D::render() m_canvas->set_as_dirty(); } -Preview::Preview(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, DynamicPrintConfig* config, BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data, std::function schedule_background_process_func) +Preview::Preview( + wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model, DynamicPrintConfig* config, + BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data, std::function schedule_background_process_func) : m_canvas_widget(nullptr) , m_canvas(nullptr) , m_double_slider_sizer(nullptr) @@ -179,14 +181,14 @@ Preview::Preview(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_t , m_volumes_cleanup_required(false) #endif // __linux__ { - if (init(parent, bed, camera, view_toolbar)) + if (init(parent, bed, camera, view_toolbar, model)) { show_hide_ui_elements("none"); load_print(); } } -bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar) +bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model) { if (!Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 /* disable wxTAB_TRAVERSAL */)) return false; @@ -196,6 +198,7 @@ bool Preview::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view m_canvas = _3DScene::get_canvas(this->m_canvas_widget); m_canvas->allow_multisample(GLCanvas3DManager::can_multisample()); m_canvas->set_config(m_config); + m_canvas->set_model(model); m_canvas->set_process(m_process); m_canvas->enable_legend_texture(true); m_canvas->enable_dynamic_background(true); @@ -773,7 +776,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()) @@ -781,6 +784,8 @@ void Preview::load_print_as_sla() } sort_remove_duplicates(zs); + m_canvas->reset_clipping_planes_cache(); + n_layers = (unsigned int)zs.size(); if (n_layers == 0) { diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index 96c49e54f..a2929f2e6 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -102,7 +102,8 @@ class Preview : public wxPanel PrusaDoubleSlider* m_slider {nullptr}; public: - Preview(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, DynamicPrintConfig* config, BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data, std::function schedule_background_process = [](){}); + Preview(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model, DynamicPrintConfig* config, + BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data, std::function schedule_background_process = [](){}); virtual ~Preview(); wxGLCanvas* get_wxglcanvas() { return m_canvas_widget; } @@ -120,7 +121,7 @@ public: void refresh_print(); private: - bool init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar); + bool init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_toolbar, Model* model); void bind_event_handlers(); void unbind_event_handlers(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 566090504..02d663e93 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -185,7 +185,10 @@ void GLGizmoCut::on_render_for_picking(const Selection& selection) const void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) { + const float approx_height = m_imgui->scaled(11.0f); + y = std::min(y, bottom_limit - approx_height); m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); + m_imgui->set_next_window_bg_alpha(0.5f); m_imgui->begin(_(L("Cut")), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index d83ef9ed8..75f13cdcf 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -565,8 +565,7 @@ void GLGizmoSlaSupports::on_render_input_window(float x, float y, float bottom_l RENDER_AGAIN: m_imgui->set_next_window_pos(x, y, ImGuiCond_Always); - const float scaling = m_imgui->get_style_scaling(); - const ImVec2 window_size(285.f * scaling, 300.f * scaling); + const ImVec2 window_size(m_imgui->scaled(15.f, 16.5f)); ImGui::SetNextWindowPos(ImVec2(x, y - std::max(0.f, y+window_size.y-bottom_limit) )); ImGui::SetNextWindowSize(ImVec2(window_size)); @@ -817,7 +816,7 @@ void GLGizmoSlaSupports::editing_mode_apply_changes() // Recalculate support structures once the editing mode is left. // m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); // m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); - wxGetApp().plater()->reslice_SLA_supports(*m_model_object); + wxGetApp().CallAfter([this]() { wxGetApp().plater()->reslice_SLA_supports(*m_model_object); }); } m_editing_mode = false; m_unsaved_changes = false; @@ -870,7 +869,7 @@ void GLGizmoSlaSupports::auto_generate() m_model_object->sla_support_points.clear(); m_model_object->sla_points_status = sla::PointsStatus::Generating; m_editing_mode_cache.clear(); - wxGetApp().plater()->reslice_SLA_supports(*m_model_object); + wxGetApp().CallAfter([this]() { wxGetApp().plater()->reslice_SLA_supports(*m_model_object); }); } } diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 0932c8ce9..1b4d4edf9 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -27,31 +27,25 @@ namespace GUI { ImGuiWrapper::ImGuiWrapper() : m_glyph_ranges(nullptr) + , m_font_size(18.0) , m_font_texture(0) , m_style_scaling(1.0) , m_mouse_buttons(0) , m_disabled(false) , m_new_frame_open(false) -{ -} - -ImGuiWrapper::~ImGuiWrapper() -{ - destroy_device_objects(); - ImGui::DestroyContext(); -} - -bool ImGuiWrapper::init() { ImGui::CreateContext(); - init_default_font(m_style_scaling); init_input(); init_style(); ImGui::GetIO().IniFilename = nullptr; +} - return true; +ImGuiWrapper::~ImGuiWrapper() +{ + destroy_font(); + ImGui::DestroyContext(); } void ImGuiWrapper::set_language(const std::string &language) @@ -87,7 +81,7 @@ void ImGuiWrapper::set_language(const std::string &language) if (ranges != m_glyph_ranges) { m_glyph_ranges = ranges; - init_default_font(m_style_scaling); + destroy_font(); } } @@ -98,13 +92,21 @@ void ImGuiWrapper::set_display_size(float w, float h) io.DisplayFramebufferScale = ImVec2(1.0f, 1.0f); } -void ImGuiWrapper::set_style_scaling(float scaling) +void ImGuiWrapper::set_scaling(float font_size, float scale_style, float scale_both) { - if (!std::isnan(scaling) && !std::isinf(scaling) && scaling != m_style_scaling) { - ImGui::GetStyle().ScaleAllSizes(scaling / m_style_scaling); - init_default_font(scaling); - m_style_scaling = scaling; + font_size *= scale_both; + scale_style *= scale_both; + + if (m_font_size == font_size && m_style_scaling == scale_style) { + return; } + + m_font_size = font_size; + + ImGui::GetStyle().ScaleAllSizes(scale_style / m_style_scaling); + m_style_scaling = scale_style; + + destroy_font(); } bool ImGuiWrapper::update_mouse_data(wxMouseEvent& evt) @@ -165,8 +167,9 @@ void ImGuiWrapper::new_frame() return; } - if (m_font_texture == 0) - create_device_objects(); + if (m_font_texture == 0) { + init_font(); + } ImGui::NewFrame(); m_new_frame_open = true; @@ -179,6 +182,12 @@ void ImGuiWrapper::render() m_new_frame_open = false; } +ImVec2 ImGuiWrapper::calc_text_size(const wxString &text) +{ + auto text_utf8 = into_u8(text); + return ImGui::CalcTextSize(text_utf8.c_str()); +} + void ImGuiWrapper::set_next_window_pos(float x, float y, int flag) { ImGui::SetNextWindowPos(ImVec2(x, y), (ImGuiCond)flag); @@ -259,7 +268,8 @@ void ImGuiWrapper::text(const std::string &label) void ImGuiWrapper::text(const wxString &label) { - this->text(into_u8(label).c_str()); + auto label_utf8 = into_u8(label); + this->text(label_utf8.c_str()); } bool ImGuiWrapper::combo(const wxString& label, const std::vector& options, int& selection) @@ -328,32 +338,23 @@ bool ImGuiWrapper::want_any_input() const return io.WantCaptureMouse || io.WantCaptureKeyboard || io.WantTextInput; } -void ImGuiWrapper::init_default_font(float scaling) +void ImGuiWrapper::init_font() { - static const float font_size = 18.0f; + const float font_size = m_font_size * m_style_scaling; - destroy_fonts_texture(); + destroy_font(); ImGuiIO& io = ImGui::GetIO(); io.Fonts->Clear(); - ImFont* font = io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/NotoSans-Regular.ttf").c_str(), font_size * scaling, nullptr, m_glyph_ranges); + ImFont* font = io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/NotoSans-Regular.ttf").c_str(), font_size, nullptr, m_glyph_ranges); if (font == nullptr) { font = io.Fonts->AddFontDefault(); if (font == nullptr) { throw std::runtime_error("ImGui: Could not load deafult font"); } } -} -void ImGuiWrapper::create_device_objects() -{ - create_fonts_texture(); -} - -void ImGuiWrapper::create_fonts_texture() -{ // Build texture atlas - ImGuiIO& io = ImGui::GetIO(); unsigned char* pixels; int width, height; io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bits (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory. @@ -559,14 +560,9 @@ bool ImGuiWrapper::display_initialized() const return io.DisplaySize.x >= 0.0f && io.DisplaySize.y >= 0.0f; } -void ImGuiWrapper::destroy_device_objects() +void ImGuiWrapper::destroy_font() { - destroy_fonts_texture(); -} - -void ImGuiWrapper::destroy_fonts_texture() -{ - if (m_font_texture) { + if (m_font_texture != 0) { ImGuiIO& io = ImGui::GetIO(); io.Fonts->TexID = 0; glDeleteTextures(1, &m_font_texture); diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index 35d0b5636..c1bf491e1 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -18,10 +18,8 @@ namespace GUI { class ImGuiWrapper { - typedef std::map FontsMap; - - FontsMap m_fonts; const ImWchar *m_glyph_ranges; + float m_font_size; unsigned m_font_texture; float m_style_scaling; unsigned m_mouse_buttons; @@ -33,20 +31,24 @@ public: ImGuiWrapper(); ~ImGuiWrapper(); - bool init(); void read_glsl_version(); void set_language(const std::string &language); void set_display_size(float w, float h); - void set_style_scaling(float scaling); + void set_scaling(float font_size, float scale_style, float scale_both); bool update_mouse_data(wxMouseEvent &evt); bool update_key_data(wxKeyEvent &evt); + float get_font_size() const { return m_font_size; } float get_style_scaling() const { return m_style_scaling; } void new_frame(); void render(); + float scaled(float x) const { return x * m_font_size * m_style_scaling; } + ImVec2 scaled(float x, float y) const { return ImVec2(x * m_font_size * m_style_scaling, y * m_font_size * m_style_scaling); } + ImVec2 calc_text_size(const wxString &text); + void set_next_window_pos(float x, float y, int flag); void set_next_window_bg_alpha(float alpha); @@ -73,15 +75,12 @@ public: bool want_any_input() const; private: - void init_default_font(float scaling); - void create_device_objects(); - void create_fonts_texture(); + void init_font(); void init_input(); void init_style(); void render_draw_data(ImDrawData *draw_data); bool display_initialized() const; - void destroy_device_objects(); - void destroy_fonts_texture(); + void destroy_font(); static const char* clipboard_get(void* user_data); static void clipboard_set(void* user_data, const char* text); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index c92c22c50..435d9548f 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1350,7 +1350,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) this->q->Bind(EVT_SLICING_UPDATE, &priv::on_slicing_update, this); view3D = new View3D(q, bed, camera, view_toolbar, &model, config, &background_process); - preview = new Preview(q, bed, camera, view_toolbar, config, &background_process, &gcode_preview_data, [this](){ schedule_background_process(); }); + preview = new Preview(q, bed, camera, view_toolbar, &model, config, &background_process, &gcode_preview_data, [this](){ schedule_background_process(); }); panels.push_back(view3D); panels.push_back(preview); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index fb35b500e..7103ca12d 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -118,17 +118,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()))) _add_volume(volume_idx); break; @@ -439,6 +439,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) @@ -452,13 +454,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 @@ -970,7 +981,23 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field) const ::glTranslated(center(0), center(1), center(2)); if (!boost::starts_with(sidebar_field, "position")) { - Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); + Transform3d orient_matrix = Transform3d::Identity(); + if (boost::starts_with(sidebar_field, "scale")) + orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); + else if (boost::starts_with(sidebar_field, "rotation")) + { + if (boost::ends_with(sidebar_field, "x")) + orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); + else if (boost::ends_with(sidebar_field, "y")) + { + const Vec3d& rotation = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation(); + if (rotation(0) == 0.0) + orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true); + else + orient_matrix.rotate(Eigen::AngleAxisd(rotation(2), Vec3d::UnitZ())); + } + } + ::glMultMatrixd(orient_matrix.data()); } } @@ -1667,5 +1694,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]->volume_idx() >= 0) && (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 6390214cd..883fb5400 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -299,6 +299,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 diff --git a/src/slic3r/Utils/HexFile.cpp b/src/slic3r/Utils/HexFile.cpp index 9e0803325..26596f629 100644 --- a/src/slic3r/Utils/HexFile.cpp +++ b/src/slic3r/Utils/HexFile.cpp @@ -18,6 +18,7 @@ static HexFile::DeviceKind parse_device_kind(const std::string &str) if (str == "mk2") { return HexFile::DEV_MK2; } else if (str == "mk3") { return HexFile::DEV_MK3; } else if (str == "mm-control") { return HexFile::DEV_MM_CONTROL; } + else if (str == "cw1") { return HexFile::DEV_CW1; } else { return HexFile::DEV_GENERIC; } } diff --git a/src/slic3r/Utils/HexFile.hpp b/src/slic3r/Utils/HexFile.hpp index 1201d23a4..742ae00e6 100644 --- a/src/slic3r/Utils/HexFile.hpp +++ b/src/slic3r/Utils/HexFile.hpp @@ -16,6 +16,7 @@ struct HexFile DEV_MK2, DEV_MK3, DEV_MM_CONTROL, + DEV_CW1, }; boost::filesystem::path path;