diff --git a/CMakeLists.txt b/CMakeLists.txt index a3c2eca12..bc0c80869 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,6 +72,9 @@ if (MSVC) # error C3859: virtual memory range for PCH exceeded; please recompile with a command line option of '-Zm90' or greater # Generate symbols at every build target, even for the release. add_compile_options(-bigobj -Zm520 /Zi) + # Disable STL4007: Many result_type typedefs and all argument_type, first_argument_type, and second_argument_type typedefs are deprecated in C++17. + #FIXME Remove this line after eigen library adapts to the new C++17 adaptor rules. + add_compile_options(-D_SILENCE_CXX17_ADAPTOR_TYPEDEFS_DEPRECATION_WARNING) endif () if (MINGW) diff --git a/deps/MPFR/MPFR.cmake b/deps/MPFR/MPFR.cmake index 17ac283ea..cda7eeea9 100644 --- a/deps/MPFR/MPFR.cmake +++ b/deps/MPFR/MPFR.cmake @@ -21,7 +21,7 @@ else () ExternalProject_Add(dep_MPFR URL http://ftp.vim.org/ftp/gnu/mpfr/mpfr-3.1.6.tar.bz2 https://www.mpfr.org/mpfr-3.1.6/mpfr-3.1.6.tar.bz2 # mirrors are allowed BUILD_IN_SOURCE ON - CONFIGURE_COMMAND ./configure --prefix=${DESTDIR}/usr/local --with-gmp=${DESTDIR}/usr/local --with-pic + CONFIGURE_COMMAND ./configure --prefix=${DESTDIR}/usr/local --enable-shared=no --enable-static=yes --with-gmp=${DESTDIR}/usr/local --with-pic BUILD_COMMAND make -j INSTALL_COMMAND make install DEPENDS dep_GMP diff --git a/resources/icons/error_tick.svg b/resources/icons/error_tick.svg new file mode 100644 index 000000000..6467d29fb --- /dev/null +++ b/resources/icons/error_tick.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/resources/localization/PrusaSlicer.pot b/resources/localization/PrusaSlicer.pot index 13f55f7bb..c5bcaa3bd 100644 --- a/resources/localization/PrusaSlicer.pot +++ b/resources/localization/PrusaSlicer.pot @@ -3622,8 +3622,7 @@ msgstr "" #: src/slic3r/GUI/Plater.cpp:2315 msgid "" -"You can't load SLA project if there is at least one multi-part object on the " -"bed" +"You cannot load SLA project with a multi-part object on the bed" msgstr "" #: src/slic3r/GUI/Plater.cpp:2316 src/slic3r/GUI/Tab.cpp:2915 diff --git a/resources/localization/pt_br/PrusaSlicer_pt_br.po b/resources/localization/pt_br/PrusaSlicer_pt_br.po index e54f17ec3..30c0687a5 100644 --- a/resources/localization/pt_br/PrusaSlicer_pt_br.po +++ b/resources/localization/pt_br/PrusaSlicer_pt_br.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2019-09-09 16:39+0200\n" -"PO-Revision-Date: 2019-11-18 16:39-0300\n" +"PO-Revision-Date: 2019-12-21 19:55-0300\n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -1432,7 +1432,7 @@ msgid "" "You will lose content of the plater." msgstr "" "Alterar a linguagem fará com que o aplicativo reinicie.\n" -"Você irá perder conteúdo no prato." +"Você irá perder conteúdo na bandeja." #: src/slic3r/GUI/GUI_App.cpp:877 msgid "Do you want to proceed?" @@ -2302,7 +2302,7 @@ msgstr "(Re)fatiar" #: src/slic3r/GUI/KBShortcutsDialog.cpp:116 msgid "Select Plater Tab" -msgstr "Selecione a guia de prato" +msgstr "Selecione a guia de bandeja" #: src/slic3r/GUI/KBShortcutsDialog.cpp:118 msgid "Select Print Settings Tab" @@ -2466,7 +2466,7 @@ msgstr "Desmarcar Gizmo/limpar seleção" #: src/slic3r/GUI/KBShortcutsDialog.cpp:166 msgid "Plater Shortcuts" -msgstr "Atalhos do prato" +msgstr "Atalhos da bandeja" #: src/slic3r/GUI/KBShortcutsDialog.cpp:181 #: src/slic3r/GUI/KBShortcutsDialog.cpp:193 @@ -2546,7 +2546,7 @@ msgstr "baseado no Slic3r" #: src/slic3r/GUI/MainFrame.cpp:189 msgid "Plater" -msgstr "Prato" +msgstr "Bandeja" #: src/slic3r/GUI/MainFrame.cpp:400 msgid "&New Project" @@ -2635,7 +2635,7 @@ msgstr "Exportar &G-code" #: src/slic3r/GUI/MainFrame.cpp:469 msgid "Export current plate as G-code" -msgstr "Exporte o prato atual como o G-code" +msgstr "Exporte a bandeja atual como o G-code" #: src/slic3r/GUI/MainFrame.cpp:473 src/slic3r/GUI/MainFrame.cpp:720 msgid "S&end G-code" @@ -2643,31 +2643,31 @@ msgstr "E&nviar G-code" #: src/slic3r/GUI/MainFrame.cpp:473 msgid "Send to print current plate as G-code" -msgstr "Enviar para imprimir prato atual como G-code" +msgstr "Enviar para imprimir a bandeja atual como G-code" #: src/slic3r/GUI/MainFrame.cpp:478 msgid "Export plate as &STL" -msgstr "Exportar prato como &STL" +msgstr "Exportar bandeja como &STL" #: src/slic3r/GUI/MainFrame.cpp:478 msgid "Export current plate as STL" -msgstr "Exporte o prato atual como STL" +msgstr "Exporte a bandeja atual como STL" #: src/slic3r/GUI/MainFrame.cpp:481 msgid "Export plate as STL &including supports" -msgstr "Exportar prato como STL &incluindo suportes" +msgstr "Exportar bandeja como STL &incluindo suportes" #: src/slic3r/GUI/MainFrame.cpp:481 msgid "Export current plate as STL including supports" -msgstr "Exporte o prato atual como o STL que inclui suportes" +msgstr "Exporte a bandeja atual como o STL que inclui suportes" #: src/slic3r/GUI/MainFrame.cpp:484 msgid "Export plate as &AMF" -msgstr "Exportar prato como &AMF" +msgstr "Exportar bandeja como &AMF" #: src/slic3r/GUI/MainFrame.cpp:484 msgid "Export current plate as AMF" -msgstr "Exporte o prato atual como o AMF" +msgstr "Exporte a bandeja atual como o AMF" #: src/slic3r/GUI/MainFrame.cpp:488 msgid "Export &toolpaths as OBJ" @@ -2804,11 +2804,11 @@ msgstr "Colar área de transferência" #: src/slic3r/GUI/MainFrame.cpp:590 msgid "&Plater Tab" -msgstr "&Prato" +msgstr "&Bandeja" #: src/slic3r/GUI/MainFrame.cpp:590 msgid "Show the plater" -msgstr "Mostrar o prato" +msgstr "Mostrar a bandeja" #: src/slic3r/GUI/MainFrame.cpp:597 msgid "P&rint Settings Tab" @@ -6198,7 +6198,7 @@ msgstr "Distância entre cópias" #: src/libslic3r/PrintConfig.cpp:352 msgid "Distance used for the auto-arrange feature of the plater." -msgstr "Distância usada para o recurso de organizar automaticamente o prato." +msgstr "Distância usada para o recurso de organizar automaticamente a bandeja." #: src/libslic3r/PrintConfig.cpp:359 msgid "Elephant foot compensation" @@ -6395,8 +6395,8 @@ msgid "" msgstr "" "Defina isso para o raio de folga em torno de sua extrusora. Se a extrusora " "não estiver centralizada, escolha o maior valor para a segurança. Essa " -"config. é usada para verificar colisões e exibir a visualização gráfica no " -"prato." +"config. é usada para verificar colisões e exibir a visualização gráfica na " +"bandeja." #: src/libslic3r/PrintConfig.cpp:505 msgid "Extruder Color" diff --git a/resources/profiles/Creality.ini b/resources/profiles/Creality.ini index 4ebf99d32..181bb4f4e 100644 --- a/resources/profiles/Creality.ini +++ b/resources/profiles/Creality.ini @@ -18,6 +18,9 @@ config_update_url = http://files.prusa3d.com/wp-content/uploads/repository/Prusa name = Creality Ender-3 variants = 0.4 technology = FFF +bed_model = ender3_bed.stl +bed_texture = ender3.svg +default_materials = Creality PLA @ENDER3; Prusament PLA @ENDER3 # All presets starting with asterisk, for example *common*, are intermediate and they will # not make it into the user interface. diff --git a/resources/icons/bed/ender3.svg b/resources/profiles/Creality/ender3.svg similarity index 100% rename from resources/icons/bed/ender3.svg rename to resources/profiles/Creality/ender3.svg diff --git a/resources/models/ender3_bed.stl b/resources/profiles/Creality/ender3_bed.stl similarity index 100% rename from resources/models/ender3_bed.stl rename to resources/profiles/Creality/ender3_bed.stl diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index 746aaf966..4f5c0482b 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -22,72 +22,108 @@ name = Original Prusa MINI variants = 0.4; 0.25; 0.6 technology = FFF family = MINI +bed_model = mini_bed.stl +bed_texture = mini.svg +default_materials = Prusament PLA; Prusament PETG @MINI [printer_model:MK3S] name = Original Prusa i3 MK3S variants = 0.4; 0.25; 0.6 technology = FFF family = MK3 +bed_model = mk3_bed.stl +bed_texture = mk3.svg +default_materials = Prusament PLA; Prusament PETG [printer_model:MK3] name = Original Prusa i3 MK3 variants = 0.4; 0.25; 0.6 technology = FFF family = MK3 +bed_model = mk3_bed.stl +bed_texture = mk3.svg +default_materials = Prusament PLA; Prusament PETG [printer_model:MK3SMMU2S] name = Original Prusa i3 MK3S MMU2S variants = 0.4; 0.25; 0.6 technology = FFF family = MK3 +bed_model = mk3_bed.stl +bed_texture = mk3.svg +default_materials = Prusament PLA @MMU2; Prusament PETG @MMU2 [printer_model:MK3MMU2] name = Original Prusa i3 MK3 MMU2 variants = 0.4; 0.25; 0.6 technology = FFF family = MK3 +bed_model = mk3_bed.stl +bed_texture = mk3.svg +default_materials = Prusament PLA @MMU2; Prusament PETG @MMU2 [printer_model:MK2.5S] name = Original Prusa i3 MK2.5S variants = 0.4; 0.25; 0.6 technology = FFF family = MK2.5 +bed_model = mk3_bed.stl +bed_texture = mk3.svg +default_materials = Prusament PLA; Prusament PETG [printer_model:MK2.5] name = Original Prusa i3 MK2.5 variants = 0.4; 0.25; 0.6 technology = FFF family = MK2.5 +bed_model = mk3_bed.stl +bed_texture = mk3.svg +default_materials = Prusament PLA; Prusament PETG [printer_model:MK2.5SMMU2S] name = Original Prusa i3 MK2.5S MMU2S variants = 0.4; 0.25; 0.6 technology = FFF family = MK2.5 +bed_model = mk3_bed.stl +bed_texture = mk3.svg +default_materials = Prusament PLA @MMU2; Prusament PETG @MMU2 [printer_model:MK2.5MMU2] name = Original Prusa i3 MK2.5 MMU2 variants = 0.4; 0.25; 0.6 technology = FFF family = MK2.5 +bed_model = mk3_bed.stl +bed_texture = mk3.svg +default_materials = Prusament PLA @MMU2; Prusament PETG @MMU2 [printer_model:MK2S] name = Original Prusa i3 MK2S variants = 0.4; 0.25; 0.6 technology = FFF family = MK2 +bed_model = mk2_bed.stl +bed_texture = mk2.svg +default_materials = Prusament PLA; Prusament PETG [printer_model:MK2SMM] name = Original Prusa i3 MK2S MMU1 variants = 0.4; 0.6 technology = FFF family = MK2 +bed_model = mk2_bed.stl +bed_texture = mk2.svg +default_materials = Prusament PLA; Prusament PETG @MMU1 [printer_model:SL1] name = Original Prusa SL1 variants = default technology = SLA family = SL1 +bed_model = sl1_bed.stl +bed_texture = sl1.svg +default_materials = Prusa Transparent Tough @0.05 # All presets starting with asterisk, for example *common*, are intermediate and they will # not make it into the user interface. diff --git a/resources/icons/bed/mini.svg b/resources/profiles/PrusaResearch/mini.svg similarity index 100% rename from resources/icons/bed/mini.svg rename to resources/profiles/PrusaResearch/mini.svg diff --git a/resources/models/mini_bed.stl b/resources/profiles/PrusaResearch/mini_bed.stl similarity index 100% rename from resources/models/mini_bed.stl rename to resources/profiles/PrusaResearch/mini_bed.stl diff --git a/resources/icons/bed/mk2.svg b/resources/profiles/PrusaResearch/mk2.svg similarity index 100% rename from resources/icons/bed/mk2.svg rename to resources/profiles/PrusaResearch/mk2.svg diff --git a/resources/models/mk2_bed.stl b/resources/profiles/PrusaResearch/mk2_bed.stl similarity index 100% rename from resources/models/mk2_bed.stl rename to resources/profiles/PrusaResearch/mk2_bed.stl diff --git a/resources/icons/bed/mk3.svg b/resources/profiles/PrusaResearch/mk3.svg similarity index 100% rename from resources/icons/bed/mk3.svg rename to resources/profiles/PrusaResearch/mk3.svg diff --git a/resources/models/mk3_bed.stl b/resources/profiles/PrusaResearch/mk3_bed.stl similarity index 100% rename from resources/models/mk3_bed.stl rename to resources/profiles/PrusaResearch/mk3_bed.stl diff --git a/resources/icons/bed/sl1.svg b/resources/profiles/PrusaResearch/sl1.svg similarity index 100% rename from resources/icons/bed/sl1.svg rename to resources/profiles/PrusaResearch/sl1.svg diff --git a/resources/models/sl1_bed.stl b/resources/profiles/PrusaResearch/sl1_bed.stl similarity index 100% rename from resources/models/sl1_bed.stl rename to resources/profiles/PrusaResearch/sl1_bed.stl diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index d755f0b2e..28aadc045 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -641,10 +641,18 @@ bool CLI::export_models(IO::ExportFormat format) const std::string path = this->output_filepath(model, format); bool success = false; switch (format) { +#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF + case IO::AMF: success = Slic3r::store_amf(path.c_str(), &model, nullptr, false); break; +#else case IO::AMF: success = Slic3r::store_amf(path.c_str(), &model, nullptr); break; +#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF case IO::OBJ: success = Slic3r::store_obj(path.c_str(), &model); break; case IO::STL: success = Slic3r::store_stl(path.c_str(), &model, true); break; +#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF + case IO::TMF: success = Slic3r::store_3mf(path.c_str(), &model, nullptr, false); break; +#else case IO::TMF: success = Slic3r::store_3mf(path.c_str(), &model, nullptr); break; +#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF default: assert(false); break; } if (success) diff --git a/src/libnest2d/CMakeLists.txt b/src/libnest2d/CMakeLists.txt index 6484da3d0..592ab069c 100644 --- a/src/libnest2d/CMakeLists.txt +++ b/src/libnest2d/CMakeLists.txt @@ -24,7 +24,7 @@ set(LIBNEST2D_SRCFILES src/libnest2d.cpp ) -add_library(libnest2d ${LIBNEST2D_SRCFILES}) +add_library(libnest2d STATIC ${LIBNEST2D_SRCFILES}) target_include_directories(libnest2d PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include) target_link_libraries(libnest2d PUBLIC clipper NLopt::nlopt TBB::tbb Boost::boost) diff --git a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp index 003028758..e44e1dae1 100644 --- a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp +++ b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp @@ -1116,12 +1116,8 @@ private: for(Item& item : items_) item.translate(d); } - void setInitialPosition(Item& item) { - auto sh = item.rawShape(); - sl::translate(sh, item.translation()); - sl::rotate(sh, item.rotation()); - - Box bb = sl::boundingBox(sh); + void setInitialPosition(Item& item) { + Box bb = item.boundingBox(); Vertex ci, cb; auto bbin = sl::boundingBox(bin_); diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 87a79d93f..9b04ae026 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -289,13 +289,13 @@ public: throw std::runtime_error("ConfigOptionVector::set_at(): Assigning an incompatible type"); } - T& get_at(size_t i) + const T& get_at(size_t i) const { assert(! this->values.empty()); return (i < this->values.size()) ? this->values[i] : this->values.front(); } - const T& get_at(size_t i) const { return const_cast*>(this)->get_at(i); } + T& get_at(size_t i) { return const_cast(std::as_const(*this).get_at(i)); } // Resize this vector by duplicating the /*last*/first value. // If the current vector is empty, the default value is used instead. @@ -500,7 +500,7 @@ public: if (NULLABLE) this->values.push_back(nil_value()); else - std::runtime_error("Deserializing nil into a non-nullable object"); + throw std::runtime_error("Deserializing nil into a non-nullable object"); } else { std::istringstream iss(item_str); double value; @@ -525,9 +525,9 @@ protected: if (NULLABLE) ss << "nil"; else - std::runtime_error("Serializing NaN"); + throw std::runtime_error("Serializing NaN"); } else - std::runtime_error("Serializing invalid number"); + throw std::runtime_error("Serializing invalid number"); } static bool vectors_equal(const std::vector &v1, const std::vector &v2) { if (NULLABLE) { @@ -646,7 +646,7 @@ public: if (NULLABLE) this->values.push_back(nil_value()); else - std::runtime_error("Deserializing nil into a non-nullable object"); + throw std::runtime_error("Deserializing nil into a non-nullable object"); } else { std::istringstream iss(item_str); int value; @@ -663,7 +663,7 @@ private: if (NULLABLE) ss << "nil"; else - std::runtime_error("Serializing NaN"); + throw std::runtime_error("Serializing NaN"); } else ss << v; } @@ -1126,7 +1126,7 @@ public: if (NULLABLE) this->values.push_back(nil_value()); else - std::runtime_error("Deserializing nil into a non-nullable object"); + throw std::runtime_error("Deserializing nil into a non-nullable object"); } else this->values.push_back(item_str.compare("1") == 0); } @@ -1139,7 +1139,7 @@ protected: if (NULLABLE) ss << "nil"; else - std::runtime_error("Serializing NaN"); + throw std::runtime_error("Serializing NaN"); } else ss << (v ? "1" : "0"); } @@ -1638,7 +1638,7 @@ class DynamicConfig : public virtual ConfigBase public: DynamicConfig() {} DynamicConfig(const DynamicConfig &rhs) { *this = rhs; } - DynamicConfig(DynamicConfig &&rhs) : options(std::move(rhs.options)) { rhs.options.clear(); } + DynamicConfig(DynamicConfig &&rhs) noexcept : options(std::move(rhs.options)) { rhs.options.clear(); } explicit DynamicConfig(const ConfigBase &rhs, const t_config_option_keys &keys); explicit DynamicConfig(const ConfigBase& rhs) : DynamicConfig(rhs, rhs.keys()) {} virtual ~DynamicConfig() override { clear(); } @@ -1656,7 +1656,7 @@ public: // Move a content of one DynamicConfig to another DynamicConfig. // If rhs.def() is not null, then it has to be equal to this->def(). - DynamicConfig& operator=(DynamicConfig &&rhs) + DynamicConfig& operator=(DynamicConfig &&rhs) noexcept { assert(this->def() == nullptr || this->def() == rhs.def()); this->clear(); diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index 4ee8974f4..7c0dfcce5 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -19,7 +19,7 @@ class ExPolygon public: ExPolygon() {} ExPolygon(const ExPolygon &other) : contour(other.contour), holes(other.holes) {} - ExPolygon(ExPolygon &&other) : contour(std::move(other.contour)), holes(std::move(other.holes)) {} + ExPolygon(ExPolygon &&other) noexcept : contour(std::move(other.contour)), holes(std::move(other.holes)) {} explicit ExPolygon(const Polygon &contour) : contour(contour) {} explicit ExPolygon(Polygon &&contour) : contour(std::move(contour)) {} explicit ExPolygon(const Points &contour) : contour(contour) {} @@ -32,7 +32,7 @@ public: ExPolygon(std::initializer_list contour, std::initializer_list hole) : contour(contour), holes({ hole }) {} ExPolygon& operator=(const ExPolygon &other) { contour = other.contour; holes = other.holes; return *this; } - ExPolygon& operator=(ExPolygon &&other) { contour = std::move(other.contour); holes = std::move(other.holes); return *this; } + ExPolygon& operator=(ExPolygon &&other) noexcept { contour = std::move(other.contour); holes = std::move(other.holes); return *this; } Polygon contour; Polygons holes; diff --git a/src/libslic3r/Extruder.hpp b/src/libslic3r/Extruder.hpp index df92bf84b..066a5f56d 100644 --- a/src/libslic3r/Extruder.hpp +++ b/src/libslic3r/Extruder.hpp @@ -48,9 +48,6 @@ public: double retract_length_toolchange() const; double retract_restart_extra_toolchange() const; - // Constructor for a key object, to be used by the stdlib search functions. - static Extruder key(unsigned int id) { return Extruder(id); } - private: // Private constructor to create a key for a search in std::set. Extruder(unsigned int id) : m_id(id) {} diff --git a/src/libslic3r/ExtrusionEntityCollection.cpp b/src/libslic3r/ExtrusionEntityCollection.cpp index e1a9709d1..147a60d95 100644 --- a/src/libslic3r/ExtrusionEntityCollection.cpp +++ b/src/libslic3r/ExtrusionEntityCollection.cpp @@ -6,6 +6,26 @@ namespace Slic3r { +void filter_by_extrusion_role_in_place(ExtrusionEntitiesPtr &extrusion_entities, ExtrusionRole role) +{ + if (role != erMixed) { + auto first = extrusion_entities.begin(); + auto last = extrusion_entities.end(); + auto result = first; + while (first != last) { + // The caller wants only paths with a specific extrusion role. + auto role2 = (*first)->role(); + if (role != role2) { + // This extrusion entity does not match the role asked. + assert(role2 != erMixed); + *result = *first; + ++ result; + } + ++ first; + } + } +} + ExtrusionEntityCollection::ExtrusionEntityCollection(const ExtrusionPaths &paths) : no_sort(false) { @@ -74,31 +94,16 @@ void ExtrusionEntityCollection::remove(size_t i) this->entities.erase(this->entities.begin() + i); } -ExtrusionEntityCollection ExtrusionEntityCollection::chained_path_from(const Point &start_near, ExtrusionRole role) const +ExtrusionEntityCollection ExtrusionEntityCollection::chained_path_from(const ExtrusionEntitiesPtr& extrusion_entities, const Point &start_near, ExtrusionRole role) { - ExtrusionEntityCollection out; - if (this->no_sort) { - out = *this; - } else { - if (role == erMixed) - out = *this; - else { - for (const ExtrusionEntity *ee : this->entities) { - if (role != erMixed) { - // The caller wants only paths with a specific extrusion role. - auto role2 = ee->role(); - if (role != role2) { - // This extrusion entity does not match the role asked. - assert(role2 != erMixed); - continue; - } - } - out.entities.emplace_back(ee->clone()); - } - } - chain_and_reorder_extrusion_entities(out.entities, &start_near); - } - return out; + // Return a filtered copy of the collection. + ExtrusionEntityCollection out; + out.entities = filter_by_extrusion_role(extrusion_entities, role); + // Clone the extrusion entities. + for (auto &ptr : out.entities) + ptr = ptr->clone(); + chain_and_reorder_extrusion_entities(out.entities, &start_near); + return out; } void ExtrusionEntityCollection::polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const diff --git a/src/libslic3r/ExtrusionEntityCollection.hpp b/src/libslic3r/ExtrusionEntityCollection.hpp index 3084e5741..8aacb4d46 100644 --- a/src/libslic3r/ExtrusionEntityCollection.hpp +++ b/src/libslic3r/ExtrusionEntityCollection.hpp @@ -6,6 +6,21 @@ namespace Slic3r { +// Remove those items from extrusion_entities, that do not match role. +// Do nothing if role is mixed. +// Removed elements are NOT being deleted. +void filter_by_extrusion_role_in_place(ExtrusionEntitiesPtr &extrusion_entities, ExtrusionRole role); + +// Return new vector of ExtrusionEntities* with only those items from input extrusion_entities, that match role. +// Return all extrusion entities if role is mixed. +// Returned extrusion entities are shared with the source vector, they are NOT cloned, they are considered to be owned by extrusion_entities. +inline ExtrusionEntitiesPtr filter_by_extrusion_role(const ExtrusionEntitiesPtr &extrusion_entities, ExtrusionRole role) +{ + ExtrusionEntitiesPtr out { extrusion_entities }; + filter_by_extrusion_role_in_place(out, role); + return out; +} + class ExtrusionEntityCollection : public ExtrusionEntity { public: @@ -65,7 +80,9 @@ public: } void replace(size_t i, const ExtrusionEntity &entity); void remove(size_t i); - ExtrusionEntityCollection chained_path_from(const Point &start_near, ExtrusionRole role = erMixed) const; + static ExtrusionEntityCollection chained_path_from(const ExtrusionEntitiesPtr &extrusion_entities, const Point &start_near, ExtrusionRole role = erMixed); + ExtrusionEntityCollection chained_path_from(const Point &start_near, ExtrusionRole role = erMixed) const + { return this->no_sort ? *this : chained_path_from(this->entities, start_near, role); } void reverse(); const Point& first_point() const { return this->entities.front()->first_point(); } const Point& last_point() const { return this->entities.back()->last_point(); } @@ -105,6 +122,6 @@ public: } }; -} +} // namespace Slic3r #endif diff --git a/src/libslic3r/Flow.cpp b/src/libslic3r/Flow.cpp index 6069677a1..2c5c64fe7 100644 --- a/src/libslic3r/Flow.cpp +++ b/src/libslic3r/Flow.cpp @@ -107,9 +107,9 @@ double Flow::mm3_per_mm() const { float res = this->bridge ? // Area of a circle with dmr of this->width. - (this->width * this->width) * 0.25 * PI : + float((this->width * this->width) * 0.25 * PI) : // Rectangle with semicircles at the ends. ~ h (w - 0.215 h) - this->height * (this->width - this->height * (1. - 0.25 * PI)); + float(this->height * (this->width - this->height * (1. - 0.25 * PI))); //assert(res > 0.); if (res <= 0.) throw std::runtime_error("Flow::mm3_per_mm() produced negative flow. Did you set some extrusion width too small?"); diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index b3ccc44b9..de7b26f04 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -1187,7 +1187,7 @@ namespace Slic3r { return; pt::ptree code_tree = main_tree.front().second; - m_model->custom_gcode_per_print_z.clear(); + m_model->custom_gcode_per_print_z.gcodes.clear(); for (const auto& code : code_tree) { @@ -1199,7 +1199,7 @@ namespace Slic3r { int extruder = tree.get (".extruder" ); std::string color = tree.get (".color" ); - m_model->custom_gcode_per_print_z.push_back(Model::CustomGCode{print_z, gcode, extruder, color}) ; + m_model->custom_gcode_per_print_z.gcodes.push_back(Model::CustomGCode{print_z, gcode, extruder, color}) ; } } } @@ -1816,9 +1816,6 @@ namespace Slic3r { break; } } -#if !ENABLE_KEEP_LOADED_VOLUME_TRANSFORM_AS_STAND_ALONE - Transform3d inv_matrix = volume_matrix_to_object.inverse(); -#endif // !ENABLE_KEEP_LOADED_VOLUME_TRANSFORM_AS_STAND_ALONE // splits volume out of imported geometry TriangleMesh triangle_mesh; @@ -1838,15 +1835,7 @@ namespace Slic3r { for (unsigned int v = 0; v < 3; ++v) { unsigned int tri_id = geometry.triangles[src_start_id + ii + v] * 3; -#if ENABLE_KEEP_LOADED_VOLUME_TRANSFORM_AS_STAND_ALONE facet.vertex[v] = Vec3f(geometry.vertices[tri_id + 0], geometry.vertices[tri_id + 1], geometry.vertices[tri_id + 2]); -#else - Vec3f vertex(geometry.vertices[tri_id + 0], geometry.vertices[tri_id + 1], geometry.vertices[tri_id + 2]); - facet.vertex[v] = has_transform ? - // revert the vertices to the original mesh reference system - (inv_matrix * vertex.cast()).cast() : - vertex; -#endif // ENABLE_KEEP_LOADED_VOLUME_TRANSFORM_AS_STAND_ALONE } } @@ -1854,15 +1843,9 @@ namespace Slic3r { triangle_mesh.repair(); ModelVolume* volume = object.add_volume(std::move(triangle_mesh)); -#if ENABLE_KEEP_LOADED_VOLUME_TRANSFORM_AS_STAND_ALONE // stores the volume matrix taken from the metadata, if present if (has_transform) volume->source.transform = Slic3r::Geometry::Transformation(volume_matrix_to_object); -#else - // apply the volume matrix taken from the metadata, if present - if (has_transform) - volume->set_transformation(Slic3r::Geometry::Transformation(volume_matrix_to_object)); -#endif //ENABLE_KEEP_LOADED_VOLUME_TRANSFORM_AS_STAND_ALONE volume->calculate_convex_hull(); // apply the remaining volume's metadata @@ -1975,12 +1958,24 @@ namespace Slic3r { typedef std::vector BuildItemsList; typedef std::map IdToObjectDataMap; +#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF + bool m_fullpath_sources{ true }; +#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF + public: +#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF +#if ENABLE_THUMBNAIL_GENERATOR + bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data = nullptr); +#else + bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources); +#endif // ENABLE_THUMBNAIL_GENERATOR +#else #if ENABLE_THUMBNAIL_GENERATOR bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data = nullptr); #else bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config); #endif // ENABLE_THUMBNAIL_GENERATOR +#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF private: #if ENABLE_THUMBNAIL_GENERATOR @@ -2006,6 +2001,22 @@ namespace Slic3r { bool _add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model); }; +#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF +#if ENABLE_THUMBNAIL_GENERATOR + bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data) + { + clear_errors(); + m_fullpath_sources = fullpath_sources; + return _save_model_to_file(filename, model, config, thumbnail_data); + } +#else + bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources) + { + clear_errors(); + return _save_model_to_file(filename, model, config); + } +#endif // ENABLE_THUMBNAIL_GENERATOR +#else #if ENABLE_THUMBNAIL_GENERATOR bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data) { @@ -2019,6 +2030,7 @@ namespace Slic3r { return _save_model_to_file(filename, model, config); } #endif // ENABLE_THUMBNAIL_GENERATOR +#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF #if ENABLE_THUMBNAIL_GENERATOR bool _3MF_Exporter::_save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data) @@ -2690,11 +2702,7 @@ namespace Slic3r { // stores volume's local matrix stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << MATRIX_KEY << "\" " << VALUE_ATTR << "=\""; -#if ENABLE_KEEP_LOADED_VOLUME_TRANSFORM_AS_STAND_ALONE Transform3d matrix = volume->get_matrix() * volume->source.transform.get_matrix(); -#else - const Transform3d& matrix = volume->get_matrix(); -#endif // ENABLE_KEEP_LOADED_VOLUME_TRANSFORM_AS_STAND_ALONE for (int r = 0; r < 4; ++r) { for (int c = 0; c < 4; ++c) @@ -2709,7 +2717,12 @@ namespace Slic3r { // stores volume's source data if (!volume->source.input_file.empty()) { +#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF + std::string input_file = xml_escape(m_fullpath_sources ? volume->source.input_file : boost::filesystem::path(volume->source.input_file).filename().string()); + stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_FILE_KEY << "\" " << VALUE_ATTR << "=\"" << input_file << "\"/>\n"; +#else stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_FILE_KEY << "\" " << VALUE_ATTR << "=\"" << xml_escape(volume->source.input_file) << "\"/>\n"; +#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_OBJECT_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.object_idx << "\"/>\n"; stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_VOLUME_ID_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.volume_idx << "\"/>\n"; stream << " <" << METADATA_TAG << " " << TYPE_ATTR << "=\"" << VOLUME_TYPE << "\" " << KEY_ATTR << "=\"" << SOURCE_OFFSET_X_KEY << "\" " << VALUE_ATTR << "=\"" << volume->source.mesh_offset(0) << "\"/>\n"; @@ -2749,12 +2762,12 @@ bool _3MF_Exporter::_add_custom_gcode_per_print_z_file_to_archive( mz_zip_archiv { std::string out = ""; - if (!model.custom_gcode_per_print_z.empty()) + if (!model.custom_gcode_per_print_z.gcodes.empty()) { pt::ptree tree; pt::ptree& main_tree = tree.add("custom_gcodes_per_print_z", ""); - for (const Model::CustomGCode& code : model.custom_gcode_per_print_z) + for (const Model::CustomGCode& code : model.custom_gcode_per_print_z.gcodes) { pt::ptree& code_tree = main_tree.add("code", ""); // store minX and maxZ @@ -2798,21 +2811,37 @@ bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model, bool c return res; } +#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF +#if ENABLE_THUMBNAIL_GENERATOR +bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data) +#else +bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources) +#endif // ENABLE_THUMBNAIL_GENERATOR +#else #if ENABLE_THUMBNAIL_GENERATOR bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data) #else bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config) #endif // ENABLE_THUMBNAIL_GENERATOR +#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF { if ((path == nullptr) || (model == nullptr)) return false; _3MF_Exporter exporter; +#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF +#if ENABLE_THUMBNAIL_GENERATOR + bool res = exporter.save_model_to_file(path, *model, config, fullpath_sources, thumbnail_data); +#else + bool res = exporter.save_model_to_file(path, *model, config, fullpath_sources); +#endif // ENABLE_THUMBNAIL_GENERATOR +#else #if ENABLE_THUMBNAIL_GENERATOR bool res = exporter.save_model_to_file(path, *model, config, thumbnail_data); #else bool res = exporter.save_model_to_file(path, *model, config); #endif // ENABLE_THUMBNAIL_GENERATOR +#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF if (!res) exporter.log_errors(); diff --git a/src/libslic3r/Format/3mf.hpp b/src/libslic3r/Format/3mf.hpp index 7da0ce7fc..79ac61a23 100644 --- a/src/libslic3r/Format/3mf.hpp +++ b/src/libslic3r/Format/3mf.hpp @@ -35,11 +35,19 @@ namespace Slic3r { // Save the given model and the config data contained in the given Print into a 3mf file. // The model could be modified during the export process if meshes are not repaired or have no shared vertices +#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF +#if ENABLE_THUMBNAIL_GENERATOR + extern bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data = nullptr); +#else + extern bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources); +#endif // ENABLE_THUMBNAIL_GENERATOR +#else #if ENABLE_THUMBNAIL_GENERATOR extern bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data = nullptr); #else extern bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config); #endif // ENABLE_THUMBNAIL_GENERATOR +#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF }; // namespace Slic3r diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index 5b65d8fa2..7041956ba 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -585,36 +585,20 @@ void AMFParserContext::endElement(const char * /* name */) stl_allocate(&stl); bool has_transform = ! m_volume_transform.isApprox(Transform3d::Identity(), 1e-10); -#if !ENABLE_KEEP_LOADED_VOLUME_TRANSFORM_AS_STAND_ALONE - Transform3d inv_matrix = m_volume_transform.inverse(); -#endif // !ENABLE_KEEP_LOADED_VOLUME_TRANSFORM_AS_STAND_ALONE for (size_t i = 0; i < m_volume_facets.size();) { stl_facet &facet = stl.facet_start[i/3]; for (unsigned int v = 0; v < 3; ++v) { unsigned int tri_id = m_volume_facets[i++] * 3; -#if ENABLE_KEEP_LOADED_VOLUME_TRANSFORM_AS_STAND_ALONE facet.vertex[v] = Vec3f(m_object_vertices[tri_id + 0], m_object_vertices[tri_id + 1], m_object_vertices[tri_id + 2]); -#else - Vec3f vertex(m_object_vertices[tri_id + 0], m_object_vertices[tri_id + 1], m_object_vertices[tri_id + 2]); - facet.vertex[v] = has_transform ? - // revert the vertices to the original mesh reference system - (inv_matrix * vertex.cast()).cast() : - vertex; -#endif // ENABLE_KEEP_LOADED_VOLUME_TRANSFORM_AS_STAND_ALONE } } stl_get_size(&stl); mesh.repair(); m_volume->set_mesh(std::move(mesh)); -#if ENABLE_KEEP_LOADED_VOLUME_TRANSFORM_AS_STAND_ALONE // stores the volume matrix taken from the metadata, if present if (has_transform) m_volume->source.transform = Slic3r::Geometry::Transformation(m_volume_transform); -#else - if (has_transform) - m_volume->set_transformation(m_volume_transform); -#endif // ENABLE_KEEP_LOADED_VOLUME_TRANSFORM_AS_STAND_ALONE if (m_volume->source.input_file.empty() && (m_volume->type() == ModelVolumeType::MODEL_PART)) { m_volume->source.object_idx = (int)m_model.objects.size() - 1; @@ -653,7 +637,7 @@ void AMFParserContext::endElement(const char * /* name */) int extruder = atoi(m_value[2].c_str()); const std::string& color = m_value[3]; - m_model.custom_gcode_per_print_z.push_back(Model::CustomGCode{height, gcode, extruder, color}); + m_model.custom_gcode_per_print_z.gcodes.push_back(Model::CustomGCode{height, gcode, extruder, color}); for (std::string& val: m_value) val.clear(); @@ -1019,7 +1003,11 @@ bool load_amf(const char* path, DynamicPrintConfig* config, Model* model, bool c return false; } +#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF +bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources) +#else bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) +#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF { if ((path == nullptr) || (model == nullptr)) return false; @@ -1159,11 +1147,7 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) stream << " 1\n"; stream << " " << ModelVolume::type_to_string(volume->type()) << "\n"; stream << " "; -#if ENABLE_KEEP_LOADED_VOLUME_TRANSFORM_AS_STAND_ALONE const Transform3d& matrix = volume->get_matrix() * volume->source.transform.get_matrix(); -#else - const Transform3d& matrix = volume->get_matrix(); -#endif // ENABLE_KEEP_LOADED_VOLUME_TRANSFORM_AS_STAND_ALONE stream << std::setprecision(std::numeric_limits::max_digits10); for (int r = 0; r < 4; ++r) { @@ -1177,7 +1161,12 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) stream << "\n"; if (!volume->source.input_file.empty()) { +#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF + std::string input_file = xml_escape(fullpath_sources ? volume->source.input_file : boost::filesystem::path(volume->source.input_file).filename().string()); + stream << " " << input_file << "\n"; +#else stream << " " << xml_escape(volume->source.input_file) << "\n"; +#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF stream << " " << volume->source.object_idx << "\n"; stream << " " << volume->source.volume_idx << "\n"; stream << " " << volume->source.mesh_offset(0) << "\n"; @@ -1241,14 +1230,14 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config) stream << " \n"; } - if (!model->custom_gcode_per_print_z.empty()) + if (!model->custom_gcode_per_print_z.gcodes.empty()) { std::string out = ""; pt::ptree tree; pt::ptree& main_tree = tree.add("custom_gcodes_per_height", ""); - for (const Model::CustomGCode& code : model->custom_gcode_per_print_z) + for (const Model::CustomGCode& code : model->custom_gcode_per_print_z.gcodes) { pt::ptree& code_tree = main_tree.add("code", ""); // store minX and maxZ diff --git a/src/libslic3r/Format/AMF.hpp b/src/libslic3r/Format/AMF.hpp index 1206d60d0..7404e1e90 100644 --- a/src/libslic3r/Format/AMF.hpp +++ b/src/libslic3r/Format/AMF.hpp @@ -11,7 +11,11 @@ extern bool load_amf(const char* path, DynamicPrintConfig* config, Model* model, // Save the given model and the config data into an amf file. // The model could be modified during the export process if meshes are not repaired or have no shared vertices +#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF +extern bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources); +#else extern bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config); +#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF }; // namespace Slic3r diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 40ca7b074..517b5281b 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -34,6 +35,8 @@ #include "miniz_extension.hpp" +using namespace std::literals::string_view_literals; + #if 0 // Enable debugging and asserts, even in the release build. #define DEBUG @@ -792,6 +795,297 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_ PROFILE_OUTPUT(debug_out_path("gcode-export-profile.txt").c_str()); } +// free functions called by GCode::_do_export() +namespace DoExport { + static void init_time_estimators(const PrintConfig &config, GCodeTimeEstimator &normal_time_estimator, GCodeTimeEstimator &silent_time_estimator, bool &silent_time_estimator_enabled) + { + // resets time estimators + normal_time_estimator.reset(); + normal_time_estimator.set_dialect(config.gcode_flavor); + normal_time_estimator.set_extrusion_axis(config.get_extrusion_axis()[0]); + silent_time_estimator_enabled = (config.gcode_flavor == gcfMarlin) && config.silent_mode; + + // Until we have a UI support for the other firmwares than the Marlin, use the hardcoded default values + // and let the user to enter the G-code limits into the start G-code. + // If the following block is enabled for other firmwares than the Marlin, then the function + // this->print_machine_envelope(file, print); + // shall be adjusted as well to produce a G-code block compatible with the particular firmware flavor. + if (config.gcode_flavor.value == gcfMarlin) { + normal_time_estimator.set_max_acceleration((float)config.machine_max_acceleration_extruding.values[0]); + normal_time_estimator.set_retract_acceleration((float)config.machine_max_acceleration_retracting.values[0]); + normal_time_estimator.set_minimum_feedrate((float)config.machine_min_extruding_rate.values[0]); + normal_time_estimator.set_minimum_travel_feedrate((float)config.machine_min_travel_rate.values[0]); + normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, (float)config.machine_max_acceleration_x.values[0]); + normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, (float)config.machine_max_acceleration_y.values[0]); + normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, (float)config.machine_max_acceleration_z.values[0]); + normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, (float)config.machine_max_acceleration_e.values[0]); + normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, (float)config.machine_max_feedrate_x.values[0]); + normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, (float)config.machine_max_feedrate_y.values[0]); + normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, (float)config.machine_max_feedrate_z.values[0]); + normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, (float)config.machine_max_feedrate_e.values[0]); + normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, (float)config.machine_max_jerk_x.values[0]); + normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, (float)config.machine_max_jerk_y.values[0]); + normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, (float)config.machine_max_jerk_z.values[0]); + normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, (float)config.machine_max_jerk_e.values[0]); + + if (silent_time_estimator_enabled) + { + silent_time_estimator.reset(); + silent_time_estimator.set_dialect(config.gcode_flavor); + silent_time_estimator.set_extrusion_axis(config.get_extrusion_axis()[0]); + /* "Stealth mode" values can be just a copy of "normal mode" values + * (when they aren't input for a printer preset). + * Thus, use back value from values, instead of second one, which could be absent + */ + silent_time_estimator.set_max_acceleration((float)config.machine_max_acceleration_extruding.values.back()); + silent_time_estimator.set_retract_acceleration((float)config.machine_max_acceleration_retracting.values.back()); + silent_time_estimator.set_minimum_feedrate((float)config.machine_min_extruding_rate.values.back()); + silent_time_estimator.set_minimum_travel_feedrate((float)config.machine_min_travel_rate.values.back()); + silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, (float)config.machine_max_acceleration_x.values.back()); + silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, (float)config.machine_max_acceleration_y.values.back()); + silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, (float)config.machine_max_acceleration_z.values.back()); + silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, (float)config.machine_max_acceleration_e.values.back()); + silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, (float)config.machine_max_feedrate_x.values.back()); + silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, (float)config.machine_max_feedrate_y.values.back()); + silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, (float)config.machine_max_feedrate_z.values.back()); + silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, (float)config.machine_max_feedrate_e.values.back()); + silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, (float)config.machine_max_jerk_x.values.back()); + silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, (float)config.machine_max_jerk_y.values.back()); + silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, (float)config.machine_max_jerk_z.values.back()); + silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, (float)config.machine_max_jerk_e.values.back()); + if (config.single_extruder_multi_material) { + // As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they + // are considered to be active for the single extruder multi-material printers only. + silent_time_estimator.set_filament_load_times(config.filament_load_time.values); + silent_time_estimator.set_filament_unload_times(config.filament_unload_time.values); + } + } + } + // Filament load / unload times are not specific to a firmware flavor. Let anybody use it if they find it useful. + if (config.single_extruder_multi_material) { + // As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they + // are considered to be active for the single extruder multi-material printers only. + normal_time_estimator.set_filament_load_times(config.filament_load_time.values); + normal_time_estimator.set_filament_unload_times(config.filament_unload_time.values); + } + } + + static void init_gcode_analyzer(const PrintConfig &config, GCodeAnalyzer &analyzer) + { + // resets analyzer + analyzer.reset(); + + // send extruder offset data to analyzer + GCodeAnalyzer::ExtruderOffsetsMap extruder_offsets; + unsigned int num_extruders = static_cast(config.nozzle_diameter.values.size()); + for (unsigned int extruder_id = 0; extruder_id < num_extruders; ++ extruder_id) + { + Vec2d offset = config.extruder_offset.get_at(extruder_id); + if (!offset.isApprox(Vec2d::Zero())) + extruder_offsets[extruder_id] = offset; + } + analyzer.set_extruder_offsets(extruder_offsets); + + // tell analyzer about the extrusion axis + analyzer.set_extrusion_axis(config.get_extrusion_axis()[0]); + + // send extruders count to analyzer to allow it to detect invalid extruder idxs + analyzer.set_extruders_count(num_extruders); + + // tell analyzer about the gcode flavor + analyzer.set_gcode_flavor(config.gcode_flavor); + } + + static double autospeed_volumetric_limit(const Print &print) + { + // get the minimum cross-section used in the print + std::vector mm3_per_mm; + for (auto object : print.objects()) { + for (size_t region_id = 0; region_id < object->region_volumes.size(); ++ region_id) { + const PrintRegion* region = print.regions()[region_id]; + for (auto layer : object->layers()) { + const LayerRegion* layerm = layer->regions()[region_id]; + if (region->config().get_abs_value("perimeter_speed") == 0 || + region->config().get_abs_value("small_perimeter_speed") == 0 || + region->config().get_abs_value("external_perimeter_speed") == 0 || + region->config().get_abs_value("bridge_speed") == 0) + mm3_per_mm.push_back(layerm->perimeters.min_mm3_per_mm()); + if (region->config().get_abs_value("infill_speed") == 0 || + region->config().get_abs_value("solid_infill_speed") == 0 || + region->config().get_abs_value("top_solid_infill_speed") == 0 || + region->config().get_abs_value("bridge_speed") == 0) + mm3_per_mm.push_back(layerm->fills.min_mm3_per_mm()); + } + } + if (object->config().get_abs_value("support_material_speed") == 0 || + object->config().get_abs_value("support_material_interface_speed") == 0) + for (auto layer : object->support_layers()) + mm3_per_mm.push_back(layer->support_fills.min_mm3_per_mm()); + } + // filter out 0-width segments + mm3_per_mm.erase(std::remove_if(mm3_per_mm.begin(), mm3_per_mm.end(), [](double v) { return v < 0.000001; }), mm3_per_mm.end()); + double volumetric_speed = 0.; + if (! mm3_per_mm.empty()) { + // In order to honor max_print_speed we need to find a target volumetric + // speed that we can use throughout the print. So we define this target + // volumetric speed as the volumetric speed produced by printing the + // smallest cross-section at the maximum speed: any larger cross-section + // will need slower feedrates. + volumetric_speed = *std::min_element(mm3_per_mm.begin(), mm3_per_mm.end()) * print.config().max_print_speed.value; + // limit such volumetric speed with max_volumetric_speed if set + if (print.config().max_volumetric_speed.value > 0) + volumetric_speed = std::min(volumetric_speed, print.config().max_volumetric_speed.value); + } + return volumetric_speed; + } + + 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) { + Points skirt_points; + for (const ExtrusionEntity *ee : print.skirt().entities) + for (const ExtrusionPath &path : dynamic_cast(ee)->paths) + append(skirt_points, path.polyline.points); + if (! skirt_points.empty()) { + Polygon outer_skirt = Slic3r::Geometry::convex_hull(skirt_points); + Polygons skirts; + for (unsigned int extruder_id : print.extruders()) { + const Vec2d &extruder_offset = print.config().extruder_offset.get_at(extruder_id); + Polygon s(outer_skirt); + s.translate(Point::new_scale(-extruder_offset(0), -extruder_offset(1))); + skirts.emplace_back(std::move(s)); + } + ooze_prevention.enable = true; + ooze_prevention.standby_points = offset(Slic3r::Geometry::convex_hull(skirts), scale_(3.f)).front().equally_spaced_points(scale_(10.)); + #if 0 + require "Slic3r/SVG.pm"; + Slic3r::SVG::output( + "ooze_prevention.svg", + red_polygons => \@skirts, + polygons => [$outer_skirt], + points => $gcodegen->ooze_prevention->standby_points, + ); + #endif + } + } + } + + #if ENABLE_THUMBNAIL_GENERATOR + template + static void export_thumbnails_to_file(ThumbnailsGeneratorCallback &thumbnail_cb, const std::vector &sizes, WriteToOutput output, ThrowIfCanceledCallback throw_if_canceled) + { + // Write thumbnails using base64 encoding + if (thumbnail_cb != nullptr) + { + const size_t max_row_length = 78; + ThumbnailsList thumbnails; + thumbnail_cb(thumbnails, sizes, true, true, true, true); + for (const ThumbnailData& data : thumbnails) + { + if (data.is_valid()) + { + size_t png_size = 0; + void* png_data = tdefl_write_image_to_png_file_in_memory_ex((const void*)data.pixels.data(), data.width, data.height, 4, &png_size, MZ_DEFAULT_LEVEL, 1); + if (png_data != nullptr) + { + std::string encoded; + encoded.resize(boost::beast::detail::base64::encoded_size(png_size)); + encoded.resize(boost::beast::detail::base64::encode((void*)&encoded[0], (const void*)png_data, png_size)); + + output((boost::format("\n;\n; thumbnail begin %dx%d %d\n") % data.width % data.height % encoded.size()).str().c_str()); + + unsigned int row_count = 0; + while (encoded.size() > max_row_length) + { + output((boost::format("; %s\n") % encoded.substr(0, max_row_length)).str().c_str()); + encoded = encoded.substr(max_row_length); + ++row_count; + } + + if (encoded.size() > 0) + output((boost::format("; %s\n") % encoded).str().c_str()); + + output("; thumbnail end\n;\n"); + + mz_free(png_data); + } + } + throw_if_canceled(); + } + } + } + #endif // ENABLE_THUMBNAIL_GENERATOR + + // Fill in print_statistics and return formatted string containing filament statistics to be inserted into G-code comment section. + static std::string update_print_stats_and_format_filament_stats( + const GCodeTimeEstimator &normal_time_estimator, + const GCodeTimeEstimator &silent_time_estimator, + const bool silent_time_estimator_enabled, + const bool has_wipe_tower, + const WipeTowerData &wipe_tower_data, + const std::vector &extruders, + PrintStatistics &print_statistics) + { + std::string filament_stats_string_out; + + print_statistics.clear(); + print_statistics.estimated_normal_print_time = normal_time_estimator.get_time_dhms(); + print_statistics.estimated_silent_print_time = silent_time_estimator_enabled ? silent_time_estimator.get_time_dhms() : "N/A"; + print_statistics.estimated_normal_color_print_times = normal_time_estimator.get_color_times_dhms(true); + if (silent_time_estimator_enabled) + print_statistics.estimated_silent_color_print_times = silent_time_estimator.get_color_times_dhms(true); + print_statistics.total_toolchanges = std::max(0, wipe_tower_data.number_of_toolchanges); + if (! extruders.empty()) { + std::pair out_filament_used_mm ("; filament used [mm] = ", 0); + std::pair out_filament_used_cm3("; filament used [cm3] = ", 0); + std::pair out_filament_used_g ("; filament used [g] = ", 0); + std::pair out_filament_cost ("; filament cost = ", 0); + for (const Extruder &extruder : extruders) { + double used_filament = extruder.used_filament() + (has_wipe_tower ? wipe_tower_data.used_filament[extruder.id()] : 0.f); + double extruded_volume = extruder.extruded_volume() + (has_wipe_tower ? wipe_tower_data.used_filament[extruder.id()] * 2.4052f : 0.f); // assumes 1.75mm filament diameter + double filament_weight = extruded_volume * extruder.filament_density() * 0.001; + double filament_cost = filament_weight * extruder.filament_cost() * 0.001; + auto append = [&extruder, &extruders](std::pair &dst, const char *tmpl, double value) { + while (dst.second < extruder.id()) { + // Fill in the non-printing extruders with zeros. + dst.first += (dst.second > 0) ? ", 0" : "0"; + ++ dst.second; + } + if (dst.second > 0) + dst.first += ", "; + char buf[64]; + sprintf(buf, tmpl, value); + dst.first += buf; + ++ dst.second; + }; + print_statistics.filament_stats.insert(std::pair{extruder.id(), (float)used_filament}); + append(out_filament_used_mm, "%.1lf", used_filament); + append(out_filament_used_cm3, "%.1lf", extruded_volume * 0.001); + if (filament_weight > 0.) { + print_statistics.total_weight = print_statistics.total_weight + filament_weight; + append(out_filament_used_g, "%.1lf", filament_weight); + if (filament_cost > 0.) { + print_statistics.total_cost = print_statistics.total_cost + filament_cost; + append(out_filament_cost, "%.1lf", filament_cost); + } + } + print_statistics.total_used_filament += used_filament; + print_statistics.total_extruded_volume += extruded_volume; + print_statistics.total_wipe_tower_filament += has_wipe_tower ? used_filament - extruder.used_filament() : 0.; + print_statistics.total_wipe_tower_cost += has_wipe_tower ? (extruded_volume - extruder.extruded_volume())* extruder.filament_density() * 0.001 * extruder.filament_cost() * 0.001 : 0.; + } + filament_stats_string_out += out_filament_used_mm.first; + filament_stats_string_out += out_filament_used_cm3.first; + if (out_filament_used_g.second) + filament_stats_string_out += out_filament_used_g.first; + if (out_filament_cost.second) + filament_stats_string_out += out_filament_cost.first; + } + return filament_stats_string_out; + } +} + #if ENABLE_THUMBNAIL_GENERATOR void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thumbnail_cb) #else @@ -800,99 +1094,10 @@ void GCode::_do_export(Print& print, FILE* file) { PROFILE_FUNC(); - // resets time estimators - m_normal_time_estimator.reset(); - m_normal_time_estimator.set_dialect(print.config().gcode_flavor); - m_normal_time_estimator.set_extrusion_axis(print.config().get_extrusion_axis()[0]); - m_silent_time_estimator_enabled = (print.config().gcode_flavor == gcfMarlin) && print.config().silent_mode; - - // Until we have a UI support for the other firmwares than the Marlin, use the hardcoded default values - // and let the user to enter the G-code limits into the start G-code. - // If the following block is enabled for other firmwares than the Marlin, then the function - // this->print_machine_envelope(file, print); - // shall be adjusted as well to produce a G-code block compatible with the particular firmware flavor. - if (print.config().gcode_flavor.value == gcfMarlin) { - m_normal_time_estimator.set_max_acceleration((float)print.config().machine_max_acceleration_extruding.values[0]); - m_normal_time_estimator.set_retract_acceleration((float)print.config().machine_max_acceleration_retracting.values[0]); - m_normal_time_estimator.set_minimum_feedrate((float)print.config().machine_min_extruding_rate.values[0]); - m_normal_time_estimator.set_minimum_travel_feedrate((float)print.config().machine_min_travel_rate.values[0]); - m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, (float)print.config().machine_max_acceleration_x.values[0]); - m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, (float)print.config().machine_max_acceleration_y.values[0]); - m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, (float)print.config().machine_max_acceleration_z.values[0]); - m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, (float)print.config().machine_max_acceleration_e.values[0]); - m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, (float)print.config().machine_max_feedrate_x.values[0]); - m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, (float)print.config().machine_max_feedrate_y.values[0]); - m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, (float)print.config().machine_max_feedrate_z.values[0]); - m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, (float)print.config().machine_max_feedrate_e.values[0]); - m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, (float)print.config().machine_max_jerk_x.values[0]); - m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, (float)print.config().machine_max_jerk_y.values[0]); - m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, (float)print.config().machine_max_jerk_z.values[0]); - m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, (float)print.config().machine_max_jerk_e.values[0]); - - if (m_silent_time_estimator_enabled) - { - m_silent_time_estimator.reset(); - m_silent_time_estimator.set_dialect(print.config().gcode_flavor); - m_silent_time_estimator.set_extrusion_axis(print.config().get_extrusion_axis()[0]); - /* "Stealth mode" values can be just a copy of "normal mode" values - * (when they aren't input for a printer preset). - * Thus, use back value from values, instead of second one, which could be absent - */ - m_silent_time_estimator.set_max_acceleration((float)print.config().machine_max_acceleration_extruding.values.back()); - m_silent_time_estimator.set_retract_acceleration((float)print.config().machine_max_acceleration_retracting.values.back()); - m_silent_time_estimator.set_minimum_feedrate((float)print.config().machine_min_extruding_rate.values.back()); - m_silent_time_estimator.set_minimum_travel_feedrate((float)print.config().machine_min_travel_rate.values.back()); - m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, (float)print.config().machine_max_acceleration_x.values.back()); - m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, (float)print.config().machine_max_acceleration_y.values.back()); - m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, (float)print.config().machine_max_acceleration_z.values.back()); - m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, (float)print.config().machine_max_acceleration_e.values.back()); - m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, (float)print.config().machine_max_feedrate_x.values.back()); - m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, (float)print.config().machine_max_feedrate_y.values.back()); - m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, (float)print.config().machine_max_feedrate_z.values.back()); - m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, (float)print.config().machine_max_feedrate_e.values.back()); - m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, (float)print.config().machine_max_jerk_x.values.back()); - m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, (float)print.config().machine_max_jerk_y.values.back()); - m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, (float)print.config().machine_max_jerk_z.values.back()); - m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, (float)print.config().machine_max_jerk_e.values.back()); - if (print.config().single_extruder_multi_material) { - // As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they - // are considered to be active for the single extruder multi-material printers only. - m_silent_time_estimator.set_filament_load_times(print.config().filament_load_time.values); - m_silent_time_estimator.set_filament_unload_times(print.config().filament_unload_time.values); - } - } - } - // Filament load / unload times are not specific to a firmware flavor. Let anybody use it if they find it useful. - if (print.config().single_extruder_multi_material) { - // As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they - // are considered to be active for the single extruder multi-material printers only. - m_normal_time_estimator.set_filament_load_times(print.config().filament_load_time.values); - m_normal_time_estimator.set_filament_unload_times(print.config().filament_unload_time.values); - } - - // resets analyzer - m_analyzer.reset(); - - // send extruder offset data to analyzer - GCodeAnalyzer::ExtruderOffsetsMap extruder_offsets; - for (unsigned int extruder_id : print.extruders()) - { - Vec2d offset = print.config().extruder_offset.get_at(extruder_id); - if (!offset.isApprox(Vec2d::Zero())) - extruder_offsets[extruder_id] = offset; - } - m_analyzer.set_extruder_offsets(extruder_offsets); - - // tell analyzer about the extrusion axis - m_analyzer.set_extrusion_axis(print.config().get_extrusion_axis()[0]); - - // send extruders count to analyzer to allow it to detect invalid extruder idxs - const ConfigOptionStrings* extruders_opt = dynamic_cast(print.config().option("extruder_colour")); - const ConfigOptionStrings* filamemts_opt = dynamic_cast(print.config().option("filament_colour")); - m_analyzer.set_extruders_count(std::max((unsigned int)extruders_opt->values.size(), (unsigned int)filamemts_opt->values.size())); - - // tell analyzer about the gcode flavor - m_analyzer.set_gcode_flavor(print.config().gcode_flavor); + DoExport::init_time_estimators(print.config(), + // modifies the following: + m_normal_time_estimator, m_silent_time_estimator, m_silent_time_estimator_enabled); + DoExport::init_gcode_analyzer(print.config(), m_analyzer); // resets analyzer's tracking data m_last_mm3_per_mm = GCodeAnalyzer::Default_mm3_per_mm; @@ -914,8 +1119,7 @@ void GCode::_do_export(Print& print, FILE* file) std::sort(zs.begin(), zs.end()); m_layer_count += (unsigned int)(object->copies().size() * (std::unique(zs.begin(), zs.end()) - zs.begin())); } - } - else { + } else { // Print all objects with the same print_z together. std::vector zs; for (auto object : print.objects()) { @@ -932,53 +1136,8 @@ void GCode::_do_export(Print& print, FILE* file) m_enable_cooling_markers = true; this->apply_print_config(print.config()); - this->set_extruders(print.extruders()); - // Initialize custom gcode - Model* model = print.get_object(0)->model_object()->get_model(); - m_custom_gcode_per_print_z = model->custom_gcode_per_print_z; - - // Initialize autospeed. - { - // get the minimum cross-section used in the print - std::vector mm3_per_mm; - for (auto object : print.objects()) { - for (size_t region_id = 0; region_id < object->region_volumes.size(); ++region_id) { - const PrintRegion* region = print.regions()[region_id]; - for (auto layer : object->layers()) { - const LayerRegion* layerm = layer->regions()[region_id]; - if (region->config().get_abs_value("perimeter_speed") == 0 || - region->config().get_abs_value("small_perimeter_speed") == 0 || - region->config().get_abs_value("external_perimeter_speed") == 0 || - region->config().get_abs_value("bridge_speed") == 0) - mm3_per_mm.push_back(layerm->perimeters.min_mm3_per_mm()); - if (region->config().get_abs_value("infill_speed") == 0 || - region->config().get_abs_value("solid_infill_speed") == 0 || - region->config().get_abs_value("top_solid_infill_speed") == 0 || - region->config().get_abs_value("bridge_speed") == 0) - mm3_per_mm.push_back(layerm->fills.min_mm3_per_mm()); - } - } - if (object->config().get_abs_value("support_material_speed") == 0 || - object->config().get_abs_value("support_material_interface_speed") == 0) - for (auto layer : object->support_layers()) - mm3_per_mm.push_back(layer->support_fills.min_mm3_per_mm()); - } - print.throw_if_canceled(); - // filter out 0-width segments - mm3_per_mm.erase(std::remove_if(mm3_per_mm.begin(), mm3_per_mm.end(), [](double v) { return v < 0.000001; }), mm3_per_mm.end()); - if (!mm3_per_mm.empty()) { - // In order to honor max_print_speed we need to find a target volumetric - // speed that we can use throughout the print. So we define this target - // volumetric speed as the volumetric speed produced by printing the - // smallest cross-section at the maximum speed: any larger cross-section - // will need slower feedrates. - m_volumetric_speed = *std::min_element(mm3_per_mm.begin(), mm3_per_mm.end()) * print.config().max_print_speed.value; - // limit such volumetric speed with max_volumetric_speed if set - if (print.config().max_volumetric_speed.value > 0) - m_volumetric_speed = std::min(m_volumetric_speed, print.config().max_volumetric_speed.value); - } - } + m_volumetric_speed = DoExport::autospeed_volumetric_limit(print); print.throw_if_canceled(); m_cooling_buffer = make_unique(*this); @@ -996,47 +1155,9 @@ void GCode::_do_export(Print& print, FILE* file) // Write information on the generator. _write_format(file, "; %s\n\n", Slic3r::header_slic3r_generated().c_str()); -#if ENABLE_THUMBNAIL_GENERATOR - // Write thumbnails using base64 encoding - if (thumbnail_cb != nullptr) - { - const size_t max_row_length = 78; - ThumbnailsList thumbnails; - thumbnail_cb(thumbnails, print.full_print_config().option("thumbnails")->values, true, true, true, true); - for (const ThumbnailData& data : thumbnails) - { - if (data.is_valid()) - { - size_t png_size = 0; - void* png_data = tdefl_write_image_to_png_file_in_memory_ex((const void*)data.pixels.data(), data.width, data.height, 4, &png_size, MZ_DEFAULT_LEVEL, 1); - if (png_data != nullptr) - { - std::string encoded; - encoded.resize(boost::beast::detail::base64::encoded_size(png_size)); - encoded.resize(boost::beast::detail::base64::encode((void*)&encoded[0], (const void*)png_data, png_size)); - - _write_format(file, "\n;\n; thumbnail begin %dx%d %d\n", data.width, data.height, encoded.size()); - - unsigned int row_count = 0; - while (encoded.size() > max_row_length) - { - _write_format(file, "; %s\n", encoded.substr(0, max_row_length).c_str()); - encoded = encoded.substr(max_row_length); - ++row_count; - } - - if (encoded.size() > 0) - _write_format(file, "; %s\n", encoded.c_str()); - - _write(file, "; thumbnail end\n;\n"); - - mz_free(png_data); - } - } - print.throw_if_canceled(); - } - } -#endif // ENABLE_THUMBNAIL_GENERATOR + 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) { @@ -1079,9 +1200,6 @@ void GCode::_do_export(Print& print, FILE* file) _writeln(file, GCodeTimeEstimator::Silent_First_M73_Output_Placeholder_Tag); } - // Hold total number of print toolchanges. Check for negative toolchanges (single extruder mode) and set to 0 (no tool change). - int total_toolchanges = std::max(0, print.wipe_tower_data().number_of_toolchanges); - // Prepare the helper object for replacing placeholders in custom G-code and output filename. m_placeholder_parser = print.placeholder_parser(); m_placeholder_parser.update_timestamp(); @@ -1101,18 +1219,23 @@ void GCode::_do_export(Print& print, FILE* file) if ((initial_extruder_id = tool_ordering.first_extruder()) != (unsigned int)-1) break; } + // 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()); } else { // 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.wipe_tower_data().tool_ordering.empty() ? - ToolOrdering(print, initial_extruder_id) : - print.wipe_tower_data().tool_ordering; + 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) ? + initial_extruder_id = (has_wipe_tower && ! print.config().single_extruder_multi_material_priming) ? // The priming towers will be skipped. tool_ordering.all_extruders().back() : - // Don't skip the priming towers. + // Don't skip the priming towers. 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()); } if (initial_extruder_id == (unsigned int)-1) { // Nothing to print! @@ -1124,49 +1247,6 @@ void GCode::_do_export(Print& print, FILE* file) } print.throw_if_canceled(); -// #ys_FIXME_no_exported_codes - /* - /* To avoid change filament for non-used extruder for Multi-material, - * check model->custom_gcode_per_print_z using tool_ordering values - * / - if (!m_custom_gcode_per_print_z. empty()) - { - bool delete_executed = false; - auto it = m_custom_gcode_per_print_z.end(); - while (it != m_custom_gcode_per_print_z.begin()) - { - --it; - if (it->gcode != ColorChangeCode) - continue; - - auto it_layer_tools = std::lower_bound(tool_ordering.begin(), tool_ordering.end(), LayerTools(it->print_z)); - - bool used_extruder = false; - for (; it_layer_tools != tool_ordering.end(); it_layer_tools++) - { - const std::vector& extruders = it_layer_tools->extruders; - if (std::find(extruders.begin(), extruders.end(), (unsigned)(it->extruder-1)) != extruders.end()) - { - used_extruder = true; - break; - } - } - if (used_extruder) - continue; - - /* If we are there, current extruder wouldn't be used, - * so this color change is a redundant move. - * Delete this item from m_custom_gcode_per_print_z - * / - it = m_custom_gcode_per_print_z.erase(it); - delete_executed = true; - } - - if (delete_executed) - model->custom_gcode_per_print_z = m_custom_gcode_per_print_z; - } -*/ - m_cooling_buffer->set_current_extruder(initial_extruder_id); // Emit machine envelope limits for the Marlin firmware. @@ -1187,7 +1267,8 @@ void GCode::_do_export(Print& print, FILE* file) // For the start / end G-code to do the priming and final filament pull in case there is no wipe tower provided. m_placeholder_parser.set("has_wipe_tower", has_wipe_tower); m_placeholder_parser.set("has_single_extruder_multi_material_priming", has_wipe_tower && print.config().single_extruder_multi_material_priming); - m_placeholder_parser.set("total_toolchanges", total_toolchanges); + m_placeholder_parser.set("total_toolchanges", std::max(0, print.wipe_tower_data().number_of_toolchanges)); // Check for negative toolchanges (single extruder mode) and set to 0 (no tool change). + std::string start_gcode = this->placeholder_parser_process("start_gcode", print.config().start_gcode.value, initial_extruder_id); // Set bed temperature if the start G-code does not contain any bed temp control G-codes. this->_print_first_layer_bed_temperature(file, print, start_gcode, initial_extruder_id, true); @@ -1195,12 +1276,8 @@ void GCode::_do_export(Print& print, FILE* file) this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, false); if (m_enable_analyzer) - { // adds tag for analyzer - char buf[32]; - sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erCustom); - _writeln(file, buf); - } + _write_format(file, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erCustom); // Write the custom start G-code _writeln(file, start_gcode); @@ -1228,35 +1305,8 @@ void GCode::_do_export(Print& print, FILE* file) } // Calculate wiping points if needed - if (print.config().ooze_prevention.value && ! print.config().single_extruder_multi_material) { - Points skirt_points; - for (const ExtrusionEntity *ee : print.skirt().entities) - for (const ExtrusionPath &path : dynamic_cast(ee)->paths) - append(skirt_points, path.polyline.points); - if (! skirt_points.empty()) { - Polygon outer_skirt = Slic3r::Geometry::convex_hull(skirt_points); - Polygons skirts; - for (unsigned int extruder_id : print.extruders()) { - const Vec2d &extruder_offset = print.config().extruder_offset.get_at(extruder_id); - Polygon s(outer_skirt); - s.translate(Point::new_scale(-extruder_offset(0), -extruder_offset(1))); - skirts.emplace_back(std::move(s)); - } - m_ooze_prevention.enable = true; - m_ooze_prevention.standby_points = - offset(Slic3r::Geometry::convex_hull(skirts), scale_(3.f)).front().equally_spaced_points(scale_(10.)); -#if 0 - require "Slic3r/SVG.pm"; - Slic3r::SVG::output( - "ooze_prevention.svg", - red_polygons => \@skirts, - polygons => [$outer_skirt], - points => $gcodegen->ooze_prevention->standby_points, - ); -#endif - } - print.throw_if_canceled(); - } + DoExport::init_ooze_prevention(print, m_ooze_prevention); + print.throw_if_canceled(); if (! (has_wipe_tower && print.config().single_extruder_multi_material_priming)) { // Set initial extruder only after custom start G-code. @@ -1387,12 +1437,8 @@ void GCode::_do_export(Print& print, FILE* file) _write(file, m_writer.set_fan(false)); if (m_enable_analyzer) - { // adds tag for analyzer - char buf[32]; - sprintf(buf, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erCustom); - _writeln(file, buf); - } + _write_format(file, ";%s%d\n", GCodeAnalyzer::Extrusion_Role_Tag.c_str(), erCustom); // Process filament-specific gcode in extruder order. { @@ -1432,60 +1478,13 @@ void GCode::_do_export(Print& print, FILE* file) m_silent_time_estimator.calculate_time(false); // Get filament stats. - print.m_print_statistics.clear(); - print.m_print_statistics.estimated_normal_print_time = m_normal_time_estimator.get_time_dhms(); - print.m_print_statistics.estimated_silent_print_time = m_silent_time_estimator_enabled ? m_silent_time_estimator.get_time_dhms() : "N/A"; - print.m_print_statistics.estimated_normal_color_print_times = m_normal_time_estimator.get_color_times_dhms(true); - if (m_silent_time_estimator_enabled) - print.m_print_statistics.estimated_silent_color_print_times = m_silent_time_estimator.get_color_times_dhms(true); - print.m_print_statistics.total_toolchanges = total_toolchanges; - std::vector extruders = m_writer.extruders(); - if (! extruders.empty()) { - std::pair out_filament_used_mm ("; filament used [mm] = ", 0); - std::pair out_filament_used_cm3("; filament used [cm3] = ", 0); - std::pair out_filament_used_g ("; filament used [g] = ", 0); - std::pair out_filament_cost ("; filament cost = ", 0); - for (const Extruder &extruder : extruders) { - double used_filament = extruder.used_filament() + (has_wipe_tower ? print.wipe_tower_data().used_filament[extruder.id()] : 0.f); - double extruded_volume = extruder.extruded_volume() + (has_wipe_tower ? print.wipe_tower_data().used_filament[extruder.id()] * 2.4052f : 0.f); // assumes 1.75mm filament diameter - double filament_weight = extruded_volume * extruder.filament_density() * 0.001; - double filament_cost = filament_weight * extruder.filament_cost() * 0.001; - auto append = [&extruder, &extruders](std::pair &dst, const char *tmpl, double value) { - while (dst.second < extruder.id()) { - // Fill in the non-printing extruders with zeros. - dst.first += (dst.second > 0) ? ", 0" : "0"; - ++ dst.second; - } - if (dst.second > 0) - dst.first += ", "; - char buf[64]; - sprintf(buf, tmpl, value); - dst.first += buf; - ++ dst.second; - }; - print.m_print_statistics.filament_stats.insert(std::pair{extruder.id(), (float)used_filament}); - append(out_filament_used_mm, "%.1lf", used_filament); - append(out_filament_used_cm3, "%.1lf", extruded_volume * 0.001); - if (filament_weight > 0.) { - print.m_print_statistics.total_weight = print.m_print_statistics.total_weight + filament_weight; - append(out_filament_used_g, "%.1lf", filament_weight); - if (filament_cost > 0.) { - print.m_print_statistics.total_cost = print.m_print_statistics.total_cost + filament_cost; - append(out_filament_cost, "%.1lf", filament_cost); - } - } - print.m_print_statistics.total_used_filament += used_filament; - print.m_print_statistics.total_extruded_volume += extruded_volume; - print.m_print_statistics.total_wipe_tower_filament += has_wipe_tower ? used_filament - extruder.used_filament() : 0.; - print.m_print_statistics.total_wipe_tower_cost += has_wipe_tower ? (extruded_volume - extruder.extruded_volume())* extruder.filament_density() * 0.001 * extruder.filament_cost() * 0.001 : 0.; - } - _writeln(file, out_filament_used_mm.first); - _writeln(file, out_filament_used_cm3.first); - if (out_filament_used_g.second) - _writeln(file, out_filament_used_g.first); - if (out_filament_cost.second) - _writeln(file, out_filament_cost.first); - } + _write(file, DoExport::update_print_stats_and_format_filament_stats( + // Const inputs + m_normal_time_estimator, m_silent_time_estimator, m_silent_time_estimator_enabled, + has_wipe_tower, print.wipe_tower_data(), + m_writer.extruders(), + // Modifies + print.m_print_statistics)); _write_format(file, "; total filament used [g] = %.1lf\n", print.m_print_statistics.total_weight); _write_format(file, "; total filament cost = %.1lf\n", print.m_print_statistics.total_cost); if (print.m_print_statistics.total_toolchanges > 0) @@ -1729,6 +1728,136 @@ std::vector GCode::sort_print_object_instances( return out; } +namespace ProcessLayer +{ + + static std::string emit_custom_gcode_per_print_z( + const Model::CustomGCode *custom_gcode, + // ID of the first extruder printing this layer. + unsigned int first_extruder_id, + bool single_material_print) + { + std::string gcode; + + if (custom_gcode != nullptr) { + // Extruder switches are processed by LayerTools, they should be filtered out. + assert(custom_gcode->gcode != ToolChangeCode); + + const std::string &custom_code = custom_gcode->gcode; + std::string pause_print_msg; + int m600_extruder_before_layer = -1; + if (custom_code == ColorChangeCode && custom_gcode->extruder > 0) + m600_extruder_before_layer = custom_gcode->extruder - 1; + else if (custom_code == PausePrintCode) + pause_print_msg = custom_gcode->color; + + // we should add or not colorprint_change in respect to nozzle_diameter count instead of really used extruders count + if (custom_code == ColorChangeCode) // color change + { + // add tag for analyzer + gcode += "; " + GCodeAnalyzer::Color_Change_Tag + ",T" + std::to_string(m600_extruder_before_layer) + "\n"; + // add tag for time estimator + gcode += "; " + GCodeTimeEstimator::Color_Change_Tag + "\n"; + + if (!single_material_print && m600_extruder_before_layer >= 0 && first_extruder_id != m600_extruder_before_layer + // && !MMU1 + ) { + //! FIXME_in_fw show message during print pause + gcode += "M601\n"; // pause print + gcode += "M117 Change filament for Extruder " + std::to_string(m600_extruder_before_layer) + "\n"; + } + else + gcode += custom_code + "\n"; + } + else + { + if (custom_code == PausePrintCode) // Pause print + { + // add tag for analyzer + gcode += "; " + GCodeAnalyzer::Pause_Print_Tag + "\n"; + //! FIXME_in_fw show message during print pause + if (!pause_print_msg.empty()) + gcode += "M117 " + pause_print_msg + "\n"; + // add tag for time estimator + //gcode += "; " + GCodeTimeEstimator::Pause_Print_Tag + "\n"; + } + else // custom Gcode + { + // add tag for analyzer + gcode += "; " + GCodeAnalyzer::Custom_Code_Tag + "\n"; + // add tag for time estimator + //gcode += "; " + GCodeTimeEstimator::Custom_Code_Tag + "\n"; + } + gcode += custom_code + "\n"; + } + } + + 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) + { + // 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, + // 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 + // 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_done.emplace_back(layer_tools.print_z); + } + return skirt_loops_per_extruder_out; + } + + static std::map> make_skirt_loops_per_extruder_other_layers( + const Print &print, + const std::vector &layers, + const LayerTools &layer_tools, + // 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 + // not at the print_z of the interlaced support material layers. + std::map> skirt_loops_per_extruder_out; + if (print.has_skirt() && ! print.skirt().entities.empty() && + // Not enough skirt layers printed yet. + //FIXME infinite or high skirt does not make sense for sequential print! + (skirt_done.size() < (size_t)print.config().skirt_height.value || print.has_infinite_skirt()) && + // This print_z has not been extruded yet (sequential print) + skirt_done.back() < layer_tools.print_z - EPSILON && + // and this layer is an object layer, or it is a raft layer. + (layer_tools.has_object || layers.front().support_layer->id() < (size_t)layers.front().support_layer->object()->config().raft_layers.value)) { +#if 0 + // 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 + // https://github.com/prusa3d/PrusaSlicer/issues/469#issuecomment-322450619 + skirt_loops_per_extruder_all_printing(print, layer_tools, skirt_loops_per_extruder_out); +#endif + assert(!skirt_done.empty()); + skirt_done.emplace_back(layer_tools.print_z); + } + return skirt_loops_per_extruder_out; + } + +} // namespace Skirt + // 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. @@ -1765,7 +1894,7 @@ void GCode::process_layer( if (l.support_layer != nullptr && support_layer == nullptr) support_layer = l.support_layer; } - const Layer &layer = (object_layer != nullptr) ? *object_layer : *support_layer; + const Layer &layer = (object_layer != nullptr) ? *object_layer : *support_layer; coordf_t print_z = layer.print_z; bool first_layer = layer.id() == 0; unsigned int first_extruder_id = layer_tools.extruders.front(); @@ -1829,124 +1958,22 @@ void GCode::process_layer( m_second_layer_things_done = true; } - // Let's issue a filament change command if requested at this layer. - // In case there are more toolchange requests that weren't done yet and should happen simultaneously, erase them all. - // (Layers can be close to each other, model could have been resliced with bigger layer height, ...). - bool colorprint_change = false; + // Map from extruder ID to index of skirt loops to be extruded with that extruder. + std::map> skirt_loops_per_extruder; - std::string custom_code = ""; - std::string pause_print_msg = ""; - int m600_before_extruder = -1; - while (!m_custom_gcode_per_print_z.empty() && m_custom_gcode_per_print_z.front().print_z - EPSILON < layer.print_z) { - custom_code = m_custom_gcode_per_print_z.front().gcode; - - if (custom_code == ColorChangeCode && m_custom_gcode_per_print_z.front().extruder > 0) - m600_before_extruder = m_custom_gcode_per_print_z.front().extruder - 1; - if (custom_code == PausePrintCode) - pause_print_msg = m_custom_gcode_per_print_z.front().color; - - m_custom_gcode_per_print_z.erase(m_custom_gcode_per_print_z.begin()); - colorprint_change = true; + if (single_object_instance_idx == size_t(-1)) { + // Normal (non-sequential) print. + gcode += ProcessLayer::emit_custom_gcode_per_print_z(layer_tools.custom_gcode, first_extruder_id, print.config().nozzle_diameter.size() == 1); } - - // we should add or not colorprint_change in respect to nozzle_diameter count instead of really used extruders count - - // don't save "tool_change"(ExtruderChangeCode) code to GCode - if (colorprint_change && custom_code != ExtruderChangeCode) { - const bool single_material_print = print.config().nozzle_diameter.size() == 1; - - if (custom_code == ColorChangeCode) // color change - { - // add tag for analyzer - gcode += "; " + GCodeAnalyzer::Color_Change_Tag + ",T" + std::to_string(m600_before_extruder) + "\n"; - // add tag for time estimator - gcode += "; " + GCodeTimeEstimator::Color_Change_Tag + "\n"; - - if (!single_material_print && m600_before_extruder >= 0 && first_extruder_id != m600_before_extruder - // && !MMU1 - ) { - //! FIXME_in_fw show message during print pause - gcode += "M601\n"; // pause print - gcode += "M117 Change filament for Extruder " + std::to_string(m600_before_extruder) + "\n"; - } - else - gcode += custom_code + "\n"; - } - else - { - if (custom_code == PausePrintCode) // Pause print - { - // add tag for analyzer - gcode += "; " + GCodeAnalyzer::Pause_Print_Tag + "\n"; - //! FIXME_in_fw show message during print pause - if (!pause_print_msg.empty()) - gcode += "M117 " + pause_print_msg + "\n"; - // add tag for time estimator - //gcode += "; " + GCodeTimeEstimator::Pause_Print_Tag + "\n"; - } - else // custom Gcode - { - // add tag for analyzer - gcode += "; " + GCodeAnalyzer::Custom_Code_Tag + "\n"; - // add tag for time estimator - //gcode += "; " + GCodeTimeEstimator::Custom_Code_Tag + "\n"; - } - gcode += custom_code + "\n"; - } - } - - // Extrude skirt at the print_z of the raft layers and normal object layers // not at the print_z of the interlaced support material layers. - bool extrude_skirt = - ! print.skirt().entities.empty() && - // Not enough skirt layers printed yet. - (m_skirt_done.size() < (size_t)print.config().skirt_height.value || print.has_infinite_skirt()) && - // This print_z has not been extruded yet - (m_skirt_done.empty() ? 0. : m_skirt_done.back()) < print_z - EPSILON && - // and this layer is the 1st layer, or it is an object layer, or it is a raft layer. - (first_layer || object_layer != nullptr || support_layer->id() < (size_t)m_config.raft_layers.value); - std::map> skirt_loops_per_extruder; - coordf_t skirt_height = 0.; - if (extrude_skirt) { - // Fill in skirt_loops_per_extruder. - skirt_height = print_z - (m_skirt_done.empty() ? 0. : m_skirt_done.back()); - m_skirt_done.push_back(print_z); - if (first_layer) { - // Prime the extruders over the skirt lines. - std::vector extruder_ids = m_writer.extruder_ids(); - // Reorder the extruders, so that the last used extruder is at the front. - for (size_t i = 1; i < extruder_ids.size(); ++ i) - if (extruder_ids[i] == first_extruder_id) { - // Move the last extruder to the front. - memmove(extruder_ids.data() + 1, extruder_ids.data(), i * sizeof(unsigned int)); - extruder_ids.front() = first_extruder_id; - break; - } - size_t n_loops = print.skirt().entities.size(); - if (n_loops <= extruder_ids.size()) { - for (size_t i = 0; i < n_loops; ++i) - skirt_loops_per_extruder[extruder_ids[i]] = std::pair(i, i + 1); - } else { - // Assign skirt loops to the extruders. - std::vector extruder_loops(extruder_ids.size(), 1); - n_loops -= extruder_loops.size(); - while (n_loops > 0) { - for (size_t i = 0; i < extruder_ids.size() && n_loops > 0; ++ i, -- n_loops) - ++ extruder_loops[i]; - } - for (size_t i = 0; i < extruder_ids.size(); ++ i) - skirt_loops_per_extruder[extruder_ids[i]] = std::make_pair( - (i == 0) ? 0 : extruder_loops[i - 1], - ((i == 0) ? 0 : extruder_loops[i - 1]) + extruder_loops[i]); - } - } else - // Extrude all skirts with the current extruder. - skirt_loops_per_extruder[first_extruder_id] = std::pair(0, print.config().skirts.value); - } + skirt_loops_per_extruder = first_layer ? + Skirt::make_skirt_loops_per_extruder_1st_layer(print, layers, layer_tools, m_skirt_done) : + Skirt::make_skirt_loops_per_extruder_other_layers(print, layers, layer_tools, m_skirt_done); // Group extrusions by an extruder, then by an object, an island and a region. std::map> by_extruder; + bool is_anything_overridden = const_cast(layer_tools).wiping_extrusions().is_anything_overridden(); for (const LayerToPrint &layer_to_print : layers) { if (layer_to_print.support_layer != nullptr) { const SupportLayer &support_layer = *layer_to_print.support_layer; @@ -2014,7 +2041,7 @@ void GCode::process_layer( slices_test_order.reserve(n_slices); for (size_t i = 0; i < n_slices; ++ i) slices_test_order.emplace_back(i); - std::sort(slices_test_order.begin(), slices_test_order.end(), [&layer_surface_bboxes](int i, int j) { + 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(); @@ -2026,58 +2053,71 @@ void GCode::process_layer( layer.lslices[i].contour.contains(point); }; - for (size_t region_id = 0; region_id < print.regions().size(); ++ region_id) { - const LayerRegion *layerm = (region_id < layer.regions().size()) ? layer.regions()[region_id] : nullptr; + for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id) { + const LayerRegion *layerm = layer.regions()[region_id]; if (layerm == nullptr) continue; const PrintRegion ®ion = *print.regions()[region_id]; - // Now we must process perimeters and infills and create islands of extrusions in by_region std::map. // It is also necessary to save which extrusions are part of MM wiping and which are not. // The process is almost the same for perimeters and infills - we will do it in a cycle that repeats twice: - for (std::string entity_type("infills") ; entity_type != "done" ; entity_type = entity_type=="infills" ? "perimeters" : "done") { - - const ExtrusionEntitiesPtr& source_entities = entity_type=="infills" ? layerm->fills.entities : layerm->perimeters.entities; - - for (const ExtrusionEntity *ee : source_entities) { - // fill represents infill extrusions of a single island. - const auto *fill = dynamic_cast(ee); - if (fill->entities.empty()) // This shouldn't happen but first_point() would fail. + std::vector printing_extruders; + for (const ObjectByExtruder::Island::Region::Type entity_type : { ObjectByExtruder::Island::Region::INFILL, ObjectByExtruder::Island::Region::PERIMETERS }) { + for (const ExtrusionEntity *ee : (entity_type == ObjectByExtruder::Island::Region::INFILL) ? layerm->fills.entities : layerm->perimeters.entities) { + // extrusions represents infill or perimeter extrusions of a single island. + assert(dynamic_cast(ee) != nullptr); + const auto *extrusions = static_cast(ee); + if (extrusions->entities.empty()) // This shouldn't happen but first_point() would fail. continue; // This extrusion is part of certain Region, which tells us which extruder should be used for it: - int correct_extruder_id = Print::get_extruder(*fill, region); + int correct_extruder_id = layer_tools.extruder(*extrusions, region); // Let's recover vector of extruder overrides: - const ExtruderPerCopy* entity_overrides = const_cast(layer_tools).wiping_extrusions().get_extruder_overrides(fill, correct_extruder_id, layer_to_print.object()->copies().size()); + const WipingExtrusions::ExtruderPerCopy *entity_overrides = nullptr; + if (is_anything_overridden) { + printing_extruders.clear(); + 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 + // 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(); + } + entity_overrides = const_cast(layer_tools).wiping_extrusions().get_extruder_overrides(extrusions, correct_extruder_id, layer_to_print.object()->copies().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 = { (unsigned int)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 : layer_tools.extruders) + for (unsigned int extruder : printing_extruders) { - // Init by_extruder item only if we actually use the extruder: - if (std::find(entity_overrides->begin(), entity_overrides->end(), extruder) != entity_overrides->end() || // at least one copy is overridden to use this extruder - std::find(entity_overrides->begin(), entity_overrides->end(), -extruder-1) != entity_overrides->end() || // at least one copy would normally be printed with this extruder (see get_extruder_overrides function for explanation) - (std::find(layer_tools.extruders.begin(), layer_tools.extruders.end(), correct_extruder_id) == layer_tools.extruders.end() && extruder == layer_tools.extruders.back())) // 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) - { - std::vector &islands = object_islands_by_extruder( - by_extruder, - extruder, - &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]; - if (// fill->first_point does not fit inside any slice - last || - // fill->first_point fits inside ith slice - point_inside_surface(island_idx, fill->first_point())) { - if (islands[island_idx].by_region.empty()) - islands[island_idx].by_region.assign(print.regions().size(), ObjectByExtruder::Island::Region()); - islands[island_idx].by_region[region_id].append(entity_type, fill, entity_overrides, layer_to_print.object()->copies().size()); - break; - } + std::vector &islands = object_islands_by_extruder( + by_extruder, + extruder, + &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]; + if (// extrusions->first_point does not fit inside any slice + last || + // extrusions->first_point fits inside ith slice + point_inside_surface(island_idx, extrusions->first_point())) { + if (islands[island_idx].by_region.empty()) + islands[island_idx].by_region.assign(print.regions().size(), ObjectByExtruder::Island::Region()); + islands[island_idx].by_region[region_id].append(entity_type, extrusions, entity_overrides); + break; } } } @@ -2099,30 +2139,27 @@ void GCode::process_layer( if (m_enable_analyzer && layer_tools.has_wipe_tower && m_wipe_tower) m_last_analyzer_extrusion_role = erWipeTower; - if (extrude_skirt) { - auto loops_it = skirt_loops_per_extruder.find(extruder_id); - if (loops_it != skirt_loops_per_extruder.end()) { - const std::pair loops = loops_it->second; - this->set_origin(0.,0.); - m_avoid_crossing_perimeters.use_external_mp = true; - Flow skirt_flow = print.skirt_flow(); - for (size_t i = loops.first; i < loops.second; ++ i) { - // Adjust flow according to this layer's layer height. - ExtrusionLoop loop = *dynamic_cast(print.skirt().entities[i]); - Flow layer_skirt_flow(skirt_flow); - layer_skirt_flow.height = (float)skirt_height; - double mm3_per_mm = layer_skirt_flow.mm3_per_mm(); - for (ExtrusionPath &path : loop.paths) { - path.height = (float)layer.height; - path.mm3_per_mm = mm3_per_mm; - } - gcode += this->extrude_loop(loop, "skirt", m_config.support_material_speed.value); + if (auto loops_it = skirt_loops_per_extruder.find(extruder_id); loops_it != skirt_loops_per_extruder.end()) { + const std::pair loops = loops_it->second; + this->set_origin(0., 0.); + m_avoid_crossing_perimeters.use_external_mp = true; + Flow layer_skirt_flow(print.skirt_flow()); + layer_skirt_flow.height = float(m_skirt_done.back() - (m_skirt_done.size() == 1 ? 0. : m_skirt_done[m_skirt_done.size() - 2])); + double mm3_per_mm = layer_skirt_flow.mm3_per_mm(); + for (size_t i = loops.first; i < loops.second; ++i) { + // Adjust flow according to this layer's layer height. + ExtrusionLoop loop = *dynamic_cast(print.skirt().entities[i]); + for (ExtrusionPath &path : loop.paths) { + path.height = layer_skirt_flow.height; + path.mm3_per_mm = mm3_per_mm; } - m_avoid_crossing_perimeters.use_external_mp = false; - // Allow a straight travel move to the first object point if this is the first layer (but don't in next layers). - if (first_layer && loops.first == 0) - m_avoid_crossing_perimeters.disable_once = true; + //FIXME using the support_material_speed of the 1st object printed. + gcode += this->extrude_loop(loop, "skirt", m_config.support_material_speed.value); } + m_avoid_crossing_perimeters.use_external_mp = false; + // Allow a straight travel move to the first object point if this is the first layer (but don't in next layers). + if (first_layer && loops.first == 0) + m_avoid_crossing_perimeters.disable_once = true; } // Extrude brim with the extruder of the 1st region. @@ -2146,7 +2183,7 @@ void GCode::process_layer( 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): - bool is_anything_overridden = const_cast(layer_tools).wiping_extrusions().is_anything_overridden(); + 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"; @@ -2174,8 +2211,8 @@ void GCode::process_layer( m_layer = layers[instance_to_print.layer_id].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(instance_to_print.instance_id, extruder_id, print_wipe_extrusions) : island.by_region; - + 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. if (print.config().infill_first) { gcode += this->extrude_infill(print, by_region_specific); gcode += this->extrude_perimeters(print, by_region_specific, lower_layer_edge_grids[instance_to_print.layer_id]); @@ -2234,8 +2271,20 @@ 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(); + // 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 + }; + 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); + }; for (const std::string &key : cfg.keys()) - if (key != "compatible_prints" && key != "compatible_printers" && ! cfg.option(key)->is_nil()) + if (! is_banned(key) && ! cfg.option(key)->is_nil()) str += "; " + key + " = " + cfg.opt_serialize(key) + "\n"; } @@ -2783,7 +2832,7 @@ std::string GCode::extrude_perimeters(const Print &print, const std::vectorconfig()); - for (ExtrusionEntity *ee : region.perimeters.entities) + for (const ExtrusionEntity *ee : region.perimeters) gcode += this->extrude_entity(*ee, "perimeter", -1., &lower_layer_edge_grid); } return gcode; @@ -2795,8 +2844,10 @@ std::string GCode::extrude_infill(const Print &print, const std::vectorconfig()); - for (ExtrusionEntity *fill : region.infills.chained_path_from(m_last_pos).entities) { - auto *eec = dynamic_cast(fill); + ExtrusionEntitiesPtr extrusions { region.infills }; + 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, "infill"); @@ -3260,59 +3311,96 @@ Point GCode::gcode_to_point(const Vec2d &point) const // Goes through by_region std::vector and returns reference to a subvector of entities, that are to be printed // during infill/perimeter wiping, or normally (depends on wiping_entities parameter) -// Returns a reference to member to avoid copying. -const std::vector& GCode::ObjectByExtruder::Island::by_region_per_copy(unsigned int copy, int extruder, bool wiping_entities) +// Fills in by_region_per_copy_cache and returns its reference. +const std::vector& GCode::ObjectByExtruder::Island::by_region_per_copy(std::vector &by_region_per_copy_cache, unsigned int copy, unsigned int extruder, bool wiping_entities) const { + bool has_overrides = false; + for (const auto& reg : by_region) + if (! reg.infills_overrides.empty() || ! reg.perimeters_overrides.empty()) { + has_overrides = true; + break; + } + if (! has_overrides) + // Simple case. No need to copy the regions. + return 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. + // Filter out the extrusions based on the infill_overrides / perimeter_overrides: + + // Data is cleared, but the memory is not. by_region_per_copy_cache.clear(); for (const auto& reg : by_region) { - by_region_per_copy_cache.push_back(ObjectByExtruder::Island::Region()); // creates a region in the newly created Island + by_region_per_copy_cache.emplace_back(); // creates a region in the newly created Island // Now we are going to iterate through perimeters and infills and pick ones that are supposed to be printed // References are used so that we don't have to repeat the same code for (int iter = 0; iter < 2; ++iter) { - const ExtrusionEntitiesPtr& entities = (iter ? reg.infills.entities : reg.perimeters.entities); - ExtrusionEntityCollection& target_eec = (iter ? by_region_per_copy_cache.back().infills : by_region_per_copy_cache.back().perimeters); - const std::vector& overrides = (iter ? reg.infills_overrides : reg.perimeters_overrides); + const ExtrusionEntitiesPtr& entities = (iter ? reg.infills : reg.perimeters); + ExtrusionEntitiesPtr& target_eec = (iter ? by_region_per_copy_cache.back().infills : by_region_per_copy_cache.back().perimeters); + const std::vector& overrides = (iter ? reg.infills_overrides : reg.perimeters_overrides); // Now the most important thing - which extrusion should we print. // See function ToolOrdering::get_extruder_overrides for details about the negative numbers hack. - int this_extruder_mark = wiping_entities ? extruder : -extruder-1; - - for (unsigned int i=0;iat(copy) == this_extruder_mark) // this copy should be printed with this extruder - target_eec.append((*entities[i])); + 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 < overrides.size(); ++ i) + target_eec.emplace_back(entities[i]); + } } } return by_region_per_copy_cache; } - - // This function takes the eec and appends its entities to either perimeters or infills of this Region (depending on the first parameter) // It also saves pointer to ExtruderPerCopy struct (for each entity), that holds information about which extruders should be used for which copy. -void GCode::ObjectByExtruder::Island::Region::append(const std::string& type, const ExtrusionEntityCollection* eec, const ExtruderPerCopy* copies_extruder, size_t object_copies_num) +void GCode::ObjectByExtruder::Island::Region::append(const Type type, const ExtrusionEntityCollection* eec, const WipingExtrusions::ExtruderPerCopy* copies_extruder) { // We are going to manipulate either perimeters or infills, exactly in the same way. Let's create pointers to the proper structure to not repeat ourselves: - ExtrusionEntityCollection* perimeters_or_infills = &infills; - std::vector* perimeters_or_infills_overrides = &infills_overrides; + ExtrusionEntitiesPtr* perimeters_or_infills; + std::vector* perimeters_or_infills_overrides; - if (type == "perimeters") { - perimeters_or_infills = &perimeters; - perimeters_or_infills_overrides = &perimeters_overrides; + switch (type) { + case PERIMETERS: + perimeters_or_infills = &perimeters; + perimeters_or_infills_overrides = &perimeters_overrides; + break; + case INFILL: + perimeters_or_infills = &infills; + perimeters_or_infills_overrides = &infills_overrides; + break; + default: + throw std::invalid_argument("Unknown parameter!"); } - else - if (type != "infills") { - throw std::invalid_argument("Unknown parameter!"); - return; - } - // First we append the entities, there are eec->entities.size() of them: - perimeters_or_infills->append(eec->entities); + size_t old_size = perimeters_or_infills->size(); + perimeters_or_infills->reserve(perimeters_or_infills->size() + eec->entities.size()); + for (auto* ee : eec->entities) + perimeters_or_infills->emplace_back(ee); - for (unsigned int i=0;ientities.size();++i) - perimeters_or_infills_overrides->push_back(copies_extruder); + if (copies_extruder != nullptr) { + perimeters_or_infills_overrides->reserve(old_size + eec->entities.size()); + perimeters_or_infills_overrides->resize(old_size, nullptr); + for (unsigned int i = 0; i < eec->entities.size(); ++ i) + perimeters_or_infills_overrides->emplace_back(copies_extruder); + } } } // namespace Slic3r diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 497dc8c71..0344924a1 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -197,23 +197,25 @@ public: // append full config to the given string static void append_full_config(const Print& print, std::string& str); -protected: + // Object and support extrusions of the same PrintObject at the same print_z. + // public, so that it could be accessed by free helper functions from GCode.cpp + struct LayerToPrint + { + LayerToPrint() : object_layer(nullptr), support_layer(nullptr) {} + const Layer* object_layer; + const SupportLayer* support_layer; + const Layer* layer() const { return (object_layer != nullptr) ? object_layer : support_layer; } + const PrintObject* object() const { return (this->layer() != nullptr) ? this->layer()->object() : nullptr; } + coordf_t print_z() const { return (object_layer != nullptr && support_layer != nullptr) ? 0.5 * (object_layer->print_z + support_layer->print_z) : this->layer()->print_z; } + }; + +private: #if ENABLE_THUMBNAIL_GENERATOR - void _do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thumbnail_cb); + void _do_export(Print &print, FILE *file, ThumbnailsGeneratorCallback thumbnail_cb); #else void _do_export(Print &print, FILE *file); #endif //ENABLE_THUMBNAIL_GENERATOR - // Object and support extrusions of the same PrintObject at the same print_z. - struct LayerToPrint - { - LayerToPrint() : object_layer(nullptr), support_layer(nullptr) {} - const Layer *object_layer; - const SupportLayer *support_layer; - const Layer* layer() const { return (object_layer != nullptr) ? object_layer : support_layer; } - const PrintObject* object() const { return (this->layer() != nullptr) ? this->layer()->object() : nullptr; } - coordf_t print_z() const { return (object_layer != nullptr && support_layer != nullptr) ? 0.5 * (object_layer->print_z + support_layer->print_z) : this->layer()->print_z; } - }; static std::vector collect_layers_to_print(const PrintObject &object); static std::vector>> collect_layers_to_print(const Print &print); void process_layer( @@ -239,7 +241,6 @@ protected: std::string extrude_multi_path(ExtrusionMultiPath multipath, std::string description = "", double speed = -1.); std::string extrude_path(ExtrusionPath path, std::string description = "", double speed = -1.); - typedef std::vector ExtruderPerCopy; // Extruding multiple objects with soluble / non-soluble / combined supports // on a multi-material printer, trying to minimize tool switches. // Following structures sort extrusions by the extruder ID, by an order of objects and object islands. @@ -253,21 +254,29 @@ protected: struct Island { struct Region { - ExtrusionEntityCollection perimeters; - ExtrusionEntityCollection infills; + // Non-owned references to LayerRegion::perimeters::entities + // std::vector would be better here, but there is no way in C++ to convert from std::vector std::vector without copying. + ExtrusionEntitiesPtr perimeters; + // Non-owned references to LayerRegion::fills::entities + ExtrusionEntitiesPtr infills; - std::vector infills_overrides; - std::vector perimeters_overrides; + std::vector infills_overrides; + std::vector perimeters_overrides; + + enum Type { + PERIMETERS, + INFILL, + }; // Appends perimeter/infill entities and writes don't indices of those that are not to be extruder as part of perimeter/infill wiping - void append(const std::string& type, const ExtrusionEntityCollection* eec, const ExtruderPerCopy* copy_extruders, size_t object_copies_num); + void append(const Type type, const ExtrusionEntityCollection* eec, const WipingExtrusions::ExtruderPerCopy* copy_extruders); }; - std::vector by_region; // all extrusions for this island, grouped by regions - const std::vector& by_region_per_copy(unsigned int copy, int extruder, bool wiping_entities = false); // returns reference to subvector of by_region - private: - std::vector by_region_per_copy_cache; // caches vector generated by function above to avoid copying and recalculating + std::vector by_region; // all extrusions for this island, grouped by regions + + // Fills in by_region_per_copy_cache and returns its reference. + const std::vector& by_region_per_copy(std::vector &by_region_per_copy_cache, unsigned int copy, unsigned int extruder, bool wiping_entities = false) const; }; std::vector islands; }; @@ -277,7 +286,9 @@ protected: InstanceToPrint(ObjectByExtruder &object_by_extruder, size_t layer_id, const PrintObject &print_object, size_t instance_id) : object_by_extruder(object_by_extruder), layer_id(layer_id), print_object(print_object), instance_id(instance_id) {} - ObjectByExtruder &object_by_extruder; + // Repository + ObjectByExtruder &object_by_extruder; + // Index into std::vector, which contains Object and Support layers for the current print_z, collected for a single object, or for possibly multiple objects with multiple instances. const size_t layer_id; const PrintObject &print_object; // Instance idx of the copy of a print object. @@ -285,7 +296,8 @@ protected: }; std::vector sort_print_object_instances( - std::vector &objects_by_extruder, + std::vector &objects_by_extruder, + // Object and Support layers for the current print_z, collected for a single object, or for possibly multiple objects with multiple instances. const std::vector &layers, // Ordering must be defined for normal (non-sequential print). const std::vector> *ordering, @@ -354,7 +366,7 @@ protected: #endif /* HAS_PRESSURE_EQUALIZER */ std::unique_ptr m_wipe_tower; - // Heights at which the skirt has already been extruded. + // Heights (print_z) at which the skirt has already been extruded. std::vector m_skirt_done; // Has the brim been extruded already? Brim is being extruded only for the first object of a multi-object print. bool m_brim_done; @@ -362,11 +374,6 @@ protected: bool m_second_layer_things_done; // Index of a last object copy extruded. std::pair m_last_obj_copy; - // Extensions for colorprint - now it's not a just color_print_heights, - // there can be some custom gcode. - // Updated before the export and erased during the process, - // so no toolchange occurs twice. - std::vector m_custom_gcode_per_print_z; // Time estimators GCodeTimeEstimator m_normal_time_estimator; diff --git a/src/libslic3r/GCode/Analyzer.cpp b/src/libslic3r/GCode/Analyzer.cpp index 7b8004ab0..ec5de71b1 100644 --- a/src/libslic3r/GCode/Analyzer.cpp +++ b/src/libslic3r/GCode/Analyzer.cpp @@ -311,24 +311,22 @@ void GCodeAnalyzer::_processG1(const GCodeReader::GCodeLine& line) { auto axis_absolute_position = [this](GCodeAnalyzer::EAxis axis, const GCodeReader::GCodeLine& lineG1) -> float { - float current_absolute_position = _get_axis_position(axis); - float current_origin = _get_axis_origin(axis); - float lengthsScaleFactor = (_get_units() == GCodeAnalyzer::Inches) ? INCHES_TO_MM : 1.0f; - bool is_relative = (_get_global_positioning_type() == Relative); if (axis == E) is_relative |= (_get_e_local_positioning_type() == Relative); if (lineG1.has(Slic3r::Axis(axis))) { + float lengthsScaleFactor = (_get_units() == GCodeAnalyzer::Inches) ? INCHES_TO_MM : 1.0f; float ret = lineG1.value(Slic3r::Axis(axis)) * lengthsScaleFactor; - return is_relative ? current_absolute_position + ret : ret + current_origin; + return is_relative ? _get_axis_position(axis) + ret : _get_axis_origin(axis) + ret; } else - return current_absolute_position; + return _get_axis_position(axis); }; // updates axes positions from line + float new_pos[Num_Axis]; for (unsigned char a = X; a < Num_Axis; ++a) { @@ -352,7 +350,7 @@ void GCodeAnalyzer::_processG1(const GCodeReader::GCodeLine& line) if (delta_pos[E] < 0.0f) { if ((delta_pos[X] != 0.0f) || (delta_pos[Y] != 0.0f) || (delta_pos[Z] != 0.0f)) - type = GCodeMove::Move; + type = GCodeMove::Move; else type = GCodeMove::Retract; } @@ -440,7 +438,9 @@ void GCodeAnalyzer::_processG92(const GCodeReader::GCodeLine& line) if (line.has_e()) { - _set_axis_origin(E, _get_axis_position(E) - line.e() * lengthsScaleFactor); + // extruder coordinate can grow to the point where its float representation does not allow for proper addition with small increments, + // we set the value taken from the G92 line as the new current position for it + _set_axis_position(E, line.e() * lengthsScaleFactor); anyFound = true; } @@ -956,7 +956,7 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ GCodePreviewData::Extrusion::Path &path = paths.back(); path.polyline = polyline; path.extrusion_role = data.extrusion_role; - path.mm3_per_mm = data.mm3_per_mm; + path.mm3_per_mm = float(data.mm3_per_mm); path.width = data.width; path.height = data.height; path.feedrate = data.feedrate; @@ -978,7 +978,7 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ float volumetric_rate = FLT_MAX; GCodePreviewData::Range height_range; GCodePreviewData::Range width_range; - GCodePreviewData::Range feedrate_range; + GCodePreviewData::MultiRange feedrate_range; GCodePreviewData::Range volumetric_rate_range; GCodePreviewData::Range fan_speed_range; @@ -1013,7 +1013,7 @@ void GCodeAnalyzer::_calc_gcode_preview_extrusion_layers(GCodePreviewData& previ volumetric_rate = move.data.feedrate * (float)move.data.mm3_per_mm; height_range.update_from(move.data.height); width_range.update_from(move.data.width); - feedrate_range.update_from(move.data.feedrate); + feedrate_range.update_from(move.data.feedrate, GCodePreviewData::FeedrateKind::EXTRUSION); volumetric_rate_range.update_from(volumetric_rate); fan_speed_range.update_from(move.data.fan_speed); } @@ -1066,7 +1066,7 @@ void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data, s GCodePreviewData::Range height_range; GCodePreviewData::Range width_range; - GCodePreviewData::Range feedrate_range; + GCodePreviewData::MultiRange feedrate_range; // to avoid to call the callback too often unsigned int cancel_callback_threshold = (unsigned int)std::max((int)travel_moves->second.size() / 25, 1); @@ -1106,7 +1106,7 @@ void GCodeAnalyzer::_calc_gcode_preview_travel(GCodePreviewData& preview_data, s extruder_id = move.data.extruder_id; height_range.update_from(move.data.height); width_range.update_from(move.data.width); - feedrate_range.update_from(move.data.feedrate); + feedrate_range.update_from(move.data.feedrate, GCodePreviewData::FeedrateKind::TRAVEL); } // store last polyline diff --git a/src/libslic3r/GCode/CoolingBuffer.cpp b/src/libslic3r/GCode/CoolingBuffer.cpp index b00bc73eb..6815ea73a 100644 --- a/src/libslic3r/GCode/CoolingBuffer.cpp +++ b/src/libslic3r/GCode/CoolingBuffer.cpp @@ -303,8 +303,8 @@ std::vector CoolingBuffer::parse_layer_gcode(const std:: unsigned int extruder_id = extruders[i].id(); adj.extruder_id = extruder_id; adj.cooling_slow_down_enabled = config.cooling.get_at(extruder_id); - adj.slowdown_below_layer_time = config.slowdown_below_layer_time.get_at(extruder_id); - adj.min_print_speed = config.min_print_speed.get_at(extruder_id); + adj.slowdown_below_layer_time = float(config.slowdown_below_layer_time.get_at(extruder_id)); + adj.min_print_speed = float(config.min_print_speed.get_at(extruder_id)); map_extruder_to_per_extruder_adjustment[extruder_id] = i; } diff --git a/src/libslic3r/GCode/PreviewData.cpp b/src/libslic3r/GCode/PreviewData.cpp index e7475089a..eecabe718 100644 --- a/src/libslic3r/GCode/PreviewData.cpp +++ b/src/libslic3r/GCode/PreviewData.cpp @@ -1,6 +1,5 @@ #include "Analyzer.hpp" #include "PreviewData.hpp" -#include #include #include "Utils.hpp" @@ -11,9 +10,7 @@ namespace Slic3r { -const GCodePreviewData::Color GCodePreviewData::Color::Dummy(0.0f, 0.0f, 0.0f, 0.0f); - -std::vector GCodePreviewData::Color::as_bytes() const +std::vector Color::as_bytes() const { std::vector ret; for (unsigned int i = 0; i < 4; ++i) @@ -38,20 +35,6 @@ GCodePreviewData::Travel::Polyline::Polyline(EType type, EDirection direction, f { } -const GCodePreviewData::Color GCodePreviewData::Range::Default_Colors[Colors_Count] = -{ - Color(0.043f, 0.173f, 0.478f, 1.0f), - Color(0.075f, 0.349f, 0.522f, 1.0f), - Color(0.110f, 0.533f, 0.569f, 1.0f), - Color(0.016f, 0.839f, 0.059f, 1.0f), - Color(0.667f, 0.949f, 0.000f, 1.0f), - Color(0.988f, 0.975f, 0.012f, 1.0f), - Color(0.961f, 0.808f, 0.039f, 1.0f), - Color(0.890f, 0.533f, 0.125f, 1.0f), - Color(0.820f, 0.408f, 0.188f, 1.0f), - Color(0.761f, 0.322f, 0.235f, 1.0f) -}; - GCodePreviewData::Range::Range() { reset(); @@ -59,69 +42,73 @@ GCodePreviewData::Range::Range() void GCodePreviewData::Range::reset() { - min = FLT_MAX; - max = -FLT_MAX; + min_val = FLT_MAX; + max_val = -FLT_MAX; } bool GCodePreviewData::Range::empty() const { - return min == max; + return min_val >= max_val; } void GCodePreviewData::Range::update_from(float value) { - min = std::min(min, value); - max = std::max(max, value); + min_val = std::min(min_val, value); + max_val = std::max(max_val, value); } -void GCodePreviewData::Range::update_from(const Range& other) +void GCodePreviewData::Range::update_from(const RangeBase& other) { - min = std::min(min, other.min); - max = std::max(max, other.max); + min_val = std::min(min_val, other.min()); + max_val = std::max(max_val, other.max()); } -void GCodePreviewData::Range::set_from(const Range& other) +float GCodePreviewData::RangeBase::step_size() const { - min = other.min; - max = other.max; + return (max() - min()) / static_cast(range_rainbow_colors.size() - 1); } -float GCodePreviewData::Range::step_size() const +Color GCodePreviewData::RangeBase::get_color_at(float value) const { - return (max - min) / (float)(Colors_Count - 1); -} + // Input value scaled to the color range + float step = step_size(); + const float global_t = (step != 0.0f) ? std::max(0.0f, value - min()) / step_size() : 0.0f; // lower limit of 0.0f -GCodePreviewData::Color GCodePreviewData::Range::get_color_at(float value) const -{ - if (empty()) - return Color::Dummy; + constexpr std::size_t color_max_idx = range_rainbow_colors.size() - 1; - float global_t = (value - min) / step_size(); + // Compute the two colors just below (low) and above (high) the input value + const std::size_t color_low_idx = std::clamp(static_cast(global_t), std::size_t{ 0 }, color_max_idx); + const std::size_t color_high_idx = std::clamp(color_low_idx + 1, std::size_t{ 0 }, color_max_idx); - unsigned int low = (unsigned int)global_t; - unsigned int high = clamp((unsigned int)0, Colors_Count - 1, low + 1); + // Compute how far the value is between the low and high colors so that they can be interpolated + const float local_t = std::min(global_t - static_cast(color_low_idx), 1.0f); // upper limit of 1.0f - Color color_low = colors[low]; - Color color_high = colors[high]; - - float local_t = global_t - (float)low; - - // interpolate in RGB space + // Interpolate between the low and high colors in RGB space to find exactly which color the input value should get Color ret; for (unsigned int i = 0; i < 4; ++i) { - ret.rgba[i] = lerp(color_low.rgba[i], color_high.rgba[i], local_t); + ret.rgba[i] = lerp(range_rainbow_colors[color_low_idx].rgba[i], range_rainbow_colors[color_high_idx].rgba[i], local_t); } return ret; } -GCodePreviewData::LegendItem::LegendItem(const std::string& text, const GCodePreviewData::Color& color) +float GCodePreviewData::Range::min() const +{ + return min_val; +} + +float GCodePreviewData::Range::max() const +{ + return max_val; +} + +GCodePreviewData::LegendItem::LegendItem(const std::string& text, const Color& color) : text(text) , color(color) { } -const GCodePreviewData::Color GCodePreviewData::Extrusion::Default_Extrusion_Role_Colors[erCount] = +const Color GCodePreviewData::Extrusion::Default_Extrusion_Role_Colors[erCount] = { Color(0.0f, 0.0f, 0.0f, 1.0f), // erNone Color(1.0f, 0.0f, 0.0f, 1.0f), // erPerimeter @@ -180,7 +167,7 @@ size_t GCodePreviewData::Extrusion::memory_used() const const float GCodePreviewData::Travel::Default_Width = 0.075f; const float GCodePreviewData::Travel::Default_Height = 0.075f; -const GCodePreviewData::Color GCodePreviewData::Travel::Default_Type_Colors[Num_Types] = +const Color GCodePreviewData::Travel::Default_Type_Colors[Num_Types] = { Color(0.0f, 0.0f, 0.75f, 1.0f), // Move Color(0.0f, 0.75f, 0.0f, 1.0f), // Extrude @@ -206,7 +193,7 @@ size_t GCodePreviewData::Travel::memory_used() const return out; } -const GCodePreviewData::Color GCodePreviewData::Retraction::Default_Color = GCodePreviewData::Color(1.0f, 1.0f, 1.0f, 1.0f); +const Color GCodePreviewData::Retraction::Default_Color = Color(1.0f, 1.0f, 1.0f, 1.0f); GCodePreviewData::Retraction::Position::Position(const Vec3crd& position, float width, float height) : position(position) @@ -238,17 +225,15 @@ GCodePreviewData::GCodePreviewData() void GCodePreviewData::set_default() { - ::memcpy((void*)ranges.height.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color)); - ::memcpy((void*)ranges.width.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color)); - ::memcpy((void*)ranges.feedrate.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color)); - ::memcpy((void*)ranges.fan_speed.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color)); - ::memcpy((void*)ranges.volumetric_rate.colors, (const void*)Range::Default_Colors, Range::Colors_Count * sizeof(Color)); - extrusion.set_default(); travel.set_default(); retraction.set_default(); unretraction.set_default(); shell.set_default(); + + // Configure the color range for feedrate to match the default for travels and to enable extrusions since they are always visible + ranges.feedrate.set_mode(FeedrateKind::TRAVEL, travel.is_visible); + ranges.feedrate.set_mode(FeedrateKind::EXTRUSION, true); } void GCodePreviewData::reset() @@ -268,32 +253,32 @@ bool GCodePreviewData::empty() const return extrusion.layers.empty() && travel.polylines.empty() && retraction.positions.empty() && unretraction.positions.empty(); } -GCodePreviewData::Color GCodePreviewData::get_extrusion_role_color(ExtrusionRole role) const +Color GCodePreviewData::get_extrusion_role_color(ExtrusionRole role) const { return extrusion.role_colors[role]; } -GCodePreviewData::Color GCodePreviewData::get_height_color(float height) const +Color GCodePreviewData::get_height_color(float height) const { return ranges.height.get_color_at(height); } -GCodePreviewData::Color GCodePreviewData::get_width_color(float width) const +Color GCodePreviewData::get_width_color(float width) const { return ranges.width.get_color_at(width); } -GCodePreviewData::Color GCodePreviewData::get_feedrate_color(float feedrate) const +Color GCodePreviewData::get_feedrate_color(float feedrate) const { return ranges.feedrate.get_color_at(feedrate); } -GCodePreviewData::Color GCodePreviewData::get_fan_speed_color(float fan_speed) const +Color GCodePreviewData::get_fan_speed_color(float fan_speed) const { return ranges.fan_speed.get_color_at(fan_speed); } -GCodePreviewData::Color GCodePreviewData::get_volumetric_rate_color(float rate) const +Color GCodePreviewData::get_volumetric_rate_color(float rate) const { return ranges.volumetric_rate.get_color_at(rate); } @@ -384,16 +369,25 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std:: { struct Helper { - static void FillListFromRange(LegendItemsList& list, const Range& range, unsigned int decimals, float scale_factor) + static void FillListFromRange(LegendItemsList& list, const RangeBase& range, unsigned int decimals, float scale_factor) { - list.reserve(Range::Colors_Count); + list.reserve(range_rainbow_colors.size()); float step = range.step_size(); - for (int i = Range::Colors_Count - 1; i >= 0; --i) + if (step == 0.0f) { char buf[1024]; - sprintf(buf, "%.*f", decimals, scale_factor * (range.min + (float)i * step)); - list.emplace_back(buf, range.colors[i]); + sprintf(buf, "%.*f", decimals, scale_factor * range.min()); + list.emplace_back(buf, range_rainbow_colors[0]); + } + else + { + for (int i = static_cast(range_rainbow_colors.size()) - 1; i >= 0; --i) + { + char buf[1024]; + sprintf(buf, "%.*f", decimals, scale_factor * (range.min() + (float)i * step)); + list.emplace_back(buf, range_rainbow_colors[i]); + } } } }; @@ -446,8 +440,8 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std:: items.reserve(tools_colors_count); for (unsigned int i = 0; i < tools_colors_count; ++i) { - GCodePreviewData::Color color; - ::memcpy((void*)color.rgba, (const void*)(tool_colors.data() + i * 4), 4 * sizeof(float)); + Color color; + ::memcpy((void*)color.rgba.data(), (const void*)(tool_colors.data() + i * 4), 4 * sizeof(float)); items.emplace_back((boost::format(Slic3r::I18N::translate(L("Extruder %d"))) % (i + 1)).str(), color); } @@ -460,7 +454,7 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std:: if (color_print_cnt == 1) // means "Default print color" { Color color; - ::memcpy((void*)color.rgba, (const void*)(tool_colors.data()), 4 * sizeof(float)); + ::memcpy((void*)color.rgba.data(), (const void*)(tool_colors.data()), 4 * sizeof(float)); items.emplace_back(cp_items[0], color); break; @@ -472,7 +466,7 @@ GCodePreviewData::LegendItemsList GCodePreviewData::get_legend_items(const std:: for (int i = 0 ; i < color_print_cnt; ++i) { Color color; - ::memcpy((void*)color.rgba, (const void*)(tool_colors.data() + i * 4), 4 * sizeof(float)); + ::memcpy((void*)color.rgba.data(), (const void*)(tool_colors.data() + i * 4), 4 * sizeof(float)); items.emplace_back(cp_items[i], color); } @@ -502,20 +496,20 @@ const std::vector& GCodePreviewData::ColorPrintColors() return color_print; } -GCodePreviewData::Color operator + (const GCodePreviewData::Color& c1, const GCodePreviewData::Color& c2) +Color operator + (const Color& c1, const Color& c2) { - return GCodePreviewData::Color(clamp(0.0f, 1.0f, c1.rgba[0] + c2.rgba[0]), - clamp(0.0f, 1.0f, c1.rgba[1] + c2.rgba[1]), - clamp(0.0f, 1.0f, c1.rgba[2] + c2.rgba[2]), - clamp(0.0f, 1.0f, c1.rgba[3] + c2.rgba[3])); + return Color(std::clamp(c1.rgba[0] + c2.rgba[0], 0.0f, 1.0f), + std::clamp(c1.rgba[1] + c2.rgba[1], 0.0f, 1.0f), + std::clamp(c1.rgba[2] + c2.rgba[2], 0.0f, 1.0f), + std::clamp(c1.rgba[3] + c2.rgba[3], 0.0f, 1.0f)); } -GCodePreviewData::Color operator * (float f, const GCodePreviewData::Color& color) +Color operator * (float f, const Color& color) { - return GCodePreviewData::Color(clamp(0.0f, 1.0f, f * color.rgba[0]), - clamp(0.0f, 1.0f, f * color.rgba[1]), - clamp(0.0f, 1.0f, f * color.rgba[2]), - clamp(0.0f, 1.0f, f * color.rgba[3])); + return Color(std::clamp(f * color.rgba[0], 0.0f, 1.0f), + std::clamp(f * color.rgba[1], 0.0f, 1.0f), + std::clamp(f * color.rgba[2], 0.0f, 1.0f), + std::clamp(f * color.rgba[3], 0.0f, 1.0f)); } } // namespace Slic3r diff --git a/src/libslic3r/GCode/PreviewData.hpp b/src/libslic3r/GCode/PreviewData.hpp index 725c0258d..35bbfa50a 100644 --- a/src/libslic3r/GCode/PreviewData.hpp +++ b/src/libslic3r/GCode/PreviewData.hpp @@ -5,43 +5,190 @@ #include "../ExtrusionEntity.hpp" #include "../Point.hpp" +#include +#include +#include +#include +#include +#include +#include + +#include + namespace Slic3r { +// Represents an RGBA color +struct Color +{ + std::array rgba; + + Color(const float *argba) + { + memcpy(this->rgba.data(), argba, sizeof(float) * 4); + } + constexpr Color(float r = 1.f, float g = 1.f, float b = 1.f, float a = 1.f) : rgba{r,g,b,a} + { + // Intentionally empty + } + + std::vector as_bytes() const; +}; +Color operator + (const Color& c1, const Color& c2); +Color operator * (float f, const Color& color); + +// Default colors for Ranges +constexpr std::array range_rainbow_colors{ + Color{0.043f, 0.173f, 0.478f, 1.0f}, + Color{0.075f, 0.349f, 0.522f, 1.0f}, + Color{0.110f, 0.533f, 0.569f, 1.0f}, + Color{0.016f, 0.839f, 0.059f, 1.0f}, + Color{0.667f, 0.949f, 0.000f, 1.0f}, + Color{0.988f, 0.975f, 0.012f, 1.0f}, + Color{0.961f, 0.808f, 0.039f, 1.0f}, + Color{0.890f, 0.533f, 0.125f, 1.0f}, + Color{0.820f, 0.408f, 0.188f, 1.0f}, + Color{0.761f, 0.322f, 0.235f, 1.0f}}; + class GCodePreviewData { public: - struct Color + + // Color mapping to convert a float into a smooth rainbow of 10 colors. + class RangeBase { - float rgba[4]; + public: - Color(const float *argba) { memcpy(this->rgba, argba, sizeof(float) * 4); } - Color(float r = 1.f, float g = 1.f, float b = 1.f, float a = 1.f) { rgba[0] = r; rgba[1] = g; rgba[2] = b; rgba[3] = a; } - - std::vector as_bytes() const; - - static const Color Dummy; + virtual void reset() = 0; + virtual bool empty() const = 0; + virtual float min() const = 0; + virtual float max() const = 0; + + // Gets the step size using min(), max() and colors + float step_size() const; + + // Gets the color at a value using colors, min(), and max() + Color get_color_at(float value) const; }; - - // Color mapping from a range into a smooth rainbow of 10 colors. - struct Range + + // Color mapping converting a float in a range between a min and a max into a smooth rainbow of 10 colors. + class Range : public RangeBase { - static const unsigned int Colors_Count = 10; - static const Color Default_Colors[Colors_Count]; - - Color colors[Colors_Count]; - float min; - float max; - + public: Range(); - void reset(); - bool empty() const; + // RangeBase Overrides + void reset() override; + bool empty() const override; + float min() const override; + float max() const override; + + // Range-specific methods void update_from(float value); - void update_from(const Range& other); - void set_from(const Range& other); - float step_size() const; + void update_from(const RangeBase& other); - Color get_color_at(float value) const; + private: + float min_val; + float max_val; + }; + + // Like Range, but stores multiple ranges internally that are used depending on mode. + // Template param EnumRangeType must be an enum with values for each type of range that needs to be tracked in this MultiRange. + // The last enum value should be num_values. The numerical values of all enum values should range from 0 to num_values. + template + class MultiRange : public RangeBase + { + public: + + void reset() override + { + bounds = decltype(bounds){}; + } + + bool empty() const override + { + for (std::size_t i = 0; i < bounds.size(); ++i) + { + if (bounds[i].min != bounds[i].max) + return false; + } + return true; + } + + float min() const override + { + float min = FLT_MAX; + for (std::size_t i = 0; i < bounds.size(); ++i) + { + // Only use bounds[i] if the current mode includes it + if (mode.test(i)) + { + min = std::min(min, bounds[i].min); + } + } + return min; + } + + float max() const override + { + float max = -FLT_MAX; + for (std::size_t i = 0; i < bounds.size(); ++i) + { + // Only use bounds[i] if the current mode includes it + if (mode.test(i)) + { + max = std::max(max, bounds[i].max); + } + } + return max; + } + + void update_from(const float value, EnumRangeType range_type_value) + { + bounds[static_cast(range_type_value)].update_from(value); + } + + void update_from(const MultiRange& other) + { + for (std::size_t i = 0; i < bounds.size(); ++i) + { + bounds[i].update_from(other.bounds[i]); + } + } + + void set_mode(const EnumRangeType range_type_value, const bool enable) + { + mode.set(static_cast(range_type_value), enable); + } + + private: + + // Interval bounds + struct Bounds + { + float min{FLT_MAX}; + float max{-FLT_MAX}; + void update_from(const float value) + { + min = std::min(min, value); + max = std::max(max, value); + } + void update_from(const Bounds other_bounds) + { + min = std::min(min, other_bounds.min); + max = std::max(max, other_bounds.max); + } + }; + + std::array(EnumRangeType::num_values)> bounds; + std::bitset(EnumRangeType::num_values)> mode; + }; + + // Enum distinguishing different kinds of feedrate data + enum class FeedrateKind + { + EXTRUSION = 0, // values must go from 0 up to num_values + TRAVEL, + num_values //must be last in the list of values }; struct Ranges @@ -51,7 +198,7 @@ public: // Color mapping by extrusion width. Range width; // Color mapping by feedrate. - Range feedrate; + MultiRange feedrate; // Color mapping by fan speed. Range fan_speed; // Color mapping by volumetric extrusion rate. @@ -245,9 +392,6 @@ public: static const std::vector& ColorPrintColors(); }; -GCodePreviewData::Color operator + (const GCodePreviewData::Color& c1, const GCodePreviewData::Color& c2); -GCodePreviewData::Color operator * (float f, const GCodePreviewData::Color& color); - } // namespace Slic3r #endif /* slic3r_GCode_PreviewData_hpp_ */ diff --git a/src/libslic3r/GCode/PrintExtents.cpp b/src/libslic3r/GCode/PrintExtents.cpp index d44ef1aad..1fedcf3f0 100644 --- a/src/libslic3r/GCode/PrintExtents.cpp +++ b/src/libslic3r/GCode/PrintExtents.cpp @@ -29,7 +29,7 @@ static inline BoundingBox extrusion_polyline_extents(const Polyline &polyline, c static inline BoundingBoxf extrusionentity_extents(const ExtrusionPath &extrusion_path) { - BoundingBox bbox = extrusion_polyline_extents(extrusion_path.polyline, scale_(0.5 * extrusion_path.width)); + BoundingBox bbox = extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width))); BoundingBoxf bboxf; if (! empty(bbox)) { bboxf.min = unscale(bbox.min); @@ -43,7 +43,7 @@ static inline BoundingBoxf extrusionentity_extents(const ExtrusionLoop &extrusio { BoundingBox bbox; for (const ExtrusionPath &extrusion_path : extrusion_loop.paths) - bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, scale_(0.5 * extrusion_path.width))); + bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width)))); BoundingBoxf bboxf; if (! empty(bbox)) { bboxf.min = unscale(bbox.min); @@ -57,7 +57,7 @@ static inline BoundingBoxf extrusionentity_extents(const ExtrusionMultiPath &ext { BoundingBox bbox; for (const ExtrusionPath &extrusion_path : extrusion_multi_path.paths) - bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, scale_(0.5 * extrusion_path.width))); + bbox.merge(extrusion_polyline_extents(extrusion_path.polyline, coord_t(scale_(0.5 * extrusion_path.width)))); BoundingBoxf bboxf; if (! empty(bbox)) { bboxf.min = unscale(bbox.min); diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index b7d1d57df..3e01e2594 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -13,13 +13,17 @@ #include #include +#include + +#include "../GCodeWriter.hpp" + namespace Slic3r { // Returns true in case that extruder a comes before b (b does not have to be present). False otherwise. bool LayerTools::is_extruder_order(unsigned int a, unsigned int b) const { - if (a==b) + if (a == b) return false; for (auto extruder : extruders) { @@ -32,6 +36,39 @@ bool LayerTools::is_extruder_order(unsigned int a, unsigned int b) const return false; } +// Return a zero based extruder from the region, or extruder_override if overriden. +unsigned int LayerTools::perimeter_extruder(const PrintRegion ®ion) const +{ + assert(region.config().perimeter_extruder.value > 0); + return ((this->extruder_override == 0) ? region.config().perimeter_extruder.value : this->extruder_override) - 1; +} + +unsigned int LayerTools::infill_extruder(const PrintRegion ®ion) const +{ + assert(region.config().infill_extruder.value > 0); + return ((this->extruder_override == 0) ? region.config().infill_extruder.value : this->extruder_override) - 1; +} + +unsigned int LayerTools::solid_infill_extruder(const PrintRegion ®ion) const +{ + assert(region.config().solid_infill_extruder.value > 0); + return ((this->extruder_override == 0) ? region.config().solid_infill_extruder.value : this->extruder_override) - 1; +} + +// Returns a zero based extruder this eec should be printed with, according to PrintRegion config or extruder_override if overriden. +unsigned int LayerTools::extruder(const ExtrusionEntityCollection &extrusions, const PrintRegion ®ion) const +{ + assert(region.config().perimeter_extruder.value > 0); + assert(region.config().infill_extruder.value > 0); + assert(region.config().solid_infill_extruder.value > 0); + // 1 based extruder ID. + unsigned int extruder = ((this->extruder_override == 0) ? + (is_infill(extrusions.role()) ? + (is_solid_infill(extrusions.entities.front()->role()) ? region.config().solid_infill_extruder : region.config().infill_extruder) : + region.config().perimeter_extruder.value) : + this->extruder_override); + return (extruder == 0) ? 0 : extruder - 1; +} // For the use case when each object is printed separately // (print.config().complete_objects is true). @@ -52,7 +89,7 @@ ToolOrdering::ToolOrdering(const PrintObject &object, unsigned int first_extrude } // Collect extruders reuqired to print the layers. - this->collect_extruders(object); + this->collect_extruders(object, std::vector>()); // Reorder the extruders to minimize tool switches. this->reorder_extruders(first_extruder); @@ -89,9 +126,19 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool this->initialize_layers(zs); } + // Use the extruder switches from Model::custom_gcode_per_print_z to override the extruder to print the object. + // Do it only if all the objects were configured to be printed with a single extruder. + std::vector> per_layer_extruder_switches; + if (auto num_extruders = unsigned(print.config().nozzle_diameter.size()); + num_extruders > 1 && print.object_extruders().size() == 1) { + // Printing a single extruder platter on a printer with more than 1 extruder (or single-extruder multi-material). + // There may be custom per-layer tool changes available at the model. + per_layer_extruder_switches = custom_tool_changes(print.model(), num_extruders); + } + // Collect extruders reuqired to print the layers. for (auto object : print.objects()) - this->collect_extruders(*object); + this->collect_extruders(*object, per_layer_extruder_switches); // Reorder the extruders to minimize tool switches. this->reorder_extruders(first_extruder); @@ -111,13 +158,13 @@ void ToolOrdering::initialize_layers(std::vector &zs) coordf_t zmax = zs[i] + EPSILON; for (; j < zs.size() && zs[j] <= zmax; ++ j) ; // Assign an average print_z to the set of layers with nearly equal print_z. - m_layer_tools.emplace_back(LayerTools(0.5 * (zs[i] + zs[j-1]), m_print_config_ptr)); + m_layer_tools.emplace_back(LayerTools(0.5 * (zs[i] + zs[j-1]))); i = j; } } // Collect extruders reuqired to print layers. -void ToolOrdering::collect_extruders(const PrintObject &object) +void ToolOrdering::collect_extruders(const PrintObject &object, const std::vector> &per_layer_extruder_switches) { // Collect the support extruders. for (auto support_layer : object.support_layers()) { @@ -134,9 +181,23 @@ void ToolOrdering::collect_extruders(const PrintObject &object) if (has_support || has_interface) layer_tools.has_support = true; } + + // Extruder overrides are ordered by print_z. + std::vector>::const_iterator it_per_layer_extruder_override; + it_per_layer_extruder_override = per_layer_extruder_switches.begin(); + unsigned int extruder_override = 0; + // Collect the object extruders. for (auto layer : object.layers()) { LayerTools &layer_tools = this->tools_for_layer(layer->print_z); + + // Override extruder with the next + for (; it_per_layer_extruder_override != per_layer_extruder_switches.end() && it_per_layer_extruder_override->first < layer->print_z + EPSILON; ++ it_per_layer_extruder_override) + extruder_override = (int)it_per_layer_extruder_override->second; + + // Store the current extruder override (set to zero if no overriden), so that layer_tools.wiping_extrusions().is_overridable_and_mark() will use it. + layer_tools.extruder_override = extruder_override; + // What extruders are required to print this object layer? for (size_t region_id = 0; region_id < object.region_volumes.size(); ++ region_id) { const LayerRegion *layerm = (region_id < layer->regions().size()) ? layer->regions()[region_id] : nullptr; @@ -150,19 +211,18 @@ void ToolOrdering::collect_extruders(const PrintObject &object) if (m_print_config_ptr) { // in this case complete_objects is false (see ToolOrdering constructors) something_nonoverriddable = false; for (const auto& eec : layerm->perimeters.entities) // let's check if there are nonoverriddable entities - if (!layer_tools.wiping_extrusions().is_overriddable(dynamic_cast(*eec), *m_print_config_ptr, object, region)) { + if (!layer_tools.wiping_extrusions().is_overriddable_and_mark(dynamic_cast(*eec), *m_print_config_ptr, object, region)) { something_nonoverriddable = true; break; } } if (something_nonoverriddable) - layer_tools.extruders.push_back(region.config().perimeter_extruder.value); + layer_tools.extruders.emplace_back((extruder_override == 0) ? region.config().perimeter_extruder.value : extruder_override); layer_tools.has_object = true; } - bool has_infill = false; bool has_solid_infill = false; bool something_nonoverriddable = false; @@ -176,17 +236,19 @@ void ToolOrdering::collect_extruders(const PrintObject &object) has_infill = true; if (m_print_config_ptr) { - if (!something_nonoverriddable && !layer_tools.wiping_extrusions().is_overriddable(*fill, *m_print_config_ptr, object, region)) + if (!something_nonoverriddable && !layer_tools.wiping_extrusions().is_overriddable_and_mark(*fill, *m_print_config_ptr, object, region)) something_nonoverriddable = true; } } - if (something_nonoverriddable || !m_print_config_ptr) - { - if (has_solid_infill) - layer_tools.extruders.push_back(region.config().solid_infill_extruder); - if (has_infill) - layer_tools.extruders.push_back(region.config().infill_extruder); + if (something_nonoverriddable || !m_print_config_ptr) { + if (extruder_override == 0) { + if (has_solid_infill) + layer_tools.extruders.emplace_back(region.config().solid_infill_extruder); + if (has_infill) + layer_tools.extruders.emplace_back(region.config().infill_extruder); + } else if (has_solid_infill || has_infill) + layer_tools.extruders.emplace_back(extruder_override); } if (has_solid_infill || has_infill) layer_tools.has_object = true; @@ -199,7 +261,7 @@ void ToolOrdering::collect_extruders(const PrintObject &object) // make sure that there are some tools for each object layer (e.g. tall wiping object will result in empty extruders vector) if (layer.extruders.empty() && layer.has_object) - layer.extruders.push_back(0); // 0="dontcare" extruder - it will be taken care of in reorder_extruders + layer.extruders.emplace_back(0); // 0="dontcare" extruder - it will be taken care of in reorder_extruders } } @@ -254,11 +316,9 @@ void ToolOrdering::reorder_extruders(unsigned int last_extruder_id) for (unsigned int &extruder_id : lt.extruders) { assert(extruder_id > 0); -- extruder_id; - } + } } - - void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z) { if (m_layer_tools.empty()) @@ -394,17 +454,76 @@ void ToolOrdering::collect_extruder_statistics(bool prime_multi_material) } } +// Assign a pointer to a custom G-code to the respective ToolOrdering::LayerTools. +// Ignore color changes, which are performed on a layer and for such an extruder, that the extruder will not be printing above that layer. +// If multiple events are planned over a span of a single layer, use the last one. +void ToolOrdering::assign_custom_gcodes(const Print &print) +{ + // Only valid for non-sequential print. + assert(! print.config().complete_objects.value); + const Model::CustomGCodeInfo &custom_gcode_per_print_z = print.model().custom_gcode_per_print_z; + if (custom_gcode_per_print_z.gcodes.empty()) + return; + + unsigned int num_extruders = *std::max_element(m_all_printing_extruders.begin(), m_all_printing_extruders.end()) + 1; + std::vector extruder_printing_above(num_extruders, false); + auto custom_gcode_it = custom_gcode_per_print_z.gcodes.rbegin(); + // If printing on a single extruder machine, make the tool changes trigger color change (M600) events. + bool tool_changes_as_color_changes = num_extruders == 1; + // From the last layer to the first one: + for (auto it_lt = m_layer_tools.rbegin(); it_lt != m_layer_tools.rend(); ++ it_lt) { + LayerTools < = *it_lt; + // Add the extruders of the current layer to the set of extruders printing at and above this print_z. + for (unsigned int i : lt.extruders) + extruder_printing_above[i] = true; + // Skip all custom G-codes above this layer and skip all extruder switches. + for (; custom_gcode_it != custom_gcode_per_print_z.gcodes.rend() && (custom_gcode_it->print_z > lt.print_z + EPSILON || custom_gcode_it->gcode == ToolChangeCode); ++ custom_gcode_it); + if (custom_gcode_it == custom_gcode_per_print_z.gcodes.rend()) + // Custom G-codes were processed. + break; + // Some custom G-code is configured for this layer or a layer below. + const Model::CustomGCode &custom_gcode = *custom_gcode_it; + // print_z of the layer below the current layer. + coordf_t print_z_below = 0.; + if (auto it_lt_below = it_lt; ++ it_lt_below != m_layer_tools.rend()) + print_z_below = it_lt_below->print_z; + if (custom_gcode.print_z > print_z_below + 0.5 * EPSILON) { + // The custom G-code applies to the current layer. + if ( tool_changes_as_color_changes || custom_gcode.gcode != ColorChangeCode || + (custom_gcode.extruder <= num_extruders && extruder_printing_above[unsigned(custom_gcode.extruder - 1)])) + // If it is color change, it will actually be useful as the exturder above will print. + lt.custom_gcode = &custom_gcode; + // Consume that custom G-code event. + ++ custom_gcode_it; + } + } +} + +const LayerTools& ToolOrdering::tools_for_layer(coordf_t print_z) const +{ + auto it_layer_tools = std::lower_bound(m_layer_tools.begin(), m_layer_tools.end(), LayerTools(print_z - EPSILON)); + assert(it_layer_tools != m_layer_tools.end()); + coordf_t dist_min = std::abs(it_layer_tools->print_z - print_z); + for (++ it_layer_tools; it_layer_tools != m_layer_tools.end(); ++ it_layer_tools) { + coordf_t d = std::abs(it_layer_tools->print_z - print_z); + if (d >= dist_min) + break; + dist_min = d; + } + -- it_layer_tools; + assert(dist_min < EPSILON); + return *it_layer_tools; +} // This function is called from Print::mark_wiping_extrusions and sets extruder this entity should be printed with (-1 .. as usual) -void WipingExtrusions::set_extruder_override(const ExtrusionEntity* entity, unsigned int copy_id, int extruder, unsigned int num_of_copies) +void WipingExtrusions::set_extruder_override(const ExtrusionEntity* entity, size_t copy_id, int extruder, size_t num_of_copies) { something_overridden = true; - auto entity_map_it = (entity_map.insert(std::make_pair(entity, std::vector()))).first; // (add and) return iterator - auto& copies_vector = entity_map_it->second; - if (copies_vector.size() < num_of_copies) - copies_vector.resize(num_of_copies, -1); + auto entity_map_it = (entity_map.emplace(entity, ExtruderPerCopy())).first; // (add and) return iterator + ExtruderPerCopy& copies_vector = entity_map_it->second; + copies_vector.resize(num_of_copies, -1); if (copies_vector[copy_id] != -1) std::cout << "ERROR: Entity extruder overriden multiple times!!!\n"; // A debugging message - this must never happen. @@ -412,7 +531,6 @@ void WipingExtrusions::set_extruder_override(const ExtrusionEntity* entity, unsi copies_vector[copy_id] = extruder; } - // Finds first non-soluble extruder on the layer int WipingExtrusions::first_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const { @@ -435,11 +553,10 @@ int WipingExtrusions::last_nonsoluble_extruder_on_layer(const PrintConfig& print return (-1); } - // Decides whether this entity could be overridden bool WipingExtrusions::is_overriddable(const ExtrusionEntityCollection& eec, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) const { - if (print_config.filament_soluble.get_at(Print::get_extruder(eec, region))) + if (print_config.filament_soluble.get_at(m_layer_tools->extruder(eec, region))) return false; if (object.config().wipe_into_objects) @@ -451,7 +568,6 @@ bool WipingExtrusions::is_overriddable(const ExtrusionEntityCollection& eec, con return true; } - // Following function iterates through all extrusions on the layer, remembers those that could be used for wiping after toolchange // and returns volume that is left to be wiped on the wipe tower. float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int old_extruder, unsigned int new_extruder, float volume_to_wipe) @@ -459,8 +575,8 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int const LayerTools& lt = *m_layer_tools; const float min_infill_volume = 0.f; // ignore infill with smaller volume than this - if (print.config().filament_soluble.get_at(old_extruder) || print.config().filament_soluble.get_at(new_extruder)) - return volume_to_wipe; // Soluble filament cannot be wiped in a random infill, neither the filament after it + if (! this->something_overridable || volume_to_wipe <= 0. || print.config().filament_soluble.get_at(old_extruder) || print.config().filament_soluble.get_at(new_extruder)) + return std::max(0.f, volume_to_wipe); // Soluble filament cannot be wiped in a random infill, neither the filament after it // we will sort objects so that dedicated for wiping are at the beginning: PrintObjectPtrs object_list = print.objects(); @@ -483,13 +599,13 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int const PrintObject* object = object_list[i]; // Finds this layer: - auto this_layer_it = std::find_if(object->layers().begin(), object->layers().end(), [<](const Layer* lay) { return std::abs(lt.print_z - lay->print_z)layers().end()) - continue; - const Layer* this_layer = *this_layer_it; + const Layer* this_layer = object->get_layer_at_printz(lt.print_z, EPSILON); + if (this_layer == nullptr) + continue; size_t num_of_copies = object->copies().size(); - for (unsigned int copy = 0; copy < num_of_copies; ++copy) { // iterate through copies first, so that we mark neighbouring infills to minimize travel moves + // iterate through copies (aka PrintObject instances) first, so that we mark neighbouring infills to minimize travel moves + for (unsigned int copy = 0; copy < num_of_copies; ++copy) { for (size_t region_id = 0; region_id < object->region_volumes.size(); ++ region_id) { const auto& region = *object->print()->regions()[region_id]; @@ -497,51 +613,48 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int if (!region.config().wipe_into_infill && !object->config().wipe_into_objects) continue; - - if ((!print.config().infill_first ? perimeters_done : !perimeters_done) || (!object->config().wipe_into_objects && region.config().wipe_into_infill)) { + bool wipe_into_infill_only = ! object->config().wipe_into_objects && region.config().wipe_into_infill; + if (print.config().infill_first != perimeters_done || wipe_into_infill_only) { for (const ExtrusionEntity* ee : this_layer->regions()[region_id]->fills.entities) { // iterate through all infill Collections auto* fill = dynamic_cast(ee); if (!is_overriddable(*fill, print.config(), *object, region)) continue; - if (volume_to_wipe<=0) - continue; - - if (!object->config().wipe_into_objects && !print.config().infill_first && region.config().wipe_into_infill) + if (wipe_into_infill_only && ! print.config().infill_first) // In this case we must check that the original extruder is used on this layer before the one we are overridding // (and the perimeters will be finished before the infill is printed): - if (!lt.is_extruder_order(region.config().perimeter_extruder - 1, new_extruder)) + if (!lt.is_extruder_order(lt.perimeter_extruder(region), new_extruder)) continue; if ((!is_entity_overridden(fill, copy) && fill->total_volume() > min_infill_volume)) { // this infill will be used to wipe this extruder set_extruder_override(fill, copy, new_extruder, num_of_copies); - volume_to_wipe -= float(fill->total_volume()); + if ((volume_to_wipe -= float(fill->total_volume())) <= 0.f) + // More material was purged already than asked for. + return 0.f; } } } // Now the same for perimeters - see comments above for explanation: - if (object->config().wipe_into_objects && (print.config().infill_first ? perimeters_done : !perimeters_done)) + if (object->config().wipe_into_objects && print.config().infill_first == perimeters_done) { for (const ExtrusionEntity* ee : this_layer->regions()[region_id]->perimeters.entities) { auto* fill = dynamic_cast(ee); - if (!is_overriddable(*fill, print.config(), *object, region)) - continue; - - if (volume_to_wipe<=0) - continue; - - if ((!is_entity_overridden(fill, copy) && fill->total_volume() > min_infill_volume)) { + if (is_overriddable(*fill, print.config(), *object, region) && !is_entity_overridden(fill, copy) && fill->total_volume() > min_infill_volume) { set_extruder_override(fill, copy, new_extruder, num_of_copies); - volume_to_wipe -= float(fill->total_volume()); + if ((volume_to_wipe -= float(fill->total_volume())) <= 0.f) + // More material was purged already than asked for. + return 0.f; } } } } } } - return std::max(0.f, volume_to_wipe); + // Some purge remains to be done on the Wipe Tower. + assert(volume_to_wipe > 0.); + return volume_to_wipe; } @@ -552,16 +665,18 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int // them again and make sure we override it. void WipingExtrusions::ensure_perimeters_infills_order(const Print& print) { + if (! this->something_overridable) + return; + const LayerTools& lt = *m_layer_tools; unsigned int first_nonsoluble_extruder = first_nonsoluble_extruder_on_layer(print.config()); unsigned int last_nonsoluble_extruder = last_nonsoluble_extruder_on_layer(print.config()); for (const PrintObject* object : print.objects()) { // Finds this layer: - auto this_layer_it = std::find_if(object->layers().begin(), object->layers().end(), [<](const Layer* lay) { return std::abs(lt.print_z - lay->print_z)layers().end()) - continue; - const Layer* this_layer = *this_layer_it; + const Layer* this_layer = object->get_layer_at_printz(lt.print_z, EPSILON); + if (this_layer == nullptr) + continue; size_t num_of_copies = object->copies().size(); for (size_t copy = 0; copy < num_of_copies; ++copy) { // iterate through copies first, so that we mark neighbouring infills to minimize travel moves @@ -584,9 +699,8 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print) // Either way, we will now force-override it with something suitable: if (print.config().infill_first || object->config().wipe_into_objects // in this case the perimeter is overridden, so we can override by the last one safely - || lt.is_extruder_order(region.config().perimeter_extruder - 1, last_nonsoluble_extruder // !infill_first, but perimeter is already printed when last extruder prints - || std::find(lt.extruders.begin(), lt.extruders.end(), region.config().infill_extruder - 1) == lt.extruders.end()) // we have to force override - this could violate infill_first (FIXME) - ) + || lt.is_extruder_order(lt.perimeter_extruder(region), last_nonsoluble_extruder // !infill_first, but perimeter is already printed when last extruder prints + || ! lt.has_extruder(lt.infill_extruder(region)))) // we have to force override - this could violate infill_first (FIXME) set_extruder_override(fill, copy, (print.config().infill_first ? first_nonsoluble_extruder : last_nonsoluble_extruder), num_of_copies); else { // In this case we can (and should) leave it to be printed normally. @@ -597,42 +711,31 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print) // Now the same for perimeters - see comments above for explanation: for (const ExtrusionEntity* ee : this_layer->regions()[region_id]->perimeters.entities) { // iterate through all perimeter Collections auto* fill = dynamic_cast(ee); - if (!is_overriddable(*fill, print.config(), *object, region) - || is_entity_overridden(fill, copy) ) - continue; - - set_extruder_override(fill, copy, (print.config().infill_first ? last_nonsoluble_extruder : first_nonsoluble_extruder), num_of_copies); + if (is_overriddable(*fill, print.config(), *object, region) && ! is_entity_overridden(fill, copy)) + set_extruder_override(fill, copy, (print.config().infill_first ? last_nonsoluble_extruder : first_nonsoluble_extruder), num_of_copies); } } } } } - - - - - - -// Following function is called from process_layer and returns pointer to vector with information about which extruders should be used for given copy of this entity. -// It first makes sure the pointer is valid (creates the vector if it does not exist) and contains a record for each copy -// It also modifies the vector in place and changes all -1 to correct_extruder_id (at the time the overrides were created, correct extruders were not known, -// so -1 was used as "print as usual". -// The resulting vector has to keep track of which extrusions are the ones that were overridden and which were not. In the extruder is used as overridden, -// its number is saved as it is (zero-based index). Usual extrusions are saved as -number-1 (unfortunately there is no negative zero). -const std::vector* WipingExtrusions::get_extruder_overrides(const ExtrusionEntity* entity, int correct_extruder_id, size_t num_of_copies) +// Following function is called from GCode::process_layer and returns pointer to vector with information about which extruders should be used for given copy of this entity. +// If this extrusion does not have any override, nullptr is returned. +// Otherwise it modifies the vector in place and changes all -1 to correct_extruder_id (at the time the overrides were created, correct extruders were not known, +// so -1 was used as "print as usual"). +// The resulting vector therefore keeps track of which extrusions are the ones that were overridden and which were not. If the extruder used is overridden, +// its number is saved as is (zero-based index). Regular extrusions are saved as -number-1 (unfortunately there is no negative zero). +const WipingExtrusions::ExtruderPerCopy* WipingExtrusions::get_extruder_overrides(const ExtrusionEntity* entity, int correct_extruder_id, size_t num_of_copies) { + ExtruderPerCopy *overrides = nullptr; auto entity_map_it = entity_map.find(entity); - if (entity_map_it == entity_map.end()) - entity_map_it = (entity_map.insert(std::make_pair(entity, std::vector()))).first; - - // Now the entity_map_it should be valid, let's make sure the vector is long enough: - entity_map_it->second.resize(num_of_copies, -1); - - // Each -1 now means "print as usual" - we will replace it with actual extruder id (shifted it so we don't lose that information): - std::replace(entity_map_it->second.begin(), entity_map_it->second.end(), -1, -correct_extruder_id-1); - - return &(entity_map_it->second); + if (entity_map_it != entity_map.end()) { + overrides = &entity_map_it->second; + overrides->resize(num_of_copies, -1); + // Each -1 now means "print as usual" - we will replace it with actual extruder id (shifted it so we don't lose that information): + std::replace(overrides->begin(), overrides->end(), -1, -correct_extruder_id-1); + } + return overrides; } diff --git a/src/libslic3r/GCode/ToolOrdering.hpp b/src/libslic3r/GCode/ToolOrdering.hpp index 0d4b83ecb..08dd72656 100644 --- a/src/libslic3r/GCode/ToolOrdering.hpp +++ b/src/libslic3r/GCode/ToolOrdering.hpp @@ -5,6 +5,10 @@ #include "../libslic3r.h" +#include + +#include + namespace Slic3r { class Print; @@ -23,8 +27,19 @@ public: return something_overridden; } + // When allocating extruder overrides of an object's ExtrusionEntity, overrides for maximum 3 copies are allocated in place. + typedef boost::container::small_vector ExtruderPerCopy; + + class ExtruderOverrides + { + public: + ExtruderOverrides(const ExtruderPerCopy *overrides, const int correct_extruder_id) : m_overrides(overrides) {} + private: + const ExtruderPerCopy *m_overrides; + }; + // This is called from GCode::process_layer - see implementation for further comments: - const std::vector* get_extruder_overrides(const ExtrusionEntity* entity, int correct_extruder_id, size_t num_of_copies); + const ExtruderPerCopy* get_extruder_overrides(const ExtrusionEntity* entity, int correct_extruder_id, size_t num_of_copies); // This function goes through all infill entities, decides which ones will be used for wiping and // marks them by the extruder id. Returns volume that remains to be wiped on the wipe tower: @@ -33,6 +48,11 @@ public: void ensure_perimeters_infills_order(const Print& print); bool is_overriddable(const ExtrusionEntityCollection& ee, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) const; + bool is_overriddable_and_mark(const ExtrusionEntityCollection& ee, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) { + bool out = this->is_overriddable(ee, print_config, object, region); + this->something_overridable |= out; + return out; + } void set_layer_tools_ptr(const LayerTools* lt) { m_layer_tools = lt; } @@ -41,14 +61,16 @@ private: int last_nonsoluble_extruder_on_layer(const PrintConfig& print_config) const; // This function is called from mark_wiping_extrusions and sets extruder that it should be printed with (-1 .. as usual) - void set_extruder_override(const ExtrusionEntity* entity, unsigned int copy_id, int extruder, unsigned int num_of_copies); + void set_extruder_override(const ExtrusionEntity* entity, size_t copy_id, int extruder, size_t num_of_copies); // Returns true in case that entity is not printed with its usual extruder for a given copy: bool is_entity_overridden(const ExtrusionEntity* entity, size_t copy_id) const { - return (entity_map.find(entity) == entity_map.end() ? false : entity_map.at(entity).at(copy_id) != -1); + auto it = entity_map.find(entity); + return it == entity_map.end() ? false : it->second[copy_id] != -1; } - std::map> entity_map; // to keep track of who prints what + std::map entity_map; // to keep track of who prints what + bool something_overridable = false; bool something_overridden = false; const LayerTools* m_layer_tools; // so we know which LayerTools object this belongs to }; @@ -58,13 +80,7 @@ private: class LayerTools { public: - LayerTools(const coordf_t z, const PrintConfig* print_config_ptr = nullptr) : - print_z(z), - has_object(false), - has_support(false), - has_wipe_tower(false), - wipe_tower_partitions(0), - wipe_tower_layer_height(0.) {} + LayerTools(const coordf_t z) : print_z(z) {} // Changing these operators to epsilon version can make a problem in cases where support and object layers get close to each other. // In case someone tries to do it, make sure you know what you're doing and test it properly (slice multiple objects at once with supports). @@ -72,20 +88,33 @@ public: bool operator==(const LayerTools &rhs) const { return print_z == rhs.print_z; } bool is_extruder_order(unsigned int a, unsigned int b) const; + bool has_extruder(unsigned int extruder) const { return std::find(this->extruders.begin(), this->extruders.end(), extruder) != this->extruders.end(); } - coordf_t print_z; - bool has_object; - bool has_support; + // Return a zero based extruder from the region, or extruder_override if overriden. + unsigned int perimeter_extruder(const PrintRegion ®ion) const; + unsigned int infill_extruder(const PrintRegion ®ion) const; + unsigned int solid_infill_extruder(const PrintRegion ®ion) const; + // Returns a zero based extruder this eec should be printed with, according to PrintRegion config or extruder_override if overriden. + unsigned int extruder(const ExtrusionEntityCollection &extrusions, const PrintRegion ®ion) const; + + coordf_t print_z = 0.; + bool has_object = false; + bool has_support = false; // Zero based extruder IDs, ordered to minimize tool switches. std::vector extruders; + // If per layer extruder switches are inserted by the G-code preview slider, this value contains the new (1 based) extruder, with which the whole object layer is being printed with. + // If not overriden, it is set to 0. + unsigned int extruder_override = 0; // Will there be anything extruded on this layer for the wipe tower? // Due to the support layers possibly interleaving the object layers, // wipe tower will be disabled for some support only layers. - bool has_wipe_tower; + bool has_wipe_tower = false; // Number of wipe tower partitions to support the required number of tool switches // and to support the wipe tower partitions above this one. - size_t wipe_tower_partitions; - coordf_t wipe_tower_layer_height; + size_t wipe_tower_partitions = 0; + coordf_t wipe_tower_layer_height = 0.; + // Custom G-code (color change, extruder switch, pause) to be performed before this layer starts to print. + const Model::CustomGCode *custom_gcode = nullptr; WipingExtrusions& wiping_extrusions() { m_wiping_extrusions.set_layer_tools_ptr(this); @@ -106,14 +135,20 @@ public: // For the use case when each object is printed separately // (print.config.complete_objects is true). - ToolOrdering(const PrintObject &object, unsigned int first_extruder = (unsigned int)-1, bool prime_multi_material = false); + ToolOrdering(const PrintObject &object, unsigned int first_extruder, bool prime_multi_material = false); // For the use case when all objects are printed at once. // (print.config.complete_objects is false). - ToolOrdering(const Print &print, unsigned int first_extruder = (unsigned int)-1, bool prime_multi_material = false); + ToolOrdering(const Print &print, unsigned int first_extruder, bool prime_multi_material = false); void clear() { m_layer_tools.clear(); } + // Only valid for non-sequential print: + // Assign a pointer to a custom G-code to the respective ToolOrdering::LayerTools. + // Ignore color changes, which are performed on a layer and for such an extruder, that the extruder will not be printing above that layer. + // If multiple events are planned over a span of a single layer, use the last one. + void assign_custom_gcodes(const Print &print); + // Get the first extruder printing, including the extruder priming areas, returns -1 if there is no layer printed. unsigned int first_extruder() const { return m_first_printing_extruder; } @@ -123,25 +158,9 @@ public: // For a multi-material print, the printing extruders are ordered in the order they shall be primed. const std::vector& all_extruders() const { return m_all_printing_extruders; } - template static auto tools_for_layer(Self& self, coordf_t print_z) -> decltype (*self.m_layer_tools.begin()) - { - auto it_layer_tools = std::lower_bound(self.m_layer_tools.begin(), self.m_layer_tools.end(), LayerTools(print_z - EPSILON)); - assert(it_layer_tools != self.m_layer_tools.end()); - coordf_t dist_min = std::abs(it_layer_tools->print_z - print_z); - for (++ it_layer_tools; it_layer_tools != self.m_layer_tools.end(); ++it_layer_tools) { - coordf_t d = std::abs(it_layer_tools->print_z - print_z); - if (d >= dist_min) - break; - dist_min = d; - } - -- it_layer_tools; - assert(dist_min < EPSILON); - return *it_layer_tools; - } - // Find LayerTools with the closest print_z. - LayerTools& tools_for_layer(coordf_t print_z) { return tools_for_layer(*this, print_z); } - const LayerTools& tools_for_layer(coordf_t print_z) const { return tools_for_layer(*this, print_z); } + const LayerTools& tools_for_layer(coordf_t print_z) const; + LayerTools& tools_for_layer(coordf_t print_z) { return const_cast(std::as_const(*this).tools_for_layer(print_z)); } const LayerTools& front() const { return m_layer_tools.front(); } const LayerTools& back() const { return m_layer_tools.back(); } @@ -153,7 +172,7 @@ public: private: void initialize_layers(std::vector &zs); - void collect_extruders(const PrintObject &object); + void collect_extruders(const PrintObject &object, const std::vector> &per_layer_extruder_switches); void reorder_extruders(unsigned int last_extruder_id); void fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z); void collect_extruder_statistics(bool prime_multi_material); @@ -166,7 +185,6 @@ private: // All extruders, which extrude some material over m_layer_tools. std::vector m_all_printing_extruders; - const PrintConfig* m_print_config_ptr = nullptr; }; diff --git a/src/libslic3r/GCodeTimeEstimator.cpp b/src/libslic3r/GCodeTimeEstimator.cpp index c624c0fce..36127cad7 100644 --- a/src/libslic3r/GCodeTimeEstimator.cpp +++ b/src/libslic3r/GCodeTimeEstimator.cpp @@ -1261,7 +1261,9 @@ namespace Slic3r { if (line.has_e()) { - set_axis_origin(E, get_axis_position(E) - line.e() * lengthsScaleFactor); + // extruder coordinate can grow to the point where its float representation does not allow for proper addition with small increments, + // we set the value taken from the G92 line as the new current position for it + set_axis_position(E, line.e() * lengthsScaleFactor); anyFound = true; } else diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp index 364ba12ae..4c53048dc 100644 --- a/src/libslic3r/GCodeWriter.cpp +++ b/src/libslic3r/GCodeWriter.cpp @@ -19,12 +19,13 @@ void GCodeWriter::apply_print_config(const PrintConfig &print_config) this->config.apply(print_config, true); m_extrusion_axis = this->config.get_extrusion_axis(); m_single_extruder_multi_material = print_config.single_extruder_multi_material.value; - m_max_acceleration = (print_config.gcode_flavor.value == gcfMarlin) ? - print_config.machine_max_acceleration_extruding.values.front() : 0; + m_max_acceleration = std::lrint((print_config.gcode_flavor.value == gcfMarlin) ? + print_config.machine_max_acceleration_extruding.values.front() : 0); } -void GCodeWriter::set_extruders(const std::vector &extruder_ids) +void GCodeWriter::set_extruders(std::vector extruder_ids) { + std::sort(extruder_ids.begin(), extruder_ids.end()); m_extruders.clear(); m_extruders.reserve(extruder_ids.size()); for (unsigned int extruder_id : extruder_ids) @@ -247,9 +248,9 @@ std::string GCodeWriter::toolchange_prefix() const std::string GCodeWriter::toolchange(unsigned int extruder_id) { // set the new extruder - auto it_extruder = std::lower_bound(m_extruders.begin(), m_extruders.end(), Extruder::key(extruder_id)); - assert(it_extruder != m_extruders.end()); - m_extruder = const_cast(&*it_extruder); + auto it_extruder = Slic3r::lower_bound_by_predicate(m_extruders.begin(), m_extruders.end(), [extruder_id](const Extruder &e) { return e.id() < extruder_id; }); + assert(it_extruder != m_extruders.end() && it_extruder->id() == extruder_id); + m_extruder = &*it_extruder; // return the toolchange command // if we are running a single-extruder setup, just set the extruder and return nothing diff --git a/src/libslic3r/GCodeWriter.hpp b/src/libslic3r/GCodeWriter.hpp index 98abdda28..abeaf0024 100644 --- a/src/libslic3r/GCodeWriter.hpp +++ b/src/libslic3r/GCodeWriter.hpp @@ -13,7 +13,7 @@ namespace Slic3r { // Additional Codes which can be set by user using DoubleSlider static constexpr char ColorChangeCode[] = "M600"; static constexpr char PausePrintCode[] = "M601"; -static constexpr char ExtruderChangeCode[] = "tool_change"; +static constexpr char ToolChangeCode[] = "tool_change"; class GCodeWriter { public: @@ -33,7 +33,7 @@ public: std::string extrusion_axis() const { return m_extrusion_axis; } void apply_print_config(const PrintConfig &print_config); // Extruders are expected to be sorted in an increasing order. - void set_extruders(const std::vector &extruder_ids); + void set_extruders(std::vector extruder_ids); const std::vector& extruders() const { return m_extruders; } std::vector extruder_ids() const { std::vector out; @@ -74,7 +74,8 @@ public: Vec3d get_position() const { return m_pos; } private: - std::vector m_extruders; + // Extruders are sorted by their ID, so that binary search is possible. + std::vector m_extruders; std::string m_extrusion_axis; bool m_single_extruder_multi_material; Extruder* m_extruder; diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 5f5199efb..0513653a2 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -126,7 +126,7 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c if (add_default_instances) model.add_default_instances(); - update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z, config); + update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z.gcodes, config); return model; } @@ -163,7 +163,7 @@ Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig if (add_default_instances) model.add_default_instances(); - update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z, config); + update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z.gcodes, config); return model; } @@ -598,21 +598,6 @@ std::string Model::propose_export_file_name_and_path(const std::string &new_exte return boost::filesystem::path(this->propose_export_file_name_and_path()).replace_extension(new_extension).string(); } -std::vector> Model::get_custom_tool_changes(double default_layer_height, size_t num_extruders) const -{ - std::vector> custom_tool_changes; - for (const CustomGCode& custom_gcode : custom_gcode_per_print_z) - if (custom_gcode.gcode == ExtruderChangeCode) { - DynamicPrintConfig config; - // If extruder count in PrinterSettings was changed, use default (0) extruder for extruders, more than num_extruders - config.set_key_value("extruder", new ConfigOptionInt(custom_gcode.extruder > num_extruders ? 0 : custom_gcode.extruder)); - // For correct extruders(tools) changing, we should decrease custom_gcode.height value by one default layer height - custom_tool_changes.push_back({ custom_gcode.print_z - default_layer_height, config }); - } - - return custom_tool_changes; -} - ModelObject::~ModelObject() { this->clear_volumes(); @@ -860,7 +845,7 @@ TriangleMesh ModelObject::mesh() const } // 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 platter +// Currently used by ModelObject::mesh(), to calculate the 2D envelope for 2D plater // and to display the object statistics at ModelObject::print_info(). TriangleMesh ModelObject::raw_mesh() const { @@ -1863,6 +1848,19 @@ arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const return ret; } +// Return pairs of sorted by increasing print_z from custom_gcode_per_print_z. +// print_z corresponds to the first layer printed with the new extruder. +std::vector> custom_tool_changes(const Model &model, size_t num_extruders) +{ + std::vector> custom_tool_changes; + for (const Model::CustomGCode &custom_gcode : model.custom_gcode_per_print_z.gcodes) + if (custom_gcode.gcode == ToolChangeCode) { + // If extruder count in PrinterSettings was changed, use default (0) extruder for extruders, more than num_extruders + custom_tool_changes.emplace_back(custom_gcode.print_z, static_cast(custom_gcode.extruder > num_extruders ? 1 : custom_gcode.extruder)); + } + return custom_tool_changes; +} + // Test whether the two models contain the same number of ModelObjects with the same set of IDs // ordered in the same order. In that case it is not necessary to kill the background processing. bool model_object_list_equal(const Model &model_old, const Model &model_new) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 934eea94f..5f07acc6a 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -240,7 +240,7 @@ public: // A mesh containing all transformed instances of this object. TriangleMesh mesh() const; // 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 platter. + // Currently used by ModelObject::mesh() and to calculate the 2D envelope for 2D plater. TriangleMesh raw_mesh() const; // Non-transformed (non-rotated, non-scaled, non-translated) sum of all object volumes. TriangleMesh full_raw_mesh() const; @@ -403,13 +403,9 @@ public: int object_idx{ -1 }; int volume_idx{ -1 }; Vec3d mesh_offset{ Vec3d::Zero() }; -#if ENABLE_KEEP_LOADED_VOLUME_TRANSFORM_AS_STAND_ALONE Geometry::Transformation transform; template void serialize(Archive& ar) { ar(input_file, object_idx, volume_idx, mesh_offset, transform); } -#else - template void serialize(Archive& ar) { ar(input_file, object_idx, volume_idx, mesh_offset); } -#endif // ENABLE_KEEP_LOADED_VOLUME_TRANSFORM_AS_STAND_ALONE }; Source source; @@ -771,12 +767,34 @@ public: double print_z; std::string gcode; - int extruder; // 0 - "gcode" will be applied for whole print - // else - "gcode" will be applied only for "extruder" print + int extruder; // Informative value for ColorChangeCode and ToolChangeCode + // "gcode" == ColorChangeCode => M600 will be applied for "extruder" extruder + // "gcode" == ToolChangeCode => for whole print tool will be switched to "extruder" extruder std::string color; // if gcode is equal to PausePrintCode, // this field is used for save a short message shown on Printer display }; - std::vector custom_gcode_per_print_z; + + struct CustomGCodeInfo + { + enum MODE + { + SingleExtruder, // single extruder printer preset is selected + MultiAsSingle, // multiple extruder printer preset is selected, but + // this mode works just for Single extruder print + // (For all print from objects settings is used just one extruder) + MultiExtruder // multiple extruder printer preset is selected + } mode; + + std::vector gcodes; + + bool operator==(const CustomGCodeInfo& rhs) const + { + return (rhs.mode == this->mode ) && + (rhs.gcodes == this->gcodes ); + } + bool operator!=(const CustomGCodeInfo& rhs) const { return !(*this == rhs); } + } + custom_gcode_per_print_z; // Default constructor assigns a new ID to the model. Model() { assert(this->id().valid()); } @@ -842,9 +860,6 @@ public: // Propose an output path, replace extension. The new_extension shall contain the initial dot. std::string propose_export_file_name_and_path(const std::string &new_extension) const; - // from custom_gcode_per_print_z get just tool_change codes - std::vector> get_custom_tool_changes(double default_layer_height, size_t num_extruders) const; - private: explicit Model(int) : ObjectBase(-1) { assert(this->id().invalid()); }; void assign_new_unique_ids_recursive(); @@ -861,6 +876,10 @@ private: #undef OBJECTBASE_DERIVED_COPY_MOVE_CLONE #undef OBJECTBASE_DERIVED_PRIVATE_COPY_MOVE +// Return pairs of sorted by increasing print_z from custom_gcode_per_print_z. +// print_z corresponds to the first layer printed with the new extruder. +extern std::vector> custom_tool_changes(const Model &model, size_t num_extruders); + // Test whether the two models contain the same number of ModelObjects with the same set of IDs // ordered in the same order. In that case it is not necessary to kill the background processing. extern bool model_object_list_equal(const Model &model_old, const Model &model_new); diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index 0cfd97415..bbf32a141 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -332,6 +332,21 @@ namespace client return expr(); } + expr unary_integer(const Iterator start_pos) const + { + switch (this->type) { + case TYPE_INT : + return expr(this->i(), start_pos, this->it_range.end()); + case TYPE_DOUBLE: + return expr((int)(this->d()), start_pos, this->it_range.end()); + default: + this->throw_exception("Cannot convert to integer."); + } + assert(false); + // Suppress compiler warnings. + return expr(); + } + expr unary_not(const Iterator start_pos) const { switch (this->type) { @@ -415,6 +430,22 @@ namespace client return *this; } + expr &operator%=(const expr &rhs) + { + this->throw_if_not_numeric("Cannot divide a non-numeric type."); + rhs.throw_if_not_numeric("Cannot divide with a non-numeric type."); + if ((this->type == TYPE_INT) ? (rhs.i() == 0) : (rhs.d() == 0.)) + rhs.throw_exception("Division by zero"); + if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) { + double d = std::fmod(this->as_d(), rhs.as_d()); + this->data.d = d; + this->type = TYPE_DOUBLE; + } else + this->data.i %= rhs.i(); + this->it_range = boost::iterator_range(this->it_range.begin(), rhs.it_range.end()); + return *this; + } + static void to_string2(expr &self, std::string &out) { out = self.to_string(); @@ -1087,6 +1118,7 @@ namespace client unary_expression(_r1) [_val = _1] >> *( (lit('*') > unary_expression(_r1) ) [_val *= _1] | (lit('/') > unary_expression(_r1) ) [_val /= _1] + | (lit('%') > unary_expression(_r1) ) [_val %= _1] ); multiplicative_expression.name("multiplicative_expression"); @@ -1107,6 +1139,8 @@ namespace client { out = value.unary_minus(out.it_range.begin()); } static void not_(expr &value, expr &out) { out = value.unary_not(out.it_range.begin()); } + static void to_int(expr &value, expr &out) + { out = value.unary_integer(out.it_range.begin()); } }; unary_expression = iter_pos[px::bind(&FactorActions::set_start_pos, _1, _val)] >> ( scalar_variable_reference(_r1) [ _val = _1 ] @@ -1118,6 +1152,8 @@ namespace client [ px::bind(&expr::min, _val, _2) ] | (kw["max"] > '(' > conditional_expression(_r1) [_val = _1] > ',' > conditional_expression(_r1) > ')') [ px::bind(&expr::max, _val, _2) ] + //FIXME this is likley not correct + | (kw["int"] > '(' > unary_expression(_r1) /* > ')' */ ) [ px::bind(&FactorActions::to_int, _1, _val) ] | (strict_double > iter_pos) [ px::bind(&FactorActions::double_, _1, _2, _val) ] | (int_ > iter_pos) [ px::bind(&FactorActions::int_, _1, _2, _val) ] | (kw[bool_] > iter_pos) [ px::bind(&FactorActions::bool_, _1, _2, _val) ] diff --git a/src/libslic3r/PlaceholderParser.hpp b/src/libslic3r/PlaceholderParser.hpp index 14fdc5c28..d744dba22 100644 --- a/src/libslic3r/PlaceholderParser.hpp +++ b/src/libslic3r/PlaceholderParser.hpp @@ -41,7 +41,7 @@ public: // Fill in the template using a macro processing language. // Throws std::runtime_error on syntax or runtime error. - std::string process(const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override = nullptr) const; + 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. diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 02d9da784..1cb16b4de 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -478,7 +478,7 @@ static std::vector print_objects_from_model_object(const ModelOb // Compare just the layer ranges and their layer heights, not the associated configs. // Ignore the layer heights if check_layer_heights is false. -bool layer_height_ranges_equal(const t_layer_config_ranges &lr1, const t_layer_config_ranges &lr2, bool check_layer_height) +static bool layer_height_ranges_equal(const t_layer_config_ranges &lr1, const t_layer_config_ranges &lr2, bool check_layer_height) { if (lr1.size() != lr2.size()) return false; @@ -493,6 +493,37 @@ bool layer_height_ranges_equal(const t_layer_config_ranges &lr1, const t_layer_c return true; } +// Returns true if va == vb when all CustomGCode items that are not ToolChangeCode are ignored. +static bool custom_per_printz_gcodes_tool_changes_differ(const std::vector &va, const std::vector &vb) +{ + auto it_a = va.begin(); + auto it_b = vb.begin(); + while (it_a != va.end() && it_b != vb.end()) { + if (it_a != va.end() && it_a->gcode != ToolChangeCode) { + // Skip any CustomGCode items, which are not tool changes. + ++ it_a; + continue; + } + if (it_b != vb.end() && it_b->gcode != ToolChangeCode) { + // Skip any CustomGCode items, which are not tool changes. + ++ it_b; + continue; + } + if (it_a == va.end() || it_b == vb.end()) + // va or vb contains more Tool Changes than the other. + return true; + assert(it_a->gcode == ToolChangeCode); + assert(it_b->gcode == ToolChangeCode); + if (*it_a != *it_b) + // The two Tool Changes differ. + return true; + ++ it_a; + ++ it_b; + } + // There is no change in custom Tool Changes. + return false; +} + // Collect diffs of configuration values at various containers, // resolve the filament rectract overrides of extruder retract values. void Print::config_diffs( @@ -638,48 +669,6 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ m_ranges.emplace_back(t_layer_height_range(m_ranges.back().first.second, DBL_MAX), nullptr); } - // Convert input config ranges into continuous non-overlapping sorted vector of intervals and their configs, - // considering custom_tool_change values - void assign(const t_layer_config_ranges &in, const std::vector> &custom_tool_changes) { - m_ranges.clear(); - m_ranges.reserve(in.size()); - // Input ranges are sorted lexicographically. First range trims the other ranges. - coordf_t last_z = 0; - for (const std::pair &range : in) - if (range.first.second > last_z) { - coordf_t min_z = std::max(range.first.first, 0.); - if (min_z > last_z + EPSILON) { - m_ranges.emplace_back(t_layer_height_range(last_z, min_z), nullptr); - last_z = min_z; - } - if (range.first.second > last_z + EPSILON) { - const DynamicPrintConfig* cfg = &range.second; - m_ranges.emplace_back(t_layer_height_range(last_z, range.first.second), cfg); - last_z = range.first.second; - } - } - - // add ranges for extruder changes from custom_tool_changes - for (size_t i = 0; i < custom_tool_changes.size(); i++) { - const DynamicPrintConfig* cfg = &custom_tool_changes[i].second; - coordf_t cur_Z = custom_tool_changes[i].first; - coordf_t next_Z = i == custom_tool_changes.size()-1 ? DBL_MAX : custom_tool_changes[i+1].first; - if (cur_Z > last_z + EPSILON) { - if (i==0) - m_ranges.emplace_back(t_layer_height_range(last_z, cur_Z), nullptr); - m_ranges.emplace_back(t_layer_height_range(cur_Z, next_Z), cfg); - } - else if (next_Z > last_z + EPSILON) - m_ranges.emplace_back(t_layer_height_range(last_z, next_Z), cfg); - } - - if (m_ranges.empty()) - m_ranges.emplace_back(t_layer_height_range(0, DBL_MAX), nullptr); - else if (m_ranges.back().second == nullptr) - m_ranges.back().first.second = DBL_MAX; - else if (m_ranges.back().first.second != DBL_MAX) - m_ranges.emplace_back(t_layer_height_range(m_ranges.back().first.second, DBL_MAX), nullptr); - } const DynamicPrintConfig* config(const t_layer_height_range &range) const { auto it = std::lower_bound(m_ranges.begin(), m_ranges.end(), std::make_pair< t_layer_height_range, const DynamicPrintConfig*>(t_layer_height_range(range.first - EPSILON, range.second - EPSILON), nullptr)); // #ys_FIXME_COLOR @@ -733,17 +722,18 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ for (const ModelObject *model_object : m_model.objects) model_object_status.emplace(model_object->id(), ModelObjectStatus::New); } else { + if (m_model.custom_gcode_per_print_z != model.custom_gcode_per_print_z) { + update_apply_status(custom_per_printz_gcodes_tool_changes_differ(m_model.custom_gcode_per_print_z.gcodes, model.custom_gcode_per_print_z.gcodes) ? + // The Tool Ordering and the Wipe Tower are no more valid. + this->invalidate_steps({ psWipeTower, psGCodeExport }) : + // There is no change in Tool Changes stored in custom_gcode_per_print_z, therefore there is no need to update Tool Ordering. + this->invalidate_step(psGCodeExport)); + m_model.custom_gcode_per_print_z = model.custom_gcode_per_print_z; + } if (model_object_list_equal(m_model, model)) { // The object list did not change. for (const ModelObject *model_object : m_model.objects) model_object_status.emplace(model_object->id(), ModelObjectStatus::Old); - - // But if custom gcode per layer height was changed - if (m_model.custom_gcode_per_print_z != model.custom_gcode_per_print_z) { - // we should stop background processing - update_apply_status(this->invalidate_step(psGCodeExport)); - m_model.custom_gcode_per_print_z = model.custom_gcode_per_print_z; - } } else if (model_object_list_extended(m_model, model)) { // Add new objects. Their volumes and configs will be synchronized later. update_apply_status(this->invalidate_step(psGCodeExport)); @@ -835,9 +825,6 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ for (PrintObject *print_object : m_objects) print_object_status.emplace(PrintObjectStatus(print_object)); - std::vector> custom_tool_changes = - m_model.get_custom_tool_changes(m_default_object_config.layer_height, num_extruders); - // 3) Synchronize ModelObjects & PrintObjects. for (size_t idx_model_object = 0; idx_model_object < model.objects.size(); ++ idx_model_object) { ModelObject &model_object = *m_model.objects[idx_model_object]; @@ -845,9 +832,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ assert(it_status != model_object_status.end()); assert(it_status->status != ModelObjectStatus::Deleted); const ModelObject& model_object_new = *model.objects[idx_model_object]; - // ys_FIXME_COLOR - // const_cast(*it_status).layer_ranges.assign(model_object_new.layer_config_ranges); - const_cast(*it_status).layer_ranges.assign(model_object_new.layer_config_ranges, custom_tool_changes); + const_cast(*it_status).layer_ranges.assign(model_object_new.layer_config_ranges); if (it_status->status == ModelObjectStatus::New) // PrintObject instances will be added in the next loop. continue; @@ -1015,8 +1000,6 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ PrintRegionConfig this_region_config; bool this_region_config_set = false; for (PrintObject *print_object : m_objects) { - if(m_force_update_print_regions && !custom_tool_changes.empty()) - goto print_object_end; const LayerRanges *layer_ranges; { auto it_status = model_object_status.find(ModelObjectStatus(print_object->model_object()->id())); @@ -1572,9 +1555,13 @@ void Print::process() obj->generate_support_material(); if (this->set_started(psWipeTower)) { m_wipe_tower_data.clear(); + m_tool_ordering.clear(); if (this->has_wipe_tower()) { //this->set_status(95, L("Generating wipe tower")); this->_make_wipe_tower(); + } else if (! this->config().complete_objects.value) { + // 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); } this->set_done(psWipeTower); } @@ -1963,8 +1950,8 @@ const WipeTowerData& Print::wipe_tower_data(size_t extruders_cnt, double first_l // If the wipe tower wasn't created yet, make sure the depth and brim_width members are set to default. if (! is_step_done(psWipeTower) && extruders_cnt !=0) { - float width = m_config.wipe_tower_width; - float brim_spacing = nozzle_diameter * 1.25f - first_layer_height * (1. - M_PI_4); + float width = float(m_config.wipe_tower_width); + float brim_spacing = float(nozzle_diameter * 1.25f - first_layer_height * (1. - M_PI_4)); const_cast(this)->m_wipe_tower_data.depth = (900.f/width) * float(extruders_cnt - 1); const_cast(this)->m_wipe_tower_data.brim_width = 4.5f * brim_spacing; @@ -1990,6 +1977,7 @@ void Print::_make_wipe_tower() // Let the ToolOrdering class know there will be initial priming extrusions at the start of the print. m_wipe_tower_data.tool_ordering = ToolOrdering(*this, (unsigned int)-1, true); + if (! m_wipe_tower_data.tool_ordering.has_wipe_tower()) // Don't generate any wipe tower. return; @@ -2107,13 +2095,6 @@ void Print::_make_wipe_tower() m_wipe_tower_data.number_of_toolchanges = wipe_tower.get_number_of_toolchanges(); } -// Returns extruder this eec should be printed with, according to PrintRegion config -int Print::get_extruder(const ExtrusionEntityCollection& fill, const PrintRegion ®ion) -{ - return is_infill(fill.role()) ? std::max(0, (is_solid_infill(fill.entities.front()->role()) ? region.config().solid_infill_extruder : region.config().infill_extruder) - 1) : - std::max(region.config().perimeter_extruder.value - 1, 0); -} - // Generate a recommended G-code output file name based on the format template, default extension, and template parameters // (timestamps, object placeholders derived from the model, current placeholder prameters and print statistics. // Use the final print statistics if available, or just keep the print statistics placeholders if not available yet (before G-code is finalized). diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 95c7b656a..53a6f97b9 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -15,6 +15,8 @@ #include "GCode/ThumbnailData.hpp" #endif // ENABLE_THUMBNAIL_GENERATOR +#include "libslic3r.h" + namespace Slic3r { class Print; @@ -25,8 +27,19 @@ class GCodePreviewData; // Print step IDs for keeping track of the print state. enum PrintStep { - psSkirt, psBrim, psWipeTower, psGCodeExport, psCount, + psSkirt, + psBrim, + // Synonym for the last step before the Wipe Tower / Tool Ordering, for the G-code preview slider to understand that + // all the extrusions are there for the layer slider to add color changes etc. + psExtrusionPaths = psBrim, + psWipeTower, + // psToolOrdering is a synonym to psWipeTower, as the Wipe Tower calculates and modifies the ToolOrdering, + // while if printing without the Wipe Tower, the ToolOrdering is calculated as well. + psToolOrdering = psWipeTower, + psGCodeExport, + psCount, }; + enum PrintObjectStep { posSlice, posPerimeters, posPrepareInfill, posInfill, posSupportMaterial, posCount, @@ -50,7 +63,7 @@ public: // Average diameter of nozzles participating on extruding this region. coordf_t bridging_height_avg(const PrintConfig &print_config) const; - // Collect extruder indices used to print this region's object. + // Collect 0-based extruder indices used to print this region's object. void collect_object_printing_extruders(std::vector &object_extruders) const; static void collect_object_printing_extruders(const PrintConfig &print_config, const PrintRegionConfig ®ion_config, std::vector &object_extruders); @@ -116,8 +129,21 @@ public: size_t total_layer_count() const { return this->layer_count() + this->support_layer_count(); } size_t layer_count() const { return m_layers.size(); } void clear_layers(); - Layer* get_layer(int idx) { return m_layers[idx]; } - const Layer* get_layer(int idx) const { return m_layers[idx]; } + const Layer* get_layer(int idx) const { return m_layers[idx]; } + Layer* get_layer(int idx) { return m_layers[idx]; } + // Get a layer exactly at print_z. + const Layer* get_layer_at_printz(coordf_t print_z) const { + auto it = Slic3r::lower_bound_by_predicate(m_layers.begin(), m_layers.end(), [print_z](const Layer *layer) { return layer->print_z < print_z; }); + return (it == m_layers.end() || (*it)->print_z != print_z) ? nullptr : *it; + } + Layer* get_layer_at_printz(coordf_t print_z) { return const_cast(std::as_const(*this).get_layer_at_printz(print_z)); } + // Get a layer approximately at print_z. + const Layer* get_layer_at_printz(coordf_t print_z, coordf_t epsilon) const { + coordf_t limit = print_z + epsilon; + auto it = Slic3r::lower_bound_by_predicate(m_layers.begin(), m_layers.end(), [limit](const Layer *layer) { return layer->print_z < limit; }); + return (it == m_layers.end() || (*it)->print_z < print_z - epsilon) ? nullptr : *it; + } + Layer* get_layer_at_printz(coordf_t print_z, coordf_t epsilon) { return const_cast(std::as_const(*this).get_layer_at_printz(print_z, epsilon)); } // print_z: top of the layer; slice_z: center of the layer. Layer* add_layer(int id, coordf_t height, coordf_t print_z, coordf_t slice_z); @@ -219,7 +245,7 @@ struct WipeTowerData // Following section will be consumed by the GCodeGenerator. // Tool ordering of a non-sequential print has to be known to calculate the wipe tower. // Cache it here, so it does not need to be recalculated during the G-code generation. - ToolOrdering tool_ordering; + ToolOrdering &tool_ordering; // Cache of tool changes per print layer. std::unique_ptr> priming; std::vector> tool_changes; @@ -232,7 +258,6 @@ struct WipeTowerData float brim_width; void clear() { - tool_ordering.clear(); priming.reset(nullptr); tool_changes.clear(); final_purge.reset(nullptr); @@ -241,6 +266,14 @@ struct WipeTowerData depth = 0.f; brim_width = 0.f; } + +private: + // Only allow the WipeTowerData to be instantiated internally by Print, + // as this WipeTowerData shares reference to Print::m_tool_ordering. + friend class Print; + WipeTowerData(ToolOrdering &tool_ordering) : tool_ordering(tool_ordering) { clear(); } + WipeTowerData(const WipeTowerData & /* rhs */) = delete; + WipeTowerData &operator=(const WipeTowerData & /* rhs */) = delete; }; struct PrintStatistics @@ -345,6 +378,7 @@ public: const PrintConfig& config() const { return m_config; } const PrintObjectConfig& default_object_config() const { return m_default_object_config; } const PrintRegionConfig& default_region_config() const { return m_default_region_config; } + //FIXME returning const vector to non-const PrintObject*, caller could modify PrintObjects! const PrintObjectPtrs& objects() const { return m_objects; } PrintObject* get_object(size_t idx) { return m_objects[idx]; } const PrintObject* get_object(size_t idx) const { return m_objects[idx]; } @@ -353,9 +387,6 @@ public: // If zero, then the print is empty and the print shall not be executed. unsigned int num_object_instances() const; - // Returns extruder this eec should be printed with, according to PrintRegion config: - static int get_extruder(const ExtrusionEntityCollection& fill, const PrintRegion ®ion); - const ExtrusionEntityCollection& skirt() const { return m_skirt; } const ExtrusionEntityCollection& brim() const { return m_brim; } @@ -364,14 +395,13 @@ public: // Wipe tower support. bool has_wipe_tower() const; const WipeTowerData& wipe_tower_data(size_t extruders_cnt = 0, double first_layer_height = 0., double nozzle_diameter = 0.) const; + const ToolOrdering& tool_ordering() const { return m_tool_ordering; } std::string output_filename(const std::string &filename_base = std::string()) const override; // Accessed by SupportMaterial const PrintRegion* get_region(size_t idx) const { return m_regions[idx]; } - - // force update of PrintRegions, when custom_tool_change is not empty and (Re)Slicing is started - void set_force_update_print_regions(bool force_update_print_regions) { m_force_update_print_regions = force_update_print_regions; } + const ToolOrdering& get_tool_ordering() const { return m_wipe_tower_data.tool_ordering; } // #ys_FIXME just for testing protected: // methods for handling regions @@ -410,14 +440,12 @@ private: ExtrusionEntityCollection m_brim; // Following section will be consumed by the GCodeGenerator. - WipeTowerData m_wipe_tower_data; + ToolOrdering m_tool_ordering; + WipeTowerData m_wipe_tower_data {m_tool_ordering}; // Estimated print time, filament consumed. PrintStatistics m_print_statistics; - // flag used - bool m_force_update_print_regions = false; - // To allow GCode to set the Print's GCodeExport step status. friend class GCode; // Allow PrintObject to access m_mutex and m_cancel_callback. diff --git a/src/libslic3r/PrintBase.cpp b/src/libslic3r/PrintBase.cpp index fdee67f2e..55e24178d 100644 --- a/src/libslic3r/PrintBase.cpp +++ b/src/libslic3r/PrintBase.cpp @@ -37,7 +37,7 @@ void PrintBase::update_object_placeholders(DynamicConfig &config, const std::str } } - config.set_key_value("year", new ConfigOptionStrings(v_scale)); + config.set_key_value("scale", new ConfigOptionStrings(v_scale)); if (! input_file.empty()) { // get basename with and without suffix const std::string input_filename = boost::filesystem::path(input_file).filename().string(); diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 00c33dcfb..47a72b829 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1337,7 +1337,7 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back("OctoPrint"); def->enum_labels.push_back("Duet"); def->enum_labels.push_back("FlashAir"); - def->enum_values.push_back("AstroBox"); + def->enum_labels.push_back("AstroBox"); def->mode = comAdvanced; def->set_default_value(new ConfigOptionEnum(htOctoPrint)); diff --git a/src/libslic3r/Slicing.cpp b/src/libslic3r/Slicing.cpp index 8199bde03..2a32ba5ef 100644 --- a/src/libslic3r/Slicing.cpp +++ b/src/libslic3r/Slicing.cpp @@ -300,26 +300,14 @@ std::vector layer_height_profile_adaptive(const SlicingParameters& slici layer_height_profile.push_back(print_z); layer_height_profile.push_back(height); print_z += height; -#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE - layer_height_profile.push_back(print_z); - layer_height_profile.push_back(height); -#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE } -#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE double z_gap = slicing_params.object_print_z_height() - layer_height_profile[layer_height_profile.size() - 2]; if (z_gap > 0.0) { layer_height_profile.push_back(slicing_params.object_print_z_height()); layer_height_profile.push_back(clamp(slicing_params.min_layer_height, slicing_params.max_layer_height, z_gap)); } -#else - double last = std::max(slicing_params.first_object_layer_height, layer_height_profile[layer_height_profile.size() - 2]); - layer_height_profile.push_back(last); - layer_height_profile.push_back(slicing_params.first_object_layer_height); - layer_height_profile.push_back(slicing_params.object_print_z_height()); - layer_height_profile.push_back(slicing_params.first_object_layer_height); -#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE return layer_height_profile; } diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 64c5ce953..727200ca8 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -41,24 +41,6 @@ #define ENABLE_THUMBNAIL_GENERATOR (1 && ENABLE_2_2_0_ALPHA1) #define ENABLE_THUMBNAIL_GENERATOR_DEBUG (0 && ENABLE_THUMBNAIL_GENERATOR) -// Enable adaptive layer height profile -#define ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE (1 && ENABLE_2_2_0_ALPHA1) - -// Enable grayed variant for gizmos icons in non activable state -#define ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE (1 && ENABLE_2_2_0_ALPHA1) - -// Enable fix for view toolbar background not showing up on Mac with dark mode -#define ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX (1 && ENABLE_2_2_0_ALPHA1) - -// Enable selection for missing files in reload from disk command -#define ENABLE_RELOAD_FROM_DISK_MISSING_SELECTION (1 && ENABLE_2_2_0_ALPHA1) - -// Enable closing 3Dconnextion imgui settings dialog by clicking on [X] and [Close] buttons -#define ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG (1 && ENABLE_2_2_0_ALPHA1) - -// Enable not applying volume transformation during 3mf and amf loading, but keeping it as a ModelVolume member -#define ENABLE_KEEP_LOADED_VOLUME_TRANSFORM_AS_STAND_ALONE (1 && ENABLE_2_2_0_ALPHA1) - //================== // 2.2.0.beta1 techs @@ -68,4 +50,13 @@ // Enable using Y axis of 3Dconnexion devices as zoom #define ENABLE_3DCONNEXION_Y_AS_ZOOM (1 && ENABLE_2_2_0_BETA1) +// Enable a modified version of the toolbar textures where all the icons are separated by 1 pixel +#define ENABLE_MODIFIED_TOOLBAR_TEXTURES (1 && ENABLE_2_2_0_BETA1) + +// Enable configurable paths export (fullpath or not) to 3mf and amf +#define ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF (1 && ENABLE_2_2_0_BETA1) + +// Enable 6 degrees of freedom camera +#define ENABLE_6DOF_CAMERA (1 && ENABLE_2_2_0_BETA1) + #endif // _technologies_h_ diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index f2dd00114..0454644bb 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -158,6 +158,53 @@ inline std::unique_ptr make_unique(Args&&... args) { return std::unique_ptr(new T(std::forward(args)...)); } +// Variant of std::lower_bound() with compare predicate, but without the key. +// This variant is very useful in case that the T type is large or it does not even have a public constructor. +template +ForwardIt lower_bound_by_predicate(ForwardIt first, ForwardIt last, LowerThanKeyPredicate lower_thank_key) +{ + ForwardIt it; + typename std::iterator_traits::difference_type count, step; + count = std::distance(first, last); + + while (count > 0) { + it = first; + step = count / 2; + std::advance(it, step); + if (lower_thank_key(*it)) { + first = ++it; + count -= step + 1; + } + else + count = step; + } + return first; +} + +// from https://en.cppreference.com/w/cpp/algorithm/lower_bound +template> +ForwardIt binary_find(ForwardIt first, ForwardIt last, const T& value, Compare comp={}) +{ + // Note: BOTH type T and the type after ForwardIt is dereferenced + // must be implicitly convertible to BOTH Type1 and Type2, used in Compare. + // This is stricter than lower_bound requirement (see above) + + first = std::lower_bound(first, last, value, comp); + return first != last && !comp(value, *first) ? first : last; +} + +// from https://en.cppreference.com/w/cpp/algorithm/lower_bound +template +ForwardIt binary_find_by_predicate(ForwardIt first, ForwardIt last, LowerThanKeyPredicate lower_thank_key, EqualToKeyPredicate equal_to_key) +{ + // Note: BOTH type T and the type after ForwardIt is dereferenced + // must be implicitly convertible to BOTH Type1 and Type2, used in Compare. + // This is stricter than lower_bound requirement (see above) + + first = lower_bound_by_predicate(first, last, lower_thank_key); + return first != last && equal_to_key(*first) ? first : last; +} + template static inline T sqr(T x) { diff --git a/src/libslic3r/miniz_extension.cpp b/src/libslic3r/miniz_extension.cpp index e87b1a552..17cc136fc 100644 --- a/src/libslic3r/miniz_extension.cpp +++ b/src/libslic3r/miniz_extension.cpp @@ -26,8 +26,20 @@ bool open_zip(mz_zip_archive *zip, const char *fname, bool isread) return false; } - return isread ? mz_zip_reader_init_cfile(zip, f, 0, 0) - : mz_zip_writer_init_cfile(zip, f, 0); + bool res = false; + if (isread) + { + res = mz_zip_reader_init_cfile(zip, f, 0, 0); + if (!res) + // if we get here it means we tried to open a non-zip file + // we need to close the file here because the call to mz_zip_get_cfile() made into close_zip() returns a null pointer + // see: https://github.com/prusa3d/PrusaSlicer/issues/3536 + fclose(f); + } + else + res = mz_zip_writer_init_cfile(zip, f, 0); + + return res; } bool close_zip(mz_zip_archive *zip, bool isread) diff --git a/src/libslic3r/pchheader.hpp b/src/libslic3r/pchheader.hpp index 67b3d3a21..1339368bd 100644 --- a/src/libslic3r/pchheader.hpp +++ b/src/libslic3r/pchheader.hpp @@ -63,6 +63,7 @@ #include #include #include +#include #include #include #include diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index f6f80d822..00b7f2382 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -189,8 +189,6 @@ void Bed3D::Axes::render_axis(double length) const Bed3D::Bed3D() : m_type(Custom) - , m_custom_texture("") - , m_custom_model("") , m_vbo_id(0) , m_scale_factor(1.0f) { @@ -198,31 +196,31 @@ Bed3D::Bed3D() bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model) { - EType new_type = detect_type(shape); + auto check_texture = [](const std::string& texture) { + return !texture.empty() && (boost::algorithm::iends_with(texture, ".png") || boost::algorithm::iends_with(texture, ".svg")) && boost::filesystem::exists(texture); + }; - // check that the passed custom texture filename is valid - std::string cst_texture(custom_texture); - if (!cst_texture.empty()) - { - if ((!boost::algorithm::iends_with(custom_texture, ".png") && !boost::algorithm::iends_with(custom_texture, ".svg")) || !boost::filesystem::exists(custom_texture)) - cst_texture = ""; - } + auto check_model = [](const std::string& model) { + return !model.empty() && boost::algorithm::iends_with(model, ".stl") && boost::filesystem::exists(model); + }; - // check that the passed custom texture filename is valid - std::string cst_model(custom_model); - if (!cst_model.empty()) - { - if (!boost::algorithm::iends_with(custom_model, ".stl") || !boost::filesystem::exists(custom_model)) - cst_model = ""; - } + auto [new_type, system_model, system_texture] = detect_type(shape); - if ((m_shape == shape) && (m_type == new_type) && (m_custom_texture == cst_texture) && (m_custom_model == cst_model)) + std::string texture_filename = custom_texture.empty() ? system_texture : custom_texture; + if (!check_texture(texture_filename)) + texture_filename.clear(); + + std::string model_filename = custom_model.empty() ? system_model : custom_model; + if (!check_model(model_filename)) + model_filename.clear(); + + if ((m_shape == shape) && (m_type == new_type) && (m_texture_filename == texture_filename) && (m_model_filename == model_filename)) // No change, no need to update the UI. return false; m_shape = shape; - m_custom_texture = cst_texture; - m_custom_model = cst_model; + m_texture_filename = texture_filename; + m_model_filename = model_filename; m_type = new_type; calc_bounding_boxes(); @@ -244,7 +242,7 @@ bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, c m_texture.reset(); m_model.reset(); - // Set the origin and size for painting of the coordinate system axes. + // Set the origin and size for rendering the coordinate system axes. m_axes.origin = Vec3d(0.0, 0.0, (double)GROUND_Z); m_axes.length = 0.1 * m_bounding_box.max_size() * Vec3d::Ones(); @@ -262,7 +260,11 @@ Point Bed3D::point_projection(const Point& point) const return m_polygon.point_projection(point); } +#if ENABLE_6DOF_CAMERA +void Bed3D::render(GLCanvas3D& canvas, bool bottom, float scale_factor, bool show_axes) const +#else void Bed3D::render(GLCanvas3D& canvas, float theta, float scale_factor, bool show_axes) const +#endif // ENABLE_6DOF_CAMERA { m_scale_factor = scale_factor; @@ -273,13 +275,15 @@ void Bed3D::render(GLCanvas3D& canvas, float theta, float scale_factor, bool sho switch (m_type) { - case MK2: { render_prusa(canvas, "mk2", theta > 90.0f); break; } - case MK3: { render_prusa(canvas, "mk3", theta > 90.0f); break; } - case SL1: { render_prusa(canvas, "sl1", theta > 90.0f); break; } - case MINI: { render_prusa(canvas, "mini", theta > 90.0f); break; } - case ENDER3: { render_prusa(canvas, "ender3", theta > 90.0f); break; } +#if ENABLE_6DOF_CAMERA + case System: { render_system(canvas, bottom); break; } + default: + case Custom: { render_custom(canvas, bottom); break; } +#else + case System: { render_system(canvas, theta > 90.0f); break; } default: case Custom: { render_custom(canvas, theta > 90.0f); break; } +#endif // ENABLE_6DOF_CAMERA } glsafe(::glDisable(GL_DEPTH_TEST)); @@ -341,10 +345,26 @@ void Bed3D::calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox) printf("Unable to create bed grid lines\n"); } -Bed3D::EType Bed3D::detect_type(const Pointfs& shape) const +static std::string system_print_bed_model(const Preset &preset) { - EType type = Custom; + std::string out; + const VendorProfile::PrinterModel *pm = PresetUtils::system_printer_model(preset); + if (pm != nullptr && ! pm->bed_model.empty()) + out = Slic3r::resources_dir() + "/profiles/" + preset.vendor->id + "/" + pm->bed_model; + return out; +} +static std::string system_print_bed_texture(const Preset &preset) +{ + std::string out; + const VendorProfile::PrinterModel *pm = PresetUtils::system_printer_model(preset); + if (pm != nullptr && ! pm->bed_texture.empty()) + out = Slic3r::resources_dir() + "/profiles/" + preset.vendor->id + "/" + pm->bed_texture; + return out; +} + +std::tuple Bed3D::detect_type(const Pointfs& shape) const +{ auto bundle = wxGetApp().preset_bundle; if (bundle != nullptr) { @@ -353,39 +373,12 @@ Bed3D::EType Bed3D::detect_type(const Pointfs& shape) const { if (curr->config.has("bed_shape")) { - if (curr->vendor != nullptr) + if (shape == dynamic_cast(curr->config.option("bed_shape"))->values) { - if ((curr->vendor->name == "Prusa Research") && (shape == dynamic_cast(curr->config.option("bed_shape"))->values)) - { - if (boost::contains(curr->name, "SL1")) - { - type = SL1; - break; - } - else if (boost::contains(curr->name, "MK3") || boost::contains(curr->name, "MK2.5")) - { - type = MK3; - break; - } - else if (boost::contains(curr->name, "MK2")) - { - type = MK2; - break; - } - else if (boost::contains(curr->name, "MINI")) - { - type = MINI; - break; - } - } - else if ((curr->vendor->name == "Creality") && (shape == dynamic_cast(curr->config.option("bed_shape"))->values)) - { - if (boost::contains(curr->name, "ENDER-3")) - { - type = ENDER3; - break; - } - } + std::string model_filename = system_print_bed_model(*curr); + std::string texture_filename = system_print_bed_texture(*curr); + if (!model_filename.empty() && !texture_filename.empty()) + return std::make_tuple(System, model_filename, texture_filename); } } @@ -393,7 +386,7 @@ Bed3D::EType Bed3D::detect_type(const Pointfs& shape) const } } - return type; + return std::make_tuple(Custom, "", ""); } void Bed3D::render_axes() const @@ -402,35 +395,35 @@ void Bed3D::render_axes() const m_axes.render(); } -void Bed3D::render_prusa(GLCanvas3D& canvas, const std::string& key, bool bottom) const +void Bed3D::render_system(GLCanvas3D& canvas, bool bottom) const { if (!bottom) - render_model(m_custom_model.empty() ? resources_dir() + "/models/" + key + "_bed.stl" : m_custom_model); + render_model(); - render_texture(m_custom_texture.empty() ? resources_dir() + "/icons/bed/" + key + ".svg" : m_custom_texture, bottom, canvas); + render_texture(bottom, canvas); } -void Bed3D::render_texture(const std::string& filename, bool bottom, GLCanvas3D& canvas) const +void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const { - if (filename.empty()) + if (m_texture_filename.empty()) { m_texture.reset(); render_default(bottom); return; } - if ((m_texture.get_id() == 0) || (m_texture.get_source() != filename)) + if ((m_texture.get_id() == 0) || (m_texture.get_source() != m_texture_filename)) { m_texture.reset(); - if (boost::algorithm::iends_with(filename, ".svg")) + if (boost::algorithm::iends_with(m_texture_filename, ".svg")) { // use higher resolution images if graphic card and opengl version allow GLint max_tex_size = GLCanvas3DManager::get_gl_info().get_max_tex_size(); - if ((m_temp_texture.get_id() == 0) || (m_temp_texture.get_source() != filename)) + if ((m_temp_texture.get_id() == 0) || (m_temp_texture.get_source() != m_texture_filename)) { // generate a temporary lower resolution texture to show while no main texture levels have been compressed - if (!m_temp_texture.load_from_svg_file(filename, false, false, false, max_tex_size / 8)) + if (!m_temp_texture.load_from_svg_file(m_texture_filename, false, false, false, max_tex_size / 8)) { render_default(bottom); return; @@ -439,18 +432,18 @@ void Bed3D::render_texture(const std::string& filename, bool bottom, GLCanvas3D& } // starts generating the main texture, compression will run asynchronously - if (!m_texture.load_from_svg_file(filename, true, true, true, max_tex_size)) + if (!m_texture.load_from_svg_file(m_texture_filename, true, true, true, max_tex_size)) { render_default(bottom); return; } } - else if (boost::algorithm::iends_with(filename, ".png")) + else if (boost::algorithm::iends_with(m_texture_filename, ".png")) { // generate a temporary lower resolution texture to show while no main texture levels have been compressed - if ((m_temp_texture.get_id() == 0) || (m_temp_texture.get_source() != filename)) + if ((m_temp_texture.get_id() == 0) || (m_temp_texture.get_source() != m_texture_filename)) { - if (!m_temp_texture.load_from_file(filename, false, GLTexture::None, false)) + if (!m_temp_texture.load_from_file(m_texture_filename, false, GLTexture::None, false)) { render_default(bottom); return; @@ -459,7 +452,7 @@ void Bed3D::render_texture(const std::string& filename, bool bottom, GLCanvas3D& } // starts generating the main texture, compression will run asynchronously - if (!m_texture.load_from_file(filename, true, GLTexture::MultiThreaded, true)) + if (!m_texture.load_from_file(m_texture_filename, true, GLTexture::MultiThreaded, true)) { render_default(bottom); return; @@ -558,12 +551,12 @@ void Bed3D::render_texture(const std::string& filename, bool bottom, GLCanvas3D& } } -void Bed3D::render_model(const std::string& filename) const +void Bed3D::render_model() const { - if (filename.empty()) + if (m_model_filename.empty()) return; - if ((m_model.get_filename() != filename) && m_model.init_from_file(filename)) + if ((m_model.get_filename() != m_model_filename) && m_model.init_from_file(m_model_filename)) { // move the model so that its origin (0.0, 0.0, 0.0) goes into the bed shape center and a bit down to avoid z-fighting with the texture quad Vec3d shift = m_bounding_box.center(); @@ -584,16 +577,16 @@ void Bed3D::render_model(const std::string& filename) const void Bed3D::render_custom(GLCanvas3D& canvas, bool bottom) const { - if (m_custom_texture.empty() && m_custom_model.empty()) + if (m_texture_filename.empty() && m_model_filename.empty()) { render_default(bottom); return; } if (!bottom) - render_model(m_custom_model); + render_model(); - render_texture(m_custom_texture, bottom, canvas); + render_texture(bottom, canvas); } void Bed3D::render_default(bool bottom) const diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index cd2de862e..b1835a919 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -5,6 +5,8 @@ #include "3DScene.hpp" #include "GLShader.hpp" +#include + class GLUquadric; typedef class GLUquadric GLUquadricObj; @@ -64,11 +66,7 @@ class Bed3D public: enum EType : unsigned char { - MK2, - MK3, - SL1, - MINI, - ENDER3, + System, Custom, Num_Types }; @@ -76,19 +74,19 @@ public: private: EType m_type; Pointfs m_shape; - std::string m_custom_texture; - std::string m_custom_model; + std::string m_texture_filename; + std::string m_model_filename; mutable BoundingBoxf3 m_bounding_box; mutable BoundingBoxf3 m_extended_bounding_box; Polygon m_polygon; GeometryBuffer m_triangles; GeometryBuffer m_gridlines; mutable GLTexture m_texture; + mutable GLBed m_model; // temporary texture shown until the main texture has still no levels compressed mutable GLTexture m_temp_texture; mutable Shader m_shader; mutable unsigned int m_vbo_id; - mutable GLBed m_model; Axes m_axes; mutable float m_scale_factor; @@ -99,7 +97,6 @@ public: EType get_type() const { return m_type; } - bool is_prusa() const { return (m_type == MK2) || (m_type == MK3) || (m_type == SL1); } bool is_custom() const { return m_type == Custom; } const Pointfs& get_shape() const { return m_shape; } @@ -110,17 +107,21 @@ public: bool contains(const Point& point) const; Point point_projection(const Point& point) const; +#if ENABLE_6DOF_CAMERA + void render(GLCanvas3D& canvas, bool bottom, float scale_factor, bool show_axes) const; +#else void render(GLCanvas3D& canvas, float theta, float scale_factor, bool show_axes) const; +#endif // ENABLE_6DOF_CAMERA private: void calc_bounding_boxes() const; void calc_triangles(const ExPolygon& poly); void calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox); - EType detect_type(const Pointfs& shape) const; + std::tuple detect_type(const Pointfs& shape) const; void render_axes() const; - void render_prusa(GLCanvas3D& canvas, const std::string& key, bool bottom) const; - void render_texture(const std::string& filename, bool bottom, GLCanvas3D& canvas) const; - void render_model(const std::string& filename) const; + void render_system(GLCanvas3D& canvas, bool bottom) const; + void render_texture(bool bottom, GLCanvas3D& canvas) const; + void render_model() const; void render_custom(GLCanvas3D& canvas, bool bottom) const; void render_default(bool bottom) const; void reset(); diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index bbfcabd36..f6b1719db 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -877,13 +877,10 @@ bool can_export_to_obj(const GLVolume& volume) if (!volume.is_active || !volume.is_extrusion_path) return false; - if (volume.indexed_vertex_array.triangle_indices.empty() && (std::min(volume.indexed_vertex_array.triangle_indices_size, volume.tverts_range.second - volume.tverts_range.first) == 0)) - return false; + bool has_triangles = !volume.indexed_vertex_array.triangle_indices.empty() || (std::min(volume.indexed_vertex_array.triangle_indices_size, volume.tverts_range.second - volume.tverts_range.first) > 0); + bool has_quads = !volume.indexed_vertex_array.quad_indices.empty() || (std::min(volume.indexed_vertex_array.quad_indices_size, volume.qverts_range.second - volume.qverts_range.first) > 0); - if (volume.indexed_vertex_array.quad_indices.empty() && (std::min(volume.indexed_vertex_array.quad_indices_size, volume.qverts_range.second - volume.qverts_range.first) == 0)) - return false; - - return true; + return has_triangles || has_quads; } bool GLVolumeCollection::has_toolpaths_to_export() const diff --git a/src/slic3r/GUI/AppConfig.cpp b/src/slic3r/GUI/AppConfig.cpp index eb0a7fca6..12302a5dc 100644 --- a/src/slic3r/GUI/AppConfig.cpp +++ b/src/slic3r/GUI/AppConfig.cpp @@ -61,6 +61,11 @@ void AppConfig::set_defaults() if (get("preset_update").empty()) set("preset_update", "1"); +#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF + if (get("export_sources_full_pathnames").empty()) + set("export_sources_full_pathnames", "0"); +#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF + // remove old 'use_legacy_opengl' parameter from this config, if present if (!get("use_legacy_opengl").empty()) erase("", "use_legacy_opengl"); @@ -82,6 +87,11 @@ void AppConfig::set_defaults() if (get("use_perspective_camera").empty()) set("use_perspective_camera", "1"); +#if ENABLE_6DOF_CAMERA + if (get("use_free_camera").empty()) + set("use_free_camera", "0"); +#endif // ENABLE_6DOF_CAMERA + // Remove legacy window positions/sizes erase("", "main_frame_maximized"); erase("", "main_frame_pos"); diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 9d6a2a5ec..8af721f9d 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -95,14 +95,6 @@ void BackgroundSlicingProcess::process_fff() m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data); #endif // ENABLE_THUMBNAIL_GENERATOR - /* #ys_FIXME_no_exported_codes - if (m_fff_print->model().custom_gcode_per_print_z != GUI::wxGetApp().model().custom_gcode_per_print_z) { - GUI::wxGetApp().model().custom_gcode_per_print_z = m_fff_print->model().custom_gcode_per_print_z; - GUI::show_info(nullptr, _(L("To except of redundant tool manipulation, \n" - "Color change(s) for unused extruder(s) was(were) deleted")), _(L("Info"))); - } - */ - if (this->set_step_started(bspsGCodeFinalize)) { if (! m_export_path.empty()) { //FIXME localize the messages diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp index a66dcf39c..c8ece38f0 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp @@ -53,11 +53,11 @@ public: void set_thumbnail_cb(ThumbnailsGeneratorCallback cb) { m_thumbnail_cb = cb; } #endif // ENABLE_THUMBNAIL_GENERATOR - // The following wxCommandEvent will be sent to the UI thread / Platter window, when the slicing is finished + // The following wxCommandEvent will be sent to the UI thread / Plater window, when the slicing is finished // and the background processing will transition into G-code export. // The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed. void set_slicing_completed_event(int event_id) { m_event_slicing_completed_id = event_id; } - // The following wxCommandEvent will be sent to the UI thread / Platter window, when the G-code export is finished. + // The following wxCommandEvent will be sent to the UI thread / Plater window, when the G-code export is finished. // The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed. void set_finished_event(int event_id) { m_event_finished_id = event_id; } @@ -132,11 +132,6 @@ public: // This "finished" flag does not account for the final export of the output file (.gcode or zipped PNGs), // and it does not account for the OctoPrint scheduling. bool finished() const { return m_print->finished(); } - - void set_force_update_print_regions(bool force_update_print_regions) { - if (m_fff_print) - m_fff_print->set_force_update_print_regions(force_update_print_regions); - } private: void thread_proc(); @@ -191,9 +186,9 @@ private: void throw_if_canceled() const { if (m_print->canceled()) throw CanceledException(); } void prepare_upload(); - // wxWidgets command ID to be sent to the platter to inform that the slicing is finished, and the G-code export will continue. + // wxWidgets command ID to be sent to the plater to inform that the slicing is finished, and the G-code export will continue. int m_event_slicing_completed_id = 0; - // wxWidgets command ID to be sent to the platter to inform that the task finished. + // wxWidgets command ID to be sent to the plater to inform that the task finished. int m_event_finished_id = 0; }; diff --git a/src/slic3r/GUI/BitmapCache.cpp b/src/slic3r/GUI/BitmapCache.cpp index f7cefe72b..7322a88d1 100644 --- a/src/slic3r/GUI/BitmapCache.cpp +++ b/src/slic3r/GUI/BitmapCache.cpp @@ -1,6 +1,7 @@ #include "BitmapCache.hpp" #include "libslic3r/Utils.hpp" +#include #if ! defined(WIN32) && ! defined(__APPLE__) #define BROKEN_ALPHA @@ -15,7 +16,7 @@ #include "nanosvg/nanosvg.h" #define NANOSVGRAST_IMPLEMENTATION #include "nanosvg/nanosvgrast.h" -#include "GUI_App.hpp" +//#include "GUI_App.hpp" namespace Slic3r { namespace GUI { @@ -226,7 +227,7 @@ wxBitmap* BitmapCache::load_png(const std::string &bitmap_name, unsigned width, } wxBitmap* BitmapCache::load_svg(const std::string &bitmap_name, unsigned target_width, unsigned target_height, - float scale /* = 1.0f */, const bool grayscale/* = false*/) + float scale /* = 1.0f */, const bool grayscale/* = false*/, const bool dark_mode/* = false*/) { std::string bitmap_key = bitmap_name + ( target_height !=0 ? "-h" + std::to_string(target_height) : @@ -234,16 +235,45 @@ wxBitmap* BitmapCache::load_svg(const std::string &bitmap_name, unsigned target_ + (scale != 1.0f ? "-s" + std::to_string(scale) : "") + (grayscale ? "-gs" : ""); - target_height != 0 ? target_height *= scale : target_width *= scale; + /* For the Dark mode of any platform, we should draw icons in respect to OS background + * Note: All standard(regular) icons are collected in "icons" folder, + * SVG-icons, which have "Dark mode" variant, are collected in "icons/white" folder + */ + std::string folder; + if (dark_mode) + { +#ifdef __WXMSW__ + folder = "white\\"; +#else + folder = "white/"; +#endif + auto it = m_map.find(folder + bitmap_key); + if (it != m_map.end()) + return it->second; + else { + it = m_map.find(bitmap_key); + if (it != m_map.end()) + return it->second; + } - auto it = m_map.find(bitmap_key); - if (it != m_map.end()) - return it->second; + if (!boost::filesystem::exists(Slic3r::var(folder + bitmap_name + ".svg"))) + folder.clear(); + else + bitmap_key = folder + bitmap_key; + } + else + { + auto it = m_map.find(bitmap_key); + if (it != m_map.end()) + return it->second; + } - NSVGimage *image = ::nsvgParseFromFile(Slic3r::var(bitmap_name + ".svg").c_str(), "px", 96.0f); + NSVGimage *image = ::nsvgParseFromFile(Slic3r::var(folder + bitmap_name + ".svg").c_str(), "px", 96.0f); if (image == nullptr) return nullptr; + target_height != 0 ? target_height *= scale : target_width *= scale; + float svg_scale = target_height != 0 ? (float)target_height / image->height : target_width != 0 ? (float)target_width / image->width : 1; diff --git a/src/slic3r/GUI/BitmapCache.hpp b/src/slic3r/GUI/BitmapCache.hpp index ce77057c8..041e7d892 100644 --- a/src/slic3r/GUI/BitmapCache.hpp +++ b/src/slic3r/GUI/BitmapCache.hpp @@ -34,7 +34,7 @@ public: // Load png from resources/icons. bitmap_key is given without the .png suffix. Bitmap will be rescaled to provided height/width if nonzero. wxBitmap* load_png(const std::string &bitmap_key, unsigned width = 0, unsigned height = 0, const bool grayscale = false); // Load svg from resources/icons. bitmap_key is given without the .svg suffix. SVG will be rasterized to provided height/width. - wxBitmap* load_svg(const std::string &bitmap_key, unsigned width = 0, unsigned height = 0, float scale = 1.0f, const bool grayscale = false); + wxBitmap* load_svg(const std::string &bitmap_key, unsigned width = 0, unsigned height = 0, float scale = 1.0f, const bool grayscale = false, const bool dark_mode = false); static wxBitmap mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency); static wxBitmap mksolid(size_t width, size_t height, const unsigned char rgb[3]) { return mksolid(width, height, rgb[0], rgb[1], rgb[2], wxALPHA_OPAQUE); } diff --git a/src/slic3r/GUI/Camera.cpp b/src/slic3r/GUI/Camera.cpp index fa7a3be21..c21947d58 100644 --- a/src/slic3r/GUI/Camera.cpp +++ b/src/slic3r/GUI/Camera.cpp @@ -6,6 +6,11 @@ #endif // !ENABLE_THUMBNAIL_GENERATOR #include "GUI_App.hpp" #include "AppConfig.hpp" +#if ENABLE_CAMERA_STATISTICS +#if ENABLE_6DOF_CAMERA +#include "Mouse3DController.hpp" +#endif // ENABLE_6DOF_CAMERA +#endif // ENABLE_CAMERA_STATISTICS #include @@ -34,18 +39,27 @@ double Camera::FrustrumZMargin = 10.0; double Camera::MaxFovDeg = 60.0; Camera::Camera() +#if ENABLE_6DOF_CAMERA + : requires_zoom_to_bed(false) +#else : phi(45.0f) , requires_zoom_to_bed(false) , inverted_phi(false) +#endif // ENABLE_6DOF_CAMERA , m_type(Perspective) , m_target(Vec3d::Zero()) +#if !ENABLE_6DOF_CAMERA , m_theta(45.0f) +#endif // !ENABLE_6DOF_CAMERA , m_zoom(1.0) , m_distance(DefaultDistance) , m_gui_scale(1.0) , m_view_matrix(Transform3d::Identity()) , m_projection_matrix(Transform3d::Identity()) { +#if ENABLE_6DOF_CAMERA + set_default_orientation(); +#endif // ENABLE_6DOF_CAMERA } std::string Camera::get_type_as_string() const @@ -91,6 +105,9 @@ void Camera::select_next_type() void Camera::set_target(const Vec3d& target) { +#if ENABLE_6DOF_CAMERA + translate_world(target - m_target); +#else BoundingBoxf3 test_box = m_scene_box; test_box.translate(-m_scene_box.center()); // We may let this factor be customizable @@ -101,8 +118,10 @@ void Camera::set_target(const Vec3d& target) m_target(0) = clamp(test_box.min(0), test_box.max(0), target(0)); m_target(1) = clamp(test_box.min(1), test_box.max(1), target(1)); m_target(2) = clamp(test_box.min(2), test_box.max(2), target(2)); +#endif // ENABLE_6DOF_CAMERA } +#if !ENABLE_6DOF_CAMERA void Camera::set_theta(float theta, bool apply_limit) { if (apply_limit) @@ -114,6 +133,7 @@ void Camera::set_theta(float theta, bool apply_limit) m_theta += 360.0f; } } +#endif // !ENABLE_6DOF_CAMERA void Camera::update_zoom(double delta_zoom) { @@ -123,14 +143,33 @@ void Camera::update_zoom(double delta_zoom) void Camera::set_zoom(double zoom) { // Don't allow to zoom too far outside the scene. - double zoom_min = calc_zoom_to_bounding_box_factor(m_scene_box, (int)m_viewport[2], (int)m_viewport[3]); + double zoom_min = min_zoom(); if (zoom_min > 0.0) - zoom = std::max(zoom, zoom_min * 0.7); + zoom = std::max(zoom, zoom_min); // Don't allow to zoom too close to the scene. - m_zoom = std::min(zoom, 100.0); + m_zoom = std::min(zoom, max_zoom()); } +#if ENABLE_6DOF_CAMERA +void Camera::select_view(const std::string& direction) +{ + if (direction == "iso") + set_default_orientation(); + else if (direction == "left") + m_view_matrix = look_at(m_target - m_distance * Vec3d::UnitX(), m_target, Vec3d::UnitZ()); + else if (direction == "right") + m_view_matrix = look_at(m_target + m_distance * Vec3d::UnitX(), m_target, Vec3d::UnitZ()); + else if (direction == "top") + m_view_matrix = look_at(m_target + m_distance * Vec3d::UnitZ(), m_target, Vec3d::UnitY()); + else if (direction == "bottom") + m_view_matrix = look_at(m_target - m_distance * Vec3d::UnitZ(), m_target, -Vec3d::UnitY()); + else if (direction == "front") + m_view_matrix = look_at(m_target - m_distance * Vec3d::UnitY(), m_target, Vec3d::UnitZ()); + else if (direction == "rear") + m_view_matrix = look_at(m_target + m_distance * Vec3d::UnitY(), m_target, Vec3d::UnitZ()); +} +#else bool Camera::select_view(const std::string& direction) { const float* dir_vec = nullptr; @@ -159,6 +198,7 @@ bool Camera::select_view(const std::string& direction) else return false; } +#endif // ENABLE_6DOF_CAMERA double Camera::get_fov() const { @@ -180,20 +220,26 @@ void Camera::apply_viewport(int x, int y, unsigned int w, unsigned int h) const void Camera::apply_view_matrix() const { +#if !ENABLE_6DOF_CAMERA double theta_rad = Geometry::deg2rad(-(double)m_theta); double phi_rad = Geometry::deg2rad((double)phi); double sin_theta = ::sin(theta_rad); Vec3d camera_pos = m_target + m_distance * Vec3d(sin_theta * ::sin(phi_rad), sin_theta * ::cos(phi_rad), ::cos(theta_rad)); +#endif // !ENABLE_6DOF_CAMERA glsafe(::glMatrixMode(GL_MODELVIEW)); glsafe(::glLoadIdentity()); +#if ENABLE_6DOF_CAMERA + glsafe(::glMultMatrixd(m_view_matrix.data())); +#else glsafe(::glRotatef(-m_theta, 1.0f, 0.0f, 0.0f)); // pitch glsafe(::glRotatef(phi, 0.0f, 0.0f, 1.0f)); // yaw glsafe(::glTranslated(-camera_pos(0), -camera_pos(1), -camera_pos(2))); glsafe(::glGetDoublev(GL_MODELVIEW_MATRIX, m_view_matrix.data())); +#endif // ENABLE_6DOF_CAMERA } void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double far_z) const @@ -300,7 +346,11 @@ void Camera::zoom_to_box(const BoundingBoxf3& box, int canvas_w, int canvas_h) { m_zoom = zoom; // center view around box center +#if ENABLE_6DOF_CAMERA + set_target(box.center()); +#else m_target = box.center(); +#endif // ENABLE_6DOF_CAMERA } } @@ -313,7 +363,11 @@ void Camera::zoom_to_volumes(const GLVolumePtrs& volumes, int canvas_w, int canv { m_zoom = zoom; // center view around the calculated center +#if ENABLE_6DOF_CAMERA + set_target(center); +#else m_target = center; +#endif // ENABLE_6DOF_CAMERA } } #endif // ENABLE_THUMBNAIL_GENERATOR @@ -325,6 +379,12 @@ void Camera::debug_render() const imgui.begin(std::string("Camera statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); std::string type = get_type_as_string(); +#if ENABLE_6DOF_CAMERA + if (wxGetApp().plater()->get_mouse3d_controller().is_running() || (wxGetApp().app_config->get("use_free_camera") == "1")) + type += "/free"; + else + type += "/constrained"; +#endif // ENABLE_6DOF_CAMERA Vec3f position = get_position().cast(); Vec3f target = m_target.cast(); float distance = (float)get_distance(); @@ -360,6 +420,50 @@ void Camera::debug_render() const } #endif // ENABLE_CAMERA_STATISTICS +#if ENABLE_6DOF_CAMERA +void Camera::translate_world(const Vec3d& displacement) +{ + Vec3d new_target = validate_target(m_target + displacement); + Vec3d new_displacement = new_target - m_target; + if (!new_displacement.isApprox(Vec3d::Zero())) + { + m_target += new_displacement; + m_view_matrix.translate(-new_displacement); + } +} + +void Camera::rotate_on_sphere(double delta_azimut_rad, double delta_zenit_rad) +{ + Vec3d target = m_target; + translate_world(-target); + m_view_matrix.rotate(Eigen::AngleAxisd(delta_zenit_rad, get_dir_right())); + m_view_matrix.rotate(Eigen::AngleAxisd(delta_azimut_rad, Vec3d::UnitZ())); + translate_world(target); +} + +void Camera::rotate_local_around_target(const Vec3d& rotation_rad) +{ + rotate_local_around_pivot(rotation_rad, m_target); +} + +void Camera::rotate_local_around_pivot(const Vec3d& rotation_rad, const Vec3d& pivot) +{ + // we use a copy of the pivot because a reference to the current m_target may be passed in (see i.e. rotate_local_around_target()) + // and m_target is modified by the translate_world() calls + Vec3d center = pivot; + translate_world(-center); + m_view_matrix.rotate(Eigen::AngleAxisd(rotation_rad(0), get_dir_right())); + m_view_matrix.rotate(Eigen::AngleAxisd(rotation_rad(1), get_dir_up())); + m_view_matrix.rotate(Eigen::AngleAxisd(rotation_rad(2), get_dir_forward())); + translate_world(center); +} + +double Camera::min_zoom() const +{ + return 0.7 * calc_zoom_to_bounding_box_factor(m_scene_box, (int)m_viewport[2], (int)m_viewport[3]); +} +#endif // ENABLE_6DOF_CAMERA + std::pair Camera::calc_tight_frustrum_zs_around(const BoundingBoxf3& box) const { std::pair ret; @@ -540,6 +644,63 @@ void Camera::set_distance(double distance) const apply_view_matrix(); } +#if ENABLE_6DOF_CAMERA +Transform3d Camera::look_at(const Vec3d& position, const Vec3d& target, const Vec3d& up) const +{ + Vec3d unit_z = (position - target).normalized(); + Vec3d unit_x = up.cross(unit_z).normalized(); + Vec3d unit_y = unit_z.cross(unit_x).normalized(); + + Transform3d matrix; + + matrix(0, 0) = unit_x(0); + matrix(0, 1) = unit_x(1); + matrix(0, 2) = unit_x(2); + matrix(0, 3) = -unit_x.dot(position); + + matrix(1, 0) = unit_y(0); + matrix(1, 1) = unit_y(1); + matrix(1, 2) = unit_y(2); + matrix(1, 3) = -unit_y.dot(position); + + matrix(2, 0) = unit_z(0); + matrix(2, 1) = unit_z(1); + matrix(2, 2) = unit_z(2); + matrix(2, 3) = -unit_z.dot(position); + + matrix(3, 0) = 0.0; + matrix(3, 1) = 0.0; + matrix(3, 2) = 0.0; + matrix(3, 3) = 1.0; + + return matrix; +} + +void Camera::set_default_orientation() +{ + double theta_rad = Geometry::deg2rad(-45.0); + double phi_rad = Geometry::deg2rad(45.0); + double sin_theta = ::sin(theta_rad); + Vec3d camera_pos = m_target + m_distance * Vec3d(sin_theta * ::sin(phi_rad), sin_theta * ::cos(phi_rad), ::cos(theta_rad)); + m_view_matrix = Transform3d::Identity(); + m_view_matrix.rotate(Eigen::AngleAxisd(theta_rad, Vec3d::UnitX())).rotate(Eigen::AngleAxisd(phi_rad, Vec3d::UnitZ())).translate(-camera_pos); +} + +Vec3d Camera::validate_target(const Vec3d& target) const +{ + BoundingBoxf3 test_box = m_scene_box; + test_box.translate(-m_scene_box.center()); + // We may let this factor be customizable + static const double ScaleFactor = 1.5; + test_box.scale(ScaleFactor); + test_box.translate(m_scene_box.center()); + + return Vec3d(std::clamp(target(0), test_box.min(0), test_box.max(0)), + std::clamp(target(1), test_box.min(1), test_box.max(1)), + std::clamp(target(2), test_box.min(2), test_box.max(2))); +} +#endif // ENABLE_6DOF_CAMERA + } // GUI } // Slic3r diff --git a/src/slic3r/GUI/Camera.hpp b/src/slic3r/GUI/Camera.hpp index 6cd1b75a5..2a4336108 100644 --- a/src/slic3r/GUI/Camera.hpp +++ b/src/slic3r/GUI/Camera.hpp @@ -30,21 +30,29 @@ struct Camera Num_types }; +#if !ENABLE_6DOF_CAMERA float phi; - bool requires_zoom_to_bed; bool inverted_phi; +#endif // !ENABLE_6DOF_CAMERA + bool requires_zoom_to_bed; private: EType m_type; Vec3d m_target; +#if !ENABLE_6DOF_CAMERA float m_theta; +#endif // !ENABLE_6DOF_CAMERA double m_zoom; // Distance between camera position and camera target measured along the camera Z axis mutable double m_distance; mutable double m_gui_scale; mutable std::array m_viewport; +#if ENABLE_6DOF_CAMERA + Transform3d m_view_matrix; +#else mutable Transform3d m_view_matrix; +#endif // ENABLE_6DOF_CAMERA mutable Transform3d m_projection_matrix; mutable std::pair m_frustrum_zs; @@ -66,17 +74,24 @@ public: double get_distance() const { return m_distance; } double get_gui_scale() const { return m_gui_scale; } +#if !ENABLE_6DOF_CAMERA float get_theta() const { return m_theta; } void set_theta(float theta, bool apply_limit); +#endif // !ENABLE_6DOF_CAMERA double get_zoom() const { return m_zoom; } + double get_inv_zoom() const { assert(m_zoom != 0.0); return 1.0 / m_zoom; } void update_zoom(double delta_zoom); void set_zoom(double zoom); const BoundingBoxf3& get_scene_box() const { return m_scene_box; } void set_scene_box(const BoundingBoxf3& box) { m_scene_box = box; } +#if ENABLE_6DOF_CAMERA + void select_view(const std::string& direction); +#else bool select_view(const std::string& direction); +#endif // ENABLE_6DOF_CAMERA const std::array& get_viewport() const { return m_viewport; } const Transform3d& get_view_matrix() const { return m_view_matrix; } @@ -110,6 +125,27 @@ public: void debug_render() const; #endif // ENABLE_CAMERA_STATISTICS +#if ENABLE_6DOF_CAMERA + // translate the camera in world space + void translate_world(const Vec3d& displacement); + + // rotate the camera on a sphere having center == m_target and radius == m_distance + // using the given variations of spherical coordinates + void rotate_on_sphere(double delta_azimut_rad, double delta_zenit_rad); + + // rotate the camera around three axes parallel to the camera local axes and passing through m_target + void rotate_local_around_target(const Vec3d& rotation_rad); + + // rotate the camera around three axes parallel to the camera local axes and passing through the given pivot point + void rotate_local_around_pivot(const Vec3d& rotation_rad, const Vec3d& pivot); + + // returns true if the camera z axis (forward) is pointing in the negative direction of the world z axis + bool is_looking_downward() const { return get_dir_forward().dot(Vec3d::UnitZ()) < 0.0; } + + double max_zoom() const { return 100.0; } + double min_zoom() const; +#endif // ENABLE_6DOF_CAMERA + private: // returns tight values for nearZ and farZ plane around the given bounding box // the camera MUST be outside of the bounding box in eye coordinate of the given box @@ -121,6 +157,12 @@ private: double calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, int canvas_w, int canvas_h) const; #endif // ENABLE_THUMBNAIL_GENERATOR void set_distance(double distance) const; + +#if ENABLE_6DOF_CAMERA + Transform3d look_at(const Vec3d& position, const Vec3d& target, const Vec3d& up) const; + void set_default_orientation(); + Vec3d validate_target(const Vec3d& target) const; +#endif // ENABLE_6DOF_CAMERA }; } // GUI diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 265e7f203..add680a40 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -350,6 +350,21 @@ bool PrinterPicker::any_selected() const return false; } +std::set PrinterPicker::get_selected_models() const +{ + std::set ret_set; + + for (const auto& cb : cboxes) + if (cb->GetValue()) + ret_set.emplace(cb->model); + + for (const auto& cb : cboxes_alt) + if (cb->GetValue()) + ret_set.emplace(cb->model); + + return ret_set; +} + void PrinterPicker::on_checkbox(const Checkbox *cbox, bool checked) { PrinterPickerEvent evt(EVT_PRINTER_PICK, GetId(), vendor_id, cbox->model, cbox->variant, checked); @@ -500,6 +515,19 @@ bool PagePrinters::any_selected() const return false; } +std::set PagePrinters::get_selected_models() +{ + std::set ret_set; + + for (const auto *picker : printer_pickers) + { + std::set tmp_models = picker->get_selected_models(); + ret_set.insert(tmp_models.begin(), tmp_models.end()); + } + + return ret_set; +} + void PagePrinters::set_run_reason(ConfigWizard::RunReason run_reason) { if (technology == T_FFF @@ -765,6 +793,23 @@ PageUpdate::PageUpdate(ConfigWizard *parent) box_presets->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->preset_update = event.IsChecked(); }); } +#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF +PageReloadFromDisk::PageReloadFromDisk(ConfigWizard* parent) + : ConfigWizardPage(parent, _(L("Reload from disk")), _(L("Reload from disk"))) + , full_pathnames(false) +{ + auto* box_pathnames = new wxCheckBox(this, wxID_ANY, _(L("Export full pathnames of models and parts sources into 3mf and amf files"))); + box_pathnames->SetValue(wxGetApp().app_config->get("export_sources_full_pathnames") == "1"); + append(box_pathnames); + append_text(_(L( + "If enabled, allows the Reload from disk command to automatically find and load the files when invoked.\n" + "If not enabled, the Reload from disk command will ask to select each file using an open file dialog." + ))); + + box_pathnames->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& event) { this->full_pathnames = event.IsChecked(); }); +} +#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF + PageMode::PageMode(ConfigWizard *parent) : ConfigWizardPage(parent, _(L("View mode")), _(L("View mode"))) { @@ -1356,6 +1401,9 @@ void ConfigWizard::priv::load_pages() btn_finish->Enable(any_fff_selected || any_sla_selected); index->add_page(page_update); +#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF + index->add_page(page_reload_from_disk); +#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF index->add_page(page_mode); index->go_to(former_active); // Will restore the active item/page if possible @@ -1580,6 +1628,10 @@ void ConfigWizard::priv::on_printer_pick(PagePrinters *page, const PrinterPicker preset.is_visible = evt.enable; } } + + // if at list one printer is selected but there in no one selected material, + // select materials which is default for selected printer(s) + select_default_materials_if_needed(pair.second.vendor_profile, page->technology, evt.model_id); } if (page->technology & T_FFF) { @@ -1589,6 +1641,57 @@ void ConfigWizard::priv::on_printer_pick(PagePrinters *page, const PrinterPicker } } +void ConfigWizard::priv::select_default_materials_for_printer_model(const std::vector& models, Technology technology, const std::string& model_id) +{ + PageMaterials* page_materials = technology & T_FFF ? page_filaments : page_sla_materials; + + auto it = std::find_if(models.begin(), models.end(), [model_id](VendorProfile::PrinterModel model) {return model_id == model.id; }); + if (it != models.end()) + for (const std::string& material : it->default_materials) + appconfig_new.set(page_materials->materials->appconfig_section(), material, "1"); +} + +void ConfigWizard::priv::select_default_materials_if_needed(VendorProfile* vendor_profile, Technology technology, const std::string& model_id) +{ + if ((technology & T_FFF && !any_fff_selected) || + (technology & T_SLA && !any_sla_selected) || + check_materials_in_config(technology, false)) + return; + + select_default_materials_for_printer_model(vendor_profile->models, technology, model_id); +} + +void ConfigWizard::priv::selected_default_materials(Technology technology) +{ + auto select_default_materials_for_printer_page = [this](PagePrinters * page_printers, Technology technology) + { + std::set selected_models = page_printers->get_selected_models(); + const std::string vendor_id = page_printers->get_vendor_id(); + + for (auto& pair : bundles) + { + if (pair.first != vendor_id) + continue; + + for (const std::string& model_id : selected_models) + select_default_materials_for_printer_model(pair.second.vendor_profile->models, technology, model_id); + } + }; + + PagePrinters* page_printers = technology & T_FFF ? page_fff : page_msla; + select_default_materials_for_printer_page(page_printers, technology); + + for (const auto& printer : pages_3rdparty) + { + page_printers = technology & T_FFF ? printer.second.first : printer.second.second; + if (page_printers) + select_default_materials_for_printer_page(page_printers, technology); + } + + update_materials(technology); + (technology& T_FFF ? page_filaments : page_sla_materials)->reload_presets(); +} + void ConfigWizard::priv::on_3rdparty_install(const VendorProfile *vendor, bool install) { auto it = pages_3rdparty.find(vendor->id); @@ -1625,7 +1728,7 @@ bool ConfigWizard::priv::on_bnt_finish() return check_materials_in_config(T_ANY); } -bool ConfigWizard::priv::check_materials_in_config(Technology technology) +bool ConfigWizard::priv::check_materials_in_config(Technology technology, bool show_info_msg) { const auto exist_preset = [this](const std::string& section, const Materials& materials) { @@ -1640,15 +1743,32 @@ bool ConfigWizard::priv::check_materials_in_config(Technology technology) return false; }; + const auto ask_and_selected_default_materials = [this](wxString message, Technology technology) + { + wxMessageDialog msg(q, message, _(L("Notice")), wxYES_NO); + if (msg.ShowModal() == wxID_YES) + selected_default_materials(technology); + }; + if (any_fff_selected && technology & T_FFF && !exist_preset(AppConfig::SECTION_FILAMENTS, filaments)) { - show_info(q, _(L("You have to select at least one filament for selected printers")), ""); + if (show_info_msg) + { + wxString message = _(L("You have to select at least one filament for selected printers")) + "\n\n\t" + + _(L("Do you want to automatic select default filaments?")); + ask_and_selected_default_materials(message, T_FFF); + } return false; } if (any_sla_selected && technology & T_SLA && !exist_preset(AppConfig::SECTION_MATERIALS, sla_materials)) { - show_info(q, _(L("You have to select at least one material for selected printers")), ""); + if (show_info_msg) + { + wxString message = _(L("You have to select at least one material for selected printers")) + "\n\n\t" + + _(L("Do you want to automatic select default materials?")); + ask_and_selected_default_materials(message, T_SLA); + } return false; } @@ -1730,6 +1850,11 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese } app_config->set("version_check", page_update->version_check ? "1" : "0"); app_config->set("preset_update", page_update->preset_update ? "1" : "0"); + +#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF + app_config->set("export_sources_full_pathnames", page_reload_from_disk->full_pathnames ? "1" : "0"); +#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF + page_mode->serialize_mode(app_config); std::string preferred_model; @@ -1885,6 +2010,9 @@ ConfigWizard::ConfigWizard(wxWindow *parent) p->add_page(p->page_custom = new PageCustom(this)); p->add_page(p->page_update = new PageUpdate(this)); +#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF + p->add_page(p->page_reload_from_disk = new PageReloadFromDisk(this)); +#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF p->add_page(p->page_mode = new PageMode(this)); p->add_page(p->page_firmware = new PageFirmware(this)); p->add_page(p->page_bed = new PageBedShape(this)); diff --git a/src/slic3r/GUI/ConfigWizard_private.hpp b/src/slic3r/GUI/ConfigWizard_private.hpp index a90832a3f..9c14633c9 100644 --- a/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/src/slic3r/GUI/ConfigWizard_private.hpp @@ -149,6 +149,7 @@ struct PrinterPicker: wxPanel void select_all(bool select, bool alternates = false); void select_one(size_t i, bool select); bool any_selected() const; + std::set get_selected_models() const ; int get_width() const { return width; } const std::vector& get_button_indexes() { return m_button_indexes; } @@ -215,6 +216,9 @@ struct PagePrinters: ConfigWizardPage void select_all(bool select, bool alternates = false); int get_width() const; bool any_selected() const; + std::set get_selected_models(); + + std::string get_vendor_id() const { return printer_pickers.empty() ? "" : printer_pickers[0]->vendor_id; } virtual void set_run_reason(ConfigWizard::RunReason run_reason) override; }; @@ -301,6 +305,17 @@ struct PageUpdate: ConfigWizardPage PageUpdate(ConfigWizard *parent); }; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF +struct PageReloadFromDisk : ConfigWizardPage +{ + bool full_pathnames; + + PageReloadFromDisk(ConfigWizard* parent); +}; +#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + struct PageMode: ConfigWizardPage { wxRadioButton *radio_simple; @@ -455,6 +470,11 @@ struct ConfigWizard::priv PageMaterials *page_sla_materials = nullptr; PageCustom *page_custom = nullptr; PageUpdate *page_update = nullptr; +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF + PageReloadFromDisk *page_reload_from_disk = nullptr; +#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF +//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ PageMode *page_mode = nullptr; PageVendors *page_vendors = nullptr; Pages3rdparty pages_3rdparty; @@ -487,10 +507,17 @@ struct ConfigWizard::priv void on_custom_setup(); void on_printer_pick(PagePrinters *page, const PrinterPickerEvent &evt); + void select_default_materials_for_printer_model(const std::vector &models, + Technology technology, + const std::string & model_id); + void select_default_materials_if_needed(VendorProfile* vendor_profile, + Technology technology, + const std::string &model_id); + void selected_default_materials(Technology technology); void on_3rdparty_install(const VendorProfile *vendor, bool install); bool on_bnt_finish(); - bool check_materials_in_config(Technology technology); + bool check_materials_in_config(Technology technology, bool show_info_msg = true); void apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater); // #ys_FIXME_alise void update_presets_in_config(const std::string& section, const std::string& alias_key, bool add); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 6392b97c0..85f7197fc 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -132,9 +132,7 @@ GLCanvas3D::LayersEditing::LayersEditing() , m_object_max_z(0.f) , m_slicing_parameters(nullptr) , m_layer_height_profile_modified(false) -#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE , m_adaptive_quality(0.5f) -#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE , state(Unknown) , band_width(2.0f) , strength(0.005f) @@ -155,9 +153,6 @@ GLCanvas3D::LayersEditing::~LayersEditing() } const float GLCanvas3D::LayersEditing::THICKNESS_BAR_WIDTH = 70.0f; -#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE -const float GLCanvas3D::LayersEditing::THICKNESS_RESET_BUTTON_HEIGHT = 22.0f; -#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE bool GLCanvas3D::LayersEditing::init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename) { @@ -224,7 +219,6 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const if (!m_enabled) return; -#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f); const Size& cnv_size = canvas.get_canvas_size(); @@ -319,13 +313,6 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const imgui.end(); const Rect& bar_rect = get_bar_rect_viewport(canvas); -#else - const Rect& bar_rect = get_bar_rect_viewport(canvas); - const Rect& reset_rect = get_reset_rect_viewport(canvas); - - _render_tooltip_texture(canvas, bar_rect, reset_rect); - _render_reset_texture(reset_rect); -#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE render_active_object_annotations(canvas, bar_rect); render_profile(bar_rect); } @@ -352,68 +339,26 @@ bool GLCanvas3D::LayersEditing::bar_rect_contains(const GLCanvas3D& canvas, floa return (rect.get_left() <= x) && (x <= rect.get_right()) && (rect.get_top() <= y) && (y <= rect.get_bottom()); } -#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE -bool GLCanvas3D::LayersEditing::reset_rect_contains(const GLCanvas3D& canvas, float x, float y) -{ - const Rect& rect = get_reset_rect_screen(canvas); - return (rect.get_left() <= x) && (x <= rect.get_right()) && (rect.get_top() <= y) && (y <= rect.get_bottom()); -} -#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE - Rect GLCanvas3D::LayersEditing::get_bar_rect_screen(const GLCanvas3D& canvas) { const Size& cnv_size = canvas.get_canvas_size(); float w = (float)cnv_size.get_width(); float h = (float)cnv_size.get_height(); -#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE return Rect(w - thickness_bar_width(canvas), 0.0f, w, h); -#else - return Rect(w - thickness_bar_width(canvas), 0.0f, w, h - reset_button_height(canvas)); -#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE } -#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE -Rect GLCanvas3D::LayersEditing::get_reset_rect_screen(const GLCanvas3D& canvas) -{ - const Size& cnv_size = canvas.get_canvas_size(); - float w = (float)cnv_size.get_width(); - float h = (float)cnv_size.get_height(); - - return Rect(w - thickness_bar_width(canvas), h - reset_button_height(canvas), w, h); -} -#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE - Rect GLCanvas3D::LayersEditing::get_bar_rect_viewport(const GLCanvas3D& canvas) { const Size& cnv_size = canvas.get_canvas_size(); float half_w = 0.5f * (float)cnv_size.get_width(); float half_h = 0.5f * (float)cnv_size.get_height(); - float zoom = (float)canvas.get_camera().get_zoom(); - float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + float inv_zoom = (float)canvas.get_camera().get_inv_zoom(); -#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE return Rect((half_w - thickness_bar_width(canvas)) * inv_zoom, half_h * inv_zoom, half_w * inv_zoom, -half_h * inv_zoom); -#else - return Rect((half_w - thickness_bar_width(canvas)) * inv_zoom, half_h * inv_zoom, half_w * inv_zoom, (-half_h + reset_button_height(canvas)) * inv_zoom); -#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE } -#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE -Rect GLCanvas3D::LayersEditing::get_reset_rect_viewport(const GLCanvas3D& canvas) -{ - const Size& cnv_size = canvas.get_canvas_size(); - float half_w = 0.5f * (float)cnv_size.get_width(); - float half_h = 0.5f * (float)cnv_size.get_height(); - - float zoom = (float)canvas.get_camera().get_zoom(); - float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; - - return Rect((half_w - thickness_bar_width(canvas)) * inv_zoom, (-half_h + reset_button_height(canvas)) * inv_zoom, half_w * inv_zoom, -half_h * inv_zoom); -} -#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE - bool GLCanvas3D::LayersEditing::is_initialized() const { return m_shader.is_initialized(); @@ -448,54 +393,6 @@ std::string GLCanvas3D::LayersEditing::get_tooltip(const GLCanvas3D& canvas) con return ret; } -#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE -void GLCanvas3D::LayersEditing::_render_tooltip_texture(const GLCanvas3D& canvas, const Rect& bar_rect, const Rect& reset_rect) const -{ - // TODO: do this with ImGui - - if (m_tooltip_texture.get_id() == 0) - { - std::string filename = resources_dir() + "/icons/variable_layer_height_tooltip.png"; - if (!m_tooltip_texture.load_from_file(filename, false, GLTexture::SingleThreaded, false)) - return; - } - -#if ENABLE_RETINA_GL - const float scale = canvas.get_canvas_size().get_scale_factor(); -#else - const float scale = canvas.get_wxglcanvas()->GetContentScaleFactor(); -#endif - const float width = (float)m_tooltip_texture.get_width() * scale; - const float height = (float)m_tooltip_texture.get_height() * scale; - - float zoom = (float)canvas.get_camera().get_zoom(); - float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; - float gap = 10.0f * inv_zoom; - - float bar_left = bar_rect.get_left(); - float reset_bottom = reset_rect.get_bottom(); - - float l = bar_left - width * inv_zoom - gap; - float r = bar_left - gap; - float t = reset_bottom + height * inv_zoom + gap; - float b = reset_bottom + gap; - - GLTexture::render_texture(m_tooltip_texture.get_id(), l, r, b, t); -} - -void GLCanvas3D::LayersEditing::_render_reset_texture(const Rect& reset_rect) const -{ - if (m_reset_texture.get_id() == 0) - { - std::string filename = resources_dir() + "/icons/variable_layer_height_reset.png"; - if (!m_reset_texture.load_from_file(filename, false, GLTexture::SingleThreaded, false)) - return; - } - - GLTexture::render_texture(m_reset_texture.get_id(), reset_rect.get_left(), reset_rect.get_right(), reset_rect.get_bottom(), reset_rect.get_top()); -} -#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE - void GLCanvas3D::LayersEditing::render_active_object_annotations(const GLCanvas3D& canvas, const Rect& bar_rect) const { m_shader.start_using(); @@ -644,7 +541,6 @@ void GLCanvas3D::LayersEditing::reset_layer_height_profile(GLCanvas3D& canvas) canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); } -#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE void GLCanvas3D::LayersEditing::adaptive_layer_height_profile(GLCanvas3D& canvas, float quality_factor) { this->update_slicing_parameters(); @@ -662,7 +558,6 @@ void GLCanvas3D::LayersEditing::smooth_layer_height_profile(GLCanvas3D& canvas, m_layers_texture.valid = false; canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); } -#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE void GLCanvas3D::LayersEditing::generate_layer_height_texture() { @@ -725,19 +620,6 @@ float GLCanvas3D::LayersEditing::thickness_bar_width(const GLCanvas3D &canvas) * THICKNESS_BAR_WIDTH; } -#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE -float GLCanvas3D::LayersEditing::reset_button_height(const GLCanvas3D &canvas) -{ - return -#if ENABLE_RETINA_GL - canvas.get_canvas_size().get_scale_factor() -#else - canvas.get_wxglcanvas()->GetContentScaleFactor() -#endif - * THICKNESS_RESET_BUTTON_HEIGHT; -} -#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE - const Point GLCanvas3D::Mouse::Drag::Invalid_2D_Point(INT_MAX, INT_MAX); const Vec3d GLCanvas3D::Mouse::Drag::Invalid_3D_Point(DBL_MAX, DBL_MAX, DBL_MAX); @@ -964,8 +846,7 @@ void GLCanvas3D::WarningTexture::render(const GLCanvas3D& canvas) const if ((m_id > 0) && (m_original_width > 0) && (m_original_height > 0) && (m_width > 0) && (m_height > 0)) { const Size& cnv_size = canvas.get_canvas_size(); - float zoom = (float)canvas.get_camera().get_zoom(); - float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + float inv_zoom = (float)canvas.get_camera().get_inv_zoom(); float left = (-0.5f * (float)m_original_width) * inv_zoom; float top = (-0.5f * (float)cnv_size.get_height() + (float)m_original_height + 2.0f) * inv_zoom; float right = left + (float)m_original_width * inv_zoom; @@ -1011,7 +892,7 @@ void GLCanvas3D::LegendTexture::fill_color_print_legend_items( const GLCanvas3D std::vector& colors, std::vector& cp_legend_items) { - std::vector custom_gcode_per_print_z = wxGetApp().plater()->model().custom_gcode_per_print_z; + std::vector custom_gcode_per_print_z = wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes; const int extruders_cnt = wxGetApp().extruders_edited_cnt(); if (extruders_cnt == 1) @@ -1137,6 +1018,12 @@ bool GLCanvas3D::LegendTexture::generate(const GCodePreviewData& preview_data, c // calculate scaling const float scale_gl = canvas.get_canvas_size().get_scale_factor(); +#if ENABLE_RETINA_GL + // For non-visible or non-created window getBackingScaleFactor function return 0.0 value. + // And using of the zero scale causes a crash, when we trying to draw text to the (0,0) rectangle + if (scale_gl <= 0.0f) + return false; +#endif const float scale = scale_gl * wxGetApp().em_unit()*0.1; // get scale from em_unit() value, because of get_scale_factor() return 1 const int scaled_square = std::floor((float)Px_Square * scale); const int scaled_title_offset = Px_Title_Offset * scale; @@ -1321,8 +1208,7 @@ void GLCanvas3D::LegendTexture::render(const GLCanvas3D& canvas) const if ((m_id > 0) && (m_original_width > 0) && (m_original_height > 0) && (m_width > 0) && (m_height > 0)) { const Size& cnv_size = canvas.get_canvas_size(); - float zoom = (float)canvas.get_camera().get_zoom(); - float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + float inv_zoom = (float)canvas.get_camera().get_inv_zoom(); float left = (-0.5f * (float)cnv_size.get_width()) * inv_zoom; float top = (0.5f * (float)cnv_size.get_height()) * inv_zoom; float right = left + (float)m_original_width * inv_zoom; @@ -1343,9 +1229,6 @@ void GLCanvas3D::LegendTexture::render(const GLCanvas3D& canvas) const } } -#if !ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX -wxDEFINE_EVENT(EVT_GLCANVAS_INIT, SimpleEvent); -#endif // !ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX wxDEFINE_EVENT(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, RBtnEvent); @@ -1369,11 +1252,9 @@ wxDEFINE_EVENT(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, wxKeyEvent); wxDEFINE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent); wxDEFINE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent); -#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE wxDEFINE_EVENT(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, Event); wxDEFINE_EVENT(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, HeightProfileSmoothEvent); -#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE #if ENABLE_THUMBNAIL_GENERATOR const double GLCanvas3D::DefaultCameraZoomToBoxMarginFactor = 1.25; @@ -1515,10 +1396,6 @@ bool GLCanvas3D::init() if (m_selection.is_enabled() && !m_selection.init()) return false; -#if !ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX - post_event(SimpleEvent(EVT_GLCANVAS_INIT)); -#endif // !ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX - m_initialized = true; return true; @@ -1675,7 +1552,6 @@ bool GLCanvas3D::is_layers_editing_allowed() const return m_layers_editing.is_allowed(); } -#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE void GLCanvas3D::reset_layer_height_profile() { wxGetApp().plater()->take_snapshot(_(L("Variable layer height - Reset"))); @@ -1699,7 +1575,6 @@ void GLCanvas3D::smooth_layer_height_profile(const HeightProfileSmoothingParams& m_layers_editing.state = LayersEditing::Completed; m_dirty = true; } -#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE bool GLCanvas3D::is_reload_delayed() const { @@ -1786,8 +1661,14 @@ void GLCanvas3D::zoom_to_selection() void GLCanvas3D::select_view(const std::string& direction) { +#if ENABLE_6DOF_CAMERA + m_camera.select_view(direction); + if (m_canvas != nullptr) + m_canvas->Refresh(); +#else if (m_camera.select_view(direction) && (m_canvas != nullptr)) m_canvas->Refresh(); +#endif // ENABLE_6DOF_CAMERA } void GLCanvas3D::update_volumes_colors_by_extruder() @@ -1844,10 +1725,12 @@ void GLCanvas3D::render() GLfloat position_top[4] = { -0.5f, -0.5f, 1.0f, 0.0f }; glsafe(::glLightfv(GL_LIGHT0, GL_POSITION, position_top)); +#if !ENABLE_6DOF_CAMERA float theta = m_camera.get_theta(); if (theta > 180.f) // absolute value of the rotation theta = 360.f - theta; +#endif // !ENABLE_6DOF_CAMERA wxGetApp().imgui()->new_frame(); @@ -1872,7 +1755,11 @@ void GLCanvas3D::render() _render_objects(); _render_sla_slices(); _render_selection(); +#if ENABLE_6DOF_CAMERA + _render_bed(!m_camera.is_looking_downward(), true); +#else _render_bed(theta, true); +#endif // ENABLE_6DOF_CAMERA #if ENABLE_RENDER_SELECTION_CENTER _render_selection_center(); @@ -1921,11 +1808,7 @@ void GLCanvas3D::render() m_camera.debug_render(); #endif // ENABLE_CAMERA_STATISTICS -#if ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG wxGetApp().plater()->get_mouse3d_controller().render_settings_dialog(*this); -#else - wxGetApp().plater()->get_mouse3d_controller().render_settings_dialog((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height()); -#endif // ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG wxGetApp().imgui()->render(); @@ -2421,7 +2304,7 @@ static void load_gcode_retractions(const GCodePreviewData::Retraction& retractio volume_index.first_volumes.emplace_back(extrusion_type, 0, (unsigned int)volumes.volumes.size()); - GLVolume *volume = volumes.new_nontoolpath_volume(retractions.color.rgba, VERTEX_BUFFER_RESERVE_SIZE); + GLVolume *volume = volumes.new_nontoolpath_volume(retractions.color.rgba.data(), VERTEX_BUFFER_RESERVE_SIZE); GCodePreviewData::Retraction::PositionsList copy(retractions.positions); std::sort(copy.begin(), copy.end(), [](const GCodePreviewData::Retraction::Position& p1, const GCodePreviewData::Retraction::Position& p2) { return p1.position(2) < p2.position(2); }); @@ -3111,20 +2994,6 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_layers_editing.state = LayersEditing::Editing; _perform_layer_editing_action(&evt); } -#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE - else if ((layer_editing_object_idx != -1) && m_layers_editing.reset_rect_contains(*this, pos(0), pos(1))) - { - if (evt.LeftDown()) - { - // A volume is selected and the mouse is inside the reset button. Reset the ModelObject's layer height profile. - m_layers_editing.reset_layer_height_profile(*this); - // Index 2 means no editing, just wait for mouse up event. - m_layers_editing.state = LayersEditing::Completed; - - m_dirty = true; - } - } -#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE else if (evt.LeftDown() && (evt.ShiftDown() || evt.AltDown()) && m_picking_enabled) { if (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports) @@ -3207,7 +3076,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) // we do not want to translate objects if the user just clicked on an object while pressing shift to remove it from the selection and then drag if (m_selection.contains_volume(get_first_hover_volume_idx())) { +#if ENABLE_6DOF_CAMERA + if (std::abs(m_camera.get_dir_forward()(2)) < EPSILON) +#else if (m_camera.get_theta() == 90.0f) +#endif // ENABLE_6DOF_CAMERA { // side view -> move selected volumes orthogonally to camera view direction Linef3 ray = mouse_ray(pos); @@ -3267,9 +3140,18 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) if (m_hover_volume_idxs.empty() && m_mouse.is_start_position_3D_defined()) { const Vec3d& orig = m_mouse.drag.start_position_3D; +#if ENABLE_6DOF_CAMERA + double x = Geometry::deg2rad(pos(0) - orig(0)) * (double)TRACKBALLSIZE; + double y = Geometry::deg2rad(pos(1) - orig(1)) * (double)TRACKBALLSIZE; + if (wxGetApp().plater()->get_mouse3d_controller().is_running() || (wxGetApp().app_config->get("use_free_camera") == "1")) + m_camera.rotate_local_around_target(Vec3d(y, x, 0.0)); + else + m_camera.rotate_on_sphere(x, y); +#else float sign = m_camera.inverted_phi ? -1.0f : 1.0f; m_camera.phi += sign * ((float)pos(0) - (float)orig(0)) * TRACKBALLSIZE; m_camera.set_theta(m_camera.get_theta() - ((float)pos(1) - (float)orig(1)) * TRACKBALLSIZE, wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA); +#endif // ENABLE_6DOF_CAMERA m_dirty = true; } m_mouse.drag.start_position_3D = Vec3d((double)pos(0), (double)pos(1), 0.0); @@ -3319,9 +3201,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) if (!evt.ShiftDown() && m_picking_enabled) deselect_all(); } +#if !ENABLE_6DOF_CAMERA else if (evt.LeftUp() && m_mouse.dragging) // Flips X mouse deltas if bed is upside down m_camera.inverted_phi = (m_camera.get_dir_up()(2) < 0.0); +#endif // !ENABLE_6DOF_CAMERA else if (evt.RightUp()) { m_mouse.position = pos.cast(); @@ -3926,6 +3810,9 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, bool Camera camera; camera.set_type(Camera::Ortho); +#if ENABLE_6DOF_CAMERA + camera.set_scene_box(scene_bounding_box()); +#endif // ENABLE_6DOF_CAMERA camera.zoom_to_volumes(visible_volumes, thumbnail_data.width, thumbnail_data.height); camera.apply_viewport(0, 0, thumbnail_data.width, thumbnail_data.height); camera.apply_view_matrix(); @@ -3976,7 +3863,11 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, bool glsafe(::glDisable(GL_DEPTH_TEST)); if (show_bed) +#if ENABLE_6DOF_CAMERA + _render_bed(!camera.is_looking_downward(), false); +#else _render_bed(camera.get_theta(), false); +#endif // ENABLE_6DOF_CAMERA if (transparent_background) glsafe(::glClearColor(1.0f, 1.0f, 1.0f, 1.0f)); @@ -4227,10 +4118,8 @@ bool GLCanvas3D::_init_toolbars() if (!_init_undoredo_toolbar()) return false; -#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX if (!_init_view_toolbar()) return false; -#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX return true; } @@ -4489,12 +4378,10 @@ bool GLCanvas3D::_init_undoredo_toolbar() return true; } -#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX bool GLCanvas3D::_init_view_toolbar() { return wxGetApp().plater()->init_view_toolbar(); } -#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX bool GLCanvas3D::_set_current() { @@ -4933,8 +4820,7 @@ void GLCanvas3D::_render_main_toolbar() const #endif // ENABLE_RETINA_GL Size cnv_size = get_canvas_size(); - float zoom = (float)m_camera.get_zoom(); - float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + float inv_zoom = (float)m_camera.get_inv_zoom(); float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; float left = -0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width()) * inv_zoom; @@ -4960,8 +4846,7 @@ void GLCanvas3D::_render_undoredo_toolbar() const #endif // ENABLE_RETINA_GL Size cnv_size = get_canvas_size(); - float zoom = (float)m_camera.get_zoom(); - float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + float inv_zoom = (float)m_camera.get_inv_zoom(); float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; float left = (m_main_toolbar.get_width() - 0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width())) * inv_zoom; @@ -4983,8 +4868,7 @@ void GLCanvas3D::_render_view_toolbar() const #endif // ENABLE_RETINA_GL Size cnv_size = get_canvas_size(); - float zoom = (float)m_camera.get_zoom(); - float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + float inv_zoom = (float)m_camera.get_inv_zoom(); // places the toolbar on the bottom-left corner of the 3d scene float top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar.get_height()) * inv_zoom; @@ -5407,11 +5291,11 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c const std::string& code = it->gcode; // pause print or custom Gcode if (code == PausePrintCode || - (code != ColorChangeCode && code != ExtruderChangeCode)) + (code != ColorChangeCode && code != ToolChangeCode)) return number_tools()-1; // last color item is a gray color for pause print or custom G-code // change tool (extruder) - if (code == ExtruderChangeCode) + if (code == ToolChangeCode) return get_color_idx_for_tool_change(it, extruder); // change color for current extruder if (code == ColorChangeCode) { @@ -5433,7 +5317,7 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c return color_idx; } // change tool (extruder) - if (it->gcode == ExtruderChangeCode) + if (it->gcode == ToolChangeCode) return get_color_idx_for_tool_change(it, extruder); } @@ -5477,7 +5361,7 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c bool is_tool_change = false; while (it_n != color_print_values->begin()) { --it_n; - if (it_n->gcode == ExtruderChangeCode) { + if (it_n->gcode == ToolChangeCode) { is_tool_change = true; if (it_n->extruder == it->extruder || (it_n->extruder == 0 && it->extruder == extruder)) return get_m600_color_idx(it); @@ -5857,7 +5741,7 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat return 0.0f; } - static GCodePreviewData::Color path_color(const GCodePreviewData& data, const std::vector& tool_colors, float value) + static Color path_color(const GCodePreviewData& data, const std::vector& tool_colors, float value) { switch (data.extrusion.view_type) { @@ -5875,8 +5759,8 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat return data.get_volumetric_rate_color(value); case GCodePreviewData::Extrusion::Tool: { - GCodePreviewData::Color color; - ::memcpy((void*)color.rgba, (const void*)(tool_colors.data() + (unsigned int)value * 4), 4 * sizeof(float)); + Color color; + ::memcpy((void*)color.rgba.data(), (const void*)(tool_colors.data() + (unsigned int)value * 4), 4 * sizeof(float)); return color; } case GCodePreviewData::Extrusion::ColorPrint: @@ -5884,16 +5768,16 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat int color_cnt = (int)tool_colors.size() / 4; int val = value > color_cnt ? color_cnt - 1 : value; - GCodePreviewData::Color color; - ::memcpy((void*)color.rgba, (const void*)(tool_colors.data() + val * 4), 4 * sizeof(float)); + Color color; + ::memcpy((void*)color.rgba.data(), (const void*)(tool_colors.data() + val * 4), 4 * sizeof(float)); return color; } default: - return GCodePreviewData::Color::Dummy; + return Color{}; } - return GCodePreviewData::Color::Dummy; + return Color{}; } }; @@ -5936,7 +5820,7 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat if (! values.empty()) { m_gcode_preview_volume_index.first_volumes.emplace_back(GCodePreviewVolumeIndex::Extrusion, role, (unsigned int)m_volumes.volumes.size()); for (const float value : values) - roles_filters.back().emplace_back(value, m_volumes.new_toolpath_volume(Helper::path_color(preview_data, tool_colors, value).rgba, vertex_buffer_prealloc_size)); + roles_filters.back().emplace_back(value, m_volumes.new_toolpath_volume(Helper::path_color(preview_data, tool_colors, value).rgba.data(), vertex_buffer_prealloc_size)); } } } @@ -6019,7 +5903,7 @@ inline void travel_paths_internal( by_type.reserve(values.size()); // creates a new volume for each feedrate for (TYPE type : values) - by_type.emplace_back(type, volumes.new_nontoolpath_volume(func_color(type).rgba, VERTEX_BUFFER_RESERVE_SIZE)); + by_type.emplace_back(type, volumes.new_nontoolpath_volume(func_color(type).rgba.data(), VERTEX_BUFFER_RESERVE_SIZE)); } // populates volumes @@ -6066,19 +5950,19 @@ void GLCanvas3D::_load_gcode_travel_paths(const GCodePreviewData& preview_data, case GCodePreviewData::Extrusion::Feedrate: travel_paths_internal(preview_data, [](const GCodePreviewData::Travel::Polyline &polyline) { return polyline.feedrate; }, - [&preview_data](const float feedrate) -> const GCodePreviewData::Color { return preview_data.get_feedrate_color(feedrate); }, + [&preview_data](const float feedrate) -> const Color { return preview_data.get_feedrate_color(feedrate); }, m_volumes, m_initialized); break; case GCodePreviewData::Extrusion::Tool: travel_paths_internal(preview_data, [](const GCodePreviewData::Travel::Polyline &polyline) { return polyline.extruder_id; }, - [&tool_colors](const unsigned int extruder_id) -> const GCodePreviewData::Color { assert((extruder_id + 1) * 4 <= tool_colors.size()); return GCodePreviewData::Color(tool_colors.data() + extruder_id * 4); }, + [&tool_colors](const unsigned int extruder_id) -> const Color { assert((extruder_id + 1) * 4 <= tool_colors.size()); return Color(tool_colors.data() + extruder_id * 4); }, m_volumes, m_initialized); break; default: travel_paths_internal(preview_data, [](const GCodePreviewData::Travel::Polyline &polyline) { return polyline.type; }, - [&preview_data](const unsigned int type) -> const GCodePreviewData::Color& { return preview_data.travel.type_colors[type]; }, + [&preview_data](const unsigned int type) -> const Color& { return preview_data.travel.type_colors[type]; }, m_volumes, m_initialized); break; } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 3e8b56bd0..c842c7b42 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -83,9 +83,6 @@ template using Vec3dsEvent = ArrayEvent; using HeightProfileSmoothEvent = Event; -#if !ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX -wxDECLARE_EVENT(EVT_GLCANVAS_INIT, SimpleEvent); -#endif // !ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX wxDECLARE_EVENT(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, RBtnEvent); wxDECLARE_EVENT(EVT_GLCANVAS_REMOVE_OBJECT, SimpleEvent); @@ -108,11 +105,9 @@ wxDECLARE_EVENT(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, wxKeyEvent); wxDECLARE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent); wxDECLARE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent); -#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE wxDECLARE_EVENT(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE, SimpleEvent); wxDECLARE_EVENT(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, Event); wxDECLARE_EVENT(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, HeightProfileSmoothEvent); -#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE class GLCanvas3D { @@ -162,17 +157,10 @@ private: private: static const float THICKNESS_BAR_WIDTH; -#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE - static const float THICKNESS_RESET_BUTTON_HEIGHT; -#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE bool m_enabled; Shader m_shader; unsigned int m_z_texture_id; -#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE - mutable GLTexture m_tooltip_texture; - mutable GLTexture m_reset_texture; -#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE // Not owned by LayersEditing. const DynamicPrintConfig *m_config; // ModelObject for the currently selected object (Model::objects[last_object_id]). @@ -184,10 +172,8 @@ private: std::vector m_layer_height_profile; bool m_layer_height_profile_modified; -#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE mutable float m_adaptive_quality; mutable HeightProfileSmoothingParams m_smooth_params; -#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE class LayersTexture { @@ -235,24 +221,13 @@ private: void adjust_layer_height_profile(); void accept_changes(GLCanvas3D& canvas); void reset_layer_height_profile(GLCanvas3D& canvas); -#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE void adaptive_layer_height_profile(GLCanvas3D& canvas, float quality_factor); void smooth_layer_height_profile(GLCanvas3D& canvas, const HeightProfileSmoothingParams& smoothing_params); -#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE static float get_cursor_z_relative(const GLCanvas3D& canvas); static bool bar_rect_contains(const GLCanvas3D& canvas, float x, float y); -#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE - static bool reset_rect_contains(const GLCanvas3D& canvas, float x, float y); -#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE static Rect get_bar_rect_screen(const GLCanvas3D& canvas); -#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE - static Rect get_reset_rect_screen(const GLCanvas3D& canvas); -#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE static Rect get_bar_rect_viewport(const GLCanvas3D& canvas); -#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE - static Rect get_reset_rect_viewport(const GLCanvas3D& canvas); -#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE float object_max_z() const { return m_object_max_z; } @@ -261,18 +236,11 @@ private: private: bool is_initialized() const; void generate_layer_height_texture(); -#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE - void _render_tooltip_texture(const GLCanvas3D& canvas, const Rect& bar_rect, const Rect& reset_rect) const; - void _render_reset_texture(const Rect& reset_rect) const; -#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE void render_active_object_annotations(const GLCanvas3D& canvas, const Rect& bar_rect) const; void render_profile(const Rect& bar_rect) const; void update_slicing_parameters(); static float thickness_bar_width(const GLCanvas3D &canvas); -#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE - static float reset_button_height(const GLCanvas3D &canvas); -#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE }; struct Mouse @@ -540,11 +508,9 @@ public: bool is_layers_editing_enabled() const; bool is_layers_editing_allowed() const; -#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE void reset_layer_height_profile(); void adaptive_layer_height_profile(float quality_factor); void smooth_layer_height_profile(const HeightProfileSmoothingParams& smoothing_params); -#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE bool is_reload_delayed() const; @@ -684,9 +650,7 @@ private: bool _init_toolbars(); bool _init_main_toolbar(); bool _init_undoredo_toolbar(); -#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX bool _init_view_toolbar(); -#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX bool _set_current(); void _resize(unsigned int w, unsigned int h); diff --git a/src/slic3r/GUI/GLSelectionRectangle.cpp b/src/slic3r/GUI/GLSelectionRectangle.cpp index 684563bff..a8b69d75a 100644 --- a/src/slic3r/GUI/GLSelectionRectangle.cpp +++ b/src/slic3r/GUI/GLSelectionRectangle.cpp @@ -69,8 +69,7 @@ namespace GUI { return; const Camera& camera = canvas.get_camera(); - float zoom = (float)camera.get_zoom(); - float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + float inv_zoom = (float)camera.get_inv_zoom(); Size cnv_size = canvas.get_canvas_size(); float cnv_half_width = 0.5f * (float)cnv_size.get_width(); diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp index 62129cfc0..4ac69d71f 100644 --- a/src/slic3r/GUI/GLTexture.cpp +++ b/src/slic3r/GUI/GLTexture.cpp @@ -168,12 +168,26 @@ bool GLTexture::load_from_svg_files_as_sprites_array(const std::vectorwidth, image->height); +#if ENABLE_MODIFIED_TOOLBAR_TEXTURES + // offset by 1 to leave the first pixel empty (both in x and y) + nsvgRasterize(rast, image, 1, 1, scale, sprite_data.data(), sprite_size_px, sprite_size_px, sprite_stride); +#else nsvgRasterize(rast, image, 0, 0, scale, sprite_data.data(), sprite_size_px, sprite_size_px, sprite_stride); +#endif // ENABLE_MODIFIED_TOOLBAR_TEXTURES // makes white only copy of the sprite ::memcpy((void*)sprite_white_only_data.data(), (const void*)sprite_data.data(), sprite_bytes); @@ -231,7 +250,11 @@ bool GLTexture::load_from_svg_files_as_sprites_array(const std::vector& state : states) { @@ -250,6 +273,23 @@ bool GLTexture::load_from_svg_files_as_sprites_array(const std::vectorGLTexture::Quad_UVs + { + assert((tex_width != 0) && (tex_height != 0)); + GLTexture::Quad_UVs ret; + // tiles in the texture are spaced by 1 pixel + float icon_size_px = (float)(tex_width - 1) / (float)Num_States; + float inv_tex_width = 1.0f / (float)tex_width; + float inv_tex_height = 1.0f / (float)tex_height; + // tiles in the texture are spaced by 1 pixel + float u_offset = 1.0f * inv_tex_width; + float v_offset = 1.0f * inv_tex_height; + float du = icon_size_px * inv_tex_width; + float dv = icon_size_px * inv_tex_height; + float left = u_offset + (float)m_state * du; + float right = left + du - u_offset; + float top = v_offset + (float)m_data.sprite_id * dv; + float bottom = top + dv - v_offset; + ret.left_top = { left, top }; + ret.left_bottom = { left, bottom }; + ret.right_bottom = { right, bottom }; + ret.right_top = { right, top }; + return ret; + }; + + GLTexture::render_sub_texture(tex_id, left, right, bottom, top, uvs(tex_width, tex_height, icon_size)); +#else GLTexture::render_sub_texture(tex_id, left, right, bottom, top, get_uvs(tex_width, tex_height, icon_size)); +#endif // ENABLE_MODIFIED_TOOLBAR_TEXTURES if (is_pressed()) { @@ -97,6 +125,7 @@ void GLToolbarItem::render(unsigned int tex_id, float left, float right, float b } } +#if !ENABLE_MODIFIED_TOOLBAR_TEXTURES GLTexture::Quad_UVs GLToolbarItem::get_uvs(unsigned int tex_width, unsigned int tex_height, unsigned int icon_size) const { GLTexture::Quad_UVs uvs; @@ -110,14 +139,14 @@ GLTexture::Quad_UVs GLToolbarItem::get_uvs(unsigned int tex_width, unsigned int float right = left + scaled_icon_width; float top = (float)m_data.sprite_id * scaled_icon_height; float bottom = top + scaled_icon_height; - uvs.left_top = { left, top }; uvs.left_bottom = { left, bottom }; uvs.right_bottom = { right, bottom }; uvs.right_top = { right, top }; - + return uvs; } +#endif // !ENABLE_MODIFIED_TOOLBAR_TEXTURES BackgroundTexture::Metadata::Metadata() : filename("") @@ -624,8 +653,7 @@ std::string GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos, GLC { // NB: mouse_pos is already scaled appropriately - float zoom = (float)parent.get_camera().get_zoom(); - float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + float inv_zoom = (float)parent.get_camera().get_inv_zoom(); float factor = m_layout.scale * inv_zoom; Size cnv_size = parent.get_canvas_size(); @@ -729,8 +757,7 @@ std::string GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos, GLCan { // NB: mouse_pos is already scaled appropriately - float zoom = (float)parent.get_camera().get_zoom(); - float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + float inv_zoom = (float)parent.get_camera().get_inv_zoom(); float factor = m_layout.scale * inv_zoom; Size cnv_size = parent.get_canvas_size(); @@ -846,8 +873,7 @@ int GLToolbar::contains_mouse_horizontal(const Vec2d& mouse_pos, const GLCanvas3 { // NB: mouse_pos is already scaled appropriately - float zoom = (float)parent.get_camera().get_zoom(); - float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + float inv_zoom = (float)parent.get_camera().get_inv_zoom(); float factor = m_layout.scale * inv_zoom; Size cnv_size = parent.get_canvas_size(); @@ -920,8 +946,7 @@ int GLToolbar::contains_mouse_vertical(const Vec2d& mouse_pos, const GLCanvas3D& { // NB: mouse_pos is already scaled appropriately - float zoom = (float)parent.get_camera().get_zoom(); - float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + float inv_zoom = (float)parent.get_camera().get_inv_zoom(); float factor = m_layout.scale * inv_zoom; Size cnv_size = parent.get_canvas_size(); @@ -1073,8 +1098,7 @@ void GLToolbar::render_horizontal(const GLCanvas3D& parent) const int tex_width = m_icons_texture.get_width(); int tex_height = m_icons_texture.get_height(); - float zoom = (float)parent.get_camera().get_zoom(); - float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + float inv_zoom = (float)parent.get_camera().get_inv_zoom(); float factor = inv_zoom * m_layout.scale; float scaled_icons_size = m_layout.icons_size * factor; @@ -1122,8 +1146,7 @@ void GLToolbar::render_vertical(const GLCanvas3D& parent) const int tex_width = m_icons_texture.get_width(); int tex_height = m_icons_texture.get_height(); - float zoom = (float)parent.get_camera().get_zoom(); - float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + float inv_zoom = (float)parent.get_camera().get_inv_zoom(); float factor = inv_zoom * m_layout.scale; float scaled_icons_size = m_layout.icons_size * factor; diff --git a/src/slic3r/GUI/GLToolbar.hpp b/src/slic3r/GUI/GLToolbar.hpp index eb73dcdfd..f53b8efb9 100644 --- a/src/slic3r/GUI/GLToolbar.hpp +++ b/src/slic3r/GUI/GLToolbar.hpp @@ -143,7 +143,9 @@ public: void render(unsigned int tex_id, float left, float right, float bottom, float top, unsigned int tex_width, unsigned int tex_height, unsigned int icon_size) const; private: +#if !ENABLE_MODIFIED_TOOLBAR_TEXTURES GLTexture::Quad_UVs get_uvs(unsigned int tex_width, unsigned int tex_height, unsigned int icon_size) const; +#endif // !ENABLE_MODIFIED_TOOLBAR_TEXTURES void set_visible(bool visible) { m_data.visible = visible; } friend class GLToolbar; @@ -293,9 +295,7 @@ public: bool is_any_item_pressed() const; -#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX unsigned int get_items_count() const { return (unsigned int)m_items.size(); } -#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX int get_item_id(const std::string& name) const; void force_left_action(int item_id, GLCanvas3D& parent) { do_action(GLToolbarItem::Left, item_id, parent, false); } diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 1a4d12d31..f7010a503 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -533,7 +533,9 @@ void Preview::on_combochecklist_features(wxCommandEvent& evt) void Preview::on_checkbox_travel(wxCommandEvent& evt) { m_gcode_preview_data->travel.is_visible = m_checkbox_travel->IsChecked(); - refresh_print(); + m_gcode_preview_data->ranges.feedrate.set_mode(GCodePreviewData::FeedrateKind::TRAVEL, m_gcode_preview_data->travel.is_visible); + // Rather than refresh, reload print so that speed color ranges get recomputed (affected by travel visibility) + reload_print(); } void Preview::on_checkbox_retractions(wxCommandEvent& evt) @@ -564,7 +566,7 @@ void Preview::update_view_type(bool slice_completed) { const DynamicPrintConfig& config = wxGetApp().preset_bundle->project_config; - const wxString& choice = !wxGetApp().plater()->model().custom_gcode_per_print_z.empty() /*&& + const wxString& choice = !wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes.empty() /*&& (wxGetApp().extruders_edited_cnt()==1 || !slice_completed) */? _(L("Color Print")) : config.option("wiping_volumes_matrix")->values.size() > 1 ? @@ -664,8 +666,11 @@ void Preview::update_double_slider(const std::vector& layers_z, bool kee bool snap_to_min = force_sliders_full_range || m_slider->is_lower_at_min(); bool snap_to_max = force_sliders_full_range || m_slider->is_higher_at_max(); - std::vector &ticks_from_model = wxGetApp().plater()->model().custom_gcode_per_print_z; - check_slider_values(ticks_from_model, layers_z); + // Detect and set manipulation mode for double slider + update_double_slider_mode(); + + Model::CustomGCodeInfo &ticks_info_from_model = wxGetApp().plater()->model().custom_gcode_per_print_z; + check_slider_values(ticks_info_from_model.gcodes, layers_z); m_slider->SetSliderValues(layers_z); assert(m_slider->GetMinValue() == 0); @@ -687,12 +692,61 @@ void Preview::update_double_slider(const std::vector& layers_z, bool kee } m_slider->SetSelectionSpan(idx_low, idx_high); - m_slider->SetTicksValues(ticks_from_model); + m_slider->SetTicksValues(ticks_info_from_model); - bool color_print_enable = (wxGetApp().plater()->printer_technology() == ptFFF); + m_slider->EnableTickManipulation(wxGetApp().plater()->printer_technology() == ptFFF); +} - m_slider->EnableTickManipulation(color_print_enable); - m_slider->SetManipulationState(wxGetApp().extruders_edited_cnt()); +void Preview::update_double_slider_mode() +{ + // true -> single-extruder printer profile OR + // multi-extruder printer profile , but whole model is printed by only one extruder + // false -> multi-extruder printer profile , and model is printed by several extruders + bool one_extruder_printed_model = true; + + // extruder used for whole model for multi-extruder printer profile + int only_extruder = -1; + + if (wxGetApp().extruders_edited_cnt() > 1) + { + const ModelObjectPtrs& objects = wxGetApp().plater()->model().objects; + + // check if whole model uses just only one extruder + if (!objects.empty()) + { + const int extruder = objects[0]->config.has("extruder") ? + objects[0]->config.option("extruder")->getInt() : 0; + + auto is_one_extruder_printed_model = [objects, extruder]() + { + for (ModelObject* object : objects) + { + if (object->config.has("extruder") && + object->config.option("extruder")->getInt() != extruder) + return false; + + if (object->volumes.size() > 1) + for (ModelVolume* volume : object->volumes) + if (volume->config.has("extruder") && + volume->config.option("extruder")->getInt() != extruder) + return false; + + for (const auto& range : object->layer_config_ranges) + if (range.second.has("extruder") && + range.second.option("extruder")->getInt() != extruder) + return false; + } + return true; + }; + + if (is_one_extruder_printed_model()) + only_extruder = extruder; + else + one_extruder_printed_model = false; + } + } + + m_slider->SetModeAndOnlyExtruder(one_extruder_printed_model, only_extruder); } void Preview::reset_double_slider() @@ -784,7 +838,7 @@ void Preview::load_print_as_fff(bool keep_z_range) colors.push_back("#808080"); // gray color for pause print or custom G-code if (!gcode_preview_data_valid) - color_print_values = wxGetApp().plater()->model().custom_gcode_per_print_z; + color_print_values = wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes; } else if (gcode_preview_data_valid || (m_gcode_preview_data->extrusion.view_type == GCodePreviewData::Extrusion::Tool) ) { diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index 92ec15b22..ae93e2ff0 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -157,8 +157,9 @@ private: void create_double_slider(); void check_slider_values(std::vector &ticks_from_model, const std::vector &layers_z); - void update_double_slider(const std::vector& layers_z, bool keep_z_range = false); void reset_double_slider(); + void update_double_slider(const std::vector& layers_z, bool keep_z_range = false); + void update_double_slider_mode(); // update DoubleSlider after keyDown in canvas void update_double_slider_from_canvas(wxKeyEvent& event); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 925727e76..6be108c85 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -48,12 +48,10 @@ std::string GLGizmoMove3D::on_get_name() const return (_(L("Move")) + " [M]").ToUTF8().data(); } -#if ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE bool GLGizmoMove3D::on_is_activable() const { return !m_parent.get_selection().is_empty(); } -#endif // ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE void GLGizmoMove3D::on_start_dragging() { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp index 19958615e..b2367ac68 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp @@ -33,9 +33,7 @@ public: protected: virtual bool on_init(); virtual std::string on_get_name() const; -#if ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE virtual bool on_is_activable() const; -#endif // ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE virtual void on_start_dragging(); virtual void on_stop_dragging(); virtual void on_update(const UpdateData& data); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 5d3b6f365..9a0d75033 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -449,12 +449,10 @@ std::string GLGizmoRotate3D::on_get_name() const return (_(L("Rotate")) + " [R]").ToUTF8().data(); } -#if ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE bool GLGizmoRotate3D::on_is_activable() const { return !m_parent.get_selection().is_empty(); } -#endif // ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE void GLGizmoRotate3D::on_start_dragging() { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index 930a76e2e..6e7bf1a09 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -104,9 +104,7 @@ protected: if (id < 3) m_gizmos[id].disable_grabber(0); } -#if ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE virtual bool on_is_activable() const; -#endif // ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE virtual void on_start_dragging(); virtual void on_stop_dragging(); virtual void on_update(const UpdateData& data) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index fa49b06ce..b4972e6fa 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -49,12 +49,8 @@ std::string GLGizmoScale3D::on_get_name() const bool GLGizmoScale3D::on_is_activable() const { -#if ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE const Selection& selection = m_parent.get_selection(); return !selection.is_empty() && !selection.is_wipe_tower(); -#else - return !m_parent.get_selection().is_wipe_tower(); -#endif // ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE } void GLGizmoScale3D::on_start_dragging() diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 8300d7479..ccc6369e4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -567,7 +567,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) update_data(); wxGetApp().obj_manipul()->set_dirty(); - // Let the platter know that the dragging finished, so a delayed refresh + // Let the plater know that the dragging finished, so a delayed refresh // of the scene with the background processing data should be performed. m_parent.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); // updates camera target constraints @@ -861,13 +861,19 @@ void GLGizmosManager::render_background(float left, float top, float right, floa void GLGizmosManager::do_render_overlay() const { +#if ENABLE_MODIFIED_TOOLBAR_TEXTURES + std::vector selectable_idxs = get_selectable_idxs(); + if (selectable_idxs.empty()) + return; +#else if (m_gizmos.empty()) return; +#endif // ENABLE_MODIFIED_TOOLBAR_TEXTURES float cnv_w = (float)m_parent.get_canvas_size().get_width(); float cnv_h = (float)m_parent.get_canvas_size().get_height(); float zoom = (float)m_parent.get_camera().get_zoom(); - float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f; + float inv_zoom = (float)m_parent.get_camera().get_inv_zoom(); float height = get_scaled_total_height(); float width = get_scaled_total_width(); @@ -893,23 +899,42 @@ void GLGizmosManager::do_render_overlay() const unsigned int icons_texture_id = m_icons_texture.get_id(); int tex_width = m_icons_texture.get_width(); int tex_height = m_icons_texture.get_height(); - float inv_tex_width = (tex_width != 0) ? 1.0f / tex_width : 0.0f; - float inv_tex_height = (tex_height != 0) ? 1.0f / tex_height : 0.0f; +#if ENABLE_MODIFIED_TOOLBAR_TEXTURES + if ((icons_texture_id == 0) || (tex_width <= 1) || (tex_height <= 1)) + return; + + float du = (float)(tex_width - 1) / (4.0f * (float)tex_width); // 4 is the number of possible states if the icons + float dv = (float)(tex_height - 1) / (float)(m_gizmos.size() * tex_height); + + // tiles in the texture are spaced by 1 pixel + float u_offset = 1.0f / (float)tex_width; + float v_offset = 1.0f / (float)tex_height; +#else if ((icons_texture_id == 0) || (tex_width <= 0) || (tex_height <= 0)) return; + float inv_tex_width = (tex_width != 0) ? 1.0f / tex_width : 0.0f; + float inv_tex_height = (tex_height != 0) ? 1.0f / tex_height : 0.0f; +#endif // ENABLE_MODIFIED_TOOLBAR_TEXTURES + +#if ENABLE_MODIFIED_TOOLBAR_TEXTURES + for (size_t idx : selectable_idxs) +#else for (size_t idx : get_selectable_idxs()) +#endif // ENABLE_MODIFIED_TOOLBAR_TEXTURES { GLGizmoBase* gizmo = m_gizmos[idx].get(); unsigned int sprite_id = gizmo->get_sprite_id(); -#if ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE int icon_idx = (m_current == idx) ? 2 : ((m_hover == idx) ? 1 : (gizmo->is_activable()? 0 : 3)); -#else - int icon_idx = m_current == idx ? 2 : (m_hover == idx ? 1 : 0); -#endif // ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE +#if ENABLE_MODIFIED_TOOLBAR_TEXTURES + float v_top = v_offset + sprite_id * dv; + float u_left = u_offset + icon_idx * du; + float v_bottom = v_top + dv - v_offset; + float u_right = u_left + du - u_offset; +#else float u_icon_size = icons_size * inv_tex_width; float v_icon_size = icons_size * inv_tex_height; @@ -917,6 +942,7 @@ void GLGizmosManager::do_render_overlay() const float u_left = icon_idx * u_icon_size; float v_bottom = v_top + v_icon_size; float u_right = u_left + u_icon_size; +#endif // ENABLE_MODIFIED_TOOLBAR_TEXTURES GLTexture::render_sub_texture(icons_texture_id, zoomed_top_x, zoomed_top_x + zoomed_icons_size, zoomed_top_y - zoomed_icons_size, zoomed_top_y, { { u_left, v_bottom }, { u_right, v_bottom }, { u_right, v_top }, { u_left, v_top } }); if (idx == m_current) { @@ -960,9 +986,7 @@ bool GLGizmosManager::generate_icons_texture() const states.push_back(std::make_pair(1, false)); // Activable states.push_back(std::make_pair(0, false)); // Hovered states.push_back(std::make_pair(0, true)); // Selected -#if ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE states.push_back(std::make_pair(2, false)); // Disabled -#endif // ENABLE_GIZMO_ICONS_NON_ACTIVABLE_STATE unsigned int sprite_size_px = (unsigned int)m_layout.scaled_icons_size(); // // force even size diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 4ae03792b..d1466ef0b 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -125,7 +125,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S _3DScene::remove_all_canvases(); // Slic3r::GUI::deregister_on_request_update_callback(); - // set to null tabs and a platter + // set to null tabs and a plater // to avoid any manipulations with them from App->wxEVT_IDLE after of the mainframe closing wxGetApp().tabs_list.clear(); wxGetApp().plater_ = nullptr; @@ -1007,7 +1007,7 @@ void MainFrame::load_configbundle(wxString file/* = wxEmptyString, const bool re } // Load a provied DynamicConfig into the Print / Filament / Printer tabs, thus modifying the active preset. -// Also update the platter with the new presets. +// Also update the plater with the new presets. void MainFrame::load_config(const DynamicPrintConfig& config) { PrinterTechnology printer_technology = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology(); diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 1a7568092..3d4818eaf 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -125,7 +125,7 @@ public: void load_config(const DynamicPrintConfig& config); void select_tab(size_t tab) const; void select_view(const std::string& direction); - // Propagate changed configuration from the Tab to the Platter and save changes to the AppConfig + // Propagate changed configuration from the Tab to the Plater and save changes to the AppConfig void on_config_changed(DynamicPrintConfig* cfg) const ; void add_to_recent_projects(const wxString& filename); diff --git a/src/slic3r/GUI/Mouse3DController.cpp b/src/slic3r/GUI/Mouse3DController.cpp index 707a1d143..e28336913 100644 --- a/src/slic3r/GUI/Mouse3DController.cpp +++ b/src/slic3r/GUI/Mouse3DController.cpp @@ -5,9 +5,7 @@ #include "GUI_App.hpp" #include "PresetBundle.hpp" #include "AppConfig.hpp" -#if ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG #include "GLCanvas3D.hpp" -#endif // ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG #include @@ -156,7 +154,8 @@ bool Mouse3DController::State::apply(Camera& camera) { const Vec3d& translation = m_translation.queue.front(); #if ENABLE_3DCONNEXION_Y_AS_ZOOM - camera.set_target(camera.get_target() + m_translation_params.scale * (translation(0) * camera.get_dir_right() + translation(2) * camera.get_dir_up())); + double zoom_factor = camera.min_zoom() / camera.get_zoom(); + camera.set_target(camera.get_target() + zoom_factor * m_translation_params.scale * (translation(0) * camera.get_dir_right() + translation(2) * camera.get_dir_up())); if (translation(1) != 0.0) camera.update_zoom(m_zoom_params.scale * translation(1) / std::abs(translation(1))); #else @@ -168,12 +167,17 @@ bool Mouse3DController::State::apply(Camera& camera) if (has_rotation()) { +#if ENABLE_6DOF_CAMERA + Vec3d rotation = (m_rotation_params.scale * m_rotation.queue.front()).cast(); + camera.rotate_local_around_target(Vec3d(Geometry::deg2rad(rotation(0)), Geometry::deg2rad(-rotation(2)), Geometry::deg2rad(-rotation(1)))); +#else const Vec3f& rotation = m_rotation.queue.front(); float theta = m_rotation_params.scale * rotation(0); float phi = m_rotation_params.scale * rotation(2); float sign = camera.inverted_phi ? -1.0f : 1.0f; camera.phi += sign * phi; camera.set_theta(camera.get_theta() + theta, wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA); +#endif // ENABLE_6DOF_CAMERA m_rotation.queue.pop(); ret = true; } @@ -200,9 +204,7 @@ Mouse3DController::Mouse3DController() , m_device_str("") , m_running(false) , m_show_settings_dialog(false) -#if ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG , m_settings_dialog_closed_by_user(false) -#endif // ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG { m_last_time = std::chrono::high_resolution_clock::now(); } @@ -247,9 +249,7 @@ bool Mouse3DController::apply(Camera& camera) disconnect_device(); // hides the settings dialog if the user un-plug the device m_show_settings_dialog = false; -#if ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG m_settings_dialog_closed_by_user = false; -#endif // ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG } // check if the user plugged the device @@ -259,16 +259,11 @@ bool Mouse3DController::apply(Camera& camera) return is_device_connected() ? m_state.apply(camera) : false; } -#if ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const -#else -void Mouse3DController::render_settings_dialog(unsigned int canvas_width, unsigned int canvas_height) const -#endif // ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG { if (!m_running || !m_show_settings_dialog) return; -#if ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG // when the user clicks on [X] or [Close] button we need to trigger // an extra frame to let the dialog disappear if (m_settings_dialog_closed_by_user) @@ -280,16 +275,10 @@ void Mouse3DController::render_settings_dialog(unsigned int canvas_width, unsign } Size cnv_size = canvas.get_canvas_size(); -#endif // ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG ImGuiWrapper& imgui = *wxGetApp().imgui(); -#if ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG imgui.set_next_window_pos(0.5f * (float)cnv_size.get_width(), 0.5f * (float)cnv_size.get_height(), ImGuiCond_Always, 0.5f, 0.5f); -#else - imgui.set_next_window_pos(0.5f * (float)canvas_width, 0.5f * (float)canvas_height, ImGuiCond_Always, 0.5f, 0.5f); -#endif // ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG -#if ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG static ImVec2 last_win_size(0.0f, 0.0f); bool shown = true; if (imgui.begin(_(L("3Dconnexion settings")), &shown, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse)) @@ -304,9 +293,6 @@ void Mouse3DController::render_settings_dialog(unsigned int canvas_width, unsign last_win_size = win_size; canvas.request_extra_frame(); } -#else - imgui.begin(_(L("3Dconnexion settings")), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse); -#endif // ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG const ImVec4& color = ImGui::GetStyleColorVec4(ImGuiCol_Separator); ImGui::PushStyleColor(ImGuiCol_Text, color); @@ -321,16 +307,16 @@ void Mouse3DController::render_settings_dialog(unsigned int canvas_width, unsign ImGui::PopStyleColor(); float translation_scale = (float)m_state.get_translation_scale() / State::DefaultTranslationScale; - if (imgui.slider_float(_(L("Translation")) + "##1", &translation_scale, 0.2f, 5.0f, "%.1f")) + if (imgui.slider_float(_(L("Translation")) + "##1", &translation_scale, 0.1f, 10.0f, "%.1f")) m_state.set_translation_scale(State::DefaultTranslationScale * (double)translation_scale); float rotation_scale = m_state.get_rotation_scale() / State::DefaultRotationScale; - if (imgui.slider_float(_(L("Rotation")) + "##1", &rotation_scale, 0.2f, 5.0f, "%.1f")) + if (imgui.slider_float(_(L("Rotation")) + "##1", &rotation_scale, 0.1f, 10.0f, "%.1f")) m_state.set_rotation_scale(State::DefaultRotationScale * rotation_scale); #if ENABLE_3DCONNEXION_Y_AS_ZOOM float zoom_scale = m_state.get_zoom_scale() / State::DefaultZoomScale; - if (imgui.slider_float(_(L("Zoom")), &zoom_scale, 0.2f, 5.0f, "%.1f")) + if (imgui.slider_float(_(L("Zoom")), &zoom_scale, 0.1f, 10.0f, "%.1f")) m_state.set_zoom_scale(State::DefaultZoomScale * zoom_scale); #endif // ENABLE_3DCONNEXION_Y_AS_ZOOM @@ -389,7 +375,6 @@ void Mouse3DController::render_settings_dialog(unsigned int canvas_width, unsign Vec3f target = wxGetApp().plater()->get_camera().get_target().cast(); ImGui::InputFloat3("Target", target.data(), "%.3f", ImGuiInputTextFlags_ReadOnly); #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT -#if ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG ImGui::Separator(); if (imgui.button(_(L("Close")))) @@ -406,7 +391,6 @@ void Mouse3DController::render_settings_dialog(unsigned int canvas_width, unsign canvas.set_as_dirty(); } } -#endif // ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG imgui.end(); } @@ -631,13 +615,16 @@ bool Mouse3DController::connect_device() if (m_device != nullptr) { - std::vector manufacturer(1024, 0); - hid_get_manufacturer_string(m_device, manufacturer.data(), 1024); - m_device_str = boost::nowide::narrow(manufacturer.data()); + wchar_t buffer[1024]; + hid_get_manufacturer_string(m_device, buffer, 1024); + m_device_str = boost::nowide::narrow(buffer); + // #3479 seems to show that sometimes an extra whitespace is added, so we remove it + boost::algorithm::trim(m_device_str); - std::vector product(1024, 0); - hid_get_product_string(m_device, product.data(), 1024); - m_device_str += "/" + boost::nowide::narrow(product.data()); + hid_get_product_string(m_device, buffer, 1024); + m_device_str += "/" + boost::nowide::narrow(buffer); + // #3479 seems to show that sometimes an extra whitespace is added, so we remove it + boost::algorithm::trim(m_device_str); BOOST_LOG_TRIVIAL(info) << "Connected 3DConnexion device:"; BOOST_LOG_TRIVIAL(info) << "Manufacturer/product: " << m_device_str; @@ -647,12 +634,12 @@ bool Mouse3DController::connect_device() BOOST_LOG_TRIVIAL(info) << "Path................: '" << path << "'"; // get device parameters from the config, if present - double translation_speed = 1.0; - float rotation_speed = 1.0; + double translation_speed = 4.0; + float rotation_speed = 4.0; double translation_deadzone = State::DefaultTranslationDeadzone; float rotation_deadzone = State::DefaultRotationDeadzone; #if ENABLE_3DCONNEXION_Y_AS_ZOOM - double zoom_speed = 1.0; + double zoom_speed = 2.0; #endif // ENABLE_3DCONNEXION_Y_AS_ZOOM wxGetApp().app_config->get_mouse_device_translation_speed(m_device_str, translation_speed); wxGetApp().app_config->get_mouse_device_translation_deadzone(m_device_str, translation_deadzone); @@ -662,12 +649,12 @@ bool Mouse3DController::connect_device() wxGetApp().app_config->get_mouse_device_zoom_speed(m_device_str, zoom_speed); #endif // ENABLE_3DCONNEXION_Y_AS_ZOOM // clamp to valid values - m_state.set_translation_scale(State::DefaultTranslationScale * std::clamp(translation_speed, 0.2, 5.0)); + m_state.set_translation_scale(State::DefaultTranslationScale * std::clamp(translation_speed, 0.1, 10.0)); m_state.set_translation_deadzone(std::clamp(translation_deadzone, 0.0, State::MaxTranslationDeadzone)); - m_state.set_rotation_scale(State::DefaultRotationScale * std::clamp(rotation_speed, 0.2f, 5.0f)); + m_state.set_rotation_scale(State::DefaultRotationScale * std::clamp(rotation_speed, 0.1f, 10.0f)); m_state.set_rotation_deadzone(std::clamp(rotation_deadzone, 0.0f, State::MaxRotationDeadzone)); #if ENABLE_3DCONNEXION_Y_AS_ZOOM - m_state.set_zoom_scale(State::DefaultZoomScale * std::clamp(zoom_speed, 0.2, 5.0)); + m_state.set_zoom_scale(State::DefaultZoomScale * std::clamp(zoom_speed, 0.1, 10.0)); #endif // ENABLE_3DCONNEXION_Y_AS_ZOOM } #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT @@ -874,9 +861,15 @@ bool Mouse3DController::handle_packet_translation(const DataPacket& packet) bool Mouse3DController::handle_packet_rotation(const DataPacket& packet, unsigned int first_byte) { double deadzone = (double)m_state.get_rotation_deadzone(); +#if ENABLE_6DOF_CAMERA + Vec3f rotation((float)convert_input(packet[first_byte + 0], packet[first_byte + 1], deadzone), + (float)convert_input(packet[first_byte + 2], packet[first_byte + 3], deadzone), + (float)convert_input(packet[first_byte + 4], packet[first_byte + 5], deadzone)); +#else Vec3f rotation(-(float)convert_input(packet[first_byte + 0], packet[first_byte + 1], deadzone), (float)convert_input(packet[first_byte + 2], packet[first_byte + 3], deadzone), -(float)convert_input(packet[first_byte + 4], packet[first_byte + 5], deadzone)); +#endif // ENABLE_6DOF_CAMERA if (!rotation.isApprox(Vec3f::Zero())) { diff --git a/src/slic3r/GUI/Mouse3DController.hpp b/src/slic3r/GUI/Mouse3DController.hpp index 3598d7df7..f3826f8e2 100644 --- a/src/slic3r/GUI/Mouse3DController.hpp +++ b/src/slic3r/GUI/Mouse3DController.hpp @@ -18,9 +18,7 @@ namespace Slic3r { namespace GUI { struct Camera; -#if ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG class GLCanvas3D; -#endif // ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG class Mouse3DController { @@ -68,7 +66,7 @@ class Mouse3DController CustomParameters m_translation_params; CustomParameters m_rotation_params; #if ENABLE_3DCONNEXION_Y_AS_ZOOM - CustomParameters m_zoom_params; + CustomParameters m_zoom_params; #endif // ENABLE_3DCONNEXION_Y_AS_ZOOM // When the 3Dconnexion driver is running the system gets, by default, mouse wheel events when rotations around the X axis are detected. @@ -143,13 +141,9 @@ class Mouse3DController hid_device* m_device; std::string m_device_str; bool m_running; -#if ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG mutable bool m_show_settings_dialog; // set to true when ther user closes the dialog by clicking on [X] or [Close] buttons mutable bool m_settings_dialog_closed_by_user; -#else - bool m_show_settings_dialog; -#endif // ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG std::chrono::time_point m_last_time; public: @@ -167,11 +161,7 @@ public: bool is_settings_dialog_shown() const { return m_show_settings_dialog; } void show_settings_dialog(bool show) { m_show_settings_dialog = show && is_running(); } -#if ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG void render_settings_dialog(GLCanvas3D& canvas) const; -#else - void render_settings_dialog(unsigned int canvas_width, unsigned int canvas_height) const; -#endif // ENABLE_3DCONNEXION_DEVICES_CLOSE_SETTING_DIALOG private: bool connect_device(); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index f1a9a10f2..fb28f2a5f 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -326,7 +326,7 @@ wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(15 * cfg_new.set_key_value("extruder_colour", colors); wxGetApp().get_tab(Preset::TYPE_PRINTER)->load_config(cfg_new); - wxGetApp().preset_bundle->update_platter_filament_ui(extruder_idx, this); + wxGetApp().preset_bundle->update_plater_filament_ui(extruder_idx, this); wxGetApp().plater()->on_config_change(cfg_new); } }); @@ -952,18 +952,18 @@ void Sidebar::update_all_preset_comboboxes() // Update the print choosers to only contain the compatible presets, update the dirty flags. if (print_tech == ptFFF) - preset_bundle.prints.update_platter_ui(p->combo_print); + preset_bundle.prints.update_plater_ui(p->combo_print); else { - preset_bundle.sla_prints.update_platter_ui(p->combo_sla_print); - preset_bundle.sla_materials.update_platter_ui(p->combo_sla_material); + preset_bundle.sla_prints.update_plater_ui(p->combo_sla_print); + preset_bundle.sla_materials.update_plater_ui(p->combo_sla_material); } // Update the printer choosers, update the dirty flags. - preset_bundle.printers.update_platter_ui(p->combo_printer); + preset_bundle.printers.update_plater_ui(p->combo_printer); // Update the filament choosers to only contain the compatible presets, update the color preview, // update the dirty flags. if (print_tech == ptFFF) { for (size_t i = 0; i < p->combos_filament.size(); ++i) - preset_bundle.update_platter_filament_ui(i, p->combos_filament[i]); + preset_bundle.update_plater_filament_ui(i, p->combos_filament[i]); } } @@ -986,22 +986,22 @@ void Sidebar::update_presets(Preset::Type preset_type) } for (size_t i = 0; i < filament_cnt; i++) { - preset_bundle.update_platter_filament_ui(i, p->combos_filament[i]); + preset_bundle.update_plater_filament_ui(i, p->combos_filament[i]); } break; } case Preset::TYPE_PRINT: - preset_bundle.prints.update_platter_ui(p->combo_print); + preset_bundle.prints.update_plater_ui(p->combo_print); break; case Preset::TYPE_SLA_PRINT: - preset_bundle.sla_prints.update_platter_ui(p->combo_sla_print); + preset_bundle.sla_prints.update_plater_ui(p->combo_sla_print); break; case Preset::TYPE_SLA_MATERIAL: - preset_bundle.sla_materials.update_platter_ui(p->combo_sla_material); + preset_bundle.sla_materials.update_plater_ui(p->combo_sla_material); break; case Preset::TYPE_PRINTER: @@ -1195,9 +1195,9 @@ void Sidebar::update_sliced_info_sizer() { double material_cost = cfg->option("bottle_cost")->getFloat() / cfg->option("bottle_volume")->getFloat(); - str_total_cost = wxString::Format("%.2f", material_cost*(ps.objects_used_material + ps.support_used_material) / 1000); + str_total_cost = wxString::Format("%.3f", material_cost*(ps.objects_used_material + ps.support_used_material) / 1000); } - p->sliced_info->SetTextAndShow(siCost, str_total_cost); + p->sliced_info->SetTextAndShow(siCost, str_total_cost, "Cost"); wxString t_est = std::isnan(ps.estimated_print_time) ? "N/A" : get_time_dhms(float(ps.estimated_print_time)); p->sliced_info->SetTextAndShow(siEstimatedTime, t_est, _(L("Estimated printing time")) + " :"); @@ -1774,9 +1774,7 @@ struct Plater::priv void set_current_canvas_as_dirty(); -#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX bool init_view_toolbar(); -#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX void reset_all_gizmos(); void update_ui_from_settings(); @@ -1924,9 +1922,6 @@ private: bool complit_init_object_menu(); bool complit_init_sla_object_menu(); bool complit_init_part_menu(); -#if !ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX - void init_view_toolbar(); -#endif // !ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX bool can_split() const; bool layers_height_allowed() const; @@ -1995,7 +1990,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) background_process.set_finished_event(EVT_PROCESS_COMPLETED); // Default printer technology for default config. background_process.select_technology(this->printer_technology); - // Register progress callback from the Print class to the Platter. + // Register progress callback from the Print class to the Plater. auto statuscb = [this](const Slic3r::PrintBase::SlicingStatus &status) { wxQueueEvent(this->q, new Slic3r::SlicingStatusEvent(EVT_SLICING_UPDATE, 0, status)); @@ -2060,11 +2055,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D_canvas->Bind(EVT_GLCANVAS_RESETGIZMOS, [this](SimpleEvent&) { reset_all_gizmos(); }); view3D_canvas->Bind(EVT_GLCANVAS_UNDO, [this](SimpleEvent&) { this->undo(); }); view3D_canvas->Bind(EVT_GLCANVAS_REDO, [this](SimpleEvent&) { this->redo(); }); -#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE view3D_canvas->Bind(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE, [this](SimpleEvent&) { this->view3D->get_canvas3d()->reset_layer_height_profile(); }); view3D_canvas->Bind(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, [this](Event& evt) { this->view3D->get_canvas3d()->adaptive_layer_height_profile(evt.data); }); view3D_canvas->Bind(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, [this](HeightProfileSmoothEvent& evt) { this->view3D->get_canvas3d()->smooth_layer_height_profile(evt.data); }); -#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE // 3DScene/Toolbar: view3D_canvas->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this); @@ -2078,9 +2071,6 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D_canvas->Bind(EVT_GLTOOLBAR_SPLIT_OBJECTS, &priv::on_action_split_objects, this); view3D_canvas->Bind(EVT_GLTOOLBAR_SPLIT_VOLUMES, &priv::on_action_split_volumes, this); view3D_canvas->Bind(EVT_GLTOOLBAR_LAYERSEDITING, &priv::on_action_layersediting, this); -#if !ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX - view3D_canvas->Bind(EVT_GLCANVAS_INIT, [this](SimpleEvent&) { init_view_toolbar(); }); -#endif // !ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&) { set_bed_shape(config->option("bed_shape")->values, @@ -2276,7 +2266,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ if (object->volumes.size() > 1) { Slic3r::GUI::show_info(nullptr, - _(L("You can't load SLA project if there is at least one multi-part object on the bed")) + "\n\n" + + _(L("You cannot load SLA project with a multi-part object on the bed")) + "\n\n" + _(L("Please check your object list before preset changing.")), _(L("Attention!"))); return obj_idxs; @@ -2709,7 +2699,7 @@ void Plater::priv::reset() // The hiding of the slicing results, if shown, is not taken care by the background process, so we do it here this->sidebar->show_sliced_info_sizer(false); - model.custom_gcode_per_print_z.clear(); + model.custom_gcode_per_print_z.gcodes.clear(); } void Plater::priv::mirror(Axis axis) @@ -3008,7 +2998,6 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool this->update_print_volume_state(); // Apply new config to the possibly running background task. bool was_running = this->background_process.running(); - this->background_process.set_force_update_print_regions(force_validation); Print::ApplyStatus invalidated = this->background_process.apply(this->q->model(), wxGetApp().preset_bundle->full_config()); // Just redraw the 3D canvas without reloading the scene to consume the update of the layer height profile. @@ -3228,12 +3217,9 @@ void Plater::priv::reload_from_disk() // collects paths of files to load std::vector input_paths; -#if ENABLE_RELOAD_FROM_DISK_MISSING_SELECTION std::vector missing_input_paths; -#endif // ENABLE_RELOAD_FROM_DISK_MISSING_SELECTION for (const SelectedVolume& v : selected_volumes) { -#if ENABLE_RELOAD_FROM_DISK_MISSING_SELECTION const ModelObject* object = model.objects[v.object_idx]; const ModelVolume* volume = object->volumes[v.volume_idx]; @@ -3244,14 +3230,8 @@ void Plater::priv::reload_from_disk() else missing_input_paths.push_back(volume->source.input_file); } -#else - const ModelVolume* volume = model.objects[v.object_idx]->volumes[v.volume_idx]; - if (!volume->source.input_file.empty() && boost::filesystem::exists(volume->source.input_file)) - input_paths.push_back(volume->source.input_file); -#endif // ENABLE_RELOAD_FROM_DISK_MISSING_SELECTION } -#if ENABLE_RELOAD_FROM_DISK_MISSING_SELECTION std::sort(missing_input_paths.begin(), missing_input_paths.end()); missing_input_paths.erase(std::unique(missing_input_paths.begin(), missing_input_paths.end()), missing_input_paths.end()); @@ -3301,7 +3281,6 @@ void Plater::priv::reload_from_disk() return; } } -#endif // ENABLE_RELOAD_FROM_DISK_MISSING_SELECTION std::sort(input_paths.begin(), input_paths.end()); input_paths.erase(std::unique(input_paths.begin(), input_paths.end()), input_paths.end()); @@ -3347,27 +3326,17 @@ void Plater::priv::reload_from_disk() new_volume->config.apply(old_volume->config); new_volume->set_type(old_volume->type()); new_volume->set_material_id(old_volume->material_id()); -#if ENABLE_KEEP_LOADED_VOLUME_TRANSFORM_AS_STAND_ALONE new_volume->set_transformation(old_volume->get_transformation() * old_volume->source.transform); -#else - new_volume->set_transformation(old_volume->get_transformation()); -#endif // ENABLE_KEEP_LOADED_VOLUME_TRANSFORM_AS_STAND_ALONE new_volume->translate(new_volume->get_transformation().get_matrix(true) * (new_volume->source.mesh_offset - old_volume->source.mesh_offset)); new_volume->source.input_file = path; std::swap(old_model_object->volumes[old_v.volume_idx], old_model_object->volumes.back()); old_model_object->delete_volume(old_model_object->volumes.size() - 1); -#if ENABLE_KEEP_LOADED_VOLUME_TRANSFORM_AS_STAND_ALONE old_model_object->ensure_on_bed(); -#endif // ENABLE_KEEP_LOADED_VOLUME_TRANSFORM_AS_STAND_ALONE } } } } -#if !ENABLE_KEEP_LOADED_VOLUME_TRANSFORM_AS_STAND_ALONE - model.adjust_min_z(); -#endif // !ENABLE_KEEP_LOADED_VOLUME_TRANSFORM_AS_STAND_ALONE - // update 3D scene update(); @@ -3490,8 +3459,8 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) // TODO: ? if (preset_type == Preset::TYPE_FILAMENT && sidebar->is_multifilament()) { - // Only update the platter UI for the 2nd and other filaments. - wxGetApp().preset_bundle->update_platter_filament_ui(idx, combo); + // Only update the plater UI for the 2nd and other filaments. + wxGetApp().preset_bundle->update_plater_filament_ui(idx, combo); } else { wxWindowUpdateLocker noUpdates(sidebar->presets_panel()); @@ -3981,17 +3950,11 @@ void Plater::priv::set_current_canvas_as_dirty() preview->set_as_dirty(); } -#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX bool Plater::priv::init_view_toolbar() -#else -void Plater::priv::init_view_toolbar() -#endif //!ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX { -#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX if (view_toolbar.get_items_count() > 0) // already initialized return true; -#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX BackgroundTexture::Metadata background_data; background_data.filename = "toolbar_background.png"; @@ -4001,11 +3964,7 @@ void Plater::priv::init_view_toolbar() background_data.bottom = 16; if (!view_toolbar.init(background_data)) -#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX return false; -#else - return; -#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX view_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Left); view_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Bottom); @@ -4020,11 +3979,7 @@ void Plater::priv::init_view_toolbar() item.sprite_id = 0; item.left.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_3D)); }; if (!view_toolbar.add_item(item)) -#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX return false; -#else - return; -#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX item.name = "Preview"; item.icon_filename = "preview.svg"; @@ -4032,18 +3987,12 @@ void Plater::priv::init_view_toolbar() item.sprite_id = 1; item.left.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_PREVIEW)); }; if (!view_toolbar.add_item(item)) -#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX return false; -#else - return; -#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX view_toolbar.select_item("3D"); view_toolbar.set_enabled(true); -#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX return true; -#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX } bool Plater::priv::can_set_instance_to_object() const @@ -4108,11 +4057,7 @@ bool Plater::priv::can_reload_from_disk() const for (const SelectedVolume& v : selected_volumes) { const ModelVolume* volume = model.objects[v.object_idx]->volumes[v.volume_idx]; -#if ENABLE_RELOAD_FROM_DISK_MISSING_SELECTION if (!volume->source.input_file.empty()) -#else - if (!volume->source.input_file.empty() && boost::filesystem::exists(volume->source.input_file)) -#endif // ENABLE_RELOAD_FROM_DISK_MISSING_SELECTION paths.push_back(volume->source.input_file); } std::sort(paths.begin(), paths.end()); @@ -4895,7 +4840,12 @@ void Plater::export_amf() wxBusyCursor wait; bool export_config = true; DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure(); +#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF + bool full_pathnames = wxGetApp().app_config->get("export_sources_full_pathnames") == "1"; + if (Slic3r::store_amf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, full_pathnames)) { +#else if (Slic3r::store_amf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr)) { +#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF // Success p->statusbar()->set_status_text(wxString::Format(_(L("AMF file exported to %s")), path)); } else { @@ -4924,6 +4874,16 @@ void Plater::export_3mf(const boost::filesystem::path& output_path) DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure(); const std::string path_u8 = into_u8(path); wxBusyCursor wait; +#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF + bool full_pathnames = wxGetApp().app_config->get("export_sources_full_pathnames") == "1"; +#if ENABLE_THUMBNAIL_GENERATOR + ThumbnailData thumbnail_data; + p->generate_thumbnail(thumbnail_data, THUMBNAIL_SIZE_3MF.first, THUMBNAIL_SIZE_3MF.second, false, true, true, true); + if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, full_pathnames, &thumbnail_data)) { +#else + if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, full_pathnames)) { +#endif // ENABLE_THUMBNAIL_GENERATOR +#else #if ENABLE_THUMBNAIL_GENERATOR ThumbnailData thumbnail_data; p->generate_thumbnail(thumbnail_data, THUMBNAIL_SIZE_3MF.first, THUMBNAIL_SIZE_3MF.second, false, true, true, true); @@ -4931,6 +4891,7 @@ void Plater::export_3mf(const boost::filesystem::path& output_path) #else if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr)) { #endif // ENABLE_THUMBNAIL_GENERATOR +#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF // Success p->statusbar()->set_status_text(wxString::Format(_(L("3MF file exported to %s")), path)); p->set_project_filename(path); @@ -5158,7 +5119,7 @@ void Plater::on_extruders_change(size_t num_extruders) choices.push_back(choice); // initialize selection - wxGetApp().preset_bundle->update_platter_filament_ui(i, choice); + wxGetApp().preset_bundle->update_plater_filament_ui(i, choice); ++i; } @@ -5325,9 +5286,9 @@ std::vector Plater::get_extruder_colors_from_plater_config() const std::vector Plater::get_colors_for_color_print() const { std::vector colors = get_extruder_colors_from_plater_config(); - colors.reserve(colors.size() + p->model.custom_gcode_per_print_z.size()); + colors.reserve(colors.size() + p->model.custom_gcode_per_print_z.gcodes.size()); - for (const Model::CustomGCode& code : p->model.custom_gcode_per_print_z) + for (const Model::CustomGCode& code : p->model.custom_gcode_per_print_z.gcodes) if (code.gcode == ColorChangeCode) colors.emplace_back(code.color); @@ -5491,12 +5452,10 @@ void Plater::msw_rescale() GetParent()->Layout(); } -#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX bool Plater::init_view_toolbar() { return p->init_view_toolbar(); } -#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX const Camera& Plater::get_camera() const { diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 774875cd7..591f49378 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -267,9 +267,7 @@ public: void msw_rescale(); -#if ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX bool init_view_toolbar(); -#endif // ENABLE_VIEW_TOOLBAR_BACKGROUND_FIX const Camera& get_camera() const; const Mouse3DController& get_mouse3d_controller() const; diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 827d1f4e0..4fd63fe0e 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -19,16 +19,11 @@ PreferencesDialog::PreferencesDialog(wxWindow* parent) : void PreferencesDialog::build() { auto app_config = get_app_config(); - m_optgroup = std::make_shared(this, _(L("General"))); - m_optgroup->label_width = 40; - m_optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + m_optgroup_general = std::make_shared(this, _(L("General"))); + m_optgroup_general->label_width = 40; + m_optgroup_general->m_on_change = [this](t_config_option_key opt_key, boost::any value) { m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; - - if (opt_key == "use_custom_toolbar_size") { - m_icon_size_sizer->ShowItems(boost::any_cast(value)); - this->layout(); - } - }; + }; // TODO // $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new( @@ -47,7 +42,7 @@ void PreferencesDialog::build() "instead of the one containing the input files."); def.set_default_value(new ConfigOptionBool{ app_config->has("remember_output_path") ? app_config->get("remember_output_path") == "1" : true }); Option option(def, "remember_output_path"); - m_optgroup->append_single_option_line(option); + m_optgroup_general->append_single_option_line(option); def.label = L("Auto-center parts"); def.type = coBool; @@ -55,7 +50,7 @@ void PreferencesDialog::build() "around the print bed center."); def.set_default_value(new ConfigOptionBool{ app_config->get("autocenter") == "1" }); option = Option (def,"autocenter"); - m_optgroup->append_single_option_line(option); + m_optgroup_general->append_single_option_line(option); def.label = L("Background processing"); def.type = coBool; @@ -63,7 +58,7 @@ void PreferencesDialog::build() "as they\'re loaded in order to save time when exporting G-code."); def.set_default_value(new ConfigOptionBool{ app_config->get("background_processing") == "1" }); option = Option (def,"background_processing"); - m_optgroup->append_single_option_line(option); + m_optgroup_general->append_single_option_line(option); // Please keep in sync with ConfigWizard def.label = L("Check for application updates"); @@ -71,7 +66,17 @@ void PreferencesDialog::build() def.tooltip = L("If enabled, PrusaSlicer will check for the new versions of itself online. When a new version becomes available a notification is displayed at the next application startup (never during program usage). This is only a notification mechanisms, no automatic installation is done."); def.set_default_value(new ConfigOptionBool(app_config->get("version_check") == "1")); option = Option (def, "version_check"); - m_optgroup->append_single_option_line(option); + m_optgroup_general->append_single_option_line(option); + +#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF + // Please keep in sync with ConfigWizard + def.label = L("Export sources full pathnames to 3mf and amf"); + def.type = coBool; + def.tooltip = L("If enabled, allows the Reload from disk command to automatically find and load the files when invoked."); + def.set_default_value(new ConfigOptionBool(app_config->get("export_sources_full_pathnames") == "1")); + option = Option(def, "export_sources_full_pathnames"); + m_optgroup_general->append_single_option_line(option); +#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF // Please keep in sync with ConfigWizard def.label = L("Update built-in Presets automatically"); @@ -79,7 +84,7 @@ void PreferencesDialog::build() def.tooltip = L("If enabled, Slic3r downloads updates of built-in system presets in the background. These updates are downloaded into a separate temporary location. When a new preset version becomes available it is offered at application startup."); def.set_default_value(new ConfigOptionBool(app_config->get("preset_update") == "1")); option = Option (def, "preset_update"); - m_optgroup->append_single_option_line(option); + m_optgroup_general->append_single_option_line(option); def.label = L("Suppress \" - default - \" presets"); def.type = coBool; @@ -87,7 +92,7 @@ void PreferencesDialog::build() "selections once there are any other valid presets available."); def.set_default_value(new ConfigOptionBool{ app_config->get("no_defaults") == "1" }); option = Option (def,"no_defaults"); - m_optgroup->append_single_option_line(option); + m_optgroup_general->append_single_option_line(option); def.label = L("Show incompatible print and filament presets"); def.type = coBool; @@ -95,7 +100,7 @@ void PreferencesDialog::build() "even if they are marked as incompatible with the active printer"); def.set_default_value(new ConfigOptionBool{ app_config->get("show_incompatible_presets") == "1" }); option = Option (def,"show_incompatible_presets"); - m_optgroup->append_single_option_line(option); + m_optgroup_general->append_single_option_line(option); #if __APPLE__ def.label = L("Use Retina resolution for the 3D scene"); @@ -104,28 +109,55 @@ void PreferencesDialog::build() "If you are experiencing 3D performance problems, disabling this option may help."); def.set_default_value(new ConfigOptionBool{ app_config->get("use_retina_opengl") == "1" }); option = Option (def, "use_retina_opengl"); - m_optgroup->append_single_option_line(option); + m_optgroup_general->append_single_option_line(option); #endif - def.label = L("Use perspective camera"); - def.type = coBool; - def.tooltip = L("If enabled, use perspective camera. If not enabled, use orthographic camera."); - def.set_default_value(new ConfigOptionBool{ app_config->get("use_perspective_camera") == "1" }); - option = Option(def, "use_perspective_camera"); - m_optgroup->append_single_option_line(option); + m_optgroup_camera = std::make_shared(this, _(L("Camera"))); + m_optgroup_camera->label_width = 40; + m_optgroup_camera->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; + }; + + def.label = L("Use perspective camera"); + def.type = coBool; + def.tooltip = L("If enabled, use perspective camera. If not enabled, use orthographic camera."); + def.set_default_value(new ConfigOptionBool{ app_config->get("use_perspective_camera") == "1" }); + option = Option(def, "use_perspective_camera"); + m_optgroup_camera->append_single_option_line(option); + +#if ENABLE_6DOF_CAMERA + def.label = L("Use free camera"); + def.type = coBool; + def.tooltip = L("If enabled, use free camera. If not enabled, use constrained camera."); + def.set_default_value(new ConfigOptionBool(app_config->get("use_free_camera") == "1")); + option = Option(def, "use_free_camera"); + m_optgroup_camera->append_single_option_line(option); +#endif // ENABLE_6DOF_CAMERA + + m_optgroup_gui = std::make_shared(this, _(L("GUI"))); + m_optgroup_gui->label_width = 40; + m_optgroup_gui->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; + if (opt_key == "use_custom_toolbar_size") { + m_icon_size_sizer->ShowItems(boost::any_cast(value)); + this->layout(); + } + }; def.label = L("Use custom size for toolbar icons"); def.type = coBool; def.tooltip = L("If enabled, you can change size of toolbar icons manually."); def.set_default_value(new ConfigOptionBool{ app_config->get("use_custom_toolbar_size") == "1" }); - option = Option (def,"use_custom_toolbar_size"); - m_optgroup->append_single_option_line(option); + option = Option(def, "use_custom_toolbar_size"); + m_optgroup_gui->append_single_option_line(option); - create_icon_size_slider(); - m_icon_size_sizer->ShowItems(app_config->get("use_custom_toolbar_size") == "1"); + create_icon_size_slider(); + m_icon_size_sizer->ShowItems(app_config->get("use_custom_toolbar_size") == "1"); auto sizer = new wxBoxSizer(wxVERTICAL); - sizer->Add(m_optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10); + sizer->Add(m_optgroup_general->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10); + sizer->Add(m_optgroup_camera->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10); + sizer->Add(m_optgroup_gui->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10); SetFont(wxGetApp().normal_font()); @@ -157,7 +189,9 @@ void PreferencesDialog::accept() void PreferencesDialog::on_dpi_changed(const wxRect &suggested_rect) { - m_optgroup->msw_rescale(); + m_optgroup_general->msw_rescale(); + m_optgroup_camera->msw_rescale(); + m_optgroup_gui->msw_rescale(); msw_buttons_rescale(this, em_unit(), { wxID_OK, wxID_CANCEL }); @@ -182,7 +216,7 @@ void PreferencesDialog::create_icon_size_slider() m_icon_size_sizer = new wxBoxSizer(wxHORIZONTAL); - wxWindow* parent = m_optgroup->ctrl_parent(); + wxWindow* parent = m_optgroup_gui->ctrl_parent(); if (isOSX) // For correct rendering of the slider and value label under OSX @@ -230,8 +264,9 @@ void PreferencesDialog::create_icon_size_slider() win->SetBackgroundStyle(wxBG_STYLE_PAINT); } - m_optgroup->sizer->Add(m_icon_size_sizer, 0, wxEXPAND | wxALL, em); + m_optgroup_gui->sizer->Add(m_icon_size_sizer, 0, wxEXPAND | wxALL, em); } + } // GUI } // Slic3r \ No newline at end of file diff --git a/src/slic3r/GUI/Preferences.hpp b/src/slic3r/GUI/Preferences.hpp index 9fffe6a7f..35bf2b8c5 100644 --- a/src/slic3r/GUI/Preferences.hpp +++ b/src/slic3r/GUI/Preferences.hpp @@ -15,8 +15,10 @@ class ConfigOptionsGroup; class PreferencesDialog : public DPIDialog { std::map m_values; - std::shared_ptr m_optgroup; - wxSizer* m_icon_size_sizer; + std::shared_ptr m_optgroup_general; + std::shared_ptr m_optgroup_camera; + std::shared_ptr m_optgroup_gui; + wxSizer* m_icon_size_sizer; bool isOSX {false}; public: PreferencesDialog(wxWindow* parent); diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index 9f64c67ac..b0e1f066b 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -184,6 +184,16 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem } else { BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: Malformed variants field: `%2%`") % id % variants_field; } + auto default_materials_field = section.second.get("default_materials", ""); + if (default_materials_field.empty()) + default_materials_field = section.second.get("default_filaments", ""); + if (Slic3r::unescape_strings_cstyle(default_materials_field, model.default_materials)) { + Slic3r::sort_remove_duplicates(model.default_materials); + } else { + BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: Malformed default_materials field: `%2%`") % id % default_materials_field; + } + model.bed_model = section.second.get("bed_model", ""); + model.bed_texture = section.second.get("bed_texture", ""); if (! model.id.empty() && ! model.variants.empty()) res.models.push_back(std::move(model)); } @@ -1020,7 +1030,7 @@ size_t PresetCollection::update_compatible_internal(const PresetWithVendorProfil // Update the wxChoice UI component from this list of presets. // Hide the -void PresetCollection::update_platter_ui(GUI::PresetComboBox *ui) +void PresetCollection::update_plater_ui(GUI::PresetComboBox *ui) { if (ui == nullptr) return; @@ -1504,4 +1514,20 @@ const Preset* PrinterPresetCollection::find_by_model_id(const std::string &model return it != cend() ? &*it : nullptr; } +namespace PresetUtils { + const VendorProfile::PrinterModel* system_printer_model(const Preset &preset) + { + const VendorProfile::PrinterModel *out = nullptr; + if (preset.vendor != nullptr) { + auto *printer_model = preset.config.opt("printer_model"); + if (printer_model != nullptr && ! printer_model->value.empty()) { + auto it = std::find_if(preset.vendor->models.begin(), preset.vendor->models.end(), [printer_model](const VendorProfile::PrinterModel &pm) { return pm.id == printer_model->value; }); + if (it != preset.vendor->models.end()) + out = &(*it); + } + } + return out; + } +} // namespace PresetUtils + } // namespace Slic3r diff --git a/src/slic3r/GUI/Preset.hpp b/src/slic3r/GUI/Preset.hpp index 547814559..742d3e01d 100644 --- a/src/slic3r/GUI/Preset.hpp +++ b/src/slic3r/GUI/Preset.hpp @@ -61,6 +61,10 @@ public: PrinterTechnology technology; std::string family; std::vector variants; + std::vector default_materials; + // Vendor & Printer Model specific print bed model & texture. + std::string bed_model; + std::string bed_texture; PrinterVariant* variant(const std::string &name) { for (auto &v : this->variants) @@ -380,7 +384,8 @@ public: size_t n = this->m_presets.size(); size_t i_compatible = n; for (; i < n; ++ i) - if (m_presets[i].is_compatible) { + // Since we use the filament selection from Wizard, it's needed to control the preset visibility too + if (m_presets[i].is_compatible && m_presets[i].is_visible) { if (prefered_condition(m_presets[i].name)) return i; if (i_compatible == n) @@ -437,7 +442,7 @@ public: // Update the choice UI from the list of presets. // Only the compatible presets are shown. // If an incompatible preset is selected, it is shown as well. - void update_platter_ui(GUI::PresetComboBox *ui); + void update_plater_ui(GUI::PresetComboBox *ui); // Update a dirty floag of the current preset, update the labels of the UI component accordingly. // Return true if the dirty flag changed. @@ -526,7 +531,7 @@ private: // Is the "- default -" preset suppressed? bool m_default_suppressed = true; size_t m_num_default_presets = 0; - // Compatible & incompatible marks, to be placed at the wxBitmapComboBox items of a Platter. + // Compatible & incompatible marks, to be placed at the wxBitmapComboBox items of a Plater. // These bitmaps are not owned by PresetCollection, but by a PresetBundle. const wxBitmap *m_bitmap_compatible = nullptr; const wxBitmap *m_bitmap_incompatible = nullptr; @@ -559,6 +564,11 @@ public: const Preset* find_by_model_id(const std::string &model_id) const; }; +namespace PresetUtils { + // PrinterModel of a system profile, from which this preset is derived, or null if it is not derived from a system profile. + const VendorProfile::PrinterModel* system_printer_model(const Preset &preset); +} // namespace PresetUtils + } // namespace Slic3r #endif /* slic3r_Preset_hpp_ */ diff --git a/src/slic3r/GUI/PresetBundle.cpp b/src/slic3r/GUI/PresetBundle.cpp index 219d7b89d..bb08ce2d0 100644 --- a/src/slic3r/GUI/PresetBundle.cpp +++ b/src/slic3r/GUI/PresetBundle.cpp @@ -877,7 +877,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool // 4) Load the project config values (the per extruder wipe matrix etc). this->project_config.apply_only(config, s_project_options); - update_custom_gcode_per_print_z_from_config(GUI::wxGetApp().plater()->model().custom_gcode_per_print_z, &this->project_config); + update_custom_gcode_per_print_z_from_config(GUI::wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes, &this->project_config); break; } @@ -1572,7 +1572,7 @@ void PresetBundle::load_default_preset_bitmaps(wxWindow *window) this->load_compatible_bitmaps(window); } -void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, GUI::PresetComboBox *ui) +void PresetBundle::update_plater_filament_ui(unsigned int idx_extruder, GUI::PresetComboBox *ui) { if (ui == nullptr || this->printers.get_edited_preset().printer_technology() == ptSLA || this->filament_presets.size() <= idx_extruder ) diff --git a/src/slic3r/GUI/PresetBundle.hpp b/src/slic3r/GUI/PresetBundle.hpp index 5205042c5..ff27d0023 100644 --- a/src/slic3r/GUI/PresetBundle.hpp +++ b/src/slic3r/GUI/PresetBundle.hpp @@ -109,8 +109,8 @@ public: // Export a config bundle file containing all the presets and the names of the active presets. void export_configbundle(const std::string &path, bool export_system_settings = false); - // Update a filament selection combo box on the platter for an idx_extruder. - void update_platter_filament_ui(unsigned int idx_extruder, GUI::PresetComboBox *ui); + // Update a filament selection combo box on the plater for an idx_extruder. + void update_plater_filament_ui(unsigned int idx_extruder, GUI::PresetComboBox *ui); // Enable / disable the "- default -" preset. void set_default_suppressed(bool default_suppressed); diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 13be48289..224f5007d 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -2004,7 +2004,11 @@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) co const float max_y = box.max(1) + Margin; // view dependend order of rendering to keep correct transparency +#if ENABLE_6DOF_CAMERA + bool camera_on_top = wxGetApp().plater()->get_camera().is_looking_downward(); +#else bool camera_on_top = wxGetApp().plater()->get_camera().get_theta() <= 90.0f; +#endif // ENABLE_6DOF_CAMERA float z1 = camera_on_top ? min_z : max_z; float z2 = camera_on_top ? max_z : min_z; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index c8239e0b7..eef304e58 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -905,7 +905,7 @@ void Tab::update_wiping_button_visibility() { } -// Call a callback to update the selection of presets on the platter: +// Call a callback to update the selection of presets on the plater: // To update the content of the selection boxes, // to update the filament colors of the selection boxes, // to update the "dirty" flags of the selection boxes, @@ -2825,7 +2825,7 @@ void Tab::select_preset(std::string preset_name, bool delete_current) if (canceled) { update_tab_ui(); // Trigger the on_presets_changed event so that we also restore the previous value in the plater selector, - // if this action was initiated from the platter. + // if this action was initiated from the plater. on_presets_changed(); } else { if (current_dirty) @@ -3053,7 +3053,7 @@ void Tab::save_preset(std::string name /*= ""*/) m_preset_bundle->update_compatible(false); // Add the new item into the UI component, remove dirty flags and activate the saved item. update_tab_ui(); - // Update the selection boxes at the platter. + // Update the selection boxes at the plater. on_presets_changed(); // If current profile is saved, "delete preset" button have to be enabled m_btn_delete_preset->Enable(true); @@ -3444,27 +3444,25 @@ void TabSLAMaterial::build() DynamicPrintConfig new_conf = *m_config; if (opt_key == "bottle_volume") { - double new_bottle_weight = boost::any_cast(value)/(new_conf.option("material_density")->getFloat() * 1000); + double new_bottle_weight = boost::any_cast(value)*(new_conf.option("material_density")->getFloat() / 1000); new_conf.set_key_value("bottle_weight", new ConfigOptionFloat(new_bottle_weight)); } if (opt_key == "bottle_weight") { - double new_bottle_volume = boost::any_cast(value)*(new_conf.option("material_density")->getFloat() * 1000); + double new_bottle_volume = boost::any_cast(value)/new_conf.option("material_density")->getFloat() * 1000; new_conf.set_key_value("bottle_volume", new ConfigOptionFloat(new_bottle_volume)); } if (opt_key == "material_density") { - double new_bottle_volume = new_conf.option("bottle_weight")->getFloat() * boost::any_cast(value) * 1000; + double new_bottle_volume = new_conf.option("bottle_weight")->getFloat() / boost::any_cast(value) * 1000; new_conf.set_key_value("bottle_volume", new ConfigOptionFloat(new_bottle_volume)); } load_config(new_conf); update_dirty(); - on_value_change(opt_key, value); - if (opt_key == "bottle_volume" || opt_key == "bottle_cost") { - wxGetApp().sidebar().update_sliced_info_sizer(); - wxGetApp().sidebar().Layout(); - } + // Change of any from those options influences for an update of "Sliced Info" + wxGetApp().sidebar().update_sliced_info_sizer(); + wxGetApp().sidebar().Layout(); }; optgroup = page->new_optgroup(_(L("Layers"))); diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index ee6937652..4345f196c 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -231,7 +231,7 @@ public: // Counter for the updating (because of an update() function can have a recursive behavior): // 1. increase value from the very beginning of an update() function // 2. decrease value at the end of an update() function - // 3. propagate changed configuration to the Platter when (m_update_cnt == 0) only + // 3. propagate changed configuration to the Plater when (m_update_cnt == 0) only int m_update_cnt = 0; public: diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 7b36207f5..8f43f6c30 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -5,6 +5,7 @@ #include "libslic3r/Utils.hpp" #include "libslic3r/Model.hpp" +#include "libslic3r/Print.hpp" #include #include @@ -13,9 +14,7 @@ #include #include -#include #include -#include #include "BitmapCache.hpp" #include "GUI.hpp" @@ -426,28 +425,6 @@ static float get_svg_scale_factor(wxWindow *win) #endif } -// in the Dark mode of any platform, we should draw icons in respect to OS background -static std::string icon_name_respected_to_mode(const std::string& bmp_name_in) -{ -#ifdef __WXMSW__ - const std::string folder = "white\\"; -#else - const std::string folder = "white/"; -#endif - std::string bmp_name; - if (Slic3r::GUI::wxGetApp().dark_mode()) { - bmp_name = folder + bmp_name_in; - boost::replace_last(bmp_name, ".png", ""); - if (! boost::filesystem::exists(Slic3r::var(bmp_name + ".svg"))) - bmp_name.clear(); - } - if (bmp_name.empty()) { - bmp_name = bmp_name_in; - boost::replace_last(bmp_name, ".png", ""); - } - return bmp_name; -} - // If an icon has horizontal orientation (width > height) call this function with is_horizontal = true wxBitmap create_scaled_bitmap(wxWindow *win, const std::string& bmp_name_in, const int px_cnt/* = 16*/, const bool is_horizontal /* = false*/, const bool grayscale/* = false*/) @@ -474,13 +451,13 @@ wxBitmap create_scaled_bitmap(wxWindow *win, const std::string& bmp_name_in, scale_base = (unsigned int)(em_unit(win) * px_cnt * 0.1f + 0.5f); -// std::string bmp_name = bmp_name_in; -// boost::replace_last(bmp_name, ".png", ""); + std::string bmp_name = bmp_name_in; + boost::replace_last(bmp_name, ".png", ""); - std::string bmp_name = icon_name_respected_to_mode(bmp_name_in); +// std::string bmp_name = icon_name_respected_to_mode(bmp_name_in); // Try loading an SVG first, then PNG if SVG is not found: - wxBitmap *bmp = cache.load_svg(bmp_name, width, height, scale_factor, grayscale); + wxBitmap *bmp = cache.load_svg(bmp_name, width, height, scale_factor, grayscale, Slic3r::GUI::wxGetApp().dark_mode()); if (bmp == nullptr) { bmp = cache.load_png(bmp_name, width, height, grayscale); } @@ -2348,7 +2325,7 @@ DoubleSlider::DoubleSlider( wxWindow *parent, m_cog_icon_dim = int((float)m_bmp_cog.bmp().GetSize().x / scale_factor); m_selection = ssUndef; - m_pause_print_msg = _utf8(L("Place bearings in slots and resume")); + m_ticks.set_pause_print_msg(_utf8(L("Place bearings in slots and resume"))); // slider events Bind(wxEVT_PAINT, &DoubleSlider::OnPaint, this); @@ -2555,41 +2532,50 @@ double DoubleSlider::get_double_value(const SelectedSlider& selection) } using t_custom_code = Slic3r::Model::CustomGCode; -std::vector DoubleSlider::GetTicksValues() const +Slic3r::Model::CustomGCodeInfo DoubleSlider::GetTicksValues() const { - std::vector values; + Slic3r::Model::CustomGCodeInfo custom_gcode_per_print_z; + std::vector& values = custom_gcode_per_print_z.gcodes; const int val_size = m_values.size(); if (!m_values.empty()) - for (const TICK_CODE& tick : m_ticks_) { + for (const TICK_CODE& tick : m_ticks.ticks) { if (tick.tick > val_size) break; values.emplace_back(t_custom_code{m_values[tick.tick], tick.gcode, tick.extruder, tick.color}); } - return values; + custom_gcode_per_print_z.mode = m_force_mode_apply ? m_mode : m_ticks.mode; + + return custom_gcode_per_print_z; } -void DoubleSlider::SetTicksValues(const std::vector& heights) +void DoubleSlider::SetTicksValues(const Slic3r::Model::CustomGCodeInfo& custom_gcode_per_print_z) { if (m_values.empty()) + { + m_ticks.mode = m_mode; return; + } - const bool was_empty = m_ticks_.empty(); + const bool was_empty = m_ticks.empty(); - m_ticks_.clear(); + m_ticks.ticks.clear(); + const std::vector& heights = custom_gcode_per_print_z.gcodes; for (auto h : heights) { auto it = std::lower_bound(m_values.begin(), m_values.end(), h.print_z - epsilon()); if (it == m_values.end()) continue; - m_ticks_.emplace(TICK_CODE{int(it-m_values.begin()), h.gcode, h.extruder, h.color}); + m_ticks.ticks.emplace(TICK_CODE{int(it-m_values.begin()), h.gcode, h.extruder, h.color}); } - if (!was_empty && m_ticks_.empty()) + if (!was_empty && m_ticks.empty()) // Switch to the "Feature type"/"Tool" from the very beginning of a new object slicing after deleting of the old one - wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED)); + post_ticks_changed_event(); + + m_ticks.mode = custom_gcode_per_print_z.mode; Refresh(); Update(); @@ -2638,11 +2624,6 @@ void DoubleSlider::render() // draw line draw_scroll_line(dc, lower_pos, higher_pos); -// //lower slider: -// draw_thumb(dc, lower_pos, ssLower); -// //higher slider: -// draw_thumb(dc, higher_pos, ssHigher); - //draw color print ticks draw_ticks(dc); @@ -2668,7 +2649,7 @@ void DoubleSlider::draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoin return; wxBitmap* icon = m_is_action_icon_focesed ? &m_bmp_add_tick_off.bmp() : &m_bmp_add_tick_on.bmp(); - if (m_ticks_.find(TICK_CODE{tick}) != m_ticks_.end()) + if (m_ticks.ticks.find(TICK_CODE{tick}) != m_ticks.ticks.end()) icon = m_is_action_icon_focesed ? &m_bmp_del_tick_off.bmp() : &m_bmp_del_tick_on.bmp(); wxCoord x_draw, y_draw; @@ -2811,7 +2792,7 @@ void DoubleSlider::draw_ticks(wxDC& dc) int height, width; get_size(&width, &height); const wxCoord mid = is_horizontal() ? 0.5*height : 0.5*width; - for (auto tick : m_ticks_) + for (auto tick : m_ticks.ticks) { const wxCoord pos = get_position_from_value(tick.tick); @@ -2820,11 +2801,23 @@ void DoubleSlider::draw_ticks(wxDC& dc) is_horizontal() ? dc.DrawLine(pos, mid+14, pos, mid+9) : dc.DrawLine(mid + 14, pos/* - 1*/, mid + 9, pos/* - 1*/); - // Draw icon for "Pause print" or "Custom Gcode" - if (tick.gcode != Slic3r::ColorChangeCode && tick.gcode != Slic3r::ExtruderChangeCode) - { - wxBitmap icon = create_scaled_bitmap(this, tick.gcode == Slic3r::PausePrintCode ? "pause_print" : "edit_gcode"); + wxBitmap icon = wxNullBitmap; + // Draw icon for "Pause print" or "Custom Gcode" + if (tick.gcode != Slic3r::ColorChangeCode && tick.gcode != Slic3r::ToolChangeCode) + icon = create_scaled_bitmap(this, tick.gcode == Slic3r::PausePrintCode ? "pause_print" : "edit_gcode"); + else + { + if ((tick.gcode == Slic3r::ColorChangeCode && ( + (m_ticks.mode == t_mode::SingleExtruder && m_mode == t_mode::MultiExtruder ) || + (m_ticks.mode == t_mode::MultiExtruder && m_mode == t_mode::SingleExtruder) )) || + (tick.gcode == Slic3r::ToolChangeCode && + (m_ticks.mode == t_mode::MultiAsSingle && m_mode != t_mode::MultiAsSingle ) )) + icon = create_scaled_bitmap(this, "error_tick"); + } + + if (!icon.IsNull()) + { wxCoord x_draw, y_draw; is_horizontal() ? x_draw = pos - 0.5 * m_tick_icon_dim : y_draw = pos - 0.5 * m_tick_icon_dim; is_horizontal() ? y_draw = mid + 22 : x_draw = mid + m_thumb_size.x + 3; @@ -2834,6 +2827,40 @@ void DoubleSlider::draw_ticks(wxDC& dc) } } +std::string DoubleSlider::get_color_for_tool_change_tick(std::set::const_iterator it) const +{ + const int current_extruder = it->extruder == 0 ? std::max(m_only_extruder, 1) : it->extruder; + + auto it_n = it; + while (it_n != m_ticks.ticks.begin()) { + --it_n; + if (it_n->gcode == Slic3r::ColorChangeCode && it_n->extruder == current_extruder) + return it_n->color; + } + + return it->color; +} + +std::string DoubleSlider::get_color_for_color_change_tick(std::set::const_iterator it) const +{ + const int def_extruder = std::max(1, m_only_extruder); + auto it_n = it; + bool is_tool_change = false; + while (it_n != m_ticks.ticks.begin()) { + --it_n; + if (it_n->gcode == Slic3r::ToolChangeCode) { + is_tool_change = true; + if (it_n->extruder == it->extruder) + return it->color; + break; + } + } + if (!is_tool_change && it->extruder == def_extruder) + return it->color; + + return ""; +} + void DoubleSlider::draw_colored_band(wxDC& dc) { if (!m_is_enabled_tick_manipulation) @@ -2856,29 +2883,36 @@ void DoubleSlider::draw_colored_band(wxDC& dc) dc.DrawRectangle(band_rc); }; - const std::vector& colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config(); - int colors_cnt = colors.size(); - - const wxColour bg_clr = GetParent()->GetBackgroundColour(); - - wxColour clr = m_state == msSingleExtruder ? wxColour(colors[0]) : bg_clr; - draw_band(dc, clr, main_band); - - size_t i = 1; - for (auto tick : m_ticks_) + // don't color a band for MultiExtruder mode + if (m_ticks.empty() || m_mode == t_mode::MultiExtruder) { - if ( (m_state == msSingleExtruder && tick.gcode != Slic3r::ColorChangeCode) || - (m_state == msMultiExtruder && tick.gcode != Slic3r::ExtruderChangeCode) ) - continue; + draw_band(dc, GetParent()->GetBackgroundColour(), main_band); + return; + } - const wxCoord pos = get_position_from_value(tick.tick); - is_horizontal() ? main_band.SetLeft(SLIDER_MARGIN + pos) : - main_band.SetBottom(pos - 1); + const int default_color_idx = m_mode==t_mode::MultiAsSingle ? std::max(m_only_extruder - 1, 0) : 0; + draw_band(dc, wxColour(Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config()[default_color_idx]), main_band); - clr = (m_state == msMultiExtruder && tick.color.empty()) ? bg_clr : - m_state == msMultiExtruder ? wxColour(colors[std::min(colors_cnt - 1, tick.extruder-1)]) : wxColour(tick.color); - draw_band(dc, clr, main_band); - i++; + std::set::const_iterator tick_it = m_ticks.ticks.begin(); + + while (tick_it != m_ticks.ticks.end()) + { + if ( (m_mode == t_mode::SingleExtruder && tick_it->gcode == Slic3r::ColorChangeCode ) || + (m_mode == t_mode::MultiAsSingle && (tick_it->gcode == Slic3r::ToolChangeCode || tick_it->gcode == Slic3r::ColorChangeCode)) ) + { + const wxCoord pos = get_position_from_value(tick_it->tick); + is_horizontal() ? main_band.SetLeft(SLIDER_MARGIN + pos) : + main_band.SetBottom(pos - 1); + + const std::string clr_str = m_mode == t_mode::SingleExtruder ? tick_it->color : + tick_it->gcode == Slic3r::ToolChangeCode ? + get_color_for_tool_change_tick(tick_it) : + get_color_for_color_change_tick(tick_it); + + if (!clr_str.empty()) + draw_band(dc, wxColour(clr_str), main_band); + } + ++tick_it; } } @@ -2903,7 +2937,7 @@ void DoubleSlider::draw_one_layer_icon(wxDC& dc) void DoubleSlider::draw_revert_icon(wxDC& dc) { - if (m_ticks_.empty() || !m_is_enabled_tick_manipulation) + if (m_ticks.empty() || !m_is_enabled_tick_manipulation) return; int width, height; @@ -2921,7 +2955,7 @@ void DoubleSlider::draw_revert_icon(wxDC& dc) void DoubleSlider::draw_cog_icon(wxDC& dc) { - if (m_state != msMultiExtruder) + if (m_mode != t_mode::MultiExtruder) return; int width, height; @@ -2965,15 +2999,13 @@ void DoubleSlider::detect_selected_slider(const wxPoint& pt) bool DoubleSlider::is_point_in_rect(const wxPoint& pt, const wxRect& rect) { - if (rect.GetLeft() <= pt.x && pt.x <= rect.GetRight() && - rect.GetTop() <= pt.y && pt.y <= rect.GetBottom()) - return true; - return false; + return rect.GetLeft() <= pt.x && pt.x <= rect.GetRight() && + rect.GetTop() <= pt.y && pt.y <= rect.GetBottom(); } int DoubleSlider::is_point_near_tick(const wxPoint& pt) { - for (auto tick : m_ticks_) { + for (auto tick : m_ticks.ticks) { const wxCoord pos = get_position_from_value(tick.tick); if (is_horizontal()) { @@ -3010,8 +3042,13 @@ void DoubleSlider::OnLeftDown(wxMouseEvent& event) wxClientDC dc(this); wxPoint pos = event.GetLogicalPosition(dc); - if (is_point_in_rect(pos, m_rect_tick_action) && m_is_enabled_tick_manipulation) { - action_tick(taOnIcon); + if (is_point_in_rect(pos, m_rect_tick_action) && m_is_enabled_tick_manipulation) + { + const auto it = m_ticks.ticks.find(TICK_CODE{ m_selection == ssLower ? m_lower_value : m_higher_value }); + if (it == m_ticks.ticks.end()) + m_force_add_tick = true; + else + m_force_delete_tick = true; return; } @@ -3034,12 +3071,13 @@ void DoubleSlider::OnLeftDown(wxMouseEvent& event) m_selection == ssLower ? correct_lower_value() : correct_higher_value(); if (!m_selection) m_selection = ssHigher; - m_ticks_.clear(); - wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED)); + m_ticks.ticks.clear(); + post_ticks_changed_event(); } - else if (is_point_in_rect(pos, m_rect_cog_icon) && m_state == msMultiExtruder) { + else if (is_point_in_rect(pos, m_rect_cog_icon) && m_mode == t_mode::MultiExtruder) { // show dialog for set extruder sequence - m_edit_extruder_sequence = true; + m_force_edit_extruder_sequence = true; + return; } else detect_selected_slider(pos); @@ -3107,17 +3145,18 @@ wxString DoubleSlider::get_tooltip(IconFocus icon_focus) else if (m_is_action_icon_focesed) { const int tick = m_selection == ssLower ? m_lower_value : m_higher_value; - const auto tick_code_it = m_ticks_.find(TICK_CODE{tick}); - tooltip = tick_code_it == m_ticks_.end() ? (m_state == msSingleExtruder ? - _(L("For add color change use left mouse button click")) : - _(L("For add change extruder use left mouse button click"))) + "\n" + - _(L("For add another code use right mouse button click")) : - tick_code_it->gcode == Slic3r::ColorChangeCode ? ( m_state == msSingleExtruder ? + const auto tick_code_it = m_ticks.ticks.find(TICK_CODE{tick}); + tooltip = tick_code_it == m_ticks.ticks.end() ? (m_mode == t_mode::MultiAsSingle ? + _(L("For add change extruder use left mouse button click")) : + _(L("For add color change use left mouse button click")) ) + "\n" + + _(L("For add another code use right mouse button click")) : + tick_code_it->gcode == Slic3r::ColorChangeCode ? ( m_mode == t_mode::SingleExtruder ? _(L("For Delete color change use left mouse button click\n" "For Edit color use right mouse button click")) : from_u8((boost::format(_utf8(L("Delete color change for Extruder %1%"))) % tick_code_it->extruder).str()) ): -// tick_code_it->gcode == Slic3r::PausePrintCode ? _(L("Delete pause")) : - tick_code_it->gcode == Slic3r::ExtruderChangeCode ? + tick_code_it->gcode == Slic3r::PausePrintCode ? + _(L("Delete pause")) : + tick_code_it->gcode == Slic3r::ToolChangeCode ? from_u8((boost::format(_utf8(L("Delete extruder change to \"%1%\""))) % tick_code_it->extruder).str()) : from_u8((boost::format(_utf8(L("For Delete \"%1%\" code use left mouse button click\n" "For Edit \"%1%\" code use right mouse button click"))) % tick_code_it->gcode ).str()); @@ -3138,7 +3177,7 @@ void DoubleSlider::OnMotion(wxMouseEvent& event) if (!m_is_left_down && !m_is_one_layer) { m_is_action_icon_focesed = is_point_in_rect(pos, m_rect_tick_action); - if (!m_ticks_.empty() && is_point_in_rect(pos, m_rect_revert_icon)) + if (!m_ticks.empty() && is_point_in_rect(pos, m_rect_revert_icon)) icon_focus = ifRevert; else if (is_point_in_rect(pos, m_rect_cog_icon)) icon_focus = ifCog; @@ -3173,6 +3212,65 @@ void DoubleSlider::OnMotion(wxMouseEvent& event) } } +void DoubleSlider::append_change_extruder_menu_item(wxMenu* menu) +{ + const int extruders_cnt = Slic3r::GUI::wxGetApp().extruders_edited_cnt(); + if (extruders_cnt > 1) + { + const int initial_extruder = std::max(1 , get_extruder_for_tick(m_selection == ssLower ? m_lower_value : m_higher_value)); + + wxMenu* change_extruder_menu = new wxMenu(); + + for (int i = 1; i <= extruders_cnt; i++) + { + const bool is_active_extruder = i == initial_extruder; + const wxString item_name = wxString::Format(_(L("Extruder %d")), i) + + (is_active_extruder ? " (" + _(L("active")) + ")" : ""); + + if (m_mode == t_mode::MultiAsSingle) + append_menu_item(change_extruder_menu, wxID_ANY, item_name, "", + [this, i](wxCommandEvent&) { add_code_as_tick(Slic3r::ToolChangeCode, i); }, "", menu, + [is_active_extruder]() { return !is_active_extruder; }, Slic3r::GUI::wxGetApp().plater()); + } + + const wxString change_extruder_menu_name = m_mode == t_mode::MultiAsSingle ? _(L("Change extruder")) : _(L("Change extruder (N/A)")); + + wxMenuItem* change_extruder_menu_item = menu->AppendSubMenu(change_extruder_menu, change_extruder_menu_name, _(L("Use another extruder"))); + change_extruder_menu_item->SetBitmap(create_scaled_bitmap(this, "change_extruder")); + + Slic3r::GUI::wxGetApp().plater()->Bind(wxEVT_UPDATE_UI, [this, change_extruder_menu_item](wxUpdateUIEvent& evt) { + enable_menu_item(evt, [this]() {return m_mode == t_mode::MultiAsSingle; }, change_extruder_menu_item, this); }, + change_extruder_menu_item->GetId()); + } +} + +void DoubleSlider::append_add_color_change_menu_item(wxMenu* menu) +{ + const int extruders_cnt = Slic3r::GUI::wxGetApp().extruders_edited_cnt(); + if (extruders_cnt > 1) + { + std::set used_extruders_for_tick = get_used_extruders_for_tick(m_selection == ssLower ? m_lower_value : m_higher_value); + + wxMenu* add_color_change_menu = new wxMenu(); + + for (int i = 1; i <= extruders_cnt; i++) + { + const bool is_used_extruder = used_extruders_for_tick.empty() ? true : // #ys_FIXME till used_extruders_for_tick doesn't filled correct for mmMultiExtruder + used_extruders_for_tick.find(i) != used_extruders_for_tick.end(); + const wxString item_name = wxString::Format(_(L("Extruder %d")), i) + + (is_used_extruder ? " (" + _(L("used")) + ")" : ""); + + append_menu_item(add_color_change_menu, wxID_ANY, item_name, "", + [this, i](wxCommandEvent&) { add_code_as_tick(Slic3r::ColorChangeCode, i); }, "", menu, + [is_used_extruder]() { return is_used_extruder; }, Slic3r::GUI::wxGetApp().plater()); + } + + const wxString menu_name = from_u8((boost::format(_utf8(L("Add color change (%1%) for:"))) % Slic3r::ColorChangeCode).str()); + wxMenuItem* add_color_change_menu_item = menu->AppendSubMenu(add_color_change_menu, menu_name, ""); + add_color_change_menu_item->SetBitmap(create_scaled_bitmap(this, "colorchange_add_m")); + } +} + void DoubleSlider::OnLeftUp(wxMouseEvent& event) { if (!HasCapture()) @@ -3180,40 +3278,21 @@ void DoubleSlider::OnLeftUp(wxMouseEvent& event) this->ReleaseMouse(); m_is_left_down = false; - if (m_show_context_menu) + if (m_force_delete_tick) { - if (m_state == msMultiExtruder) - { - wxMenu menu; - const int extruders_cnt = Slic3r::GUI::wxGetApp().extruders_edited_cnt(); - if (extruders_cnt > 1) - { - const int initial_extruder = get_extruder_for_tick(m_selection == ssLower ? m_lower_value : m_higher_value); - - wxMenu* change_extruder_menu = new wxMenu(); - - for (int i = 0; i <= extruders_cnt; i++) { - const wxString item_name = i == 0 ? _(L("Default")) : wxString::Format(_(L("Extruder %d")), i); - - append_menu_radio_item(change_extruder_menu, wxID_ANY, item_name, "", - [this, i](wxCommandEvent&) { change_extruder(i); }, &menu)->Check(i == initial_extruder); - } - - wxMenuItem* change_extruder_menu_item = menu.AppendSubMenu(change_extruder_menu, _(L("Change extruder")), _(L("Use another extruder"))); - change_extruder_menu_item->SetBitmap(create_scaled_bitmap(nullptr, "change_extruder")); - } - - Slic3r::GUI::wxGetApp().plater()->PopupMenu(&menu); - } - else - add_code(Slic3r::ColorChangeCode); - - m_show_context_menu = false; + delete_current_tick(); + m_force_delete_tick = false; } - - if (m_edit_extruder_sequence) { + else + if (m_force_add_tick) + { + add_current_tick(); + m_force_add_tick = false; + } + else + if (m_force_edit_extruder_sequence) { edit_extruder_sequence(); - m_edit_extruder_sequence = false; + m_force_edit_extruder_sequence = false; } Refresh(); @@ -3259,45 +3338,6 @@ void DoubleSlider::move_current_thumb(const bool condition) ProcessWindowEvent(e); } -void DoubleSlider::action_tick(const TicksAction action) -{ - if (m_selection == ssUndef) - return; - - const int tick = m_selection == ssLower ? m_lower_value : m_higher_value; - - const auto it = m_ticks_.find(TICK_CODE{tick}); - - if (it != m_ticks_.end()) // erase this tick - { - if (action == taAdd) - return; - m_ticks_.erase(TICK_CODE{tick}); - - wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED)); - Refresh(); - Update(); - return; - } - - if (action == taDel) - return; - if (action == taAdd) - { - // OnChar() is called immediately after OnKeyDown(), which can cause call of add_code() twice. - // To avoid this case we should suppress second add_code() call. - if (m_suppress_add_code) - return; - m_suppress_add_code = true; - if (m_state != msMultiExtruder) - add_code(Slic3r::ColorChangeCode); - m_suppress_add_code = false; - return; - } - - m_show_context_menu = true; -} - void DoubleSlider::OnWheel(wxMouseEvent& event) { // Set nearest to the mouse thumb as a selected, if there is not selected thumb @@ -3321,10 +3361,18 @@ void DoubleSlider::OnWheel(wxMouseEvent& event) void DoubleSlider::OnKeyDown(wxKeyEvent &event) { const int key = event.GetKeyCode(); - if (key == WXK_NUMPAD_ADD) - action_tick(taAdd); - else if (key == 390 || key == WXK_DELETE || key == WXK_BACK) - action_tick(taDel); + if (key == WXK_NUMPAD_ADD) { + // OnChar() is called immediately after OnKeyDown(), which can cause call of add_tick() twice. + // To avoid this case we should suppress second add_tick() call. + m_ticks.suppress_plus(true); + add_current_tick(true); + } + else if (key == 390 || key == WXK_DELETE || key == WXK_BACK) { + // OnChar() is called immediately after OnKeyDown(), which can cause call of delete_tick() twice. + // To avoid this case we should suppress second delete_tick() call. + m_ticks.suppress_minus(true); + delete_current_tick(); + } else if (is_horizontal()) { if (key == WXK_LEFT || key == WXK_RIGHT) @@ -3358,10 +3406,14 @@ void DoubleSlider::OnKeyUp(wxKeyEvent &event) void DoubleSlider::OnChar(wxKeyEvent& event) { const int key = event.GetKeyCode(); - if (key == '+') - action_tick(taAdd); - else if (key == '-') - action_tick(taDel); + if (key == '+' && !m_ticks.suppressed_plus()) { + add_current_tick(true); + m_ticks.suppress_plus(false); + } + else if (key == '-' && !m_ticks.suppressed_minus()) { + delete_current_tick(); + m_ticks.suppress_minus(false); + } } void DoubleSlider::OnRightDown(wxMouseEvent& event) @@ -3376,14 +3428,14 @@ void DoubleSlider::OnRightDown(wxMouseEvent& event) { const int tick = m_selection == ssLower ? m_lower_value : m_higher_value; // if on this Z doesn't exist tick - auto it = m_ticks_.find(TICK_CODE{ tick }); - if (it == m_ticks_.end()) + auto it = m_ticks.ticks.find(TICK_CODE{ tick }); + if (it == m_ticks.ticks.end()) { // show context menu on OnRightUp() m_show_context_menu = true; return; } - if (it->gcode != Slic3r::ExtruderChangeCode) + if (it->gcode != Slic3r::ToolChangeCode) { // show "Edit" and "Delete" menu on OnRightUp() m_show_edit_menu = true; @@ -3410,17 +3462,78 @@ void DoubleSlider::OnRightDown(wxMouseEvent& event) int DoubleSlider::get_extruder_for_tick(int tick) { - if (m_ticks_.empty()) - return 0; + int default_initial_extruder = m_mode == t_mode::MultiAsSingle ? m_only_extruder : 0; + if (m_ticks.empty()) + return default_initial_extruder; - auto it = m_ticks_.lower_bound(TICK_CODE{tick}); - while (it != m_ticks_.begin()) { + auto it = m_ticks.ticks.lower_bound(TICK_CODE{tick}); + while (it != m_ticks.ticks.begin()) { --it; - if(it->gcode == Slic3r::ExtruderChangeCode) + if(it->gcode == Slic3r::ToolChangeCode) return it->extruder; } - return 0; + return default_initial_extruder; +} + +std::set DoubleSlider::get_used_extruders_for_tick(int tick) +{ + if (m_mode == t_mode::MultiExtruder) + { + // #ys_FIXME: get tool ordering from _correct_ place + const Slic3r::ToolOrdering& tool_ordering = Slic3r::GUI::wxGetApp().plater()->fff_print().get_tool_ordering(); + + if (tool_ordering.empty()) + return {}; + + std::set used_extruders; + + auto it_layer_tools = std::lower_bound(tool_ordering.begin(), tool_ordering.end(), Slic3r::LayerTools(m_values[tick])); + for (; it_layer_tools != tool_ordering.end(); it_layer_tools++) + { + const std::vector& extruders = it_layer_tools->extruders; + for (const auto& extruder : extruders) + used_extruders.emplace(extruder+1); + } + + return used_extruders; + } + + const int default_initial_extruder = m_mode == t_mode::MultiAsSingle ? std::max(m_only_extruder, 1) : 1; + if (m_ticks.empty()) + return {default_initial_extruder}; + + std::set used_extruders; + const std::set& ticks = m_ticks.ticks; + + auto it_start = ticks.lower_bound(TICK_CODE{tick}); + auto it = it_start; + if (it == ticks.begin() && it->gcode == Slic3r::ToolChangeCode) { + used_extruders.emplace(it->extruder); + if (tick < it->tick) + used_extruders.emplace(default_initial_extruder); + } + + while (it != ticks.begin()) { + --it; + if(it->gcode == Slic3r::ToolChangeCode) + { + used_extruders.emplace(it->extruder); + break; + } + } + + if (it == ticks.begin() && used_extruders.empty()) + used_extruders.emplace(default_initial_extruder); + + it = it_start; + while (it != ticks.end()) { + if(it->gcode == Slic3r::ToolChangeCode) + used_extruders.emplace(it->extruder); + ++it; + } + + return used_extruders; } void DoubleSlider::OnRightUp(wxMouseEvent& event) @@ -3433,45 +3546,23 @@ void DoubleSlider::OnRightUp(wxMouseEvent& event) if (m_show_context_menu) { wxMenu menu; - if (m_state == msMultiExtruder) - { - const int extruders_cnt = Slic3r::GUI::wxGetApp().extruders_edited_cnt(); - if (extruders_cnt > 1) - { - const int initial_extruder = get_extruder_for_tick(m_selection == ssLower ? m_lower_value : m_higher_value); - - wxMenu* change_extruder_menu = new wxMenu(); - wxMenu* add_color_change_menu = new wxMenu(); - - for (int i = 0; i <= extruders_cnt; i++) { - const wxString item_name = i == 0 ? _(L("Default")) : wxString::Format(_(L("Extruder %d")), i); - - append_menu_radio_item(change_extruder_menu, wxID_ANY, item_name, "", - [this, i](wxCommandEvent&) { change_extruder(i); }, &menu)->Check(i == initial_extruder); - - if (i==0) // don't use M600 for default extruder, if multimaterial print is selected - continue; - append_menu_item(add_color_change_menu, wxID_ANY, item_name, "", - [this, i](wxCommandEvent&) { add_code(Slic3r::ColorChangeCode, i); }, "", &menu); - } - - wxMenuItem* change_extruder_menu_item = menu.AppendSubMenu(change_extruder_menu, _(L("Change extruder")), _(L("Use another extruder"))); - change_extruder_menu_item->SetBitmap(create_scaled_bitmap(nullptr, "change_extruder")); - - const wxString menu_name = from_u8((boost::format(_utf8(L("Add color change (%1%) for:"))) % Slic3r::ColorChangeCode).str()); - wxMenuItem* add_color_change_menu_item = menu.AppendSubMenu(add_color_change_menu, menu_name, ""); - add_color_change_menu_item->SetBitmap(create_scaled_bitmap(nullptr, "colorchange_add_m")); - } - } + if (m_mode == t_mode::SingleExtruder) + append_menu_item(&menu, wxID_ANY, _(L("Add color change")) + " (M600)", "", + [this](wxCommandEvent&) { add_code_as_tick(Slic3r::ColorChangeCode); }, "colorchange_add_m", &menu, + [](){return true;}, this); else - append_menu_item(&menu, wxID_ANY, _(L("Add color change")) + " (M600)", "", - [this](wxCommandEvent&) { add_code(Slic3r::ColorChangeCode); }, "colorchange_add_m", &menu); + { + append_change_extruder_menu_item(&menu); + append_add_color_change_menu_item(&menu); + } append_menu_item(&menu, wxID_ANY, _(L("Add pause print")) + " (M601)", "", - [this](wxCommandEvent&) { add_code(Slic3r::PausePrintCode); }, "pause_print", &menu); + [this](wxCommandEvent&) { add_code_as_tick(Slic3r::PausePrintCode); }, "pause_print", &menu, + []() {return true; }, this); append_menu_item(&menu, wxID_ANY, _(L("Add custom G-code")), "", - [this](wxCommandEvent&) { add_code(""); }, "edit_gcode", &menu); + [this](wxCommandEvent&) { add_code_as_tick(""); }, "edit_gcode", &menu, + []() {return true; }, this); Slic3r::GUI::wxGetApp().plater()->PopupMenu(&menu); @@ -3480,7 +3571,7 @@ void DoubleSlider::OnRightUp(wxMouseEvent& event) else if (m_show_edit_menu) { wxMenu menu; - std::set::iterator it = m_ticks_.find(TICK_CODE{ m_selection == ssLower ? m_lower_value : m_higher_value }); + std::set::iterator it = m_ticks.ticks.find(TICK_CODE{ m_selection == ssLower ? m_lower_value : m_higher_value }); const bool is_color_change = it->gcode == Slic3r::ColorChangeCode; append_menu_item(&menu, wxID_ANY, it->gcode == Slic3r::ColorChangeCode ? _(L("Edit color")) : @@ -3491,7 +3582,7 @@ void DoubleSlider::OnRightUp(wxMouseEvent& event) append_menu_item(&menu, wxID_ANY, it->gcode == Slic3r::ColorChangeCode ? _(L("Delete color change")) : it->gcode == Slic3r::PausePrintCode ? _(L("Delete pause print")) : _(L("Delete custom G-code")), "", - [this](wxCommandEvent&) { action_tick(taDel); }, "colorchange_del_f", &menu); + [this](wxCommandEvent&) { delete_current_tick();}, "colorchange_del_f", &menu); Slic3r::GUI::wxGetApp().plater()->PopupMenu(&menu); @@ -3548,128 +3639,100 @@ static std::string get_pause_print_msg(const std::string& msg_in, double height) return into_u8(dlg.GetValue()); } -void DoubleSlider::add_code(std::string code, int selected_extruder/* = -1*/) +void DoubleSlider::add_code_as_tick(std::string code, int selected_extruder/* = -1*/) { + if (m_selection == ssUndef) + return; const int tick = m_selection == ssLower ? m_lower_value : m_higher_value; - // if on this Z doesn't exist tick - auto it = m_ticks_.find(TICK_CODE{ tick }); - if (it == m_ticks_.end()) + + if (m_ticks.ticks.find(TICK_CODE{ tick }) != m_ticks.ticks.end() || // if on this Z doesn't exist tick + !check_ticks_changed_event(code)) + return; + + const int extruder = selected_extruder > 0 ? selected_extruder : std::max(1, m_only_extruder); + + if (!m_ticks.add_tick(tick, code, extruder, m_values[tick])) + return; + + post_ticks_changed_event(code); +} + +void DoubleSlider::add_current_tick(bool call_from_keyboard /*= false*/) +{ + if (m_selection == ssUndef) + return; + const int tick = m_selection == ssLower ? m_lower_value : m_higher_value; + auto it = m_ticks.ticks.find(TICK_CODE{ tick }); + + if (it != m_ticks.ticks.end() || // this tick is already exist + !check_ticks_changed_event(m_mode == t_mode::MultiAsSingle ? Slic3r::ToolChangeCode : Slic3r::ColorChangeCode)) + return; + + if (m_mode == t_mode::SingleExtruder) + add_code_as_tick(Slic3r::ColorChangeCode); + else { - std::string color = ""; - if (code == Slic3r::ColorChangeCode) + wxMenu menu; + + if (m_mode == t_mode::MultiAsSingle) + append_change_extruder_menu_item(&menu); + else + append_add_color_change_menu_item(&menu); + + wxPoint pos = wxDefaultPosition; + if (call_from_keyboard) { - std::vector colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config(); + int width, height; + get_size(&width, &height); - if (m_state == msSingleExtruder && !m_ticks_.empty()) { - auto before_tick_it = std::lower_bound(m_ticks_.begin(), m_ticks_.end(), TICK_CODE{ tick }); - while (before_tick_it != m_ticks_.begin()) { - --before_tick_it; - if (before_tick_it->gcode == Slic3r::ColorChangeCode) { - color = before_tick_it->color; - break; - } - } + const wxCoord coord = 0.75 * (is_horizontal() ? height : width); + this->GetPosition(&width, &height); - if (color.empty()) - color = colors[0]; - } - else - color = colors[selected_extruder > 0 ? selected_extruder-1 : 0]; - - color = get_new_color(color); - if (color.empty()) - return; - } - else if (code == Slic3r::PausePrintCode) - { - /* PausePrintCode doesn't need a color, so - * this field is used for save a short message shown on Printer display - * */ - color = get_pause_print_msg(m_pause_print_msg, m_values[tick]); - if (color.empty()) - return; - m_pause_print_msg = color; - } - else if (code.empty()) - { - code = get_custom_code(m_custom_gcode, m_values[tick]); - if (code.empty()) - return; - m_custom_gcode = code; + pos = is_horizontal() ? + wxPoint(get_position_from_value(tick), height + coord) : + wxPoint(width + coord, get_position_from_value(tick)); } - int extruder = 1; - if (m_state == msMultiExtruder) { - if (code == Slic3r::ColorChangeCode && selected_extruder >= 0) - extruder = selected_extruder; - else - extruder = get_extruder_for_tick(m_selection == ssLower ? m_lower_value : m_higher_value); - } + Slic3r::GUI::wxGetApp().plater()->PopupMenu(&menu, pos); + } +} - m_ticks_.emplace(TICK_CODE{tick, code, extruder, color}); +void DoubleSlider::delete_current_tick() +{ + if (m_selection == ssUndef) + return; + auto it = m_ticks.ticks.find(TICK_CODE{ m_selection == ssLower ? m_lower_value : m_higher_value }); - wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED)); - Refresh(); - Update(); + if (it != m_ticks.ticks.end()) + { + if (!check_ticks_changed_event(it->gcode)) + return; + + const std::string code = it->gcode; + m_ticks.ticks.erase(it); + post_ticks_changed_event(code); } } void DoubleSlider::edit_tick() { const int tick = m_selection == ssLower ? m_lower_value : m_higher_value; - // if on this Z exists tick - std::set::iterator it = m_ticks_.find(TICK_CODE{ tick }); - if (it != m_ticks_.end()) - { - std::string edited_value; - if (it->gcode == Slic3r::ColorChangeCode) - edited_value = get_new_color(it->color); - else if (it->gcode == Slic3r::PausePrintCode) - edited_value = get_pause_print_msg(it->color, m_values[it->tick]); - else - edited_value = get_custom_code(it->gcode, m_values[it->tick]); + const std::set::iterator it = m_ticks.ticks.find(TICK_CODE{ tick }); - if (edited_value.empty()) - return; + if (it == m_ticks.ticks.end() || // if on this Z exists tick + !check_ticks_changed_event(it->gcode)) + return; - TICK_CODE changed_tick = *it; - if (it->gcode == Slic3r::ColorChangeCode || it->gcode == Slic3r::PausePrintCode) { - if (it->color == edited_value) - return; - changed_tick.color = edited_value; - } - else { - if (it->gcode == edited_value) - return; - changed_tick.gcode = edited_value; - } - - m_ticks_.erase(it); - m_ticks_.emplace(changed_tick); - - wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED)); - } -} - -void DoubleSlider::change_extruder(int extruder) -{ - const int tick = m_selection == ssLower ? m_lower_value : m_higher_value; - - std::vector colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config(); - - // if on this Y doesn't exist tick - if (m_ticks_.find(TICK_CODE{tick}) == m_ticks_.end()) - { - m_ticks_.emplace(TICK_CODE{tick, Slic3r::ExtruderChangeCode, extruder, extruder == 0 ? "" : colors[extruder-1]}); - - wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED)); - Refresh(); - Update(); - } + const std::string code = it->gcode; + if (m_ticks.edit_tick(it, m_values[it->tick])) + post_ticks_changed_event(code); } void DoubleSlider::edit_extruder_sequence() { + if (!check_ticks_changed_event(Slic3r::ToolChangeCode)) + return; + Slic3r::GUI::ExtruderSequenceDialog dlg(m_extruders_sequence); if (dlg.ShowModal() != wxID_OK) return; @@ -3680,13 +3743,7 @@ void DoubleSlider::edit_extruder_sequence() m_extruders_sequence = from_dlg_val; - auto it = m_ticks_.begin(); - while (it != m_ticks_.end()) { - if (it->gcode == Slic3r::ExtruderChangeCode) - it = m_ticks_.erase(it); - else - ++it; - } + m_ticks.erase_all_ticks_with_code(Slic3r::ToolChangeCode); int tick = 0; double value = 0.0; @@ -3697,8 +3754,8 @@ void DoubleSlider::edit_extruder_sequence() while (tick <= m_max_value) { - int cur_extruder = m_extruders_sequence.extruders[extruder]; - m_ticks_.emplace(TICK_CODE{tick, Slic3r::ExtruderChangeCode, cur_extruder + 1, colors[cur_extruder]}); + const int cur_extruder = m_extruders_sequence.extruders[extruder]; + m_ticks.ticks.emplace(TICK_CODE{tick, Slic3r::ToolChangeCode, cur_extruder + 1, colors[cur_extruder]}); extruder++; if (extruder == extr_cnt) @@ -3717,9 +3774,188 @@ void DoubleSlider::edit_extruder_sequence() tick += m_extruders_sequence.interval_by_layers; } + post_ticks_changed_event(Slic3r::ToolChangeCode); +} + +void DoubleSlider::post_ticks_changed_event(const std::string& gcode /*= ""*/) +{ + m_force_mode_apply = (gcode.empty() || gcode == Slic3r::ColorChangeCode || gcode == Slic3r::ToolChangeCode); + wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED)); } +bool DoubleSlider::check_ticks_changed_event(const std::string& gcode) +{ + if ( m_ticks.mode == m_mode || + (gcode != Slic3r::ColorChangeCode && gcode != Slic3r::ToolChangeCode) || + (m_ticks.mode == t_mode::SingleExtruder && m_mode == t_mode::MultiAsSingle) || // All ColorChanges will be applied for 1st extruder + (m_ticks.mode == t_mode::MultiExtruder && m_mode == t_mode::MultiAsSingle) ) // Just mark ColorChanges for all unused extruders + return true; + + if ((m_ticks.mode == t_mode::SingleExtruder && m_mode == t_mode::MultiExtruder ) || + (m_ticks.mode == t_mode::MultiExtruder && m_mode == t_mode::SingleExtruder) ) + { + if (!m_ticks.has_tick_with_code(Slic3r::ColorChangeCode)) + return true; + + wxString message = (m_ticks.mode == t_mode::SingleExtruder ? + _(L("The last color change data was saved for a single extruder printer profile.")) : + _(L("The last color change data was saved for a multiple extruder printer profile.")) + ) + "\n" + + _(L("Your current changes will cause a deletion of all saved color changes.")) + "\n\n\t" + + _(L("Are you sure you want to continue?")); + + wxMessageDialog msg(this, message, _(L("Notice")), wxYES_NO); + if (msg.ShowModal() == wxID_YES) { + m_ticks.erase_all_ticks_with_code(Slic3r::ColorChangeCode); + post_ticks_changed_event(Slic3r::ColorChangeCode); + } + return false; + } + // m_ticks_mode == t_mode::MultiAsSingle + if( m_ticks.has_tick_with_code(Slic3r::ToolChangeCode) ) + { + wxString message = m_mode == t_mode::SingleExtruder ? ( + _(L("The last color change data was saved for a multi extruder printing.")) + "\n\n" + + _(L("Select YES if you want to delete all saved tool changes, \n\t" + "NO if you want all tool changes switch to color changes, \n\t" + "or CANCEL for do nothing")) + "\n\n\t" + + _(L("Do you want to delete all saved tool changes?")) + ) : ( // t_mode::MultiExtruder + _(L("The last color change data was saved for a multi extruder printing with tool changes for whole print.")) + "\n\n" + + _(L("Your current changes will cause a deletion of all saved tool changes.")) + "\n\n\t" + + _(L("Are you sure you want to continue?")) ) ; + + wxMessageDialog msg(this, message, _(L("Notice")), wxYES_NO | (m_mode == t_mode::SingleExtruder ? wxCANCEL : 0)); + const int answer = msg.ShowModal(); + if (answer == wxID_YES) { + m_ticks.erase_all_ticks_with_code(Slic3r::ToolChangeCode); + post_ticks_changed_event(Slic3r::ToolChangeCode); + } + else if (m_mode == t_mode::SingleExtruder && answer == wxID_NO) { + m_ticks.switch_code(Slic3r::ToolChangeCode, Slic3r::ColorChangeCode); + post_ticks_changed_event(Slic3r::ColorChangeCode); + } + return false; + } + + return true; +} + +bool DoubleSlider::TICK_CODE_INFO::add_tick(const int tick, std::string& code, const int extruder, double print_z) +{ + std::string color; + if (code.empty()) // custom Gcode + { + code = get_custom_code(custom_gcode, print_z); + if (code.empty()) + return false; + custom_gcode = code; + } + else if (code == Slic3r::PausePrintCode) + { + /* PausePrintCode doesn't need a color, so + * this field is used for save a short message shown on Printer display + * */ + color = get_pause_print_msg(pause_print_msg, print_z); + if (color.empty()) + return false; + pause_print_msg = color; + } + else + { + std::vector colors = Slic3r::GUI::wxGetApp().plater()->get_extruder_colors_from_plater_config(); + color = colors[extruder - 1]; + + if (code == Slic3r::ColorChangeCode) + { + if (!ticks.empty()) + { + auto before_tick_it = std::lower_bound(ticks.begin(), ticks.end(), TICK_CODE{ tick }); + while (before_tick_it != ticks.begin()) { + --before_tick_it; + if (before_tick_it->gcode == Slic3r::ColorChangeCode && before_tick_it->extruder == extruder) { + color = before_tick_it->color; + break; + } + } + } + + color = get_new_color(color); + if (color.empty()) + return false; + } + } + + ticks.emplace(TICK_CODE{ tick, code, extruder, color }); + return true; +} + +bool DoubleSlider::TICK_CODE_INFO::edit_tick(std::set::iterator it, double print_z) +{ + std::string edited_value; + if (it->gcode == Slic3r::ColorChangeCode) + edited_value = get_new_color(it->color); + else if (it->gcode == Slic3r::PausePrintCode) + edited_value = get_pause_print_msg(it->color, print_z); + else + edited_value = get_custom_code(it->gcode, print_z); + + if (edited_value.empty()) + return false; + + TICK_CODE changed_tick = *it; + if (it->gcode == Slic3r::ColorChangeCode || it->gcode == Slic3r::PausePrintCode) { + if (it->color == edited_value) + return false; + changed_tick.color = edited_value; + } + else { + if (it->gcode == edited_value) + return false; + changed_tick.gcode = edited_value; + } + + ticks.erase(it); + ticks.emplace(changed_tick); + + return true; +} + +void DoubleSlider::TICK_CODE_INFO::switch_code(const std::string& code_from, const std::string& code_to) +{ + for (auto it{ ticks.begin() }, end{ ticks.end() }; it != end; ) + if (it->gcode == code_from) + { + TICK_CODE tick = *it; + tick.gcode = code_to; + tick.extruder = 1; + ticks.erase(it); + it = ticks.emplace(tick).first; + } + else + ++it; +} + +void DoubleSlider::TICK_CODE_INFO::erase_all_ticks_with_code(const std::string& gcode) +{ + for (auto it{ ticks.begin() }, end{ ticks.end() }; it != end; ) { + if (it->gcode == gcode) + it = ticks.erase(it); + else + ++it; + } +} + +bool DoubleSlider::TICK_CODE_INFO::has_tick_with_code(const std::string& gcode) +{ + for (const TICK_CODE& tick : ticks) + if (tick.gcode == gcode) + return true; + + return false; +} + // ---------------------------------------------------------------------------- // LockButton diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 390dafab9..8cae495ef 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -782,6 +782,8 @@ public: const wxString& name = wxEmptyString); ~DoubleSlider() {} + using t_mode = Slic3r::Model::CustomGCodeInfo::MODE; + /* For exporting GCode in GCodeWriter is used XYZF_NUM(val) = PRECISION(val, 3) for XYZ values. * So, let use same value as a permissible error for layer height. */ @@ -805,33 +807,24 @@ public: // Set low and high slider position. If the span is non-empty, disable the "one layer" mode. void SetSelectionSpan(const int lower_val, const int higher_val); void SetMaxValue(const int max_value); - void SetKoefForLabels(const double koef) { - m_label_koef = koef; - } - void SetSliderValues(const std::vector& values) { - m_values = values; - } + void SetKoefForLabels(const double koef) { m_label_koef = koef; } + void SetSliderValues(const std::vector& values) { m_values = values; } void ChangeOneLayerLock(); - std::vector GetTicksValues() const; - void SetTicksValues(const std::vector &heights); - void EnableTickManipulation(bool enable = true) { - m_is_enabled_tick_manipulation = enable; - } - void DisableTickManipulation() { - EnableTickManipulation(false); - } + Slic3r::Model::CustomGCodeInfo GetTicksValues() const; + void SetTicksValues(const Slic3r::Model::CustomGCodeInfo &custom_gcode_per_print_z); + void EnableTickManipulation(bool enable = true) { m_is_enabled_tick_manipulation = enable; } + void DisableTickManipulation() { EnableTickManipulation(false); } - enum ManipulationState { - msSingleExtruder, // single extruder printer preset is selected - msMultiExtruder // multiple extruder printer preset is selected, and "Whole print" is selected - }; - void SetManipulationState(ManipulationState state) { - m_state = state; + void SetManipulationMode(t_mode mode) { m_mode = mode; } + t_mode GetManipulationMode() const { return m_mode; } + + void SetModeAndOnlyExtruder(const bool is_one_extruder_printed_model, const int only_extruder) + { + m_mode = !is_one_extruder_printed_model ? t_mode::MultiExtruder : + only_extruder < 0 ? t_mode::SingleExtruder : + t_mode::MultiAsSingle; + m_only_extruder = only_extruder; } - void SetManipulationState(int extruders_cnt) { - m_state = extruders_cnt ==1 ? msSingleExtruder : msMultiExtruder; - } - ManipulationState GetManipulationState() const { return m_state; } bool is_horizontal() const { return m_style == wxSL_HORIZONTAL; } bool is_one_layer() const { return m_is_one_layer; } @@ -850,13 +843,27 @@ public: void OnKeyUp(wxKeyEvent &event); void OnChar(wxKeyEvent &event); void OnRightDown(wxMouseEvent& event); - int get_extruder_for_tick(int tick); void OnRightUp(wxMouseEvent& event); - void add_code(std::string code, int selected_extruder = -1); + + void add_code_as_tick(std::string code, int selected_extruder = -1); + // add default action for tick, when press "+" + void add_current_tick(bool call_from_keyboard = false); + // delete current tick, when press "-" + void delete_current_tick(); void edit_tick(); - void change_extruder(int extruder); void edit_extruder_sequence(); + struct TICK_CODE + { + bool operator<(const TICK_CODE& other) const { return other.tick > this->tick; } + bool operator>(const TICK_CODE& other) const { return other.tick < this->tick; } + + int tick = 0; + std::string gcode = Slic3r::ColorChangeCode; + int extruder = 0; + std::string color; + }; + protected: void render(); @@ -878,11 +885,11 @@ protected: void detect_selected_slider(const wxPoint& pt); void correct_lower_value(); void correct_higher_value(); - wxString get_tooltip(IconFocus icon_focus); void move_current_thumb(const bool condition); - void action_tick(const TicksAction action); void enter_window(wxMouseEvent& event, const bool enter); +private: + bool is_point_in_rect(const wxPoint& pt, const wxRect& rect); int is_point_near_tick(const wxPoint& pt); @@ -894,8 +901,18 @@ protected: wxSize get_size(); void get_size(int *w, int *h); double get_double_value(const SelectedSlider& selection); + wxString get_tooltip(IconFocus icon_focus); + + std::string get_color_for_tool_change_tick(std::set::const_iterator it) const; + std::string get_color_for_color_change_tick(std::set::const_iterator it) const; + int get_extruder_for_tick(int tick); + std::set get_used_extruders_for_tick(int tick); + + void post_ticks_changed_event(const std::string& gcode = ""); + bool check_ticks_changed_event(const std::string& gcode); + void append_change_extruder_menu_item(wxMenu*); + void append_add_color_change_menu_item(wxMenu*); -private: bool is_osx { false }; wxFont m_font; int m_min_value; @@ -914,7 +931,7 @@ private: ScalableBitmap m_bmp_one_layer_unlock_off; ScalableBitmap m_bmp_revert; ScalableBitmap m_bmp_cog; - SelectedSlider m_selection; + SelectedSlider m_selection; bool m_is_left_down = false; bool m_is_right_down = false; bool m_is_one_layer = false; @@ -924,11 +941,12 @@ private: bool m_is_enabled_tick_manipulation = true; bool m_show_context_menu = false; bool m_show_edit_menu = false; - bool m_edit_extruder_sequence = false; - bool m_suppress_add_code = false; - ManipulationState m_state = msSingleExtruder; - std::string m_custom_gcode = ""; - std::string m_pause_print_msg; + bool m_force_edit_extruder_sequence = false; + bool m_force_mode_apply = true; + bool m_force_add_tick = false; + bool m_force_delete_tick = false; + t_mode m_mode = t_mode::SingleExtruder; + int m_only_extruder = -1; wxRect m_rect_lower_thumb; wxRect m_rect_higher_thumb; @@ -957,50 +975,44 @@ private: std::vector m_line_pens; std::vector m_segm_pens; - std::set m_ticks; std::vector m_values; - struct TICK_CODE + struct TICK_CODE_INFO { - bool operator<(const TICK_CODE& other) const { return other.tick > this->tick; } - bool operator>(const TICK_CODE& other) const { return other.tick < this->tick; } + std::set ticks; + t_mode mode = t_mode::SingleExtruder; - int tick = 0; - std::string gcode = Slic3r::ColorChangeCode; - int extruder = 0; - std::string color; - }; + bool empty() const { return ticks.empty(); } + void set_pause_print_msg(const std::string& message) { pause_print_msg = message; } - std::set m_ticks_; + bool add_tick (const int tick, std::string &code, int extruder, double print_z); + bool edit_tick (std::set::iterator it, double print_z); + void switch_code(const std::string& code_from, const std::string& code_to); + void erase_all_ticks_with_code (const std::string& gcode); + bool has_tick_with_code (const std::string& gcode); + + void suppress_plus (bool suppress) { m_suppress_plus = suppress;} + void suppress_minus(bool suppress) { m_suppress_minus = suppress;} + bool suppressed_plus () { return m_suppress_plus ; } + bool suppressed_minus() { return m_suppress_minus; } + + private: + + std::string custom_gcode = ""; + std::string pause_print_msg = ""; + bool m_suppress_plus = false; + bool m_suppress_minus = false; + } + m_ticks; public: struct ExtrudersSequence { - bool is_mm_intervals; - double interval_by_mm; - int interval_by_layers; - std::vector extruders; + bool is_mm_intervals = true; + double interval_by_mm = 3.0; + int interval_by_layers = 10; + std::vector extruders = { 0 }; - ExtrudersSequence() : - is_mm_intervals(true), - interval_by_mm(3.0), - interval_by_layers(10), - extruders({ 0 }) {} - - ExtrudersSequence(const ExtrudersSequence& other) : - is_mm_intervals(other.is_mm_intervals), - interval_by_mm(other.interval_by_mm), - interval_by_layers(other.interval_by_layers), - extruders(other.extruders) {} - - ExtrudersSequence& operator=(const ExtrudersSequence& other) { - this->is_mm_intervals = other.is_mm_intervals; - this->interval_by_mm = other.interval_by_mm; - this->interval_by_layers= other.interval_by_layers; - this->extruders = other.extruders; - - return *this; - } bool operator==(const ExtrudersSequence& other) const { return (other.is_mm_intervals == this->is_mm_intervals ) && diff --git a/src/slic3r/Utils/FixModelByWin10.cpp b/src/slic3r/Utils/FixModelByWin10.cpp index b7eb12e02..28f8b54d5 100644 --- a/src/slic3r/Utils/FixModelByWin10.cpp +++ b/src/slic3r/Utils/FixModelByWin10.cpp @@ -363,10 +363,17 @@ void fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx) ModelObject *model_object = model.add_object(); model_object->add_volume(*volumes[ivolume]); model_object->add_instance(); +#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF + 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")); + } +#else if (! Slic3r::store_3mf(path_src.string().c_str(), &model, nullptr)) { boost::filesystem::remove(path_src); throw std::runtime_error(L("Export of a temporary 3mf file failed")); } +#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF model.clear_objects(); model.clear_materials(); boost::filesystem::path path_dst = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path(); diff --git a/src/slic3r/Utils/FlashAir.cpp b/src/slic3r/Utils/FlashAir.cpp index 79f3f2c2b..735e4a70a 100644 --- a/src/slic3r/Utils/FlashAir.cpp +++ b/src/slic3r/Utils/FlashAir.cpp @@ -79,7 +79,6 @@ bool FlashAir::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, Error const auto upload_filename = upload_data.upload_path.filename(); const auto upload_parent_path = upload_data.upload_path.parent_path(); - wxString test_msg; if (! test(test_msg)) { error_fn(std::move(test_msg)); @@ -88,7 +87,14 @@ bool FlashAir::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, Error bool res = false; + std::string strDest = upload_parent_path.string(); + if (strDest.front()!='/') // Needs a leading / else root uploads fail. + { + strDest.insert(0,"/"); + } + auto urlPrepare = make_url("upload.cgi", "WRITEPROTECT=ON&FTIME", timestamp_str()); + auto urlSetDir = make_url("upload.cgi","UPDIR",strDest); auto urlUpload = make_url("upload.cgi"); BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% at %3% / %4%, filename: %5%") @@ -101,7 +107,7 @@ bool FlashAir::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, Error // set filetime for upload and make card writeprotect to prevent filesystem damage auto httpPrepare = Http::get(std::move(urlPrepare)); httpPrepare.on_error([&](std::string body, std::string error, unsigned status) { - BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error prepareing upload: %2%, HTTP %3%, body: `%4%`") % name % error % status % body; + BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error preparing upload: %2%, HTTP %3%, body: `%4%`") % name % error % status % body; error_fn(format_error(body, error, status)); res = false; }) @@ -120,6 +126,26 @@ bool FlashAir::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, Error } // start file upload + auto httpDir = Http::get(std::move(urlSetDir)); + httpDir.on_error([&](std::string body, std::string error, unsigned status) { + BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error setting upload dir: %2%, HTTP %3%, body: `%4%`") % name % error % status % body; + error_fn(format_error(body, error, status)); + res = false; + }) + .on_complete([&, this](std::string body, unsigned) { + BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got dir select result: %2%") % name % body; + res = boost::icontains(body, "SUCCESS"); + if (! res) { + BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Request completed but no SUCCESS message was received.") % name; + error_fn(format_error(body, L("Unknown error occured"), 0)); + } + }) + .perform_sync(); + + if(! res ) { + return res; + } + auto http = Http::post(std::move(urlUpload)); http.form_add_file("file", upload_data.source_path.string(), upload_filename.string()) .on_complete([&](std::string body, unsigned status) { diff --git a/t/custom_gcode.t b/t/custom_gcode.t index 4c1c1b108..44f952318 100644 --- a/t/custom_gcode.t +++ b/t/custom_gcode.t @@ -1,4 +1,4 @@ -use Test::More tests => 81; +use Test::More tests => 41; use strict; use warnings; @@ -45,70 +45,6 @@ use Slic3r::Test; #========================================================== -{ - my $parser = Slic3r::GCode::PlaceholderParser->new; - my $config = Slic3r::Config::new_from_defaults; - $config->set('printer_notes', ' PRINTER_VENDOR_PRUSA3D PRINTER_MODEL_MK2 '); - $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); - $parser->apply_config($config); - $parser->set('foo' => 0); - $parser->set('bar' => 2); - $parser->set('num_extruders' => 4); - is $parser->process('[temperature_[foo]]'), - $config->temperature->[0], - "nested config options (legacy syntax)"; - is $parser->process('{temperature[foo]}'), - $config->temperature->[0], - "array reference"; - is $parser->process("test [ temperature_ [foo] ] \n hu"), - "test " . $config->temperature->[0] . " \n hu", - "whitespaces and newlines are maintained"; - is $parser->process('{2*3}'), '6', 'math: 2*3'; - is $parser->process('{2*3/6}'), '1', 'math: 2*3/6'; - is $parser->process('{2*3/12}'), '0', 'math: 2*3/12'; - ok abs($parser->process('{2.*3/12}') - 0.5) < 1e-7, 'math: 2.*3/12'; - is $parser->process('{2*(3-12)}'), '-18', 'math: 2*(3-12)'; - is $parser->process('{2*foo*(3-12)}'), '0', 'math: 2*foo*(3-12)'; - is $parser->process('{2*bar*(3-12)}'), '-36', 'math: 2*bar*(3-12)'; - ok abs($parser->process('{2.5*bar*(3-12)}') - -45) < 1e-7, 'math: 2.5*bar*(3-12)'; - is $parser->process('{min(12, 14)}'), '12', 'math: min(12, 14)'; - is $parser->process('{max(12, 14)}'), '14', 'math: max(12, 14)'; - is $parser->process('{min(13.4, -1238.1)}'), '-1238.1', 'math: min(13.4, -1238.1)'; - is $parser->process('{max(13.4, -1238.1)}'), '13.4', 'math: max(13.4, -1238.1)'; - - # Test the boolean expression parser. - is $parser->evaluate_boolean_expression('12 == 12'), 1, 'boolean expression parser: 12 == 12'; - is $parser->evaluate_boolean_expression('12 != 12'), 0, 'boolean expression parser: 12 != 12'; - is $parser->evaluate_boolean_expression('"has some PATTERN embedded" =~ /.*PATTERN.*/'), 1, 'boolean expression parser: regex matches'; - is $parser->evaluate_boolean_expression('"has some PATTERN embedded" =~ /.*PTRN.*/'), 0, 'boolean expression parser: regex does not match'; - is $parser->evaluate_boolean_expression('foo + 2 == bar'), 1, 'boolean expression parser: accessing variables, equal'; - is $parser->evaluate_boolean_expression('foo + 3 == bar'), 0, 'boolean expression parser: accessing variables, not equal'; - - is $parser->evaluate_boolean_expression('(12 == 12) and (13 != 14)'), 1, 'boolean expression parser: (12 == 12) and (13 != 14)'; - is $parser->evaluate_boolean_expression('(12 == 12) && (13 != 14)'), 1, 'boolean expression parser: (12 == 12) && (13 != 14)'; - is $parser->evaluate_boolean_expression('(12 == 12) or (13 == 14)'), 1, 'boolean expression parser: (12 == 12) or (13 == 14)'; - is $parser->evaluate_boolean_expression('(12 == 12) || (13 == 14)'), 1, 'boolean expression parser: (12 == 12) || (13 == 14)'; - is $parser->evaluate_boolean_expression('(12 == 12) and not (13 == 14)'), 1, 'boolean expression parser: (12 == 12) and not (13 == 14)'; - is $parser->evaluate_boolean_expression('(12 == 12) ? (1 - 1 == 0) : (2 * 2 == 3)'), 1, 'boolean expression parser: ternary true'; - is $parser->evaluate_boolean_expression('(12 == 21/2) ? (1 - 1 == 0) : (2 * 2 == 3)'), 0, 'boolean expression parser: ternary false'; - is $parser->evaluate_boolean_expression('(12 == 13) ? (1 - 1 == 3) : (2 * 2 == 4)'), 1, 'boolean expression parser: ternary false'; - is $parser->evaluate_boolean_expression('(12 == 2 * 6) ? (1 - 1 == 3) : (2 * 2 == 4)'), 0, 'boolean expression parser: ternary true'; - is $parser->evaluate_boolean_expression('12 < 3'), 0, 'boolean expression parser: lower than - false'; - is $parser->evaluate_boolean_expression('12 < 22'), 1, 'boolean expression parser: lower than - true'; - is $parser->evaluate_boolean_expression('12 > 3'), 1, 'boolean expression parser: greater than - true'; - is $parser->evaluate_boolean_expression('12 > 22'), 0, 'boolean expression parser: greater than - false'; - is $parser->evaluate_boolean_expression('12 <= 3'), 0, 'boolean expression parser: lower than or equal- false'; - is $parser->evaluate_boolean_expression('12 <= 22'), 1, 'boolean expression parser: lower than or equal - true'; - is $parser->evaluate_boolean_expression('12 >= 3'), 1, 'boolean expression parser: greater than or equal - true'; - is $parser->evaluate_boolean_expression('12 >= 22'), 0, 'boolean expression parser: greater than or equal - false'; - is $parser->evaluate_boolean_expression('12 <= 12'), 1, 'boolean expression parser: lower than or equal (same values) - true'; - is $parser->evaluate_boolean_expression('12 >= 12'), 1, 'boolean expression parser: greater than or equal (same values) - true'; - - is $parser->evaluate_boolean_expression('printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 and num_extruders>1'), 1, 'complex expression'; - is $parser->evaluate_boolean_expression('printer_notes=~/.*PRINTER_VEwerfNDOR_PRUSA3D.*/ or printer_notes=~/.*PRINTertER_MODEL_MK2.*/ or (nozzle_diameter[0]==0.6 and num_extruders>1)'), 1, 'complex expression2'; - is $parser->evaluate_boolean_expression('printer_notes=~/.*PRINTER_VEwerfNDOR_PRUSA3D.*/ or printer_notes=~/.*PRINTertER_MODEL_MK2.*/ or (nozzle_diameter[0]==0.3 and num_extruders>1)'), 0, 'complex expression3'; -} - { my $config = Slic3r::Config::new_from_defaults; $config->set('output_filename_format', 'ts_[travel_speed]_lh_[layer_height].gcode'); diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index 0f9d447b3..8ee43acc8 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -8,6 +8,7 @@ add_executable(${_TEST_NAME}_tests test_config.cpp test_elephant_foot_compensation.cpp test_geometry.cpp + test_placeholder_parser.cpp test_polygon.cpp test_stl.cpp ) diff --git a/tests/libslic3r/test_3mf.cpp b/tests/libslic3r/test_3mf.cpp index f3e30ea61..6b60182b5 100644 --- a/tests/libslic3r/test_3mf.cpp +++ b/tests/libslic3r/test_3mf.cpp @@ -51,7 +51,11 @@ SCENARIO("Export+Import geometry to/from 3mf file cycle", "[3mf]") { WHEN("model is saved+loaded to/from 3mf file") { // save the model to 3mf file std::string test_file = std::string(TEST_DATA_DIR) + "/test_3mf/prusa.3mf"; +#if ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF + store_3mf(test_file.c_str(), &src_model, nullptr, false); +#else store_3mf(test_file.c_str(), &src_model, nullptr); +#endif // ENABLE_CONFIGURABLE_PATHS_EXPORT_TO_3MF_AND_AMF // load back the model from the 3mf file Model dst_model; diff --git a/tests/libslic3r/test_placeholder_parser.cpp b/tests/libslic3r/test_placeholder_parser.cpp new file mode 100644 index 000000000..5802862b7 --- /dev/null +++ b/tests/libslic3r/test_placeholder_parser.cpp @@ -0,0 +1,74 @@ +#include + +#include "libslic3r/PlaceholderParser.hpp" +#include "libslic3r/PrintConfig.hpp" + +using namespace Slic3r; + +SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") { + PlaceholderParser parser; + auto config = DynamicPrintConfig::full_print_config(); + + config.set_deserialize( { + { "printer_notes", " PRINTER_VENDOR_PRUSA3D PRINTER_MODEL_MK2 " }, + { "nozzle_diameter", "0.6;0.6;0.6;0.6" }, + { "temperature", "357;359;363;378" } + }); + parser.apply_config(config); + parser.set("foo", 0); + parser.set("bar", 2); + parser.set("num_extruders", 4); + + SECTION("nested config options (legacy syntax)") { REQUIRE(parser.process("[temperature_[foo]]") == "357"); } + SECTION("array reference") { REQUIRE(parser.process("{temperature[foo]}") == "357"); } + SECTION("whitespaces and newlines are maintained") { REQUIRE(parser.process("test [ temperature_ [foo] ] \n hu") == "test 357 \n hu"); } + + // Test the math expressions. + SECTION("math: 2*3") { REQUIRE(parser.process("{2*3}") == "6"); } + SECTION("math: 2*3/6") { REQUIRE(parser.process("{2*3/6}") == "1"); } + SECTION("math: 2*3/12") { REQUIRE(parser.process("{2*3/12}") == "0"); } + SECTION("math: 2.*3/12") { REQUIRE(std::stod(parser.process("{2.*3/12}")) == Approx(0.5)); } +// SECTION("math: 10 % 2.5") { REQUIRE(parser.process("{10%2.5}") == "0"); } +// SECTION("math: 11 / 2.5") { REQUIRE(parser.process("{11/2.5-1}") == "1"); } + SECTION("math: 2*(3-12)") { REQUIRE(parser.process("{2*(3-12)}") == "-18"); } + SECTION("math: 2*foo*(3-12)") { REQUIRE(parser.process("{2*foo*(3-12)}") == "0"); } + SECTION("math: 2*bar*(3-12)") { REQUIRE(parser.process("{2*bar*(3-12)}") == "-36"); } + SECTION("math: 2.5*bar*(3-12)") { REQUIRE(std::stod(parser.process("{2.5*bar*(3-12)}")) == Approx(-45)); } + SECTION("math: min(12, 14)") { REQUIRE(parser.process("{min(12, 14)}") == "12"); } + SECTION("math: max(12, 14)") { REQUIRE(parser.process("{max(12, 14)}") == "14"); } + SECTION("math: min(13.4, -1238.1)") { REQUIRE(std::stod(parser.process("{min(13.4, -1238.1)}")) == Approx(-1238.1)); } + SECTION("math: max(13.4, -1238.1)") { REQUIRE(std::stod(parser.process("{max(13.4, -1238.1)}")) == Approx(13.4)); } +// SECTION("math: int(13.4)") { REQUIRE(parser.process("{int(13.4)}") == "13"); } + + // Test the boolean expression parser. + auto boolean_expression = [&parser](const std::string& templ) { return parser.evaluate_boolean_expression(templ, parser.config()); }; + + SECTION("boolean expression parser: 12 == 12") { REQUIRE(boolean_expression("12 == 12")); } + SECTION("boolean expression parser: 12 != 12") { REQUIRE(! boolean_expression("12 != 12")); } + SECTION("boolean expression parser: regex matches") { REQUIRE(boolean_expression("\"has some PATTERN embedded\" =~ /.*PATTERN.*/")); } + SECTION("boolean expression parser: regex does not match") { REQUIRE(! boolean_expression("\"has some PATTERN embedded\" =~ /.*PTRN.*/")); } + SECTION("boolean expression parser: accessing variables, equal") { REQUIRE(boolean_expression("foo + 2 == bar")); } + SECTION("boolean expression parser: accessing variables, not equal") { REQUIRE(! boolean_expression("foo + 3 == bar")); } + SECTION("boolean expression parser: (12 == 12) and (13 != 14)") { REQUIRE(boolean_expression("(12 == 12) and (13 != 14)")); } + SECTION("boolean expression parser: (12 == 12) && (13 != 14)") { REQUIRE(boolean_expression("(12 == 12) && (13 != 14)")); } + SECTION("boolean expression parser: (12 == 12) or (13 == 14)") { REQUIRE(boolean_expression("(12 == 12) or (13 == 14)")); } + SECTION("boolean expression parser: (12 == 12) || (13 == 14)") { REQUIRE(boolean_expression("(12 == 12) || (13 == 14)")); } + SECTION("boolean expression parser: (12 == 12) and not (13 == 14)") { REQUIRE(boolean_expression("(12 == 12) and not (13 == 14)")); } + SECTION("boolean expression parser: ternary true") { REQUIRE(boolean_expression("(12 == 12) ? (1 - 1 == 0) : (2 * 2 == 3)")); } + SECTION("boolean expression parser: ternary false") { REQUIRE(! boolean_expression("(12 == 21/2) ? (1 - 1 == 0) : (2 * 2 == 3)")); } + SECTION("boolean expression parser: ternary false 2") { REQUIRE(boolean_expression("(12 == 13) ? (1 - 1 == 3) : (2 * 2 == 4)")); } + SECTION("boolean expression parser: ternary true 2") { REQUIRE(! boolean_expression("(12 == 2 * 6) ? (1 - 1 == 3) : (2 * 2 == 4)")); } + SECTION("boolean expression parser: lower than - false") { REQUIRE(! boolean_expression("12 < 3")); } + SECTION("boolean expression parser: lower than - true") { REQUIRE(boolean_expression("12 < 22")); } + SECTION("boolean expression parser: greater than - true") { REQUIRE(boolean_expression("12 > 3")); } + SECTION("boolean expression parser: greater than - false") { REQUIRE(! boolean_expression("12 > 22")); } + SECTION("boolean expression parser: lower than or equal- false") { REQUIRE(! boolean_expression("12 <= 3")); } + SECTION("boolean expression parser: lower than or equal - true") { REQUIRE(boolean_expression("12 <= 22")); } + SECTION("boolean expression parser: greater than or equal - true") { REQUIRE(boolean_expression("12 >= 3")); } + SECTION("boolean expression parser: greater than or equal - false") { REQUIRE(! boolean_expression("12 >= 22")); } + SECTION("boolean expression parser: lower than or equal (same values) - true") { REQUIRE(boolean_expression("12 <= 12")); } + SECTION("boolean expression parser: greater than or equal (same values) - true") { REQUIRE(boolean_expression("12 >= 12")); } + SECTION("complex expression") { REQUIRE(boolean_expression("printer_notes=~/.*PRINTER_VENDOR_PRUSA3D.*/ and printer_notes=~/.*PRINTER_MODEL_MK2.*/ and nozzle_diameter[0]==0.6 and num_extruders>1")); } + SECTION("complex expression2") { REQUIRE(boolean_expression("printer_notes=~/.*PRINTER_VEwerfNDOR_PRUSA3D.*/ or printer_notes=~/.*PRINTertER_MODEL_MK2.*/ or (nozzle_diameter[0]==0.6 and num_extruders>1)")); } + SECTION("complex expression3") { REQUIRE(! boolean_expression("printer_notes=~/.*PRINTER_VEwerfNDOR_PRUSA3D.*/ or printer_notes=~/.*PRINTertER_MODEL_MK2.*/ or (nozzle_diameter[0]==0.3 and num_extruders>1)")); } +}