diff --git a/README.md b/README.md index 6fd1af4e20..2b93a47b01 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ compatible with any modern printer based on the RepRap toolchain, including all those based on the Marlin, Prusa, Sprinter and Repetier firmware. It also works with Mach3, LinuxCNC and Machinekit controllers. -PrusaSlicer is based on [Slic3r](https://github.com/Slic3r/Slic3r) by Alessandro Ranelucci and the RepRap community. +PrusaSlicer is based on [Slic3r](https://github.com/Slic3r/Slic3r) by Alessandro Ranellucci and the RepRap community. See the [project homepage](https://www.prusa3d.com/slic3r-prusa-edition/) and the [documentation directory](doc/) for more information. diff --git a/resources/icons/add.svg b/resources/icons/add.svg index 8a9b253de7..37050d7481 100644 --- a/resources/icons/add.svg +++ b/resources/icons/add.svg @@ -1,17 +1,22 @@ - + - - + + + + + + + diff --git a/resources/icons/arrange.svg b/resources/icons/arrange.svg index 4f30e979e3..62cf939e9f 100644 --- a/resources/icons/arrange.svg +++ b/resources/icons/arrange.svg @@ -1,24 +1,23 @@ - + - - + - + - + - + - + diff --git a/resources/icons/copy.svg b/resources/icons/copy.svg index 9b8430dd79..345c2590be 100644 --- a/resources/icons/copy.svg +++ b/resources/icons/copy.svg @@ -1,37 +1,29 @@ - + - - - - - + + - - - - - + + diff --git a/resources/icons/delete_all.svg b/resources/icons/delete_all.svg index 80e2e503cb..dfa9438129 100644 --- a/resources/icons/delete_all.svg +++ b/resources/icons/delete_all.svg @@ -1,31 +1,17 @@ - + - - + - + - - - - - - - - - - + diff --git a/resources/icons/instance_add.svg b/resources/icons/instance_add.svg index 5ef492cfae..a466c51dbf 100644 --- a/resources/icons/instance_add.svg +++ b/resources/icons/instance_add.svg @@ -1,50 +1,46 @@ - + - + - + - + + + + diff --git a/resources/icons/instance_remove.svg b/resources/icons/instance_remove.svg index 466752ea89..7f9b4f7e1c 100644 --- a/resources/icons/instance_remove.svg +++ b/resources/icons/instance_remove.svg @@ -1,49 +1,42 @@ - + - + - + - + diff --git a/resources/icons/paste.svg b/resources/icons/paste.svg index 028ffb8ea0..bcfe567de3 100644 --- a/resources/icons/paste.svg +++ b/resources/icons/paste.svg @@ -1,27 +1,22 @@ - + - + - - - - - + + diff --git a/resources/icons/redo_toolbar.svg b/resources/icons/redo_toolbar.svg index d005f83736..d2aca2cc7d 100644 --- a/resources/icons/redo_toolbar.svg +++ b/resources/icons/redo_toolbar.svg @@ -1,13 +1,17 @@ - + - + viewBox="0 0 128 128" enable-background="new 0 0 128 128" xml:space="preserve"> + + + + + + + + diff --git a/resources/icons/remove.svg b/resources/icons/remove.svg index acd21256cd..1bb830d91c 100644 --- a/resources/icons/remove.svg +++ b/resources/icons/remove.svg @@ -1,44 +1,60 @@ - + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/seam.svg b/resources/icons/seam.svg index 119fb6afcc..a7e7980cc0 100644 --- a/resources/icons/seam.svg +++ b/resources/icons/seam.svg @@ -1,42 +1,35 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/search_.svg b/resources/icons/search_.svg index 679bb30f71..2985ceb561 100644 --- a/resources/icons/search_.svg +++ b/resources/icons/search_.svg @@ -1,4 +1,26 @@ - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/search_blink.svg b/resources/icons/search_blink.svg new file mode 100644 index 0000000000..d005f83736 --- /dev/null +++ b/resources/icons/search_blink.svg @@ -0,0 +1,13 @@ + + + + + diff --git a/resources/icons/settings.svg b/resources/icons/settings.svg new file mode 100644 index 0000000000..db5bf458d7 --- /dev/null +++ b/resources/icons/settings.svg @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/split_objects.svg b/resources/icons/split_objects.svg index a7ccc5df86..e822fd35aa 100644 --- a/resources/icons/split_objects.svg +++ b/resources/icons/split_objects.svg @@ -1,19 +1,20 @@ - + - + - + - - + + + + diff --git a/resources/icons/split_parts.svg b/resources/icons/split_parts.svg index 82a2927706..5cfef0f330 100644 --- a/resources/icons/split_parts.svg +++ b/resources/icons/split_parts.svg @@ -1,18 +1,20 @@ - + - + - + - - + + + + diff --git a/resources/icons/undo_toolbar.svg b/resources/icons/undo_toolbar.svg index 15778a7baf..2fc25bf737 100644 --- a/resources/icons/undo_toolbar.svg +++ b/resources/icons/undo_toolbar.svg @@ -1,13 +1,17 @@ - + - + viewBox="0 0 128 128" enable-background="new 0 0 128 128" xml:space="preserve"> + + + + + + + + diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 05e84b9416..a12ad8bb73 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -589,7 +589,7 @@ int CLI::run(int argc, char **argv) #if ENABLE_GCODE_VIEWER if (start_as_gcodeviewer) { if (!m_input_files.empty()) - gui->plater()->load_gcode(wxString::FromUTF8(m_input_files[0])); + gui->plater()->load_gcode(wxString::FromUTF8(m_input_files[0].c_str())); } else { #endif // ENABLE_GCODE_VIEWER_AS #if 0 diff --git a/src/admesh/stl.h b/src/admesh/stl.h index 9224b04594..e0f2865f0d 100644 --- a/src/admesh/stl.h +++ b/src/admesh/stl.h @@ -255,18 +255,24 @@ extern void its_transform(indexed_triangle_set &its, T *trafo3x4) } template -inline void its_transform(indexed_triangle_set &its, const Eigen::Transform& t) +inline void its_transform(indexed_triangle_set &its, const Eigen::Transform& t, bool fix_left_handed = false) { //const Eigen::Matrix r = t.matrix().template block<3, 3>(0, 0); for (stl_vertex &v : its.vertices) v = (t * v.template cast()).template cast().eval(); + if (fix_left_handed && t.matrix().block(0, 0, 3, 3).determinant() < 0.) + for (stl_triangle_vertex_indices &i : its.indices) + std::swap(i[0], i[1]); } template -inline void its_transform(indexed_triangle_set &its, const Eigen::Matrix& m) +inline void its_transform(indexed_triangle_set &its, const Eigen::Matrix& m, bool fix_left_handed = false) { - for (stl_vertex &v : its.vertices) + for (stl_vertex &v : its.vertices) v = (m * v.template cast()).template cast().eval(); + if (fix_left_handed && m.determinant() < 0.) + for (stl_triangle_vertex_indices &i : its.indices) + std::swap(i[0], i[1]); } extern void its_rotate_x(indexed_triangle_set &its, float angle); diff --git a/src/libslic3r/AABBTreeIndirect.hpp b/src/libslic3r/AABBTreeIndirect.hpp index 17d918aeb3..964133faae 100644 --- a/src/libslic3r/AABBTreeIndirect.hpp +++ b/src/libslic3r/AABBTreeIndirect.hpp @@ -283,7 +283,7 @@ namespace detail { template std::enable_if_t::value && std::is_same::value, bool> - intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) { + intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v) { return intersect_triangle1(const_cast(origin.data()), const_cast(dir.data()), const_cast(v0.data()), const_cast(v1.data()), const_cast(v2.data()), &t, &u, &v); @@ -291,7 +291,7 @@ namespace detail { template std::enable_if_t::value && !std::is_same::value, bool> - intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) { + intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v) { using Vector = Eigen::Matrix; Vector w0 = v0.template cast(); Vector w1 = v1.template cast(); @@ -302,7 +302,7 @@ namespace detail { template std::enable_if_t::value && std::is_same::value, bool> - intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) { + intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v) { using Vector = Eigen::Matrix; Vector o = origin.template cast(); Vector d = dir.template cast(); @@ -311,7 +311,7 @@ namespace detail { template std::enable_if_t::value && ! std::is_same::value, bool> - intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) { + intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v) { using Vector = Eigen::Matrix; Vector o = origin.template cast(); Vector d = dir.template cast(); diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 5892b4a30d..72c4fd0e92 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -1,6 +1,7 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/Utils.hpp" #include "AppConfig.hpp" +#include "Exception.hpp" #include #include @@ -126,7 +127,7 @@ std::string AppConfig::load() // ! But to avoid the use of _utf8 (related to use of wxWidgets) // we will rethrow this exception from the place of load() call, if returned value wouldn't be empty /* - throw std::runtime_error( + throw Slic3r::RuntimeError( _utf8(L("Error parsing PrusaSlicer config file, it is probably corrupted. " "Try to manually delete the file to recover from the error. Your user profiles will not be affected.")) + "\n\n" + AppConfig::config_path() + "\n\n" + ex.what()); diff --git a/src/libslic3r/BoundingBox.cpp b/src/libslic3r/BoundingBox.cpp index e3f9395092..eb4e042a08 100644 --- a/src/libslic3r/BoundingBox.cpp +++ b/src/libslic3r/BoundingBox.cpp @@ -75,6 +75,7 @@ BoundingBoxBase::merge(const PointClass &point) } } template void BoundingBoxBase::merge(const Point &point); +template void BoundingBoxBase::merge(const Vec2f &point); template void BoundingBoxBase::merge(const Vec2d &point); template void @@ -101,6 +102,7 @@ BoundingBoxBase::merge(const BoundingBoxBase &bb) } } template void BoundingBoxBase::merge(const BoundingBoxBase &bb); +template void BoundingBoxBase::merge(const BoundingBoxBase &bb); template void BoundingBoxBase::merge(const BoundingBoxBase &bb); template void @@ -115,6 +117,7 @@ BoundingBox3Base::merge(const PointClass &point) this->defined = true; } } +template void BoundingBox3Base::merge(const Vec3f &point); template void BoundingBox3Base::merge(const Vec3d &point); template void @@ -147,6 +150,7 @@ BoundingBoxBase::size() const return PointClass(this->max(0) - this->min(0), this->max(1) - this->min(1)); } template Point BoundingBoxBase::size() const; +template Vec2f BoundingBoxBase::size() const; template Vec2d BoundingBoxBase::size() const; template PointClass @@ -154,6 +158,7 @@ BoundingBox3Base::size() const { return PointClass(this->max(0) - this->min(0), this->max(1) - this->min(1), this->max(2) - this->min(2)); } +template Vec3f BoundingBox3Base::size() const; template Vec3d BoundingBox3Base::size() const; template double BoundingBoxBase::radius() const @@ -200,6 +205,7 @@ BoundingBoxBase::center() const return (this->min + this->max) / 2; } template Point BoundingBoxBase::center() const; +template Vec2f BoundingBoxBase::center() const; template Vec2d BoundingBoxBase::center() const; template PointClass @@ -207,6 +213,7 @@ BoundingBox3Base::center() const { return (this->min + this->max) / 2; } +template Vec3f BoundingBox3Base::center() const; template Vec3d BoundingBox3Base::center() const; template coordf_t @@ -215,6 +222,7 @@ BoundingBox3Base::max_size() const PointClass s = size(); return std::max(s(0), std::max(s(1), s(2))); } +template coordf_t BoundingBox3Base::max_size() const; template coordf_t BoundingBox3Base::max_size() const; // Align a coordinate to a grid. The coordinate may be negative, diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp index 08f01d8d85..065476cb28 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -2,6 +2,7 @@ #define slic3r_BoundingBox_hpp_ #include "libslic3r.h" +#include "Exception.hpp" #include "Point.hpp" #include "Polygon.hpp" @@ -18,11 +19,13 @@ public: BoundingBoxBase() : min(PointClass::Zero()), max(PointClass::Zero()), defined(false) {} BoundingBoxBase(const PointClass &pmin, const PointClass &pmax) : min(pmin), max(pmax), defined(pmin(0) < pmax(0) && pmin(1) < pmax(1)) {} + BoundingBoxBase(const PointClass &p1, const PointClass &p2, const PointClass &p3) : + min(p1), max(p1), defined(false) { merge(p2); merge(p3); } BoundingBoxBase(const std::vector& points) : min(PointClass::Zero()), max(PointClass::Zero()) { if (points.empty()) { this->defined = false; - // throw std::invalid_argument("Empty point set supplied to BoundingBoxBase constructor"); + // throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBoxBase constructor"); } else { typename std::vector::const_iterator it = points.begin(); this->min = *it; @@ -65,10 +68,12 @@ public: BoundingBox3Base(const PointClass &pmin, const PointClass &pmax) : BoundingBoxBase(pmin, pmax) { if (pmin(2) >= pmax(2)) BoundingBoxBase::defined = false; } + BoundingBox3Base(const PointClass &p1, const PointClass &p2, const PointClass &p3) : + BoundingBoxBase(p1, p1) { merge(p2); merge(p3); } BoundingBox3Base(const std::vector& points) { if (points.empty()) - throw std::invalid_argument("Empty point set supplied to BoundingBox3Base constructor"); + throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBox3Base constructor"); typename std::vector::const_iterator it = points.begin(); this->min = *it; this->max = *it; @@ -109,24 +114,32 @@ extern template void BoundingBoxBase::scale(double factor); extern template void BoundingBoxBase::offset(coordf_t delta); extern template void BoundingBoxBase::offset(coordf_t delta); extern template void BoundingBoxBase::merge(const Point &point); +extern template void BoundingBoxBase::merge(const Vec2f &point); extern template void BoundingBoxBase::merge(const Vec2d &point); extern template void BoundingBoxBase::merge(const Points &points); extern template void BoundingBoxBase::merge(const Pointfs &points); extern template void BoundingBoxBase::merge(const BoundingBoxBase &bb); +extern template void BoundingBoxBase::merge(const BoundingBoxBase &bb); extern template void BoundingBoxBase::merge(const BoundingBoxBase &bb); extern template Point BoundingBoxBase::size() const; +extern template Vec2f BoundingBoxBase::size() const; extern template Vec2d BoundingBoxBase::size() const; extern template double BoundingBoxBase::radius() const; extern template double BoundingBoxBase::radius() const; extern template Point BoundingBoxBase::center() const; +extern template Vec2f BoundingBoxBase::center() const; extern template Vec2d BoundingBoxBase::center() const; +extern template void BoundingBox3Base::merge(const Vec3f &point); extern template void BoundingBox3Base::merge(const Vec3d &point); extern template void BoundingBox3Base::merge(const Pointf3s &points); extern template void BoundingBox3Base::merge(const BoundingBox3Base &bb); +extern template Vec3f BoundingBox3Base::size() const; extern template Vec3d BoundingBox3Base::size() const; extern template double BoundingBox3Base::radius() const; extern template void BoundingBox3Base::offset(coordf_t delta); +extern template Vec3f BoundingBox3Base::center() const; extern template Vec3d BoundingBox3Base::center() const; +extern template coordf_t BoundingBox3Base::max_size() const; extern template coordf_t BoundingBox3Base::max_size() const; class BoundingBox : public BoundingBoxBase diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index e30811133a..ee41248f3b 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -97,6 +97,8 @@ add_library(libslic3r STATIC GCode/PrintExtents.hpp GCode/SpiralVase.cpp GCode/SpiralVase.hpp + GCode/SeamPlacer.cpp + GCode/SeamPlacer.hpp GCode/ToolOrdering.cpp GCode/ToolOrdering.hpp GCode/WipeTower.cpp diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index f3f365b478..25ef93430f 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -5,7 +5,6 @@ #include #include #include -#include // std::runtime_error #include #include #include @@ -218,7 +217,7 @@ ConfigOption* ConfigOptionDef::create_empty_option() const case coInts: return new ConfigOptionIntsNullable(); case coPercents: return new ConfigOptionPercentsNullable(); case coBools: return new ConfigOptionBoolsNullable(); - default: throw std::runtime_error(std::string("Unknown option type for nullable option ") + this->label); + default: throw Slic3r::RuntimeError(std::string("Unknown option type for nullable option ") + this->label); } } else { switch (this->type) { @@ -238,7 +237,7 @@ ConfigOption* ConfigOptionDef::create_empty_option() const case coBool: return new ConfigOptionBool(); case coBools: return new ConfigOptionBools(); case coEnum: return new ConfigOptionEnumGeneric(this->enum_keys_map); - default: throw std::runtime_error(std::string("Unknown option type for option ") + this->label); + default: throw Slic3r::RuntimeError(std::string("Unknown option type for option ") + this->label); } } } @@ -535,7 +534,7 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key) const return opt_def->ratio_over.empty() ? 0. : static_cast(raw_opt)->get_abs_value(this->get_abs_value(opt_def->ratio_over)); } - throw std::runtime_error("ConfigBase::get_abs_value(): Not a valid option type for get_abs_value()"); + throw Slic3r::RuntimeError("ConfigBase::get_abs_value(): Not a valid option type for get_abs_value()"); } // Return an absolute value of a possibly relative config variable. @@ -546,7 +545,7 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key, double rati const ConfigOption *raw_opt = this->option(opt_key); assert(raw_opt != nullptr); if (raw_opt->type() != coFloatOrPercent) - throw std::runtime_error("ConfigBase::get_abs_value(): opt_key is not of coFloatOrPercent"); + throw Slic3r::RuntimeError("ConfigBase::get_abs_value(): opt_key is not of coFloatOrPercent"); // Compute absolute value. return static_cast(raw_opt)->get_abs_value(ratio_over); } @@ -609,7 +608,7 @@ void ConfigBase::load_from_gcode_file(const std::string &file) std::getline(ifs, firstline); if (strncmp(slic3r_gcode_header, firstline.c_str(), strlen(slic3r_gcode_header)) != 0 && strncmp(prusaslicer_gcode_header, firstline.c_str(), strlen(prusaslicer_gcode_header)) != 0) - throw std::runtime_error("Not a PrusaSlicer / Slic3r PE generated g-code."); + throw Slic3r::RuntimeError("Not a PrusaSlicer / Slic3r PE generated g-code."); } ifs.seekg(0, ifs.end); auto file_length = ifs.tellg(); @@ -621,7 +620,7 @@ void ConfigBase::load_from_gcode_file(const std::string &file) size_t key_value_pairs = load_from_gcode_string(data.data()); if (key_value_pairs < 80) - throw std::runtime_error(format("Suspiciously low number of configuration values extracted from %1%: %2%", file, key_value_pairs)); + throw Slic3r::RuntimeError(format("Suspiciously low number of configuration values extracted from %1%: %2%", file, key_value_pairs)); } // Load the config keys from the given string. @@ -750,7 +749,7 @@ ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool cre throw NoDefinitionException(opt_key); const ConfigOptionDef *optdef = def->get(opt_key); if (optdef == nullptr) -// throw std::runtime_error(std::string("Invalid option name: ") + opt_key); +// throw Slic3r::RuntimeError(std::string("Invalid option name: ") + opt_key); // Let the parent decide what to do if the opt_key is not defined by this->def(). return nullptr; ConfigOption *opt = optdef->create_default_option(); diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 87e0208986..28b28b405a 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -13,6 +13,7 @@ #include #include "libslic3r.h" #include "clonable_ptr.hpp" +#include "Exception.hpp" #include "Point.hpp" #include @@ -34,31 +35,31 @@ extern bool unescape_string_cstyle(const std::string &str, std::string & extern bool unescape_strings_cstyle(const std::string &str, std::vector &out); /// Specialization of std::exception to indicate that an unknown config option has been encountered. -class UnknownOptionException : public std::runtime_error { +class UnknownOptionException : public Slic3r::RuntimeError { public: UnknownOptionException() : - std::runtime_error("Unknown option exception") {} + Slic3r::RuntimeError("Unknown option exception") {} UnknownOptionException(const std::string &opt_key) : - std::runtime_error(std::string("Unknown option exception: ") + opt_key) {} + Slic3r::RuntimeError(std::string("Unknown option exception: ") + opt_key) {} }; /// Indicate that the ConfigBase derived class does not provide config definition (the method def() returns null). -class NoDefinitionException : public std::runtime_error +class NoDefinitionException : public Slic3r::RuntimeError { public: NoDefinitionException() : - std::runtime_error("No definition exception") {} + Slic3r::RuntimeError("No definition exception") {} NoDefinitionException(const std::string &opt_key) : - std::runtime_error(std::string("No definition exception: ") + opt_key) {} + Slic3r::RuntimeError(std::string("No definition exception: ") + opt_key) {} }; /// Indicate that an unsupported accessor was called on a config option. -class BadOptionTypeException : public std::runtime_error +class BadOptionTypeException : public Slic3r::RuntimeError { public: - BadOptionTypeException() : std::runtime_error("Bad option type exception") {} - BadOptionTypeException(const std::string &message) : std::runtime_error(message) {} - BadOptionTypeException(const char* message) : std::runtime_error(message) {} + BadOptionTypeException() : Slic3r::RuntimeError("Bad option type exception") {} + BadOptionTypeException(const std::string &message) : Slic3r::RuntimeError(message) {} + BadOptionTypeException(const char* message) : Slic3r::RuntimeError(message) {} }; // Type of a configuration value. @@ -167,7 +168,7 @@ public: void set(const ConfigOption *rhs) override { if (rhs->type() != this->type()) - throw std::runtime_error("ConfigOptionSingle: Assigning an incompatible type"); + throw Slic3r::RuntimeError("ConfigOptionSingle: Assigning an incompatible type"); assert(dynamic_cast*>(rhs)); this->value = static_cast*>(rhs)->value; } @@ -175,7 +176,7 @@ public: bool operator==(const ConfigOption &rhs) const override { if (rhs.type() != this->type()) - throw std::runtime_error("ConfigOptionSingle: Comparing incompatible types"); + throw Slic3r::RuntimeError("ConfigOptionSingle: Comparing incompatible types"); assert(dynamic_cast*>(&rhs)); return this->value == static_cast*>(&rhs)->value; } @@ -239,7 +240,7 @@ public: void set(const ConfigOption *rhs) override { if (rhs->type() != this->type()) - throw std::runtime_error("ConfigOptionVector: Assigning an incompatible type"); + throw Slic3r::RuntimeError("ConfigOptionVector: Assigning an incompatible type"); assert(dynamic_cast*>(rhs)); this->values = static_cast*>(rhs)->values; } @@ -256,12 +257,12 @@ public: if (opt->type() == this->type()) { auto other = static_cast*>(opt); if (other->values.empty()) - throw std::runtime_error("ConfigOptionVector::set(): Assigning from an empty vector"); + throw Slic3r::RuntimeError("ConfigOptionVector::set(): Assigning from an empty vector"); this->values.emplace_back(other->values.front()); } else if (opt->type() == this->scalar_type()) this->values.emplace_back(static_cast*>(opt)->value); else - throw std::runtime_error("ConfigOptionVector::set():: Assigning an incompatible type"); + throw Slic3r::RuntimeError("ConfigOptionVector::set():: Assigning an incompatible type"); } } @@ -280,12 +281,12 @@ public: // Assign the first value of the rhs vector. auto other = static_cast*>(rhs); if (other->values.empty()) - throw std::runtime_error("ConfigOptionVector::set_at(): Assigning from an empty vector"); + throw Slic3r::RuntimeError("ConfigOptionVector::set_at(): Assigning from an empty vector"); this->values[i] = other->get_at(j); } else if (rhs->type() == this->scalar_type()) this->values[i] = static_cast*>(rhs)->value; else - throw std::runtime_error("ConfigOptionVector::set_at(): Assigning an incompatible type"); + throw Slic3r::RuntimeError("ConfigOptionVector::set_at(): Assigning an incompatible type"); } const T& get_at(size_t i) const @@ -310,9 +311,9 @@ public: else if (n > this->values.size()) { if (this->values.empty()) { if (opt_default == nullptr) - throw std::runtime_error("ConfigOptionVector::resize(): No default value provided."); + throw Slic3r::RuntimeError("ConfigOptionVector::resize(): No default value provided."); if (opt_default->type() != this->type()) - throw std::runtime_error("ConfigOptionVector::resize(): Extending with an incompatible type."); + throw Slic3r::RuntimeError("ConfigOptionVector::resize(): Extending with an incompatible type."); this->values.resize(n, static_cast*>(opt_default)->values.front()); } else { // Resize by duplicating the last value. @@ -329,7 +330,7 @@ public: bool operator==(const ConfigOption &rhs) const override { if (rhs.type() != this->type()) - throw std::runtime_error("ConfigOptionVector: Comparing incompatible types"); + throw Slic3r::RuntimeError("ConfigOptionVector: Comparing incompatible types"); assert(dynamic_cast*>(&rhs)); return this->values == static_cast*>(&rhs)->values; } @@ -341,9 +342,9 @@ public: // An option overrides another option if it is not nil and not equal. bool overriden_by(const ConfigOption *rhs) const override { if (this->nullable()) - throw std::runtime_error("Cannot override a nullable ConfigOption."); + throw Slic3r::RuntimeError("Cannot override a nullable ConfigOption."); if (rhs->type() != this->type()) - throw std::runtime_error("ConfigOptionVector.overriden_by() applied to different types."); + throw Slic3r::RuntimeError("ConfigOptionVector.overriden_by() applied to different types."); auto rhs_vec = static_cast*>(rhs); if (! rhs->nullable()) // Overridding a non-nullable object with another non-nullable object. @@ -361,9 +362,9 @@ public: // Apply an override option, possibly a nullable one. bool apply_override(const ConfigOption *rhs) override { if (this->nullable()) - throw std::runtime_error("Cannot override a nullable ConfigOption."); + throw Slic3r::RuntimeError("Cannot override a nullable ConfigOption."); if (rhs->type() != this->type()) - throw std::runtime_error("ConfigOptionVector.apply_override() applied to different types."); + throw Slic3r::RuntimeError("ConfigOptionVector.apply_override() applied to different types."); auto rhs_vec = static_cast*>(rhs); if (! rhs->nullable()) { // Overridding a non-nullable object with another non-nullable object. @@ -452,7 +453,7 @@ public: bool operator==(const ConfigOptionFloatsTempl &rhs) const { return vectors_equal(this->values, rhs.values); } bool operator==(const ConfigOption &rhs) const override { if (rhs.type() != this->type()) - throw std::runtime_error("ConfigOptionFloatsTempl: Comparing incompatible types"); + throw Slic3r::RuntimeError("ConfigOptionFloatsTempl: Comparing incompatible types"); assert(dynamic_cast*>(&rhs)); return vectors_equal(this->values, static_cast*>(&rhs)->values); } @@ -499,7 +500,7 @@ public: if (NULLABLE) this->values.push_back(nil_value()); else - throw std::runtime_error("Deserializing nil into a non-nullable object"); + throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object"); } else { std::istringstream iss(item_str); double value; @@ -524,9 +525,9 @@ protected: if (NULLABLE) ss << "nil"; else - throw std::runtime_error("Serializing NaN"); + throw Slic3r::RuntimeError("Serializing NaN"); } else - throw std::runtime_error("Serializing invalid number"); + throw Slic3r::RuntimeError("Serializing invalid number"); } static bool vectors_equal(const std::vector &v1, const std::vector &v2) { if (NULLABLE) { @@ -645,7 +646,7 @@ public: if (NULLABLE) this->values.push_back(nil_value()); else - throw std::runtime_error("Deserializing nil into a non-nullable object"); + throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object"); } else { std::istringstream iss(item_str); int value; @@ -662,7 +663,7 @@ private: if (NULLABLE) ss << "nil"; else - throw std::runtime_error("Serializing NaN"); + throw Slic3r::RuntimeError("Serializing NaN"); } else ss << v; } @@ -847,7 +848,7 @@ public: bool operator==(const ConfigOption &rhs) const override { if (rhs.type() != this->type()) - throw std::runtime_error("ConfigOptionFloatOrPercent: Comparing incompatible types"); + throw Slic3r::RuntimeError("ConfigOptionFloatOrPercent: Comparing incompatible types"); assert(dynamic_cast(&rhs)); return *this == *static_cast(&rhs); } @@ -858,7 +859,7 @@ public: void set(const ConfigOption *rhs) override { if (rhs->type() != this->type()) - throw std::runtime_error("ConfigOptionFloatOrPercent: Assigning an incompatible type"); + throw Slic3r::RuntimeError("ConfigOptionFloatOrPercent: Assigning an incompatible type"); assert(dynamic_cast(rhs)); *this = *static_cast(rhs); } @@ -1126,7 +1127,7 @@ public: if (NULLABLE) this->values.push_back(nil_value()); else - throw std::runtime_error("Deserializing nil into a non-nullable object"); + throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object"); } else this->values.push_back(item_str.compare("1") == 0); } @@ -1139,7 +1140,7 @@ protected: if (NULLABLE) ss << "nil"; else - throw std::runtime_error("Serializing NaN"); + throw Slic3r::RuntimeError("Serializing NaN"); } else ss << (v ? "1" : "0"); } @@ -1175,14 +1176,14 @@ public: bool operator==(const ConfigOption &rhs) const override { if (rhs.type() != this->type()) - throw std::runtime_error("ConfigOptionEnum: Comparing incompatible types"); + throw Slic3r::RuntimeError("ConfigOptionEnum: Comparing incompatible types"); // rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum return this->value == (T)rhs.getInt(); } void set(const ConfigOption *rhs) override { if (rhs->type() != this->type()) - throw std::runtime_error("ConfigOptionEnum: Assigning an incompatible type"); + throw Slic3r::RuntimeError("ConfigOptionEnum: Assigning an incompatible type"); // rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum this->value = (T)rhs->getInt(); } @@ -1259,14 +1260,14 @@ public: bool operator==(const ConfigOption &rhs) const override { if (rhs.type() != this->type()) - throw std::runtime_error("ConfigOptionEnumGeneric: Comparing incompatible types"); + throw Slic3r::RuntimeError("ConfigOptionEnumGeneric: Comparing incompatible types"); // rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum return this->value == rhs.getInt(); } void set(const ConfigOption *rhs) override { if (rhs->type() != this->type()) - throw std::runtime_error("ConfigOptionEnumGeneric: Assigning an incompatible type"); + throw Slic3r::RuntimeError("ConfigOptionEnumGeneric: Assigning an incompatible type"); // rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum this->value = rhs->getInt(); } @@ -1321,7 +1322,7 @@ public: case coInts: { auto opt = new ConfigOptionIntsNullable(); archive(*opt); return opt; } case coPercents: { auto opt = new ConfigOptionPercentsNullable();archive(*opt); return opt; } case coBools: { auto opt = new ConfigOptionBoolsNullable(); archive(*opt); return opt; } - default: throw std::runtime_error(std::string("ConfigOptionDef::load_option_from_archive(): Unknown nullable option type for option ") + this->opt_key); + default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown nullable option type for option ") + this->opt_key); } } else { switch (this->type) { @@ -1340,7 +1341,7 @@ public: case coBool: { auto opt = new ConfigOptionBool(); archive(*opt); return opt; } case coBools: { auto opt = new ConfigOptionBools(); archive(*opt); return opt; } case coEnum: { auto opt = new ConfigOptionEnumGeneric(this->enum_keys_map); archive(*opt); return opt; } - default: throw std::runtime_error(std::string("ConfigOptionDef::load_option_from_archive(): Unknown option type for option ") + this->opt_key); + default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown option type for option ") + this->opt_key); } } } @@ -1352,7 +1353,7 @@ public: case coInts: archive(*static_cast(opt)); break; case coPercents: archive(*static_cast(opt));break; case coBools: archive(*static_cast(opt)); break; - default: throw std::runtime_error(std::string("ConfigOptionDef::save_option_to_archive(): Unknown nullable option type for option ") + this->opt_key); + default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown nullable option type for option ") + this->opt_key); } } else { switch (this->type) { @@ -1371,7 +1372,7 @@ public: case coBool: archive(*static_cast(opt)); break; case coBools: archive(*static_cast(opt)); break; case coEnum: archive(*static_cast(opt)); break; - default: throw std::runtime_error(std::string("ConfigOptionDef::save_option_to_archive(): Unknown option type for option ") + this->opt_key); + default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown option type for option ") + this->opt_key); } } // Make the compiler happy, shut up the warnings. diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp index daaab47555..5bdd5055ec 100644 --- a/src/libslic3r/ExPolygon.cpp +++ b/src/libslic3r/ExPolygon.cpp @@ -1,5 +1,6 @@ #include "BoundingBox.hpp" #include "ExPolygon.hpp" +#include "Exception.hpp" #include "Geometry.hpp" #include "Polygon.hpp" #include "Line.hpp" @@ -435,7 +436,7 @@ void ExPolygon::triangulate_pp(Polygons* polygons) const std::list output; int res = TPPLPartition().Triangulate_MONO(&input, &output); if (res != 1) - throw std::runtime_error("Triangulation failed"); + throw Slic3r::RuntimeError("Triangulation failed"); // convert output polygons for (std::list::iterator poly = output.begin(); poly != output.end(); ++poly) { @@ -548,7 +549,7 @@ void ExPolygon::triangulate_pp(Points *triangles) const int res = TPPLPartition().Triangulate_MONO(&input, &output); // int TPPLPartition::Triangulate_EC(TPPLPolyList *inpolys, TPPLPolyList *triangles) { if (res != 1) - throw std::runtime_error("Triangulation failed"); + throw Slic3r::RuntimeError("Triangulation failed"); *triangles = polypartition_output_to_triangles(output); } @@ -591,7 +592,7 @@ void ExPolygon::triangulate_p2t(Polygons* polygons) const } polygons->push_back(p); } - } catch (const std::runtime_error & /* err */) { + } catch (const Slic3r::RuntimeError & /* err */) { assert(false); // just ignore, don't triangulate } diff --git a/src/libslic3r/Exception.hpp b/src/libslic3r/Exception.hpp new file mode 100644 index 0000000000..8ec9f20c81 --- /dev/null +++ b/src/libslic3r/Exception.hpp @@ -0,0 +1,28 @@ +#ifndef _libslic3r_Exception_h_ +#define _libslic3r_Exception_h_ + +#include + +namespace Slic3r { + +// PrusaSlicer's own exception hierarchy is derived from std::runtime_error. +// Base for Slicer's own exceptions. +class Exception : public std::runtime_error { using std::runtime_error::runtime_error; }; +#define SLIC3R_DERIVE_EXCEPTION(DERIVED_EXCEPTION, PARENT_EXCEPTION) \ + class DERIVED_EXCEPTION : public PARENT_EXCEPTION { using PARENT_EXCEPTION::PARENT_EXCEPTION; } +// Critical exception produced by Slicer, such exception shall never propagate up to the UI thread. +// If that happens, an ugly fat message box with an ugly fat exclamation mark is displayed. +SLIC3R_DERIVE_EXCEPTION(CriticalException, Exception); +SLIC3R_DERIVE_EXCEPTION(RuntimeError, CriticalException); +SLIC3R_DERIVE_EXCEPTION(LogicError, CriticalException); +SLIC3R_DERIVE_EXCEPTION(InvalidArgument, LogicError); +SLIC3R_DERIVE_EXCEPTION(OutOfRange, LogicError); +SLIC3R_DERIVE_EXCEPTION(IOError, CriticalException); +SLIC3R_DERIVE_EXCEPTION(FileIOError, IOError); +// Runtime exception produced by Slicer. Such exception cancels the slicing process and it shall be shown in notifications. +SLIC3R_DERIVE_EXCEPTION(SlicingError, Exception); +#undef SLIC3R_DERIVE_EXCEPTION + +} // namespace Slic3r + +#endif // _libslic3r_Exception_h_ diff --git a/src/libslic3r/ExtrusionEntityCollection.hpp b/src/libslic3r/ExtrusionEntityCollection.hpp index dfece6949b..5e40ab32ec 100644 --- a/src/libslic3r/ExtrusionEntityCollection.hpp +++ b/src/libslic3r/ExtrusionEntityCollection.hpp @@ -2,6 +2,7 @@ #define slic3r_ExtrusionEntityCollection_hpp_ #include "libslic3r.h" +#include "Exception.hpp" #include "ExtrusionEntity.hpp" namespace Slic3r { @@ -107,7 +108,7 @@ public: // Following methods shall never be called on an ExtrusionEntityCollection. Polyline as_polyline() const override { - throw std::runtime_error("Calling as_polyline() on a ExtrusionEntityCollection"); + throw Slic3r::RuntimeError("Calling as_polyline() on a ExtrusionEntityCollection"); return Polyline(); }; @@ -117,7 +118,7 @@ public: } double length() const override { - throw std::runtime_error("Calling length() on a ExtrusionEntityCollection"); + throw Slic3r::RuntimeError("Calling length() on a ExtrusionEntityCollection"); return 0.; } }; diff --git a/src/libslic3r/FileParserError.hpp b/src/libslic3r/FileParserError.hpp index 3f560fa4f5..b7e63d84e0 100644 --- a/src/libslic3r/FileParserError.hpp +++ b/src/libslic3r/FileParserError.hpp @@ -10,14 +10,14 @@ namespace Slic3r { // Generic file parser error, mostly copied from boost::property_tree::file_parser_error -class file_parser_error: public std::runtime_error +class file_parser_error: public Slic3r::RuntimeError { public: file_parser_error(const std::string &msg, const std::string &file, unsigned long line = 0) : - std::runtime_error(format_what(msg, file, line)), + Slic3r::RuntimeError(format_what(msg, file, line)), m_message(msg), m_filename(file), m_line(line) {} file_parser_error(const std::string &msg, const boost::filesystem::path &file, unsigned long line = 0) : - std::runtime_error(format_what(msg, file.string(), line)), + Slic3r::RuntimeError(format_what(msg, file.string(), line)), m_message(msg), m_filename(file.string()), m_line(line) {} // gcc 3.4.2 complains about lack of throw specifier on compiler // generated dtor @@ -35,7 +35,7 @@ private: std::string m_filename; unsigned long m_line; - // Format error message to be returned by std::runtime_error::what() + // Format error message to be returned by Slic3r::RuntimeError::what() static std::string format_what(const std::string &msg, const std::string &file, unsigned long l) { std::stringstream stream; diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index d68bc7afb3..03aa798dc4 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -318,7 +318,7 @@ void export_group_fills_to_svg(const char *path, const std::vector #endif // friend to Layer -void Layer::make_fills(FillAdaptive_Internal::Octree* adaptive_fill_octree, FillAdaptive_Internal::Octree* support_fill_octree) +void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree) { for (LayerRegion *layerm : m_regions) layerm->fills.clear(); @@ -345,8 +345,7 @@ void Layer::make_fills(FillAdaptive_Internal::Octree* adaptive_fill_octree, Fill f->layer_id = this->id(); f->z = this->print_z; f->angle = surface_fill.params.angle; - f->adapt_fill_octree = adaptive_fill_octree; - f->support_fill_octree = support_fill_octree; + f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree; // calculate flow spacing for infill pattern generation bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.flow.bridge; diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index 3b9212230d..eebded55bf 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -2,14 +2,268 @@ #include "../ExPolygon.hpp" #include "../Surface.hpp" #include "../Geometry.hpp" -#include "../AABBTreeIndirect.hpp" #include "../Layer.hpp" #include "../Print.hpp" #include "../ShortestPath.hpp" #include "FillAdaptive.hpp" +// for indexed_triangle_set +#include + +#include +#include + +// Boost pool: Don't use mutexes to synchronize memory allocation. +#define BOOST_POOL_NO_MT +#include + namespace Slic3r { +namespace FillAdaptive { + +// Derived from https://github.com/juj/MathGeoLib/blob/master/src/Geometry/Triangle.cpp +// The AABB-Triangle test implementation is based on the pseudo-code in +// Christer Ericson's Real-Time Collision Detection, pp. 169-172. It is +// practically a standard SAT test. +// +// Original MathGeoLib benchmark: +// Best: 17.282 nsecs / 46.496 ticks, Avg: 17.804 nsecs, Worst: 18.434 nsecs +// +//FIXME Vojtech: The MathGeoLib contains a vectorized implementation. +template +bool triangle_AABB_intersects(const Vector &a, const Vector &b, const Vector &c, const BoundingBoxBase &aabb) +{ + using Scalar = typename Vector::Scalar; + + Vector tMin = a.cwiseMin(b.cwiseMin(c)); + Vector tMax = a.cwiseMax(b.cwiseMax(c)); + + if (tMin.x() >= aabb.max.x() || tMax.x() <= aabb.min.x() + || tMin.y() >= aabb.max.y() || tMax.y() <= aabb.min.y() + || tMin.z() >= aabb.max.z() || tMax.z() <= aabb.min.z()) + return false; + + Vector center = (aabb.min + aabb.max) * 0.5f; + Vector h = aabb.max - center; + + const Vector t[3] { b-a, c-a, c-b }; + + Vector ac = a - center; + + Vector n = t[0].cross(t[1]); + Scalar s = n.dot(ac); + Scalar r = std::abs(h.dot(n.cwiseAbs())); + if (abs(s) >= r) + return false; + + const Vector at[3] = { t[0].cwiseAbs(), t[1].cwiseAbs(), t[2].cwiseAbs() }; + + Vector bc = b - center; + Vector cc = c - center; + + // SAT test all cross-axes. + // The following is a fully unrolled loop of this code, stored here for reference: + /* + Scalar d1, d2, a1, a2; + const Vector e[3] = { DIR_VEC(1, 0, 0), DIR_VEC(0, 1, 0), DIR_VEC(0, 0, 1) }; + for(int i = 0; i < 3; ++i) + for(int j = 0; j < 3; ++j) + { + Vector axis = Cross(e[i], t[j]); + ProjectToAxis(axis, d1, d2); + aabb.ProjectToAxis(axis, a1, a2); + if (d2 <= a1 || d1 >= a2) return false; + } + */ + + // eX t[0] + Scalar d1 = t[0].y() * ac.z() - t[0].z() * ac.y(); + Scalar d2 = t[0].y() * cc.z() - t[0].z() * cc.y(); + Scalar tc = (d1 + d2) * 0.5f; + r = std::abs(h.y() * at[0].z() + h.z() * at[0].y()); + if (r + std::abs(tc - d1) < std::abs(tc)) + return false; + + // eX t[1] + d1 = t[1].y() * ac.z() - t[1].z() * ac.y(); + d2 = t[1].y() * bc.z() - t[1].z() * bc.y(); + tc = (d1 + d2) * 0.5f; + r = std::abs(h.y() * at[1].z() + h.z() * at[1].y()); + if (r + std::abs(tc - d1) < std::abs(tc)) + return false; + + // eX t[2] + d1 = t[2].y() * ac.z() - t[2].z() * ac.y(); + d2 = t[2].y() * bc.z() - t[2].z() * bc.y(); + tc = (d1 + d2) * 0.5f; + r = std::abs(h.y() * at[2].z() + h.z() * at[2].y()); + if (r + std::abs(tc - d1) < std::abs(tc)) + return false; + + // eY t[0] + d1 = t[0].z() * ac.x() - t[0].x() * ac.z(); + d2 = t[0].z() * cc.x() - t[0].x() * cc.z(); + tc = (d1 + d2) * 0.5f; + r = std::abs(h.x() * at[0].z() + h.z() * at[0].x()); + if (r + std::abs(tc - d1) < std::abs(tc)) + return false; + + // eY t[1] + d1 = t[1].z() * ac.x() - t[1].x() * ac.z(); + d2 = t[1].z() * bc.x() - t[1].x() * bc.z(); + tc = (d1 + d2) * 0.5f; + r = std::abs(h.x() * at[1].z() + h.z() * at[1].x()); + if (r + std::abs(tc - d1) < std::abs(tc)) + return false; + + // eY t[2] + d1 = t[2].z() * ac.x() - t[2].x() * ac.z(); + d2 = t[2].z() * bc.x() - t[2].x() * bc.z(); + tc = (d1 + d2) * 0.5f; + r = std::abs(h.x() * at[2].z() + h.z() * at[2].x()); + if (r + std::abs(tc - d1) < std::abs(tc)) + return false; + + // eZ t[0] + d1 = t[0].x() * ac.y() - t[0].y() * ac.x(); + d2 = t[0].x() * cc.y() - t[0].y() * cc.x(); + tc = (d1 + d2) * 0.5f; + r = std::abs(h.y() * at[0].x() + h.x() * at[0].y()); + if (r + std::abs(tc - d1) < std::abs(tc)) + return false; + + // eZ t[1] + d1 = t[1].x() * ac.y() - t[1].y() * ac.x(); + d2 = t[1].x() * bc.y() - t[1].y() * bc.x(); + tc = (d1 + d2) * 0.5f; + r = std::abs(h.y() * at[1].x() + h.x() * at[1].y()); + if (r + std::abs(tc - d1) < std::abs(tc)) + return false; + + // eZ t[2] + d1 = t[2].x() * ac.y() - t[2].y() * ac.x(); + d2 = t[2].x() * bc.y() - t[2].y() * bc.x(); + tc = (d1 + d2) * 0.5f; + r = std::abs(h.y() * at[2].x() + h.x() * at[2].y()); + if (r + std::abs(tc - d1) < std::abs(tc)) + return false; + + // No separating axis exists, the AABB and triangle intersect. + return true; +} + +static double dist2_to_triangle(const Vec3d &a, const Vec3d &b, const Vec3d &c, const Vec3d &p) +{ + double out = std::numeric_limits::max(); + const Vec3d v1 = b - a; + auto l1 = v1.squaredNorm(); + const Vec3d v2 = c - b; + auto l2 = v2.squaredNorm(); + const Vec3d v3 = a - c; + auto l3 = v3.squaredNorm(); + + // Is the triangle valid? + if (l1 > 0. && l2 > 0. && l3 > 0.) + { + // 1) Project point into the plane of the triangle. + const Vec3d n = v1.cross(v2); + double d = (p - a).dot(n); + const Vec3d foot_pt = p - n * d / n.squaredNorm(); + + // 2) Maximum projection of n. + int proj_axis; + n.array().cwiseAbs().maxCoeff(&proj_axis); + + // 3) Test whether the foot_pt is inside the triangle. + { + auto inside_triangle = [](const Vec2d& v1, const Vec2d& v2, const Vec2d& v3, const Vec2d& pt) { + const double d1 = cross2(v1, pt); + const double d2 = cross2(v2, pt); + const double d3 = cross2(v3, pt); + // Testing both CCW and CW orientations. + return (d1 >= 0. && d2 >= 0. && d3 >= 0.) || (d1 <= 0. && d2 <= 0. && d3 <= 0.); + }; + bool inside; + switch (proj_axis) { + case 0: + inside = inside_triangle({v1.y(), v1.z()}, {v2.y(), v2.z()}, {v3.y(), v3.z()}, {foot_pt.y(), foot_pt.z()}); break; + case 1: + inside = inside_triangle({v1.z(), v1.x()}, {v2.z(), v2.x()}, {v3.z(), v3.x()}, {foot_pt.z(), foot_pt.x()}); break; + default: + assert(proj_axis == 2); + inside = inside_triangle({v1.x(), v1.y()}, {v2.x(), v2.y()}, {v3.x(), v3.y()}, {foot_pt.x(), foot_pt.y()}); break; + } + if (inside) + return (p - foot_pt).squaredNorm(); + } + + // 4) Find minimum distance to triangle vertices and edges. + out = std::min((p - a).squaredNorm(), std::min((p - b).squaredNorm(), (p - c).squaredNorm())); + auto t = (p - a).dot(v1); + if (t > 0. && t < l1) + out = std::min(out, (a + v1 * (t / l1) - p).squaredNorm()); + t = (p - b).dot(v2); + if (t > 0. && t < l2) + out = std::min(out, (b + v2 * (t / l2) - p).squaredNorm()); + t = (p - c).dot(v3); + if (t > 0. && t < l3) + out = std::min(out, (c + v3 * (t / l3) - p).squaredNorm()); + } + + return out; +} + +// Ordering of children cubes. +static const std::array child_centers { + Vec3d(-1, -1, -1), Vec3d( 1, -1, -1), Vec3d(-1, 1, -1), Vec3d( 1, 1, -1), + Vec3d(-1, -1, 1), Vec3d( 1, -1, 1), Vec3d(-1, 1, 1), Vec3d( 1, 1, 1) +}; + +// Traversal order of octree children cells for three infill directions, +// so that a single line will be discretized in a strictly monotonous order. +static constexpr std::array, 3> child_traversal_order { + std::array{ 2, 3, 0, 1, 6, 7, 4, 5 }, + std::array{ 4, 0, 6, 2, 5, 1, 7, 3 }, + std::array{ 1, 5, 0, 4, 3, 7, 2, 6 }, +}; + +struct Cube +{ + Vec3d center; +#ifndef NDEBUG + Vec3d center_octree; +#endif // NDEBUG + std::array children {}; // initialized to nullptrs + Cube(const Vec3d ¢er) : center(center) {} +}; + +struct CubeProperties +{ + double edge_length; // Lenght of edge of a cube + double height; // Height of rotated cube (standing on the corner) + double diagonal_length; // Length of diagonal of a cube a face + double line_z_distance; // Defines maximal distance from a center of a cube on Z axis on which lines will be created + double line_xy_distance;// Defines maximal distance from a center of a cube on X and Y axis on which lines will be created +}; + +struct Octree +{ + // Octree will allocate its Cubes from the pool. The pool only supports deletion of the complete pool, + // perfect for building up our octree. + boost::object_pool pool; + Cube* root_cube { nullptr }; + Vec3d origin; + std::vector cubes_properties; + + Octree(const Vec3d &origin, const std::vector &cubes_properties) + : root_cube(pool.construct(origin)), origin(origin), cubes_properties(cubes_properties) {} + + void insert_triangle(const Vec3d &a, const Vec3d &b, const Vec3d &c, Cube *current_cube, const BoundingBoxf3 ¤t_bbox, int depth); +}; + +void OctreeDeleter::operator()(Octree *p) { + delete p; +} std::pair adaptive_fill_line_spacing(const PrintObject &print_object) { @@ -90,330 +344,285 @@ std::pair adaptive_fill_line_spacing(const PrintObject &print_ob return std::make_pair(adaptive_line_spacing, support_line_spacing); } -void FillAdaptive::_fill_surface_single(const FillParams & params, - unsigned int thickness_layers, - const std::pair &direction, - ExPolygon & expolygon, - Polylines & polylines_out) +// Context used by generate_infill_lines() when recursively traversing an octree in a DDA fashion +// (Digital Differential Analyzer). +struct FillContext { - if(this->adapt_fill_octree != nullptr) - this->generate_infill(params, thickness_layers, direction, expolygon, polylines_out, this->adapt_fill_octree); -} - -void FillAdaptive::generate_infill(const FillParams & params, - unsigned int thickness_layers, - const std::pair &direction, - ExPolygon & expolygon, - Polylines & polylines_out, - FillAdaptive_Internal::Octree *octree) -{ - Vec3d rotation = Vec3d((5.0 * M_PI) / 4.0, Geometry::deg2rad(215.264), M_PI / 6.0); - Transform3d rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation, Vec3d::Ones(), Vec3d::Ones()); - - // Store grouped lines by its direction (multiple of 120°) - std::vector infill_lines_dir(3); - this->generate_infill_lines(octree->root_cube.get(), - this->z, octree->origin, rotation_matrix, - infill_lines_dir, octree->cubes_properties, - int(octree->cubes_properties.size()) - 1); - - Polylines all_polylines; - all_polylines.reserve(infill_lines_dir[0].size() * 3); - for (Lines &infill_lines : infill_lines_dir) - { - for (const Line &line : infill_lines) - { - all_polylines.emplace_back(line.a, line.b); - } - } - - if (params.dont_connect) - { - // Crop all polylines - polylines_out = intersection_pl(all_polylines, to_polygons(expolygon)); - } - else - { - // Crop all polylines - all_polylines = intersection_pl(all_polylines, to_polygons(expolygon)); - - Polylines boundary_polylines; - Polylines non_boundary_polylines; - for (const Polyline &polyline : all_polylines) - { - // connect_infill required all polylines to touch the boundary. - if(polyline.lines().size() == 1 && expolygon.has_boundary_point(polyline.lines().front().a) && expolygon.has_boundary_point(polyline.lines().front().b)) - { - boundary_polylines.push_back(polyline); - } - else - { - non_boundary_polylines.push_back(polyline); - } - } - - if(!boundary_polylines.empty()) - { - boundary_polylines = chain_polylines(boundary_polylines); - FillAdaptive::connect_infill(std::move(boundary_polylines), expolygon, polylines_out, this->spacing, params); - } - - polylines_out.insert(polylines_out.end(), non_boundary_polylines.begin(), non_boundary_polylines.end()); - } - -#ifdef SLIC3R_DEBUG_SLICE_PROCESSING - { - static int iRuna = 0; - BoundingBox bbox_svg = this->bounding_box; - { - ::Slic3r::SVG svg(debug_out_path("FillAdaptive-%d.svg", iRuna), bbox_svg); - for (const Polyline &polyline : polylines_out) - { - for (const Line &line : polyline.lines()) - { - Point from = line.a; - Point to = line.b; - Point diff = to - from; - - float shrink_length = scale_(0.4); - float line_slope = (float)diff.y() / diff.x(); - float shrink_x = shrink_length / (float)std::sqrt(1.0 + (line_slope * line_slope)); - float shrink_y = line_slope * shrink_x; - - to.x() -= shrink_x; - to.y() -= shrink_y; - from.x() += shrink_x; - from.y() += shrink_y; - - svg.draw(Line(from, to)); - } - } - } - - iRuna++; - } -#endif /* SLIC3R_DEBUG */ -} - -void FillAdaptive::generate_infill_lines( - FillAdaptive_Internal::Cube *cube, - double z_position, - const Vec3d &origin, - const Transform3d &rotation_matrix, - std::vector &dir_lines_out, - const std::vector &cubes_properties, - int depth) -{ - using namespace FillAdaptive_Internal; - - if(cube == nullptr) - { - return; - } - - Vec3d cube_center_tranformed = rotation_matrix * cube->center; - double z_diff = std::abs(z_position - cube_center_tranformed.z()); - - if (z_diff > cubes_properties[depth].height / 2) - { - return; - } - - if (z_diff < cubes_properties[depth].line_z_distance) - { - Point from( - scale_((cubes_properties[depth].diagonal_length / 2) * (cubes_properties[depth].line_z_distance - z_diff) / cubes_properties[depth].line_z_distance), - scale_(cubes_properties[depth].line_xy_distance - ((z_position - (cube_center_tranformed.z() - cubes_properties[depth].line_z_distance)) / sqrt(2)))); - Point to(-from.x(), from.y()); - // Relative to cube center - - double rotation_angle = (2.0 * M_PI) / 3.0; - for (Lines &lines : dir_lines_out) - { - Vec3d offset = cube_center_tranformed - (rotation_matrix * origin); - Point from_abs(from), to_abs(to); - - from_abs.x() += int(scale_(offset.x())); - from_abs.y() += int(scale_(offset.y())); - to_abs.x() += int(scale_(offset.x())); - to_abs.y() += int(scale_(offset.y())); - -// lines.emplace_back(from_abs, to_abs); - this->connect_lines(lines, Line(from_abs, to_abs)); - - from.rotate(rotation_angle); - to.rotate(rotation_angle); - } - } - - for(const std::unique_ptr &child : cube->children) - { - if(child != nullptr) - { - generate_infill_lines(child.get(), z_position, origin, rotation_matrix, dir_lines_out, cubes_properties, depth - 1); - } - } -} - -void FillAdaptive::connect_lines(Lines &lines, Line new_line) -{ - auto eps = int(scale_(0.10)); - for (size_t i = 0; i < lines.size(); ++i) - { - if (std::abs(new_line.a.x() - lines[i].b.x()) < eps && std::abs(new_line.a.y() - lines[i].b.y()) < eps) - { - new_line.a = lines[i].a; - lines.erase(lines.begin() + i); - --i; - continue; - } - - if (std::abs(new_line.b.x() - lines[i].a.x()) < eps && std::abs(new_line.b.y() - lines[i].a.y()) < eps) - { - new_line.b = lines[i].b; - lines.erase(lines.begin() + i); - --i; - continue; - } - } - - lines.emplace_back(new_line.a, new_line.b); -} - -std::unique_ptr FillAdaptive::build_octree( - TriangleMesh &triangle_mesh, - coordf_t line_spacing, - const Vec3d &cube_center) -{ - using namespace FillAdaptive_Internal; - - if(line_spacing <= 0 || std::isnan(line_spacing)) - { - return nullptr; - } - - Vec3d bb_size = triangle_mesh.bounding_box().size(); - // The furthest point from the center of the bottom of the mesh bounding box. - double furthest_point = std::sqrt(((bb_size.x() * bb_size.x()) / 4.0) + - ((bb_size.y() * bb_size.y()) / 4.0) + - (bb_size.z() * bb_size.z())); - double max_cube_edge_length = furthest_point * 2; - - std::vector cubes_properties; - for (double edge_length = (line_spacing * 2); edge_length < (max_cube_edge_length * 2); edge_length *= 2) - { - CubeProperties props{}; - props.edge_length = edge_length; - props.height = edge_length * sqrt(3); - props.diagonal_length = edge_length * sqrt(2); - props.line_z_distance = edge_length / sqrt(3); - props.line_xy_distance = edge_length / sqrt(6); - cubes_properties.push_back(props); - } - - if (triangle_mesh.its.vertices.empty()) - { - triangle_mesh.require_shared_vertices(); - } - - AABBTreeIndirect::Tree3f aabbTree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set( - triangle_mesh.its.vertices, triangle_mesh.its.indices); - auto octree = std::make_unique(std::make_unique(cube_center), cube_center, cubes_properties); - - FillAdaptive::expand_cube(octree->root_cube.get(), cubes_properties, aabbTree, triangle_mesh, int(cubes_properties.size()) - 1); - - return octree; -} - -void FillAdaptive::expand_cube( - FillAdaptive_Internal::Cube *cube, - const std::vector &cubes_properties, - const AABBTreeIndirect::Tree3f &distance_tree, - const TriangleMesh &triangle_mesh, int depth) -{ - using namespace FillAdaptive_Internal; - - if (cube == nullptr || depth == 0) - { - return; - } - - std::vector child_centers = { - Vec3d(-1, -1, -1), Vec3d( 1, -1, -1), Vec3d(-1, 1, -1), Vec3d( 1, 1, -1), - Vec3d(-1, -1, 1), Vec3d( 1, -1, 1), Vec3d(-1, 1, 1), Vec3d( 1, 1, 1) + // The angles have to agree with child_traversal_order. + static constexpr double direction_angles[3] { + 0., + (2.0 * M_PI) / 3.0, + -(2.0 * M_PI) / 3.0 }; - double cube_radius_squared = (cubes_properties[depth].height * cubes_properties[depth].height) / 16; - - for (size_t i = 0; i < 8; ++i) + FillContext(const Octree &octree, double z_position, int direction_idx) : + cubes_properties(octree.cubes_properties), + z_position(z_position), + traversal_order(child_traversal_order[direction_idx]), + cos_a(cos(direction_angles[direction_idx])), + sin_a(sin(direction_angles[direction_idx])) { - const Vec3d &child_center = child_centers[i]; - Vec3d child_center_transformed = cube->center + (child_center * (cubes_properties[depth].edge_length / 4)); - - if(AABBTreeIndirect::is_any_triangle_in_radius(triangle_mesh.its.vertices, triangle_mesh.its.indices, - distance_tree, child_center_transformed, cube_radius_squared)) - { - cube->children[i] = std::make_unique(child_center_transformed); - FillAdaptive::expand_cube(cube->children[i].get(), cubes_properties, distance_tree, triangle_mesh, depth - 1); - } + static constexpr auto unused = std::numeric_limits::max(); + temp_lines.assign((1 << octree.cubes_properties.size()) - 1, Line(Point(unused, unused), Point(unused, unused))); } + + // Rotate the point, uses the same convention as Point::rotate(). + Vec2d rotate(const Vec2d& v) { return Vec2d(this->cos_a * v.x() - this->sin_a * v.y(), this->sin_a * v.x() + this->cos_a * v.y()); } + + const std::vector &cubes_properties; + // Top of the current layer. + const double z_position; + // Order of traversal for this line direction. + const std::array traversal_order; + // Rotation of the generated line for this line direction. + const double cos_a; + const double sin_a; + + // Linearized tree spanning a single Octree wall, used to connect lines spanning + // neighboring Octree cells. Unused lines have the Line::a::x set to infinity. + std::vector temp_lines; + // Final output + std::vector output_lines; +}; + +static constexpr double octree_rot[3] = { 5.0 * M_PI / 4.0, Geometry::deg2rad(215.264), M_PI / 6.0 }; + +Eigen::Quaterniond transform_to_world() +{ + return Eigen::AngleAxisd(octree_rot[2], Vec3d::UnitZ()) * Eigen::AngleAxisd(octree_rot[1], Vec3d::UnitY()) * Eigen::AngleAxisd(octree_rot[0], Vec3d::UnitX()); } -void FillAdaptive_Internal::Octree::propagate_point( - Vec3d point, - FillAdaptive_Internal::Cube * current, - int depth, - const std::vector &cubes_properties) +Eigen::Quaterniond transform_to_octree() { - using namespace FillAdaptive_Internal; + return Eigen::AngleAxisd(- octree_rot[0], Vec3d::UnitX()) * Eigen::AngleAxisd(- octree_rot[1], Vec3d::UnitY()) * Eigen::AngleAxisd(- octree_rot[2], Vec3d::UnitZ()); +} - if(depth <= 0) - { +#ifndef NDEBUG +// Verify that the traversal order of the octree children matches the line direction, +// therefore the infill line may get extended with O(1) time & space complexity. +static bool verify_traversal_order( + FillContext &context, + const Cube *cube, + int depth, + const Vec2d &line_from, + const Vec2d &line_to) +{ + std::array c; + Eigen::Quaterniond to_world = transform_to_world(); + for (int i = 0; i < 8; ++i) { + int j = context.traversal_order[i]; + Vec3d cntr = to_world * (cube->center_octree + (child_centers[j] * (context.cubes_properties[depth].edge_length / 4.))); + assert(!cube->children[j] || cube->children[j]->center.isApprox(cntr)); + c[i] = cntr; + } + std::array dirs = { + c[1] - c[0], c[2] - c[0], c[3] - c[1], c[3] - c[2], c[3] - c[0], + c[5] - c[4], c[6] - c[4], c[7] - c[5], c[7] - c[6], c[7] - c[4] + }; + assert(std::abs(dirs[4].z()) < 0.005); + assert(std::abs(dirs[9].z()) < 0.005); + assert(dirs[0].isApprox(dirs[3])); + assert(dirs[1].isApprox(dirs[2])); + assert(dirs[5].isApprox(dirs[8])); + assert(dirs[6].isApprox(dirs[7])); + Vec3d line_dir = Vec3d(line_to.x() - line_from.x(), line_to.y() - line_from.y(), 0.).normalized(); + for (auto& dir : dirs) { + double d = dir.normalized().dot(line_dir); + assert(d > 0.7); + } + return true; +} +#endif // NDEBUG + +static void generate_infill_lines_recursive( + FillContext &context, + const Cube *cube, + // Address of this wall in the octree, used to address context.temp_lines. + int address, + int depth) +{ + assert(cube != nullptr); + + const std::vector &cubes_properties = context.cubes_properties; + const double z_diff = context.z_position - cube->center.z(); + const double z_diff_abs = std::abs(z_diff); + + if (z_diff_abs > cubes_properties[depth].height / 2.) return; + + if (z_diff_abs < cubes_properties[depth].line_z_distance) { + // Discretize a single wall splitting the cube into two. + const double zdist = cubes_properties[depth].line_z_distance; + Vec2d from( + 0.5 * cubes_properties[depth].diagonal_length * (zdist - z_diff_abs) / zdist, + cubes_properties[depth].line_xy_distance - (zdist + z_diff) / sqrt(2.)); + Vec2d to(-from.x(), from.y()); + from = context.rotate(from); + to = context.rotate(to); + // Relative to cube center + const Vec2d offset(cube->center.x(), cube->center.y()); + from += offset; + to += offset; + // Verify that the traversal order of the octree children matches the line direction, + // therefore the infill line may get extended with O(1) time & space complexity. + assert(verify_traversal_order(context, cube, depth, from, to)); + // Either extend an existing line or start a new one. + Line &last_line = context.temp_lines[address]; + Line new_line(Point::new_scale(from), Point::new_scale(to)); + if (last_line.a.x() == std::numeric_limits::max()) { + last_line.a = new_line.a; + } else if ((new_line.a - last_line.b).cwiseAbs().maxCoeff() > 300) { // SCALED_EPSILON is 100 and it is not enough + context.output_lines.emplace_back(last_line); + last_line.a = new_line.a; + } + last_line.b = new_line.b; } - size_t octant_idx = Octree::find_octant(point, current->center); - Cube * child = current->children[octant_idx].get(); - - // Octant not exists, then create it - if(child == nullptr) { - std::vector child_centers = { - Vec3d(-1, -1, -1), Vec3d( 1, -1, -1), Vec3d(-1, 1, -1), Vec3d( 1, 1, -1), - Vec3d(-1, -1, 1), Vec3d( 1, -1, 1), Vec3d(-1, 1, 1), Vec3d( 1, 1, 1) - }; - - const Vec3d &child_center = child_centers[octant_idx]; - Vec3d child_center_transformed = current->center + (child_center * (cubes_properties[depth].edge_length / 4)); - - current->children[octant_idx] = std::make_unique(child_center_transformed); - child = current->children[octant_idx].get(); + // left child index + address = address * 2 + 1; + -- depth; + size_t i = 0; + for (const int child_idx : context.traversal_order) { + const Cube *child = cube->children[child_idx]; + if (child != nullptr) + generate_infill_lines_recursive(context, child, address, depth); + if (++ i == 4) + // right child index + ++ address; } - - Octree::propagate_point(point, child, (depth - 1), cubes_properties); } -std::unique_ptr FillSupportCubic::build_octree( - TriangleMesh & triangle_mesh, - coordf_t line_spacing, - const Vec3d & cube_center, - const Transform3d &rotation_matrix) -{ - using namespace FillAdaptive_Internal; +#ifndef NDEBUG +// #define ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT +#endif - if(line_spacing <= 0 || std::isnan(line_spacing)) +#ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT +static void export_infill_lines_to_svg(const ExPolygon &expoly, const Polylines &polylines, const std::string &path) +{ + BoundingBox bbox = get_extents(expoly); + bbox.offset(scale_(3.)); + + ::Slic3r::SVG svg(path, bbox); + svg.draw(expoly); + svg.draw_outline(expoly, "green"); + svg.draw(polylines, "red"); + static constexpr double trim_length = scale_(0.4); + for (Polyline polyline : polylines) { + Vec2d a = polyline.points.front().cast(); + Vec2d d = polyline.points.back().cast(); + if (polyline.size() == 2) { + Vec2d v = d - a; + double l = v.norm(); + if (l > 2. * trim_length) { + a += v * trim_length / l; + d -= v * trim_length / l; + polyline.points.front() = a.cast(); + polyline.points.back() = d.cast(); + } else + polyline.points.clear(); + } else if (polyline.size() > 2) { + Vec2d b = polyline.points[1].cast(); + Vec2d c = polyline.points[polyline.points.size() - 2].cast(); + Vec2d v = b - a; + double l = v.norm(); + if (l > trim_length) { + a += v * trim_length / l; + polyline.points.front() = a.cast(); + } else + polyline.points.erase(polyline.points.begin()); + v = d - c; + l = v.norm(); + if (l > trim_length) + polyline.points.back() = (d - v * trim_length / l).cast(); + else + polyline.points.pop_back(); + } + svg.draw(polyline, "black"); + } +} +#endif /* ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT */ + +void Filler::_fill_surface_single( + const FillParams & params, + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon &expolygon, + Polylines &polylines_out) +{ + assert (this->adapt_fill_octree); + + Polylines all_polylines; { - return nullptr; + // 3 contexts for three directions of infill lines + std::array contexts { + FillContext { *adapt_fill_octree, this->z, 0 }, + FillContext { *adapt_fill_octree, this->z, 1 }, + FillContext { *adapt_fill_octree, this->z, 2 } + }; + // Generate the infill lines along the octree cells, merge touching lines of the same direction. + size_t num_lines = 0; + for (auto &context : contexts) { + generate_infill_lines_recursive(context, adapt_fill_octree->root_cube, 0, int(adapt_fill_octree->cubes_properties.size()) - 1); + num_lines += context.output_lines.size() + context.temp_lines.size(); + } + // Collect the lines. + std::vector lines; + lines.reserve(num_lines); + for (auto &context : contexts) { + append(lines, context.output_lines); + for (const Line &line : context.temp_lines) + if (line.a.x() != std::numeric_limits::max()) + lines.emplace_back(line); + } +#if 0 + // Chain touching line segments, convert lines to polylines. + //all_polylines = chain_lines(lines, 300.); // SCALED_EPSILON is 100 and it is not enough +#else + // Convert lines to polylines. + all_polylines.reserve(lines.size()); + std::transform(lines.begin(), lines.end(), std::back_inserter(all_polylines), [](const Line& l) { return Polyline{ l.a, l.b }; }); +#endif } - Vec3d bb_size = triangle_mesh.bounding_box().size(); - // The furthest point from the center of the bottom of the mesh bounding box. - double furthest_point = std::sqrt(((bb_size.x() * bb_size.x()) / 4.0) + - ((bb_size.y() * bb_size.y()) / 4.0) + - (bb_size.z() * bb_size.z())); - double max_cube_edge_length = furthest_point * 2; + // Crop all polylines + all_polylines = intersection_pl(std::move(all_polylines), to_polygons(expolygon)); + +#ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT + { + static int iRun = 0; + export_infill_lines_to_svg(expolygon, all_polylines, debug_out_path("FillAdaptive-initial-%d.svg", iRun++)); + } +#endif /* ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT */ + + if (params.dont_connect || all_polylines.size() <= 1) + append(polylines_out, std::move(all_polylines)); + else + connect_infill(chain_polylines(std::move(all_polylines)), expolygon, polylines_out, this->spacing, params); + +#ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT + { + static int iRun = 0; + export_infill_lines_to_svg(expolygon, polylines_out, debug_out_path("FillAdaptive-final-%d.svg", iRun ++)); + } +#endif /* ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT */ +} + +static double bbox_max_radius(const BoundingBoxf3 &bbox, const Vec3d ¢er) +{ + const auto p = (bbox.min - center); + const auto s = bbox.size(); + double r2max = 0.; + for (int i = 0; i < 8; ++ i) + r2max = std::max(r2max, (p + Vec3d(s.x() * double(i & 1), s.y() * double(i & 2), s.z() * double(i & 4))).squaredNorm()); + return sqrt(r2max); +} + +static std::vector make_cubes_properties(double max_cube_edge_length, double line_spacing) +{ + max_cube_edge_length += EPSILON; std::vector cubes_properties; - for (double edge_length = (line_spacing * 2); edge_length < (max_cube_edge_length * 2); edge_length *= 2) + for (double edge_length = line_spacing * 2.;; edge_length *= 2.) { CubeProperties props{}; props.edge_length = edge_length; @@ -421,100 +630,113 @@ std::unique_ptr FillSupportCubic::build_octree( props.diagonal_length = edge_length * sqrt(2); props.line_z_distance = edge_length / sqrt(3); props.line_xy_distance = edge_length / sqrt(6); - cubes_properties.push_back(props); + cubes_properties.emplace_back(props); + if (edge_length > max_cube_edge_length) + break; } + return cubes_properties; +} - if (triangle_mesh.its.vertices.empty()) - { - triangle_mesh.require_shared_vertices(); - } +static inline bool is_overhang_triangle(const Vec3d &a, const Vec3d &b, const Vec3d &c, const Vec3d &up) +{ + // Calculate triangle normal. + auto n = (b - a).cross(c - b); + return n.dot(up) > 0.707 * n.norm(); +} - AABBTreeIndirect::Tree3f aabbTree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set( - triangle_mesh.its.vertices, triangle_mesh.its.indices); +static void transform_center(Cube *current_cube, const Eigen::Matrix3d &rot) +{ +#ifndef NDEBUG + current_cube->center_octree = current_cube->center; +#endif // NDEBUG + current_cube->center = rot * current_cube->center; + for (auto *child : current_cube->children) + if (child) + transform_center(child, rot); +} - auto octree = std::make_unique(std::make_unique(cube_center), cube_center, cubes_properties); +OctreePtr build_octree( + // Mesh is rotated to the coordinate system of the octree. + const indexed_triangle_set &triangle_mesh, + // Overhang triangles extracted from fill surfaces with stInternalBridge type, + // rotated to the coordinate system of the octree. + const std::vector &overhang_triangles, + coordf_t line_spacing, + bool support_overhangs_only) +{ + assert(line_spacing > 0); + assert(! std::isnan(line_spacing)); - double cube_edge_length = line_spacing / 2.0; - int max_depth = int(octree->cubes_properties.size()) - 1; - BoundingBoxf3 mesh_bb = triangle_mesh.bounding_box(); - Vec3f vertical(0, 0, 1); + BoundingBox3Base bbox(triangle_mesh.vertices); + Vec3d cube_center = bbox.center().cast(); + std::vector cubes_properties = make_cubes_properties(double(bbox.size().maxCoeff()), line_spacing); + auto octree = OctreePtr(new Octree(cube_center, cubes_properties)); - for (size_t facet_idx = 0; facet_idx < triangle_mesh.stl.facet_start.size(); ++facet_idx) - { - if(triangle_mesh.stl.facet_start[facet_idx].normal.dot(vertical) <= 0.707) - { - // The angle is smaller than PI/4, than infill don't to be there - continue; + if (cubes_properties.size() > 1) { + Octree *octree_ptr = octree.get(); + double edge_length_half = 0.5 * cubes_properties.back().edge_length; + Vec3d diag_half(edge_length_half, edge_length_half, edge_length_half); + int max_depth = int(cubes_properties.size()) - 1; + auto process_triangle = [octree_ptr, max_depth, diag_half](const Vec3d &a, const Vec3d &b, const Vec3d &c) { + octree_ptr->insert_triangle( + a, b, c, + octree_ptr->root_cube, + BoundingBoxf3(octree_ptr->root_cube->center - diag_half, octree_ptr->root_cube->center + diag_half), + max_depth); + }; + auto up_vector = support_overhangs_only ? Vec3d(transform_to_octree() * Vec3d(0., 0., 1.)) : Vec3d(); + for (auto &tri : triangle_mesh.indices) { + auto a = triangle_mesh.vertices[tri[0]].cast(); + auto b = triangle_mesh.vertices[tri[1]].cast(); + auto c = triangle_mesh.vertices[tri[2]].cast(); + if (! support_overhangs_only || is_overhang_triangle(a, b, c, up_vector)) + process_triangle(a, b, c); } - - stl_vertex v_1 = triangle_mesh.stl.facet_start[facet_idx].vertex[0]; - stl_vertex v_2 = triangle_mesh.stl.facet_start[facet_idx].vertex[1]; - stl_vertex v_3 = triangle_mesh.stl.facet_start[facet_idx].vertex[2]; - - std::vector triangle_vertices = - {Vec3d(v_1.x(), v_1.y(), v_1.z()), - Vec3d(v_2.x(), v_2.y(), v_2.z()), - Vec3d(v_3.x(), v_3.y(), v_3.z())}; - - BoundingBoxf3 triangle_bb(triangle_vertices); - - Vec3d triangle_start_relative = triangle_bb.min - mesh_bb.min; - Vec3d triangle_end_relative = triangle_bb.max - mesh_bb.min; - - Vec3crd triangle_start_idx = Vec3crd( - int(std::floor(triangle_start_relative.x() / cube_edge_length)), - int(std::floor(triangle_start_relative.y() / cube_edge_length)), - int(std::floor(triangle_start_relative.z() / cube_edge_length))); - Vec3crd triangle_end_idx = Vec3crd( - int(std::floor(triangle_end_relative.x() / cube_edge_length)), - int(std::floor(triangle_end_relative.y() / cube_edge_length)), - int(std::floor(triangle_end_relative.z() / cube_edge_length))); - - for (int z = triangle_start_idx.z(); z <= triangle_end_idx.z(); ++z) + for (size_t i = 0; i < overhang_triangles.size(); i += 3) + process_triangle(overhang_triangles[i], overhang_triangles[i + 1], overhang_triangles[i + 2]); { - for (int y = triangle_start_idx.y(); y <= triangle_end_idx.y(); ++y) - { - for (int x = triangle_start_idx.x(); x <= triangle_end_idx.x(); ++x) - { - Vec3d cube_center_relative(x * cube_edge_length + (cube_edge_length / 2.0), y * cube_edge_length + (cube_edge_length / 2.0), z * cube_edge_length); - Vec3d cube_center_absolute = cube_center_relative + mesh_bb.min; - - double cube_center_absolute_arr[3] = {cube_center_absolute.x(), cube_center_absolute.y(), cube_center_absolute.z()}; - double distance = 0, cord_u = 0, cord_v = 0; - - double dir[3] = {0.0, 0.0, 1.0}; - - double vert_0[3] = {triangle_vertices[0].x(), - triangle_vertices[0].y(), - triangle_vertices[0].z()}; - double vert_1[3] = {triangle_vertices[1].x(), - triangle_vertices[1].y(), - triangle_vertices[1].z()}; - double vert_2[3] = {triangle_vertices[2].x(), - triangle_vertices[2].y(), - triangle_vertices[2].z()}; - - if(intersect_triangle(cube_center_absolute_arr, dir, vert_0, vert_1, vert_2, &distance, &cord_u, &cord_v) && distance > 0 && distance <= cube_edge_length) - { - Vec3d cube_center_transformed(cube_center_absolute.x(), cube_center_absolute.y(), cube_center_absolute.z() + (cube_edge_length / 2.0)); - Octree::propagate_point(rotation_matrix * cube_center_transformed, octree->root_cube.get(), max_depth, octree->cubes_properties); - } - } - } + // Transform the octree to world coordinates to reduce computation when extracting infill lines. + auto rot = transform_to_world().toRotationMatrix(); + transform_center(octree->root_cube, rot); + octree->origin = rot * octree->origin; } } return octree; } -void FillSupportCubic::_fill_surface_single(const FillParams & params, - unsigned int thickness_layers, - const std::pair &direction, - ExPolygon & expolygon, - Polylines & polylines_out) +void Octree::insert_triangle(const Vec3d &a, const Vec3d &b, const Vec3d &c, Cube *current_cube, const BoundingBoxf3 ¤t_bbox, int depth) { - if (this->support_fill_octree != nullptr) - this->generate_infill(params, thickness_layers, direction, expolygon, polylines_out, this->support_fill_octree); + assert(current_cube); + assert(depth > 0); + + // Squared radius of a sphere around the child cube. + const double r2_cube = Slic3r::sqr(0.5 * this->cubes_properties[-- depth].height + EPSILON); + + for (size_t i = 0; i < 8; ++ i) { + const Vec3d &child_center_dir = child_centers[i]; + // Calculate a slightly expanded bounding box of a child cube to cope with triangles touching a cube wall and other numeric errors. + // We will rather densify the octree a bit more than necessary instead of missing a triangle. + BoundingBoxf3 bbox; + for (int k = 0; k < 3; ++ k) { + if (child_center_dir[k] == -1.) { + bbox.min[k] = current_bbox.min[k]; + bbox.max[k] = current_cube->center[k] + EPSILON; + } else { + bbox.min[k] = current_cube->center[k] - EPSILON; + bbox.max[k] = current_bbox.max[k]; + } + } + Vec3d child_center = current_cube->center + (child_center_dir * (this->cubes_properties[depth].edge_length / 2.)); + //if (dist2_to_triangle(a, b, c, child_center) < r2_cube) { + if (triangle_AABB_intersects(a, b, c, bbox)) { + if (! current_cube->children[i]) + current_cube->children[i] = this->pool.construct(child_center); + if (depth > 0) + this->insert_triangle(a, b, c, current_cube->children[i], bbox, depth); + } + } } +} // namespace FillAdaptive } // namespace Slic3r diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp index 4bb80fa063..f10c40b99f 100644 --- a/src/libslic3r/Fill/FillAdaptive.hpp +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -1,138 +1,75 @@ +// Adaptive cubic infill was inspired by the work of @mboerwinkle +// as implemented for Cura. +// https://github.com/Ultimaker/CuraEngine/issues/381 +// https://github.com/Ultimaker/CuraEngine/pull/401 +// +// Our implementation is more accurate (discretizes a bit less cubes than Cura's) +// by splitting only such cubes which contain a triangle. +// Our line extraction is time optimal instead of O(n^2) when connecting extracted lines, +// and we also implemented adaptivity for supporting internal overhangs only. + #ifndef slic3r_FillAdaptive_hpp_ #define slic3r_FillAdaptive_hpp_ -#include "../AABBTreeIndirect.hpp" - #include "FillBase.hpp" +struct indexed_triangle_set; + namespace Slic3r { class PrintObject; -namespace FillAdaptive_Internal +namespace FillAdaptive { - struct CubeProperties - { - double edge_length; // Lenght of edge of a cube - double height; // Height of rotated cube (standing on the corner) - double diagonal_length; // Length of diagonal of a cube a face - double line_z_distance; // Defines maximal distance from a center of a cube on Z axis on which lines will be created - double line_xy_distance;// Defines maximal distance from a center of a cube on X and Y axis on which lines will be created - }; - struct Cube - { - Vec3d center; - std::unique_ptr children[8] = {}; - Cube(const Vec3d ¢er) : center(center) {} - }; +struct Octree; +// To keep the definition of Octree opaque, we have to define a custom deleter. +struct OctreeDeleter { void operator()(Octree *p); }; +using OctreePtr = std::unique_ptr; - struct Octree - { - std::unique_ptr root_cube; - Vec3d origin; - std::vector cubes_properties; +// Calculate line spacing for +// 1) adaptive cubic infill +// 2) adaptive internal support cubic infill +// Returns zero for a particular infill type if no such infill is to be generated. +std::pair adaptive_fill_line_spacing(const PrintObject &print_object); - Octree(std::unique_ptr rootCube, const Vec3d &origin, const std::vector &cubes_properties) - : root_cube(std::move(rootCube)), origin(origin), cubes_properties(cubes_properties) {} +// Rotation of the octree to stand on one of its corners. +Eigen::Quaterniond transform_to_world(); +// Inverse roation of the above. +Eigen::Quaterniond transform_to_octree(); - inline static int find_octant(const Vec3d &i_cube, const Vec3d ¤t) - { - return (i_cube.z() > current.z()) * 4 + (i_cube.y() > current.y()) * 2 + (i_cube.x() > current.x()); - } - - static void propagate_point( - Vec3d point, - FillAdaptive_Internal::Cube *current_cube, - int depth, - const std::vector &cubes_properties); - }; -}; // namespace FillAdaptive_Internal +FillAdaptive::OctreePtr build_octree( + // Mesh is rotated to the coordinate system of the octree. + const indexed_triangle_set &triangle_mesh, + // Overhang triangles extracted from fill surfaces with stInternalBridge type, + // rotated to the coordinate system of the octree. + const std::vector &overhang_triangles, + coordf_t line_spacing, + // If true, octree is densified below internal overhangs only. + bool support_overhangs_only); // // Some of the algorithms used by class FillAdaptive were inspired by // Cura Engine's class SubDivCube // https://github.com/Ultimaker/CuraEngine/blob/master/src/infill/SubDivCube.h // -class FillAdaptive : public Fill +class Filler : public Slic3r::Fill { public: - virtual ~FillAdaptive() {} + virtual ~Filler() {} protected: - virtual Fill* clone() const { return new FillAdaptive(*this); }; + virtual Fill* clone() const { return new Filler(*this); }; virtual void _fill_surface_single( const FillParams ¶ms, unsigned int thickness_layers, const std::pair &direction, ExPolygon &expolygon, Polylines &polylines_out); - virtual bool no_sort() const { return true; } - - void generate_infill_lines( - FillAdaptive_Internal::Cube *cube, - double z_position, - const Vec3d & origin, - const Transform3d & rotation_matrix, - std::vector & dir_lines_out, - const std::vector &cubes_properties, - int depth); - - static void connect_lines(Lines &lines, Line new_line); - - void generate_infill(const FillParams & params, - unsigned int thickness_layers, - const std::pair &direction, - ExPolygon & expolygon, - Polylines & polylines_out, - FillAdaptive_Internal::Octree *octree); - -public: - static std::unique_ptr build_octree( - TriangleMesh &triangle_mesh, - coordf_t line_spacing, - const Vec3d & cube_center); - - static void expand_cube( - FillAdaptive_Internal::Cube *cube, - const std::vector &cubes_properties, - const AABBTreeIndirect::Tree3f &distance_tree, - const TriangleMesh & triangle_mesh, - int depth); }; -class FillSupportCubic : public FillAdaptive -{ -public: - virtual ~FillSupportCubic() = default; - -protected: - virtual Fill* clone() const { return new FillSupportCubic(*this); }; - - virtual bool no_sort() const { return true; } - - virtual void _fill_surface_single( - const FillParams ¶ms, - unsigned int thickness_layers, - const std::pair &direction, - ExPolygon &expolygon, - Polylines &polylines_out); - -public: - static std::unique_ptr build_octree( - TriangleMesh & triangle_mesh, - coordf_t line_spacing, - const Vec3d & cube_center, - const Transform3d &rotation_matrix); -}; - -// Calculate line spacing for -// 1) adaptive cubic infill -// 2) adaptive internal support cubic infill -// Returns zero for a particular infill type if no such infill is to be generated. -std::pair adaptive_fill_line_spacing(const PrintObject &print_object); - +}; // namespace FillAdaptive } // namespace Slic3r #endif // slic3r_FillAdaptive_hpp_ diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index 9001330aae..fc5f548a30 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -38,9 +38,9 @@ Fill* Fill::new_from_type(const InfillPattern type) case ipArchimedeanChords: return new FillArchimedeanChords(); case ipHilbertCurve: return new FillHilbertCurve(); case ipOctagramSpiral: return new FillOctagramSpiral(); - case ipAdaptiveCubic: return new FillAdaptive(); - case ipSupportCubic: return new FillSupportCubic(); - default: throw std::invalid_argument("unknown type"); + case ipAdaptiveCubic: return new FillAdaptive::Filler(); + case ipSupportCubic: return new FillAdaptive::Filler(); + default: throw Slic3r::InvalidArgument("unknown type"); } } @@ -847,8 +847,9 @@ void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_ boundary.assign(boundary_src.holes.size() + 1, Points()); boundary_data.assign(boundary_src.holes.size() + 1, std::vector()); // Mapping the infill_ordered end point to a (contour, point) of boundary. - std::vector> map_infill_end_point_to_boundary; - map_infill_end_point_to_boundary.assign(infill_ordered.size() * 2, std::pair(std::numeric_limits::max(), std::numeric_limits::max())); + std::vector> map_infill_end_point_to_boundary; + static constexpr auto boundary_idx_unconnected = std::numeric_limits::max(); + map_infill_end_point_to_boundary.assign(infill_ordered.size() * 2, std::pair(boundary_idx_unconnected, boundary_idx_unconnected)); { // Project the infill_ordered end points onto boundary_src. std::vector> intersection_points; @@ -898,13 +899,14 @@ void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_ contour_data.front().param = contour_data.back().param + (contour_dst.back().cast() - contour_dst.front().cast()).norm(); } -#ifndef NDEBUG assert(boundary.size() == boundary_src.num_contours()); - assert(std::all_of(map_infill_end_point_to_boundary.begin(), map_infill_end_point_to_boundary.end(), +#if 0 + // Adaptive Cubic Infill produces infill lines, which not always end at the outer boundary. + assert(std::all_of(map_infill_end_point_to_boundary.begin(), map_infill_end_point_to_boundary.end(), [&boundary](const std::pair &contour_point) { return contour_point.first < boundary.size() && contour_point.second < boundary[contour_point.first].size(); })); -#endif /* NDEBUG */ +#endif } // Mark the points and segments of split boundary as consumed if they are very close to some of the infill line. @@ -935,9 +937,9 @@ void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_ const Polyline &pl2 = infill_ordered[idx_chain]; const std::pair *cp1 = &map_infill_end_point_to_boundary[(idx_chain - 1) * 2 + 1]; const std::pair *cp2 = &map_infill_end_point_to_boundary[idx_chain * 2]; - const std::vector &contour_data = boundary_data[cp1->first]; - if (cp1->first == cp2->first) { + if (cp1->first != boundary_idx_unconnected && cp1->first == cp2->first) { // End points on the same contour. Try to connect them. + const std::vector &contour_data = boundary_data[cp1->first]; float param_lo = (cp1->second == 0) ? 0.f : contour_data[cp1->second].param; float param_hi = (cp2->second == 0) ? 0.f : contour_data[cp2->second].param; float param_end = contour_data.front().param; @@ -964,7 +966,7 @@ void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_ const std::pair *cp1prev = cp1 - 1; const std::pair *cp2 = &map_infill_end_point_to_boundary[(connection_cost.idx_first + 1) * 2]; const std::pair *cp2next = cp2 + 1; - assert(cp1->first == cp2->first); + assert(cp1->first == cp2->first && cp1->first != boundary_idx_unconnected); std::vector &contour_data = boundary_data[cp1->first]; if (connection_cost.reversed) std::swap(cp1, cp2); diff --git a/src/libslic3r/Fill/FillBase.hpp b/src/libslic3r/Fill/FillBase.hpp index dd887b8c3e..0779117ebc 100644 --- a/src/libslic3r/Fill/FillBase.hpp +++ b/src/libslic3r/Fill/FillBase.hpp @@ -11,6 +11,7 @@ #include "../libslic3r.h" #include "../BoundingBox.hpp" +#include "../Exception.hpp" #include "../Utils.hpp" namespace Slic3r { @@ -19,13 +20,14 @@ class ExPolygon; class Surface; enum InfillPattern : int; -namespace FillAdaptive_Internal { +namespace FillAdaptive { struct Octree; }; -class InfillFailedException : public std::runtime_error { +// Infill shall never fail, therefore the error is classified as RuntimeError, not SlicingError. +class InfillFailedException : public Slic3r::RuntimeError { public: - InfillFailedException() : std::runtime_error("Infill failed") {} + InfillFailedException() : Slic3r::RuntimeError("Infill failed") {} }; struct FillParams @@ -74,9 +76,7 @@ public: BoundingBox bounding_box; // Octree builds on mesh for usage in the adaptive cubic infill - FillAdaptive_Internal::Octree* adapt_fill_octree = nullptr; - // Octree builds on mesh for usage in the support cubic infill - FillAdaptive_Internal::Octree* support_fill_octree = nullptr; + FillAdaptive::Octree* adapt_fill_octree = nullptr; public: virtual ~Fill() {} diff --git a/src/libslic3r/Flow.cpp b/src/libslic3r/Flow.cpp index 1678be999c..e5dcf07310 100644 --- a/src/libslic3r/Flow.cpp +++ b/src/libslic3r/Flow.cpp @@ -53,7 +53,7 @@ static inline FlowRole opt_key_to_flow_role(const std::string &opt_key) else if (opt_key == "support_material_extrusion_width") return frSupportMaterial; else - throw std::runtime_error("opt_key_to_flow_role: invalid argument"); + throw Slic3r::RuntimeError("opt_key_to_flow_role: invalid argument"); }; static inline void throw_on_missing_variable(const std::string &opt_key, const char *dependent_opt_key) @@ -126,7 +126,7 @@ Flow Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent { // we need layer height unless it's a bridge if (height <= 0 && bridge_flow_ratio == 0) - throw std::invalid_argument("Invalid flow height supplied to new_from_config_width()"); + throw Slic3r::InvalidArgument("Invalid flow height supplied to new_from_config_width()"); float w; if (bridge_flow_ratio > 0) { @@ -151,7 +151,7 @@ Flow Flow::new_from_spacing(float spacing, float nozzle_diameter, float height, { // we need layer height unless it's a bridge if (height <= 0 && !bridge) - throw std::invalid_argument("Invalid flow height supplied to new_from_spacing()"); + throw Slic3r::InvalidArgument("Invalid flow height supplied to new_from_spacing()"); // Calculate width from spacing. // For normal extrusons, extrusion width is wider than the spacing due to the rounding and squishing of the extrusions. // For bridge extrusions, the extrusions are placed with a tiny BRIDGE_EXTRA_SPACING gaps between the threads. diff --git a/src/libslic3r/Flow.hpp b/src/libslic3r/Flow.hpp index 7d6e35873d..9e57ce9079 100644 --- a/src/libslic3r/Flow.hpp +++ b/src/libslic3r/Flow.hpp @@ -3,6 +3,7 @@ #include "libslic3r.h" #include "Config.hpp" +#include "Exception.hpp" #include "ExtrusionEntity.hpp" namespace Slic3r { @@ -27,11 +28,11 @@ enum FlowRole { frSupportMaterialInterface, }; -class FlowError : public std::invalid_argument +class FlowError : public Slic3r::InvalidArgument { public: - FlowError(const std::string& what_arg) : invalid_argument(what_arg) {} - FlowError(const char* what_arg) : invalid_argument(what_arg) {} + FlowError(const std::string& what_arg) : Slic3r::InvalidArgument(what_arg) {} + FlowError(const char* what_arg) : Slic3r::InvalidArgument(what_arg) {} }; class FlowErrorNegativeSpacing : public FlowError diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 66dd0049cf..46a6c02af1 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -1,4 +1,5 @@ #include "../libslic3r.h" +#include "../Exception.hpp" #include "../Model.hpp" #include "../Utils.hpp" #include "../GCode.hpp" @@ -123,11 +124,11 @@ const char* INVALID_OBJECT_TYPES[] = "other" }; -class version_error : public std::runtime_error +class version_error : public Slic3r::FileIOError { public: - version_error(const std::string& what_arg) : std::runtime_error(what_arg) {} - version_error(const char* what_arg) : std::runtime_error(what_arg) {} + version_error(const std::string& what_arg) : Slic3r::FileIOError(what_arg) {} + version_error(const char* what_arg) : Slic3r::FileIOError(what_arg) {} }; const char* get_attribute_value_charptr(const char** attributes, unsigned int attributes_size, const char* attribute_key) @@ -607,7 +608,7 @@ namespace Slic3r { { // ensure the zip archive is closed and rethrow the exception close_zip_reader(&archive); - throw std::runtime_error(e.what()); + throw Slic3r::FileIOError(e.what()); } } } @@ -780,7 +781,7 @@ namespace Slic3r { { char error_buf[1024]; ::sprintf(error_buf, "Error (%s) while parsing '%s' at line %d", XML_ErrorString(XML_GetErrorCode(data->parser)), data->stat.m_filename, (int)XML_GetCurrentLineNumber(data->parser)); - throw std::runtime_error(error_buf); + throw Slic3r::FileIOError(error_buf); } return n; @@ -789,7 +790,7 @@ namespace Slic3r { catch (const version_error& e) { // rethrow the exception - throw std::runtime_error(e.what()); + throw Slic3r::FileIOError(e.what()); } catch (std::exception& e) { @@ -2360,9 +2361,9 @@ namespace Slic3r { continue; if (!volume->mesh().repaired) - throw std::runtime_error("store_3mf() requires repair()"); + throw Slic3r::FileIOError("store_3mf() requires repair()"); if (!volume->mesh().has_shared_vertices()) - throw std::runtime_error("store_3mf() requires shared vertices"); + throw Slic3r::FileIOError("store_3mf() requires shared vertices"); volumes_offsets.insert(VolumeToOffsetsMap::value_type(volume, Offsets(vertices_count))).first; diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index af7b9b1b60..1a706afa92 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -7,6 +7,7 @@ #include #include "../libslic3r.h" +#include "../Exception.hpp" #include "../Model.hpp" #include "../GCode.hpp" #include "../PrintConfig.hpp" @@ -923,7 +924,7 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi { char error_buf[1024]; ::sprintf(error_buf, "Error (%s) while parsing '%s' at line %d", XML_ErrorString(XML_GetErrorCode(data->parser)), data->stat.m_filename, (int)XML_GetCurrentLineNumber(data->parser)); - throw std::runtime_error(error_buf); + throw Slic3r::FileIOError(error_buf); } return n; @@ -948,9 +949,9 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi if (check_version && (ctx.m_version > VERSION_AMF_COMPATIBLE)) { // std::string msg = _(L("The selected amf file has been saved with a newer version of " + std::string(SLIC3R_APP_NAME) + " and is not compatible.")); - // throw std::runtime_error(msg.c_str()); + // throw Slic3r::FileIOError(msg.c_str()); const std::string msg = (boost::format(_(L("The selected amf file has been saved with a newer version of %1% and is not compatible."))) % std::string(SLIC3R_APP_NAME)).str(); - throw std::runtime_error(msg); + throw Slic3r::FileIOError(msg); } return true; @@ -994,7 +995,7 @@ bool load_amf_archive(const char* path, DynamicPrintConfig* config, Model* model { // ensure the zip archive is closed and rethrow the exception close_zip_reader(&archive); - throw std::runtime_error(e.what()); + throw Slic3r::FileIOError(e.what()); } break; @@ -1147,9 +1148,9 @@ bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config, for (ModelVolume *volume : object->volumes) { vertices_offsets.push_back(num_vertices); if (! volume->mesh().repaired) - throw std::runtime_error("store_amf() requires repair()"); + throw Slic3r::FileIOError("store_amf() requires repair()"); if (! volume->mesh().has_shared_vertices()) - throw std::runtime_error("store_amf() requires shared vertices"); + throw Slic3r::FileIOError("store_amf() requires shared vertices"); const indexed_triangle_set &its = volume->mesh().its; const Transform3d& matrix = volume->get_matrix(); for (size_t i = 0; i < its.vertices.size(); ++i) { diff --git a/src/libslic3r/Format/PRUS.cpp b/src/libslic3r/Format/PRUS.cpp index d6f87197df..e2c38d9576 100644 --- a/src/libslic3r/Format/PRUS.cpp +++ b/src/libslic3r/Format/PRUS.cpp @@ -147,7 +147,7 @@ static void extract_model_from_archive( } } if (! trafo_set) - throw std::runtime_error(std::string("Archive ") + path + " does not contain a valid entry in scene.xml for " + name); + throw Slic3r::FileIOError(std::string("Archive ") + path + " does not contain a valid entry in scene.xml for " + name); // Extract the STL. StlHeader header; @@ -266,7 +266,7 @@ static void extract_model_from_archive( } if (! mesh_valid) - throw std::runtime_error(std::string("Archive ") + path + " does not contain a valid mesh for " + name); + throw Slic3r::FileIOError(std::string("Archive ") + path + " does not contain a valid mesh for " + name); // Add this mesh to the model. ModelVolume *volume = nullptr; @@ -303,7 +303,7 @@ bool load_prus(const char *path, Model *model) mz_bool res = MZ_FALSE; try { if (!open_zip_reader(&archive, path)) - throw std::runtime_error(std::string("Unable to init zip reader for ") + path); + throw Slic3r::FileIOError(std::string("Unable to init zip reader for ") + path); std::vector scene_xml_data; // For grouping multiple STLs into a single ModelObject for multi-material prints. std::map group_to_model_object; @@ -316,10 +316,10 @@ bool load_prus(const char *path, Model *model) buffer.assign((size_t)stat.m_uncomp_size, 0); res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (char*)buffer.data(), (size_t)stat.m_uncomp_size, 0); if (res == MZ_FALSE) - std::runtime_error(std::string("Error while extracting a file from ") + path); + throw Slic3r::FileIOError(std::string("Error while extracting a file from ") + path); if (strcmp(stat.m_filename, "scene.xml") == 0) { if (! scene_xml_data.empty()) - throw std::runtime_error(std::string("Multiple scene.xml were found in the archive.") + path); + throw Slic3r::FileIOError(std::string("Multiple scene.xml were found in the archive.") + path); scene_xml_data = std::move(buffer); } else if (boost::iends_with(stat.m_filename, ".stl")) { // May throw std::exception diff --git a/src/libslic3r/Format/SL1.cpp b/src/libslic3r/Format/SL1.cpp index ff1af5d8b1..274f84f002 100644 --- a/src/libslic3r/Format/SL1.cpp +++ b/src/libslic3r/Format/SL1.cpp @@ -10,6 +10,7 @@ #include +#include "libslic3r/Exception.hpp" #include "libslic3r/SlicesToTriangleMesh.hpp" #include "libslic3r/MarchingSquares.hpp" #include "libslic3r/ClipperUtils.hpp" @@ -64,7 +65,7 @@ boost::property_tree::ptree read_ini(const mz_zip_archive_file_stat &entry, if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename, buf.data(), buf.size(), 0)) - throw std::runtime_error(zip.get_errorstr()); + throw Slic3r::FileIOError(zip.get_errorstr()); boost::property_tree::ptree tree; std::stringstream ss(buf); @@ -80,7 +81,7 @@ PNGBuffer read_png(const mz_zip_archive_file_stat &entry, if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename, buf.data(), buf.size(), 0)) - throw std::runtime_error(zip.get_errorstr()); + throw Slic3r::FileIOError(zip.get_errorstr()); return {std::move(buf), (name.empty() ? entry.m_filename : name)}; } @@ -94,7 +95,7 @@ ArchiveData extract_sla_archive(const std::string &zipfname, struct Arch: public MZ_Archive { Arch(const std::string &fname) { if (!open_zip_reader(&arch, fname)) - throw std::runtime_error(get_errorstr()); + throw Slic3r::FileIOError(get_errorstr()); } ~Arch() { close_zip_reader(&arch); } @@ -202,7 +203,7 @@ RasterParams get_raster_params(const DynamicPrintConfig &cfg) if (!opt_disp_cols || !opt_disp_rows || !opt_disp_w || !opt_disp_h || !opt_mirror_x || !opt_mirror_y || !opt_orient) - throw std::runtime_error("Invalid SL1 file"); + throw Slic3r::FileIOError("Invalid SL1 file"); RasterParams rstp; @@ -228,7 +229,7 @@ SliceParams get_slice_params(const DynamicPrintConfig &cfg) auto *opt_init_layerh = cfg.option("initial_layer_height"); if (!opt_layerh || !opt_init_layerh) - throw std::runtime_error("Invalid SL1 file"); + throw Slic3r::FileIOError("Invalid SL1 file"); return SliceParams{opt_layerh->getFloat(), opt_init_layerh->getFloat()}; } diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index cfde3a03a7..32e762985a 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1,6 +1,7 @@ #include "libslic3r.h" #include "I18N.hpp" #include "GCode.hpp" +#include "Exception.hpp" #include "ExtrusionEntity.hpp" #include "EdgeGrid.hpp" #include "Geometry.hpp" @@ -175,6 +176,7 @@ namespace Slic3r { return islands; } + std::string OozePrevention::pre_toolchange(GCode& gcodegen) { std::string gcode; @@ -286,20 +288,25 @@ namespace Slic3r { std::string WipeTowerIntegration::append_tcr(GCode& gcodegen, const WipeTower::ToolChangeResult& tcr, int new_extruder_id, double z) const { if (new_extruder_id != -1 && new_extruder_id != tcr.new_tool) - throw std::invalid_argument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect."); + throw Slic3r::InvalidArgument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect."); std::string gcode; // Toolchangeresult.gcode assumes the wipe tower corner is at the origin (except for priming lines) // We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position float alpha = m_wipe_tower_rotation / 180.f * float(M_PI); + + auto transform_wt_pt = [&alpha, this](const Vec2f& pt) -> Vec2f { + Vec2f out = Eigen::Rotation2Df(alpha) * pt; + out += m_wipe_tower_pos; + return out; + }; + Vec2f start_pos = tcr.start_pos; Vec2f end_pos = tcr.end_pos; - if (!tcr.priming) { - start_pos = Eigen::Rotation2Df(alpha) * start_pos; - start_pos += m_wipe_tower_pos; - end_pos = Eigen::Rotation2Df(alpha) * end_pos; - end_pos += m_wipe_tower_pos; + if (! tcr.priming) { + start_pos = transform_wt_pt(start_pos); + end_pos = transform_wt_pt(end_pos); } Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : m_wipe_tower_pos; @@ -307,7 +314,7 @@ namespace Slic3r { std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation); - if (!tcr.priming) { + if (! tcr.priming) { // Move over the wipe tower. // Retract for a tool change, using the toolchange retract value and setting the priming extra length. gcode += gcodegen.retract(true); @@ -322,7 +329,7 @@ namespace Slic3r { double current_z = gcodegen.writer().get_position().z(); if (z == -1.) // in case no specific z was provided, print at current_z pos z = current_z; - if (!is_approx(z, current_z)) { + if (! is_approx(z, current_z)) { gcode += gcodegen.writer().retract(); gcode += gcodegen.writer().travel_to_z(z, "Travel down to the last wipe tower layer."); gcode += gcodegen.writer().unretract(); @@ -344,27 +351,25 @@ namespace Slic3r { // Process the custom toolchange_gcode. If it is empty, provide a simple Tn command to change the filament. // Otherwise, leave control to the user completely. std::string toolchange_gcode_str; - if (true /*gcodegen.writer().extruder() != nullptr*/) { - const std::string& toolchange_gcode = gcodegen.config().toolchange_gcode.value; - if (!toolchange_gcode.empty()) { - DynamicConfig config; - int previous_extruder_id = gcodegen.writer().extruder() ? (int)gcodegen.writer().extruder()->id() : -1; - config.set_key_value("previous_extruder", new ConfigOptionInt(previous_extruder_id)); - config.set_key_value("next_extruder", new ConfigOptionInt((int)new_extruder_id)); - config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index)); - config.set_key_value("layer_z", new ConfigOptionFloat(tcr.print_z)); - toolchange_gcode_str = gcodegen.placeholder_parser_process("toolchange_gcode", toolchange_gcode, new_extruder_id, &config); - check_add_eol(toolchange_gcode_str); - } + const std::string& toolchange_gcode = gcodegen.config().toolchange_gcode.value; + if (! toolchange_gcode.empty()) { + DynamicConfig config; + int previous_extruder_id = gcodegen.writer().extruder() ? (int)gcodegen.writer().extruder()->id() : -1; + config.set_key_value("previous_extruder", new ConfigOptionInt(previous_extruder_id)); + config.set_key_value("next_extruder", new ConfigOptionInt((int)new_extruder_id)); + config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index)); + config.set_key_value("layer_z", new ConfigOptionFloat(tcr.print_z)); + toolchange_gcode_str = gcodegen.placeholder_parser_process("toolchange_gcode", toolchange_gcode, new_extruder_id, &config); + check_add_eol(toolchange_gcode_str); + } - std::string toolchange_command; - if (tcr.priming || (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id))) - toolchange_command = gcodegen.writer().toolchange(new_extruder_id); - if (!custom_gcode_changes_tool(toolchange_gcode_str, gcodegen.writer().toolchange_prefix(), new_extruder_id)) - toolchange_gcode_str += toolchange_command; - else { - // We have informed the m_writer about the current extruder_id, we can ignore the generated G-code. - } + std::string toolchange_command; + if (tcr.priming || (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id))) + toolchange_command = gcodegen.writer().toolchange(new_extruder_id); + if (!custom_gcode_changes_tool(toolchange_gcode_str, gcodegen.writer().toolchange_prefix(), new_extruder_id)) + toolchange_gcode_str += toolchange_command; + else { + // We have informed the m_writer about the current extruder_id, we can ignore the generated G-code. } gcodegen.placeholder_parser().set("current_extruder", new_extruder_id); @@ -402,15 +407,9 @@ namespace Slic3r { else { // Prepare a future wipe. - gcodegen.m_wipe.path.points.clear(); - if (new_extruder_id >= 0) { - // Start the wipe at the current position. - gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, end_pos)); - // Wipe end point: Wipe direction away from the closer tower edge to the further tower edge. - gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, - Vec2f((std::abs(m_left - end_pos.x()) < std::abs(m_right - end_pos.x())) ? m_right : m_left, - end_pos.y()))); - } + gcodegen.m_wipe.reset_path(); + for (const Vec2f& wipe_pt : tcr.wipe_path) + gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, transform_wt_pt(wipe_pt))); } // Let the planner know we are traveling between objects. @@ -498,37 +497,11 @@ namespace Slic3r { assert(m_layer_idx == 0); std::string gcode; - - // Disable linear advance for the wipe tower operations. - //gcode += (gcodegen.config().gcode_flavor == gcfRepRap ? std::string("M572 D0 S0\n") : std::string("M900 K0\n")); - for (const WipeTower::ToolChangeResult& tcr : m_priming) { - if (!tcr.extrusions.empty()) + if (! tcr.extrusions.empty()) gcode += append_tcr(gcodegen, tcr, tcr.new_tool); - - - // Let the tool change be executed by the wipe tower class. - // Inform the G-code writer about the changes done behind its back. - //gcode += tcr.gcode; - // Let the m_writer know the current extruder_id, but ignore the generated G-code. - // unsigned int current_extruder_id = tcr.extrusions.back().tool; - // gcodegen.writer().toolchange(current_extruder_id); - // gcodegen.placeholder_parser().set("current_extruder", current_extruder_id); - } - // A phony move to the end position at the wipe tower. - /* gcodegen.writer().travel_to_xy(Vec2d(m_priming.back().end_pos.x, m_priming.back().end_pos.y)); - gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, m_priming.back().end_pos)); - // Prepare a future wipe. - gcodegen.m_wipe.path.points.clear(); - // Start the wipe at the current position. - gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, m_priming.back().end_pos)); - // Wipe end point: Wipe direction away from the closer tower edge to the further tower edge. - gcodegen.m_wipe.path.points.emplace_back(wipe_tower_point_to_object_point(gcodegen, - WipeTower::xy((std::abs(m_left - m_priming.back().end_pos.x) < std::abs(m_right - m_priming.back().end_pos.x)) ? m_right : m_left, - m_priming.back().end_pos.y)));*/ - return gcode; } @@ -539,7 +512,7 @@ namespace Slic3r { if (!m_brim_done || gcodegen.writer().need_toolchange(extruder_id) || finish_layer) { if (m_layer_idx < (int)m_tool_changes.size()) { if (!(size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size())) - throw std::runtime_error("Wipe tower generation failed, possibly due to empty first layer."); + throw Slic3r::RuntimeError("Wipe tower generation failed, possibly due to empty first layer."); // Calculate where the wipe tower layer will be printed. -1 means that print z will not change, @@ -628,7 +601,7 @@ std::vector GCode::collect_layers_to_print(const PrintObjec // Check that there are extrusions on the very first layer. if (layers_to_print.size() == 1u) { if (!has_extrusions) - throw std::runtime_error(_(L("There is an object with no extrusions on the first layer."))); + throw Slic3r::RuntimeError(_(L("There is an object with no extrusions on the first layer."))); } // In case there are extrusions on this layer, check there is a layer to lay it on. @@ -662,7 +635,7 @@ std::vector GCode::collect_layers_to_print(const PrintObjec return layers_to_print; } -// Prepare for non-sequential printing of multiple objects: Support resp. object layers with nearly identical print_z +// Prepare for non-sequential printing of multiple objects: Support resp. object layers with nearly identical print_z // will be printed for all objects at once. // Return a list of items. std::vector>> GCode::collect_layers_to_print(const Print& print) @@ -720,9 +693,9 @@ namespace DoExport { static void update_print_estimated_times_stats(const GCodeProcessor& processor, PrintStatistics& print_statistics) { const GCodeProcessor::Result& result = processor.get_result(); - print_statistics.estimated_normal_print_time = get_time_dhm(result.time_statistics.modes[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Normal)].time); + print_statistics.estimated_normal_print_time = get_time_dhms(result.time_statistics.modes[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Normal)].time); print_statistics.estimated_silent_print_time = processor.is_stealth_time_estimator_enabled() ? - get_time_dhm(result.time_statistics.modes[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].time) : "N/A"; + get_time_dhms(result.time_statistics.modes[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].time) : "N/A"; } } // namespace DoExport @@ -749,7 +722,7 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_ FILE *file = boost::nowide::fopen(path_tmp.c_str(), "wb"); if (file == nullptr) - throw std::runtime_error(std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n"); + throw Slic3r::RuntimeError(std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n"); #if !ENABLE_GCODE_VIEWER m_enable_analyzer = preview_data != nullptr; @@ -762,7 +735,7 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_ if (ferror(file)) { fclose(file); boost::nowide::remove(path_tmp.c_str()); - throw std::runtime_error(std::string("G-code export to ") + path + " failed\nIs the disk full?\n"); + throw Slic3r::RuntimeError(std::string("G-code export to ") + path + " failed\nIs the disk full?\n"); } } catch (std::exception & /* ex */) { // Rethrow on any exception. std::runtime_exception and CanceledException are expected to be thrown. @@ -783,14 +756,16 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_ msg += " !!!!! Failed to process the custom G-code template ...\n"; msg += "and\n"; msg += " !!!!! End of an error report for the custom G-code template ...\n"; - throw std::runtime_error(msg); + throw Slic3r::RuntimeError(msg); } #if ENABLE_GCODE_VIEWER + BOOST_LOG_TRIVIAL(debug) << "Start processing gcode, " << log_memory_info(); m_processor.process_file(path_tmp, [print]() { print->throw_if_canceled(); }); DoExport::update_print_estimated_times_stats(m_processor, print->m_print_statistics); if (result != nullptr) *result = std::move(m_processor.extract_result()); + BOOST_LOG_TRIVIAL(debug) << "Finished processing gcode, " << log_memory_info(); #else GCodeTimeEstimator::PostProcessData normal_data = m_normal_time_estimator.get_post_process_data(); GCodeTimeEstimator::PostProcessData silent_data = m_silent_time_estimator.get_post_process_data(); @@ -815,12 +790,12 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_ #endif // ENABLE_GCODE_VIEWER if (rename_file(path_tmp, path)) - throw std::runtime_error( + throw Slic3r::RuntimeError( std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + path + '\n' + "Is " + path_tmp + " locked?" + '\n'); BOOST_LOG_TRIVIAL(info) << "Exporting G-code finished" << log_memory_info(); - print->set_done(psGCodeExport); + print->set_done(psGCodeExport); // Write the profiler measurements to file PROFILE_UPDATE(); @@ -983,7 +958,8 @@ namespace DoExport { return volumetric_speed; } - static void init_ooze_prevention(const Print &print, OozePrevention &ooze_prevention) + + static void init_ooze_prevention(const Print &print, OozePrevention &ooze_prevention) { // Calculate wiping points if needed if (print.config().ooze_prevention.value && ! print.config().single_extruder_multi_material) { @@ -1123,26 +1099,26 @@ namespace DoExport { } filament_stats_string_out += out_filament_used_mm.first; filament_stats_string_out += "\n" + out_filament_used_cm3.first; - if (out_filament_used_g.second) + if (out_filament_used_g.second) filament_stats_string_out += "\n" + out_filament_used_g.first; - if (out_filament_cost.second) + if (out_filament_cost.second) filament_stats_string_out += "\n" + out_filament_cost.first; - } - return filament_stats_string_out; - } + } + return filament_stats_string_out; + } } // Sort the PrintObjects by their increasing Z, likely useful for avoiding colisions on Deltas during sequential prints. static inline std::vector sort_object_instances_by_max_z(const Print &print) { std::vector objects(print.objects().begin(), print.objects().end()); - std::sort(objects.begin(), objects.end(), [](const PrintObject *po1, const PrintObject *po2) { return po1->height() < po2->height(); }); - std::vector instances; - instances.reserve(objects.size()); - for (const PrintObject *object : objects) - for (size_t i = 0; i < object->instances().size(); ++ i) - instances.emplace_back(&object->instances()[i]); - return instances; + std::sort(objects.begin(), objects.end(), [](const PrintObject *po1, const PrintObject *po2) { return po1->height() < po2->height(); }); + std::vector instances; + instances.reserve(objects.size()); + for (const PrintObject *object : objects) + for (size_t i = 0; i < object->instances().size(); ++ i) + instances.emplace_back(&object->instances()[i]); + return instances; } // Produce a vector of PrintObjects in the order of their respective ModelObjects in print.model(). @@ -1246,8 +1222,8 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // Write information on the generator. _write_format(file, "; %s\n\n", Slic3r::header_slic3r_generated().c_str()); - DoExport::export_thumbnails_to_file(thumbnail_cb, print.full_print_config().option("thumbnails")->values, - [this, file](const char* sz) { this->_write(file, sz); }, + DoExport::export_thumbnails_to_file(thumbnail_cb, print.full_print_config().option("thumbnails")->values, + [this, file](const char* sz) { this->_write(file, sz); }, [&print]() { print.throw_if_canceled(); }); // Write notes (content of the Print Settings tab -> Notes) @@ -1282,7 +1258,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu _write_format(file, "\n"); } print.throw_if_canceled(); - + // adds tags for time estimators #if ENABLE_GCODE_VIEWER if (print.config().remaining_times.value) @@ -1321,12 +1297,12 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu } // We don't allow switching of extruders per layer by Model::custom_gcode_per_print_z in sequential mode. // Use the extruder IDs collected from Regions. - this->set_extruders(print.extruders()); + this->set_extruders(print.extruders()); } else { - // Find tool ordering for all the objects at once, and the initial extruder ID. + // Find tool ordering for all the objects at once, and the initial extruder ID. // If the tool ordering has been pre-calculated by Print class for wipe tower already, reuse it. - tool_ordering = print.tool_ordering(); - tool_ordering.assign_custom_gcodes(print); + tool_ordering = print.tool_ordering(); + tool_ordering.assign_custom_gcodes(print); has_wipe_tower = print.has_wipe_tower() && tool_ordering.has_wipe_tower(); initial_extruder_id = (has_wipe_tower && ! print.config().single_extruder_multi_material_priming) ? // The priming towers will be skipped. @@ -1335,7 +1311,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu tool_ordering.first_extruder(); // In non-sequential print, the printing extruders may have been modified by the extruder switches stored in Model::custom_gcode_per_print_z. // Therefore initialize the printing extruders from there. - this->set_extruders(tool_ordering.all_extruders()); + this->set_extruders(tool_ordering.all_extruders()); // Order object instances using a nearest neighbor search. print_object_instances_ordering = chain_print_object_instances(print); } @@ -1435,7 +1411,10 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // Calculate wiping points if needed DoExport::init_ooze_prevention(print, m_ooze_prevention); print.throw_if_canceled(); - + + // Collect custom seam data from all objects. + m_seam_placer.init(print); + if (! (has_wipe_tower && print.config().single_extruder_multi_material_priming)) { // Set initial extruder only after custom start G-code. // Ugly hack: Do not set the initial extruder if the extruder is primed using the MMU priming towers at the edge of the print bed. @@ -1510,7 +1489,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu m_wipe_tower.reset(new WipeTowerIntegration(print.config(), *print.wipe_tower_data().priming.get(), print.wipe_tower_data().tool_changes, *print.wipe_tower_data().final_purge.get())); _write(file, m_writer.travel_to_z(first_layer_height + m_config.z_offset.value, "Move to the first layer height")); if (print.config().single_extruder_multi_material_priming) { - _write(file, m_wipe_tower->prime(*this)); + _write(file, m_wipe_tower->prime(*this)); // Verify, whether the print overaps the priming extrusions. BoundingBoxf bbox_print(get_print_extrusions_extents(print)); coordf_t twolayers_printz = ((layers_to_print.size() == 1) ? layers_to_print.front() : layers_to_print[1]).first + EPSILON; @@ -1577,7 +1556,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu _writeln(file, this->placeholder_parser_process("end_filament_gcode", print.config().end_filament_gcode.get_at(extruder_id), extruder_id, &config)); } else { for (const std::string &end_gcode : print.config().end_filament_gcode.values) { - int extruder_id = (unsigned int)(&end_gcode - &print.config().end_filament_gcode.values.front()); + int extruder_id = (unsigned int)(&end_gcode - &print.config().end_filament_gcode.values.front()); config.set_key_value("filament_extruder_id", new ConfigOptionInt(extruder_id)); _writeln(file, this->placeholder_parser_process("end_filament_gcode", end_gcode, extruder_id, &config)); } @@ -1651,8 +1630,8 @@ std::string GCode::placeholder_parser_process(const std::string &name, const std m_placeholder_parser_failed_templates.insert(name); // Insert the macro error message into the G-code. return - std::string("\n!!!!! Failed to process the custom G-code template ") + name + "\n" + - err.what() + + std::string("\n!!!!! Failed to process the custom G-code template ") + name + "\n" + + err.what() + "!!!!! End of an error report for the custom G-code template " + name + "\n\n"; } } @@ -1678,12 +1657,12 @@ static bool custom_gcode_sets_temperature(const std::string &gcode, const int mc int mcode = int(strtol(ptr, &endptr, 10)); if (endptr != nullptr && endptr != ptr && (mcode == mcode_set_temp_dont_wait || mcode == mcode_set_temp_and_wait)) { // M104/M109 or M140/M190 found. - ptr = endptr; + ptr = endptr; // Let the caller know that the custom G-code sets the temperature. temp_set_by_gcode = true; // Now try to parse the temperature value. - // While not at the end of the line: - while (strchr(";\r\n\0", *ptr) == nullptr) { + // While not at the end of the line: + while (strchr(";\r\n\0", *ptr) == nullptr) { // Skip whitespaces. for (; *ptr == ' ' || *ptr == '\t'; ++ ptr); if (*ptr == 'S') { @@ -1692,22 +1671,22 @@ static bool custom_gcode_sets_temperature(const std::string &gcode, const int mc // Parse an int. endptr = nullptr; long temp_parsed = strtol(ptr, &endptr, 10); - if (endptr > ptr) { - ptr = endptr; - temp_out = temp_parsed; - } + if (endptr > ptr) { + ptr = endptr; + temp_out = temp_parsed; + } } else { // Skip this word. - for (; strchr(" \t;\r\n\0", *ptr) == nullptr; ++ ptr); + for (; strchr(" \t;\r\n\0", *ptr) == nullptr; ++ ptr); } } } } // Skip the rest of the line. for (; *ptr != 0 && *ptr != '\r' && *ptr != '\n'; ++ ptr); - // Skip the end of line indicators. + // Skip the end of line indicators. for (; *ptr == '\r' || *ptr == '\n'; ++ ptr); - } + } return temp_set_by_gcode; } @@ -1796,9 +1775,9 @@ void GCode::_print_first_layer_extruder_temperatures(FILE *file, Print &print, c } inline GCode::ObjectByExtruder& object_by_extruder( - std::map> &by_extruder, - unsigned int extruder_id, - size_t object_idx, + std::map> &by_extruder, + unsigned int extruder_id, + size_t object_idx, size_t num_objects) { std::vector &objects_by_extruder = by_extruder[extruder_id]; @@ -1808,9 +1787,9 @@ inline GCode::ObjectByExtruder& object_by_extruder( } inline std::vector& object_islands_by_extruder( - std::map> &by_extruder, - unsigned int extruder_id, - size_t object_idx, + std::map> &by_extruder, + unsigned int extruder_id, + size_t object_idx, size_t num_objects, size_t num_islands) { @@ -1821,82 +1800,82 @@ inline std::vector& object_islands_by_extruder( } std::vector GCode::sort_print_object_instances( - std::vector &objects_by_extruder, - const std::vector &layers, - // Ordering must be defined for normal (non-sequential print). - const std::vector *ordering, - // For sequential print, the instance of the object to be printing has to be defined. - const size_t single_object_instance_idx) + std::vector &objects_by_extruder, + const std::vector &layers, + // Ordering must be defined for normal (non-sequential print). + const std::vector *ordering, + // For sequential print, the instance of the object to be printing has to be defined. + const size_t single_object_instance_idx) { std::vector out; if (ordering == nullptr) { - // Sequential print, single object is being printed. - for (ObjectByExtruder &object_by_extruder : objects_by_extruder) { - const size_t layer_id = &object_by_extruder - objects_by_extruder.data(); - const PrintObject *print_object = layers[layer_id].object(); - if (print_object) - out.emplace_back(object_by_extruder, layer_id, *print_object, single_object_instance_idx); - } + // Sequential print, single object is being printed. + for (ObjectByExtruder &object_by_extruder : objects_by_extruder) { + const size_t layer_id = &object_by_extruder - objects_by_extruder.data(); + const PrintObject *print_object = layers[layer_id].object(); + if (print_object) + out.emplace_back(object_by_extruder, layer_id, *print_object, single_object_instance_idx); + } } else { - // Create mapping from PrintObject* to ObjectByExtruder*. - std::vector> sorted; - sorted.reserve(objects_by_extruder.size()); - for (ObjectByExtruder &object_by_extruder : objects_by_extruder) { - const size_t layer_id = &object_by_extruder - objects_by_extruder.data(); - const PrintObject *print_object = layers[layer_id].object(); - if (print_object) - sorted.emplace_back(print_object, &object_by_extruder); - } - std::sort(sorted.begin(), sorted.end()); + // Create mapping from PrintObject* to ObjectByExtruder*. + std::vector> sorted; + sorted.reserve(objects_by_extruder.size()); + for (ObjectByExtruder &object_by_extruder : objects_by_extruder) { + const size_t layer_id = &object_by_extruder - objects_by_extruder.data(); + const PrintObject *print_object = layers[layer_id].object(); + if (print_object) + sorted.emplace_back(print_object, &object_by_extruder); + } + std::sort(sorted.begin(), sorted.end()); - if (! sorted.empty()) { - out.reserve(sorted.size()); - for (const PrintInstance *instance : *ordering) { - const PrintObject &print_object = *instance->print_object; - std::pair key(&print_object, nullptr); - auto it = std::lower_bound(sorted.begin(), sorted.end(), key); - if (it != sorted.end() && it->first == &print_object) - // ObjectByExtruder for this PrintObject was found. - out.emplace_back(*it->second, it->second - objects_by_extruder.data(), print_object, instance - print_object.instances().data()); - } - } - } - return out; + if (! sorted.empty()) { + out.reserve(sorted.size()); + for (const PrintInstance *instance : *ordering) { + const PrintObject &print_object = *instance->print_object; + std::pair key(&print_object, nullptr); + auto it = std::lower_bound(sorted.begin(), sorted.end(), key); + if (it != sorted.end() && it->first == &print_object) + // ObjectByExtruder for this PrintObject was found. + out.emplace_back(*it->second, it->second - objects_by_extruder.data(), print_object, instance - print_object.instances().data()); + } + } + } + return out; } namespace ProcessLayer { static std::string emit_custom_gcode_per_print_z( - const CustomGCode::Item *custom_gcode, + const CustomGCode::Item *custom_gcode, // ID of the first extruder printing this layer. unsigned int first_extruder_id, const PrintConfig &config) - { + { std::string gcode; bool single_extruder_printer = config.nozzle_diameter.size() == 1; - + if (custom_gcode != nullptr) { - // Extruder switches are processed by LayerTools, they should be filtered out. - assert(custom_gcode->type != CustomGCode::ToolChange); + // Extruder switches are processed by LayerTools, they should be filtered out. + assert(custom_gcode->type != CustomGCode::ToolChange); CustomGCode::Type gcode_type = custom_gcode->type; bool color_change = gcode_type == CustomGCode::ColorChange; bool tool_change = gcode_type == CustomGCode::ToolChange; - // Tool Change is applied as Color Change for a single extruder printer only. - assert(! tool_change || single_extruder_printer); + // Tool Change is applied as Color Change for a single extruder printer only. + assert(! tool_change || single_extruder_printer); - std::string pause_print_msg; - int m600_extruder_before_layer = -1; - if (color_change && custom_gcode->extruder > 0) - m600_extruder_before_layer = custom_gcode->extruder - 1; - else if (gcode_type == CustomGCode::PausePrint) - pause_print_msg = custom_gcode->extra; + std::string pause_print_msg; + int m600_extruder_before_layer = -1; + if (color_change && custom_gcode->extruder > 0) + m600_extruder_before_layer = custom_gcode->extruder - 1; + else if (gcode_type == CustomGCode::PausePrint) + pause_print_msg = custom_gcode->extra; - // we should add or not colorprint_change in respect to nozzle_diameter count instead of really used extruders count - if (color_change || tool_change) - { + // we should add or not colorprint_change in respect to nozzle_diameter count instead of really used extruders count + if (color_change || tool_change) + { assert(m600_extruder_before_layer >= 0); // Color Change or Tool Change as Color Change. #if ENABLE_GCODE_VIEWER @@ -1910,13 +1889,13 @@ namespace ProcessLayer #endif // ENABLE_GCODE_VIEWER if (!single_extruder_printer && m600_extruder_before_layer >= 0 && first_extruder_id != (unsigned)m600_extruder_before_layer - // && !MMU1 - ) { - //! FIXME_in_fw show message during print pause - gcode += config.pause_print_gcode;// pause print + // && !MMU1 + ) { + //! FIXME_in_fw show message during print pause + gcode += config.pause_print_gcode;// pause print gcode += "\n"; - gcode += "M117 Change filament for Extruder " + std::to_string(m600_extruder_before_layer) + "\n"; - } + gcode += "M117 Change filament for Extruder " + std::to_string(m600_extruder_before_layer) + "\n"; + } else { gcode += config.color_change_gcode;//ColorChangeCode; gcode += "\n"; @@ -1956,32 +1935,32 @@ namespace ProcessLayer if (gcode_type == CustomGCode::Template) // Template Cistom Gcode gcode += config.template_custom_gcode; else // custom Gcode - gcode += custom_gcode->extra; + gcode += custom_gcode->extra; - } - gcode += "\n"; - } - } + } + gcode += "\n"; + } + } - return gcode; - } + return gcode; + } } // namespace ProcessLayer namespace Skirt { - static void skirt_loops_per_extruder_all_printing(const Print &print, const LayerTools &layer_tools, std::map> &skirt_loops_per_extruder_out) - { + static void skirt_loops_per_extruder_all_printing(const Print &print, const LayerTools &layer_tools, std::map> &skirt_loops_per_extruder_out) + { // Prime all extruders printing over the 1st layer over the skirt lines. size_t n_loops = print.skirt().entities.size(); size_t n_tools = layer_tools.extruders.size(); size_t lines_per_extruder = (n_loops + n_tools - 1) / n_tools; for (size_t i = 0; i < n_loops; i += lines_per_extruder) skirt_loops_per_extruder_out[layer_tools.extruders[i / lines_per_extruder]] = std::pair(i, std::min(i + lines_per_extruder, n_loops)); - } + } static std::map> make_skirt_loops_per_extruder_1st_layer( const Print &print, - const std::vector & /*layers */, - const LayerTools &layer_tools, + const std::vector & /*layers */, + const LayerTools &layer_tools, // Heights (print_z) at which the skirt has already been extruded. std::vector &skirt_done) { @@ -1989,7 +1968,7 @@ namespace Skirt { // not at the print_z of the interlaced support material layers. std::map> skirt_loops_per_extruder_out; if (skirt_done.empty() && print.has_skirt() && ! print.skirt().entities.empty()) { - skirt_loops_per_extruder_all_printing(print, layer_tools, skirt_loops_per_extruder_out); + skirt_loops_per_extruder_all_printing(print, layer_tools, skirt_loops_per_extruder_out); skirt_done.emplace_back(layer_tools.print_z); } return skirt_loops_per_extruder_out; @@ -1997,11 +1976,11 @@ namespace Skirt { static std::map> make_skirt_loops_per_extruder_other_layers( const Print &print, - const std::vector &layers, - const LayerTools &layer_tools, - // First non-empty support layer. - const SupportLayer *support_layer, - // Heights (print_z) at which the skirt has already been extruded. + const std::vector &layers, + const LayerTools &layer_tools, + // First non-empty support layer. + const SupportLayer *support_layer, + // Heights (print_z) at which the skirt has already been extruded. std::vector &skirt_done) { // Extrude skirt at the print_z of the raft layers and normal object layers @@ -2019,7 +1998,7 @@ namespace Skirt { // Prime just the first printing extruder. This is original Slic3r's implementation. skirt_loops_per_extruder_out[layer_tools.extruders.front()] = std::pair(0, print.config().skirts.value); #else - // Prime all extruders planned for this layer, see + // Prime all extruders planned for this layer, see // https://github.com/prusa3d/PrusaSlicer/issues/469#issuecomment-322450619 skirt_loops_per_extruder_all_printing(print, layer_tools, skirt_loops_per_extruder_out); #endif @@ -2031,7 +2010,7 @@ namespace Skirt { } // namespace Skirt -// In sequential mode, process_layer is called once per each object and its copy, +// In sequential mode, process_layer is called once per each object and its copy, // therefore layers will contain a single entry and single_object_instance_idx will point to the copy of the object. // In non-sequential mode, process_layer is called per each print_z height with all object and support layers accumulated. // For multi-material prints, this routine minimizes extruder switches by gathering extruder specific extrusion paths @@ -2043,8 +2022,8 @@ void GCode::process_layer( // Set of object & print layers of the same PrintObject and with the same print_z. const std::vector &layers, const LayerTools &layer_tools, - // Pairs of PrintObject index and its instance index. - const std::vector *ordering, + // Pairs of PrintObject index and its instance index. + const std::vector *ordering, // If set to size_t(-1), then print all copies of all objects. // Otherwise print a single copy of a single object. const size_t single_object_instance_idx) @@ -2091,7 +2070,7 @@ void GCode::process_layer( } // If we're going to apply spiralvase to this layer, disable loop clipping m_enable_loop_clipping = ! m_spiral_vase || ! m_spiral_vase->enable; - + std::string gcode; #if ENABLE_GCODE_VIEWER @@ -2120,7 +2099,7 @@ void GCode::process_layer( + "\n"; } gcode += this->change_layer(print_z); // this will increase m_layer_index - m_layer = &layer; + m_layer = &layer; if (! print.config().layer_gcode.value.empty()) { DynamicConfig config; config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); @@ -2212,8 +2191,8 @@ void GCode::process_layer( } if (layer_to_print.object_layer != nullptr) { const Layer &layer = *layer_to_print.object_layer; - // We now define a strategy for building perimeters and fills. The separation - // between regions doesn't matter in terms of printing order, as we follow + // We now define a strategy for building perimeters and fills. The separation + // between regions doesn't matter in terms of printing order, as we follow // another logic instead: // - we group all extrusions by extruder so that we minimize toolchanges // - we start from the last used extruder @@ -2228,13 +2207,13 @@ void GCode::process_layer( std::vector slices_test_order; slices_test_order.reserve(n_slices); for (size_t i = 0; i < n_slices; ++ i) - slices_test_order.emplace_back(i); + slices_test_order.emplace_back(i); std::sort(slices_test_order.begin(), slices_test_order.end(), [&layer_surface_bboxes](size_t i, size_t j) { - const Vec2d s1 = layer_surface_bboxes[i].size().cast(); - const Vec2d s2 = layer_surface_bboxes[j].size().cast(); - return s1.x() * s1.y() < s2.x() * s2.y(); + const Vec2d s1 = layer_surface_bboxes[i].size().cast(); + const Vec2d s2 = layer_surface_bboxes[j].size().cast(); + return s1.x() * s1.y() < s2.x() * s2.y(); }); - auto point_inside_surface = [&layer, &layer_surface_bboxes](const size_t i, const Point &point) { + auto point_inside_surface = [&layer, &layer_surface_bboxes](const size_t i, const Point &point) { const BoundingBox &bbox = layer_surface_bboxes[i]; return point(0) >= bbox.min(0) && point(0) < bbox.max(0) && point(1) >= bbox.min(1) && point(1) < bbox.max(1) && @@ -2265,27 +2244,27 @@ void GCode::process_layer( // Let's recover vector of extruder overrides: const WipingExtrusions::ExtruderPerCopy *entity_overrides = nullptr; if (! layer_tools.has_extruder(correct_extruder_id)) { - // this entity is not overridden, but its extruder is not in layer_tools - we'll print it + // this entity is not overridden, but its extruder is not in layer_tools - we'll print it // by last extruder on this layer (could happen e.g. when a wiping object is taller than others - dontcare extruders are eradicated from layer_tools) correct_extruder_id = layer_tools.extruders.back(); } printing_extruders.clear(); if (is_anything_overridden) { - entity_overrides = const_cast(layer_tools).wiping_extrusions().get_extruder_overrides(extrusions, correct_extruder_id, layer_to_print.object()->instances().size()); - if (entity_overrides == nullptr) { - printing_extruders.emplace_back(correct_extruder_id); - } else { - printing_extruders.reserve(entity_overrides->size()); - for (int extruder : *entity_overrides) - printing_extruders.emplace_back(extruder >= 0 ? - // at least one copy is overridden to use this extruder - extruder : - // at least one copy would normally be printed with this extruder (see get_extruder_overrides function for explanation) - static_cast(- extruder - 1)); - Slic3r::sort_remove_duplicates(printing_extruders); - } - } else - printing_extruders.emplace_back(correct_extruder_id); + entity_overrides = const_cast(layer_tools).wiping_extrusions().get_extruder_overrides(extrusions, correct_extruder_id, layer_to_print.object()->instances().size()); + if (entity_overrides == nullptr) { + printing_extruders.emplace_back(correct_extruder_id); + } else { + printing_extruders.reserve(entity_overrides->size()); + for (int extruder : *entity_overrides) + printing_extruders.emplace_back(extruder >= 0 ? + // at least one copy is overridden to use this extruder + extruder : + // at least one copy would normally be printed with this extruder (see get_extruder_overrides function for explanation) + static_cast(- extruder - 1)); + Slic3r::sort_remove_duplicates(printing_extruders); + } + } else + printing_extruders.emplace_back(correct_extruder_id); // Now we must add this extrusion into the by_extruder map, once for each extruder that will print it: for (unsigned int extruder : printing_extruders) @@ -2296,10 +2275,10 @@ void GCode::process_layer( &layer_to_print - layers.data(), layers.size(), n_slices+1); for (size_t i = 0; i <= n_slices; ++ i) { - bool last = i == n_slices; - size_t island_idx = last ? n_slices : slices_test_order[i]; + bool last = i == n_slices; + size_t island_idx = last ? n_slices : slices_test_order[i]; if (// extrusions->first_point does not fit inside any slice - last || + last || // extrusions->first_point fits inside ith slice point_inside_surface(island_idx, extrusions->first_point())) { if (islands[island_idx].by_region.empty()) @@ -2374,10 +2353,10 @@ void GCode::process_layer( if (objects_by_extruder_it == by_extruder.end()) continue; - std::vector instances_to_print = sort_print_object_instances(objects_by_extruder_it->second, layers, ordering, single_object_instance_idx); + std::vector instances_to_print = sort_print_object_instances(objects_by_extruder_it->second, layers, ordering, single_object_instance_idx); // We are almost ready to print. However, we must go through all the objects twice to print the the overridden extrusions first (infill/perimeter wiping feature): - std::vector by_region_per_copy_cache; + std::vector by_region_per_copy_cache; for (int print_wipe_extrusions = is_anything_overridden; print_wipe_extrusions>=0; --print_wipe_extrusions) { if (is_anything_overridden && print_wipe_extrusions == 0) gcode+="; PURGING FINISHED\n"; @@ -2406,7 +2385,7 @@ void GCode::process_layer( } for (ObjectByExtruder::Island &island : instance_to_print.object_by_extruder.islands) { const auto& by_region_specific = is_anything_overridden ? island.by_region_per_copy(by_region_per_copy_cache, static_cast(instance_to_print.instance_id), extruder_id, print_wipe_extrusions != 0) : island.by_region; - //FIXME the following code prints regions in the order they are defined, the path is not optimized in any way. + //FIXME the following code prints regions in the order they are defined, the path is not optimized in any way. if (print.config().infill_first) { gcode += this->extrude_infill(print, by_region_specific, false); gcode += this->extrude_perimeters(print, by_region_specific, lower_layer_edge_grids[instance_to_print.layer_id]); @@ -2418,13 +2397,13 @@ void GCode::process_layer( gcode += this->extrude_infill(print,by_region_specific, true); } if (this->config().gcode_label_objects) - gcode += std::string("; stop printing object ") + instance_to_print.print_object.model_object()->name + " id:" + std::to_string(instance_to_print.layer_id) + " copy " + std::to_string(instance_to_print.instance_id) + "\n"; + gcode += std::string("; stop printing object ") + instance_to_print.print_object.model_object()->name + " id:" + std::to_string(instance_to_print.layer_id) + " copy " + std::to_string(instance_to_print.instance_id) + "\n"; } } } // Apply spiral vase post-processing if this layer contains suitable geometry - // (we must feed all the G-code into the post-processor, including the first + // (we must feed all the G-code into the post-processor, including the first // bottom non-spiral layers otherwise it will mess with positions) // we apply spiral vase at this stage because it requires a full layer. // Just a reminder: A spiral vase mode is allowed for a single object per layer, single material print only. @@ -2450,16 +2429,19 @@ void GCode::process_layer( gcode = m_pressure_equalizer->process(gcode.c_str(), false); // printf("G-code after filter:\n%s\n", out.c_str()); #endif /* HAS_PRESSURE_EQUALIZER */ - + _write(file, gcode); -#if !ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER + BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z << + log_memory_info(); +#else BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z << ", time estimator memory: " << - format_memsize_MB(m_normal_time_estimator.memory_used() + (m_silent_time_estimator_enabled ? m_silent_time_estimator.memory_used() : 0)) << - ", analyzer memory: " << - format_memsize_MB(m_analyzer.memory_used()) << - log_memory_info(); -#endif // !ENABLE_GCODE_VIEWER + format_memsize_MB(m_normal_time_estimator.memory_used() + (m_silent_time_estimator_enabled ? m_silent_time_estimator.memory_used() : 0)) << + ", analyzer memory: " << + format_memsize_MB(m_analyzer.memory_used()) << + log_memory_info(); +#endif // ENABLE_GCODE_VIEWER } void GCode::apply_print_config(const PrintConfig &print_config) @@ -2470,19 +2452,19 @@ void GCode::apply_print_config(const PrintConfig &print_config) void GCode::append_full_config(const Print &print, std::string &str) { - const DynamicPrintConfig &cfg = print.full_print_config(); + const DynamicPrintConfig &cfg = print.full_print_config(); // Sorted list of config keys, which shall not be stored into the G-code. Initializer list. - static constexpr auto banned_keys = { - "compatible_printers"sv, - "compatible_prints"sv, - "print_host"sv, - "printhost_apikey"sv, - "printhost_cafile"sv - }; + static constexpr auto banned_keys = { + "compatible_printers"sv, + "compatible_prints"sv, + "print_host"sv, + "printhost_apikey"sv, + "printhost_cafile"sv + }; assert(std::is_sorted(banned_keys.begin(), banned_keys.end())); - auto is_banned = [](const std::string &key) { - return std::binary_search(banned_keys.begin(), banned_keys.end(), key); - }; + auto is_banned = [](const std::string &key) { + return std::binary_search(banned_keys.begin(), banned_keys.end(), key); + }; for (const std::string &key : cfg.keys()) if (! is_banned(key) && ! cfg.option(key)->is_nil()) str += "; " + key + " = " + cfg.opt_serialize(key) + "\n"; @@ -2491,7 +2473,7 @@ void GCode::append_full_config(const Print &print, std::string &str) void GCode::set_extruders(const std::vector &extruder_ids) { m_writer.set_extruders(extruder_ids); - + // enable wipe path generation if any extruder has wipe enabled m_wipe.enable = false; for (auto id : extruder_ids) @@ -2502,7 +2484,7 @@ void GCode::set_extruders(const std::vector &extruder_ids) } void GCode::set_origin(const Vec2d &pointf) -{ +{ // if origin increases (goes towards right), last_pos decreases because it goes towards left const Point translate( scale_(m_origin(0) - pointf(0)), @@ -2516,13 +2498,13 @@ void GCode::set_origin(const Vec2d &pointf) std::string GCode::preamble() { std::string gcode = m_writer.preamble(); - + /* Perform a *silent* move to z_offset: we need this to initialize the Z position of our writer object so that any initial lift taking place before the first layer change will raise the extruder from the correct initial Z instead of 0. */ m_writer.travel_to_z(m_config.z_offset.value); - + return gcode; } @@ -2542,178 +2524,14 @@ std::string GCode::change_layer(coordf_t print_z) comment << "move to next layer (" << m_layer_index << ")"; gcode += m_writer.travel_to_z(z, comment.str()); } - + // forget last wiping path as wiping after raising Z is pointless m_wipe.reset_path(); - + return gcode; } -// Return a value in <0, 1> of a cubic B-spline kernel centered around zero. -// The B-spline is re-scaled so it has value 1 at zero. -static inline float bspline_kernel(float x) -{ - x = std::abs(x); - if (x < 1.f) { - return 1.f - (3.f / 2.f) * x * x + (3.f / 4.f) * x * x * x; - } - else if (x < 2.f) { - x -= 1.f; - float x2 = x * x; - float x3 = x2 * x; - return (1.f / 4.f) - (3.f / 4.f) * x + (3.f / 4.f) * x2 - (1.f / 4.f) * x3; - } - else - return 0; -} -static float extrudate_overlap_penalty(float nozzle_r, float weight_zero, float overlap_distance) -{ - // The extrudate is not fully supported by the lower layer. Fit a polynomial penalty curve. - // Solved by sympy package: -/* -from sympy import * -(x,a,b,c,d,r,z)=symbols('x a b c d r z') -p = a + b*x + c*x*x + d*x*x*x -p2 = p.subs(solve([p.subs(x, -r), p.diff(x).subs(x, -r), p.diff(x,x).subs(x, -r), p.subs(x, 0)-z], [a, b, c, d])) -from sympy.plotting import plot -plot(p2.subs(r,0.2).subs(z,1.), (x, -1, 3), adaptive=False, nb_of_points=400) -*/ - if (overlap_distance < - nozzle_r) { - // The extrudate is fully supported by the lower layer. This is the ideal case, therefore zero penalty. - return 0.f; - } else { - float x = overlap_distance / nozzle_r; - float x2 = x * x; - float x3 = x2 * x; - return weight_zero * (1.f + 3.f * x + 3.f * x2 + x3); - } -} - -static Points::const_iterator project_point_to_polygon_and_insert(Polygon &polygon, const Point &pt, double eps) -{ - assert(polygon.points.size() >= 2); - if (polygon.points.size() <= 1) - if (polygon.points.size() == 1) - return polygon.points.begin(); - - Point pt_min; - double d_min = std::numeric_limits::max(); - size_t i_min = size_t(-1); - - for (size_t i = 0; i < polygon.points.size(); ++ i) { - size_t j = i + 1; - if (j == polygon.points.size()) - j = 0; - const Point &p1 = polygon.points[i]; - const Point &p2 = polygon.points[j]; - const Slic3r::Point v_seg = p2 - p1; - const Slic3r::Point v_pt = pt - p1; - const int64_t l2_seg = int64_t(v_seg(0)) * int64_t(v_seg(0)) + int64_t(v_seg(1)) * int64_t(v_seg(1)); - int64_t t_pt = int64_t(v_seg(0)) * int64_t(v_pt(0)) + int64_t(v_seg(1)) * int64_t(v_pt(1)); - if (t_pt < 0) { - // Closest to p1. - double dabs = sqrt(int64_t(v_pt(0)) * int64_t(v_pt(0)) + int64_t(v_pt(1)) * int64_t(v_pt(1))); - if (dabs < d_min) { - d_min = dabs; - i_min = i; - pt_min = p1; - } - } - else if (t_pt > l2_seg) { - // Closest to p2. Then p2 is the starting point of another segment, which shall be discovered in the next step. - continue; - } else { - // Closest to the segment. - assert(t_pt >= 0 && t_pt <= l2_seg); - int64_t d_seg = int64_t(v_seg(1)) * int64_t(v_pt(0)) - int64_t(v_seg(0)) * int64_t(v_pt(1)); - double d = double(d_seg) / sqrt(double(l2_seg)); - double dabs = std::abs(d); - if (dabs < d_min) { - d_min = dabs; - i_min = i; - // Evaluate the foot point. - pt_min = p1; - double linv = double(d_seg) / double(l2_seg); - pt_min(0) = pt(0) - coord_t(floor(double(v_seg(1)) * linv + 0.5)); - pt_min(1) = pt(1) + coord_t(floor(double(v_seg(0)) * linv + 0.5)); - assert(Line(p1, p2).distance_to(pt_min) < scale_(1e-5)); - } - } - } - - assert(i_min != size_t(-1)); - if ((pt_min - polygon.points[i_min]).cast().norm() > eps) { - // Insert a new point on the segment i_min, i_min+1. - return polygon.points.insert(polygon.points.begin() + (i_min + 1), pt_min); - } - return polygon.points.begin() + i_min; -} - -std::vector polygon_parameter_by_length(const Polygon &polygon) -{ - // Parametrize the polygon by its length. - std::vector lengths(polygon.points.size()+1, 0.); - for (size_t i = 1; i < polygon.points.size(); ++ i) - lengths[i] = lengths[i-1] + (polygon.points[i] - polygon.points[i-1]).cast().norm(); - lengths.back() = lengths[lengths.size()-2] + (polygon.points.front() - polygon.points.back()).cast().norm(); - return lengths; -} - -std::vector polygon_angles_at_vertices(const Polygon &polygon, const std::vector &lengths, float min_arm_length) -{ - assert(polygon.points.size() + 1 == lengths.size()); - if (min_arm_length > 0.25f * lengths.back()) - min_arm_length = 0.25f * lengths.back(); - - // Find the initial prev / next point span. - size_t idx_prev = polygon.points.size(); - size_t idx_curr = 0; - size_t idx_next = 1; - while (idx_prev > idx_curr && lengths.back() - lengths[idx_prev] < min_arm_length) - -- idx_prev; - while (idx_next < idx_prev && lengths[idx_next] < min_arm_length) - ++ idx_next; - - std::vector angles(polygon.points.size(), 0.f); - for (; idx_curr < polygon.points.size(); ++ idx_curr) { - // Move idx_prev up until the distance between idx_prev and idx_curr is lower than min_arm_length. - if (idx_prev >= idx_curr) { - while (idx_prev < polygon.points.size() && lengths.back() - lengths[idx_prev] + lengths[idx_curr] > min_arm_length) - ++ idx_prev; - if (idx_prev == polygon.points.size()) - idx_prev = 0; - } - while (idx_prev < idx_curr && lengths[idx_curr] - lengths[idx_prev] > min_arm_length) - ++ idx_prev; - // Move idx_prev one step back. - if (idx_prev == 0) - idx_prev = polygon.points.size() - 1; - else - -- idx_prev; - // Move idx_next up until the distance between idx_curr and idx_next is greater than min_arm_length. - if (idx_curr <= idx_next) { - while (idx_next < polygon.points.size() && lengths[idx_next] - lengths[idx_curr] < min_arm_length) - ++ idx_next; - if (idx_next == polygon.points.size()) - idx_next = 0; - } - while (idx_next < idx_curr && lengths.back() - lengths[idx_curr] + lengths[idx_next] < min_arm_length) - ++ idx_next; - // Calculate angle between idx_prev, idx_curr, idx_next. - const Point &p0 = polygon.points[idx_prev]; - const Point &p1 = polygon.points[idx_curr]; - const Point &p2 = polygon.points[idx_next]; - const Point v1 = p1 - p0; - const Point v2 = p2 - p1; - int64_t dot = int64_t(v1(0))*int64_t(v2(0)) + int64_t(v1(1))*int64_t(v2(1)); - int64_t cross = int64_t(v1(0))*int64_t(v2(1)) - int64_t(v1(1))*int64_t(v2(0)); - float angle = float(atan2(double(cross), double(dot))); - angles[idx_curr] = angle; - } - - return angles; -} std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, double speed, std::unique_ptr *lower_layer_edge_grid) { @@ -2740,187 +2558,49 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou #endif } } - + // extrude all loops ccw bool was_clockwise = loop.make_counter_clockwise(); - + SeamPosition seam_position = m_config.seam_position; - if (loop.loop_role() == elrSkirt) + if (loop.loop_role() == elrSkirt) seam_position = spNearest; - + // find the point of the loop that is closest to the current extruder position // or randomize if requested Point last_pos = this->last_pos(); if (m_config.spiral_vase) { loop.split_at(last_pos, false); - } else if (seam_position == spNearest || seam_position == spAligned || seam_position == spRear) { - Polygon polygon = loop.polygon(); - const coordf_t nozzle_dmr = EXTRUDER_CONFIG(nozzle_diameter); - const coord_t nozzle_r = coord_t(scale_(0.5 * nozzle_dmr) + 0.5); - - // Retrieve the last start position for this object. - float last_pos_weight = 1.f; - - if (seam_position == spAligned) { - // Seam is aligned to the seam at the preceding layer. - if (m_layer != NULL && m_seam_position.count(m_layer->object()) > 0) { - last_pos = m_seam_position[m_layer->object()]; - last_pos_weight = 1.f; - } - } - else if (seam_position == spRear) { - // Object is centered around (0,0) in its current coordinate system. - last_pos.x() = 0; - last_pos.y() += coord_t(3. * m_layer->object()->bounding_box().radius()); - last_pos_weight = 5.f; - } - - // Insert a projection of last_pos into the polygon. - size_t last_pos_proj_idx; - { - auto it = project_point_to_polygon_and_insert(polygon, last_pos, 0.1 * nozzle_r); - last_pos_proj_idx = it - polygon.points.begin(); - } - - // Parametrize the polygon by its length. - std::vector lengths = polygon_parameter_by_length(polygon); - - // For each polygon point, store a penalty. - // First calculate the angles, store them as penalties. The angles are caluculated over a minimum arm length of nozzle_r. - std::vector penalties = polygon_angles_at_vertices(polygon, lengths, float(nozzle_r)); - // No penalty for reflex points, slight penalty for convex points, high penalty for flat surfaces. - const float penaltyConvexVertex = 1.f; - const float penaltyFlatSurface = 5.f; - const float penaltyOverhangHalf = 10.f; - // Penalty for visible seams. - for (size_t i = 0; i < polygon.points.size(); ++ i) { - float ccwAngle = penalties[i]; - if (was_clockwise) - ccwAngle = - ccwAngle; - float penalty = 0; - if (ccwAngle <- float(0.6 * PI)) - // Sharp reflex vertex. We love that, it hides the seam perfectly. - penalty = 0.f; - else if (ccwAngle > float(0.6 * PI)) - // Seams on sharp convex vertices are more visible than on reflex vertices. - penalty = penaltyConvexVertex; - else if (ccwAngle < 0.f) { - // Interpolate penalty between maximum and zero. - penalty = penaltyFlatSurface * bspline_kernel(ccwAngle * float(PI * 2. / 3.)); - } else { - assert(ccwAngle >= 0.f); - // Interpolate penalty between maximum and the penalty for a convex vertex. - penalty = penaltyConvexVertex + (penaltyFlatSurface - penaltyConvexVertex) * bspline_kernel(ccwAngle * float(PI * 2. / 3.)); - } - // Give a negative penalty for points close to the last point or the prefered seam location. - float dist_to_last_pos_proj = (i < last_pos_proj_idx) ? - std::min(lengths[last_pos_proj_idx] - lengths[i], lengths.back() - lengths[last_pos_proj_idx] + lengths[i]) : - std::min(lengths[i] - lengths[last_pos_proj_idx], lengths.back() - lengths[i] + lengths[last_pos_proj_idx]); - float dist_max = 0.1f * lengths.back(); // 5.f * nozzle_dmr - penalty -= last_pos_weight * bspline_kernel(dist_to_last_pos_proj / dist_max); - penalties[i] = std::max(0.f, penalty); - } - - // Penalty for overhangs. - if (lower_layer_edge_grid && (*lower_layer_edge_grid)) { - // Use the edge grid distance field structure over the lower layer to calculate overhangs. - coord_t nozzle_r = coord_t(floor(scale_(0.5 * nozzle_dmr) + 0.5)); - coord_t search_r = coord_t(floor(scale_(0.8 * nozzle_dmr) + 0.5)); - for (size_t i = 0; i < polygon.points.size(); ++ i) { - const Point &p = polygon.points[i]; - coordf_t dist; - // Signed distance is positive outside the object, negative inside the object. - // The point is considered at an overhang, if it is more than nozzle radius - // outside of the lower layer contour. - [[maybe_unused]] bool found = (*lower_layer_edge_grid)->signed_distance(p, search_r, dist); - // If the approximate Signed Distance Field was initialized over lower_layer_edge_grid, - // then the signed distnace shall always be known. - assert(found); - penalties[i] += extrudate_overlap_penalty(float(nozzle_r), penaltyOverhangHalf, float(dist)); - } - } - - // Find a point with a minimum penalty. - size_t idx_min = std::min_element(penalties.begin(), penalties.end()) - penalties.begin(); - - // For all (aligned, nearest, rear) seams: - { - // Very likely the weight of idx_min is very close to the weight of last_pos_proj_idx. - // In that case use last_pos_proj_idx instead. - float penalty_aligned = penalties[last_pos_proj_idx]; - float penalty_min = penalties[idx_min]; - float penalty_diff_abs = std::abs(penalty_min - penalty_aligned); - float penalty_max = std::max(penalty_min, penalty_aligned); - float penalty_diff_rel = (penalty_max == 0.f) ? 0.f : penalty_diff_abs / penalty_max; - // printf("Align seams, penalty aligned: %f, min: %f, diff abs: %f, diff rel: %f\n", penalty_aligned, penalty_min, penalty_diff_abs, penalty_diff_rel); - if (penalty_diff_rel < 0.05) { - // Penalty of the aligned point is very close to the minimum penalty. - // Align the seams as accurately as possible. - idx_min = last_pos_proj_idx; - } - m_seam_position[m_layer->object()] = polygon.points[idx_min]; - } - - // Export the contour into a SVG file. - #if 0 - { - static int iRun = 0; - SVG svg(debug_out_path("GCode_extrude_loop-%d.svg", iRun ++)); - if (m_layer->lower_layer != NULL) - svg.draw(m_layer->lower_layer->slices); - for (size_t i = 0; i < loop.paths.size(); ++ i) - svg.draw(loop.paths[i].as_polyline(), "red"); - Polylines polylines; - for (size_t i = 0; i < loop.paths.size(); ++ i) - polylines.push_back(loop.paths[i].as_polyline()); - Slic3r::Polygons polygons; - coordf_t nozzle_dmr = EXTRUDER_CONFIG(nozzle_diameter); - coord_t delta = scale_(0.5*nozzle_dmr); - Slic3r::offset(polylines, &polygons, delta); -// for (size_t i = 0; i < polygons.size(); ++ i) svg.draw((Polyline)polygons[i], "blue"); - svg.draw(last_pos, "green", 3); - svg.draw(polygon.points[idx_min], "yellow", 3); - svg.Close(); - } - #endif - + } else { + const EdgeGrid::Grid* edge_grid_ptr = (lower_layer_edge_grid && *lower_layer_edge_grid) + ? lower_layer_edge_grid->get() + : nullptr; + Point seam = m_seam_placer.get_seam(m_layer->id(), seam_position, loop, + last_pos, EXTRUDER_CONFIG(nozzle_diameter), + (m_layer == NULL ? nullptr : m_layer->object()), + was_clockwise, edge_grid_ptr); // Split the loop at the point with a minium penalty. - if (!loop.split_at_vertex(polygon.points[idx_min])) + if (!loop.split_at_vertex(seam)) // The point is not in the original loop. Insert it. - loop.split_at(polygon.points[idx_min], true); - - } else if (seam_position == spRandom) { - if (loop.loop_role() == elrContourInternalPerimeter) { - // This loop does not contain any other loop. Set a random position. - // The other loops will get a seam close to the random point chosen - // on the inner most contour. - //FIXME This works correctly for inner contours first only. - //FIXME Better parametrize the loop by its length. - Polygon polygon = loop.polygon(); - Point centroid = polygon.centroid(); - last_pos = Point(polygon.bounding_box().max(0), centroid(1)); - last_pos.rotate(fmod((float)rand()/16.0, 2.0*PI), centroid); - } - // Find the closest point, avoid overhangs. - loop.split_at(last_pos, true); + loop.split_at(seam, true); } - + // clip the path to avoid the extruder to get exactly on the first point of the loop; // if polyline was shorter than the clipping distance we'd get a null polyline, so // we discard it in that case - double clip_length = m_enable_loop_clipping ? - scale_(EXTRUDER_CONFIG(nozzle_diameter)) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER : + double clip_length = m_enable_loop_clipping ? + scale_(EXTRUDER_CONFIG(nozzle_diameter)) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER : 0; // get paths ExtrusionPaths paths; loop.clip_end(clip_length, &paths); if (paths.empty()) return ""; - + // apply the small perimeter speed if (is_perimeter(paths.front().role()) && loop.length() <= SMALL_PERIMETER_LENGTH && speed == -1) speed = m_config.small_perimeter_speed.get_abs_value(m_config.perimeter_speed); - + // extrude along the path std::string gcode; for (ExtrusionPaths::iterator path = paths.begin(); path != paths.end(); ++path) { @@ -2929,31 +2609,31 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou path->simplify(SCALED_RESOLUTION); gcode += this->_extrude(*path, description, speed); } - + // reset acceleration gcode += m_writer.set_acceleration((unsigned int)(m_config.default_acceleration.value + 0.5)); - + if (m_wipe.enable) m_wipe.path = paths.front().polyline; // TODO: don't limit wipe to last path - + // make a little move inwards before leaving loop - if (paths.back().role() == erExternalPerimeter && m_layer != NULL && m_config.perimeters.value > 1 && paths.front().size() >= 2 && paths.back().polyline.points.size() >= 3) { + if (paths.back().role() == erExternalPerimeter && m_layer != NULL && m_config.perimeters.value > 1 && paths.front().size() >= 2 && paths.back().polyline.points.size() >= 3) { // detect angle between last and first segment // the side depends on the original winding order of the polygon (left for contours, right for holes) - //FIXME improve the algorithm in case the loop is tiny. - //FIXME improve the algorithm in case the loop is split into segments with a low number of points (see the Point b query). + //FIXME improve the algorithm in case the loop is tiny. + //FIXME improve the algorithm in case the loop is split into segments with a low number of points (see the Point b query). Point a = paths.front().polyline.points[1]; // second point Point b = *(paths.back().polyline.points.end()-3); // second to last point if (was_clockwise) { // swap points Point c = a; a = b; b = c; } - + double angle = paths.front().first_point().ccw_angle(a, b) / 3; - + // turn left if contour, turn right if hole if (was_clockwise) angle *= -1; - + // create the destination point along the first segment and rotate it // we make sure we don't exceed the segment length because we don't know // the rotation of the second segment so we might cross the object boundary @@ -2969,7 +2649,7 @@ std::string GCode::extrude_loop(ExtrusionLoop loop, std::string description, dou // generate the travel move gcode += m_writer.travel_to_xy(this->point_to_gcode(pt), "move inwards before travel"); } - + return gcode; } @@ -3001,7 +2681,7 @@ std::string GCode::extrude_entity(const ExtrusionEntity &entity, std::string des else if (const ExtrusionLoop* loop = dynamic_cast(&entity)) return this->extrude_loop(*loop, description, speed, lower_layer_edge_grid); else - throw std::invalid_argument("Invalid argument supplied to extrude()"); + throw Slic3r::InvalidArgument("Invalid argument supplied to extrude()"); return ""; } @@ -3040,23 +2720,23 @@ std::string GCode::extrude_infill(const Print &print, const std::vectorrole() == erIroning) == ironing) - extrusions.emplace_back(ee); - if (! extrusions.empty()) { - m_config.apply(print.regions()[®ion - &by_region.front()]->config()); - chain_and_reorder_extrusion_entities(extrusions, &m_last_pos); - for (const ExtrusionEntity *fill : extrusions) { - auto *eec = dynamic_cast(fill); - if (eec) { - for (ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities) - gcode += this->extrude_entity(*ee, extrusion_name); - } else - gcode += this->extrude_entity(*fill, extrusion_name); - } - } + extrusions.clear(); + extrusions.reserve(region.infills.size()); + for (ExtrusionEntity *ee : region.infills) + if ((ee->role() == erIroning) == ironing) + extrusions.emplace_back(ee); + if (! extrusions.empty()) { + m_config.apply(print.regions()[®ion - &by_region.front()]->config()); + chain_and_reorder_extrusion_entities(extrusions, &m_last_pos); + for (const ExtrusionEntity *fill : extrusions) { + auto *eec = dynamic_cast(fill); + if (eec) { + for (ExtrusionEntity *ee : eec->chained_path_from(m_last_pos).entities) + gcode += this->extrude_entity(*ee, extrusion_name); + } else + gcode += this->extrude_entity(*fill, extrusion_name); + } + } } return gcode; } @@ -3150,10 +2830,10 @@ void GCode::_write_format(FILE* file, const char* format, ...) std::string GCode::_extrude(const ExtrusionPath &path, std::string description, double speed) { std::string gcode; - + if (is_bridge(path.role())) description += " (bridge)"; - + // go to first point of extrusion path if (!m_last_pos_defined || m_last_pos != path.first_point()) { gcode += this->travel_to( @@ -3162,10 +2842,10 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, "move to first " + description + " point" ); } - + // compensate retraction gcode += this->unretract(); - + // adjust acceleration { double acceleration; @@ -3182,11 +2862,11 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, } gcode += m_writer.set_acceleration((unsigned int)floor(acceleration + 0.5)); } - + // calculate extrusion length per distance unit double e_per_mm = m_writer.extruder()->e_per_mm3() * path.mm3_per_mm; if (m_writer.extrusion_axis().empty()) e_per_mm = 0; - + // set speed if (speed == -1) { if (path.role() == erPerimeter) { @@ -3206,7 +2886,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, } else if (path.role() == erGapFill) { speed = m_config.get_abs_value("gap_fill_speed"); } else { - throw std::invalid_argument("Invalid speed"); + throw Slic3r::InvalidArgument("Invalid speed"); } } if (this->on_first_layer()) @@ -3228,7 +2908,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, ); } double F = speed * 60; // convert mm/sec to mm/min - + // extrude arc or line if (m_enable_extrusion_role_markers) { @@ -3336,40 +3016,40 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, } if (m_enable_cooling_markers) gcode += is_bridge(path.role()) ? ";_BRIDGE_FAN_END\n" : ";_EXTRUDE_END\n"; - + this->set_last_pos(path.last_point()); return gcode; } // This method accepts &point in print coordinates. std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string comment) -{ +{ /* Define the travel move as a line between current position and the taget point. This is expressed in print coordinates, so it will need to be translated by this->origin in order to get G-code coordinates. */ Polyline travel; travel.append(this->last_pos()); travel.append(point); - + // check whether a straight travel move would need retraction bool needs_retraction = this->needs_retraction(travel, role); - + // if a retraction would be needed, try to use avoid_crossing_perimeters to plan a // multi-hop travel path inside the configuration space if (needs_retraction && m_config.avoid_crossing_perimeters && ! m_avoid_crossing_perimeters.disable_once) { travel = m_avoid_crossing_perimeters.travel_to(*this, point); - + // check again whether the new travel path still needs a retraction needs_retraction = this->needs_retraction(travel, role); //if (needs_retraction && m_layer_index > 1) exit(0); } - + // Re-allow avoid_crossing_perimeters for the next travel moves m_avoid_crossing_perimeters.disable_once = false; m_avoid_crossing_perimeters.use_external_mp_once = false; - + // generate G-code for the travel move std::string gcode; if (needs_retraction) @@ -3377,12 +3057,12 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string else // Reset the wipe path when traveling, so one would not wipe along an old path. m_wipe.reset_path(); - + // use G1 because we rely on paths being straight (G0 may make round paths) Lines lines = travel.lines(); if (! lines.empty()) { for (const Line &line : lines) - gcode += m_writer.travel_to_xy(this->point_to_gcode(line.b), comment); + gcode += m_writer.travel_to_xy(this->point_to_gcode(line.b), comment); this->set_last_pos(lines.back().b); } return gcode; @@ -3394,7 +3074,7 @@ bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role) // skip retraction if the move is shorter than the configured threshold return false; } - + if (role == erSupportMaterial) { const SupportLayer* support_layer = dynamic_cast(m_layer); //FIXME support_layer->support_islands.contains should use some search structure! @@ -3411,7 +3091,7 @@ bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role) // internal infill is enabled (so that stringing is entirely not visible). //FIXME any_internal_region_slice_contains() is potentionally very slow, it shall test for the bounding boxes first. return false; - + // retract if only_retract_when_crossing_perimeters is disabled or doesn't apply return true; } @@ -3419,26 +3099,26 @@ bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role) std::string GCode::retract(bool toolchange) { std::string gcode; - + if (m_writer.extruder() == nullptr) return gcode; - + // wipe (if it's enabled for this extruder and we have a stored wipe path) if (EXTRUDER_CONFIG(wipe) && m_wipe.has_path()) { gcode += toolchange ? m_writer.retract_for_toolchange(true) : m_writer.retract(true); gcode += m_wipe.wipe(*this, toolchange); } - + /* The parent class will decide whether we need to perform an actual retraction - (the extruder might be already retracted fully or partially). We call these + (the extruder might be already retracted fully or partially). We call these methods even if we performed wipe, since this will ensure the entire retraction length is honored in case wipe path was too short. */ gcode += toolchange ? m_writer.retract_for_toolchange() : m_writer.retract(); - + gcode += m_writer.reset_e(); if (m_writer.extruder()->retract_length() > 0 || m_config.use_firmware_retraction) gcode += m_writer.lift(); - + return gcode; } @@ -3446,11 +3126,11 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z) { if (!m_writer.need_toolchange(extruder_id)) return ""; - + // if we are running a single-extruder setup, just set the extruder and return nothing if (!m_writer.multiple_extruders) { m_placeholder_parser.set("current_extruder", extruder_id); - + std::string gcode; // Append the filament start G-code. const std::string &start_filament_gcode = m_config.start_filament_gcode.get_at(extruder_id); @@ -3462,13 +3142,13 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z) gcode += m_writer.toolchange(extruder_id); return gcode; } - + // prepend retraction on the current extruder std::string gcode = this->retract(true); // Always reset the extrusion path, even if the tool change retract is set to zero. m_wipe.reset_path(); - + if (m_writer.extruder() != nullptr) { // Process the custom end_filament_gcode. set_extruder() is only called if there is no wipe tower // so it should not be injected twice. @@ -3480,7 +3160,7 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z) } } - + // If ooze prevention is enabled, park current extruder in the nearest // standby point and set it to the standby temperature. if (m_ooze_prevention.enable && m_writer.extruder() != nullptr) @@ -3529,7 +3209,7 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z) // Set the new extruder to the operating temperature. if (m_ooze_prevention.enable) gcode += m_ooze_prevention.post_toolchange(*this); - + return gcode; } @@ -3556,17 +3236,17 @@ const std::vector& GCode::ObjectByExtru { bool has_overrides = false; for (const auto& reg : by_region) - if (! reg.infills_overrides.empty() || ! reg.perimeters_overrides.empty()) { - has_overrides = true; - break; - } + if (! reg.infills_overrides.empty() || ! reg.perimeters_overrides.empty()) { + has_overrides = true; + break; + } - // Data is cleared, but the memory is not. + // Data is cleared, but the memory is not. by_region_per_copy_cache.clear(); if (! has_overrides) - // Simple case. No need to copy the regions. - return wiping_entities ? by_region_per_copy_cache : this->by_region; + // Simple case. No need to copy the regions. + return wiping_entities ? by_region_per_copy_cache : this->by_region; // Complex case. Some of the extrusions of some object instances are to be printed first - those are the wiping extrusions. // Some of the extrusions of some object instances are printed later - those are the clean print extrusions. @@ -3585,25 +3265,25 @@ const std::vector& GCode::ObjectByExtru // Now the most important thing - which extrusion should we print. // See function ToolOrdering::get_extruder_overrides for details about the negative numbers hack. if (wiping_entities) { - // Apply overrides for this region. - for (unsigned int i = 0; i < overrides.size(); ++ i) { - const WipingExtrusions::ExtruderPerCopy *this_override = overrides[i]; - // This copy (aka object instance) should be printed with this extruder, which overrides the default one. - if (this_override != nullptr && (*this_override)[copy] == int(extruder)) - target_eec.emplace_back(entities[i]); - } - } else { - // Apply normal extrusions (non-overrides) for this region. - unsigned int i = 0; - for (; i < overrides.size(); ++ i) { - const WipingExtrusions::ExtruderPerCopy *this_override = overrides[i]; - // This copy (aka object instance) should be printed with this extruder, which shall be equal to the default one. - if (this_override == nullptr || (*this_override)[copy] == -int(extruder)-1) - target_eec.emplace_back(entities[i]); - } - for (; i < entities.size(); ++ i) + // Apply overrides for this region. + for (unsigned int i = 0; i < overrides.size(); ++ i) { + const WipingExtrusions::ExtruderPerCopy *this_override = overrides[i]; + // This copy (aka object instance) should be printed with this extruder, which overrides the default one. + if (this_override != nullptr && (*this_override)[copy] == int(extruder)) + target_eec.emplace_back(entities[i]); + } + } else { + // Apply normal extrusions (non-overrides) for this region. + unsigned int i = 0; + for (; i < overrides.size(); ++ i) { + const WipingExtrusions::ExtruderPerCopy *this_override = overrides[i]; + // This copy (aka object instance) should be printed with this extruder, which shall be equal to the default one. + if (this_override == nullptr || (*this_override)[copy] == -int(extruder)-1) + target_eec.emplace_back(entities[i]); + } + for (; i < entities.size(); ++ i) target_eec.emplace_back(entities[i]); - } + } } } return by_region_per_copy_cache; @@ -3623,11 +3303,11 @@ void GCode::ObjectByExtruder::Island::Region::append(const Type type, const Extr perimeters_or_infills_overrides = &perimeters_overrides; break; case INFILL: - perimeters_or_infills = &infills; - perimeters_or_infills_overrides = &infills_overrides; + perimeters_or_infills = &infills; + perimeters_or_infills_overrides = &infills_overrides; break; default: - throw std::invalid_argument("Unknown parameter!"); + throw Slic3r::InvalidArgument("Unknown parameter!"); } // First we append the entities, there are eec->entities.size() of them: @@ -3635,18 +3315,18 @@ void GCode::ObjectByExtruder::Island::Region::append(const Type type, const Extr size_t new_size = old_size + (eec->can_reverse() ? eec->entities.size() : 1); perimeters_or_infills->reserve(new_size); if (eec->can_reverse()) { - for (auto* ee : eec->entities) - perimeters_or_infills->emplace_back(ee); - } else - perimeters_or_infills->emplace_back(const_cast(eec)); + for (auto* ee : eec->entities) + perimeters_or_infills->emplace_back(ee); + } else + perimeters_or_infills->emplace_back(const_cast(eec)); if (copies_extruder != nullptr) { - // Don't reallocate overrides if not needed. - // Missing overrides are implicitely considered non-overridden. + // Don't reallocate overrides if not needed. + // Missing overrides are implicitely considered non-overridden. perimeters_or_infills_overrides->reserve(new_size); perimeters_or_infills_overrides->resize(old_size, nullptr); perimeters_or_infills_overrides->resize(new_size, copies_extruder); - } + } } } // namespace Slic3r diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 8bae2ef43d..01650b6eeb 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -13,6 +13,7 @@ #include "GCode/SpiralVase.hpp" #include "GCode/ToolOrdering.hpp" #include "GCode/WipeTower.hpp" +#include "GCode/SeamPlacer.hpp" #if ENABLE_GCODE_VIEWER #include "GCode/GCodeProcessor.hpp" #else @@ -69,6 +70,7 @@ private: std::unique_ptr m_layer_mp; }; + class OozePrevention { public: bool enable; @@ -338,6 +340,9 @@ private: std::string unretract() { return m_writer.unlift() + m_writer.unretract(); } std::string set_extruder(unsigned int extruder_id, double print_z); + // Cache for custom seam enforcers/blockers for each layer. + SeamPlacer m_seam_placer; + /* Origin of print coordinates expressed in unscaled G-code coordinates. This affects the input arguments supplied to the extrude*() and travel_to() methods. */ @@ -376,7 +381,6 @@ private: // Current layer processed. Insequential printing mode, only a single copy will be printed. // In non-sequential mode, all its copies will be printed. const Layer* m_layer; - std::map m_seam_position; double m_volumetric_speed; // Support for the extrusion role markers. Which marker is active? ExtrusionRole m_last_extrusion_role; diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index db69f4f0ba..0a5617559d 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -319,13 +319,13 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) { boost::nowide::ifstream in(filename); if (!in.good()) - throw std::runtime_error(std::string("Time estimator post process export failed.\nCannot open file for reading.\n")); + throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nCannot open file for reading.\n")); // temporary file to contain modified gcode std::string out_path = filename + ".postprocess"; FILE* out = boost::nowide::fopen(out_path.c_str(), "wb"); if (out == nullptr) - throw std::runtime_error(std::string("Time estimator post process export failed.\nCannot open file for writing.\n")); + throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nCannot open file for writing.\n")); auto time_in_minutes = [](float time_in_seconds) { return int(::roundf(time_in_seconds / 60.0f)); @@ -418,7 +418,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) in.close(); fclose(out); boost::nowide::remove(out_path.c_str()); - throw std::runtime_error(std::string("Time estimator post process export failed.\nIs the disk full?\n")); + throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nIs the disk full?\n")); } export_line.clear(); }; @@ -426,7 +426,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) while (std::getline(in, gcode_line)) { if (!in.good()) { fclose(out); - throw std::runtime_error(std::string("Time estimator post process export failed.\nError while reading from file.\n")); + throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nError while reading from file.\n")); } gcode_line += "\n"; @@ -460,7 +460,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) in.close(); if (rename_file(out_path, filename)) - throw std::runtime_error(std::string("Failed to rename the output G-code file from ") + out_path + " to " + filename + '\n' + + throw Slic3r::RuntimeError(std::string("Failed to rename the output G-code file from ") + out_path + " to " + filename + '\n' + "Is " + out_path + " locked?" + '\n'); } diff --git a/src/libslic3r/GCode/PostProcessor.cpp b/src/libslic3r/GCode/PostProcessor.cpp index 25982959be..17aa76fb9d 100644 --- a/src/libslic3r/GCode/PostProcessor.cpp +++ b/src/libslic3r/GCode/PostProcessor.cpp @@ -79,7 +79,7 @@ static DWORD execute_process_winapi(const std::wstring &command_line) if (! ::CreateProcessW( nullptr /* lpApplicationName */, (LPWSTR)command_line.c_str(), nullptr /* lpProcessAttributes */, nullptr /* lpThreadAttributes */, false /* bInheritHandles */, CREATE_UNICODE_ENVIRONMENT /* | CREATE_NEW_CONSOLE */ /* dwCreationFlags */, (LPVOID)envstr.c_str(), nullptr /* lpCurrentDirectory */, &startup_info, &process_info)) - throw std::runtime_error(std::string("Failed starting the script ") + boost::nowide::narrow(command_line) + ", Win32 error: " + std::to_string(int(::GetLastError()))); + throw Slic3r::RuntimeError(std::string("Failed starting the script ") + boost::nowide::narrow(command_line) + ", Win32 error: " + std::to_string(int(::GetLastError()))); ::WaitForSingleObject(process_info.hProcess, INFINITE); ULONG rc = 0; ::GetExitCodeProcess(process_info.hProcess, &rc); @@ -98,13 +98,13 @@ static int run_script(const std::string &script, const std::string &gcode, std:: LPWSTR *szArglist = CommandLineToArgvW(boost::nowide::widen(script).c_str(), &nArgs); if (szArglist == nullptr || nArgs <= 0) { // CommandLineToArgvW failed. Maybe the command line escapment is invalid? - throw std::runtime_error(std::string("Post processing script ") + script + " on file " + gcode + " failed. CommandLineToArgvW() refused to parse the command line path."); + throw Slic3r::RuntimeError(std::string("Post processing script ") + script + " on file " + gcode + " failed. CommandLineToArgvW() refused to parse the command line path."); } std::wstring command_line; std::wstring command = szArglist[0]; if (! boost::filesystem::exists(boost::filesystem::path(command))) - throw std::runtime_error(std::string("The configured post-processing script does not exist: ") + boost::nowide::narrow(command)); + throw Slic3r::RuntimeError(std::string("The configured post-processing script does not exist: ") + boost::nowide::narrow(command)); if (boost::iends_with(command, L".pl")) { // This is a perl script. Run it through the perl interpreter. // The current process may be slic3r.exe or slic3r-console.exe. @@ -115,7 +115,7 @@ static int run_script(const std::string &script, const std::string &gcode, std:: boost::filesystem::path path_perl = path_exe.parent_path() / "perl" / "perl.exe"; if (! boost::filesystem::exists(path_perl)) { LocalFree(szArglist); - throw std::runtime_error(std::string("Perl interpreter ") + path_perl.string() + " does not exist."); + throw Slic3r::RuntimeError(std::string("Perl interpreter ") + path_perl.string() + " does not exist."); } // Replace it with the current perl interpreter. quote_argv_winapi(boost::nowide::widen(path_perl.string()), command_line); @@ -187,7 +187,7 @@ void run_post_process_scripts(const std::string &path, const PrintConfig &config config.setenv_(); auto gcode_file = boost::filesystem::path(path); if (! boost::filesystem::exists(gcode_file)) - throw std::runtime_error(std::string("Post-processor can't find exported gcode file")); + throw Slic3r::RuntimeError(std::string("Post-processor can't find exported gcode file")); for (const std::string &scripts : config.post_process.values) { std::vector lines; @@ -205,7 +205,7 @@ void run_post_process_scripts(const std::string &path, const PrintConfig &config const std::string msg = std_err.empty() ? (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%") % script % path % result).str() : (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%\nOutput:\n%4%") % script % path % result % std_err).str(); BOOST_LOG_TRIVIAL(error) << msg; - throw std::runtime_error(msg); + throw Slic3r::RuntimeError(msg); } } } diff --git a/src/libslic3r/GCode/PressureEqualizer.cpp b/src/libslic3r/GCode/PressureEqualizer.cpp index 3b2a58a884..33601e5e9f 100644 --- a/src/libslic3r/GCode/PressureEqualizer.cpp +++ b/src/libslic3r/GCode/PressureEqualizer.cpp @@ -148,7 +148,7 @@ static inline int parse_int(const char *&line) char *endptr = NULL; long result = strtol(line, &endptr, 10); if (endptr == NULL || !is_ws_or_eol(*endptr)) - throw std::runtime_error("PressureEqualizer: Error parsing an int"); + throw Slic3r::RuntimeError("PressureEqualizer: Error parsing an int"); line = endptr; return int(result); }; @@ -160,7 +160,7 @@ static inline float parse_float(const char *&line) char *endptr = NULL; float result = strtof(line, &endptr); if (endptr == NULL || !is_ws_or_eol(*endptr)) - throw std::runtime_error("PressureEqualizer: Error parsing a float"); + throw Slic3r::RuntimeError("PressureEqualizer: Error parsing a float"); line = endptr; return result; }; @@ -229,7 +229,7 @@ bool PressureEqualizer::process_line(const char *line, const size_t len, GCodeLi assert(false); } if (i == -1) - throw std::runtime_error(std::string("GCode::PressureEqualizer: Invalid axis for G0/G1: ") + axis); + throw Slic3r::RuntimeError(std::string("GCode::PressureEqualizer: Invalid axis for G0/G1: ") + axis); buf.pos_provided[i] = true; new_pos[i] = parse_float(line); if (i == 3 && m_config->use_relative_e_distances.value) @@ -298,7 +298,7 @@ bool PressureEqualizer::process_line(const char *line, const size_t len, GCodeLi set = true; break; default: - throw std::runtime_error(std::string("GCode::PressureEqualizer: Incorrect axis in a G92 G-code: ") + axis); + throw Slic3r::RuntimeError(std::string("GCode::PressureEqualizer: Incorrect axis in a G92 G-code: ") + axis); } eatws(line); } diff --git a/src/libslic3r/GCode/PrintExtents.cpp b/src/libslic3r/GCode/PrintExtents.cpp index 4a6624531b..a86411519f 100644 --- a/src/libslic3r/GCode/PrintExtents.cpp +++ b/src/libslic3r/GCode/PrintExtents.cpp @@ -94,7 +94,7 @@ static BoundingBoxf extrusionentity_extents(const ExtrusionEntity *extrusion_ent auto *extrusion_entity_collection = dynamic_cast(extrusion_entity); if (extrusion_entity_collection != nullptr) return extrusionentity_extents(*extrusion_entity_collection); - throw std::runtime_error("Unexpected extrusion_entity type in extrusionentity_extents()"); + throw Slic3r::RuntimeError("Unexpected extrusion_entity type in extrusionentity_extents()"); return BoundingBoxf(); } diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp new file mode 100644 index 0000000000..db31f8f67f --- /dev/null +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -0,0 +1,672 @@ +#include "SeamPlacer.hpp" + +#include "libslic3r/ExtrusionEntity.hpp" +#include "libslic3r/Print.hpp" +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/EdgeGrid.hpp" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/SVG.hpp" + +namespace Slic3r { + +// This penalty is added to all points inside custom blockers (subtracted from pts inside enforcers). +static constexpr float ENFORCER_BLOCKER_PENALTY = 100; + +// In case there are custom enforcers/blockers, the loop polygon shall always have +// sides smaller than this (so it isn't limited to original resolution). +static constexpr float MINIMAL_POLYGON_SIDE = scale_(0.2f); + +// When spAligned is active and there is a support enforcer, +// add this penalty to its center. +static constexpr float ENFORCER_CENTER_PENALTY = -10.f; + + + + +static float extrudate_overlap_penalty(float nozzle_r, float weight_zero, float overlap_distance) +{ + // The extrudate is not fully supported by the lower layer. Fit a polynomial penalty curve. + // Solved by sympy package: +/* +from sympy import * +(x,a,b,c,d,r,z)=symbols('x a b c d r z') +p = a + b*x + c*x*x + d*x*x*x +p2 = p.subs(solve([p.subs(x, -r), p.diff(x).subs(x, -r), p.diff(x,x).subs(x, -r), p.subs(x, 0)-z], [a, b, c, d])) +from sympy.plotting import plot +plot(p2.subs(r,0.2).subs(z,1.), (x, -1, 3), adaptive=False, nb_of_points=400) +*/ + if (overlap_distance < - nozzle_r) { + // The extrudate is fully supported by the lower layer. This is the ideal case, therefore zero penalty. + return 0.f; + } else { + float x = overlap_distance / nozzle_r; + float x2 = x * x; + float x3 = x2 * x; + return weight_zero * (1.f + 3.f * x + 3.f * x2 + x3); + } +} + + + +// Return a value in <0, 1> of a cubic B-spline kernel centered around zero. +// The B-spline is re-scaled so it has value 1 at zero. +static inline float bspline_kernel(float x) +{ + x = std::abs(x); + if (x < 1.f) { + return 1.f - (3.f / 2.f) * x * x + (3.f / 4.f) * x * x * x; + } + else if (x < 2.f) { + x -= 1.f; + float x2 = x * x; + float x3 = x2 * x; + return (1.f / 4.f) - (3.f / 4.f) * x + (3.f / 4.f) * x2 - (1.f / 4.f) * x3; + } + else + return 0; +} + + + +static Points::const_iterator project_point_to_polygon_and_insert(Polygon &polygon, const Point &pt, double eps) +{ + assert(polygon.points.size() >= 2); + if (polygon.points.size() <= 1) + if (polygon.points.size() == 1) + return polygon.points.begin(); + + Point pt_min; + double d_min = std::numeric_limits::max(); + size_t i_min = size_t(-1); + + for (size_t i = 0; i < polygon.points.size(); ++ i) { + size_t j = i + 1; + if (j == polygon.points.size()) + j = 0; + const Point &p1 = polygon.points[i]; + const Point &p2 = polygon.points[j]; + const Slic3r::Point v_seg = p2 - p1; + const Slic3r::Point v_pt = pt - p1; + const int64_t l2_seg = int64_t(v_seg(0)) * int64_t(v_seg(0)) + int64_t(v_seg(1)) * int64_t(v_seg(1)); + int64_t t_pt = int64_t(v_seg(0)) * int64_t(v_pt(0)) + int64_t(v_seg(1)) * int64_t(v_pt(1)); + if (t_pt < 0) { + // Closest to p1. + double dabs = sqrt(int64_t(v_pt(0)) * int64_t(v_pt(0)) + int64_t(v_pt(1)) * int64_t(v_pt(1))); + if (dabs < d_min) { + d_min = dabs; + i_min = i; + pt_min = p1; + } + } + else if (t_pt > l2_seg) { + // Closest to p2. Then p2 is the starting point of another segment, which shall be discovered in the next step. + continue; + } else { + // Closest to the segment. + assert(t_pt >= 0 && t_pt <= l2_seg); + int64_t d_seg = int64_t(v_seg(1)) * int64_t(v_pt(0)) - int64_t(v_seg(0)) * int64_t(v_pt(1)); + double d = double(d_seg) / sqrt(double(l2_seg)); + double dabs = std::abs(d); + if (dabs < d_min) { + d_min = dabs; + i_min = i; + // Evaluate the foot point. + pt_min = p1; + double linv = double(d_seg) / double(l2_seg); + pt_min(0) = pt(0) - coord_t(floor(double(v_seg(1)) * linv + 0.5)); + pt_min(1) = pt(1) + coord_t(floor(double(v_seg(0)) * linv + 0.5)); + assert(Line(p1, p2).distance_to(pt_min) < scale_(1e-5)); + } + } + } + + assert(i_min != size_t(-1)); + if ((pt_min - polygon.points[i_min]).cast().norm() > eps) { + // Insert a new point on the segment i_min, i_min+1. + return polygon.points.insert(polygon.points.begin() + (i_min + 1), pt_min); + } + return polygon.points.begin() + i_min; +} + + + +static std::vector polygon_angles_at_vertices(const Polygon &polygon, const std::vector &lengths, float min_arm_length) +{ + assert(polygon.points.size() + 1 == lengths.size()); + if (min_arm_length > 0.25f * lengths.back()) + min_arm_length = 0.25f * lengths.back(); + + // Find the initial prev / next point span. + size_t idx_prev = polygon.points.size(); + size_t idx_curr = 0; + size_t idx_next = 1; + while (idx_prev > idx_curr && lengths.back() - lengths[idx_prev] < min_arm_length) + -- idx_prev; + while (idx_next < idx_prev && lengths[idx_next] < min_arm_length) + ++ idx_next; + + std::vector angles(polygon.points.size(), 0.f); + for (; idx_curr < polygon.points.size(); ++ idx_curr) { + // Move idx_prev up until the distance between idx_prev and idx_curr is lower than min_arm_length. + if (idx_prev >= idx_curr) { + while (idx_prev < polygon.points.size() && lengths.back() - lengths[idx_prev] + lengths[idx_curr] > min_arm_length) + ++ idx_prev; + if (idx_prev == polygon.points.size()) + idx_prev = 0; + } + while (idx_prev < idx_curr && lengths[idx_curr] - lengths[idx_prev] > min_arm_length) + ++ idx_prev; + // Move idx_prev one step back. + if (idx_prev == 0) + idx_prev = polygon.points.size() - 1; + else + -- idx_prev; + // Move idx_next up until the distance between idx_curr and idx_next is greater than min_arm_length. + if (idx_curr <= idx_next) { + while (idx_next < polygon.points.size() && lengths[idx_next] - lengths[idx_curr] < min_arm_length) + ++ idx_next; + if (idx_next == polygon.points.size()) + idx_next = 0; + } + while (idx_next < idx_curr && lengths.back() - lengths[idx_curr] + lengths[idx_next] < min_arm_length) + ++ idx_next; + // Calculate angle between idx_prev, idx_curr, idx_next. + const Point &p0 = polygon.points[idx_prev]; + const Point &p1 = polygon.points[idx_curr]; + const Point &p2 = polygon.points[idx_next]; + const Point v1 = p1 - p0; + const Point v2 = p2 - p1; + int64_t dot = int64_t(v1(0))*int64_t(v2(0)) + int64_t(v1(1))*int64_t(v2(1)); + int64_t cross = int64_t(v1(0))*int64_t(v2(1)) - int64_t(v1(1))*int64_t(v2(0)); + float angle = float(atan2(double(cross), double(dot))); + angles[idx_curr] = angle; + } + + return angles; +} + + + +void SeamPlacer::init(const Print& print) +{ + m_enforcers.clear(); + m_blockers.clear(); + //m_last_seam_position.clear(); + m_seam_history.clear(); + + for (const PrintObject* po : print.objects()) { + po->project_and_append_custom_facets(true, EnforcerBlockerType::ENFORCER, m_enforcers); + po->project_and_append_custom_facets(true, EnforcerBlockerType::BLOCKER, m_blockers); + } + const std::vector& nozzle_dmrs = print.config().nozzle_diameter.values; + float max_nozzle_dmr = *std::max_element(nozzle_dmrs.begin(), nozzle_dmrs.end()); + for (ExPolygons& explgs : m_enforcers) + explgs = Slic3r::offset_ex(explgs, scale_(max_nozzle_dmr)); + for (ExPolygons& explgs : m_blockers) + explgs = Slic3r::offset_ex(explgs, scale_(max_nozzle_dmr)); +} + + + +Point SeamPlacer::get_seam(const size_t layer_idx, const SeamPosition seam_position, + const ExtrusionLoop& loop, Point last_pos, coordf_t nozzle_dmr, + const PrintObject* po, bool was_clockwise, const EdgeGrid::Grid* lower_layer_edge_grid) +{ + Polygon polygon = loop.polygon(); + BoundingBox polygon_bb = polygon.bounding_box(); + const coord_t nozzle_r = coord_t(scale_(0.5 * nozzle_dmr) + 0.5); + + if (this->is_custom_seam_on_layer(layer_idx)) { + // Seam enf/blockers can begin and end in between the original vertices. + // Let add extra points in between and update the leghths. + polygon.densify(MINIMAL_POLYGON_SIDE); + } + + if (seam_position != spRandom) { + // Retrieve the last start position for this object. + float last_pos_weight = 1.f; + + if (seam_position == spAligned) { + // Seam is aligned to the seam at the preceding layer. + if (po != nullptr) { + std::optional pos = m_seam_history.get_last_seam(po, layer_idx, polygon_bb); + if (pos.has_value()) { + //last_pos = m_last_seam_position[po]; + last_pos = *pos; + last_pos_weight = is_custom_enforcer_on_layer(layer_idx) ? 0.f : 1.f; + } + } + } + else if (seam_position == spRear) { + // Object is centered around (0,0) in its current coordinate system. + last_pos.x() = 0; + last_pos.y() += coord_t(3. * po->bounding_box().radius()); + last_pos_weight = 5.f; + } if (seam_position == spNearest) { + // last_pos already contains current nozzle position + } + + // Insert a projection of last_pos into the polygon. + size_t last_pos_proj_idx; + { + auto it = project_point_to_polygon_and_insert(polygon, last_pos, 0.1 * nozzle_r); + last_pos_proj_idx = it - polygon.points.begin(); + } + + // Parametrize the polygon by its length. + std::vector lengths = polygon.parameter_by_length(); + + // For each polygon point, store a penalty. + // First calculate the angles, store them as penalties. The angles are caluculated over a minimum arm length of nozzle_r. + std::vector penalties = polygon_angles_at_vertices(polygon, lengths, float(nozzle_r)); + // No penalty for reflex points, slight penalty for convex points, high penalty for flat surfaces. + const float penaltyConvexVertex = 1.f; + const float penaltyFlatSurface = 5.f; + const float penaltyOverhangHalf = 10.f; + // Penalty for visible seams. + for (size_t i = 0; i < polygon.points.size(); ++ i) { + float ccwAngle = penalties[i]; + if (was_clockwise) + ccwAngle = - ccwAngle; + float penalty = 0; + if (ccwAngle <- float(0.6 * PI)) + // Sharp reflex vertex. We love that, it hides the seam perfectly. + penalty = 0.f; + else if (ccwAngle > float(0.6 * PI)) + // Seams on sharp convex vertices are more visible than on reflex vertices. + penalty = penaltyConvexVertex; + else if (ccwAngle < 0.f) { + // Interpolate penalty between maximum and zero. + penalty = penaltyFlatSurface * bspline_kernel(ccwAngle * float(PI * 2. / 3.)); + } else { + assert(ccwAngle >= 0.f); + // Interpolate penalty between maximum and the penalty for a convex vertex. + penalty = penaltyConvexVertex + (penaltyFlatSurface - penaltyConvexVertex) * bspline_kernel(ccwAngle * float(PI * 2. / 3.)); + } + // Give a negative penalty for points close to the last point or the prefered seam location. + float dist_to_last_pos_proj = (i < last_pos_proj_idx) ? + std::min(lengths[last_pos_proj_idx] - lengths[i], lengths.back() - lengths[last_pos_proj_idx] + lengths[i]) : + std::min(lengths[i] - lengths[last_pos_proj_idx], lengths.back() - lengths[i] + lengths[last_pos_proj_idx]); + float dist_max = 0.1f * lengths.back(); // 5.f * nozzle_dmr + penalty -= last_pos_weight * bspline_kernel(dist_to_last_pos_proj / dist_max); + penalties[i] = std::max(0.f, penalty); + } + + // Penalty for overhangs. + if (lower_layer_edge_grid) { + // Use the edge grid distance field structure over the lower layer to calculate overhangs. + coord_t nozzle_r = coord_t(std::floor(scale_(0.5 * nozzle_dmr) + 0.5)); + coord_t search_r = coord_t(std::floor(scale_(0.8 * nozzle_dmr) + 0.5)); + for (size_t i = 0; i < polygon.points.size(); ++ i) { + const Point &p = polygon.points[i]; + coordf_t dist; + // Signed distance is positive outside the object, negative inside the object. + // The point is considered at an overhang, if it is more than nozzle radius + // outside of the lower layer contour. + [[maybe_unused]] bool found = lower_layer_edge_grid->signed_distance(p, search_r, dist); + // If the approximate Signed Distance Field was initialized over lower_layer_edge_grid, + // then the signed distnace shall always be known. + assert(found); + penalties[i] += extrudate_overlap_penalty(float(nozzle_r), penaltyOverhangHalf, float(dist)); + } + } + + // Custom seam. Huge (negative) constant penalty is applied inside + // blockers (enforcers) to rule out points that should not win. + this->apply_custom_seam(polygon, penalties, lengths, layer_idx, seam_position); + + // Find a point with a minimum penalty. + size_t idx_min = std::min_element(penalties.begin(), penalties.end()) - penalties.begin(); + + if (seam_position != spAligned || ! is_custom_enforcer_on_layer(layer_idx)) { + // Very likely the weight of idx_min is very close to the weight of last_pos_proj_idx. + // In that case use last_pos_proj_idx instead. + float penalty_aligned = penalties[last_pos_proj_idx]; + float penalty_min = penalties[idx_min]; + float penalty_diff_abs = std::abs(penalty_min - penalty_aligned); + float penalty_max = std::max(penalty_min, penalty_aligned); + float penalty_diff_rel = (penalty_max == 0.f) ? 0.f : penalty_diff_abs / penalty_max; + // printf("Align seams, penalty aligned: %f, min: %f, diff abs: %f, diff rel: %f\n", penalty_aligned, penalty_min, penalty_diff_abs, penalty_diff_rel); + if (std::abs(penalty_diff_rel) < 0.05) { + // Penalty of the aligned point is very close to the minimum penalty. + // Align the seams as accurately as possible. + idx_min = last_pos_proj_idx; + } + } + + if (seam_position == spAligned && loop.role() == erExternalPerimeter) + m_seam_history.add_seam(po, polygon.points[idx_min], polygon_bb); + + + // Export the contour into a SVG file. + #if 0 + { + static int iRun = 0; + SVG svg(debug_out_path("GCode_extrude_loop-%d.svg", iRun ++)); + if (m_layer->lower_layer != NULL) + svg.draw(m_layer->lower_layer->slices); + for (size_t i = 0; i < loop.paths.size(); ++ i) + svg.draw(loop.paths[i].as_polyline(), "red"); + Polylines polylines; + for (size_t i = 0; i < loop.paths.size(); ++ i) + polylines.push_back(loop.paths[i].as_polyline()); + Slic3r::Polygons polygons; + coordf_t nozzle_dmr = EXTRUDER_CONFIG(nozzle_diameter); + coord_t delta = scale_(0.5*nozzle_dmr); + Slic3r::offset(polylines, &polygons, delta); +// for (size_t i = 0; i < polygons.size(); ++ i) svg.draw((Polyline)polygons[i], "blue"); + svg.draw(last_pos, "green", 3); + svg.draw(polygon.points[idx_min], "yellow", 3); + svg.Close(); + } + #endif + return polygon.points[idx_min]; + + } else { // spRandom + if (loop.loop_role() == elrContourInternalPerimeter && loop.role() != erExternalPerimeter) { + // This loop does not contain any other loop. Set a random position. + // The other loops will get a seam close to the random point chosen + // on the innermost contour. + //FIXME This works correctly for inner contours first only. + last_pos = this->get_random_seam(layer_idx, polygon); + } + if (loop.role() == erExternalPerimeter && is_custom_seam_on_layer(layer_idx)) { + // There is a possibility that the loop will be influenced by custom + // seam enforcer/blocker. In this case do not inherit the seam + // from internal loops (which may conflict with the custom selection + // and generate another random one. + bool saw_custom = false; + Point candidate = this->get_random_seam(layer_idx, polygon, &saw_custom); + if (saw_custom) + last_pos = candidate; + } + return last_pos; + } +} + + +Point SeamPlacer::get_random_seam(size_t layer_idx, const Polygon& polygon, + bool* saw_custom) const +{ + // Parametrize the polygon by its length. + std::vector lengths = polygon.parameter_by_length(); + + // Which of the points are inside enforcers/blockers? + std::vector enforcers_idxs; + std::vector blockers_idxs; + this->get_enforcers_and_blockers(layer_idx, polygon, enforcers_idxs, blockers_idxs); + + bool has_enforcers = ! enforcers_idxs.empty(); + bool has_blockers = ! blockers_idxs.empty(); + if (saw_custom) + *saw_custom = has_enforcers || has_blockers; + + // FIXME FIXME FIXME: This is just to test the outcome and whether it is + // reasonable. The algorithm should really sum the length of all available + // pieces, get a random length and find the respective point. + float rand_len = 0.f; + size_t pt_idx = 0; + do { + rand_len = lengths.back() * (rand()/float(RAND_MAX)); + auto it = std::lower_bound(lengths.begin(), lengths.end(), rand_len); + pt_idx = it == lengths.end() ? 0 : (it-lengths.begin()-1); + + // If there are blockers and the point is inside, repeat. + // If there are enforcers and the point is NOT inside, repeat. + } while ((has_blockers && std::binary_search(blockers_idxs.begin(), blockers_idxs.end(), pt_idx)) + || (has_enforcers && ! std::binary_search(enforcers_idxs.begin(), enforcers_idxs.end(), pt_idx))); + + if (! has_enforcers && ! has_blockers) { + // The polygon may be too coarse, calculate the point exactly. + bool last_seg = pt_idx == polygon.points.size()-1; + size_t next_idx = last_seg ? 0 : pt_idx+1; + const Point& prev = polygon.points[pt_idx]; + const Point& next = polygon.points[next_idx]; + assert(next_idx == 0 || pt_idx+1 == next_idx); + coordf_t diff_x = next.x() - prev.x(); + coordf_t diff_y = next.y() - prev.y(); + coordf_t dist = lengths[last_seg ? pt_idx+1 : next_idx] - lengths[pt_idx]; + return Point(prev.x() + (rand_len - lengths[pt_idx]) * (diff_x/dist), + prev.y() + (rand_len - lengths[pt_idx]) * (diff_y/dist)); + + } else { + // The polygon should be dense enough. + return polygon.points[pt_idx]; + } +} + + + + + + + + +void SeamPlacer::get_enforcers_and_blockers(size_t layer_id, + const Polygon& polygon, + std::vector& enforcers_idxs, + std::vector& blockers_idxs) const +{ + enforcers_idxs.clear(); + blockers_idxs.clear(); + + // FIXME: This is quadratic and it should be improved, maybe by building + // an AABB tree (or at least utilize bounding boxes). + for (size_t i=0; i find_enforcer_centers(const Polygon& polygon, + const std::vector& lengths, + const std::vector& enforcers_idxs) +{ + std::vector out; + assert(polygon.points.size()+1 == lengths.size()); + assert(std::is_sorted(enforcers_idxs.begin(), enforcers_idxs.end())); + if (polygon.size() < 2 || enforcers_idxs.empty()) + return out; + + auto get_center_idx = [&polygon, &lengths](size_t start_idx, size_t end_idx) -> size_t { + assert(end_idx >= start_idx); + if (start_idx == end_idx) + return start_idx; + float t_c = lengths[start_idx] + 0.5f * (lengths[end_idx] - lengths[start_idx]); + auto it = std::lower_bound(lengths.begin() + start_idx, lengths.begin() + end_idx, t_c); + int ret = it - lengths.begin(); + return ret; + }; + + int last_enforcer_start_idx = enforcers_idxs.front(); + bool first_pt_in_list = enforcers_idxs.front() != 0; + bool last_pt_in_list = enforcers_idxs.back() == polygon.points.size() - 1; + bool wrap_around = last_pt_in_list && first_pt_in_list; + + for (size_t i=0; i t_e) ? t_s + half_dist : t_e - half_dist; + + auto it = std::lower_bound(lengths.begin(), lengths.end(), t_c); + out[0] = it - lengths.begin(); + if (out[0] == lengths.size() - 1) + --out[0]; + assert(out[0] < lengths.size() - 1); + } + return out; +} + + + +void SeamPlacer::apply_custom_seam(const Polygon& polygon, + std::vector& penalties, + const std::vector& lengths, + int layer_id, SeamPosition seam_position) const +{ + if (! is_custom_seam_on_layer(layer_id)) + return; + + std::vector enforcers_idxs; + std::vector blockers_idxs; + this->get_enforcers_and_blockers(layer_id, polygon, enforcers_idxs, blockers_idxs); + + for (size_t i : enforcers_idxs) { + assert(i < penalties.size()); + penalties[i] -= float(ENFORCER_BLOCKER_PENALTY); + } + for (size_t i : blockers_idxs) { + assert(i < penalties.size()); + penalties[i] += float(ENFORCER_BLOCKER_PENALTY); + } + if (seam_position == spAligned) { + std::vector enf_centers = find_enforcer_centers(polygon, lengths, enforcers_idxs); + for (size_t idx : enf_centers) { + assert(idx < penalties.size()); + penalties[idx] += ENFORCER_CENTER_PENALTY; + } + } + +//////////////////////// +// std::ostringstream os; +// os << std::setw(3) << std::setfill('0') << layer_id; +// int a = scale_(20.); +// SVG svg("custom_seam" + os.str() + ".svg", BoundingBox(Point(-a, -a), Point(a, a))); +// /*if (! m_enforcers.empty()) +// svg.draw(m_enforcers[layer_id], "blue"); +// if (! m_blockers.empty()) +// svg.draw(m_blockers[layer_id], "red");*/ + +// size_t min_idx = std::min_element(penalties.begin(), penalties.end()) - penalties.begin(); + +// //svg.draw(polygon.points[idx_min], "red", 6e5); +// for (size_t i=0; i SeamHistory::get_last_seam(const PrintObject* po, size_t layer_id, const BoundingBox& island_bb) +{ + assert(layer_id >= m_layer_id); + if (layer_id > m_layer_id) { + // Get seam was called for different layer than last time. + m_data_last_layer = m_data_this_layer; + m_data_this_layer.clear(); + m_layer_id = layer_id; + } + + + + std::optional out; + + auto seams_it = m_data_last_layer.find(po); + if (seams_it == m_data_last_layer.end()) + return out; + + const std::vector& seam_data_po = seams_it->second; + + // Find a bounding-box on the last layer that is close to one we see now. + double min_score = std::numeric_limits::max(); + for (const SeamPoint& sp : seam_data_po) { + const BoundingBox& bb = sp.m_island_bb; + + if (! bb.overlap(island_bb)) { + // This bb does not even overlap. It is likely unrelated. + continue; + } + + double score = std::pow(bb.min(0) - island_bb.min(0), 2.) + + std::pow(bb.min(1) - island_bb.min(1), 2.) + + std::pow(bb.max(0) - island_bb.max(0), 2.) + + std::pow(bb.max(1) - island_bb.max(1), 2.); + + if (score < min_score) { + min_score = score; + out = sp.m_pos; + } + } + + return out; +} + + + +void SeamHistory::add_seam(const PrintObject* po, const Point& pos, const BoundingBox& island_bb) +{ + m_data_this_layer[po].push_back({pos, island_bb});; +} + + + +void SeamHistory::clear() +{ + m_layer_id = 0; + m_data_last_layer.clear(); + m_data_this_layer.clear(); +} + + +} diff --git a/src/libslic3r/GCode/SeamPlacer.hpp b/src/libslic3r/GCode/SeamPlacer.hpp new file mode 100644 index 0000000000..e603b7d57b --- /dev/null +++ b/src/libslic3r/GCode/SeamPlacer.hpp @@ -0,0 +1,89 @@ +#ifndef libslic3r_SeamPlacer_hpp_ +#define libslic3r_SeamPlacer_hpp_ + +#include + +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/BoundingBox.hpp" + +namespace Slic3r { + +class PrintObject; +class ExtrusionLoop; +class Print; +namespace EdgeGrid { class Grid; } + + +class SeamHistory { +public: + SeamHistory() { clear(); } + std::optional get_last_seam(const PrintObject* po, size_t layer_id, const BoundingBox& island_bb); + void add_seam(const PrintObject* po, const Point& pos, const BoundingBox& island_bb); + void clear(); + +private: + struct SeamPoint { + Point m_pos; + BoundingBox m_island_bb; + }; + + std::map> m_data_last_layer; + std::map> m_data_this_layer; + size_t m_layer_id; +}; + + + +class SeamPlacer { +public: + void init(const Print& print); + + Point get_seam(const size_t layer_idx, const SeamPosition seam_position, + const ExtrusionLoop& loop, Point last_pos, + coordf_t nozzle_diameter, const PrintObject* po, + bool was_clockwise, const EdgeGrid::Grid* lower_layer_edge_grid); + +private: + std::vector m_enforcers; + std::vector m_blockers; + + //std::map m_last_seam_position; + SeamHistory m_seam_history; + + // Get indices of points inside enforcers and blockers. + void get_enforcers_and_blockers(size_t layer_id, + const Polygon& polygon, + std::vector& enforcers_idxs, + std::vector& blockers_idxs) const; + + // Apply penalties to points inside enforcers/blockers. + void apply_custom_seam(const Polygon& polygon, + std::vector& penalties, + const std::vector& lengths, + int layer_id, SeamPosition seam_position) const; + + // Return random point of a polygon. The distribution will be uniform + // along the contour and account for enforcers and blockers. + Point get_random_seam(size_t layer_idx, const Polygon& polygon, + bool* saw_custom = nullptr) const; + + // Is there any enforcer/blocker on this layer? + bool is_custom_seam_on_layer(size_t layer_id) const { + return is_custom_enforcer_on_layer(layer_id) + || is_custom_blocker_on_layer(layer_id); + } + + bool is_custom_enforcer_on_layer(size_t layer_id) const { + return (! m_enforcers.empty() && ! m_enforcers[layer_id].empty()); + } + + bool is_custom_blocker_on_layer(size_t layer_id) const { + return (! m_blockers.empty() && ! m_blockers[layer_id].empty()); + } +}; + + +} + +#endif // libslic3r_SeamPlacer_hpp_ diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 0752b6dfcb..944bea5c1f 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -1,22 +1,6 @@ -/* - -TODO LIST ---------- - -1. cooling moves - DONE -2. account for perimeter and finish_layer extrusions and subtract it from last wipe - DONE -3. priming extrusions (last wipe must clear the color) - DONE -4. Peter's wipe tower - layer's are not exactly square -5. Peter's wipe tower - variable width for higher levels -6. Peter's wipe tower - make sure it is not too sparse (apply max_bridge_distance and make last wipe longer) -7. Peter's wipe tower - enable enhanced first layer adhesion - -*/ - #include "WipeTower.hpp" -#include -#include +#include #include #include #include @@ -28,13 +12,16 @@ TODO LIST #endif // ENABLE_GCODE_VIEWER #include "BoundingBox.hpp" -#if defined(__linux) || defined(__GNUC__ ) -#include -#endif /* __linux */ -#ifdef _MSC_VER -#define strcasecmp _stricmp -#endif +// Experimental "Peter's wipe tower" feature was partially implemented, inspired by +// PJR's idea of alternating two perpendicular wiping directions on a square tower. +// It is probably never going to be finished, there are multiple remaining issues +// and there is probably no need to go down this way. m_peters_wipe_tower variable +// turns this on, maybe it should just be removed. Anyway, the issues are +// - layer's are not exactly square +// - variable width for higher levels +// - make sure it is not too sparse (apply max_bridge_distance and make last wipe longer) +// - enable enhanced first layer adhesion namespace Slic3r @@ -441,9 +428,26 @@ public: WipeTowerWriter& append(const std::string& text) { m_gcode += text; return *this; } + std::vector wipe_path() const + { + return m_wipe_path; + } + + WipeTowerWriter& add_wipe_point(const Vec2f& pt) + { + m_wipe_path.push_back(rotate(pt)); + return *this; + } + + WipeTowerWriter& add_wipe_point(float x, float y) + { + return add_wipe_point(Vec2f(x, y)); + } + private: Vec2f m_start_pos; Vec2f m_current_pos; + std::vector m_wipe_path; float m_current_z; float m_current_feedrate; size_t m_current_tool; @@ -713,7 +717,7 @@ std::vector WipeTower::prime( return results; } -WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool, bool last_in_layer) +WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool) { if ( m_print_brim ) return toolchange_Brim(); @@ -790,7 +794,9 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool, bool last_in_lay else { writer.rectangle(Vec2f::Zero(), m_wipe_tower_width, m_layer_info->depth + m_perimeter_width); if (layer_finished()) { // no finish_layer will be called, we must wipe the nozzle - writer.travel(writer.x()> m_wipe_tower_width / 2.f ? 0.f : m_wipe_tower_width, writer.y()); + writer.add_wipe_point(writer.x(), writer.y()) + .add_wipe_point(writer.x()> m_wipe_tower_width / 2.f ? 0.f : m_wipe_tower_width, writer.y()); + } } } @@ -820,6 +826,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool, bool last_in_lay result.extrusions = writer.extrusions(); result.start_pos = writer.start_pos_rotated(); result.end_pos = writer.pos_rotated(); + result.wipe_path = writer.wipe_path(); return result; } @@ -853,13 +860,15 @@ WipeTower::ToolChangeResult WipeTower::toolchange_Brim(bool sideOnly, float y_of for (size_t i = 0; i < 4; ++ i) { box.expand(spacing); writer.travel (box.ld, 7000) - .extrude(box.lu, 2100).extrude(box.ru) - .extrude(box.rd ).extrude(box.ld); + .extrude(box.lu, 2100).extrude(box.ru) + .extrude(box.rd ).extrude(box.ld); } - writer.travel(wipeTower_box.ld, 7000); // Move to the front left corner. - writer.travel(wipeTower_box.rd) // Always wipe the nozzle with a long wipe to reduce stringing when moving away from the wipe tower. - .travel(wipeTower_box.ld); + box.expand(-spacing); + writer.add_wipe_point(writer.x(), writer.y()) + .add_wipe_point(box.ld) + .add_wipe_point(box.rd); + writer.append("; CP WIPE TOWER FIRST LAYER BRIM END\n" ";-----------------------------------\n"); @@ -884,6 +893,7 @@ WipeTower::ToolChangeResult WipeTower::toolchange_Brim(bool sideOnly, float y_of result.extrusions = writer.extrusions(); result.start_pos = writer.start_pos_rotated(); result.end_pos = writer.pos_rotated(); + result.wipe_path = writer.wipe_path(); return result; } @@ -927,13 +937,6 @@ void WipeTower::toolchange_Unload( else sparse_beginning_y += (m_layer_info-1)->toolchanges_depth() + m_perimeter_width; - //debugging: - /* float oldx = writer.x(); - float oldy = writer.y(); - writer.travel(xr,sparse_beginning_y); - writer.extrude(xr+5,writer.y()); - writer.travel(oldx,oldy);*/ - float sum_of_depths = 0.f; for (const auto& tch : m_layer_info->tool_changes) { // let's find this toolchange if (tch.old_tool == m_current_tool) { @@ -941,13 +944,6 @@ void WipeTower::toolchange_Unload( float ramming_end_y = sum_of_depths; ramming_end_y -= (y_step/m_extra_spacing-m_perimeter_width) / 2.f; // center of final ramming line - // debugging: - /*float oldx = writer.x(); - float oldy = writer.y(); - writer.travel(xl,ramming_end_y); - writer.extrude(xl-15,writer.y()); - writer.travel(oldx,oldy);*/ - if ( (m_current_shape == SHAPE_REVERSED && ramming_end_y < sparse_beginning_y - 0.5f*m_perimeter_width ) || (m_current_shape == SHAPE_NORMAL && ramming_end_y > sparse_beginning_y + 0.5f*m_perimeter_width ) ) { @@ -998,12 +994,6 @@ void WipeTower::toolchange_Unload( .retract(0.70f * total_retraction_distance, 1.0f * m_filpar[m_current_tool].unloading_speed * 60.f) .retract(0.20f * total_retraction_distance, 0.5f * m_filpar[m_current_tool].unloading_speed * 60.f) .retract(0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed * 60.f) - - /*.load_move_x_advanced(turning_point, -15.f, 83.f, 50.f) // this is done at fixed speed - .load_move_x_advanced(old_x, -0.70f * total_retraction_distance, 1.0f * m_filpar[m_current_tool].unloading_speed) - .load_move_x_advanced(turning_point, -0.20f * total_retraction_distance, 0.5f * m_filpar[m_current_tool].unloading_speed) - .load_move_x_advanced(old_x, -0.10f * total_retraction_distance, 0.3f * m_filpar[m_current_tool].unloading_speed) - .travel(old_x, writer.y()) // in case previous move was shortened to limit feedrate*/ .resume_preview(); } // Wipe tower should only change temperature with single extruder MM. Otherwise, all temperatures should @@ -1096,11 +1086,6 @@ void WipeTower::toolchange_Load( writer.append("; CP TOOLCHANGE LOAD\n") .suppress_preview() - /*.load_move_x_advanced(turning_point, 0.2f * edist, 0.3f * m_filpar[m_current_tool].loading_speed) // Acceleration - .load_move_x_advanced(oldx, 0.5f * edist, m_filpar[m_current_tool].loading_speed) // Fast phase - .load_move_x_advanced(turning_point, 0.2f * edist, 0.3f * m_filpar[m_current_tool].loading_speed) // Slowing down - .load_move_x_advanced(oldx, 0.1f * edist, 0.1f * m_filpar[m_current_tool].loading_speed) // Super slow*/ - .load(0.2f * edist, 60.f * m_filpar[m_current_tool].loading_speed_start) .load_move_x_advanced(turning_point, 0.7f * edist, m_filpar[m_current_tool].loading_speed) // Fast phase .load_move_x_advanced(oldx, 0.1f * edist, 0.1f * m_filpar[m_current_tool].loading_speed) // Super slow*/ @@ -1170,11 +1155,14 @@ void WipeTower::toolchange_Wipe( m_left_to_right = !m_left_to_right; } - // this is neither priming nor not the last toolchange on this layer - we are going back to the model - wipe the nozzle + // this is neither priming nor not the last toolchange on this layer - we are + // going back to the model - wipe the nozzle. if (m_layer_info != m_plan.end() && m_current_tool != m_layer_info->tool_changes.back().new_tool) { m_left_to_right = !m_left_to_right; - writer.travel(writer.x(), writer.y() - dy) - .travel(m_left_to_right ? m_wipe_tower_width : 0.f, writer.y()); + writer.add_wipe_point(writer.x(), writer.y()) + .add_wipe_point(writer.x(), writer.y() - dy) + .add_wipe_point(m_left_to_right ? m_wipe_tower_width : 0.f, writer.y() - dy); + } writer.set_extrusion_flow(m_extrusion_flow); // Reset the extrusion flow. @@ -1238,7 +1226,8 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() writer.extrude(box.rd.x() - m_perimeter_width / 2.f, writer.y() + 0.5f * step); writer.extrude(box.ld.x() + m_perimeter_width / 2.f, writer.y()); } - writer.travel(box.rd.x()-m_perimeter_width/2.f,writer.y()); // wipe the nozzle + writer.add_wipe_point(writer.x(), writer.y()) + .add_wipe_point(box.rd.x()-m_perimeter_width/2.f,writer.y()); } else { // Extrude a sparse infill to support the material to be printed above. const float dy = (fill_box.lu.y() - fill_box.ld.y() - m_perimeter_width); @@ -1257,10 +1246,13 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() writer.travel(x,writer.y()); writer.extrude(x,i%2 ? fill_box.rd.y() : fill_box.ru.y()); } - writer.travel(left,writer.y(),7200); // wipes the nozzle before moving away from the wipe tower + writer.add_wipe_point(Vec2f(writer.x(), writer.y())) + .add_wipe_point(Vec2f(left, writer.y())); + } + else { + writer.add_wipe_point(Vec2f(writer.x(), writer.y())) + .add_wipe_point(Vec2f(right, writer.y())); } - else - writer.travel(right,writer.y(),7200); // wipes the nozzle before moving away from the wipe tower } writer.append("; CP EMPTY GRID END\n" ";------------------\n\n\n\n\n\n\n"); @@ -1285,6 +1277,7 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() result.extrusions = writer.extrusions(); result.start_pos = writer.start_pos_rotated(); result.end_pos = writer.pos_rotated(); + result.wipe_path = writer.wipe_path(); return result; } @@ -1356,7 +1349,7 @@ void WipeTower::save_on_last_wipe() continue; for (const auto &toolchange : m_layer_info->tool_changes) - tool_change(toolchange.new_tool, false); + tool_change(toolchange.new_tool); float width = m_wipe_tower_width - 3*m_perimeter_width; // width we draw into float length_to_save = 2*(m_wipe_tower_width+m_wipe_tower_depth) + (!layer_finished() ? finish_layer().total_extrusion_length_in_plane() : 0.f); @@ -1418,7 +1411,7 @@ void WipeTower::generate(std::vector> & m_y_shift = (m_wipe_tower_depth-m_layer_info->depth-m_perimeter_width)/2.f; for (const auto &toolchange : layer.tool_changes) - layer_result.emplace_back(tool_change(toolchange.new_tool, false)); + layer_result.emplace_back(tool_change(toolchange.new_tool)); if (! layer_finished()) { auto finish_layer_toolchange = finish_layer(); @@ -1432,6 +1425,7 @@ void WipeTower::generate(std::vector> & last_toolchange.gcode += finish_layer_toolchange.gcode; last_toolchange.extrusions.insert(last_toolchange.extrusions.end(), finish_layer_toolchange.extrusions.begin(), finish_layer_toolchange.extrusions.end()); last_toolchange.end_pos = finish_layer_toolchange.end_pos; + last_toolchange.wipe_path = finish_layer_toolchange.wipe_path; } else layer_result.emplace_back(std::move(finish_layer_toolchange)); @@ -1467,4 +1461,4 @@ void WipeTower::make_wipe_tower_square() lay.extra_spacing = lay.depth / lay.toolchanges_depth(); } -}; // namespace Slic3r +} // namespace Slic3r diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index f353151575..26f48785a9 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -57,6 +57,13 @@ public: // Is this a priming extrusion? (If so, the wipe tower rotation & translation will not be applied later) bool priming; + // Pass a polyline so that normal G-code generator can do a wipe for us. + // The wipe cannot be done by the wipe tower because it has to pass back + // a loaded extruder, so it would have to either do a wipe with no retraction + // (leading to https://github.com/prusa3d/PrusaSlicer/issues/2834) or do + // an extra retraction-unretraction pair. + std::vector wipe_path; + // Initial tool int initial_tool; @@ -154,7 +161,7 @@ public: // Returns gcode for a toolchange and a final print head position. // On the first layer, extrude a brim around the future wipe tower first. - ToolChangeResult tool_change(size_t new_tool, bool last_in_layer); + ToolChangeResult tool_change(size_t new_tool); // Fill the unfilled space with a sparse infill. // Call this method only if layer_finished() is false. diff --git a/src/libslic3r/GCodeSender.cpp b/src/libslic3r/GCodeSender.cpp index 9567e07d28..7bda299923 100644 --- a/src/libslic3r/GCodeSender.cpp +++ b/src/libslic3r/GCodeSender.cpp @@ -153,7 +153,7 @@ GCodeSender::set_baud_rate(unsigned int baud_rate) if (::tcsetattr(handle, TCSAFLUSH, &ios) != 0) printf("Failed to set baud rate: %s\n", strerror(errno)); #else - //throw invalid_argument ("OS does not currently support custom bauds"); + //throw Slic3r::InvalidArgument("OS does not currently support custom bauds"); #endif } } diff --git a/src/libslic3r/GCodeTimeEstimator.cpp b/src/libslic3r/GCodeTimeEstimator.cpp index aa9ee2f643..a3e20ca2f4 100644 --- a/src/libslic3r/GCodeTimeEstimator.cpp +++ b/src/libslic3r/GCodeTimeEstimator.cpp @@ -1,3 +1,4 @@ +#include "Exception.hpp" #include "GCodeTimeEstimator.hpp" #include "Utils.hpp" #include @@ -254,13 +255,13 @@ namespace Slic3r { { boost::nowide::ifstream in(filename); if (!in.good()) - throw std::runtime_error(std::string("Time estimator post process export failed.\nCannot open file for reading.\n")); + throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nCannot open file for reading.\n")); std::string path_tmp = filename + ".postprocess"; FILE* out = boost::nowide::fopen(path_tmp.c_str(), "wb"); if (out == nullptr) - throw std::runtime_error(std::string("Time estimator post process export failed.\nCannot open file for writing.\n")); + throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nCannot open file for writing.\n")); std::string normal_time_mask = "M73 P%s R%s\n"; std::string silent_time_mask = "M73 Q%s S%s\n"; @@ -278,7 +279,7 @@ namespace Slic3r { in.close(); fclose(out); boost::nowide::remove(path_tmp.c_str()); - throw std::runtime_error(std::string("Time estimator post process export failed.\nIs the disk full?\n")); + throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nIs the disk full?\n")); } export_line.clear(); }; @@ -326,7 +327,7 @@ namespace Slic3r { if (!in.good()) { fclose(out); - throw std::runtime_error(std::string("Time estimator post process export failed.\nError while reading from file.\n")); + throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nError while reading from file.\n")); } // check tags @@ -383,7 +384,7 @@ namespace Slic3r { in.close(); if (rename_file(path_tmp, filename)) - throw std::runtime_error(std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + filename + '\n' + + throw Slic3r::RuntimeError(std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + filename + '\n' + "Is " + path_tmp + " locked?" + '\n'); return true; diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 00a4ad47c3..3b9fcd6176 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -1,4 +1,5 @@ #include "libslic3r.h" +#include "Exception.hpp" #include "Geometry.hpp" #include "ClipperUtils.hpp" #include "ExPolygon.hpp" @@ -471,7 +472,7 @@ Pointfs arrange(size_t num_parts, const Vec2d &part_size, coordf_t gap, const Bo size_t cellw = size_t(floor((bed_bbox.size()(0) + gap) / cell_size(0))); size_t cellh = size_t(floor((bed_bbox.size()(1) + gap) / cell_size(1))); if (num_parts > cellw * cellh) - throw std::invalid_argument("%zu parts won't fit in your print area!\n", num_parts); + throw Slic3r::InvalidArgument("%zu parts won't fit in your print area!\n", num_parts); // Get a bounding box of cellw x cellh cells, centered at the center of the bed. Vec2d cells_size(cellw * cell_size(0) - gap, cellh * cell_size(1) - gap); diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index 75f3708d25..b690b478d9 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -281,7 +281,7 @@ bool directions_parallel(double angle1, double angle2, double max_diff = 0); template bool contains(const std::vector &vector, const Point &point); template T rad2deg(T angle) { return T(180.0) * angle / T(PI); } double rad2deg_dir(double angle); -template T deg2rad(T angle) { return T(PI) * angle / T(180.0); } +template constexpr T deg2rad(const T angle) { return T(PI) * angle / T(180.0); } template T angle_to_0_2PI(T angle) { static const T TWO_PI = T(2) * T(PI); diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 8d5db42fc0..8285b5493f 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -13,7 +13,7 @@ class Layer; class PrintRegion; class PrintObject; -namespace FillAdaptive_Internal { +namespace FillAdaptive { struct Octree; }; @@ -139,7 +139,7 @@ public: } void make_perimeters(); void make_fills() { this->make_fills(nullptr, nullptr); }; - void make_fills(FillAdaptive_Internal::Octree* adaptive_fill_octree, FillAdaptive_Internal::Octree* support_fill_octree); + void make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree); void make_ironing(); void export_region_slices_to_svg(const char *path) const; diff --git a/src/libslic3r/MeshBoolean.cpp b/src/libslic3r/MeshBoolean.cpp index 66167c7209..dd34b28300 100644 --- a/src/libslic3r/MeshBoolean.cpp +++ b/src/libslic3r/MeshBoolean.cpp @@ -1,3 +1,4 @@ +#include "Exception.hpp" #include "MeshBoolean.hpp" #include "libslic3r/TriangleMesh.hpp" #undef PI @@ -136,7 +137,7 @@ template void triangle_mesh_to_cgal(const TriangleMesh &M, _Mesh &o if(CGAL::is_closed(out)) CGALProc::orient_to_bound_a_volume(out); else - std::runtime_error("Mesh not watertight"); + throw Slic3r::RuntimeError("Mesh not watertight"); } inline Vec3d to_vec3d(const _EpicMesh::Point &v) @@ -222,7 +223,7 @@ template void _cgal_do(Op &&op, CGALMesh &A, CGALMesh &B) } if (! success) - throw std::runtime_error("CGAL mesh boolean operation failed."); + throw Slic3r::RuntimeError("CGAL mesh boolean operation failed."); } void minus(CGALMesh &A, CGALMesh &B) { _cgal_do(_cgal_diff, A, B); } diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 67fe63871d..3539cc0faa 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1,3 +1,4 @@ +#include "Exception.hpp" #include "Model.hpp" #include "ModelArrange.hpp" #include "Geometry.hpp" @@ -116,13 +117,13 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c else if (boost::algorithm::iends_with(input_file, ".prusa")) result = load_prus(input_file.c_str(), &model); else - throw std::runtime_error("Unknown file format. Input file must have .stl, .obj, .amf(.xml) or .prusa extension."); + throw Slic3r::RuntimeError("Unknown file format. Input file must have .stl, .obj, .amf(.xml) or .prusa extension."); if (! result) - throw std::runtime_error("Loading of a model file failed."); + throw Slic3r::RuntimeError("Loading of a model file failed."); if (model.objects.empty()) - throw std::runtime_error("The supplied file couldn't be read because it's empty"); + throw Slic3r::RuntimeError("The supplied file couldn't be read because it's empty"); for (ModelObject *o : model.objects) o->input_file = input_file; @@ -146,13 +147,13 @@ Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig else if (boost::algorithm::iends_with(input_file, ".zip.amf")) result = load_amf(input_file.c_str(), config, &model, check_version); else - throw std::runtime_error("Unknown file format. Input file must have .3mf or .zip.amf extension."); + throw Slic3r::RuntimeError("Unknown file format. Input file must have .3mf or .zip.amf extension."); if (!result) - throw std::runtime_error("Loading of a model file failed."); + throw Slic3r::RuntimeError("Loading of a model file failed."); if (model.objects.empty()) - throw std::runtime_error("The supplied file couldn't be read because it's empty"); + throw Slic3r::RuntimeError("The supplied file couldn't be read because it's empty"); for (ModelObject *o : model.objects) { @@ -776,6 +777,38 @@ TriangleMesh ModelObject::raw_mesh() const return mesh; } +// Non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes. +// Currently used by ModelObject::mesh(), to calculate the 2D envelope for 2D plater +// and to display the object statistics at ModelObject::print_info(). +indexed_triangle_set ModelObject::raw_indexed_triangle_set() const +{ + size_t num_vertices = 0; + size_t num_faces = 0; + for (const ModelVolume *v : this->volumes) + if (v->is_model_part()) { + num_vertices += v->mesh().its.vertices.size(); + num_faces += v->mesh().its.indices.size(); + } + indexed_triangle_set out; + out.vertices.reserve(num_vertices); + out.indices.reserve(num_faces); + for (const ModelVolume *v : this->volumes) + if (v->is_model_part()) { + size_t i = out.vertices.size(); + size_t j = out.indices.size(); + append(out.vertices, v->mesh().its.vertices); + append(out.indices, v->mesh().its.indices); + auto m = v->get_matrix(); + for (; i < out.vertices.size(); ++ i) + out.vertices[i] = (m * out.vertices[i].cast()).cast().eval(); + if (v->is_left_handed()) { + for (; j < out.indices.size(); ++ j) + std::swap(out.indices[j][0], out.indices[j][1]); + } + } + return out; +} + // Non-transformed (non-rotated, non-scaled, non-translated) sum of all object volumes. TriangleMesh ModelObject::full_raw_mesh() const { @@ -817,7 +850,7 @@ const BoundingBoxf3& ModelObject::raw_bounding_box() const m_raw_bounding_box_valid = true; m_raw_bounding_box.reset(); if (this->instances.empty()) - throw std::invalid_argument("Can't call raw_bounding_box() with no instances"); + throw Slic3r::InvalidArgument("Can't call raw_bounding_box() with no instances"); const Transform3d& inst_matrix = this->instances.front()->get_transformation().get_matrix(true); for (const ModelVolume *v : this->volumes) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index a623f5cca0..b9045e28bf 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -244,6 +244,8 @@ public: // Non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes. // Currently used by ModelObject::mesh() and to calculate the 2D envelope for 2D plater. TriangleMesh raw_mesh() const; + // The same as above, but producing a lightweight indexed_triangle_set. + indexed_triangle_set raw_indexed_triangle_set() const; // Non-transformed (non-rotated, non-scaled, non-translated) sum of all object volumes. TriangleMesh full_raw_mesh() const; // A transformed snug bounding box around the non-modifier object volumes, without the translation applied. diff --git a/src/libslic3r/ModelArrange.hpp b/src/libslic3r/ModelArrange.hpp index afe146d438..124c5c018f 100644 --- a/src/libslic3r/ModelArrange.hpp +++ b/src/libslic3r/ModelArrange.hpp @@ -20,7 +20,7 @@ using VirtualBedFn = std::function; [[noreturn]] inline void throw_if_out_of_bed(arrangement::ArrangePolygon&) { - throw std::runtime_error("Objects could not fit on the bed"); + throw Slic3r::RuntimeError("Objects could not fit on the bed"); } ArrangePolygons get_arrange_polys(const Model &model, ModelInstancePtrs &instances); diff --git a/src/libslic3r/OpenVDBUtils.cpp b/src/libslic3r/OpenVDBUtils.cpp index 53a71f194f..31ae203ddf 100644 --- a/src/libslic3r/OpenVDBUtils.cpp +++ b/src/libslic3r/OpenVDBUtils.cpp @@ -64,11 +64,6 @@ openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh &mesh, float interiorBandWidth, int flags) { -// openvdb::initialize(); -// return openvdb::tools::meshToVolume( -// TriangleMeshDataAdapter{mesh}, tr, exteriorBandWidth, -// interiorBandWidth, flags); - openvdb::initialize(); TriangleMeshPtrs meshparts = mesh.split(); @@ -83,15 +78,23 @@ openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh &mesh, openvdb::FloatGrid::Ptr grid; for (TriangleMesh *m : meshparts) { - auto gridptr = openvdb::tools::meshToVolume( + auto subgrid = openvdb::tools::meshToVolume( TriangleMeshDataAdapter{*m}, tr, exteriorBandWidth, interiorBandWidth, flags); - if (grid && gridptr) openvdb::tools::csgUnion(*grid, *gridptr); - else if (gridptr) grid = std::move(gridptr); + if (grid && subgrid) openvdb::tools::csgUnion(*grid, *subgrid); + else if (subgrid) grid = std::move(subgrid); } - grid = openvdb::tools::levelSetRebuild(*grid, 0., exteriorBandWidth, interiorBandWidth); + if (grid) { + grid = openvdb::tools::levelSetRebuild(*grid, 0., exteriorBandWidth, + interiorBandWidth); + } else if(meshparts.empty()) { + // Splitting failed, fall back to hollow the original mesh + grid = openvdb::tools::meshToVolume( + TriangleMeshDataAdapter{mesh}, tr, exteriorBandWidth, + interiorBandWidth, flags); + } return grid; } diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index 527d82b4cb..0434e3a0aa 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -1,4 +1,5 @@ #include "PlaceholderParser.hpp" +#include "Exception.hpp" #include "Flow.hpp" #include #include @@ -1303,7 +1304,7 @@ static std::string process_macro(const std::string &templ, client::MyContext &co if (!context.error_message.empty()) { if (context.error_message.back() != '\n' && context.error_message.back() != '\r') context.error_message += '\n'; - throw std::runtime_error(context.error_message); + throw Slic3r::RuntimeError(context.error_message); } return output; } @@ -1319,7 +1320,7 @@ std::string PlaceholderParser::process(const std::string &templ, unsigned int cu } // Evaluate a boolean expression using the full expressive power of the PlaceholderParser boolean expression syntax. -// Throws std::runtime_error on syntax or runtime error. +// Throws Slic3r::RuntimeError on syntax or runtime error. bool PlaceholderParser::evaluate_boolean_expression(const std::string &templ, const DynamicConfig &config, const DynamicConfig *config_override) { client::MyContext context; diff --git a/src/libslic3r/PlaceholderParser.hpp b/src/libslic3r/PlaceholderParser.hpp index d744dba220..14be020aca 100644 --- a/src/libslic3r/PlaceholderParser.hpp +++ b/src/libslic3r/PlaceholderParser.hpp @@ -40,11 +40,11 @@ public: const DynamicConfig* external_config() const { return m_external_config; } // Fill in the template using a macro processing language. - // Throws std::runtime_error on syntax or runtime error. + // Throws Slic3r::RuntimeError on syntax or runtime error. std::string process(const std::string &templ, unsigned int current_extruder_id = 0, const DynamicConfig *config_override = nullptr) const; // Evaluate a boolean expression using the full expressive power of the PlaceholderParser boolean expression syntax. - // Throws std::runtime_error on syntax or runtime error. + // Throws Slic3r::RuntimeError on syntax or runtime error. static bool evaluate_boolean_expression(const std::string &templ, const DynamicConfig &config, const DynamicConfig *config_override = nullptr); // Update timestamp, year, month, day, hour, minute, second variables at the provided config. diff --git a/src/libslic3r/Point.cpp b/src/libslic3r/Point.cpp index c2417d0dc9..f3ed413421 100644 --- a/src/libslic3r/Point.cpp +++ b/src/libslic3r/Point.cpp @@ -44,16 +44,6 @@ Pointf3s transform(const Pointf3s& points, const Transform3d& t) return ret_points; } -void Point::rotate(double angle) -{ - double cur_x = (double)(*this)(0); - double cur_y = (double)(*this)(1); - double s = ::sin(angle); - double c = ::cos(angle); - (*this)(0) = (coord_t)round(c * cur_x - s * cur_y); - (*this)(1) = (coord_t)round(c * cur_y + s * cur_x); -} - void Point::rotate(double angle, const Point ¢er) { double cur_x = (double)(*this)(0); diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 30a1a4942c..5082bb746f 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -105,6 +105,7 @@ public: template Point(const Eigen::MatrixBase &other) : Vec2crd(other) {} static Point new_scale(coordf_t x, coordf_t y) { return Point(coord_t(scale_(x)), coord_t(scale_(y))); } + static Point new_scale(const Vec2d &v) { return Point(coord_t(scale_(v.x())), coord_t(scale_(v.y()))); } // This method allows you to assign Eigen expressions to MyVectorType template @@ -121,7 +122,14 @@ public: Point& operator*=(const double &rhs) { (*this)(0) = coord_t((*this)(0) * rhs); (*this)(1) = coord_t((*this)(1) * rhs); return *this; } Point operator*(const double &rhs) { return Point((*this)(0) * rhs, (*this)(1) * rhs); } - void rotate(double angle); + void rotate(double angle) { this->rotate(std::cos(angle), std::sin(angle)); } + void rotate(double cos_a, double sin_a) { + double cur_x = (double)(*this)(0); + double cur_y = (double)(*this)(1); + (*this)(0) = (coord_t)round(cos_a * cur_x - sin_a * cur_y); + (*this)(1) = (coord_t)round(cos_a * cur_y + sin_a * cur_x); + } + void rotate(double angle, const Point ¢er); Point rotated(double angle) const { Point res(*this); res.rotate(angle); return res; } Point rotated(double angle, const Point ¢er) const { Point res(*this); res.rotate(angle, center); return res; } diff --git a/src/libslic3r/Polygon.cpp b/src/libslic3r/Polygon.cpp index 48e63dab31..13cdf65b4f 100644 --- a/src/libslic3r/Polygon.cpp +++ b/src/libslic3r/Polygon.cpp @@ -1,5 +1,6 @@ #include "BoundingBox.hpp" #include "ClipperUtils.hpp" +#include "Exception.hpp" #include "Polygon.hpp" #include "Polyline.hpp" @@ -16,7 +17,7 @@ Polyline Polygon::split_at_vertex(const Point &point) const for (const Point &pt : this->points) if (pt == point) return this->split_at_index(int(&pt - &this->points.front())); - throw std::invalid_argument("Point not found"); + throw Slic3r::InvalidArgument("Point not found"); return Polyline(); } @@ -259,6 +260,44 @@ Point Polygon::point_projection(const Point &point) const return proj; } +std::vector Polygon::parameter_by_length() const +{ + // Parametrize the polygon by its length. + std::vector lengths(points.size()+1, 0.); + for (size_t i = 1; i < points.size(); ++ i) + lengths[i] = lengths[i-1] + (points[i] - points[i-1]).cast().norm(); + lengths.back() = lengths[lengths.size()-2] + (points.front() - points.back()).cast().norm(); + return lengths; +} + +void Polygon::densify(float min_length, std::vector* lengths_ptr) +{ + std::vector lengths_local; + std::vector& lengths = lengths_ptr ? *lengths_ptr : lengths_local; + + if (! lengths_ptr) { + // Length parametrization has not been provided. Calculate our own. + lengths = this->parameter_by_length(); + } + + assert(points.size() == lengths.size() - 1); + + for (size_t j=1; j<=points.size(); ++j) { + bool last = j == points.size(); + int i = last ? 0 : j; + + if (lengths[j] - lengths[j-1] > min_length) { + Point diff = points[i] - points[j-1]; + float diff_len = lengths[j] - lengths[j-1]; + float r = (min_length/diff_len); + Point new_pt = points[j-1] + Point(r*diff[0], r*diff[1]); + points.insert(points.begin() + j, new_pt); + lengths.insert(lengths.begin() + j, lengths[j-1] + min_length); + } + } + assert(points.size() == lengths.size() - 1); +} + BoundingBox get_extents(const Points &points) { return BoundingBox(points); diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index 48dd1b64dd..f5aa289143 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -61,12 +61,14 @@ public: bool contains(const Point &point) const; Polygons simplify(double tolerance) const; void simplify(double tolerance, Polygons &polygons) const; + void densify(float min_length, std::vector* lengths = nullptr); void triangulate_convex(Polygons* polygons) const; Point centroid() const; Points concave_points(double angle = PI) const; Points convex_points(double angle = PI) const; // Projection of a point onto the polygon. Point point_projection(const Point &point) const; + std::vector parameter_by_length() const; }; inline bool operator==(const Polygon &lhs, const Polygon &rhs) { return lhs.points == rhs.points; } diff --git a/src/libslic3r/Polyline.cpp b/src/libslic3r/Polyline.cpp index 26aad83d2b..d24788c7bc 100644 --- a/src/libslic3r/Polyline.cpp +++ b/src/libslic3r/Polyline.cpp @@ -1,5 +1,6 @@ #include "BoundingBox.hpp" #include "Polyline.hpp" +#include "Exception.hpp" #include "ExPolygon.hpp" #include "ExPolygonCollection.hpp" #include "Line.hpp" @@ -19,7 +20,7 @@ Polyline::operator Polylines() const Polyline::operator Line() const { if (this->points.size() > 2) - throw std::invalid_argument("Can't convert polyline with more than two points to a line"); + throw Slic3r::InvalidArgument("Can't convert polyline with more than two points to a line"); return Line(this->points.front(), this->points.back()); } @@ -207,7 +208,7 @@ BoundingBox get_extents(const Polylines &polylines) const Point& leftmost_point(const Polylines &polylines) { if (polylines.empty()) - throw std::invalid_argument("leftmost_point() called on empty PolylineCollection"); + throw Slic3r::InvalidArgument("leftmost_point() called on empty PolylineCollection"); Polylines::const_iterator it = polylines.begin(); const Point *p = &it->leftmost_point(); for (++ it; it != polylines.end(); ++it) { diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 284c37435a..18a6e387cf 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1,5 +1,6 @@ #include +#include "Exception.hpp" #include "Preset.hpp" #include "AppConfig.hpp" @@ -107,7 +108,7 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem const std::string id = path.stem().string(); if (! boost::filesystem::exists(path)) { - throw std::runtime_error((boost::format("Cannot load Vendor Config Bundle `%1%`: File not found: `%2%`.") % id % path).str()); + throw Slic3r::RuntimeError((boost::format("Cannot load Vendor Config Bundle `%1%`: File not found: `%2%`.") % id % path).str()); } VendorProfile res(id); @@ -117,7 +118,7 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem { auto res = tree.find(key); if (res == tree.not_found()) { - throw std::runtime_error((boost::format("Vendor Config Bundle `%1%` is not valid: Missing secion or key: `%2%`.") % id % key).str()); + throw Slic3r::RuntimeError((boost::format("Vendor Config Bundle `%1%` is not valid: Missing secion or key: `%2%`.") % id % key).str()); } return res; }; @@ -129,7 +130,7 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem auto config_version_str = get_or_throw(vendor_section, "config_version")->second.data(); auto config_version = Semver::parse(config_version_str); if (! config_version) { - throw std::runtime_error((boost::format("Vendor Config Bundle `%1%` is not valid: Cannot parse config_version: `%2%`.") % id % config_version_str).str()); + throw Slic3r::RuntimeError((boost::format("Vendor Config Bundle `%1%` is not valid: Cannot parse config_version: `%2%`.") % id % config_version_str).str()); } else { res.config_version = std::move(*config_version); } @@ -672,9 +673,9 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri preset.file << "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed"; preset.loaded = true; } catch (const std::ifstream::failure &err) { - throw std::runtime_error(std::string("The selected preset cannot be loaded: ") + preset.file + "\n\tReason: " + err.what()); + throw Slic3r::RuntimeError(std::string("The selected preset cannot be loaded: ") + preset.file + "\n\tReason: " + err.what()); } catch (const std::runtime_error &err) { - throw std::runtime_error(std::string("Failed loading the preset file: ") + preset.file + "\n\tReason: " + err.what()); + throw Slic3r::RuntimeError(std::string("Failed loading the preset file: ") + preset.file + "\n\tReason: " + err.what()); } presets_loaded.emplace_back(preset); } catch (const std::runtime_error &err) { @@ -686,7 +687,7 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri std::sort(m_presets.begin() + m_num_default_presets, m_presets.end()); this->select_preset(first_visible_idx()); if (! errors_cummulative.empty()) - throw std::runtime_error(errors_cummulative); + throw Slic3r::RuntimeError(errors_cummulative); } // Load a preset from an already parsed config file, insert it into the sorted sequence of presets @@ -1557,10 +1558,10 @@ void PhysicalPrinterCollection::load_printers(const std::string& dir_path, const printer.loaded = true; } catch (const std::ifstream::failure& err) { - throw std::runtime_error(std::string("The selected preset cannot be loaded: ") + printer.file + "\n\tReason: " + err.what()); + throw Slic3r::RuntimeError(std::string("The selected preset cannot be loaded: ") + printer.file + "\n\tReason: " + err.what()); } catch (const std::runtime_error& err) { - throw std::runtime_error(std::string("Failed loading the preset file: ") + printer.file + "\n\tReason: " + err.what()); + throw Slic3r::RuntimeError(std::string("Failed loading the preset file: ") + printer.file + "\n\tReason: " + err.what()); } printers_loaded.emplace_back(printer); } @@ -1572,7 +1573,7 @@ void PhysicalPrinterCollection::load_printers(const std::string& dir_path, const m_printers.insert(m_printers.end(), std::make_move_iterator(printers_loaded.begin()), std::make_move_iterator(printers_loaded.end())); std::sort(m_printers.begin(), m_printers.end()); if (!errors_cummulative.empty()) - throw std::runtime_error(errors_cummulative); + throw Slic3r::RuntimeError(errors_cummulative); } // if there is saved user presets, contains information about "Print Host upload", diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index ac1b0a7176..c100e6971a 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -157,7 +157,7 @@ void PresetBundle::setup_directories() subdir.make_preferred(); if (! boost::filesystem::is_directory(subdir) && ! boost::filesystem::create_directory(subdir)) - throw std::runtime_error(std::string("Slic3r was unable to create its data directory at ") + subdir.string()); + throw Slic3r::RuntimeError(std::string("Slic3r was unable to create its data directory at ") + subdir.string()); } } @@ -207,7 +207,7 @@ void PresetBundle::load_presets(AppConfig &config, const std::string &preferred_ this->update_multi_material_filament_presets(); this->update_compatible(PresetSelectCompatibleType::Never); if (! errors_cummulative.empty()) - throw std::runtime_error(errors_cummulative); + throw Slic3r::RuntimeError(errors_cummulative); this->load_selections(config, preferred_model_id); } @@ -679,21 +679,21 @@ void PresetBundle::load_config_file(const std::string &path) boost::nowide::ifstream ifs(path); boost::property_tree::read_ini(ifs, tree); } catch (const std::ifstream::failure &err) { - throw std::runtime_error(std::string("The Config Bundle cannot be loaded: ") + path + "\n\tReason: " + err.what()); + throw Slic3r::RuntimeError(std::string("The Config Bundle cannot be loaded: ") + path + "\n\tReason: " + err.what()); } catch (const boost::property_tree::file_parser_error &err) { - throw std::runtime_error((boost::format("Failed loading the Config Bundle \"%1%\": %2% at line %3%") + throw Slic3r::RuntimeError((boost::format("Failed loading the Config Bundle \"%1%\": %2% at line %3%") % err.filename() % err.message() % err.line()).str()); } catch (const std::runtime_error &err) { - throw std::runtime_error(std::string("Failed loading the preset file: ") + path + "\n\tReason: " + err.what()); + throw Slic3r::RuntimeError(std::string("Failed loading the preset file: ") + path + "\n\tReason: " + err.what()); } // 2) Continue based on the type of the configuration file. ConfigFileType config_file_type = guess_config_file_type(tree); switch (config_file_type) { case CONFIG_FILE_TYPE_UNKNOWN: - throw std::runtime_error(std::string("Unknown configuration file type: ") + path); + throw Slic3r::RuntimeError(std::string("Unknown configuration file type: ") + path); case CONFIG_FILE_TYPE_APP_CONFIG: - throw std::runtime_error(std::string("Invalid configuration file: ") + path + ". This is an application config file."); + throw Slic3r::RuntimeError(std::string("Invalid configuration file: ") + path + ". This is an application config file."); case CONFIG_FILE_TYPE_CONFIG: { // Initialize a config from full defaults. diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index c405c764ef..a360d840f4 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1,5 +1,6 @@ #include "clipper/clipper_z.hpp" +#include "Exception.hpp" #include "Print.hpp" #include "BoundingBox.hpp" #include "ClipperUtils.hpp" @@ -1507,7 +1508,7 @@ BoundingBox Print::total_bounding_box() const double Print::skirt_first_layer_height() const { if (m_objects.empty()) - throw std::invalid_argument("skirt_first_layer_height() can't be called without PrintObjects"); + throw Slic3r::InvalidArgument("skirt_first_layer_height() can't be called without PrintObjects"); return m_objects.front()->config().get_abs_value("first_layer_height"); } @@ -1583,7 +1584,7 @@ void Print::auto_assign_extruders(ModelObject* model_object) const // Slicing process, running at a background thread. void Print::process() { - BOOST_LOG_TRIVIAL(info) << "Staring the slicing process." << log_memory_info(); + BOOST_LOG_TRIVIAL(info) << "Starting the slicing process." << log_memory_info(); for (PrintObject *obj : m_objects) obj->make_perimeters(); this->set_status(70, L("Infilling layers")); @@ -1603,7 +1604,7 @@ void Print::process() // Initialize the tool ordering, so it could be used by the G-code preview slider for planning tool changes and filament switches. m_tool_ordering = ToolOrdering(*this, -1, false); if (m_tool_ordering.empty() || m_tool_ordering.last_extruder() == unsigned(-1)) - throw std::runtime_error("The print is empty. The model is not printable with current print settings."); + throw Slic3r::SlicingError("The print is empty. The model is not printable with current print settings."); } this->set_done(psWipeTower); } @@ -2173,7 +2174,7 @@ void Print::_make_wipe_tower() wipe_tower.set_layer(float(m_wipe_tower_data.tool_ordering.back().print_z), float(layer_height), 0, false, true); } m_wipe_tower_data.final_purge = Slic3r::make_unique( - wipe_tower.tool_change((unsigned int)-1, false)); + wipe_tower.tool_change((unsigned int)(-1))); m_wipe_tower_data.used_filament = wipe_tower.get_used_filament(); m_wipe_tower_data.number_of_toolchanges = wipe_tower.get_number_of_toolchanges(); diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 98a1314112..a389ef00da 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -30,8 +30,10 @@ enum class SlicingMode : uint32_t; class Layer; class SupportLayer; -namespace FillAdaptive_Internal { +namespace FillAdaptive { struct Octree; + struct OctreeDeleter; + using OctreePtr = std::unique_ptr; }; // Print step IDs for keeping track of the print state. @@ -239,7 +241,7 @@ private: void discover_horizontal_shells(); void combine_infill(); void _generate_support_material(); - std::pair, std::unique_ptr> prepare_adaptive_infill_data(); + std::pair prepare_adaptive_infill_data(); // XYZ in scaled coordinates Vec3crd m_size; diff --git a/src/libslic3r/PrintBase.cpp b/src/libslic3r/PrintBase.cpp index ab6ca3d350..7cdf6448c3 100644 --- a/src/libslic3r/PrintBase.cpp +++ b/src/libslic3r/PrintBase.cpp @@ -1,3 +1,4 @@ +#include "Exception.hpp" #include "PrintBase.hpp" #include @@ -68,7 +69,7 @@ std::string PrintBase::output_filename(const std::string &format, const std::str filename = boost::filesystem::change_extension(filename, default_ext); return filename.string(); } catch (std::runtime_error &err) { - throw std::runtime_error(L("Failed processing of the output_filename_format template.") + "\n" + err.what()); + throw Slic3r::RuntimeError(L("Failed processing of the output_filename_format template.") + "\n" + err.what()); } } diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index e2dba5bb2a..4b83062a0c 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1,3 +1,4 @@ +#include "Exception.hpp" #include "Print.hpp" #include "BoundingBox.hpp" #include "ClipperUtils.hpp" @@ -8,6 +9,7 @@ #include "SupportMaterial.hpp" #include "Surface.hpp" #include "Slicing.hpp" +#include "Tesselate.hpp" #include "Utils.hpp" #include "AABBTreeIndirect.hpp" #include "Fill/FillAdaptive.hpp" @@ -138,7 +140,7 @@ void PrintObject::slice() } }); if (m_layers.empty()) - throw std::runtime_error("No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n"); + throw Slic3r::SlicingError("No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n"); this->set_done(posSlice); } @@ -426,81 +428,52 @@ void PrintObject::generate_support_material() // therefore they cannot be printed without supports. for (const Layer *layer : m_layers) if (layer->empty()) - throw std::runtime_error("Levitating objects cannot be printed without supports."); + throw Slic3r::SlicingError("Levitating objects cannot be printed without supports."); #endif } this->set_done(posSupportMaterial); } } -//#define ADAPTIVE_SUPPORT_SIMPLE - -std::pair, std::unique_ptr> PrintObject::prepare_adaptive_infill_data() +std::pair PrintObject::prepare_adaptive_infill_data() { - using namespace FillAdaptive_Internal; + using namespace FillAdaptive; auto [adaptive_line_spacing, support_line_spacing] = adaptive_fill_line_spacing(*this); + if (adaptive_line_spacing == 0. && support_line_spacing == 0. || this->layers().empty()) + return std::make_pair(OctreePtr(), OctreePtr()); - std::unique_ptr adaptive_fill_octree = {}, support_fill_octree = {}; + indexed_triangle_set mesh = this->model_object()->raw_indexed_triangle_set(); + // Rotate mesh and build octree on it with axis-aligned (standart base) cubes. + Transform3d m = m_trafo; + m.pretranslate(Vec3d(- unscale(m_center_offset.x()), - unscale(m_center_offset.y()), 0)); + auto to_octree = transform_to_octree().toRotationMatrix(); + its_transform(mesh, to_octree * m, true); - if (adaptive_line_spacing == 0. && support_line_spacing == 0.) - return std::make_pair(std::move(adaptive_fill_octree), std::move(support_fill_octree)); + // Triangulate internal bridging surfaces. + std::vector> overhangs(this->layers().size()); + tbb::parallel_for( + tbb::blocked_range(0, int(m_layers.size()) - 1), + [this, &to_octree, &overhangs](const tbb::blocked_range &range) { + std::vector &out = overhangs[range.begin()]; + for (int idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { + m_print->throw_if_canceled(); + const Layer *layer = this->layers()[idx_layer]; + for (const LayerRegion *layerm : layer->regions()) + for (const Surface &surface : layerm->fill_surfaces.surfaces) + if (surface.surface_type == stInternalBridge) + append(out, triangulate_expolygon_3d(surface.expolygon, layer->bottom_z())); + } + for (Vec3d &p : out) + p = (to_octree * p).eval(); + }); + // and gather them. + for (size_t i = 1; i < overhangs.size(); ++ i) + append(overhangs.front(), std::move(overhangs[i])); - TriangleMesh mesh = this->model_object()->raw_mesh(); - mesh.transform(m_trafo, true); - // Apply XY shift - mesh.translate(- unscale(m_center_offset.x()), - unscale(m_center_offset.y()), 0); - // Center of the first cube in octree - Vec3d mesh_origin = mesh.bounding_box().center(); - -#ifdef ADAPTIVE_SUPPORT_SIMPLE - if (mesh.its.vertices.empty()) - { - mesh.require_shared_vertices(); - } - - Vec3f vertical(0, 0, 1); - - indexed_triangle_set its_set; - its_set.vertices = mesh.its.vertices; - - // Filter out non overhanging faces - for (size_t i = 0; i < mesh.its.indices.size(); ++i) { - stl_triangle_vertex_indices vertex_idx = mesh.its.indices[i]; - - auto its_calculate_normal = [](const stl_triangle_vertex_indices &index, const std::vector &vertices) { - stl_normal normal = (vertices[index.y()] - vertices[index.x()]).cross(vertices[index.z()] - vertices[index.x()]); - return normal; - }; - - stl_normal normal = its_calculate_normal(vertex_idx, mesh.its.vertices); - stl_normalize_vector(normal); - - if(normal.dot(vertical) >= 0.707) { - its_set.indices.push_back(vertex_idx); - } - } - - mesh = TriangleMesh(its_set); - -#ifdef SLIC3R_DEBUG_SLICE_PROCESSING - Slic3r::store_stl(debug_out_path("overhangs.stl").c_str(), &mesh, false); -#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ -#endif /* ADAPTIVE_SUPPORT_SIMPLE */ - - Vec3d rotation = Vec3d((5.0 * M_PI) / 4.0, Geometry::deg2rad(215.264), M_PI / 6.0); - Transform3d rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation, Vec3d::Ones(), Vec3d::Ones()).inverse(); - - if (adaptive_line_spacing != 0.) { - // Rotate mesh and build octree on it with axis-aligned (standart base) cubes - mesh.transform(rotation_matrix); - adaptive_fill_octree = FillAdaptive::build_octree(mesh, adaptive_line_spacing, rotation_matrix * mesh_origin); - } - - if (support_line_spacing != 0.) - support_fill_octree = FillSupportCubic::build_octree(mesh, support_line_spacing, rotation_matrix * mesh_origin, rotation_matrix); - - return std::make_pair(std::move(adaptive_fill_octree), std::move(support_fill_octree)); + return std::make_pair( + adaptive_line_spacing ? build_octree(mesh, overhangs.front(), adaptive_line_spacing, false) : OctreePtr(), + support_line_spacing ? build_octree(mesh, overhangs.front(), support_line_spacing, true) : OctreePtr()); } void PrintObject::clear_layers() @@ -2836,8 +2809,8 @@ void PrintObject::project_and_append_custom_facets( // Calculate how to move points on triangle sides per unit z increment. Vec2f ta(trianglef[1] - trianglef[0]); Vec2f tb(trianglef[2] - trianglef[0]); - ta *= 1./(facet[1].z() - facet[0].z()); - tb *= 1./(facet[2].z() - facet[0].z()); + ta *= 1.f/(facet[1].z() - facet[0].z()); + tb *= 1.f/(facet[2].z() - facet[0].z()); // Projection on current slice will be build directly in place. LightPolygon* proj = &projections_of_triangles[idx].polygons[0]; @@ -2848,7 +2821,7 @@ void PrintObject::project_and_append_custom_facets( // Project a sub-polygon on all slices intersecting the triangle. while (it != layers().end()) { - const float z = (*it)->slice_z; + const float z = float((*it)->slice_z); // Projections of triangle sides intersections with slices. // a moves along one side, b tracks the other. @@ -2860,7 +2833,7 @@ void PrintObject::project_and_append_custom_facets( if (z > facet[1].z() && ! passed_first) { proj->add(trianglef[1]); ta = trianglef[2]-trianglef[1]; - ta *= 1./(facet[2].z() - facet[1].z()); + ta *= 1.f/(facet[2].z() - facet[1].z()); passed_first = true; } diff --git a/src/libslic3r/PrintRegion.cpp b/src/libslic3r/PrintRegion.cpp index b3ac6a4a5b..2a75cd621d 100644 --- a/src/libslic3r/PrintRegion.cpp +++ b/src/libslic3r/PrintRegion.cpp @@ -1,3 +1,4 @@ +#include "Exception.hpp" #include "Print.hpp" namespace Slic3r { @@ -13,7 +14,7 @@ unsigned int PrintRegion::extruder(FlowRole role) const else if (role == frSolidInfill || role == frTopSolidInfill) extruder = m_config.solid_infill_extruder; else - throw std::invalid_argument("Unknown role"); + throw Slic3r::InvalidArgument("Unknown role"); return extruder; } @@ -40,7 +41,7 @@ Flow PrintRegion::flow(FlowRole role, double layer_height, bool bridge, bool fir } else if (role == frTopSolidInfill) { config_width = m_config.top_infill_extrusion_width; } else { - throw std::invalid_argument("Unknown role"); + throw Slic3r::InvalidArgument("Unknown role"); } } diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index eaf9698198..d94bc682b3 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -187,7 +188,7 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) } if (MeshBoolean::cgal::does_self_intersect(*holes_mesh_cgal)) - throw std::runtime_error(L("Too much overlapping holes.")); + throw Slic3r::SlicingError(L("Too many overlapping holes.")); auto hollowed_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal(hollowed_mesh); @@ -195,7 +196,7 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) MeshBoolean::cgal::minus(*hollowed_mesh_cgal, *holes_mesh_cgal); hollowed_mesh = MeshBoolean::cgal::cgal_to_triangle_mesh(*hollowed_mesh_cgal); } catch (const std::runtime_error &) { - throw std::runtime_error(L( + throw Slic3r::SlicingError(L( "Drilling holes into the mesh failed. " "This is usually caused by broken model. Try to fix it first.")); } @@ -241,7 +242,7 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po) if(slindex_it == po.m_slice_index.end()) //TRN To be shown at the status bar on SLA slicing error. - throw std::runtime_error( + throw Slic3r::RuntimeError( L("Slicing had to be stopped due to an internal error: " "Inconsistent slice index.")); @@ -445,7 +446,7 @@ void SLAPrint::Steps::generate_pad(SLAPrintObject &po) { auto &pad_mesh = po.m_supportdata->support_tree_ptr->retrieve_mesh(sla::MeshType::Pad); if (!validate_pad(pad_mesh, pcfg)) - throw std::runtime_error( + throw Slic3r::SlicingError( L("No pad can be generated for this model with the " "current configuration")); @@ -613,7 +614,7 @@ void SLAPrint::Steps::initialize_printer_input() for(const SliceRecord& slicerecord : o->get_slice_index()) { if (!slicerecord.is_valid()) - throw std::runtime_error( + throw Slic3r::SlicingError( L("There are unprintable objects. Try to " "adjust support settings to make the " "objects printable.")); diff --git a/src/libslic3r/Semver.hpp b/src/libslic3r/Semver.hpp index 24ca74f837..f55fa9f9f4 100644 --- a/src/libslic3r/Semver.hpp +++ b/src/libslic3r/Semver.hpp @@ -10,6 +10,8 @@ #include "semver/semver.h" +#include "Exception.hpp" + namespace Slic3r { @@ -38,7 +40,7 @@ public: { auto parsed = parse(str); if (! parsed) { - throw std::runtime_error(std::string("Could not parse version string: ") + str); + throw Slic3r::RuntimeError(std::string("Could not parse version string: ") + str); } ver = parsed->ver; parsed->ver = semver_zero(); diff --git a/src/libslic3r/ShortestPath.cpp b/src/libslic3r/ShortestPath.cpp index 314bbf7163..3d5903df13 100644 --- a/src/libslic3r/ShortestPath.cpp +++ b/src/libslic3r/ShortestPath.cpp @@ -1973,4 +1973,59 @@ std::vector chain_print_object_instances(const Print &prin return out; } +Polylines chain_lines(const std::vector &lines, const double point_distance_epsilon) +{ + // Create line end point lookup. + struct LineEnd { + LineEnd(const Line *line, bool start) : line(line), start(start) {} + const Line *line; + // Is it the start or end point? + bool start; + const Point& point() const { return start ? line->a : line->b; } + const Point& other_point() const { return start ? line->b : line->a; } + LineEnd other_end() const { return LineEnd(line, ! start); } + bool operator==(const LineEnd &rhs) const { return this->line == rhs.line && this->start == rhs.start; } + }; + struct LineEndAccessor { + const Point* operator()(const LineEnd &pt) const { return &pt.point(); } + }; + typedef ClosestPointInRadiusLookup ClosestPointLookupType; + ClosestPointLookupType closest_end_point_lookup(point_distance_epsilon); + for (const Line &line : lines) { + closest_end_point_lookup.insert(LineEnd(&line, true)); + closest_end_point_lookup.insert(LineEnd(&line, false)); + } + + // Chain the lines. + std::vector line_consumed(lines.size(), false); + static const double point_distance_epsilon2 = point_distance_epsilon * point_distance_epsilon; + Polylines out; + for (const Line &seed : lines) + if (! line_consumed[&seed - lines.data()]) { + line_consumed[&seed - lines.data()] = true; + closest_end_point_lookup.erase(LineEnd(&seed, false)); + closest_end_point_lookup.erase(LineEnd(&seed, true)); + Polyline pl { seed.a, seed.b }; + for (size_t round = 0; round < 2; ++ round) { + for (;;) { + auto [line_end, dist2] = closest_end_point_lookup.find(pl.last_point()); + if (line_end == nullptr || dist2 >= point_distance_epsilon2) + // Cannot extent in this direction. + break; + // Average the last point. + pl.points.back() = (0.5 * (pl.points.back().cast() + line_end->point().cast())).cast(); + // and extend with the new line segment. + pl.points.emplace_back(line_end->other_point()); + closest_end_point_lookup.erase(*line_end); + closest_end_point_lookup.erase(line_end->other_end()); + line_consumed[line_end->line - lines.data()] = true; + } + // reverse and try the oter direction. + pl.reverse(); + } + out.emplace_back(std::move(pl)); + } + return out; +} + } // namespace Slic3r diff --git a/src/libslic3r/ShortestPath.hpp b/src/libslic3r/ShortestPath.hpp index 65d8b7f239..14912ee857 100644 --- a/src/libslic3r/ShortestPath.hpp +++ b/src/libslic3r/ShortestPath.hpp @@ -33,6 +33,8 @@ class Print; struct PrintInstance; std::vector chain_print_object_instances(const Print &print); +// Chain lines into polylines. +Polylines chain_lines(const std::vector &lines, const double point_distance_epsilon); } // namespace Slic3r diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 49fc625af9..8ba34e5160 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -1,3 +1,4 @@ +#include "Exception.hpp" #include "TriangleMesh.hpp" #include "ClipperUtils.hpp" #include "Geometry.hpp" @@ -420,7 +421,7 @@ std::deque TriangleMesh::find_unvisited_neighbors(std::vectorrepaired) - throw std::runtime_error("find_unvisited_neighbors() requires repair()"); + throw Slic3r::RuntimeError("find_unvisited_neighbors() requires repair()"); // If the visited list is empty, populate it with false for every facet. if (facet_visited.empty()) @@ -683,7 +684,7 @@ void TriangleMeshSlicer::init(const TriangleMesh *_mesh, throw_on_cancel_callbac { mesh = _mesh; if (! mesh->has_shared_vertices()) - throw std::invalid_argument("TriangleMeshSlicer was passed a mesh without shared vertices."); + throw Slic3r::InvalidArgument("TriangleMeshSlicer was passed a mesh without shared vertices."); throw_on_cancel(); facets_edges.assign(_mesh->stl.stats.number_of_facets * 3, -1); diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index ef531169d1..99b105a6b1 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -348,11 +348,11 @@ inline std::string get_time_dhm(float time_in_secs) char buffer[64]; if (days > 0) - ::sprintf(buffer, "%dd %dh %dm %ds", days, hours, minutes, (int)time_in_secs); + ::sprintf(buffer, "%dd %dh %dm", days, hours, minutes); else if (hours > 0) - ::sprintf(buffer, "%dh %dm %ds", hours, minutes, (int)time_in_secs); + ::sprintf(buffer, "%dh %dm", hours, minutes); else if (minutes > 0) - ::sprintf(buffer, "%dm %ds", minutes, (int)time_in_secs); + ::sprintf(buffer, "%dm", minutes); return buffer; } diff --git a/src/libslic3r/Zipper.cpp b/src/libslic3r/Zipper.cpp index 02f022083b..7a95829cd0 100644 --- a/src/libslic3r/Zipper.cpp +++ b/src/libslic3r/Zipper.cpp @@ -1,5 +1,6 @@ #include +#include "Exception.hpp" #include "Zipper.hpp" #include "miniz_extension.hpp" #include @@ -29,7 +30,7 @@ public: SLIC3R_NORETURN void blow_up() const { - throw std::runtime_error(formatted_errorstr()); + throw Slic3r::RuntimeError(formatted_errorstr()); } bool is_alive() diff --git a/src/platform/msw/PrusaSlicer-gcodeviewer.rc.in b/src/platform/msw/PrusaSlicer-gcodeviewer.rc.in index 7f4e5a15c3..eed737cb77 100644 --- a/src/platform/msw/PrusaSlicer-gcodeviewer.rc.in +++ b/src/platform/msw/PrusaSlicer-gcodeviewer.rc.in @@ -12,7 +12,7 @@ PRODUCTVERSION @SLIC3R_RC_VERSION@ VALUE "ProductName", "@SLIC3R_APP_NAME@ G-code Viewer" VALUE "ProductVersion", "@SLIC3R_BUILD_ID@" VALUE "InternalName", "@SLIC3R_APP_NAME@ G-code Viewer" - VALUE "LegalCopyright", "Copyright \251 2016-2020 Prusa Research, \251 2011-2018 Alessandro Ranelucci" + VALUE "LegalCopyright", "Copyright \251 2016-2020 Prusa Research, \251 2011-2018 Alessandro Ranellucci" VALUE "OriginalFilename", "prusa-gcodeviewer.exe" } } diff --git a/src/platform/msw/PrusaSlicer.rc.in b/src/platform/msw/PrusaSlicer.rc.in index fb75305c8a..a4520c6d73 100644 --- a/src/platform/msw/PrusaSlicer.rc.in +++ b/src/platform/msw/PrusaSlicer.rc.in @@ -12,7 +12,7 @@ PRODUCTVERSION @SLIC3R_RC_VERSION@ VALUE "ProductName", "@SLIC3R_APP_NAME@" VALUE "ProductVersion", "@SLIC3R_BUILD_ID@" VALUE "InternalName", "@SLIC3R_APP_NAME@" - VALUE "LegalCopyright", "Copyright \251 2016-2020 Prusa Research, \251 2011-2018 Alessandro Ranelucci" + VALUE "LegalCopyright", "Copyright \251 2016-2020 Prusa Research, \251 2011-2018 Alessandro Ranellucci" VALUE "OriginalFilename", "prusa-slicer.exe" } } diff --git a/src/slic3r/Config/Snapshot.cpp b/src/slic3r/Config/Snapshot.cpp index 30596b614d..45dc998741 100644 --- a/src/slic3r/Config/Snapshot.cpp +++ b/src/slic3r/Config/Snapshot.cpp @@ -315,7 +315,7 @@ size_t SnapshotDB::load_db() // Sort the snapshots by their date/time. std::sort(m_snapshots.begin(), m_snapshots.end(), [](const Snapshot &s1, const Snapshot &s2) { return s1.time_captured < s2.time_captured; }); if (! errors_cummulative.empty()) - throw std::runtime_error(errors_cummulative); + throw Slic3r::RuntimeError(errors_cummulative); return m_snapshots.size(); } @@ -339,7 +339,7 @@ static void copy_config_dir_single_level(const boost::filesystem::path &path_src { if (! boost::filesystem::is_directory(path_dst) && ! boost::filesystem::create_directory(path_dst)) - throw std::runtime_error(std::string("Slic3r was unable to create a directory at ") + path_dst.string()); + throw Slic3r::RuntimeError(std::string("Slic3r was unable to create a directory at ") + path_dst.string()); for (auto &dir_entry : boost::filesystem::directory_iterator(path_src)) if (Slic3r::is_ini_file(dir_entry)) @@ -429,7 +429,7 @@ const Snapshot& SnapshotDB::restore_snapshot(const std::string &id, AppConfig &a this->restore_snapshot(snapshot, app_config); return snapshot; } - throw std::runtime_error(std::string("Snapshot with id " + id + " was not found.")); + throw Slic3r::RuntimeError(std::string("Snapshot with id " + id + " was not found.")); } void SnapshotDB::restore_snapshot(const Snapshot &snapshot, AppConfig &app_config) @@ -501,7 +501,7 @@ boost::filesystem::path SnapshotDB::create_db_dir() subdir.make_preferred(); if (! boost::filesystem::is_directory(subdir) && ! boost::filesystem::create_directory(subdir)) - throw std::runtime_error(std::string("Slic3r was unable to create a directory at ") + subdir.string()); + throw Slic3r::RuntimeError(std::string("Slic3r was unable to create a directory at ") + subdir.string()); } return snapshots_dir; } diff --git a/src/slic3r/Config/Version.cpp b/src/slic3r/Config/Version.cpp index d00e4a2abf..04ce05ab5b 100644 --- a/src/slic3r/Config/Version.cpp +++ b/src/slic3r/Config/Version.cpp @@ -324,7 +324,7 @@ std::vector Index::load_db() } if (! errors_cummulative.empty()) - throw std::runtime_error(errors_cummulative); + throw Slic3r::RuntimeError(errors_cummulative); return index_db; } diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 70fec670c7..ca96af49c1 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -1926,7 +1926,7 @@ void _3DScene::extrusionentity_to_verts(const ExtrusionEntity *extrusion_entity, if (extrusion_entity_collection != nullptr) extrusionentity_to_verts(*extrusion_entity_collection, print_z, copy, volume); else { - throw std::runtime_error("Unexpected extrusion_entity type in to_verts()"); + throw Slic3r::RuntimeError("Unexpected extrusion_entity type in to_verts()"); } } } diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 9675db10ef..605a98eea0 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -41,6 +41,36 @@ namespace Slic3r { +bool SlicingProcessCompletedEvent::critical_error() const +{ + try { + this->rethrow_exception(); + } catch (const Slic3r::SlicingError &ex) { + // Exception derived from SlicingError is non-critical. + return false; + } catch (...) { + } + return true; +} + +std::string SlicingProcessCompletedEvent::format_error_message() const +{ + std::string error; + try { + this->rethrow_exception(); + } catch (const std::bad_alloc& ex) { + wxString errmsg = GUI::from_u8((boost::format(_utf8(L("%s has encountered an error. It was likely caused by running out of memory. " + "If you are sure you have enough RAM on your system, this may also be a bug and we would " + "be glad if you reported it."))) % SLIC3R_APP_NAME).str()); + error = std::string(errmsg.ToUTF8()) + "\n\n" + std::string(ex.what()); + } catch (std::exception &ex) { + error = ex.what(); + } catch (...) { + error = "Unknown C++ exception."; + } + return error; +} + BackgroundSlicingProcess::BackgroundSlicingProcess() { boost::filesystem::path temp_path(wxStandardPaths::Get().GetTempDir().utf8_str().data()); @@ -109,19 +139,19 @@ void BackgroundSlicingProcess::process_fff() switch (copy_ret_val) { case SUCCESS: break; // no error case FAIL_COPY_FILE: - throw std::runtime_error(_utf8(L("Copying of the temporary G-code to the output G-code failed. Maybe the SD card is write locked?"))); + throw Slic3r::RuntimeError(_utf8(L("Copying of the temporary G-code to the output G-code failed. Maybe the SD card is write locked?"))); break; case FAIL_FILES_DIFFERENT: - throw std::runtime_error((boost::format(_utf8(L("Copying of the temporary G-code to the output G-code failed. There might be problem with target device, please try exporting again or using different device. The corrupted output G-code is at %1%.tmp."))) % export_path).str()); + throw Slic3r::RuntimeError((boost::format(_utf8(L("Copying of the temporary G-code to the output G-code failed. There might be problem with target device, please try exporting again or using different device. The corrupted output G-code is at %1%.tmp."))) % export_path).str()); break; case FAIL_RENAMING: - throw std::runtime_error((boost::format(_utf8(L("Renaming of the G-code after copying to the selected destination folder has failed. Current path is %1%.tmp. Please try exporting again."))) % export_path).str()); + throw Slic3r::RuntimeError((boost::format(_utf8(L("Renaming of the G-code after copying to the selected destination folder has failed. Current path is %1%.tmp. Please try exporting again."))) % export_path).str()); break; case FAIL_CHECK_ORIGIN_NOT_OPENED: - throw std::runtime_error((boost::format(_utf8(L("Copying of the temporary G-code has finished but the original code at %1% couldn't be opened during copy check. The output G-code is at %2%.tmp."))) % m_temp_output_path % export_path).str()); + throw Slic3r::RuntimeError((boost::format(_utf8(L("Copying of the temporary G-code has finished but the original code at %1% couldn't be opened during copy check. The output G-code is at %2%.tmp."))) % m_temp_output_path % export_path).str()); break; case FAIL_CHECK_TARGET_NOT_OPENED: - throw std::runtime_error((boost::format(_utf8(L("Copying of the temporary G-code has finished but the exported code couldn't be opened during copy check. The output G-code is at %1%.tmp."))) % export_path).str()); + throw Slic3r::RuntimeError((boost::format(_utf8(L("Copying of the temporary G-code has finished but the exported code couldn't be opened during copy check. The output G-code is at %1%.tmp."))) % export_path).str()); break; default: BOOST_LOG_TRIVIAL(warning) << "Unexpected fail code(" << (int)copy_ret_val << ") durring copy_file() to " << export_path << "."; @@ -210,7 +240,7 @@ void BackgroundSlicingProcess::thread_proc() // Process the background slicing task. m_state = STATE_RUNNING; lck.unlock(); - std::string error; + std::exception_ptr exception; try { assert(m_print != nullptr); switch(m_print->technology()) { @@ -221,15 +251,8 @@ void BackgroundSlicingProcess::thread_proc() } catch (CanceledException & /* ex */) { // Canceled, this is all right. assert(m_print->canceled()); - } catch (const std::bad_alloc& ex) { - wxString errmsg = GUI::from_u8((boost::format(_utf8(L("%s has encountered an error. It was likely caused by running out of memory. " - "If you are sure you have enough RAM on your system, this may also be a bug and we would " - "be glad if you reported it."))) % SLIC3R_APP_NAME).str()); - error = std::string(errmsg.ToUTF8()) + "\n\n" + std::string(ex.what()); - } catch (std::exception &ex) { - error = ex.what(); } catch (...) { - error = "Unknown C++ exception."; + exception = std::current_exception(); } m_print->finalize(); lck.lock(); @@ -237,9 +260,9 @@ void BackgroundSlicingProcess::thread_proc() if (m_print->cancel_status() != Print::CANCELED_INTERNAL) { // Only post the canceled event, if canceled by user. // Don't post the canceled event, if canceled from Print::apply(). - wxCommandEvent evt(m_event_finished_id); - evt.SetString(GUI::from_u8(error)); - evt.SetInt(m_print->canceled() ? -1 : (error.empty() ? 1 : 0)); + SlicingProcessCompletedEvent evt(m_event_finished_id, 0, + (m_state == STATE_CANCELED) ? SlicingProcessCompletedEvent::Cancelled : + exception ? SlicingProcessCompletedEvent::Error : SlicingProcessCompletedEvent::Finished, exception); wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt.Clone()); } m_print->restart(); @@ -299,7 +322,7 @@ bool BackgroundSlicingProcess::start() // The background processing thread is already running. return false; if (! this->idle()) - throw std::runtime_error("Cannot start a background task, the worker thread is not idle."); + throw Slic3r::RuntimeError("Cannot start a background task, the worker thread is not idle."); m_state = STATE_STARTED; m_print->set_cancel_callback([this](){ this->stop_internal(); }); lck.unlock(); @@ -494,7 +517,7 @@ void BackgroundSlicingProcess::prepare_upload() if (m_print == m_fff_print) { m_print->set_status(95, _utf8(L("Running post-processing scripts"))); if (copy_file(m_temp_output_path, source_path.string()) != SUCCESS) { - throw std::runtime_error(_utf8(L("Copying of the temporary G-code to the output G-code failed"))); + throw Slic3r::RuntimeError(_utf8(L("Copying of the temporary G-code to the output G-code failed"))); } run_post_process_scripts(source_path.string(), m_fff_print->config()); m_upload_job.upload_data.upload_path = m_fff_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string()); diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp index 9fe1157b6e..1b2687e63a 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp @@ -37,6 +37,36 @@ public: PrintBase::SlicingStatus status; }; +class SlicingProcessCompletedEvent : public wxEvent +{ +public: + enum StatusType { + Finished, + Cancelled, + Error + }; + + SlicingProcessCompletedEvent(wxEventType eventType, int winid, StatusType status, std::exception_ptr exception) : + wxEvent(winid, eventType), m_status(status), m_exception(exception) {} + virtual wxEvent* Clone() const { return new SlicingProcessCompletedEvent(*this); } + + StatusType status() const { return m_status; } + bool finished() const { return m_status == Finished; } + bool success() const { return m_status == Finished; } + bool cancelled() const { return m_status == Cancelled; } + bool error() const { return m_status == Error; } + // Unhandled error produced by stdlib or a Win32 structured exception, or unhandled Slic3r's own critical exception. + bool critical_error() const; + // Only valid if error() + void rethrow_exception() const { assert(this->error()); assert(m_exception); std::rethrow_exception(m_exception); } + // Produce a human readable message to be displayed by a notification or a message box. + std::string format_error_message() const; + +private: + StatusType m_status; + std::exception_ptr m_exception; +}; + wxDEFINE_EVENT(EVT_SLICING_UPDATE, SlicingStatusEvent); // Print step IDs for keeping track of the print state. diff --git a/src/slic3r/GUI/ConfigExceptions.hpp b/src/slic3r/GUI/ConfigExceptions.hpp index 9038d3445e..181442d4e3 100644 --- a/src/slic3r/GUI/ConfigExceptions.hpp +++ b/src/slic3r/GUI/ConfigExceptions.hpp @@ -1,15 +1,15 @@ #include namespace Slic3r { -class ConfigError : public std::runtime_error { -using std::runtime_error::runtime_error; +class ConfigError : public Slic3r::RuntimeError { + using Slic3r::RuntimeError::RuntimeError; }; namespace GUI { class ConfigGUITypeError : public ConfigError { -using ConfigError::ConfigError; + using ConfigError::ConfigError; }; -} -} +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 2cedbfdf78..c98b736b7c 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -123,7 +123,7 @@ Bundle& BundleMap::prusa_bundle() { auto it = find(PresetBundle::PRUSA_BUNDLE); if (it == end()) { - throw std::runtime_error("ConfigWizard: Internal error in BundleMap: PRUSA_BUNDLE not loaded"); + throw Slic3r::RuntimeError("ConfigWizard: Internal error in BundleMap: PRUSA_BUNDLE not loaded"); } return it->second; diff --git a/src/slic3r/GUI/FirmwareDialog.cpp b/src/slic3r/GUI/FirmwareDialog.cpp index fe7ff4e5de..5441c84ed5 100644 --- a/src/slic3r/GUI/FirmwareDialog.cpp +++ b/src/slic3r/GUI/FirmwareDialog.cpp @@ -766,7 +766,7 @@ const char* FirmwareDialog::priv::avr109_dev_name(Avr109Pid usb_pid) { return "Original Prusa CW1"; break; - default: throw std::runtime_error((boost::format("Invalid avr109 device USB PID: %1%") % usb_pid.boot).str()); + default: throw Slic3r::RuntimeError((boost::format("Invalid avr109 device USB PID: %1%") % usb_pid.boot).str()); } } diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 5984afaa45..6bb62dbf05 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -129,16 +129,19 @@ void GCodeViewer::TBuffer::reset() { // release gpu memory vertices.reset(); - indices.reset(); + for (IBuffer& buffer : indices) { + buffer.reset(); + } // release cpu memory + indices = std::vector(); paths = std::vector(); render_paths = std::vector(); } -void GCodeViewer::TBuffer::add_path(const GCodeProcessor::MoveVertex& move, unsigned int i_id, unsigned int s_id) +void GCodeViewer::TBuffer::add_path(const GCodeProcessor::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id) { - Path::Endpoint endpoint = { i_id, s_id, move.position }; + Path::Endpoint endpoint = { b_id, i_id, s_id, move.position }; // use rounding to reduce the number of generated paths paths.push_back({ move.type, move.extrusion_role, endpoint, endpoint, move.delta_extruder, round_to_nearest(move.height, 2), round_to_nearest(move.width, 2), move.feedrate, move.fan_speed, @@ -236,12 +239,12 @@ void GCodeViewer::SequentialView::Marker::render() const const std::vector GCodeViewer::Extrusion_Role_Colors {{ { 0.75f, 0.75f, 0.75f }, // erNone - { 1.00f, 0.90f, 0.43f }, // erPerimeter + { 1.00f, 0.90f, 0.30f }, // erPerimeter { 1.00f, 0.49f, 0.22f }, // erExternalPerimeter { 0.12f, 0.12f, 1.00f }, // erOverhangPerimeter { 0.69f, 0.19f, 0.16f }, // erInternalInfill { 0.59f, 0.33f, 0.80f }, // erSolidInfill - { 0.94f, 0.33f, 0.33f }, // erTopSolidInfill + { 0.94f, 0.25f, 0.25f }, // erTopSolidInfill { 1.00f, 0.55f, 0.41f }, // erIroning { 0.30f, 0.50f, 0.73f }, // erBridgeInfill { 1.00f, 1.00f, 1.00f }, // erGapFill @@ -284,8 +287,7 @@ const std::vector GCodeViewer::Range_Colors {{ bool GCodeViewer::init() { - for (size_t i = 0; i < m_buffers.size(); ++i) - { + for (size_t i = 0; i < m_buffers.size(); ++i) { TBuffer& buffer = m_buffers[i]; switch (buffer_type(i)) { @@ -299,18 +301,21 @@ bool GCodeViewer::init() { buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Point; buffer.vertices.format = VBuffer::EFormat::Position; + buffer.shader = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20) ? "options_120" : "options_110"; break; } case EMoveType::Extrude: { buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Triangle; buffer.vertices.format = VBuffer::EFormat::PositionNormal3; + buffer.shader = "gouraud_light"; break; } case EMoveType::Travel: { buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Line; buffer.vertices.format = VBuffer::EFormat::PositionNormal1; + buffer.shader = "toolpaths_lines"; break; } } @@ -318,7 +323,6 @@ bool GCodeViewer::init() set_toolpath_move_type_visible(EMoveType::Extrude, true); m_sequential_view.marker.init(); - init_shaders(); std::array point_sizes; ::glGetIntegerv(GL_ALIASED_POINT_SIZE_RANGE, point_sizes.data()); @@ -392,7 +396,7 @@ void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std: auto start_time = std::chrono::high_resolution_clock::now(); #endif // ENABLE_GCODE_VIEWER_STATISTICS - if (m_vertices_count == 0) + if (m_moves_count == 0) return; wxBusyCursor busy; @@ -406,7 +410,7 @@ void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std: // update ranges for coloring / legend m_extrusions.reset_ranges(); - for (size_t i = 0; i < m_vertices_count; ++i) { + for (size_t i = 0; i < m_moves_count; ++i) { // skip first vertex if (i == 0) continue; @@ -440,11 +444,13 @@ void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std: // update buffers' render paths refresh_render_paths(false, false); + + log_memory_used("Refreshed G-code extrusion paths, "); } void GCodeViewer::reset() { - m_vertices_count = 0; + m_moves_count = 0; for (TBuffer& buffer : m_buffers) { buffer.reset(); } @@ -559,7 +565,7 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const // the data needed is contained into the Extrude TBuffer const TBuffer& buffer = m_buffers[buffer_id(EMoveType::Extrude)]; - if (buffer.vertices.id == 0 || buffer.indices.id == 0) + if (!buffer.has_data()) return; // collect color information to generate materials @@ -609,10 +615,13 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); // get indices data from index buffer on gpu - std::vector indices = std::vector(buffer.indices.count); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.indices.id)); - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, static_cast(indices.size() * sizeof(unsigned int)), indices.data())); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + MultiIndexBuffer indices; + for (size_t i = 0; i < buffer.indices.size(); ++i) { + indices.push_back(IndexBuffer(buffer.indices[i].count)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.indices[i].id)); + glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, static_cast(indices.back().size() * sizeof(unsigned int)), indices.back().data())); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } auto get_vertex = [&vertices, floats_per_vertex](unsigned int id) { // extract vertex from vector of floats @@ -670,6 +679,7 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const for (size_t i = 0; i < buffer.render_paths.size(); ++i) { // get paths segments from buffer paths const RenderPath& render_path = buffer.render_paths[i]; + const IndexBuffer& ibuffer = indices[render_path.index_buffer_id]; const Path& path = buffer.paths[render_path.path_id]; float half_width = 0.5f * path.width; // clamp height to avoid artifacts due to z-fighting when importing the obj file into blender and similar @@ -685,7 +695,7 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const unsigned int end = start + render_path.sizes[j]; for (size_t k = start; k < end; k += static_cast(indices_per_segment)) { - Segment curr = generate_segment(indices[k + start_vertex_offset], indices[k + end_vertex_offset], half_width, half_height); + Segment curr = generate_segment(ibuffer[k + start_vertex_offset], ibuffer[k + end_vertex_offset], half_width, half_height); if (k == start) { // starting endpoint vertices/normals out_vertices.push_back(curr.v1 + curr.rl_displacement); out_normals.push_back(curr.right); // right @@ -708,7 +718,7 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const out_vertices_count += 2; size_t first_vertex_id = k - static_cast(indices_per_segment); - Segment prev = generate_segment(indices[first_vertex_id + start_vertex_offset], indices[first_vertex_id + end_vertex_offset], half_width, half_height); + Segment prev = generate_segment(ibuffer[first_vertex_id + start_vertex_offset], ibuffer[first_vertex_id + end_vertex_offset], half_width, half_height); float disp = 0.0f; float cos_dir = prev.dir.dot(curr.dir); if (cos_dir > -0.9998477f) { @@ -838,28 +848,6 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const fclose(fp); } -void GCodeViewer::init_shaders() -{ - unsigned char begin_id = buffer_id(EMoveType::Retract); - unsigned char end_id = buffer_id(EMoveType::Count); - - bool is_glsl_120 = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20); - for (unsigned char i = begin_id; i < end_id; ++i) { - switch (buffer_type(i)) - { - case EMoveType::Tool_change: { m_buffers[i].shader = is_glsl_120 ? "options_120" : "options_110"; break; } - case EMoveType::Color_change: { m_buffers[i].shader = is_glsl_120 ? "options_120" : "options_110"; break; } - case EMoveType::Pause_Print: { m_buffers[i].shader = is_glsl_120 ? "options_120" : "options_110"; break; } - case EMoveType::Custom_GCode: { m_buffers[i].shader = is_glsl_120 ? "options_120" : "options_110"; break; } - case EMoveType::Retract: { m_buffers[i].shader = is_glsl_120 ? "options_120" : "options_110"; break; } - case EMoveType::Unretract: { m_buffers[i].shader = is_glsl_120 ? "options_120" : "options_110"; break; } - case EMoveType::Extrude: { m_buffers[i].shader = "gouraud_light"; break; } - case EMoveType::Travel: { m_buffers[i].shader = "toolpaths_lines"; break; } - default: { break; } - } - } -} - void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) { #if ENABLE_GCODE_VIEWER_STATISTICS @@ -869,11 +857,11 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) #endif // ENABLE_GCODE_VIEWER_STATISTICS // vertices data - m_vertices_count = gcode_result.moves.size(); - if (m_vertices_count == 0) + m_moves_count = gcode_result.moves.size(); + if (m_moves_count == 0) return; - for (size_t i = 0; i < m_vertices_count; ++i) { + for (size_t i = 0; i < m_moves_count; ++i) { const GCodeProcessor::MoveVertex& move = gcode_result.moves[i]; if (wxGetApp().is_gcode_viewer()) // for the gcode viewer we need all moves to correctly size the printbed @@ -891,277 +879,364 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) m_max_bounding_box = m_paths_bounding_box; m_max_bounding_box.merge(m_paths_bounding_box.max + m_sequential_view.marker.get_bounding_box().size()[2] * Vec3d::UnitZ()); + auto log_memory_usage = [this](const std::string& label, const std::vector>& vertices, const std::vector& indices) { + long long vertices_size = 0; + for (size_t i = 0; i < vertices.size(); ++i) { + vertices_size += SLIC3R_STDVEC_MEMSIZE(vertices[i], float); + } + long long indices_size = 0; + for (size_t i = 0; i < indices.size(); ++i) { + for (size_t j = 0; j < indices[i].size(); ++j) { + indices_size += SLIC3R_STDVEC_MEMSIZE(indices[i][j], unsigned int); + } + } + log_memory_used(label, vertices_size + indices_size); + }; + // format data into the buffers to be rendered as points - auto add_as_point = [](const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, - std::vector& buffer_vertices, std::vector& buffer_indices, size_t move_id) { + auto add_vertices_as_point = [](const GCodeProcessor::MoveVertex& curr, std::vector& buffer_vertices) { for (int j = 0; j < 3; ++j) { buffer_vertices.push_back(curr.position[j]); } - buffer.add_path(curr, static_cast(buffer_indices.size()), static_cast(move_id)); - buffer_indices.push_back(static_cast(buffer_indices.size())); + }; + auto add_indices_as_point = [](const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, + unsigned int index_buffer_id, IndexBuffer& buffer_indices, size_t move_id) { + buffer.add_path(curr, index_buffer_id, buffer_indices.size(), move_id); + buffer_indices.push_back(static_cast(buffer_indices.size())); }; // format data into the buffers to be rendered as lines - auto add_as_line = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, - std::vector& buffer_vertices, std::vector& buffer_indices, size_t move_id) { - // x component of the normal to the current segment (the normal is parallel to the XY plane) - float normal_x = (curr.position - prev.position).normalized()[1]; + auto add_vertices_as_line = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, + TBuffer& buffer, std::vector& buffer_vertices) { + // x component of the normal to the current segment (the normal is parallel to the XY plane) + float normal_x = (curr.position - prev.position).normalized()[1]; - if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { - // add starting vertex position - for (int j = 0; j < 3; ++j) { - buffer_vertices.push_back(prev.position[j]); + auto add_vertex = [&buffer_vertices, normal_x](const GCodeProcessor::MoveVertex& vertex) { + // add position + for (int j = 0; j < 3; ++j) { + buffer_vertices.push_back(vertex.position[j]); + } + // add normal x component + buffer_vertices.push_back(normal_x); + }; + + // add previous vertex + add_vertex(prev); + // add current vertex + add_vertex(curr); + }; + auto add_indices_as_line = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, + unsigned int index_buffer_id, IndexBuffer& buffer_indices, size_t move_id) { + // x component of the normal to the current segment (the normal is parallel to the XY plane) + float normal_x = (curr.position - prev.position).normalized()[1]; + + if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { + // add starting index + buffer_indices.push_back(static_cast(buffer_indices.size())); + buffer.add_path(curr, index_buffer_id, buffer_indices.size() - 1, move_id - 1); + buffer.paths.back().first.position = prev.position; } - // add starting vertex normal x component - buffer_vertices.push_back(normal_x); - // add starting index - buffer_indices.push_back(static_cast(buffer_indices.size())); - buffer.add_path(curr, static_cast(buffer_indices.size() - 1), static_cast(move_id - 1)); - buffer.paths.back().first.position = prev.position; - } - Path& last_path = buffer.paths.back(); - if (last_path.first.i_id != last_path.last.i_id) { - // add previous vertex position - for (int j = 0; j < 3; ++j) { - buffer_vertices.push_back(prev.position[j]); + Path& last_path = buffer.paths.back(); + if (last_path.first.i_id != last_path.last.i_id) { + // add previous index + buffer_indices.push_back(static_cast(buffer_indices.size())); } - // add previous vertex normal x component - buffer_vertices.push_back(normal_x); - // add previous index - buffer_indices.push_back(static_cast(buffer_indices.size())); - } - // add current vertex position - for (int j = 0; j < 3; ++j) { - buffer_vertices.push_back(curr.position[j]); - } - // add current vertex normal x component - buffer_vertices.push_back(normal_x); - // add current index - buffer_indices.push_back(static_cast(buffer_indices.size())); - last_path.last = { static_cast(buffer_indices.size() - 1), static_cast(move_id), curr.position }; + // add current index + buffer_indices.push_back(static_cast(buffer_indices.size())); + last_path.last = { index_buffer_id, buffer_indices.size() - 1, move_id, curr.position }; }; // format data into the buffers to be rendered as solid - auto add_as_solid = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, - std::vector& buffer_vertices, std::vector& buffer_indices, size_t move_id) { - static Vec3f prev_dir; - static Vec3f prev_up; - static float prev_length; - auto store_vertex = [](std::vector& buffer_vertices, const Vec3f& position, const Vec3f& normal) { - // append position - for (int j = 0; j < 3; ++j) { - buffer_vertices.push_back(position[j]); - } - // append normal - for (int j = 0; j < 3; ++j) { - buffer_vertices.push_back(normal[j]); - } - }; - auto store_triangle = [](std::vector& buffer_indices, unsigned int i1, unsigned int i2, unsigned int i3) { - buffer_indices.push_back(i1); - buffer_indices.push_back(i2); - buffer_indices.push_back(i3); - }; - auto extract_position_at = [](const std::vector& vertices, size_t id) { - return Vec3f(vertices[id + 0], vertices[id + 1], vertices[id + 2]); - }; - auto update_position_at = [](std::vector& vertices, size_t id, const Vec3f& position) { - vertices[id + 0] = position[0]; - vertices[id + 1] = position[1]; - vertices[id + 2] = position[2]; - }; - auto append_dummy_cap = [store_triangle](std::vector& buffer_indices, unsigned int id) { - store_triangle(buffer_indices, id, id, id); - store_triangle(buffer_indices, id, id, id); - }; - - if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { - buffer.add_path(curr, static_cast(buffer_indices.size()), static_cast(move_id - 1)); - buffer.paths.back().first.position = prev.position; - } - - unsigned int starting_vertices_size = static_cast(buffer_vertices.size() / buffer.vertices.vertex_size_floats()); - - Vec3f dir = (curr.position - prev.position).normalized(); - Vec3f right = (std::abs(std::abs(dir.dot(Vec3f::UnitZ())) - 1.0f) < EPSILON) ? -Vec3f::UnitY() : Vec3f(dir[1], -dir[0], 0.0f).normalized(); - Vec3f left = -right; - Vec3f up = right.cross(dir); - Vec3f bottom = -up; - - Path& last_path = buffer.paths.back(); - - float half_width = 0.5f * last_path.width; - float half_height = 0.5f * last_path.height; - - Vec3f prev_pos = prev.position - half_height * up; - Vec3f curr_pos = curr.position - half_height * up; - - float length = (curr_pos - prev_pos).norm(); - if (last_path.vertices_count() == 1) { - // 1st segment - - // vertices 1st endpoint - store_vertex(buffer_vertices, prev_pos + half_height * up, up); - store_vertex(buffer_vertices, prev_pos + half_width * right, right); - store_vertex(buffer_vertices, prev_pos + half_height * bottom, bottom); - store_vertex(buffer_vertices, prev_pos + half_width * left, left); - - // vertices 2nd endpoint - store_vertex(buffer_vertices, curr_pos + half_height * up, up); - store_vertex(buffer_vertices, curr_pos + half_width * right, right); - store_vertex(buffer_vertices, curr_pos + half_height * bottom, bottom); - store_vertex(buffer_vertices, curr_pos + half_width * left, left); - - // triangles starting cap - store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 2, starting_vertices_size + 1); - store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 3, starting_vertices_size + 2); - - // dummy triangles outer corner cap - append_dummy_cap(buffer_indices, starting_vertices_size); - - // triangles sides - store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 1, starting_vertices_size + 4); - store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size + 5, starting_vertices_size + 4); - store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size + 2, starting_vertices_size + 5); - store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 6, starting_vertices_size + 5); - store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 3, starting_vertices_size + 6); - store_triangle(buffer_indices, starting_vertices_size + 3, starting_vertices_size + 7, starting_vertices_size + 6); - store_triangle(buffer_indices, starting_vertices_size + 3, starting_vertices_size + 0, starting_vertices_size + 7); - store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 4, starting_vertices_size + 7); - - // triangles ending cap - store_triangle(buffer_indices, starting_vertices_size + 4, starting_vertices_size + 6, starting_vertices_size + 7); - store_triangle(buffer_indices, starting_vertices_size + 4, starting_vertices_size + 5, starting_vertices_size + 6); - } - else { - // any other segment - float displacement = 0.0f; - float cos_dir = prev_dir.dot(dir); - if (cos_dir > -0.9998477f) { - // if the angle between adjacent segments is smaller than 179 degrees - Vec3f med_dir = (prev_dir + dir).normalized(); - displacement = half_width * ::tan(::acos(std::clamp(dir.dot(med_dir), -1.0f, 1.0f))); - } - - Vec3f displacement_vec = displacement * prev_dir; - bool can_displace = displacement > 0.0f && displacement < prev_length && displacement < length; - - size_t prev_right_id = (starting_vertices_size - 3) * buffer.vertices.vertex_size_floats(); - size_t prev_left_id = (starting_vertices_size - 1) * buffer.vertices.vertex_size_floats(); - Vec3f prev_right_pos = extract_position_at(buffer_vertices, prev_right_id); - Vec3f prev_left_pos = extract_position_at(buffer_vertices, prev_left_id); - - bool is_right_turn = prev_up.dot(prev_dir.cross(dir)) <= 0.0f; - // whether the angle between adjacent segments is greater than 45 degrees - bool is_sharp = cos_dir < 0.7071068f; - - bool right_displaced = false; - bool left_displaced = false; - - // displace the vertex (inner with respect to the corner) of the previous segment 2nd enpoint, if possible - if (can_displace) { - if (is_right_turn) { - prev_right_pos -= displacement_vec; - update_position_at(buffer_vertices, prev_right_id, prev_right_pos); - right_displaced = true; + auto add_vertices_as_solid = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, + std::vector& buffer_vertices, size_t move_id) { + static Vec3f prev_dir; + static Vec3f prev_up; + static float prev_length; + auto store_vertex = [](std::vector& buffer_vertices, const Vec3f& position, const Vec3f& normal) { + // append position + for (int j = 0; j < 3; ++j) { + buffer_vertices.push_back(position[j]); } - else { - prev_left_pos -= displacement_vec; - update_position_at(buffer_vertices, prev_left_id, prev_left_pos); - left_displaced = true; + // append normal + for (int j = 0; j < 3; ++j) { + buffer_vertices.push_back(normal[j]); } + }; + auto extract_position_at = [](const std::vector& vertices, size_t id) { + return Vec3f(vertices[id + 0], vertices[id + 1], vertices[id + 2]); + }; + auto update_position_at = [](std::vector& vertices, size_t id, const Vec3f& position) { + vertices[id + 0] = position[0]; + vertices[id + 1] = position[1]; + vertices[id + 2] = position[2]; + }; + + if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { + buffer.add_path(curr, 0, 0, move_id - 1); + buffer.paths.back().first.position = prev.position; } - if (!is_sharp) { - // displace the vertex (outer with respect to the corner) of the previous segment 2nd enpoint, if possible + unsigned int starting_vertices_size = static_cast(buffer_vertices.size() / buffer.vertices.vertex_size_floats()); + + Vec3f dir = (curr.position - prev.position).normalized(); + Vec3f right = (std::abs(std::abs(dir.dot(Vec3f::UnitZ())) - 1.0f) < EPSILON) ? -Vec3f::UnitY() : Vec3f(dir[1], -dir[0], 0.0f).normalized(); + Vec3f left = -right; + Vec3f up = right.cross(dir); + Vec3f down = -up; + + Path& last_path = buffer.paths.back(); + + float half_width = 0.5f * last_path.width; + float half_height = 0.5f * last_path.height; + + Vec3f prev_pos = prev.position - half_height * up; + Vec3f curr_pos = curr.position - half_height * up; + + float length = (curr_pos - prev_pos).norm(); + if (last_path.vertices_count() == 1) { + // 1st segment + + // vertices 1st endpoint + store_vertex(buffer_vertices, prev_pos + half_height * up, up); + store_vertex(buffer_vertices, prev_pos + half_width * right, right); + store_vertex(buffer_vertices, prev_pos + half_height * down, down); + store_vertex(buffer_vertices, prev_pos + half_width * left, left); + + // vertices 2nd endpoint + store_vertex(buffer_vertices, curr_pos + half_height * up, up); + store_vertex(buffer_vertices, curr_pos + half_width * right, right); + store_vertex(buffer_vertices, curr_pos + half_height * down, down); + store_vertex(buffer_vertices, curr_pos + half_width * left, left); + } + else { + // any other segment + float displacement = 0.0f; + float cos_dir = prev_dir.dot(dir); + if (cos_dir > -0.9998477f) { + // if the angle between adjacent segments is smaller than 179 degrees + Vec3f med_dir = (prev_dir + dir).normalized(); + displacement = half_width * ::tan(::acos(std::clamp(dir.dot(med_dir), -1.0f, 1.0f))); + } + + Vec3f displacement_vec = displacement * prev_dir; + bool can_displace = displacement > 0.0f && displacement < prev_length&& displacement < length; + + size_t prev_right_id = (starting_vertices_size - 3) * buffer.vertices.vertex_size_floats(); + size_t prev_left_id = (starting_vertices_size - 1) * buffer.vertices.vertex_size_floats(); + Vec3f prev_right_pos = extract_position_at(buffer_vertices, prev_right_id); + Vec3f prev_left_pos = extract_position_at(buffer_vertices, prev_left_id); + + bool is_right_turn = prev_up.dot(prev_dir.cross(dir)) <= 0.0f; + // whether the angle between adjacent segments is greater than 45 degrees + bool is_sharp = cos_dir < 0.7071068f; + + bool right_displaced = false; + bool left_displaced = false; + + // displace the vertex (inner with respect to the corner) of the previous segment 2nd enpoint, if possible if (can_displace) { if (is_right_turn) { - prev_left_pos += displacement_vec; - update_position_at(buffer_vertices, prev_left_id, prev_left_pos); - left_displaced = true; - } - else { - prev_right_pos += displacement_vec; + prev_right_pos -= displacement_vec; update_position_at(buffer_vertices, prev_right_id, prev_right_pos); right_displaced = true; } + else { + prev_left_pos -= displacement_vec; + update_position_at(buffer_vertices, prev_left_id, prev_left_pos); + left_displaced = true; + } } - // vertices 1st endpoint (top and bottom are from previous segment 2nd endpoint) - // vertices position matches that of the previous segment 2nd endpoint, if displaced - store_vertex(buffer_vertices, right_displaced ? prev_right_pos : prev_pos + half_width * right, right); - store_vertex(buffer_vertices, left_displaced ? prev_left_pos : prev_pos + half_width * left, left); - } - else { - // vertices 1st endpoint (top and bottom are from previous segment 2nd endpoint) - // the inner corner vertex position matches that of the previous segment 2nd endpoint, if displaced - if (is_right_turn) { + if (!is_sharp) { + // displace the vertex (outer with respect to the corner) of the previous segment 2nd enpoint, if possible + if (can_displace) { + if (is_right_turn) { + prev_left_pos += displacement_vec; + update_position_at(buffer_vertices, prev_left_id, prev_left_pos); + left_displaced = true; + } + else { + prev_right_pos += displacement_vec; + update_position_at(buffer_vertices, prev_right_id, prev_right_pos); + right_displaced = true; + } + } + + // vertices 1st endpoint (top and bottom are from previous segment 2nd endpoint) + // vertices position matches that of the previous segment 2nd endpoint, if displaced store_vertex(buffer_vertices, right_displaced ? prev_right_pos : prev_pos + half_width * right, right); - store_vertex(buffer_vertices, prev_pos + half_width * left, left); - } - else { - store_vertex(buffer_vertices, prev_pos + half_width * right, right); store_vertex(buffer_vertices, left_displaced ? prev_left_pos : prev_pos + half_width * left, left); } + else { + // vertices 1st endpoint (top and bottom are from previous segment 2nd endpoint) + // the inner corner vertex position matches that of the previous segment 2nd endpoint, if displaced + if (is_right_turn) { + store_vertex(buffer_vertices, right_displaced ? prev_right_pos : prev_pos + half_width * right, right); + store_vertex(buffer_vertices, prev_pos + half_width * left, left); + } + else { + store_vertex(buffer_vertices, prev_pos + half_width * right, right); + store_vertex(buffer_vertices, left_displaced ? prev_left_pos : prev_pos + half_width * left, left); + } + } + + // vertices 2nd endpoint + store_vertex(buffer_vertices, curr_pos + half_height * up, up); + store_vertex(buffer_vertices, curr_pos + half_width * right, right); + store_vertex(buffer_vertices, curr_pos + half_height * down, down); + store_vertex(buffer_vertices, curr_pos + half_width * left, left); } - // vertices 2nd endpoint - store_vertex(buffer_vertices, curr_pos + half_height * up, up); - store_vertex(buffer_vertices, curr_pos + half_width * right, right); - store_vertex(buffer_vertices, curr_pos + half_height * bottom, bottom); - store_vertex(buffer_vertices, curr_pos + half_width * left, left); + last_path.last = { 0, 0, move_id, curr.position }; + prev_dir = dir; + prev_up = up; + prev_length = length; + }; + auto add_indices_as_solid = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, + size_t& buffer_vertices_size, unsigned int index_buffer_id, IndexBuffer& buffer_indices, size_t move_id) { + static Vec3f prev_dir; + static Vec3f prev_up; + static float prev_length; + auto store_triangle = [](IndexBuffer& buffer_indices, unsigned int i1, unsigned int i2, unsigned int i3) { + buffer_indices.push_back(i1); + buffer_indices.push_back(i2); + buffer_indices.push_back(i3); + }; + auto append_dummy_cap = [store_triangle](IndexBuffer& buffer_indices, unsigned int id) { + store_triangle(buffer_indices, id, id, id); + store_triangle(buffer_indices, id, id, id); + }; - // triangles starting cap - store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size - 2, starting_vertices_size + 0); - store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size + 1, starting_vertices_size - 2); + if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { + buffer.add_path(curr, index_buffer_id, buffer_indices.size(), move_id - 1); + buffer.paths.back().first.position = prev.position; + } - // triangles outer corner cap - if (is_right_turn) { - if (left_displaced) - // dummy triangles - append_dummy_cap(buffer_indices, starting_vertices_size); - else { - store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size + 1, starting_vertices_size - 1); - store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size - 2, starting_vertices_size - 1); - } + unsigned int starting_vertices_size = static_cast(buffer_vertices_size); + + Vec3f dir = (curr.position - prev.position).normalized(); + Vec3f right = (std::abs(std::abs(dir.dot(Vec3f::UnitZ())) - 1.0f) < EPSILON) ? -Vec3f::UnitY() : Vec3f(dir[1], -dir[0], 0.0f).normalized(); + Vec3f up = right.cross(dir); + + Path& last_path = buffer.paths.back(); + + float half_width = 0.5f * last_path.width; + float half_height = 0.5f * last_path.height; + + Vec3f prev_pos = prev.position - half_height * up; + Vec3f curr_pos = curr.position - half_height * up; + + float length = (curr_pos - prev_pos).norm(); + if (last_path.vertices_count() == 1) { + // 1st segment + buffer_vertices_size += 8; + + // triangles starting cap + store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 2, starting_vertices_size + 1); + store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 3, starting_vertices_size + 2); + + // dummy triangles outer corner cap + append_dummy_cap(buffer_indices, starting_vertices_size); + + // triangles sides + store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 1, starting_vertices_size + 4); + store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size + 5, starting_vertices_size + 4); + store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size + 2, starting_vertices_size + 5); + store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 6, starting_vertices_size + 5); + store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 3, starting_vertices_size + 6); + store_triangle(buffer_indices, starting_vertices_size + 3, starting_vertices_size + 7, starting_vertices_size + 6); + store_triangle(buffer_indices, starting_vertices_size + 3, starting_vertices_size + 0, starting_vertices_size + 7); + store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 4, starting_vertices_size + 7); + + // triangles ending cap + store_triangle(buffer_indices, starting_vertices_size + 4, starting_vertices_size + 6, starting_vertices_size + 7); + store_triangle(buffer_indices, starting_vertices_size + 4, starting_vertices_size + 5, starting_vertices_size + 6); } else { - if (right_displaced) - // dummy triangles - append_dummy_cap(buffer_indices, starting_vertices_size); - else { - store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size - 3, starting_vertices_size + 0); - store_triangle(buffer_indices, starting_vertices_size - 3, starting_vertices_size - 2, starting_vertices_size + 0); + // any other segment + float displacement = 0.0f; + float cos_dir = prev_dir.dot(dir); + if (cos_dir > -0.9998477f) { + // if the angle between adjacent segments is smaller than 179 degrees + Vec3f med_dir = (prev_dir + dir).normalized(); + displacement = half_width * ::tan(::acos(std::clamp(dir.dot(med_dir), -1.0f, 1.0f))); } + + Vec3f displacement_vec = displacement * prev_dir; + bool can_displace = displacement > 0.0f && displacement < prev_length && displacement < length; + + bool is_right_turn = prev_up.dot(prev_dir.cross(dir)) <= 0.0f; + // whether the angle between adjacent segments is greater than 45 degrees + bool is_sharp = cos_dir < 0.7071068f; + + bool right_displaced = false; + bool left_displaced = false; + + if (!is_sharp) { + if (can_displace) { + if (is_right_turn) + left_displaced = true; + else + right_displaced = true; + } + } + + buffer_vertices_size += 6; + + // triangles starting cap + store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size - 2, starting_vertices_size + 0); + store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size + 1, starting_vertices_size - 2); + + // triangles outer corner cap + if (is_right_turn) { + if (left_displaced) + // dummy triangles + append_dummy_cap(buffer_indices, starting_vertices_size); + else { + store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size + 1, starting_vertices_size - 1); + store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size - 2, starting_vertices_size - 1); + } + } + else { + if (right_displaced) + // dummy triangles + append_dummy_cap(buffer_indices, starting_vertices_size); + else { + store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size - 3, starting_vertices_size + 0); + store_triangle(buffer_indices, starting_vertices_size - 3, starting_vertices_size - 2, starting_vertices_size + 0); + } + } + + // triangles sides + store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size + 0, starting_vertices_size + 2); + store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 3, starting_vertices_size + 2); + store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size - 2, starting_vertices_size + 3); + store_triangle(buffer_indices, starting_vertices_size - 2, starting_vertices_size + 4, starting_vertices_size + 3); + store_triangle(buffer_indices, starting_vertices_size - 2, starting_vertices_size + 1, starting_vertices_size + 4); + store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size + 5, starting_vertices_size + 4); + store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size - 4, starting_vertices_size + 5); + store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size + 2, starting_vertices_size + 5); + + // triangles ending cap + store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 4, starting_vertices_size + 5); + store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 3, starting_vertices_size + 4); } - // triangles sides - store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size + 0, starting_vertices_size + 2); - store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 3, starting_vertices_size + 2); - store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size - 2, starting_vertices_size + 3); - store_triangle(buffer_indices, starting_vertices_size - 2, starting_vertices_size + 4, starting_vertices_size + 3); - store_triangle(buffer_indices, starting_vertices_size - 2, starting_vertices_size + 1, starting_vertices_size + 4); - store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size + 5, starting_vertices_size + 4); - store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size - 4, starting_vertices_size + 5); - store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size + 2, starting_vertices_size + 5); - - // triangles ending cap - store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 4, starting_vertices_size + 5); - store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 3, starting_vertices_size + 4); - } - - last_path.last = { static_cast(buffer_indices.size() - 1), static_cast(move_id), curr.position }; - prev_dir = dir; - prev_up = up; - prev_length = length; + last_path.last = { index_buffer_id, buffer_indices.size() - 1, move_id, curr.position }; + prev_dir = dir; + prev_up = up; + prev_length = length; }; - // toolpaths data -> extract from result + // to reduce the peak in memory usage, we split the generation of the vertex and index buffers in two steps. + // the data are deleted as soon as they are sent to the gpu. std::vector> vertices(m_buffers.size()); - std::vector> indices(m_buffers.size()); - for (size_t i = 0; i < m_vertices_count; ++i) { + std::vector indices(m_buffers.size()); + + // toolpaths data -> extract vertices from result + for (size_t i = 0; i < m_moves_count; ++i) { // skip first vertex if (i == 0) continue; @@ -1172,62 +1247,142 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) unsigned char id = buffer_id(curr.type); TBuffer& buffer = m_buffers[id]; std::vector& buffer_vertices = vertices[id]; - std::vector& buffer_indices = indices[id]; - switch (curr.type) + switch (buffer.render_primitive_type) { - case EMoveType::Tool_change: - case EMoveType::Color_change: - case EMoveType::Pause_Print: - case EMoveType::Custom_GCode: - case EMoveType::Retract: - case EMoveType::Unretract: + case TBuffer::ERenderPrimitiveType::Point: { - add_as_point(curr, buffer, buffer_vertices, buffer_indices, i); + add_vertices_as_point(curr, buffer_vertices); break; } - case EMoveType::Extrude: + case TBuffer::ERenderPrimitiveType::Line: { - add_as_solid(prev, curr, buffer, buffer_vertices, buffer_indices, i); + add_vertices_as_line(prev, curr, buffer, buffer_vertices); break; } - case EMoveType::Travel: + case TBuffer::ERenderPrimitiveType::Triangle: { - add_as_line(prev, curr, buffer, buffer_vertices, buffer_indices, i); + add_vertices_as_solid(prev, curr, buffer, buffer_vertices, i); break; } - default: { break; } } } - // toolpaths data -> send data to gpu + log_memory_usage("Loaded G-code generated vertex buffers, ", vertices, indices); + + // toolpaths data -> send vertices data to gpu for (size_t i = 0; i < m_buffers.size(); ++i) { TBuffer& buffer = m_buffers[i]; - // vertices const std::vector& buffer_vertices = vertices[i]; buffer.vertices.count = buffer_vertices.size() / buffer.vertices.vertex_size_floats(); #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.vertices_gpu_size += buffer_vertices.size() * sizeof(float); + m_statistics.max_vertices_in_vertex_buffer = std::max(m_statistics.max_vertices_in_vertex_buffer, static_cast(buffer.vertices.count)); #endif // ENABLE_GCODE_VIEWER_STATISTICS glsafe(::glGenBuffers(1, &buffer.vertices.id)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, buffer.vertices.id)); glsafe(::glBufferData(GL_ARRAY_BUFFER, buffer_vertices.size() * sizeof(float), buffer_vertices.data(), GL_STATIC_DRAW)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + } - // indices - const std::vector& buffer_indices = indices[i]; - buffer.indices.count = buffer_indices.size(); + // dismiss vertices data, no more needed + std::vector>().swap(vertices); + + // toolpaths data -> extract indices from result + // ensure that at least one index buffer is defined for each multibuffer + for (auto i : indices) { + i.push_back(IndexBuffer()); + } + // paths may have been filled while extracting vertices, + // so reset them, they will be filled again while extracting indices + for (TBuffer& buffer : m_buffers) { + buffer.paths.clear(); + } + // variable used to keep track of the current size (in vertices) of the vertex buffer + size_t curr_buffer_vertices_size = 0; + for (size_t i = 0; i < m_moves_count; ++i) { + // skip first vertex + if (i == 0) + continue; + + const GCodeProcessor::MoveVertex& prev = gcode_result.moves[i - 1]; + const GCodeProcessor::MoveVertex& curr = gcode_result.moves[i]; + + unsigned char id = buffer_id(curr.type); + TBuffer& buffer = m_buffers[id]; + MultiIndexBuffer& buffer_indices = indices[id]; + if (buffer_indices.empty()) + buffer_indices.push_back(IndexBuffer()); + + static const size_t THRESHOLD = 1024 * 1024 * 128; + // if adding the indices for the current segment exceeds the threshold size of the current index buffer + // create another index buffer, and move the current path indices into it + if (buffer_indices.back().size() >= THRESHOLD - static_cast(buffer.indices_per_segment())) { + buffer_indices.push_back(IndexBuffer()); + if (curr.type == EMoveType::Extrude || curr.type == EMoveType::Travel) { + if (!(prev.type != curr.type || !buffer.paths.back().matches(curr))) { + Path& last_path = buffer.paths.back(); + size_t delta_id = last_path.last.i_id - last_path.first.i_id; + + // move indices of the last path from the previous into the new index buffer + IndexBuffer& src_buffer = buffer_indices[buffer_indices.size() - 2]; + IndexBuffer& dst_buffer = buffer_indices[buffer_indices.size() - 1]; + std::move(src_buffer.begin() + last_path.first.i_id, src_buffer.end(), std::back_inserter(dst_buffer)); + src_buffer.erase(src_buffer.begin() + last_path.first.i_id, src_buffer.end()); + + // updates path indices + last_path.first.b_id = buffer_indices.size() - 1; + last_path.first.i_id = 0; + last_path.last.b_id = buffer_indices.size() - 1; + last_path.last.i_id = delta_id; + } + } + } + + switch (buffer.render_primitive_type) + { + case TBuffer::ERenderPrimitiveType::Point: + { + add_indices_as_point(curr, buffer, static_cast(buffer_indices.size()) - 1, buffer_indices.back(), i); + break; + } + case TBuffer::ERenderPrimitiveType::Line: + { + add_indices_as_line(prev, curr, buffer, static_cast(buffer_indices.size()) - 1, buffer_indices.back(), i); + break; + } + case TBuffer::ERenderPrimitiveType::Triangle: + { + add_indices_as_solid(prev, curr, buffer, curr_buffer_vertices_size, static_cast(buffer_indices.size()) - 1, buffer_indices.back(), i); + break; + } + } + } + + log_memory_usage("Loaded G-code generated indices buffers, ", vertices, indices); + + // toolpaths data -> send indices data to gpu + for (size_t i = 0; i < m_buffers.size(); ++i) { + TBuffer& buffer = m_buffers[i]; + + for (size_t j = 0; j < indices[i].size(); ++j) { + const IndexBuffer& buffer_indices = indices[i][j]; + buffer.indices.push_back(IBuffer()); + IBuffer& ibuffer = buffer.indices.back(); + ibuffer.count = buffer_indices.size(); #if ENABLE_GCODE_VIEWER_STATISTICS - m_statistics.indices_gpu_size += buffer.indices.count * sizeof(unsigned int); + m_statistics.indices_gpu_size += ibuffer.count * sizeof(unsigned int); + m_statistics.max_indices_in_index_buffer = std::max(m_statistics.max_indices_in_index_buffer, static_cast(ibuffer.count)); #endif // ENABLE_GCODE_VIEWER_STATISTICS - if (buffer.indices.count > 0) { - glsafe(::glGenBuffers(1, &buffer.indices.id)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.indices.id)); - glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, buffer.indices.count * sizeof(unsigned int), buffer_indices.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + if (ibuffer.count > 0) { + glsafe(::glGenBuffers(1, &ibuffer.id)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibuffer.id)); + glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, buffer_indices.size() * sizeof(unsigned int), buffer_indices.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } } } @@ -1236,13 +1391,22 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) m_statistics.paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.paths, Path); } unsigned int travel_buffer_id = buffer_id(EMoveType::Travel); - m_statistics.travel_segments_count = indices[travel_buffer_id].size() / m_buffers[travel_buffer_id].indices_per_segment(); + const MultiIndexBuffer& travel_buffer_indices = indices[travel_buffer_id]; + for (size_t i = 0; i < travel_buffer_indices.size(); ++i) { + m_statistics.travel_segments_count = travel_buffer_indices[i].size() / m_buffers[travel_buffer_id].indices_per_segment(); + } unsigned int extrude_buffer_id = buffer_id(EMoveType::Extrude); - m_statistics.extrude_segments_count = indices[extrude_buffer_id].size() / m_buffers[extrude_buffer_id].indices_per_segment(); + const MultiIndexBuffer& extrude_buffer_indices = indices[extrude_buffer_id]; + for (size_t i = 0; i < extrude_buffer_indices.size(); ++i) { + m_statistics.extrude_segments_count = extrude_buffer_indices[i].size() / m_buffers[extrude_buffer_id].indices_per_segment(); + } #endif // ENABLE_GCODE_VIEWER_STATISTICS + // dismiss indices data, no more needed + std::vector().swap(indices); + // layers zs / roles / extruder ids / cp color ids -> extract from result - for (size_t i = 0; i < m_vertices_count; ++i) { + for (size_t i = 0; i < m_moves_count; ++i) { const GCodeProcessor::MoveVertex& move = gcode_result.moves[i]; if (move.type == EMoveType::Extrude) m_layers_zs.emplace_back(static_cast(move.position[2])); @@ -1264,8 +1428,10 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) m_layers_zs[k++] = (j > i + 1) ? (0.5 * (m_layers_zs[i] + m_layers_zs[j - 1])) : m_layers_zs[i]; i = j; } - if (k < n) + if (k < n) { m_layers_zs.erase(m_layers_zs.begin() + k, m_layers_zs.end()); + m_layers_zs.shrink_to_fit(); + } // set layers z range m_layers_z_range = { m_layers_zs.front(), m_layers_zs.back() }; @@ -1273,10 +1439,14 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) // roles -> remove duplicates std::sort(m_roles.begin(), m_roles.end()); m_roles.erase(std::unique(m_roles.begin(), m_roles.end()), m_roles.end()); + m_roles.shrink_to_fit(); // extruder ids -> remove duplicates std::sort(m_extruder_ids.begin(), m_extruder_ids.end()); m_extruder_ids.erase(std::unique(m_extruder_ids.begin(), m_extruder_ids.end()), m_extruder_ids.end()); + m_extruder_ids.shrink_to_fit(); + + log_memory_usage("Loaded G-code generated extrusion paths, ", vertices, indices); #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.load_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); @@ -1366,7 +1536,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool auto travel_color = [this](const Path& path) { return (path.delta_extruder < 0.0f) ? Travel_Colors[2] /* Retract */ : - ((path.delta_extruder > 0.0f) ? Travel_Colors[1] /* Extrude */ : + ((path.delta_extruder > 0.0f) ? Travel_Colors[1] /* Extrude */ : Travel_Colors[0] /* Move */); }; @@ -1374,15 +1544,15 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool m_statistics.render_paths_size = 0; #endif // ENABLE_GCODE_VIEWER_STATISTICS - m_sequential_view.endpoints.first = m_vertices_count; + m_sequential_view.endpoints.first = m_moves_count; m_sequential_view.endpoints.last = 0; if (!keep_sequential_current_first) m_sequential_view.current.first = 0; if (!keep_sequential_current_last) - m_sequential_view.current.last = m_vertices_count; + m_sequential_view.current.last = m_moves_count; // first pass: collect visible paths and update sequential view data - std::vector> paths; + std::vector> paths; for (TBuffer& buffer : m_buffers) { // reset render paths buffer.render_paths.clear(); @@ -1403,7 +1573,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool continue; // store valid path - paths.push_back({ &buffer, i }); + paths.push_back({ &buffer, path.first.b_id, static_cast(i) }); m_sequential_view.endpoints.first = std::min(m_sequential_view.endpoints.first, path.first.s_id); m_sequential_view.endpoints.last = std::max(m_sequential_view.endpoints.last, path.last.s_id); @@ -1420,7 +1590,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool // searches the path containing the current position for (const Path& path : buffer.paths) { if (path.contains(m_sequential_view.current.last)) { - unsigned int offset = m_sequential_view.current.last - path.first.s_id; + unsigned int offset = static_cast(m_sequential_view.current.last - path.first.s_id); if (offset > 0) { if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Line) offset = 2 * offset - 1; @@ -1429,11 +1599,11 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool offset = indices_count * (offset - 1) + (indices_count - 6); } } - offset += path.first.i_id; + offset += static_cast(path.first.i_id); // gets the index from the index buffer on gpu unsigned int index = 0; - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.indices.id)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.indices[path.first.b_id].id)); glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast(offset * sizeof(unsigned int)), static_cast(sizeof(unsigned int)), static_cast(&index))); glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); @@ -1450,8 +1620,8 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool } // second pass: filter paths by sequential data and collect them by color - for (const auto& [buffer, id] : paths) { - const Path& path = buffer->paths[id]; + for (const auto& [buffer, index_buffer_id, path_id] : paths) { + const Path& path = buffer->paths[path_id]; if (m_sequential_view.current.last <= path.first.s_id || path.last.s_id <= m_sequential_view.current.first) continue; @@ -1463,20 +1633,23 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool default: { color = { 0.0f, 0.0f, 0.0f }; break; } } - auto it = std::find_if(buffer->render_paths.begin(), buffer->render_paths.end(), [color](const RenderPath& path) { return path.color == color; }); + unsigned int ibuffer_id = index_buffer_id; + auto it = std::find_if(buffer->render_paths.begin(), buffer->render_paths.end(), + [color, ibuffer_id](const RenderPath& path) { return path.index_buffer_id == ibuffer_id && path.color == color; }); if (it == buffer->render_paths.end()) { it = buffer->render_paths.insert(buffer->render_paths.end(), RenderPath()); it->color = color; - it->path_id = id; + it->path_id = path_id; + it->index_buffer_id = index_buffer_id; } - unsigned int size_in_vertices = std::min(m_sequential_view.current.last, path.last.s_id) - std::max(m_sequential_view.current.first, path.first.s_id) + 1; + unsigned int segments_count = std::min(m_sequential_view.current.last, path.last.s_id) - std::max(m_sequential_view.current.first, path.first.s_id) + 1; unsigned int size_in_indices = 0; switch (buffer->render_primitive_type) { - case TBuffer::ERenderPrimitiveType::Point: { size_in_indices = size_in_vertices; break; } + case TBuffer::ERenderPrimitiveType::Point: { size_in_indices = segments_count; break; } case TBuffer::ERenderPrimitiveType::Line: - case TBuffer::ERenderPrimitiveType::Triangle: { size_in_indices = buffer->indices_per_segment() * (size_in_vertices - 1); break; } + case TBuffer::ERenderPrimitiveType::Triangle: { size_in_indices = buffer->indices_per_segment() * (segments_count - 1); break; } } it->sizes.push_back(size_in_indices); @@ -1517,7 +1690,8 @@ void GCodeViewer::render_toolpaths() const shader.set_uniform("uniform_color", color4); }; - auto render_as_points = [this, zoom, point_size, near_plane_height, set_uniform_color](const TBuffer& buffer, EOptionsColors color_id, GLShaderProgram& shader) { + auto render_as_points = [this, zoom, point_size, near_plane_height, set_uniform_color] + (const TBuffer& buffer, unsigned int index_buffer_id, EOptionsColors color_id, GLShaderProgram& shader) { set_uniform_color(Options_Colors[static_cast(color_id)], shader); shader.set_uniform("zoom", zoom); shader.set_uniform("percent_outline_radius", 0.0f); @@ -1529,34 +1703,40 @@ void GCodeViewer::render_toolpaths() const glsafe(::glEnable(GL_POINT_SPRITE)); for (const RenderPath& path : buffer.render_paths) { - glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); + if (path.index_buffer_id == index_buffer_id) { + glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); #if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_multi_points_calls_count; + ++m_statistics.gl_multi_points_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS + } } glsafe(::glDisable(GL_POINT_SPRITE)); glsafe(::glDisable(GL_VERTEX_PROGRAM_POINT_SIZE)); }; - auto render_as_lines = [this, light_intensity, set_uniform_color](const TBuffer& buffer, GLShaderProgram& shader) { + auto render_as_lines = [this, light_intensity, set_uniform_color](const TBuffer& buffer, unsigned int index_buffer_id, GLShaderProgram& shader) { shader.set_uniform("light_intensity", light_intensity); for (const RenderPath& path : buffer.render_paths) { - set_uniform_color(path.color, shader); - glsafe(::glMultiDrawElements(GL_LINES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); + if (path.index_buffer_id == index_buffer_id) { + set_uniform_color(path.color, shader); + glsafe(::glMultiDrawElements(GL_LINES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); #if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_multi_lines_calls_count; + ++m_statistics.gl_multi_lines_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS + } } }; - auto render_as_triangles = [this, set_uniform_color](const TBuffer& buffer, GLShaderProgram& shader) { + auto render_as_triangles = [this, set_uniform_color](const TBuffer& buffer, unsigned int index_buffer_id, GLShaderProgram& shader) { for (const RenderPath& path : buffer.render_paths) { - set_uniform_color(path.color, shader); - glsafe(::glMultiDrawElements(GL_TRIANGLES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); + if (path.index_buffer_id == index_buffer_id) { + set_uniform_color(path.color, shader); + glsafe(::glMultiDrawElements(GL_TRIANGLES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); #if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_multi_triangles_calls_count; + ++m_statistics.gl_multi_triangles_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS + } } }; @@ -1571,10 +1751,7 @@ void GCodeViewer::render_toolpaths() const for (unsigned char i = begin_id; i < end_id; ++i) { const TBuffer& buffer = m_buffers[i]; - if (!buffer.visible) - continue; - - if (buffer.vertices.id == 0 || buffer.indices.id == 0) + if (!buffer.visible || !buffer.has_data()) continue; GLShaderProgram* shader = wxGetApp().get_shader(buffer.shader.c_str()); @@ -1590,38 +1767,40 @@ void GCodeViewer::render_toolpaths() const glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); } - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.indices.id)); + for (size_t j = 0; j < buffer.indices.size(); ++j) { + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.indices[j].id)); - switch (buffer.render_primitive_type) - { - case TBuffer::ERenderPrimitiveType::Point: - { - EOptionsColors color; - switch (buffer_type(i)) + switch (buffer.render_primitive_type) { - case EMoveType::Tool_change: { color = EOptionsColors::ToolChanges; break; } - case EMoveType::Color_change: { color = EOptionsColors::ColorChanges; break; } - case EMoveType::Pause_Print: { color = EOptionsColors::PausePrints; break; } - case EMoveType::Custom_GCode: { color = EOptionsColors::CustomGCodes; break; } - case EMoveType::Retract: { color = EOptionsColors::Retractions; break; } - case EMoveType::Unretract: { color = EOptionsColors::Unretractions; break; } + case TBuffer::ERenderPrimitiveType::Point: + { + EOptionsColors color; + switch (buffer_type(i)) + { + case EMoveType::Tool_change: { color = EOptionsColors::ToolChanges; break; } + case EMoveType::Color_change: { color = EOptionsColors::ColorChanges; break; } + case EMoveType::Pause_Print: { color = EOptionsColors::PausePrints; break; } + case EMoveType::Custom_GCode: { color = EOptionsColors::CustomGCodes; break; } + case EMoveType::Retract: { color = EOptionsColors::Retractions; break; } + case EMoveType::Unretract: { color = EOptionsColors::Unretractions; break; } + } + render_as_points(buffer, static_cast(j), color, *shader); + break; + } + case TBuffer::ERenderPrimitiveType::Line: + { + render_as_lines(buffer, static_cast(j), *shader); + break; + } + case TBuffer::ERenderPrimitiveType::Triangle: + { + render_as_triangles(buffer, static_cast(j), *shader); + break; + } } - render_as_points(buffer, color, *shader); - break; - } - case TBuffer::ERenderPrimitiveType::Line: - { - render_as_lines(buffer, *shader); - break; - } - case TBuffer::ERenderPrimitiveType::Triangle: - { - render_as_triangles(buffer, *shader); - break; - } - } - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } if (has_normals) glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); @@ -1684,90 +1863,90 @@ void GCodeViewer::render_legend() const std::function callback = nullptr) { if (!visible) ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); - ImVec2 pos = ImGui::GetCursorScreenPos(); - switch (type) - { - default: - case EItemType::Rect: - { - draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, - ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f })); - break; - } - case EItemType::Circle: - { - ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); - if (m_buffers[buffer_id(EMoveType::Retract)].shader == "options_120") { - draw_list->AddCircleFilled(center, 0.5f * icon_size, - ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); - float radius = 0.5f * icon_size; - draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); - radius = 0.5f * icon_size * 0.01f * 33.0f; - draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); + ImVec2 pos = ImGui::GetCursorScreenPos(); + switch (type) + { + default: + case EItemType::Rect: + { + draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, + ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f })); + break; } - else - draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); + case EItemType::Circle: + { + ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); + if (m_buffers[buffer_id(EMoveType::Retract)].shader == "options_120") { + draw_list->AddCircleFilled(center, 0.5f * icon_size, + ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); + float radius = 0.5f * icon_size; + draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); + radius = 0.5f * icon_size * 0.01f * 33.0f; + draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); + } + else + draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); - break; - } - case EItemType::Hexagon: - { - ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); - draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 6); - break; - } - case EItemType::Line: - { - draw_list->AddLine({ pos.x + 1, pos.y + icon_size - 1}, { pos.x + icon_size - 1, pos.y + 1 }, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 3.0f); - break; - } - } + break; + } + case EItemType::Hexagon: + { + ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); + draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 6); + break; + } + case EItemType::Line: + { + draw_list->AddLine({ pos.x + 1, pos.y + icon_size - 1 }, { pos.x + icon_size - 1, pos.y + 1 }, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 3.0f); + break; + } + } - // draw text - ImGui::Dummy({ icon_size, icon_size }); - ImGui::SameLine(); - if (callback != nullptr) { - if (ImGui::MenuItem(label.c_str())) - callback(); - else { - // show tooltip - if (ImGui::IsItemHovered()) { - if (!visible) - ImGui::PopStyleVar(); - ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); - ImGui::BeginTooltip(); - imgui.text(visible ? _u8L("Click to hide") : _u8L("Click to show")); - ImGui::EndTooltip(); - ImGui::PopStyleColor(); - if (!visible) - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); + // draw text + ImGui::Dummy({ icon_size, icon_size }); + ImGui::SameLine(); + if (callback != nullptr) { + if (ImGui::MenuItem(label.c_str())) + callback(); + else { + // show tooltip + if (ImGui::IsItemHovered()) { + if (!visible) + ImGui::PopStyleVar(); + ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); + ImGui::BeginTooltip(); + imgui.text(visible ? _u8L("Click to hide") : _u8L("Click to show")); + ImGui::EndTooltip(); + ImGui::PopStyleColor(); + if (!visible) + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); - // to avoid the tooltip to change size when moving the mouse - wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); - wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); + // to avoid the tooltip to change size when moving the mouse + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); + } + } + + if (!time.empty()) { + ImGui::SameLine(offsets[0]); + imgui.text(time); + ImGui::SameLine(offsets[1]); + pos = ImGui::GetCursorScreenPos(); + float width = std::max(1.0f, percent_bar_size * percent / max_percent); + draw_list->AddRectFilled({ pos.x, pos.y + 2.0f }, { pos.x + width, pos.y + icon_size - 2.0f }, + ImGui::GetColorU32(ImGuiWrapper::COL_ORANGE_LIGHT)); + ImGui::Dummy({ percent_bar_size, icon_size }); + ImGui::SameLine(); + char buf[64]; + ::sprintf(buf, "%.1f%%", 100.0f * percent); + ImGui::TextUnformatted((percent > 0.0f) ? buf : ""); } } + else + imgui.text(label); - if (!time.empty()) { - ImGui::SameLine(offsets[0]); - imgui.text(time); - ImGui::SameLine(offsets[1]); - pos = ImGui::GetCursorScreenPos(); - float width = std::max(1.0f, percent_bar_size * percent / max_percent); - draw_list->AddRectFilled({ pos.x, pos.y + 2.0f }, { pos.x + width, pos.y + icon_size - 2.0f }, - ImGui::GetColorU32(ImGuiWrapper::COL_ORANGE_LIGHT)); - ImGui::Dummy({ percent_bar_size, icon_size }); - ImGui::SameLine(); - char buf[64]; - ::sprintf(buf, "%.1f%%", 100.0f * percent); - ImGui::TextUnformatted((percent > 0.0f) ? buf : ""); - } - } - else - imgui.text(label); - - if (!visible) - ImGui::PopStyleVar(); + if (!visible) + ImGui::PopStyleVar(); }; auto append_range = [this, draw_list, &imgui, append_item](const Extrusions::Range& range, unsigned int decimals) { @@ -1893,60 +2072,6 @@ void GCodeViewer::render_legend() const offsets = calculate_offsets(labels, times, { _u8L("Feature type"), _u8L("Time") }, icon_size); } - // total estimated printing time section - if (time_mode.time > 0.0f && (m_view_type == EViewType::FeatureType || - (m_view_type == EViewType::ColorPrint && !time_mode.custom_gcode_times.empty()))) { - ImGui::AlignTextToFramePadding(); - switch (m_time_estimate_mode) - { - case PrintEstimatedTimeStatistics::ETimeMode::Normal: - { - imgui.text(_u8L("Estimated printing time") + " [" + _u8L("Normal mode") + "]:"); - break; - } - case PrintEstimatedTimeStatistics::ETimeMode::Stealth: - { - imgui.text(_u8L("Estimated printing time") + " [" + _u8L("Stealth mode") + "]:"); - break; - } - } - ImGui::SameLine(); - imgui.text(short_time(get_time_dhms(time_mode.time))); - - auto show_mode_button = [this, &imgui](const std::string& label, PrintEstimatedTimeStatistics::ETimeMode mode) { - bool show = false; - for (size_t i = 0; i < m_time_statistics.modes.size(); ++i) { - if (i != static_cast(mode) && - short_time(get_time_dhms(m_time_statistics.modes[static_cast(mode)].time)) != short_time(get_time_dhms(m_time_statistics.modes[i].time))) { - show = true; - break; - } - } - if (show && m_time_statistics.modes[static_cast(mode)].roles_times.size() > 0) { - if (imgui.button(label)) { - m_time_estimate_mode = mode; - wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); - wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); - } - } - }; - - switch (m_time_estimate_mode) - { - case PrintEstimatedTimeStatistics::ETimeMode::Normal: - { - show_mode_button(_u8L("Show stealth mode"), PrintEstimatedTimeStatistics::ETimeMode::Stealth); - break; - } - case PrintEstimatedTimeStatistics::ETimeMode::Stealth: - { - show_mode_button(_u8L("Show normal mode"), PrintEstimatedTimeStatistics::ETimeMode::Normal); - break; - } - } - ImGui::Spacing(); - } - // extrusion paths section -> title switch (m_view_type) { @@ -1987,10 +2112,10 @@ void GCodeViewer::render_legend() const } break; } - case EViewType::Height: { append_range(m_extrusions.ranges.height, 3); break; } - case EViewType::Width: { append_range(m_extrusions.ranges.width, 3); break; } - case EViewType::Feedrate: { append_range(m_extrusions.ranges.feedrate, 1); break; } - case EViewType::FanSpeed: { append_range(m_extrusions.ranges.fan_speed, 0); break; } + case EViewType::Height: { append_range(m_extrusions.ranges.height, 3); break; } + case EViewType::Width: { append_range(m_extrusions.ranges.width, 3); break; } + case EViewType::Feedrate: { append_range(m_extrusions.ranges.feedrate, 1); break; } + case EViewType::FanSpeed: { append_range(m_extrusions.ranges.fan_speed, 0); break; } case EViewType::VolumetricRate: { append_range(m_extrusions.ranges.volumetric_rate, 3); break; } case EViewType::Tool: { @@ -2223,7 +2348,7 @@ void GCodeViewer::render_legend() const auto any_option_available = [this]() { auto available = [this](EMoveType type) { const TBuffer& buffer = m_buffers[buffer_id(type)]; - return buffer.visible && buffer.indices.count > 0; + return buffer.visible && buffer.has_data(); }; return available(EMoveType::Color_change) || @@ -2236,7 +2361,7 @@ void GCodeViewer::render_legend() const auto add_option = [this, append_item](EMoveType move_type, EOptionsColors color, const std::string& text) { const TBuffer& buffer = m_buffers[buffer_id(move_type)]; - if (buffer.visible && buffer.indices.count > 0) + if (buffer.visible && buffer.has_data()) append_item((buffer.shader == "options_110") ? EItemType::Rect : EItemType::Circle, Options_Colors[static_cast(color)], text); }; @@ -2255,6 +2380,67 @@ void GCodeViewer::render_legend() const add_option(EMoveType::Custom_GCode, EOptionsColors::CustomGCodes, _u8L("Custom GCodes")); } + // total estimated printing time section + if (time_mode.time > 0.0f && (m_view_type == EViewType::FeatureType || + (m_view_type == EViewType::ColorPrint && !time_mode.custom_gcode_times.empty()))) { + + ImGui::Spacing(); + ImGui::Spacing(); + ImGui::PushStyleColor(ImGuiCol_Separator, { 1.0f, 1.0f, 1.0f, 1.0f }); + ImGui::Separator(); + ImGui::PopStyleColor(); + ImGui::Spacing(); + + ImGui::AlignTextToFramePadding(); + switch (m_time_estimate_mode) + { + case PrintEstimatedTimeStatistics::ETimeMode::Normal: + { + imgui.text(_u8L("Estimated printing time") + " [" + _u8L("Normal mode") + "]:"); + break; + } + case PrintEstimatedTimeStatistics::ETimeMode::Stealth: + { + imgui.text(_u8L("Estimated printing time") + " [" + _u8L("Stealth mode") + "]:"); + break; + } + } + ImGui::SameLine(); + imgui.text(short_time(get_time_dhms(time_mode.time))); + + auto show_mode_button = [this, &imgui](const std::string& label, PrintEstimatedTimeStatistics::ETimeMode mode) { + bool show = false; + for (size_t i = 0; i < m_time_statistics.modes.size(); ++i) { + if (i != static_cast(mode) && + short_time(get_time_dhms(m_time_statistics.modes[static_cast(mode)].time)) != short_time(get_time_dhms(m_time_statistics.modes[i].time))) { + show = true; + break; + } + } + if (show && m_time_statistics.modes[static_cast(mode)].roles_times.size() > 0) { + if (imgui.button(label)) { + m_time_estimate_mode = mode; + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); + } + } + }; + + switch (m_time_estimate_mode) + { + case PrintEstimatedTimeStatistics::ETimeMode::Normal: + { + show_mode_button(_u8L("Show stealth mode"), PrintEstimatedTimeStatistics::ETimeMode::Stealth); + break; + } + case PrintEstimatedTimeStatistics::ETimeMode::Stealth: + { + show_mode_button(_u8L("Show normal mode"), PrintEstimatedTimeStatistics::ETimeMode::Normal); + break; + } + } + } + imgui.end(); ImGui::PopStyleVar(); } @@ -2262,81 +2448,91 @@ void GCodeViewer::render_legend() const #if ENABLE_GCODE_VIEWER_STATISTICS void GCodeViewer::render_statistics() const { - static const float offset = 230.0f; + static const float offset = 250.0f; ImGuiWrapper& imgui = *wxGetApp().imgui(); + auto add_time = [this, &imgui](const std::string& label, long long time) { + char buf[1024]; + sprintf(buf, "%lld ms (%s)", time, get_time_dhms(static_cast(time) * 0.001f).c_str()); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label); + ImGui::SameLine(offset); + imgui.text(buf); + }; + + auto add_memory = [this, &imgui](const std::string& label, long long memory) { + static const float mb = 1024.0f * 1024.0f; + static const float inv_mb = 1.0f / mb; + static const float gb = 1024.0f * mb; + static const float inv_gb = 1.0f / gb; + char buf[1024]; + if (static_cast(memory) < gb) + sprintf(buf, "%lld bytes (%.3f MB)", memory, static_cast(memory) * inv_mb); + else + sprintf(buf, "%lld bytes (%.3f GB)", memory, static_cast(memory) * inv_gb); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label); + ImGui::SameLine(offset); + imgui.text(buf); + }; + + auto add_counter = [this, &imgui](const std::string& label, long long counter) { + char buf[1024]; + sprintf(buf, "%lld", counter); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label); + ImGui::SameLine(offset); + imgui.text(buf); + }; + imgui.set_next_window_pos(0.5f * wxGetApp().plater()->get_current_canvas3D()->get_canvas_size().get_width(), 0.0f, ImGuiCond_Once, 0.5f, 0.0f); + ImGui::SetNextWindowSizeConstraints({ 300, -1 }, { 600, -1 }); imgui.begin(std::string("GCodeViewer Statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize); ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow()); - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("GCodeProcessor time:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.results_time) + " ms"); + if (ImGui::CollapsingHeader("Time")) { + add_time(std::string("GCodeProcessor:"), m_statistics.results_time); - ImGui::Separator(); + ImGui::Separator(); + add_time(std::string("Load:"), m_statistics.load_time); + add_time(std::string("Refresh:"), m_statistics.refresh_time); + add_time(std::string("Refresh paths:"), m_statistics.refresh_paths_time); + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); + } - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Load time:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.load_time) + " ms"); + if (ImGui::CollapsingHeader("OpenGL calls")) { + add_counter(std::string("Multi GL_POINTS:"), m_statistics.gl_multi_points_calls_count); + add_counter(std::string("Multi GL_LINES:"), m_statistics.gl_multi_lines_calls_count); + add_counter(std::string("Multi GL_TRIANGLES:"), m_statistics.gl_multi_triangles_calls_count); + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); + } - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Refresh time:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.refresh_time) + " ms"); + if (ImGui::CollapsingHeader("CPU memory")) { + add_memory(std::string("GCodeProcessor results:"), m_statistics.results_size); - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Refresh paths time:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.refresh_paths_time) + " ms"); + ImGui::Separator(); + add_memory(std::string("Paths:"), m_statistics.paths_size); + add_memory(std::string("Render paths:"), m_statistics.render_paths_size); + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); + } - ImGui::Separator(); + if (ImGui::CollapsingHeader("GPU memory")) { + add_memory(std::string("Vertices:"), m_statistics.vertices_gpu_size); + add_memory(std::string("Indices:"), m_statistics.indices_gpu_size); + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); + } - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Multi GL_POINTS calls:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.gl_multi_points_calls_count)); + if (ImGui::CollapsingHeader("Other")) { + add_counter(std::string("Travel segments count:"), m_statistics.travel_segments_count); + add_counter(std::string("Extrude segments count:"), m_statistics.extrude_segments_count); + add_counter(std::string("Max vertices in vertex buffer:"), m_statistics.max_vertices_in_vertex_buffer); + add_counter(std::string("Max indices in index buffer:"), m_statistics.max_indices_in_index_buffer); - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Multi GL_LINES calls:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.gl_multi_lines_calls_count)); - - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Multi GL_TRIANGLES calls:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.gl_multi_triangles_calls_count)); - - ImGui::Separator(); - - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("GCodeProcessor results:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.results_size) + " bytes"); - - ImGui::Separator(); - - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Paths CPU:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.paths_size) + " bytes"); - - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Render paths CPU:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.render_paths_size) + " bytes"); - - ImGui::Separator(); - - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Vertices GPU:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.vertices_gpu_size) + " bytes"); - - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Indices GPU:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.indices_gpu_size) + " bytes"); - - ImGui::Separator(); - - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Travel segments count:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.travel_segments_count)); - - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Extrude segments count:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.extrude_segments_count)); + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); + } imgui.end(); } @@ -2365,6 +2561,26 @@ bool GCodeViewer::is_travel_in_z_range(size_t id) const return is_in_z_range(path); } +void GCodeViewer::log_memory_used(const std::string& label, long long additional) const +{ + if (Slic3r::get_logging_level() >= 5) { + long long paths_size = 0; + long long render_paths_size = 0; + for (const TBuffer& buffer : m_buffers) { + paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.paths, Path); + render_paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.render_paths, RenderPath); + for (const RenderPath& path : buffer.render_paths) { + render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.sizes, unsigned int); + render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.offsets, size_t); + } + } + long long layers_zs_size = SLIC3R_STDVEC_MEMSIZE(m_layers_zs, double); + BOOST_LOG_TRIVIAL(trace) << label + << format_memsize_MB(additional + paths_size + render_paths_size + layers_zs_size) + << log_memory_info(); + } +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 68fed6f334..8c5d8b743f 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -18,6 +18,9 @@ namespace GUI { class GCodeViewer { using Color = std::array; + using IndexBuffer = std::vector; + using MultiIndexBuffer = std::vector; + static const std::vector Extrusion_Role_Colors; static const std::vector Options_Colors; static const std::vector Travel_Colors; @@ -112,10 +115,12 @@ class GCodeViewer { struct Endpoint { - // index into the indices buffer - unsigned int i_id{ 0u }; - // sequential id - unsigned int s_id{ 0u }; + // index of the index buffer + unsigned int b_id{ 0 }; + // index into the index buffer + size_t i_id{ 0 }; + // sequential id (index into the vertex buffer) + size_t s_id{ 0 }; Vec3f position{ Vec3f::Zero() }; }; @@ -134,16 +139,17 @@ class GCodeViewer bool matches(const GCodeProcessor::MoveVertex& move) const; size_t vertices_count() const { return last.s_id - first.s_id + 1; } - bool contains(unsigned int id) const { return first.s_id <= id && id <= last.s_id; } + bool contains(size_t id) const { return first.s_id <= id && id <= last.s_id; } }; // Used to batch the indices needed to render paths struct RenderPath { Color color; - size_t path_id; + unsigned int path_id; + unsigned int index_buffer_id; std::vector sizes; - std::vector offsets; // use size_t because we need the pointer's size (used in the call glMultiDrawElements()) + std::vector offsets; // use size_t because we need an unsigned int whose size matches pointer's size (used in the call glMultiDrawElements()) }; // buffer containing data for rendering a specific toolpath type @@ -158,7 +164,7 @@ class GCodeViewer ERenderPrimitiveType render_primitive_type; VBuffer vertices; - IBuffer indices; + std::vector indices; std::string shader; std::vector paths; @@ -166,7 +172,10 @@ class GCodeViewer bool visible{ false }; void reset(); - void add_path(const GCodeProcessor::MoveVertex& move, unsigned int i_id, unsigned int s_id); + // b_id index of buffer contained in this->indices + // i_id index of first index contained in this->indices[b_id] + // s_id index of first vertex contained in this->vertices + void add_path(const GCodeProcessor::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id); unsigned int indices_per_segment() const { switch (render_primitive_type) { @@ -194,6 +203,8 @@ class GCodeViewer default: { return 0; } } } + + bool has_data() const { return vertices.id != 0 && !indices.empty() && indices.front().id != 0; } }; // helper to render shells @@ -264,7 +275,7 @@ class GCodeViewer #if ENABLE_GCODE_VIEWER_STATISTICS struct Statistics { - // times + // time long long results_time{ 0 }; long long load_time{ 0 }; long long refresh_time{ 0 }; @@ -279,15 +290,17 @@ class GCodeViewer long long indices_gpu_size{ 0 }; long long paths_size{ 0 }; long long render_paths_size{ 0 }; - // others + // other long long travel_segments_count{ 0 }; long long extrude_segments_count{ 0 }; + long long max_vertices_in_vertex_buffer{ 0 }; + long long max_indices_in_index_buffer{ 0 }; void reset_all() { reset_times(); reset_opengl(); reset_sizes(); - reset_counters(); + reset_others(); } void reset_times() { @@ -311,9 +324,11 @@ class GCodeViewer render_paths_size = 0; } - void reset_counters() { + void reset_others() { travel_segments_count = 0; extrude_segments_count = 0; + max_vertices_in_vertex_buffer = 0; + max_indices_in_index_buffer = 0; } }; #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -346,8 +361,8 @@ public: struct Endpoints { - unsigned int first{ 0 }; - unsigned int last{ 0 }; + size_t first{ 0 }; + size_t last{ 0 }; }; Endpoints endpoints; @@ -371,7 +386,7 @@ public: private: unsigned int m_last_result_id{ 0 }; - size_t m_vertices_count{ 0 }; + size_t m_moves_count{ 0 }; mutable std::vector m_buffers{ static_cast(EMoveType::Extrude) }; // bounding box of toolpaths BoundingBoxf3 m_paths_bounding_box; @@ -444,7 +459,6 @@ public: void export_toolpaths_to_obj(const char* filename) const; private: - void init_shaders(); void load_toolpaths(const GCodeProcessor::Result& gcode_result); void load_shells(const Print& print, bool initialized); void refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const; @@ -466,6 +480,7 @@ private: return in_z_range(path.first.position[2]) || in_z_range(path.last.position[2]); } bool is_travel_in_z_range(size_t id) const; + void log_memory_used(const std::string& label, long long additional = 0) const; }; } // namespace GUI diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 00034087c7..e0c8c4c5bd 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -4999,7 +4999,7 @@ bool GLCanvas3D::_init_main_toolbar() return false; item.name = "settings"; - item.icon_filename = "cog_.svg"; + item.icon_filename = "settings.svg"; item.tooltip = _u8L("Switch to Settings") + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "2] - " + _u8L("Print Settings Tab") + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "3] - " + (m_process->current_printer_technology() == ptFFF ? _u8L("Filament Settings Tab") : _u8L("Material Settings Tab")) + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "4] - " + _u8L("Printer Settings Tab") ; @@ -5011,35 +5011,16 @@ bool GLCanvas3D::_init_main_toolbar() if (!m_main_toolbar.add_item(item)) return false; + /* if (!m_main_toolbar.add_separator()) return false; - - item.name = "layersediting"; - item.icon_filename = "layers_white.svg"; - item.tooltip = _utf8(L("Variable layer height")); - item.sprite_id = 11; - item.left.toggable = true; - item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); }; - item.visibility_callback = [this]()->bool - { - bool res = m_process->current_printer_technology() == ptFFF; - // turns off if changing printer technology - if (!res && m_main_toolbar.is_item_visible("layersediting") && m_main_toolbar.is_item_pressed("layersediting")) - force_main_toolbar_left_action(get_main_toolbar_item_id("layersediting")); - - return res; - }; - item.enabling_callback = []()->bool { return wxGetApp().plater()->can_layers_editing(); }; - if (!m_main_toolbar.add_item(item)) - return false; - - if (!m_main_toolbar.add_separator()) - return false; + */ item.name = "search"; item.icon_filename = "search_.svg"; item.tooltip = _utf8(L("Search")) + " [" + GUI::shortkey_ctrl_prefix() + "F]"; - item.sprite_id = 12; + item.sprite_id = 11; + item.left.toggable = true; item.left.render_callback = [this](float left, float right, float, float) { if (m_canvas != nullptr) { @@ -5053,6 +5034,27 @@ bool GLCanvas3D::_init_main_toolbar() if (!m_main_toolbar.add_item(item)) return false; + if (!m_main_toolbar.add_separator()) + return false; + + item.name = "layersediting"; + item.icon_filename = "layers_white.svg"; + item.tooltip = _utf8(L("Variable layer height")); + item.sprite_id = 12; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); }; + item.visibility_callback = [this]()->bool { + bool res = m_process->current_printer_technology() == ptFFF; + // turns off if changing printer technology + if (!res && m_main_toolbar.is_item_visible("layersediting") && m_main_toolbar.is_item_pressed("layersediting")) + force_main_toolbar_left_action(get_main_toolbar_item_id("layersediting")); + + return res; + }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_layers_editing(); }; + item.left.render_callback = GLToolbarItem::Default_Render_Callback; + if (!m_main_toolbar.add_item(item)) + return false; + return true; } @@ -5161,10 +5163,10 @@ bool GLCanvas3D::_init_undoredo_toolbar() if (!m_undoredo_toolbar.add_item(item)) return false; - + /* if (!m_undoredo_toolbar.add_separator()) return false; - + */ return true; } @@ -5553,6 +5555,10 @@ void GLCanvas3D::_render_selection_center() const void GLCanvas3D::_check_and_update_toolbar_icon_scale() const { + // Don't update a toolbar scale, when we are on a Preview + if (wxGetApp().plater()->is_preview_shown()) + return; + float scale = wxGetApp().toolbar_icon_scale(); Size cnv_size = get_canvas_size(); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 14fabc3edf..a9d75e5df7 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -211,7 +211,7 @@ public: copyright_string += //"Slic3r" + _L("is licensed under the") + _L("GNU Affero General Public License, version 3") + "\n\n" + _L("PrusaSlicer is based on Slic3r by Alessandro Ranellucci and the RepRap community.") + "\n\n" + _L("Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, Petr Ledvina, Joseph Lenox, Y. Sapir, Mike Sheldrake, Vojtech Bubnik and numerous others.") + "\n\n" + - _L("Splash screen could be desabled from the \"Preferences\""); + _L("Splash screen can be disabled from the \"Preferences\""); word_wrap_string(copyright_string, banner_width, screen_scale); @@ -599,7 +599,7 @@ void GUI_App::init_app_config() std::string error = app_config->load(); if (!error.empty()) // Error while parsing config file. We'll customize the error message and rethrow to be displayed. - throw std::runtime_error( + throw Slic3r::RuntimeError( _u8L("Error parsing PrusaSlicer config file, it is probably corrupted. " "Try to manually delete the file to recover from the error. Your user profiles will not be affected.") + "\n\n" + AppConfig::config_path() + "\n\n" + error); diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 0fecc822db..f9a23cb51c 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -927,7 +927,7 @@ void ImGuiWrapper::init_font(bool compress) if (font == nullptr) { font = io.Fonts->AddFontDefault(); if (font == nullptr) { - throw std::runtime_error("ImGui: Could not load deafult font"); + throw Slic3r::RuntimeError("ImGui: Could not load deafult font"); } } diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 6e653c2f81..d96b0565ad 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -7,6 +7,7 @@ #include #include #include +#include "libslic3r/Exception.hpp" #include "libslic3r/Utils.hpp" #include "I18N.hpp" @@ -64,7 +65,7 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co break; case coNone: break; default: - throw /*//!ConfigGUITypeError("")*/std::logic_error("This control doesn't exist till now"); break; + throw Slic3r::LogicError("This control doesn't exist till now"); break; } } // Grab a reference to fields for convenience @@ -683,7 +684,7 @@ boost::any ConfigOptionsGroup::config_value(const std::string& opt_key, int opt_ // Aggregate the strings the old way. // Currently used for the post_process config value only. if (opt_index != -1) - throw std::out_of_range("Can't deserialize option indexed value"); + throw Slic3r::OutOfRange("Can't deserialize option indexed value"); // return join(';', m_config->get(opt_key)}); return get_config_value(*m_config, opt_key); } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 7d6823b974..19f735714c 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -107,7 +107,7 @@ namespace GUI { wxDEFINE_EVENT(EVT_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent); wxDEFINE_EVENT(EVT_SLICING_UPDATE, SlicingStatusEvent); wxDEFINE_EVENT(EVT_SLICING_COMPLETED, wxCommandEvent); -wxDEFINE_EVENT(EVT_PROCESS_COMPLETED, wxCommandEvent); +wxDEFINE_EVENT(EVT_PROCESS_COMPLETED, SlicingProcessCompletedEvent); wxDEFINE_EVENT(EVT_EXPORT_BEGAN, wxCommandEvent); // Sidebar widgets @@ -1168,8 +1168,27 @@ void Sidebar::update_sliced_info_sizer() p->sliced_info->SetTextAndShow(siCost, info_text, new_label); #if ENABLE_GCODE_VIEWER - // hide the estimate time - p->sliced_info->SetTextAndShow(siEstimatedTime, "N/A"); + if (ps.estimated_normal_print_time == "N/A" && ps.estimated_silent_print_time == "N/A") + p->sliced_info->SetTextAndShow(siEstimatedTime, "N/A"); + else { + info_text = ""; + new_label = _L("Estimated printing time") + ":"; + if (ps.estimated_normal_print_time != "N/A") { + new_label += format_wxstr("\n - %1%", _L("normal mode")); + info_text += format_wxstr("\n%1%", short_time(ps.estimated_normal_print_time)); + + // uncomment next line to not disappear slicing finished notif when colapsing sidebar before time estimate + //if (p->plater->is_sidebar_collapsed()) + p->plater->get_notification_manager()->set_slicing_complete_large(p->plater->is_sidebar_collapsed()); + p->plater->get_notification_manager()->set_slicing_complete_print_time("Estimated printing time: " + ps.estimated_normal_print_time); + + } + if (ps.estimated_silent_print_time != "N/A") { + new_label += format_wxstr("\n - %1%", _L("stealth mode")); + info_text += format_wxstr("\n%1%", short_time(ps.estimated_silent_print_time)); + } + p->sliced_info->SetTextAndShow(siEstimatedTime, info_text, new_label); + } #else if (ps.estimated_normal_print_time == "N/A" && ps.estimated_silent_print_time == "N/A") p->sliced_info->SetTextAndShow(siEstimatedTime, "N/A"); @@ -1684,7 +1703,7 @@ struct Plater::priv void on_select_preset(wxCommandEvent&); void on_slicing_update(SlicingStatusEvent&); void on_slicing_completed(wxCommandEvent&); - void on_process_completed(wxCommandEvent&); + void on_process_completed(SlicingProcessCompletedEvent&); void on_export_began(wxCommandEvent&); void on_layer_editing_toggled(bool enable); void on_slicing_began(); @@ -3512,7 +3531,7 @@ bool Plater::priv::warnings_dialog() return res == wxID_OK; } -void Plater::priv::on_process_completed(wxCommandEvent &evt) +void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) { // Stop the background task, wait until the thread goes into the "Idle" state. // At this point of time the thread should be either finished or canceled, @@ -3521,27 +3540,30 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt) this->statusbar()->reset_cancel_callback(); this->statusbar()->stop_busy(); - const bool canceled = evt.GetInt() < 0; - const bool error = evt.GetInt() == 0; - const bool success = evt.GetInt() > 0; // Reset the "export G-code path" name, so that the automatic background processing will be enabled again. this->background_process.reset_export(); - if (error) { - wxString message = evt.GetString(); - if (message.IsEmpty()) - message = _L("Export failed."); - notification_manager->push_slicing_error_notification(boost::nowide::narrow(message), *q->get_current_canvas3D()); - this->statusbar()->set_status_text(message); + if (evt.error()) { + std::string message = evt.format_error_message(); + if (evt.critical_error()) { + if (q->m_tracking_popup_menu) + // We don't want to pop-up a message box when tracking a pop-up menu. + // We postpone the error message instead. + q->m_tracking_popup_menu_error_message = message; + else + show_error(q, message); + } else + notification_manager->push_slicing_error_notification(message, *q->get_current_canvas3D()); + this->statusbar()->set_status_text(from_u8(message)); const wxString invalid_str = _L("Invalid data"); for (auto btn : { ActionButtonType::abReslice, ActionButtonType::abSendGCode, ActionButtonType::abExport }) sidebar->set_btn_label(btn, invalid_str); process_completed_with_error = true; } - if (canceled) + if (evt.cancelled()) this->statusbar()->set_status_text(_L("Cancelled")); - this->sidebar->show_sliced_info_sizer(success); + this->sidebar->show_sliced_info_sizer(evt.success()); // This updates the "Slice now", "Export G-code", "Arrange" buttons status. // Namely, it refreshes the "Out of print bed" property of all the ModelObjects, and it enables @@ -3562,7 +3584,7 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt) default: break; } - if (canceled) { + if (evt.cancelled()) { if (wxGetApp().get_mode() == comSimple) sidebar->set_btn_label(ActionButtonType::abReslice, "Slice now"); show_action_buttons(true); @@ -5378,6 +5400,7 @@ void Plater::on_config_change(const DynamicPrintConfig &config) this->set_printer_technology(config.opt_enum(opt_key)); // print technology is changed, so we should to update a search list p->sidebar->update_searcher(); + p->sidebar->show_sliced_info_sizer(false); #if ENABLE_GCODE_VIEWER p->reset_gcode_toolpaths(); #endif // ENABLE_GCODE_VIEWER diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index 8bc9393873..8c0fefc760 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -741,7 +741,8 @@ void PlaterPresetComboBox::update() if (m_type == Preset::TYPE_FILAMENT) { // Assign an extruder color to the selected item if the extruder color is defined. - filament_rgb = preset.config.opt_string("filament_colour", 0); + filament_rgb = is_selected ? selected_filament_preset->config.opt_string("filament_colour", 0) : + preset.config.opt_string("filament_colour", 0); extruder_rgb = (is_selected && !extruder_color.empty()) ? extruder_color : filament_rgb; single_bar = filament_rgb == extruder_rgb; diff --git a/src/slic3r/GUI/PrintHostDialogs.cpp b/src/slic3r/GUI/PrintHostDialogs.cpp index 216af5df49..96299c3813 100644 --- a/src/slic3r/GUI/PrintHostDialogs.cpp +++ b/src/slic3r/GUI/PrintHostDialogs.cpp @@ -140,8 +140,6 @@ PrintHostQueueDialog::PrintHostQueueDialog(wxWindow *parent) { const auto em = GetTextExtent("m").x; - SetSize(wxSize(HEIGHT * em, WIDTH * em)); - auto *topsizer = new wxBoxSizer(wxVERTICAL); job_list = new wxDataViewListCtrl(this, wxID_ANY); @@ -168,6 +166,8 @@ PrintHostQueueDialog::PrintHostQueueDialog(wxWindow *parent) topsizer->Add(btnsizer, 0, wxEXPAND); SetSizer(topsizer); + SetSize(wxSize(HEIGHT * em, WIDTH * em)); + job_list->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [this](wxDataViewEvent&) { on_list_select(); }); btn_cancel->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index 5a0d23a209..e7dec9fa81 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -590,8 +590,11 @@ void UnsavedChangesDialog::build(Preset::Type type, PresetCollection* dependent_ int btn_idx = 0; add_btn(&m_save_btn, m_save_btn_id, "save", Action::Save, btn_idx++); - if (dependent_presets && (type != dependent_presets->type() ? true : - dependent_presets->get_edited_preset().printer_technology() == dependent_presets->find_preset(new_selected_preset)->printer_technology())) + + const PresetCollection& printers = wxGetApp().preset_bundle->printers; + if (dependent_presets && (type == dependent_presets->type() ? + dependent_presets->get_edited_preset().printer_technology() == dependent_presets->find_preset(new_selected_preset)->printer_technology() : + printers.get_edited_preset().printer_technology() == printers.find_preset(new_selected_preset)->printer_technology() ) ) add_btn(&m_move_btn, m_move_btn_id, "paste_menu", Action::Move, btn_idx++); add_btn(&m_continue_btn, m_continue_btn_id, "cross", Action::Continue, btn_idx, false); @@ -935,7 +938,7 @@ void UnsavedChangesDialog::update_tree(Preset::Type type, PresetCollection* pres } - for (const std::string& opt_key : /*presets->current_dirty_options()*/dirty_options) { + for (const std::string& opt_key : dirty_options) { const Search::Option& option = searcher.get_option(opt_key); ItemData item_data = { opt_key, option.label_local, get_string_value(opt_key, old_config), get_string_value(opt_key, new_config), type }; diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index d23c3415fa..6f79db5916 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -436,7 +436,7 @@ wxBitmap create_scaled_bitmap( const std::string& bmp_name_in, if (bmp == nullptr) { // Neither SVG nor PNG has been found, raise error - throw std::runtime_error("Could not load bitmap: " + bmp_name); + throw Slic3r::RuntimeError("Could not load bitmap: " + bmp_name); } return *bmp; diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index e20e5c8bda..4f04bc5b20 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -338,7 +338,7 @@ class BlinkingBitmap : public wxStaticBitmap { public: BlinkingBitmap() {}; - BlinkingBitmap(wxWindow* parent, const std::string& icon_name = "redo_toolbar"); + BlinkingBitmap(wxWindow* parent, const std::string& icon_name = "search_blink"); ~BlinkingBitmap() {} diff --git a/src/slic3r/Utils/FixModelByWin10.cpp b/src/slic3r/Utils/FixModelByWin10.cpp index 86ff79aaaf..bcab6daaf8 100644 --- a/src/slic3r/Utils/FixModelByWin10.cpp +++ b/src/slic3r/Utils/FixModelByWin10.cpp @@ -209,10 +209,10 @@ typedef std::functionGetResults(model.GetAddressOf()); else - throw std::runtime_error(L("Failed loading the input model.")); + throw Slic3r::RuntimeError(L("Failed loading the input model.")); Microsoft::WRL::ComPtr> meshes; hr = model->get_Meshes(meshes.GetAddressOf()); @@ -245,7 +245,7 @@ void fix_model_by_win10_sdk(const std::string &path_src, const std::string &path hr = model->RepairAsync(repairAsync.GetAddressOf()); status = winrt_async_await(repairAsync, throw_on_cancel); if (status != AsyncStatus::Completed) - throw std::runtime_error(L("Mesh repair failed.")); + throw Slic3r::RuntimeError(L("Mesh repair failed.")); repairAsync->GetResults(); on_progress(L("Loading repaired model"), 60); @@ -260,14 +260,14 @@ void fix_model_by_win10_sdk(const std::string &path_src, const std::string &path hr = printing3d3mfpackage->SaveModelToPackageAsync(model.Get(), saveToPackageAsync.GetAddressOf()); status = winrt_async_await(saveToPackageAsync, throw_on_cancel); if (status != AsyncStatus::Completed) - throw std::runtime_error(L("Saving mesh into the 3MF container failed.")); + throw Slic3r::RuntimeError(L("Saving mesh into the 3MF container failed.")); hr = saveToPackageAsync->GetResults(); Microsoft::WRL::ComPtr> generatorStreamAsync; hr = printing3d3mfpackage->SaveAsync(generatorStreamAsync.GetAddressOf()); status = winrt_async_await(generatorStreamAsync, throw_on_cancel); if (status != AsyncStatus::Completed) - throw std::runtime_error(L("Saving mesh into the 3MF container failed.")); + throw Slic3r::RuntimeError(L("Saving mesh into the 3MF container failed.")); Microsoft::WRL::ComPtr generatorStream; hr = generatorStreamAsync->GetResults(generatorStream.GetAddressOf()); @@ -299,7 +299,7 @@ void fix_model_by_win10_sdk(const std::string &path_src, const std::string &path hr = inputStream->ReadAsync(buffer.Get(), 65536 * 2048, ABI::Windows::Storage::Streams::InputStreamOptions_ReadAhead, asyncRead.GetAddressOf()); status = winrt_async_await(asyncRead, throw_on_cancel); if (status != AsyncStatus::Completed) - throw std::runtime_error(L("Saving mesh into the 3MF container failed.")); + throw Slic3r::RuntimeError(L("Saving mesh into the 3MF container failed.")); hr = buffer->get_Length(&length); if (length == 0) break; @@ -365,7 +365,7 @@ void fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx) model_object->add_instance(); if (!Slic3r::store_3mf(path_src.string().c_str(), &model, nullptr, false)) { boost::filesystem::remove(path_src); - throw std::runtime_error(L("Export of a temporary 3mf file failed")); + throw Slic3r::RuntimeError(L("Export of a temporary 3mf file failed")); } model.clear_objects(); model.clear_materials(); @@ -380,15 +380,15 @@ void fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx) bool loaded = Slic3r::load_3mf(path_dst.string().c_str(), &config, &model, false); boost::filesystem::remove(path_dst); if (! loaded) - throw std::runtime_error(L("Import of the repaired 3mf file failed")); + throw Slic3r::RuntimeError(L("Import of the repaired 3mf file failed")); if (model.objects.size() == 0) - throw std::runtime_error(L("Repaired 3MF file does not contain any object")); + throw Slic3r::RuntimeError(L("Repaired 3MF file does not contain any object")); if (model.objects.size() > 1) - throw std::runtime_error(L("Repaired 3MF file contains more than one object")); + throw Slic3r::RuntimeError(L("Repaired 3MF file contains more than one object")); if (model.objects.front()->volumes.size() == 0) - throw std::runtime_error(L("Repaired 3MF file does not contain any volume")); + throw Slic3r::RuntimeError(L("Repaired 3MF file does not contain any volume")); if (model.objects.front()->volumes.size() > 1) - throw std::runtime_error(L("Repaired 3MF file contains more than one volume")); + throw Slic3r::RuntimeError(L("Repaired 3MF file contains more than one volume")); meshes_repaired.emplace_back(std::move(model.objects.front()->volumes.front()->mesh())); } for (size_t i = 0; i < volumes.size(); ++ i) { diff --git a/src/slic3r/Utils/Http.cpp b/src/slic3r/Utils/Http.cpp index e55c21fe17..8c79a478a1 100644 --- a/src/slic3r/Utils/Http.cpp +++ b/src/slic3r/Utils/Http.cpp @@ -156,7 +156,7 @@ Http::priv::priv(const std::string &url) Http::tls_global_init(); if (curl == nullptr) { - throw std::runtime_error(std::string("Could not construct Curl object")); + throw Slic3r::RuntimeError(std::string("Could not construct Curl object")); } set_timeout_connect(DEFAULT_TIMEOUT_CONNECT); diff --git a/src/slic3r/Utils/OctoPrint.cpp b/src/slic3r/Utils/OctoPrint.cpp index 82d8a6bb63..fad45f8225 100644 --- a/src/slic3r/Utils/OctoPrint.cpp +++ b/src/slic3r/Utils/OctoPrint.cpp @@ -11,7 +11,6 @@ #include -#include "libslic3r/PrintConfig.hpp" #include "slic3r/GUI/I18N.hpp" #include "slic3r/GUI/GUI.hpp" #include "Http.hpp" diff --git a/src/slic3r/Utils/OctoPrint.hpp b/src/slic3r/Utils/OctoPrint.hpp index 4f8e4819fc..ed1c61bd60 100644 --- a/src/slic3r/Utils/OctoPrint.hpp +++ b/src/slic3r/Utils/OctoPrint.hpp @@ -6,6 +6,7 @@ #include #include "PrintHost.hpp" +#include "libslic3r/PrintConfig.hpp" namespace Slic3r { diff --git a/src/slic3r/Utils/Process.hpp b/src/slic3r/Utils/Process.hpp index c6acaa643e..65f90222dd 100644 --- a/src/slic3r/Utils/Process.hpp +++ b/src/slic3r/Utils/Process.hpp @@ -2,6 +2,7 @@ #define GUI_PROCESS_HPP class wxWindow; +class wxString; namespace Slic3r { namespace GUI { diff --git a/src/slic3r/Utils/Serial.cpp b/src/slic3r/Utils/Serial.cpp index 737e76c0b5..0c1ad1de5b 100644 --- a/src/slic3r/Utils/Serial.cpp +++ b/src/slic3r/Utils/Serial.cpp @@ -1,5 +1,7 @@ #include "Serial.hpp" +#include "libslic3r/Exception.hpp" + #include #include #include @@ -298,7 +300,7 @@ void Serial::set_baud_rate(unsigned baud_rate) auto handle_errno = [](int retval) { if (retval != 0) { - throw std::runtime_error( + throw Slic3r::RuntimeError( (boost::format("Could not set baud rate: %1%") % strerror(errno)).str() ); } @@ -346,7 +348,7 @@ void Serial::set_baud_rate(unsigned baud_rate) handle_errno(::cfsetspeed(&ios, baud_rate)); handle_errno(::tcsetattr(handle, TCSAFLUSH, &ios)); #else - throw std::runtime_error("Custom baud rates are not currently supported on this OS"); + throw Slic3r::RuntimeError("Custom baud rates are not currently supported on this OS"); #endif } } @@ -358,7 +360,7 @@ void Serial::set_DTR(bool on) auto handle = native_handle(); #if defined(_WIN32) && !defined(__SYMBIAN32__) if (! EscapeCommFunction(handle, on ? SETDTR : CLRDTR)) { - throw std::runtime_error("Could not set serial port DTR"); + throw Slic3r::RuntimeError("Could not set serial port DTR"); } #else int status; @@ -369,7 +371,7 @@ void Serial::set_DTR(bool on) } } - throw std::runtime_error( + throw Slic3r::RuntimeError( (boost::format("Could not set serial port DTR: %1%") % strerror(errno)).str() ); #endif diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index 9c8d7a8c68..10b8062f7b 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -847,7 +847,7 @@ void StackImpl::load_snapshot(size_t timestamp, Slic3r::Model& model, Slic3r::GU // Find the snapshot by time. It must exist. const auto it_snapshot = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(timestamp)); if (it_snapshot == m_snapshots.end() || it_snapshot->timestamp != timestamp) - throw std::runtime_error((boost::format("Snapshot with timestamp %1% does not exist") % timestamp).str()); + throw Slic3r::RuntimeError((boost::format("Snapshot with timestamp %1% does not exist") % timestamp).str()); m_active_snapshot_time = timestamp; model.clear_objects(); diff --git a/tests/fff_print/test_data.cpp b/tests/fff_print/test_data.cpp index 09ca730ec7..8e5f6bafdc 100644 --- a/tests/fff_print/test_data.cpp +++ b/tests/fff_print/test_data.cpp @@ -137,7 +137,7 @@ TriangleMesh mesh(TestMesh m) { {0,1,2}, {2,1,3}, {4,0,5}, {4,1,0}, {6,4,7}, {7,4,5}, {4,8,1}, {0,2,5}, {5,2,9}, {2,10,9}, {10,3,11}, {2,3,10}, {9,10,12}, {13,9,12}, {3,1,8}, {11,3,8}, {10,11,8}, {4,10,8}, {6,12,10}, {4,6,10}, {7,13,12}, {6,7,12}, {7,5,9}, {13,7,9} }); break; default: - throw std::invalid_argument("Slic3r::Test::mesh(): called with invalid mesh ID"); + throw Slic3r::InvalidArgument("Slic3r::Test::mesh(): called with invalid mesh ID"); break; } diff --git a/tests/sla_print/CMakeLists.txt b/tests/sla_print/CMakeLists.txt index d071b49739..dc583f1a1c 100644 --- a/tests/sla_print/CMakeLists.txt +++ b/tests/sla_print/CMakeLists.txt @@ -1,7 +1,7 @@ get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests_main.cpp sla_print_tests.cpp - sla_test_utils.hpp sla_test_utils.cpp sla_treebuilder_tests.cpp + sla_test_utils.hpp sla_test_utils.cpp sla_supptgen_tests.cpp sla_raycast_tests.cpp) target_link_libraries(${_TEST_NAME}_tests test_common libslic3r) diff --git a/tests/sla_print/sla_print_tests.cpp b/tests/sla_print/sla_print_tests.cpp index 1575ee0e6b..c1941c8d98 100644 --- a/tests/sla_print/sla_print_tests.cpp +++ b/tests/sla_print/sla_print_tests.cpp @@ -128,9 +128,9 @@ TEST_CASE("WingedPadAroundObjectIsValid", "[SLASupportGeneration]") { TEST_CASE("ElevatedSupportGeometryIsValid", "[SLASupportGeneration]") { sla::SupportTreeConfig supportcfg; - supportcfg.object_elevation_mm = 5.; + supportcfg.object_elevation_mm = 10.; - for (auto fname : SUPPORT_TEST_MODELS) test_supports(fname); + for (auto fname : SUPPORT_TEST_MODELS) test_supports(fname, supportcfg); } TEST_CASE("FloorSupportGeometryIsValid", "[SLASupportGeneration]") { diff --git a/tests/sla_print/sla_treebuilder_tests.cpp b/tests/sla_print/sla_treebuilder_tests.cpp deleted file mode 100644 index 91c2ea6f8e..0000000000 --- a/tests/sla_print/sla_treebuilder_tests.cpp +++ /dev/null @@ -1,99 +0,0 @@ -//#include -//#include - -//#include "libslic3r/TriangleMesh.hpp" -//#include "libslic3r/SLA/SupportTreeBuildsteps.hpp" -//#include "libslic3r/SLA/SupportTreeMesher.hpp" - -//TEST_CASE("Test bridge_mesh_intersect on a cube's wall", "[SLABridgeMeshInters]") { -// using namespace Slic3r; - -// TriangleMesh cube = make_cube(10., 10., 10.); - -// sla::SupportConfig cfg = {}; // use default config -// sla::SupportPoints pts = {{10.f, 5.f, 5.f, float(cfg.head_front_radius_mm), false}}; -// sla::SupportableMesh sm{cube, pts, cfg}; - -// size_t steps = 45; -// SECTION("Bridge is straight horizontal and pointing away from the cube") { - -// sla::Bridge bridge(pts[0].pos.cast(), Vec3d{15., 5., 5.}, -// pts[0].head_front_radius); - -// auto hit = sla::query_hit(sm, bridge); - -// REQUIRE(std::isinf(hit.distance())); - -// cube.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); -// cube.require_shared_vertices(); -// cube.WriteOBJFile("cube1.obj"); -// } - -// SECTION("Bridge is tilted down in 45 degrees, pointing away from the cube") { -// sla::Bridge bridge(pts[0].pos.cast(), Vec3d{15., 5., 0.}, -// pts[0].head_front_radius); - -// auto hit = sla::query_hit(sm, bridge); - -// REQUIRE(std::isinf(hit.distance())); - -// cube.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); -// cube.require_shared_vertices(); -// cube.WriteOBJFile("cube2.obj"); -// } -//} - - -//TEST_CASE("Test bridge_mesh_intersect on a sphere", "[SLABridgeMeshInters]") { -// using namespace Slic3r; - -// TriangleMesh sphere = make_sphere(1.); - -// sla::SupportConfig cfg = {}; // use default config -// cfg.head_back_radius_mm = cfg.head_front_radius_mm; -// sla::SupportPoints pts = {{1.f, 0.f, 0.f, float(cfg.head_front_radius_mm), false}}; -// sla::SupportableMesh sm{sphere, pts, cfg}; - -// size_t steps = 45; -// SECTION("Bridge is straight horizontal and pointing away from the sphere") { - -// sla::Bridge bridge(pts[0].pos.cast(), Vec3d{2., 0., 0.}, -// pts[0].head_front_radius); - -// auto hit = sla::query_hit(sm, bridge); - -// sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); -// sphere.require_shared_vertices(); -// sphere.WriteOBJFile("sphere1.obj"); - -// REQUIRE(std::isinf(hit.distance())); -// } - -// SECTION("Bridge is tilted down 45 deg and pointing away from the sphere") { - -// sla::Bridge bridge(pts[0].pos.cast(), Vec3d{2., 0., -2.}, -// pts[0].head_front_radius); - -// auto hit = sla::query_hit(sm, bridge); - -// sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); -// sphere.require_shared_vertices(); -// sphere.WriteOBJFile("sphere2.obj"); - -// REQUIRE(std::isinf(hit.distance())); -// } - -// SECTION("Bridge is tilted down 90 deg and pointing away from the sphere") { - -// sla::Bridge bridge(pts[0].pos.cast(), Vec3d{1., 0., -2.}, -// pts[0].head_front_radius); - -// auto hit = sla::query_hit(sm, bridge); - -// sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); -// sphere.require_shared_vertices(); -// sphere.WriteOBJFile("sphere3.obj"); - -// REQUIRE(std::isinf(hit.distance())); -// } -//}