diff --git a/resources/icons/PrusaSlicer-gcodeviewer_128px.png b/resources/icons/PrusaSlicer-gcodeviewer_128px.png new file mode 100644 index 000000000..d8e3e438b Binary files /dev/null and b/resources/icons/PrusaSlicer-gcodeviewer_128px.png differ diff --git a/resources/icons/PrusaSlicer-gcodeviewer_192px.png b/resources/icons/PrusaSlicer-gcodeviewer_192px.png new file mode 100644 index 000000000..0e0243012 Binary files /dev/null and b/resources/icons/PrusaSlicer-gcodeviewer_192px.png differ diff --git a/resources/icons/PrusaSlicerGCodeViewer_128px.png b/resources/icons/PrusaSlicerGCodeViewer_128px.png deleted file mode 100644 index 0bf85abbd..000000000 Binary files a/resources/icons/PrusaSlicerGCodeViewer_128px.png and /dev/null differ diff --git a/resources/icons/add.svg b/resources/icons/add.svg index 8a9b253de..37050d748 100644 --- a/resources/icons/add.svg +++ b/resources/icons/add.svg @@ -1,17 +1,22 @@ - + - - + + + + + + + diff --git a/resources/icons/arrange.svg b/resources/icons/arrange.svg index 4f30e979e..62cf939e9 100644 --- a/resources/icons/arrange.svg +++ b/resources/icons/arrange.svg @@ -1,24 +1,23 @@ - + - - + - + - + - + - + diff --git a/resources/icons/copy.svg b/resources/icons/copy.svg index 9b8430dd7..345c2590b 100644 --- a/resources/icons/copy.svg +++ b/resources/icons/copy.svg @@ -1,37 +1,29 @@ - + - - - - - + + - - - - - + + diff --git a/resources/icons/delete_all.svg b/resources/icons/delete_all.svg index 80e2e503c..dfa943812 100644 --- a/resources/icons/delete_all.svg +++ b/resources/icons/delete_all.svg @@ -1,31 +1,17 @@ - + - - + - + - - - - - - - - - - + diff --git a/resources/icons/instance_add.svg b/resources/icons/instance_add.svg index 5ef492cfa..a466c51db 100644 --- a/resources/icons/instance_add.svg +++ b/resources/icons/instance_add.svg @@ -1,50 +1,46 @@ - + - + - + - + + + + diff --git a/resources/icons/instance_remove.svg b/resources/icons/instance_remove.svg index 466752ea8..7f9b4f7e1 100644 --- a/resources/icons/instance_remove.svg +++ b/resources/icons/instance_remove.svg @@ -1,49 +1,42 @@ - + - + - + - + diff --git a/resources/icons/paste.svg b/resources/icons/paste.svg index 028ffb8ea..bcfe567de 100644 --- a/resources/icons/paste.svg +++ b/resources/icons/paste.svg @@ -1,27 +1,22 @@ - + - + - - - - - + + diff --git a/resources/icons/prusa_slicer_logo.svg b/resources/icons/prusa_slicer_logo.svg new file mode 100644 index 000000000..927c3e70b --- /dev/null +++ b/resources/icons/prusa_slicer_logo.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/resources/icons/redo_toolbar.svg b/resources/icons/redo_toolbar.svg index d005f8373..d2aca2cc7 100644 --- a/resources/icons/redo_toolbar.svg +++ b/resources/icons/redo_toolbar.svg @@ -1,13 +1,17 @@ - + - + viewBox="0 0 128 128" enable-background="new 0 0 128 128" xml:space="preserve"> + + + + + + + + diff --git a/resources/icons/remove.svg b/resources/icons/remove.svg index acd21256c..1bb830d91 100644 --- a/resources/icons/remove.svg +++ b/resources/icons/remove.svg @@ -1,44 +1,60 @@ - + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/seam.svg b/resources/icons/seam.svg index 119fb6afc..a7e7980cc 100644 --- a/resources/icons/seam.svg +++ b/resources/icons/seam.svg @@ -1,42 +1,35 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/search_.svg b/resources/icons/search_.svg index 679bb30f7..2985ceb56 100644 --- a/resources/icons/search_.svg +++ b/resources/icons/search_.svg @@ -1,4 +1,26 @@ - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/search_blink.svg b/resources/icons/search_blink.svg new file mode 100644 index 000000000..d005f8373 --- /dev/null +++ b/resources/icons/search_blink.svg @@ -0,0 +1,13 @@ + + + + + diff --git a/resources/icons/settings.svg b/resources/icons/settings.svg new file mode 100644 index 000000000..db5bf458d --- /dev/null +++ b/resources/icons/settings.svg @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons/splashscreen-gcodeviewer.jpg b/resources/icons/splashscreen-gcodeviewer.jpg new file mode 100644 index 000000000..f170f390c Binary files /dev/null and b/resources/icons/splashscreen-gcodeviewer.jpg differ diff --git a/resources/icons/splashscreen.jpg b/resources/icons/splashscreen.jpg new file mode 100644 index 000000000..86c55f30f Binary files /dev/null and b/resources/icons/splashscreen.jpg differ diff --git a/resources/icons/split_objects.svg b/resources/icons/split_objects.svg index a7ccc5df8..e822fd35a 100644 --- a/resources/icons/split_objects.svg +++ b/resources/icons/split_objects.svg @@ -1,19 +1,20 @@ - + - + - + - - + + + + diff --git a/resources/icons/split_parts.svg b/resources/icons/split_parts.svg index 82a292770..5cfef0f33 100644 --- a/resources/icons/split_parts.svg +++ b/resources/icons/split_parts.svg @@ -1,18 +1,20 @@ - + - + - + - - + + + + diff --git a/resources/icons/undo_toolbar.svg b/resources/icons/undo_toolbar.svg index 15778a7ba..2fc25bf73 100644 --- a/resources/icons/undo_toolbar.svg +++ b/resources/icons/undo_toolbar.svg @@ -1,13 +1,17 @@ - + - + viewBox="0 0 128 128" enable-background="new 0 0 128 128" xml:space="preserve"> + + + + + + + + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0b0b3c0ee..ca57ca553 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -106,9 +106,9 @@ if (MINGW) set_target_properties(PrusaSlicer PROPERTIES PREFIX "") endif (MINGW) -if (NOT WIN32) - # Binary name on unix like systems (OSX, Linux) - set_target_properties(PrusaSlicer PROPERTIES OUTPUT_NAME "prusa-slicer") +if (NOT WIN32 AND NOT APPLE) + # Binary name on unix like systems (Linux, Unix) + set_target_properties(PrusaSlicer PROPERTIES OUTPUT_NAME "prusa-slicer") endif () target_link_libraries(PrusaSlicer libslic3r cereal) @@ -209,20 +209,34 @@ if (WIN32) add_custom_target(PrusaSlicerDllsCopy ALL DEPENDS PrusaSlicer) prusaslicer_copy_dlls(PrusaSlicerDllsCopy) -elseif (XCODE) - # Because of Debug/Release/etc. configurations (similar to MSVC) the slic3r binary is located in an extra level - add_custom_command(TARGET PrusaSlicer POST_BUILD - COMMAND ln -sfn "${SLIC3R_RESOURCES_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/resources" - COMMENT "Symlinking the resources directory into the build tree" - VERBATIM - ) else () + if (APPLE) + # On OSX, the name of the binary matches the name of the Application. + add_custom_command(TARGET PrusaSlicer POST_BUILD + COMMAND ln -sf PrusaSlicer prusa-slicer + COMMAND ln -sf PrusaSlicer prusa-gcodeviewer + COMMAND ln -sf PrusaSlicer PrusaGCodeViewer + WORKING_DIRECTORY "$" + COMMENT "Symlinking the G-code viewer to PrusaSlicer, symlinking to prusa-slicer and prusa-gcodeviewer" + VERBATIM) + else () + add_custom_command(TARGET PrusaSlicer POST_BUILD + COMMAND ln -sf prusa-slicer prusa-gcodeviewer + WORKING_DIRECTORY "$" + COMMENT "Symlinking the G-code viewer to PrusaSlicer" + VERBATIM) + endif () + if (XCODE) + # Because of Debug/Release/etc. configurations (similar to MSVC) the slic3r binary is located in an extra level + set(BIN_RESOURCES_DIR "${CMAKE_CURRENT_BINARY_DIR}/resources") + else () + set(BIN_RESOURCES_DIR "${CMAKE_CURRENT_BINARY_DIR}/../resources") + endif () add_custom_command(TARGET PrusaSlicer POST_BUILD - COMMAND ln -sfn "${SLIC3R_RESOURCES_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/../resources" + COMMAND ln -sfn "${SLIC3R_RESOURCES_DIR}" "${BIN_RESOURCES_DIR}" COMMENT "Symlinking the resources directory into the build tree" - VERBATIM - ) -endif() + VERBATIM) +endif () # Slic3r binary install target if (WIN32) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 2962f0cdf..a12ad8bb7 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -101,8 +102,14 @@ int CLI::run(int argc, char **argv) std::find(m_transforms.begin(), m_transforms.end(), "cut") == m_transforms.end() && std::find(m_transforms.begin(), m_transforms.end(), "cut_x") == m_transforms.end() && std::find(m_transforms.begin(), m_transforms.end(), "cut_y") == m_transforms.end(); - bool start_as_gcodeviewer = false; - + bool start_as_gcodeviewer = +#ifdef _WIN32 + false; +#else + // On Unix systems, the prusa-slicer binary may be symlinked to give the application a different meaning. + boost::algorithm::iends_with(boost::filesystem::path(argv[0]).filename().string(), "gcodeviewer"); +#endif // _WIN32 + const std::vector &load_configs = m_config.option("load", true)->values; // load config files supplied via --load @@ -133,37 +140,57 @@ int CLI::run(int argc, char **argv) m_print_config.apply(config); } - // Read input file(s) if any. - for (const std::string &file : m_input_files) { - if (! boost::filesystem::exists(file)) { - boost::nowide::cerr << "No such file: " << file << std::endl; - exit(1); +#if ENABLE_GCODE_VIEWER + // are we starting as gcodeviewer ? + for (auto it = m_actions.begin(); it != m_actions.end(); ++it) { + if (*it == "gcodeviewer") { + start_gui = true; + start_as_gcodeviewer = true; + m_actions.erase(it); + break; } - Model model; - try { - // When loading an AMF or 3MF, config is imported as well, including the printer technology. - DynamicPrintConfig config; - model = Model::read_from_file(file, &config, true); - PrinterTechnology other_printer_technology = Slic3r::printer_technology(config); - if (printer_technology == ptUnknown) { - printer_technology = other_printer_technology; - } else if (printer_technology != other_printer_technology && other_printer_technology != ptUnknown) { - boost::nowide::cerr << "Mixing configurations for FFF and SLA technologies" << std::endl; + } +#endif // ENABLE_GCODE_VIEWER + + // Read input file(s) if any. +#if ENABLE_GCODE_VIEWER + if (!start_as_gcodeviewer) { +#endif // ENABLE_GCODE_VIEWER + for (const std::string& file : m_input_files) { + if (!boost::filesystem::exists(file)) { + boost::nowide::cerr << "No such file: " << file << std::endl; + exit(1); + } + Model model; + try { + // When loading an AMF or 3MF, config is imported as well, including the printer technology. + DynamicPrintConfig config; + model = Model::read_from_file(file, &config, true); + PrinterTechnology other_printer_technology = Slic3r::printer_technology(config); + if (printer_technology == ptUnknown) { + printer_technology = other_printer_technology; + } + else if (printer_technology != other_printer_technology && other_printer_technology != ptUnknown) { + boost::nowide::cerr << "Mixing configurations for FFF and SLA technologies" << std::endl; + return 1; + } + // config is applied to m_print_config before the current m_config values. + config += std::move(m_print_config); + m_print_config = std::move(config); + } + catch (std::exception& e) { + boost::nowide::cerr << file << ": " << e.what() << std::endl; return 1; } - // config is applied to m_print_config before the current m_config values. - config += std::move(m_print_config); - m_print_config = std::move(config); - } catch (std::exception &e) { - boost::nowide::cerr << file << ": " << e.what() << std::endl; - return 1; + if (model.objects.empty()) { + boost::nowide::cerr << "Error: file is empty: " << file << std::endl; + continue; + } + m_models.push_back(model); } - if (model.objects.empty()) { - boost::nowide::cerr << "Error: file is empty: " << file << std::endl; - continue; - } - m_models.push_back(model); +#if ENABLE_GCODE_VIEWER } +#endif // ENABLE_GCODE_VIEWER // Apply command line options to a more specific DynamicPrintConfig which provides normalize() // (command line options override --load files) @@ -522,9 +549,11 @@ int CLI::run(int argc, char **argv) << " (" << print.total_extruded_volume()/1000 << "cm3)" << std::endl; */ } +#if !ENABLE_GCODE_VIEWER } else if (opt_key == "gcodeviewer") { - start_gui = true; + start_gui = true; start_as_gcodeviewer = true; +#endif // !ENABLE_GCODE_VIEWER } else { boost::nowide::cerr << "error: option not supported yet: " << opt_key << std::endl; return 1; @@ -534,7 +563,11 @@ int CLI::run(int argc, char **argv) if (start_gui) { #ifdef SLIC3R_GUI // #ifdef USE_WX +#if ENABLE_GCODE_VIEWER + GUI::GUI_App* gui = new GUI::GUI_App(start_as_gcodeviewer ? GUI::GUI_App::EAppMode::GCodeViewer : GUI::GUI_App::EAppMode::Editor); +#else GUI::GUI_App *gui = new GUI::GUI_App(); +#endif // ENABLE_GCODE_VIEWER bool gui_single_instance_setting = gui->app_config->get("single_instance") == "1"; if (Slic3r::instance_check(argc, argv, gui_single_instance_setting)) { @@ -544,28 +577,42 @@ int CLI::run(int argc, char **argv) // gui->autosave = m_config.opt_string("autosave"); GUI::GUI_App::SetInstance(gui); +#if ENABLE_GCODE_VIEWER + gui->CallAfter([gui, this, &load_configs, start_as_gcodeviewer] { +#else gui->CallAfter([gui, this, &load_configs] { +#endif // ENABLE_GCODE_VIEWER if (!gui->initialized()) { return; } + +#if ENABLE_GCODE_VIEWER + if (start_as_gcodeviewer) { + if (!m_input_files.empty()) + gui->plater()->load_gcode(wxString::FromUTF8(m_input_files[0].c_str())); + } else { +#endif // ENABLE_GCODE_VIEWER_AS #if 0 - // Load the cummulative config over the currently active profiles. - //FIXME if multiple configs are loaded, only the last one will have an effect. - // We need to decide what to do about loading of separate presets (just print preset, just filament preset etc). - // As of now only the full configs are supported here. - if (!m_print_config.empty()) - gui->mainframe->load_config(m_print_config); + // Load the cummulative config over the currently active profiles. + //FIXME if multiple configs are loaded, only the last one will have an effect. + // We need to decide what to do about loading of separate presets (just print preset, just filament preset etc). + // As of now only the full configs are supported here. + if (!m_print_config.empty()) + gui->mainframe->load_config(m_print_config); #endif - if (! load_configs.empty()) - // Load the last config to give it a name at the UI. The name of the preset may be later - // changed by loading an AMF or 3MF. - //FIXME this is not strictly correct, as one may pass a print/filament/printer profile here instead of a full config. - gui->mainframe->load_config_file(load_configs.back()); - // If loading a 3MF file, the config is loaded from the last one. - if (! m_input_files.empty()) - gui->plater()->load_files(m_input_files, true, true); - if (! m_extra_config.empty()) - gui->mainframe->load_config(m_extra_config); + if (!load_configs.empty()) + // Load the last config to give it a name at the UI. The name of the preset may be later + // changed by loading an AMF or 3MF. + //FIXME this is not strictly correct, as one may pass a print/filament/printer profile here instead of a full config. + gui->mainframe->load_config_file(load_configs.back()); + // If loading a 3MF file, the config is loaded from the last one. + if (!m_input_files.empty()) + gui->plater()->load_files(m_input_files, true, true); + if (!m_extra_config.empty()) + gui->mainframe->load_config(m_extra_config); +#if ENABLE_GCODE_VIEWER + } +#endif // ENABLE_GCODE_VIEWER }); int result = wxEntry(argc, argv); return result; diff --git a/src/admesh/stl.h b/src/admesh/stl.h index 9224b0459..e0f2865f0 100644 --- a/src/admesh/stl.h +++ b/src/admesh/stl.h @@ -255,18 +255,24 @@ extern void its_transform(indexed_triangle_set &its, T *trafo3x4) } template -inline void its_transform(indexed_triangle_set &its, const Eigen::Transform& t) +inline void its_transform(indexed_triangle_set &its, const Eigen::Transform& t, bool fix_left_handed = false) { //const Eigen::Matrix r = t.matrix().template block<3, 3>(0, 0); for (stl_vertex &v : its.vertices) v = (t * v.template cast()).template cast().eval(); + if (fix_left_handed && t.matrix().block(0, 0, 3, 3).determinant() < 0.) + for (stl_triangle_vertex_indices &i : its.indices) + std::swap(i[0], i[1]); } template -inline void its_transform(indexed_triangle_set &its, const Eigen::Matrix& m) +inline void its_transform(indexed_triangle_set &its, const Eigen::Matrix& m, bool fix_left_handed = false) { - for (stl_vertex &v : its.vertices) + for (stl_vertex &v : its.vertices) v = (m * v.template cast()).template cast().eval(); + if (fix_left_handed && m.determinant() < 0.) + for (stl_triangle_vertex_indices &i : its.indices) + std::swap(i[0], i[1]); } extern void its_rotate_x(indexed_triangle_set &its, float angle); diff --git a/src/libslic3r/AABBTreeIndirect.hpp b/src/libslic3r/AABBTreeIndirect.hpp index ec9b14a7a..964133faa 100644 --- a/src/libslic3r/AABBTreeIndirect.hpp +++ b/src/libslic3r/AABBTreeIndirect.hpp @@ -283,7 +283,7 @@ namespace detail { template std::enable_if_t::value && std::is_same::value, bool> - intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) { + intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v) { return intersect_triangle1(const_cast(origin.data()), const_cast(dir.data()), const_cast(v0.data()), const_cast(v1.data()), const_cast(v2.data()), &t, &u, &v); @@ -291,7 +291,7 @@ namespace detail { template std::enable_if_t::value && !std::is_same::value, bool> - intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) { + intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v) { using Vector = Eigen::Matrix; Vector w0 = v0.template cast(); Vector w1 = v1.template cast(); @@ -302,7 +302,7 @@ namespace detail { template std::enable_if_t::value && std::is_same::value, bool> - intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) { + intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v) { using Vector = Eigen::Matrix; Vector o = origin.template cast(); Vector d = dir.template cast(); @@ -311,7 +311,7 @@ namespace detail { template std::enable_if_t::value && ! std::is_same::value, bool> - intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) { + intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W &v2, double &t, double &u, double &v) { using Vector = Eigen::Matrix; Vector o = origin.template cast(); Vector d = dir.template cast(); @@ -692,6 +692,40 @@ inline typename VectorType::Scalar squared_distance_to_indexed_triangle_set( detail::squared_distance_to_indexed_triangle_set_recursive(distancer, size_t(0), Scalar(0), std::numeric_limits::infinity(), hit_idx_out, hit_point_out); } +// Decides if exists some triangle in defined radius on a 3D indexed triangle set using a pre-built AABBTreeIndirect::Tree. +// Closest point to triangle test will be performed with the accuracy of VectorType::Scalar +// even if the triangle mesh and the AABB Tree are built with floats. +// Returns true if exists some triangle in defined radius, false otherwise. +template +inline bool is_any_triangle_in_radius( + // Indexed triangle set - 3D vertices. + const std::vector &vertices, + // Indexed triangle set - triangular faces, references to vertices. + const std::vector &faces, + // AABBTreeIndirect::Tree over vertices & faces, bounding boxes built with the accuracy of vertices. + const TreeType &tree, + // Point to which the closest point on the indexed triangle set is searched for. + const VectorType &point, + // Maximum distance in which triangle is search for + typename VectorType::Scalar &max_distance) +{ + using Scalar = typename VectorType::Scalar; + auto distancer = detail::IndexedTriangleSetDistancer + { vertices, faces, tree, point }; + + size_t hit_idx; + VectorType hit_point = VectorType::Ones() * (std::nan("")); + + if(tree.empty()) + { + return false; + } + + detail::squared_distance_to_indexed_triangle_set_recursive(distancer, size_t(0), Scalar(0), max_distance, hit_idx, hit_point); + + return hit_point.allFinite(); +} + } // namespace AABBTreeIndirect } // namespace Slic3r diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 8b41bd271..72c4fd0e9 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -1,6 +1,7 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/Utils.hpp" #include "AppConfig.hpp" +#include "Exception.hpp" #include #include @@ -101,6 +102,9 @@ void AppConfig::set_defaults() if (get("use_inches").empty()) set("use_inches", "0"); + if (get("show_splash_screen").empty()) + set("show_splash_screen", "1"); + // Remove legacy window positions/sizes erase("", "main_frame_maximized"); erase("", "main_frame_pos"); @@ -123,7 +127,7 @@ std::string AppConfig::load() // ! But to avoid the use of _utf8 (related to use of wxWidgets) // we will rethrow this exception from the place of load() call, if returned value wouldn't be empty /* - throw std::runtime_error( + throw Slic3r::RuntimeError( _utf8(L("Error parsing PrusaSlicer config file, it is probably corrupted. " "Try to manually delete the file to recover from the error. Your user profiles will not be affected.")) + "\n\n" + AppConfig::config_path() + "\n\n" + ex.what()); @@ -179,6 +183,11 @@ std::string AppConfig::load() void AppConfig::save() { +#if ENABLE_GCODE_VIEWER + if (!m_save_enabled) + return; +#endif // ENABLE_GCODE_VIEWER + // The config is first written to a file with a PID suffix and then moved // to avoid race conditions with multiple instances of Slic3r const auto path = config_path(); diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp index ffd1b9fdf..f22a6314a 100644 --- a/src/libslic3r/AppConfig.hpp +++ b/src/libslic3r/AppConfig.hpp @@ -18,6 +18,9 @@ public: AppConfig() : m_dirty(false), m_orig_version(Semver::invalid()), +#if ENABLE_GCODE_VIEWER + m_save_enabled(true), +#endif // ENABLE_GCODE_VIEWER m_legacy_datadir(false) { this->reset(); @@ -157,6 +160,10 @@ public: bool get_mouse_device_swap_yz(const std::string& name, bool& swap) const { return get_3dmouse_device_numeric_value(name, "swap_yz", swap); } +#if ENABLE_GCODE_VIEWER + void enable_save(bool enable) { m_save_enabled = enable; } +#endif // ENABLE_GCODE_VIEWER + static const std::string SECTION_FILAMENTS; static const std::string SECTION_MATERIALS; @@ -183,6 +190,10 @@ private: bool m_dirty; // Original version found in the ini file before it was overwritten Semver m_orig_version; +#if ENABLE_GCODE_VIEWER + // Whether or not calls to save() should take effect + bool m_save_enabled; +#endif // ENABLE_GCODE_VIEWER // Whether the existing version is before system profiles & configuration updating bool m_legacy_datadir; }; diff --git a/src/libslic3r/BoundingBox.cpp b/src/libslic3r/BoundingBox.cpp index e3f939509..eb4e042a0 100644 --- a/src/libslic3r/BoundingBox.cpp +++ b/src/libslic3r/BoundingBox.cpp @@ -75,6 +75,7 @@ BoundingBoxBase::merge(const PointClass &point) } } template void BoundingBoxBase::merge(const Point &point); +template void BoundingBoxBase::merge(const Vec2f &point); template void BoundingBoxBase::merge(const Vec2d &point); template void @@ -101,6 +102,7 @@ BoundingBoxBase::merge(const BoundingBoxBase &bb) } } template void BoundingBoxBase::merge(const BoundingBoxBase &bb); +template void BoundingBoxBase::merge(const BoundingBoxBase &bb); template void BoundingBoxBase::merge(const BoundingBoxBase &bb); template void @@ -115,6 +117,7 @@ BoundingBox3Base::merge(const PointClass &point) this->defined = true; } } +template void BoundingBox3Base::merge(const Vec3f &point); template void BoundingBox3Base::merge(const Vec3d &point); template void @@ -147,6 +150,7 @@ BoundingBoxBase::size() const return PointClass(this->max(0) - this->min(0), this->max(1) - this->min(1)); } template Point BoundingBoxBase::size() const; +template Vec2f BoundingBoxBase::size() const; template Vec2d BoundingBoxBase::size() const; template PointClass @@ -154,6 +158,7 @@ BoundingBox3Base::size() const { return PointClass(this->max(0) - this->min(0), this->max(1) - this->min(1), this->max(2) - this->min(2)); } +template Vec3f BoundingBox3Base::size() const; template Vec3d BoundingBox3Base::size() const; template double BoundingBoxBase::radius() const @@ -200,6 +205,7 @@ BoundingBoxBase::center() const return (this->min + this->max) / 2; } template Point BoundingBoxBase::center() const; +template Vec2f BoundingBoxBase::center() const; template Vec2d BoundingBoxBase::center() const; template PointClass @@ -207,6 +213,7 @@ BoundingBox3Base::center() const { return (this->min + this->max) / 2; } +template Vec3f BoundingBox3Base::center() const; template Vec3d BoundingBox3Base::center() const; template coordf_t @@ -215,6 +222,7 @@ BoundingBox3Base::max_size() const PointClass s = size(); return std::max(s(0), std::max(s(1), s(2))); } +template coordf_t BoundingBox3Base::max_size() const; template coordf_t BoundingBox3Base::max_size() const; // Align a coordinate to a grid. The coordinate may be negative, diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp index 08f01d8d8..065476cb2 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -2,6 +2,7 @@ #define slic3r_BoundingBox_hpp_ #include "libslic3r.h" +#include "Exception.hpp" #include "Point.hpp" #include "Polygon.hpp" @@ -18,11 +19,13 @@ public: BoundingBoxBase() : min(PointClass::Zero()), max(PointClass::Zero()), defined(false) {} BoundingBoxBase(const PointClass &pmin, const PointClass &pmax) : min(pmin), max(pmax), defined(pmin(0) < pmax(0) && pmin(1) < pmax(1)) {} + BoundingBoxBase(const PointClass &p1, const PointClass &p2, const PointClass &p3) : + min(p1), max(p1), defined(false) { merge(p2); merge(p3); } BoundingBoxBase(const std::vector& points) : min(PointClass::Zero()), max(PointClass::Zero()) { if (points.empty()) { this->defined = false; - // throw std::invalid_argument("Empty point set supplied to BoundingBoxBase constructor"); + // throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBoxBase constructor"); } else { typename std::vector::const_iterator it = points.begin(); this->min = *it; @@ -65,10 +68,12 @@ public: BoundingBox3Base(const PointClass &pmin, const PointClass &pmax) : BoundingBoxBase(pmin, pmax) { if (pmin(2) >= pmax(2)) BoundingBoxBase::defined = false; } + BoundingBox3Base(const PointClass &p1, const PointClass &p2, const PointClass &p3) : + BoundingBoxBase(p1, p1) { merge(p2); merge(p3); } BoundingBox3Base(const std::vector& points) { if (points.empty()) - throw std::invalid_argument("Empty point set supplied to BoundingBox3Base constructor"); + throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBox3Base constructor"); typename std::vector::const_iterator it = points.begin(); this->min = *it; this->max = *it; @@ -109,24 +114,32 @@ extern template void BoundingBoxBase::scale(double factor); extern template void BoundingBoxBase::offset(coordf_t delta); extern template void BoundingBoxBase::offset(coordf_t delta); extern template void BoundingBoxBase::merge(const Point &point); +extern template void BoundingBoxBase::merge(const Vec2f &point); extern template void BoundingBoxBase::merge(const Vec2d &point); extern template void BoundingBoxBase::merge(const Points &points); extern template void BoundingBoxBase::merge(const Pointfs &points); extern template void BoundingBoxBase::merge(const BoundingBoxBase &bb); +extern template void BoundingBoxBase::merge(const BoundingBoxBase &bb); extern template void BoundingBoxBase::merge(const BoundingBoxBase &bb); extern template Point BoundingBoxBase::size() const; +extern template Vec2f BoundingBoxBase::size() const; extern template Vec2d BoundingBoxBase::size() const; extern template double BoundingBoxBase::radius() const; extern template double BoundingBoxBase::radius() const; extern template Point BoundingBoxBase::center() const; +extern template Vec2f BoundingBoxBase::center() const; extern template Vec2d BoundingBoxBase::center() const; +extern template void BoundingBox3Base::merge(const Vec3f &point); extern template void BoundingBox3Base::merge(const Vec3d &point); extern template void BoundingBox3Base::merge(const Pointf3s &points); extern template void BoundingBox3Base::merge(const BoundingBox3Base &bb); +extern template Vec3f BoundingBox3Base::size() const; extern template Vec3d BoundingBox3Base::size() const; extern template double BoundingBox3Base::radius() const; extern template void BoundingBox3Base::offset(coordf_t delta); +extern template Vec3f BoundingBox3Base::center() const; extern template Vec3d BoundingBox3Base::center() const; +extern template coordf_t BoundingBox3Base::max_size() const; extern template coordf_t BoundingBox3Base::max_size() const; class BoundingBox : public BoundingBoxBase diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index cde2e9eab..ee41248f3 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -46,6 +46,8 @@ add_library(libslic3r STATIC Fill/Fill.hpp Fill/Fill3DHoneycomb.cpp Fill/Fill3DHoneycomb.hpp + Fill/FillAdaptive.cpp + Fill/FillAdaptive.hpp Fill/FillBase.cpp Fill/FillBase.hpp Fill/FillConcentric.cpp @@ -215,7 +217,9 @@ add_library(libslic3r STATIC SimplifyMeshImpl.hpp SimplifyMesh.cpp MarchingSquares.hpp - Optimizer.hpp + Optimize/Optimizer.hpp + Optimize/NLoptOptimizer.hpp + Optimize/BruteforceOptimizer.hpp ${OpenVDBUtils_SOURCES} SLA/Pad.hpp SLA/Pad.cpp diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index f3f365b47..25ef93430 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -5,7 +5,6 @@ #include #include #include -#include // std::runtime_error #include #include #include @@ -218,7 +217,7 @@ ConfigOption* ConfigOptionDef::create_empty_option() const case coInts: return new ConfigOptionIntsNullable(); case coPercents: return new ConfigOptionPercentsNullable(); case coBools: return new ConfigOptionBoolsNullable(); - default: throw std::runtime_error(std::string("Unknown option type for nullable option ") + this->label); + default: throw Slic3r::RuntimeError(std::string("Unknown option type for nullable option ") + this->label); } } else { switch (this->type) { @@ -238,7 +237,7 @@ ConfigOption* ConfigOptionDef::create_empty_option() const case coBool: return new ConfigOptionBool(); case coBools: return new ConfigOptionBools(); case coEnum: return new ConfigOptionEnumGeneric(this->enum_keys_map); - default: throw std::runtime_error(std::string("Unknown option type for option ") + this->label); + default: throw Slic3r::RuntimeError(std::string("Unknown option type for option ") + this->label); } } } @@ -535,7 +534,7 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key) const return opt_def->ratio_over.empty() ? 0. : static_cast(raw_opt)->get_abs_value(this->get_abs_value(opt_def->ratio_over)); } - throw std::runtime_error("ConfigBase::get_abs_value(): Not a valid option type for get_abs_value()"); + throw Slic3r::RuntimeError("ConfigBase::get_abs_value(): Not a valid option type for get_abs_value()"); } // Return an absolute value of a possibly relative config variable. @@ -546,7 +545,7 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key, double rati const ConfigOption *raw_opt = this->option(opt_key); assert(raw_opt != nullptr); if (raw_opt->type() != coFloatOrPercent) - throw std::runtime_error("ConfigBase::get_abs_value(): opt_key is not of coFloatOrPercent"); + throw Slic3r::RuntimeError("ConfigBase::get_abs_value(): opt_key is not of coFloatOrPercent"); // Compute absolute value. return static_cast(raw_opt)->get_abs_value(ratio_over); } @@ -609,7 +608,7 @@ void ConfigBase::load_from_gcode_file(const std::string &file) std::getline(ifs, firstline); if (strncmp(slic3r_gcode_header, firstline.c_str(), strlen(slic3r_gcode_header)) != 0 && strncmp(prusaslicer_gcode_header, firstline.c_str(), strlen(prusaslicer_gcode_header)) != 0) - throw std::runtime_error("Not a PrusaSlicer / Slic3r PE generated g-code."); + throw Slic3r::RuntimeError("Not a PrusaSlicer / Slic3r PE generated g-code."); } ifs.seekg(0, ifs.end); auto file_length = ifs.tellg(); @@ -621,7 +620,7 @@ void ConfigBase::load_from_gcode_file(const std::string &file) size_t key_value_pairs = load_from_gcode_string(data.data()); if (key_value_pairs < 80) - throw std::runtime_error(format("Suspiciously low number of configuration values extracted from %1%: %2%", file, key_value_pairs)); + throw Slic3r::RuntimeError(format("Suspiciously low number of configuration values extracted from %1%: %2%", file, key_value_pairs)); } // Load the config keys from the given string. @@ -750,7 +749,7 @@ ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool cre throw NoDefinitionException(opt_key); const ConfigOptionDef *optdef = def->get(opt_key); if (optdef == nullptr) -// throw std::runtime_error(std::string("Invalid option name: ") + opt_key); +// throw Slic3r::RuntimeError(std::string("Invalid option name: ") + opt_key); // Let the parent decide what to do if the opt_key is not defined by this->def(). return nullptr; ConfigOption *opt = optdef->create_default_option(); diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 87e020898..28b28b405 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -13,6 +13,7 @@ #include #include "libslic3r.h" #include "clonable_ptr.hpp" +#include "Exception.hpp" #include "Point.hpp" #include @@ -34,31 +35,31 @@ extern bool unescape_string_cstyle(const std::string &str, std::string & extern bool unescape_strings_cstyle(const std::string &str, std::vector &out); /// Specialization of std::exception to indicate that an unknown config option has been encountered. -class UnknownOptionException : public std::runtime_error { +class UnknownOptionException : public Slic3r::RuntimeError { public: UnknownOptionException() : - std::runtime_error("Unknown option exception") {} + Slic3r::RuntimeError("Unknown option exception") {} UnknownOptionException(const std::string &opt_key) : - std::runtime_error(std::string("Unknown option exception: ") + opt_key) {} + Slic3r::RuntimeError(std::string("Unknown option exception: ") + opt_key) {} }; /// Indicate that the ConfigBase derived class does not provide config definition (the method def() returns null). -class NoDefinitionException : public std::runtime_error +class NoDefinitionException : public Slic3r::RuntimeError { public: NoDefinitionException() : - std::runtime_error("No definition exception") {} + Slic3r::RuntimeError("No definition exception") {} NoDefinitionException(const std::string &opt_key) : - std::runtime_error(std::string("No definition exception: ") + opt_key) {} + Slic3r::RuntimeError(std::string("No definition exception: ") + opt_key) {} }; /// Indicate that an unsupported accessor was called on a config option. -class BadOptionTypeException : public std::runtime_error +class BadOptionTypeException : public Slic3r::RuntimeError { public: - BadOptionTypeException() : std::runtime_error("Bad option type exception") {} - BadOptionTypeException(const std::string &message) : std::runtime_error(message) {} - BadOptionTypeException(const char* message) : std::runtime_error(message) {} + BadOptionTypeException() : Slic3r::RuntimeError("Bad option type exception") {} + BadOptionTypeException(const std::string &message) : Slic3r::RuntimeError(message) {} + BadOptionTypeException(const char* message) : Slic3r::RuntimeError(message) {} }; // Type of a configuration value. @@ -167,7 +168,7 @@ public: void set(const ConfigOption *rhs) override { if (rhs->type() != this->type()) - throw std::runtime_error("ConfigOptionSingle: Assigning an incompatible type"); + throw Slic3r::RuntimeError("ConfigOptionSingle: Assigning an incompatible type"); assert(dynamic_cast*>(rhs)); this->value = static_cast*>(rhs)->value; } @@ -175,7 +176,7 @@ public: bool operator==(const ConfigOption &rhs) const override { if (rhs.type() != this->type()) - throw std::runtime_error("ConfigOptionSingle: Comparing incompatible types"); + throw Slic3r::RuntimeError("ConfigOptionSingle: Comparing incompatible types"); assert(dynamic_cast*>(&rhs)); return this->value == static_cast*>(&rhs)->value; } @@ -239,7 +240,7 @@ public: void set(const ConfigOption *rhs) override { if (rhs->type() != this->type()) - throw std::runtime_error("ConfigOptionVector: Assigning an incompatible type"); + throw Slic3r::RuntimeError("ConfigOptionVector: Assigning an incompatible type"); assert(dynamic_cast*>(rhs)); this->values = static_cast*>(rhs)->values; } @@ -256,12 +257,12 @@ public: if (opt->type() == this->type()) { auto other = static_cast*>(opt); if (other->values.empty()) - throw std::runtime_error("ConfigOptionVector::set(): Assigning from an empty vector"); + throw Slic3r::RuntimeError("ConfigOptionVector::set(): Assigning from an empty vector"); this->values.emplace_back(other->values.front()); } else if (opt->type() == this->scalar_type()) this->values.emplace_back(static_cast*>(opt)->value); else - throw std::runtime_error("ConfigOptionVector::set():: Assigning an incompatible type"); + throw Slic3r::RuntimeError("ConfigOptionVector::set():: Assigning an incompatible type"); } } @@ -280,12 +281,12 @@ public: // Assign the first value of the rhs vector. auto other = static_cast*>(rhs); if (other->values.empty()) - throw std::runtime_error("ConfigOptionVector::set_at(): Assigning from an empty vector"); + throw Slic3r::RuntimeError("ConfigOptionVector::set_at(): Assigning from an empty vector"); this->values[i] = other->get_at(j); } else if (rhs->type() == this->scalar_type()) this->values[i] = static_cast*>(rhs)->value; else - throw std::runtime_error("ConfigOptionVector::set_at(): Assigning an incompatible type"); + throw Slic3r::RuntimeError("ConfigOptionVector::set_at(): Assigning an incompatible type"); } const T& get_at(size_t i) const @@ -310,9 +311,9 @@ public: else if (n > this->values.size()) { if (this->values.empty()) { if (opt_default == nullptr) - throw std::runtime_error("ConfigOptionVector::resize(): No default value provided."); + throw Slic3r::RuntimeError("ConfigOptionVector::resize(): No default value provided."); if (opt_default->type() != this->type()) - throw std::runtime_error("ConfigOptionVector::resize(): Extending with an incompatible type."); + throw Slic3r::RuntimeError("ConfigOptionVector::resize(): Extending with an incompatible type."); this->values.resize(n, static_cast*>(opt_default)->values.front()); } else { // Resize by duplicating the last value. @@ -329,7 +330,7 @@ public: bool operator==(const ConfigOption &rhs) const override { if (rhs.type() != this->type()) - throw std::runtime_error("ConfigOptionVector: Comparing incompatible types"); + throw Slic3r::RuntimeError("ConfigOptionVector: Comparing incompatible types"); assert(dynamic_cast*>(&rhs)); return this->values == static_cast*>(&rhs)->values; } @@ -341,9 +342,9 @@ public: // An option overrides another option if it is not nil and not equal. bool overriden_by(const ConfigOption *rhs) const override { if (this->nullable()) - throw std::runtime_error("Cannot override a nullable ConfigOption."); + throw Slic3r::RuntimeError("Cannot override a nullable ConfigOption."); if (rhs->type() != this->type()) - throw std::runtime_error("ConfigOptionVector.overriden_by() applied to different types."); + throw Slic3r::RuntimeError("ConfigOptionVector.overriden_by() applied to different types."); auto rhs_vec = static_cast*>(rhs); if (! rhs->nullable()) // Overridding a non-nullable object with another non-nullable object. @@ -361,9 +362,9 @@ public: // Apply an override option, possibly a nullable one. bool apply_override(const ConfigOption *rhs) override { if (this->nullable()) - throw std::runtime_error("Cannot override a nullable ConfigOption."); + throw Slic3r::RuntimeError("Cannot override a nullable ConfigOption."); if (rhs->type() != this->type()) - throw std::runtime_error("ConfigOptionVector.apply_override() applied to different types."); + throw Slic3r::RuntimeError("ConfigOptionVector.apply_override() applied to different types."); auto rhs_vec = static_cast*>(rhs); if (! rhs->nullable()) { // Overridding a non-nullable object with another non-nullable object. @@ -452,7 +453,7 @@ public: bool operator==(const ConfigOptionFloatsTempl &rhs) const { return vectors_equal(this->values, rhs.values); } bool operator==(const ConfigOption &rhs) const override { if (rhs.type() != this->type()) - throw std::runtime_error("ConfigOptionFloatsTempl: Comparing incompatible types"); + throw Slic3r::RuntimeError("ConfigOptionFloatsTempl: Comparing incompatible types"); assert(dynamic_cast*>(&rhs)); return vectors_equal(this->values, static_cast*>(&rhs)->values); } @@ -499,7 +500,7 @@ public: if (NULLABLE) this->values.push_back(nil_value()); else - throw std::runtime_error("Deserializing nil into a non-nullable object"); + throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object"); } else { std::istringstream iss(item_str); double value; @@ -524,9 +525,9 @@ protected: if (NULLABLE) ss << "nil"; else - throw std::runtime_error("Serializing NaN"); + throw Slic3r::RuntimeError("Serializing NaN"); } else - throw std::runtime_error("Serializing invalid number"); + throw Slic3r::RuntimeError("Serializing invalid number"); } static bool vectors_equal(const std::vector &v1, const std::vector &v2) { if (NULLABLE) { @@ -645,7 +646,7 @@ public: if (NULLABLE) this->values.push_back(nil_value()); else - throw std::runtime_error("Deserializing nil into a non-nullable object"); + throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object"); } else { std::istringstream iss(item_str); int value; @@ -662,7 +663,7 @@ private: if (NULLABLE) ss << "nil"; else - throw std::runtime_error("Serializing NaN"); + throw Slic3r::RuntimeError("Serializing NaN"); } else ss << v; } @@ -847,7 +848,7 @@ public: bool operator==(const ConfigOption &rhs) const override { if (rhs.type() != this->type()) - throw std::runtime_error("ConfigOptionFloatOrPercent: Comparing incompatible types"); + throw Slic3r::RuntimeError("ConfigOptionFloatOrPercent: Comparing incompatible types"); assert(dynamic_cast(&rhs)); return *this == *static_cast(&rhs); } @@ -858,7 +859,7 @@ public: void set(const ConfigOption *rhs) override { if (rhs->type() != this->type()) - throw std::runtime_error("ConfigOptionFloatOrPercent: Assigning an incompatible type"); + throw Slic3r::RuntimeError("ConfigOptionFloatOrPercent: Assigning an incompatible type"); assert(dynamic_cast(rhs)); *this = *static_cast(rhs); } @@ -1126,7 +1127,7 @@ public: if (NULLABLE) this->values.push_back(nil_value()); else - throw std::runtime_error("Deserializing nil into a non-nullable object"); + throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object"); } else this->values.push_back(item_str.compare("1") == 0); } @@ -1139,7 +1140,7 @@ protected: if (NULLABLE) ss << "nil"; else - throw std::runtime_error("Serializing NaN"); + throw Slic3r::RuntimeError("Serializing NaN"); } else ss << (v ? "1" : "0"); } @@ -1175,14 +1176,14 @@ public: bool operator==(const ConfigOption &rhs) const override { if (rhs.type() != this->type()) - throw std::runtime_error("ConfigOptionEnum: Comparing incompatible types"); + throw Slic3r::RuntimeError("ConfigOptionEnum: Comparing incompatible types"); // rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum return this->value == (T)rhs.getInt(); } void set(const ConfigOption *rhs) override { if (rhs->type() != this->type()) - throw std::runtime_error("ConfigOptionEnum: Assigning an incompatible type"); + throw Slic3r::RuntimeError("ConfigOptionEnum: Assigning an incompatible type"); // rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum this->value = (T)rhs->getInt(); } @@ -1259,14 +1260,14 @@ public: bool operator==(const ConfigOption &rhs) const override { if (rhs.type() != this->type()) - throw std::runtime_error("ConfigOptionEnumGeneric: Comparing incompatible types"); + throw Slic3r::RuntimeError("ConfigOptionEnumGeneric: Comparing incompatible types"); // rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum return this->value == rhs.getInt(); } void set(const ConfigOption *rhs) override { if (rhs->type() != this->type()) - throw std::runtime_error("ConfigOptionEnumGeneric: Assigning an incompatible type"); + throw Slic3r::RuntimeError("ConfigOptionEnumGeneric: Assigning an incompatible type"); // rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum this->value = rhs->getInt(); } @@ -1321,7 +1322,7 @@ public: case coInts: { auto opt = new ConfigOptionIntsNullable(); archive(*opt); return opt; } case coPercents: { auto opt = new ConfigOptionPercentsNullable();archive(*opt); return opt; } case coBools: { auto opt = new ConfigOptionBoolsNullable(); archive(*opt); return opt; } - default: throw std::runtime_error(std::string("ConfigOptionDef::load_option_from_archive(): Unknown nullable option type for option ") + this->opt_key); + default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown nullable option type for option ") + this->opt_key); } } else { switch (this->type) { @@ -1340,7 +1341,7 @@ public: case coBool: { auto opt = new ConfigOptionBool(); archive(*opt); return opt; } case coBools: { auto opt = new ConfigOptionBools(); archive(*opt); return opt; } case coEnum: { auto opt = new ConfigOptionEnumGeneric(this->enum_keys_map); archive(*opt); return opt; } - default: throw std::runtime_error(std::string("ConfigOptionDef::load_option_from_archive(): Unknown option type for option ") + this->opt_key); + default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown option type for option ") + this->opt_key); } } } @@ -1352,7 +1353,7 @@ public: case coInts: archive(*static_cast(opt)); break; case coPercents: archive(*static_cast(opt));break; case coBools: archive(*static_cast(opt)); break; - default: throw std::runtime_error(std::string("ConfigOptionDef::save_option_to_archive(): Unknown nullable option type for option ") + this->opt_key); + default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown nullable option type for option ") + this->opt_key); } } else { switch (this->type) { @@ -1371,7 +1372,7 @@ public: case coBool: archive(*static_cast(opt)); break; case coBools: archive(*static_cast(opt)); break; case coEnum: archive(*static_cast(opt)); break; - default: throw std::runtime_error(std::string("ConfigOptionDef::save_option_to_archive(): Unknown option type for option ") + this->opt_key); + default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown option type for option ") + this->opt_key); } } // Make the compiler happy, shut up the warnings. diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp index daaab4755..5bdd5055e 100644 --- a/src/libslic3r/ExPolygon.cpp +++ b/src/libslic3r/ExPolygon.cpp @@ -1,5 +1,6 @@ #include "BoundingBox.hpp" #include "ExPolygon.hpp" +#include "Exception.hpp" #include "Geometry.hpp" #include "Polygon.hpp" #include "Line.hpp" @@ -435,7 +436,7 @@ void ExPolygon::triangulate_pp(Polygons* polygons) const std::list output; int res = TPPLPartition().Triangulate_MONO(&input, &output); if (res != 1) - throw std::runtime_error("Triangulation failed"); + throw Slic3r::RuntimeError("Triangulation failed"); // convert output polygons for (std::list::iterator poly = output.begin(); poly != output.end(); ++poly) { @@ -548,7 +549,7 @@ void ExPolygon::triangulate_pp(Points *triangles) const int res = TPPLPartition().Triangulate_MONO(&input, &output); // int TPPLPartition::Triangulate_EC(TPPLPolyList *inpolys, TPPLPolyList *triangles) { if (res != 1) - throw std::runtime_error("Triangulation failed"); + throw Slic3r::RuntimeError("Triangulation failed"); *triangles = polypartition_output_to_triangles(output); } @@ -591,7 +592,7 @@ void ExPolygon::triangulate_p2t(Polygons* polygons) const } polygons->push_back(p); } - } catch (const std::runtime_error & /* err */) { + } catch (const Slic3r::RuntimeError & /* err */) { assert(false); // just ignore, don't triangulate } diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index 4aad3603f..373853f97 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -333,6 +333,14 @@ extern std::list expoly_to_polypartition_input(const ExPolygons &expp) extern std::list expoly_to_polypartition_input(const ExPolygon &ex); extern std::vector polypartition_output_to_triangles(const std::list &output); +inline double area(const ExPolygons &polys) +{ + double s = 0.; + for (auto &p : polys) s += p.area(); + + return s; +} + } // namespace Slic3r // start Boost diff --git a/src/libslic3r/Exception.hpp b/src/libslic3r/Exception.hpp new file mode 100644 index 000000000..8ec9f20c8 --- /dev/null +++ b/src/libslic3r/Exception.hpp @@ -0,0 +1,28 @@ +#ifndef _libslic3r_Exception_h_ +#define _libslic3r_Exception_h_ + +#include + +namespace Slic3r { + +// PrusaSlicer's own exception hierarchy is derived from std::runtime_error. +// Base for Slicer's own exceptions. +class Exception : public std::runtime_error { using std::runtime_error::runtime_error; }; +#define SLIC3R_DERIVE_EXCEPTION(DERIVED_EXCEPTION, PARENT_EXCEPTION) \ + class DERIVED_EXCEPTION : public PARENT_EXCEPTION { using PARENT_EXCEPTION::PARENT_EXCEPTION; } +// Critical exception produced by Slicer, such exception shall never propagate up to the UI thread. +// If that happens, an ugly fat message box with an ugly fat exclamation mark is displayed. +SLIC3R_DERIVE_EXCEPTION(CriticalException, Exception); +SLIC3R_DERIVE_EXCEPTION(RuntimeError, CriticalException); +SLIC3R_DERIVE_EXCEPTION(LogicError, CriticalException); +SLIC3R_DERIVE_EXCEPTION(InvalidArgument, LogicError); +SLIC3R_DERIVE_EXCEPTION(OutOfRange, LogicError); +SLIC3R_DERIVE_EXCEPTION(IOError, CriticalException); +SLIC3R_DERIVE_EXCEPTION(FileIOError, IOError); +// Runtime exception produced by Slicer. Such exception cancels the slicing process and it shall be shown in notifications. +SLIC3R_DERIVE_EXCEPTION(SlicingError, Exception); +#undef SLIC3R_DERIVE_EXCEPTION + +} // namespace Slic3r + +#endif // _libslic3r_Exception_h_ diff --git a/src/libslic3r/ExtrusionEntityCollection.hpp b/src/libslic3r/ExtrusionEntityCollection.hpp index dfece6949..5e40ab32e 100644 --- a/src/libslic3r/ExtrusionEntityCollection.hpp +++ b/src/libslic3r/ExtrusionEntityCollection.hpp @@ -2,6 +2,7 @@ #define slic3r_ExtrusionEntityCollection_hpp_ #include "libslic3r.h" +#include "Exception.hpp" #include "ExtrusionEntity.hpp" namespace Slic3r { @@ -107,7 +108,7 @@ public: // Following methods shall never be called on an ExtrusionEntityCollection. Polyline as_polyline() const override { - throw std::runtime_error("Calling as_polyline() on a ExtrusionEntityCollection"); + throw Slic3r::RuntimeError("Calling as_polyline() on a ExtrusionEntityCollection"); return Polyline(); }; @@ -117,7 +118,7 @@ public: } double length() const override { - throw std::runtime_error("Calling length() on a ExtrusionEntityCollection"); + throw Slic3r::RuntimeError("Calling length() on a ExtrusionEntityCollection"); return 0.; } }; diff --git a/src/libslic3r/FileParserError.hpp b/src/libslic3r/FileParserError.hpp index 3f560fa4f..b7e63d84e 100644 --- a/src/libslic3r/FileParserError.hpp +++ b/src/libslic3r/FileParserError.hpp @@ -10,14 +10,14 @@ namespace Slic3r { // Generic file parser error, mostly copied from boost::property_tree::file_parser_error -class file_parser_error: public std::runtime_error +class file_parser_error: public Slic3r::RuntimeError { public: file_parser_error(const std::string &msg, const std::string &file, unsigned long line = 0) : - std::runtime_error(format_what(msg, file, line)), + Slic3r::RuntimeError(format_what(msg, file, line)), m_message(msg), m_filename(file), m_line(line) {} file_parser_error(const std::string &msg, const boost::filesystem::path &file, unsigned long line = 0) : - std::runtime_error(format_what(msg, file.string(), line)), + Slic3r::RuntimeError(format_what(msg, file.string(), line)), m_message(msg), m_filename(file.string()), m_line(line) {} // gcc 3.4.2 complains about lack of throw specifier on compiler // generated dtor @@ -35,7 +35,7 @@ private: std::string m_filename; unsigned long m_line; - // Format error message to be returned by std::runtime_error::what() + // Format error message to be returned by Slic3r::RuntimeError::what() static std::string format_what(const std::string &msg, const std::string &file, unsigned long l) { std::stringstream stream; diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 3c16527f0..03aa798dc 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -318,7 +318,7 @@ void export_group_fills_to_svg(const char *path, const std::vector #endif // friend to Layer -void Layer::make_fills() +void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree) { for (LayerRegion *layerm : m_regions) layerm->fills.clear(); @@ -345,6 +345,7 @@ void Layer::make_fills() f->layer_id = this->id(); f->z = this->print_z; f->angle = surface_fill.params.angle; + f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree; // calculate flow spacing for infill pattern generation bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.flow.bridge; diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp new file mode 100644 index 000000000..aed71aa72 --- /dev/null +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -0,0 +1,720 @@ +#include "../ClipperUtils.hpp" +#include "../ExPolygon.hpp" +#include "../Surface.hpp" +#include "../Geometry.hpp" +#include "../Layer.hpp" +#include "../Print.hpp" +#include "../ShortestPath.hpp" + +#include "FillAdaptive.hpp" + +// for indexed_triangle_set +#include + +#include +#include + +// Boost pool: Don't use mutexes to synchronize memory allocation. +#define BOOST_POOL_NO_MT +#include + +namespace Slic3r { +namespace FillAdaptive { + +// Derived from https://github.com/juj/MathGeoLib/blob/master/src/Geometry/Triangle.cpp +// The AABB-Triangle test implementation is based on the pseudo-code in +// Christer Ericson's Real-Time Collision Detection, pp. 169-172. It is +// practically a standard SAT test. +// +// Original MathGeoLib benchmark: +// Best: 17.282 nsecs / 46.496 ticks, Avg: 17.804 nsecs, Worst: 18.434 nsecs +// +//FIXME Vojtech: The MathGeoLib contains a vectorized implementation. +template +bool triangle_AABB_intersects(const Vector &a, const Vector &b, const Vector &c, const BoundingBoxBase &aabb) +{ + using Scalar = typename Vector::Scalar; + + Vector tMin = a.cwiseMin(b.cwiseMin(c)); + Vector tMax = a.cwiseMax(b.cwiseMax(c)); + + if (tMin.x() >= aabb.max.x() || tMax.x() <= aabb.min.x() + || tMin.y() >= aabb.max.y() || tMax.y() <= aabb.min.y() + || tMin.z() >= aabb.max.z() || tMax.z() <= aabb.min.z()) + return false; + + Vector center = (aabb.min + aabb.max) * 0.5f; + Vector h = aabb.max - center; + + const Vector t[3] { b-a, c-a, c-b }; + + Vector ac = a - center; + + Vector n = t[0].cross(t[1]); + Scalar s = n.dot(ac); + Scalar r = std::abs(h.dot(n.cwiseAbs())); + if (abs(s) >= r) + return false; + + const Vector at[3] = { t[0].cwiseAbs(), t[1].cwiseAbs(), t[2].cwiseAbs() }; + + Vector bc = b - center; + Vector cc = c - center; + + // SAT test all cross-axes. + // The following is a fully unrolled loop of this code, stored here for reference: + /* + Scalar d1, d2, a1, a2; + const Vector e[3] = { DIR_VEC(1, 0, 0), DIR_VEC(0, 1, 0), DIR_VEC(0, 0, 1) }; + for(int i = 0; i < 3; ++i) + for(int j = 0; j < 3; ++j) + { + Vector axis = Cross(e[i], t[j]); + ProjectToAxis(axis, d1, d2); + aabb.ProjectToAxis(axis, a1, a2); + if (d2 <= a1 || d1 >= a2) return false; + } + */ + + // eX t[0] + Scalar d1 = t[0].y() * ac.z() - t[0].z() * ac.y(); + Scalar d2 = t[0].y() * cc.z() - t[0].z() * cc.y(); + Scalar tc = (d1 + d2) * 0.5f; + r = std::abs(h.y() * at[0].z() + h.z() * at[0].y()); + if (r + std::abs(tc - d1) < std::abs(tc)) + return false; + + // eX t[1] + d1 = t[1].y() * ac.z() - t[1].z() * ac.y(); + d2 = t[1].y() * bc.z() - t[1].z() * bc.y(); + tc = (d1 + d2) * 0.5f; + r = std::abs(h.y() * at[1].z() + h.z() * at[1].y()); + if (r + std::abs(tc - d1) < std::abs(tc)) + return false; + + // eX t[2] + d1 = t[2].y() * ac.z() - t[2].z() * ac.y(); + d2 = t[2].y() * bc.z() - t[2].z() * bc.y(); + tc = (d1 + d2) * 0.5f; + r = std::abs(h.y() * at[2].z() + h.z() * at[2].y()); + if (r + std::abs(tc - d1) < std::abs(tc)) + return false; + + // eY t[0] + d1 = t[0].z() * ac.x() - t[0].x() * ac.z(); + d2 = t[0].z() * cc.x() - t[0].x() * cc.z(); + tc = (d1 + d2) * 0.5f; + r = std::abs(h.x() * at[0].z() + h.z() * at[0].x()); + if (r + std::abs(tc - d1) < std::abs(tc)) + return false; + + // eY t[1] + d1 = t[1].z() * ac.x() - t[1].x() * ac.z(); + d2 = t[1].z() * bc.x() - t[1].x() * bc.z(); + tc = (d1 + d2) * 0.5f; + r = std::abs(h.x() * at[1].z() + h.z() * at[1].x()); + if (r + std::abs(tc - d1) < std::abs(tc)) + return false; + + // eY t[2] + d1 = t[2].z() * ac.x() - t[2].x() * ac.z(); + d2 = t[2].z() * bc.x() - t[2].x() * bc.z(); + tc = (d1 + d2) * 0.5f; + r = std::abs(h.x() * at[2].z() + h.z() * at[2].x()); + if (r + std::abs(tc - d1) < std::abs(tc)) + return false; + + // eZ t[0] + d1 = t[0].x() * ac.y() - t[0].y() * ac.x(); + d2 = t[0].x() * cc.y() - t[0].y() * cc.x(); + tc = (d1 + d2) * 0.5f; + r = std::abs(h.y() * at[0].x() + h.x() * at[0].y()); + if (r + std::abs(tc - d1) < std::abs(tc)) + return false; + + // eZ t[1] + d1 = t[1].x() * ac.y() - t[1].y() * ac.x(); + d2 = t[1].x() * bc.y() - t[1].y() * bc.x(); + tc = (d1 + d2) * 0.5f; + r = std::abs(h.y() * at[1].x() + h.x() * at[1].y()); + if (r + std::abs(tc - d1) < std::abs(tc)) + return false; + + // eZ t[2] + d1 = t[2].x() * ac.y() - t[2].y() * ac.x(); + d2 = t[2].x() * bc.y() - t[2].y() * bc.x(); + tc = (d1 + d2) * 0.5f; + r = std::abs(h.y() * at[2].x() + h.x() * at[2].y()); + if (r + std::abs(tc - d1) < std::abs(tc)) + return false; + + // No separating axis exists, the AABB and triangle intersect. + return true; +} + +// Ordering of children cubes. +static const std::array child_centers { + Vec3d(-1, -1, -1), Vec3d( 1, -1, -1), Vec3d(-1, 1, -1), Vec3d( 1, 1, -1), + Vec3d(-1, -1, 1), Vec3d( 1, -1, 1), Vec3d(-1, 1, 1), Vec3d( 1, 1, 1) +}; + +// Traversal order of octree children cells for three infill directions, +// so that a single line will be discretized in a strictly monotonous order. +static constexpr std::array, 3> child_traversal_order { + std::array{ 2, 3, 0, 1, 6, 7, 4, 5 }, + std::array{ 4, 0, 6, 2, 5, 1, 7, 3 }, + std::array{ 1, 5, 0, 4, 3, 7, 2, 6 }, +}; + +struct Cube +{ + Vec3d center; +#ifndef NDEBUG + Vec3d center_octree; +#endif // NDEBUG + std::array children {}; // initialized to nullptrs + Cube(const Vec3d ¢er) : center(center) {} +}; + +struct CubeProperties +{ + double edge_length; // Lenght of edge of a cube + double height; // Height of rotated cube (standing on the corner) + double diagonal_length; // Length of diagonal of a cube a face + double line_z_distance; // Defines maximal distance from a center of a cube on Z axis on which lines will be created + double line_xy_distance;// Defines maximal distance from a center of a cube on X and Y axis on which lines will be created +}; + +struct Octree +{ + // Octree will allocate its Cubes from the pool. The pool only supports deletion of the complete pool, + // perfect for building up our octree. + boost::object_pool pool; + Cube* root_cube { nullptr }; + Vec3d origin; + std::vector cubes_properties; + + Octree(const Vec3d &origin, const std::vector &cubes_properties) + : root_cube(pool.construct(origin)), origin(origin), cubes_properties(cubes_properties) {} + + void insert_triangle(const Vec3d &a, const Vec3d &b, const Vec3d &c, Cube *current_cube, const BoundingBoxf3 ¤t_bbox, int depth); +}; + +void OctreeDeleter::operator()(Octree *p) { + delete p; +} + +std::pair adaptive_fill_line_spacing(const PrintObject &print_object) +{ + // Output, spacing for icAdaptiveCubic and icSupportCubic + double adaptive_line_spacing = 0.; + double support_line_spacing = 0.; + + enum class Tristate { + Yes, + No, + Maybe + }; + struct RegionFillData { + Tristate has_adaptive_infill; + Tristate has_support_infill; + double density; + double extrusion_width; + }; + std::vector region_fill_data; + region_fill_data.reserve(print_object.print()->regions().size()); + bool build_octree = false; + for (const PrintRegion *region : print_object.print()->regions()) { + const PrintRegionConfig &config = region->config(); + bool nonempty = config.fill_density > 0; + bool has_adaptive_infill = nonempty && config.fill_pattern == ipAdaptiveCubic; + bool has_support_infill = nonempty && config.fill_pattern == ipSupportCubic; + region_fill_data.push_back(RegionFillData({ + has_adaptive_infill ? Tristate::Maybe : Tristate::No, + has_support_infill ? Tristate::Maybe : Tristate::No, + config.fill_density, + config.infill_extrusion_width + })); + build_octree |= has_adaptive_infill || has_support_infill; + } + + if (build_octree) { + // Compute the average of above parameters over all layers + for (const Layer *layer : print_object.layers()) + for (size_t region_id = 0; region_id < layer->regions().size(); ++ region_id) { + RegionFillData &rd = region_fill_data[region_id]; + if (rd.has_adaptive_infill == Tristate::Maybe && ! layer->regions()[region_id]->fill_surfaces.empty()) + rd.has_adaptive_infill = Tristate::Yes; + if (rd.has_support_infill == Tristate::Maybe && ! layer->regions()[region_id]->fill_surfaces.empty()) + rd.has_support_infill = Tristate::Yes; + } + + double adaptive_fill_density = 0.; + double adaptive_infill_extrusion_width = 0.; + int adaptive_cnt = 0; + double support_fill_density = 0.; + double support_infill_extrusion_width = 0.; + int support_cnt = 0; + + for (const RegionFillData &rd : region_fill_data) { + if (rd.has_adaptive_infill == Tristate::Yes) { + adaptive_fill_density += rd.density; + adaptive_infill_extrusion_width += rd.extrusion_width; + ++ adaptive_cnt; + } else if (rd.has_support_infill == Tristate::Yes) { + support_fill_density += rd.density; + support_infill_extrusion_width += rd.extrusion_width; + ++ support_cnt; + } + } + + auto to_line_spacing = [](int cnt, double density, double extrusion_width) { + if (cnt) { + density /= double(cnt); + extrusion_width /= double(cnt); + return extrusion_width / ((density / 100.0f) * 0.333333333f); + } else + return 0.; + }; + adaptive_line_spacing = to_line_spacing(adaptive_cnt, adaptive_fill_density, adaptive_infill_extrusion_width); + support_line_spacing = to_line_spacing(support_cnt, support_fill_density, support_infill_extrusion_width); + } + + return std::make_pair(adaptive_line_spacing, support_line_spacing); +} + +// Context used by generate_infill_lines() when recursively traversing an octree in a DDA fashion +// (Digital Differential Analyzer). +struct FillContext +{ + // The angles have to agree with child_traversal_order. + static constexpr double direction_angles[3] { + 0., + (2.0 * M_PI) / 3.0, + -(2.0 * M_PI) / 3.0 + }; + + FillContext(const Octree &octree, double z_position, int direction_idx) : + origin_world(octree.origin), + cubes_properties(octree.cubes_properties), + z_position(z_position), + traversal_order(child_traversal_order[direction_idx]), + cos_a(cos(direction_angles[direction_idx])), + sin_a(sin(direction_angles[direction_idx])) + { + static constexpr auto unused = std::numeric_limits::max(); + temp_lines.assign((1 << octree.cubes_properties.size()) - 1, Line(Point(unused, unused), Point(unused, unused))); + } + + // Rotate the point, uses the same convention as Point::rotate(). + Vec2d rotate(const Vec2d& v) { return Vec2d(this->cos_a * v.x() - this->sin_a * v.y(), this->sin_a * v.x() + this->cos_a * v.y()); } + + // Center of the root cube in the Octree coordinate system. + const Vec3d origin_world; + const std::vector &cubes_properties; + // Top of the current layer. + const double z_position; + // Order of traversal for this line direction. + const std::array traversal_order; + // Rotation of the generated line for this line direction. + const double cos_a; + const double sin_a; + + // Linearized tree spanning a single Octree wall, used to connect lines spanning + // neighboring Octree cells. Unused lines have the Line::a::x set to infinity. + std::vector temp_lines; + // Final output + std::vector output_lines; +}; + +static constexpr double octree_rot[3] = { 5.0 * M_PI / 4.0, Geometry::deg2rad(215.264), M_PI / 6.0 }; + +Eigen::Quaterniond transform_to_world() +{ + return Eigen::AngleAxisd(octree_rot[2], Vec3d::UnitZ()) * Eigen::AngleAxisd(octree_rot[1], Vec3d::UnitY()) * Eigen::AngleAxisd(octree_rot[0], Vec3d::UnitX()); +} + +Eigen::Quaterniond transform_to_octree() +{ + return Eigen::AngleAxisd(- octree_rot[0], Vec3d::UnitX()) * Eigen::AngleAxisd(- octree_rot[1], Vec3d::UnitY()) * Eigen::AngleAxisd(- octree_rot[2], Vec3d::UnitZ()); +} + +#ifndef NDEBUG +// Verify that the traversal order of the octree children matches the line direction, +// therefore the infill line may get extended with O(1) time & space complexity. +static bool verify_traversal_order( + FillContext &context, + const Cube *cube, + int depth, + const Vec2d &line_from, + const Vec2d &line_to) +{ + std::array c; + Eigen::Quaterniond to_world = transform_to_world(); + for (int i = 0; i < 8; ++i) { + int j = context.traversal_order[i]; + Vec3d cntr = to_world * (cube->center_octree + (child_centers[j] * (context.cubes_properties[depth].edge_length / 4.))); + assert(!cube->children[j] || cube->children[j]->center.isApprox(cntr)); + c[i] = cntr; + } + std::array dirs = { + c[1] - c[0], c[2] - c[0], c[3] - c[1], c[3] - c[2], c[3] - c[0], + c[5] - c[4], c[6] - c[4], c[7] - c[5], c[7] - c[6], c[7] - c[4] + }; + assert(std::abs(dirs[4].z()) < 0.001); + assert(std::abs(dirs[9].z()) < 0.001); + assert(dirs[0].isApprox(dirs[3])); + assert(dirs[1].isApprox(dirs[2])); + assert(dirs[5].isApprox(dirs[8])); + assert(dirs[6].isApprox(dirs[7])); + Vec3d line_dir = Vec3d(line_to.x() - line_from.x(), line_to.y() - line_from.y(), 0.).normalized(); + for (auto& dir : dirs) { + double d = dir.normalized().dot(line_dir); + assert(d > 0.7); + } + return true; +} +#endif // NDEBUG + +static void generate_infill_lines_recursive( + FillContext &context, + const Cube *cube, + // Address of this wall in the octree, used to address context.temp_lines. + int address, + int depth) +{ + assert(cube != nullptr); + + const std::vector &cubes_properties = context.cubes_properties; + const double z_diff = context.z_position - cube->center.z(); + const double z_diff_abs = std::abs(z_diff); + + if (z_diff_abs > cubes_properties[depth].height / 2.) + return; + + if (z_diff_abs < cubes_properties[depth].line_z_distance) { + // Discretize a single wall splitting the cube into two. + const double zdist = cubes_properties[depth].line_z_distance; + Vec2d from( + 0.5 * cubes_properties[depth].diagonal_length * (zdist - z_diff_abs) / zdist, + cubes_properties[depth].line_xy_distance - (zdist + z_diff) / sqrt(2.)); + Vec2d to(-from.x(), from.y()); + from = context.rotate(from); + to = context.rotate(to); + // Relative to cube center + Vec2d offset(cube->center.x() - context.origin_world.x(), cube->center.y() - context.origin_world.y()); + from += offset; + to += offset; + // Verify that the traversal order of the octree children matches the line direction, + // therefore the infill line may get extended with O(1) time & space complexity. + assert(verify_traversal_order(context, cube, depth, from, to)); + // Either extend an existing line or start a new one. + Line &last_line = context.temp_lines[address]; + Line new_line(Point::new_scale(from), Point::new_scale(to)); + if (last_line.a.x() == std::numeric_limits::max()) { + last_line.a = new_line.a; + } else if ((new_line.a - last_line.b).cwiseAbs().maxCoeff() > 300) { // SCALED_EPSILON is 100 and it is not enough) { + context.output_lines.emplace_back(last_line); + last_line.a = new_line.a; + } + last_line.b = new_line.b; + } + + // left child index + address = address * 2 + 1; + -- depth; + size_t i = 0; + for (const int child_idx : context.traversal_order) { + const Cube *child = cube->children[child_idx]; + if (child != nullptr) + generate_infill_lines_recursive(context, child, address, depth); + if (++ i == 4) + // right child index + ++ address; + } +} + +#if 0 +// Collect the line segments. +static Polylines chain_lines(const std::vector &lines, const double point_distance_epsilon) +{ + // Create line end point lookup. + struct LineEnd { + LineEnd(Line *line, bool start) : line(line), start(start) {} + Line *line; + // Is it the start or end point? + bool start; + const Point& point() const { return start ? line->a : line->b; } + const Point& other_point() const { return start ? line->b : line->a; } + LineEnd other_end() const { return LineEnd(line, ! start); } + bool operator==(const LineEnd &rhs) const { return this->line == rhs.line && this->start == rhs.start; } + }; + struct LineEndAccessor { + const Point* operator()(const LineEnd &pt) const { return &pt.point(); } + }; + typedef ClosestPointInRadiusLookup ClosestPointLookupType; + ClosestPointLookupType closest_end_point_lookup(point_distance_epsilon); + for (const Line &line : lines) { + closest_end_point_lookup.insert(LineEnd(&line, true)); + closest_end_point_lookup.insert(LineEnd(&line, false)); + } + + // Chain the lines. + std::vector line_consumed(lines.size(), false); + static const double point_distance_epsilon2 = point_distance_epsilon * point_distance_epsilon; + Polylines out; + for (const Line &seed : lines) + if (! line_consumed[&seed - lines.data()]) { + line_consumed[&seed - lines.data()] = true; + closest_end_point_lookup.erase(LineEnd(&seed, false)); + closest_end_point_lookup.erase(LineEnd(&seed, true)); + Polyline pl { seed.a, seed.b }; + for (size_t round = 0; round < 2; ++ round) { + for (;;) { + auto [line_end, dist2] = closest_end_point_lookup.find(pl.last_point()); + if (line_end == nullptr || dist2 >= point_distance_epsilon2) + // Cannot extent in this direction. + break; + // Average the last point. + pl.points.back() = 0.5 * (pl.points.back() + line_end->point()); + // and extend with the new line segment. + pl.points.emplace_back(line_end->other_point()); + closest_end_point_lookup.erase(line_end); + closest_end_point_lookup.erase(line_end->other_end()); + line_consumed[line_end->line - lines.data()] = true; + } + // reverse and try the oter direction. + pl.reverse(); + } + out.emplace_back(std::move(pl)); + } + return out; +} +#endif + +#ifndef NDEBUG +// #define ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT +#endif + +#ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT +static void export_infill_lines_to_svg(const ExPolygon &expoly, const Polylines &polylines, const std::string &path) +{ + BoundingBox bbox = get_extents(expoly); + bbox.offset(scale_(3.)); + + ::Slic3r::SVG svg(path, bbox); + svg.draw(expoly); + svg.draw_outline(expoly, "green"); + svg.draw(polylines, "red"); + static constexpr double trim_length = scale_(0.4); + for (Polyline polyline : polylines) { + Vec2d a = polyline.points.front().cast(); + Vec2d d = polyline.points.back().cast(); + if (polyline.size() == 2) { + Vec2d v = d - a; + double l = v.norm(); + if (l > 2. * trim_length) { + a += v * trim_length / l; + d -= v * trim_length / l; + polyline.points.front() = a.cast(); + polyline.points.back() = d.cast(); + } else + polyline.points.clear(); + } else if (polyline.size() > 2) { + Vec2d b = polyline.points[1].cast(); + Vec2d c = polyline.points[polyline.points.size() - 2].cast(); + Vec2d v = b - a; + double l = v.norm(); + if (l > trim_length) { + a += v * trim_length / l; + polyline.points.front() = a.cast(); + } else + polyline.points.erase(polyline.points.begin()); + v = d - c; + l = v.norm(); + if (l > trim_length) + polyline.points.back() = (d - v * trim_length / l).cast(); + else + polyline.points.pop_back(); + } + svg.draw(polyline, "black"); + } +} +#endif /* ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT */ + +void Filler::_fill_surface_single( + const FillParams & params, + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon &expolygon, + Polylines &polylines_out) +{ + assert (this->adapt_fill_octree); + + Polylines all_polylines; + { + // 3 contexts for three directions of infill lines + std::array contexts { + FillContext { *adapt_fill_octree, this->z, 0 }, + FillContext { *adapt_fill_octree, this->z, 1 }, + FillContext { *adapt_fill_octree, this->z, 2 } + }; + // Generate the infill lines along the octree cells, merge touching lines of the same direction. + size_t num_lines = 0; + for (auto &context : contexts) { + generate_infill_lines_recursive(context, adapt_fill_octree->root_cube, 0, int(adapt_fill_octree->cubes_properties.size()) - 1); + num_lines += context.output_lines.size() + context.temp_lines.size(); + } + // Collect the lines. + std::vector lines; + lines.reserve(num_lines); + for (auto &context : contexts) { + append(lines, context.output_lines); + for (const Line &line : context.temp_lines) + if (line.a.x() != std::numeric_limits::max()) + lines.emplace_back(line); + } + // Convert lines to polylines. + //FIXME chain the lines + all_polylines.reserve(lines.size()); + std::transform(lines.begin(), lines.end(), std::back_inserter(all_polylines), [](const Line& l) { return Polyline{ l.a, l.b }; }); + } + + // Crop all polylines + all_polylines = intersection_pl(std::move(all_polylines), to_polygons(expolygon)); + +#ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT + { + static int iRun = 0; + export_infill_lines_to_svg(expolygon, all_polylines, debug_out_path("FillAdaptive-initial-%d.svg", iRun++)); + } +#endif /* ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT */ + + if (params.dont_connect) + append(polylines_out, std::move(all_polylines)); + else + connect_infill(chain_polylines(std::move(all_polylines)), expolygon, polylines_out, this->spacing, params); + +#ifdef ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT + { + static int iRun = 0; + export_infill_lines_to_svg(expolygon, polylines_out, debug_out_path("FillAdaptive-final-%d.svg", iRun ++)); + } +#endif /* ADAPTIVE_CUBIC_INFILL_DEBUG_OUTPUT */ +} + +static double bbox_max_radius(const BoundingBoxf3 &bbox, const Vec3d ¢er) +{ + const auto p = (bbox.min - center); + const auto s = bbox.size(); + double r2max = 0.; + for (int i = 0; i < 8; ++ i) + r2max = std::max(r2max, (p + Vec3d(s.x() * double(i & 1), s.y() * double(i & 2), s.z() * double(i & 4))).squaredNorm()); + return sqrt(r2max); +} + +static std::vector make_cubes_properties(double max_cube_edge_length, double line_spacing) +{ + max_cube_edge_length += EPSILON; + + std::vector cubes_properties; + for (double edge_length = line_spacing * 2.;; edge_length *= 2.) + { + CubeProperties props{}; + props.edge_length = edge_length; + props.height = edge_length * sqrt(3); + props.diagonal_length = edge_length * sqrt(2); + props.line_z_distance = edge_length / sqrt(3); + props.line_xy_distance = edge_length / sqrt(6); + cubes_properties.emplace_back(props); + if (edge_length > max_cube_edge_length) + break; + } + return cubes_properties; +} + +static inline bool is_overhang_triangle(const Vec3d &a, const Vec3d &b, const Vec3d &c, const Vec3d &up) +{ + // Calculate triangle normal. + auto n = (b - a).cross(c - b); + return n.dot(up) > 0.707 * n.norm(); +} + +static void transform_center(Cube *current_cube, const Eigen::Matrix3d &rot) +{ +#ifndef NDEBUG + current_cube->center_octree = current_cube->center; +#endif // NDEBUG + current_cube->center = rot * current_cube->center; + for (auto *child : current_cube->children) + if (child) + transform_center(child, rot); +} + +OctreePtr build_octree(const indexed_triangle_set &triangle_mesh, coordf_t line_spacing, bool support_overhangs_only) +{ + assert(line_spacing > 0); + assert(! std::isnan(line_spacing)); + + BoundingBox3Base bbox(triangle_mesh.vertices); + Vec3d cube_center = bbox.center().cast(); + std::vector cubes_properties = make_cubes_properties(double(bbox.size().maxCoeff()), line_spacing); + auto octree = OctreePtr(new Octree(cube_center, cubes_properties)); + + if (cubes_properties.size() > 1) { + auto up_vector = support_overhangs_only ? Vec3d(transform_to_octree() * Vec3d(0., 0., 1.)) : Vec3d(); + for (auto &tri : triangle_mesh.indices) { + auto a = triangle_mesh.vertices[tri[0]].cast(); + auto b = triangle_mesh.vertices[tri[1]].cast(); + auto c = triangle_mesh.vertices[tri[2]].cast(); + if (support_overhangs_only && ! is_overhang_triangle(a, b, c, up_vector)) + continue; + double edge_length_half = 0.5 * cubes_properties.back().edge_length; + Vec3d diag_half(edge_length_half, edge_length_half, edge_length_half); + octree->insert_triangle( + a, b, c, + octree->root_cube, + BoundingBoxf3(octree->root_cube->center - diag_half, octree->root_cube->center + diag_half), + int(cubes_properties.size()) - 1); + } + { + // Transform the octree to world coordinates to reduce computation when extracting infill lines. + auto rot = transform_to_world().toRotationMatrix(); + transform_center(octree->root_cube, rot); + octree->origin = rot * octree->origin; + } + } + + return octree; +} + +void Octree::insert_triangle(const Vec3d &a, const Vec3d &b, const Vec3d &c, Cube *current_cube, const BoundingBoxf3 ¤t_bbox, int depth) +{ + assert(current_cube); + assert(depth > 0); + + for (size_t i = 0; i < 8; ++ i) { + const Vec3d &child_center = child_centers[i]; + // Calculate a slightly expanded bounding box of a child cube to cope with triangles touching a cube wall and other numeric errors. + // We will rather densify the octree a bit more than necessary instead of missing a triangle. + BoundingBoxf3 bbox; + for (int k = 0; k < 3; ++ k) { + if (child_center[k] == -1.) { + bbox.min[k] = current_bbox.min[k]; + bbox.max[k] = current_cube->center[k] + EPSILON; + } else { + bbox.min[k] = current_cube->center[k] - EPSILON; + bbox.max[k] = current_bbox.max[k]; + } + } + if (triangle_AABB_intersects(a, b, c, bbox)) { + if (! current_cube->children[i]) + current_cube->children[i] = this->pool.construct(current_cube->center + (child_center * (this->cubes_properties[depth].edge_length / 4))); + if (depth > 1) + this->insert_triangle(a, b, c, current_cube->children[i], bbox, depth - 1); + } + } +} + +} // namespace FillAdaptive +} // namespace Slic3r diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp new file mode 100644 index 000000000..aca8d1d7b --- /dev/null +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -0,0 +1,72 @@ +// Adaptive cubic infill was inspired by the work of @mboerwinkle +// as implemented for Cura. +// https://github.com/Ultimaker/CuraEngine/issues/381 +// https://github.com/Ultimaker/CuraEngine/pull/401 +// +// Our implementation is more accurate (discretizes a bit less cubes than Cura's) +// by splitting only such cubes which contain a triangle. +// Our line extraction is time optimal instead of O(n^2) when connecting extracted lines, +// and we also implemented adaptivity for supporting internal overhangs only. + +#ifndef slic3r_FillAdaptive_hpp_ +#define slic3r_FillAdaptive_hpp_ + +#include "FillBase.hpp" + +struct indexed_triangle_set; + +namespace Slic3r { + +class PrintObject; + +namespace FillAdaptive +{ + +struct Octree; +// To keep the definition of Octree opaque, we have to define a custom deleter. +struct OctreeDeleter { void operator()(Octree *p); }; +using OctreePtr = std::unique_ptr; + +// Calculate line spacing for +// 1) adaptive cubic infill +// 2) adaptive internal support cubic infill +// Returns zero for a particular infill type if no such infill is to be generated. +std::pair adaptive_fill_line_spacing(const PrintObject &print_object); + +// Rotation of the octree to stand on one of its corners. +Eigen::Quaterniond transform_to_world(); +// Inverse roation of the above. +Eigen::Quaterniond transform_to_octree(); + +FillAdaptive::OctreePtr build_octree( + // Mesh is rotated to the coordinate system of the octree. + const indexed_triangle_set &triangle_mesh, + coordf_t line_spacing, + // If true, octree is densified below internal overhangs only. + bool support_overhangs_only); + +// +// Some of the algorithms used by class FillAdaptive were inspired by +// Cura Engine's class SubDivCube +// https://github.com/Ultimaker/CuraEngine/blob/master/src/infill/SubDivCube.h +// +class Filler : public Slic3r::Fill +{ +public: + virtual ~Filler() {} + +protected: + virtual Fill* clone() const { return new Filler(*this); }; + virtual void _fill_surface_single( + const FillParams ¶ms, + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon &expolygon, + Polylines &polylines_out); + virtual bool no_sort() const { return true; } +}; + +}; // namespace FillAdaptive +} // namespace Slic3r + +#endif // slic3r_FillAdaptive_hpp_ diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index c760218c0..fc5f548a3 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -16,6 +16,7 @@ #include "FillRectilinear.hpp" #include "FillRectilinear2.hpp" #include "FillRectilinear3.hpp" +#include "FillAdaptive.hpp" namespace Slic3r { @@ -37,7 +38,9 @@ Fill* Fill::new_from_type(const InfillPattern type) case ipArchimedeanChords: return new FillArchimedeanChords(); case ipHilbertCurve: return new FillHilbertCurve(); case ipOctagramSpiral: return new FillOctagramSpiral(); - default: throw std::invalid_argument("unknown type"); + case ipAdaptiveCubic: return new FillAdaptive::Filler(); + case ipSupportCubic: return new FillAdaptive::Filler(); + default: throw Slic3r::InvalidArgument("unknown type"); } } @@ -844,8 +847,9 @@ void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_ boundary.assign(boundary_src.holes.size() + 1, Points()); boundary_data.assign(boundary_src.holes.size() + 1, std::vector()); // Mapping the infill_ordered end point to a (contour, point) of boundary. - std::vector> map_infill_end_point_to_boundary; - map_infill_end_point_to_boundary.assign(infill_ordered.size() * 2, std::pair(std::numeric_limits::max(), std::numeric_limits::max())); + std::vector> map_infill_end_point_to_boundary; + static constexpr auto boundary_idx_unconnected = std::numeric_limits::max(); + map_infill_end_point_to_boundary.assign(infill_ordered.size() * 2, std::pair(boundary_idx_unconnected, boundary_idx_unconnected)); { // Project the infill_ordered end points onto boundary_src. std::vector> intersection_points; @@ -895,13 +899,14 @@ void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_ contour_data.front().param = contour_data.back().param + (contour_dst.back().cast() - contour_dst.front().cast()).norm(); } -#ifndef NDEBUG assert(boundary.size() == boundary_src.num_contours()); - assert(std::all_of(map_infill_end_point_to_boundary.begin(), map_infill_end_point_to_boundary.end(), +#if 0 + // Adaptive Cubic Infill produces infill lines, which not always end at the outer boundary. + assert(std::all_of(map_infill_end_point_to_boundary.begin(), map_infill_end_point_to_boundary.end(), [&boundary](const std::pair &contour_point) { return contour_point.first < boundary.size() && contour_point.second < boundary[contour_point.first].size(); })); -#endif /* NDEBUG */ +#endif } // Mark the points and segments of split boundary as consumed if they are very close to some of the infill line. @@ -932,9 +937,9 @@ void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_ const Polyline &pl2 = infill_ordered[idx_chain]; const std::pair *cp1 = &map_infill_end_point_to_boundary[(idx_chain - 1) * 2 + 1]; const std::pair *cp2 = &map_infill_end_point_to_boundary[idx_chain * 2]; - const std::vector &contour_data = boundary_data[cp1->first]; - if (cp1->first == cp2->first) { + if (cp1->first != boundary_idx_unconnected && cp1->first == cp2->first) { // End points on the same contour. Try to connect them. + const std::vector &contour_data = boundary_data[cp1->first]; float param_lo = (cp1->second == 0) ? 0.f : contour_data[cp1->second].param; float param_hi = (cp2->second == 0) ? 0.f : contour_data[cp2->second].param; float param_end = contour_data.front().param; @@ -961,7 +966,7 @@ void Fill::connect_infill(Polylines &&infill_ordered, const ExPolygon &boundary_ const std::pair *cp1prev = cp1 - 1; const std::pair *cp2 = &map_infill_end_point_to_boundary[(connection_cost.idx_first + 1) * 2]; const std::pair *cp2next = cp2 + 1; - assert(cp1->first == cp2->first); + assert(cp1->first == cp2->first && cp1->first != boundary_idx_unconnected); std::vector &contour_data = boundary_data[cp1->first]; if (connection_cost.reversed) std::swap(cp1, cp2); diff --git a/src/libslic3r/Fill/FillBase.hpp b/src/libslic3r/Fill/FillBase.hpp index 2e9b64735..0779117eb 100644 --- a/src/libslic3r/Fill/FillBase.hpp +++ b/src/libslic3r/Fill/FillBase.hpp @@ -11,6 +11,7 @@ #include "../libslic3r.h" #include "../BoundingBox.hpp" +#include "../Exception.hpp" #include "../Utils.hpp" namespace Slic3r { @@ -19,9 +20,14 @@ class ExPolygon; class Surface; enum InfillPattern : int; -class InfillFailedException : public std::runtime_error { +namespace FillAdaptive { + struct Octree; +}; + +// Infill shall never fail, therefore the error is classified as RuntimeError, not SlicingError. +class InfillFailedException : public Slic3r::RuntimeError { public: - InfillFailedException() : std::runtime_error("Infill failed") {} + InfillFailedException() : Slic3r::RuntimeError("Infill failed") {} }; struct FillParams @@ -69,6 +75,9 @@ public: // In scaled coordinates. Bounding box of the 2D projection of the object. BoundingBox bounding_box; + // Octree builds on mesh for usage in the adaptive cubic infill + FillAdaptive::Octree* adapt_fill_octree = nullptr; + public: virtual ~Fill() {} diff --git a/src/libslic3r/Flow.cpp b/src/libslic3r/Flow.cpp index 1678be999..e5dcf0731 100644 --- a/src/libslic3r/Flow.cpp +++ b/src/libslic3r/Flow.cpp @@ -53,7 +53,7 @@ static inline FlowRole opt_key_to_flow_role(const std::string &opt_key) else if (opt_key == "support_material_extrusion_width") return frSupportMaterial; else - throw std::runtime_error("opt_key_to_flow_role: invalid argument"); + throw Slic3r::RuntimeError("opt_key_to_flow_role: invalid argument"); }; static inline void throw_on_missing_variable(const std::string &opt_key, const char *dependent_opt_key) @@ -126,7 +126,7 @@ Flow Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent { // we need layer height unless it's a bridge if (height <= 0 && bridge_flow_ratio == 0) - throw std::invalid_argument("Invalid flow height supplied to new_from_config_width()"); + throw Slic3r::InvalidArgument("Invalid flow height supplied to new_from_config_width()"); float w; if (bridge_flow_ratio > 0) { @@ -151,7 +151,7 @@ Flow Flow::new_from_spacing(float spacing, float nozzle_diameter, float height, { // we need layer height unless it's a bridge if (height <= 0 && !bridge) - throw std::invalid_argument("Invalid flow height supplied to new_from_spacing()"); + throw Slic3r::InvalidArgument("Invalid flow height supplied to new_from_spacing()"); // Calculate width from spacing. // For normal extrusons, extrusion width is wider than the spacing due to the rounding and squishing of the extrusions. // For bridge extrusions, the extrusions are placed with a tiny BRIDGE_EXTRA_SPACING gaps between the threads. diff --git a/src/libslic3r/Flow.hpp b/src/libslic3r/Flow.hpp index 7d6e35873..9e57ce907 100644 --- a/src/libslic3r/Flow.hpp +++ b/src/libslic3r/Flow.hpp @@ -3,6 +3,7 @@ #include "libslic3r.h" #include "Config.hpp" +#include "Exception.hpp" #include "ExtrusionEntity.hpp" namespace Slic3r { @@ -27,11 +28,11 @@ enum FlowRole { frSupportMaterialInterface, }; -class FlowError : public std::invalid_argument +class FlowError : public Slic3r::InvalidArgument { public: - FlowError(const std::string& what_arg) : invalid_argument(what_arg) {} - FlowError(const char* what_arg) : invalid_argument(what_arg) {} + FlowError(const std::string& what_arg) : Slic3r::InvalidArgument(what_arg) {} + FlowError(const char* what_arg) : Slic3r::InvalidArgument(what_arg) {} }; class FlowErrorNegativeSpacing : public FlowError diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index 66dd0049c..46a6c02af 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -1,4 +1,5 @@ #include "../libslic3r.h" +#include "../Exception.hpp" #include "../Model.hpp" #include "../Utils.hpp" #include "../GCode.hpp" @@ -123,11 +124,11 @@ const char* INVALID_OBJECT_TYPES[] = "other" }; -class version_error : public std::runtime_error +class version_error : public Slic3r::FileIOError { public: - version_error(const std::string& what_arg) : std::runtime_error(what_arg) {} - version_error(const char* what_arg) : std::runtime_error(what_arg) {} + version_error(const std::string& what_arg) : Slic3r::FileIOError(what_arg) {} + version_error(const char* what_arg) : Slic3r::FileIOError(what_arg) {} }; const char* get_attribute_value_charptr(const char** attributes, unsigned int attributes_size, const char* attribute_key) @@ -607,7 +608,7 @@ namespace Slic3r { { // ensure the zip archive is closed and rethrow the exception close_zip_reader(&archive); - throw std::runtime_error(e.what()); + throw Slic3r::FileIOError(e.what()); } } } @@ -780,7 +781,7 @@ namespace Slic3r { { char error_buf[1024]; ::sprintf(error_buf, "Error (%s) while parsing '%s' at line %d", XML_ErrorString(XML_GetErrorCode(data->parser)), data->stat.m_filename, (int)XML_GetCurrentLineNumber(data->parser)); - throw std::runtime_error(error_buf); + throw Slic3r::FileIOError(error_buf); } return n; @@ -789,7 +790,7 @@ namespace Slic3r { catch (const version_error& e) { // rethrow the exception - throw std::runtime_error(e.what()); + throw Slic3r::FileIOError(e.what()); } catch (std::exception& e) { @@ -2360,9 +2361,9 @@ namespace Slic3r { continue; if (!volume->mesh().repaired) - throw std::runtime_error("store_3mf() requires repair()"); + throw Slic3r::FileIOError("store_3mf() requires repair()"); if (!volume->mesh().has_shared_vertices()) - throw std::runtime_error("store_3mf() requires shared vertices"); + throw Slic3r::FileIOError("store_3mf() requires shared vertices"); volumes_offsets.insert(VolumeToOffsetsMap::value_type(volume, Offsets(vertices_count))).first; diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index af7b9b1b6..1a706afa9 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -7,6 +7,7 @@ #include #include "../libslic3r.h" +#include "../Exception.hpp" #include "../Model.hpp" #include "../GCode.hpp" #include "../PrintConfig.hpp" @@ -923,7 +924,7 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi { char error_buf[1024]; ::sprintf(error_buf, "Error (%s) while parsing '%s' at line %d", XML_ErrorString(XML_GetErrorCode(data->parser)), data->stat.m_filename, (int)XML_GetCurrentLineNumber(data->parser)); - throw std::runtime_error(error_buf); + throw Slic3r::FileIOError(error_buf); } return n; @@ -948,9 +949,9 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi if (check_version && (ctx.m_version > VERSION_AMF_COMPATIBLE)) { // std::string msg = _(L("The selected amf file has been saved with a newer version of " + std::string(SLIC3R_APP_NAME) + " and is not compatible.")); - // throw std::runtime_error(msg.c_str()); + // throw Slic3r::FileIOError(msg.c_str()); const std::string msg = (boost::format(_(L("The selected amf file has been saved with a newer version of %1% and is not compatible."))) % std::string(SLIC3R_APP_NAME)).str(); - throw std::runtime_error(msg); + throw Slic3r::FileIOError(msg); } return true; @@ -994,7 +995,7 @@ bool load_amf_archive(const char* path, DynamicPrintConfig* config, Model* model { // ensure the zip archive is closed and rethrow the exception close_zip_reader(&archive); - throw std::runtime_error(e.what()); + throw Slic3r::FileIOError(e.what()); } break; @@ -1147,9 +1148,9 @@ bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config, for (ModelVolume *volume : object->volumes) { vertices_offsets.push_back(num_vertices); if (! volume->mesh().repaired) - throw std::runtime_error("store_amf() requires repair()"); + throw Slic3r::FileIOError("store_amf() requires repair()"); if (! volume->mesh().has_shared_vertices()) - throw std::runtime_error("store_amf() requires shared vertices"); + throw Slic3r::FileIOError("store_amf() requires shared vertices"); const indexed_triangle_set &its = volume->mesh().its; const Transform3d& matrix = volume->get_matrix(); for (size_t i = 0; i < its.vertices.size(); ++i) { diff --git a/src/libslic3r/Format/PRUS.cpp b/src/libslic3r/Format/PRUS.cpp index d6f87197d..e2c38d957 100644 --- a/src/libslic3r/Format/PRUS.cpp +++ b/src/libslic3r/Format/PRUS.cpp @@ -147,7 +147,7 @@ static void extract_model_from_archive( } } if (! trafo_set) - throw std::runtime_error(std::string("Archive ") + path + " does not contain a valid entry in scene.xml for " + name); + throw Slic3r::FileIOError(std::string("Archive ") + path + " does not contain a valid entry in scene.xml for " + name); // Extract the STL. StlHeader header; @@ -266,7 +266,7 @@ static void extract_model_from_archive( } if (! mesh_valid) - throw std::runtime_error(std::string("Archive ") + path + " does not contain a valid mesh for " + name); + throw Slic3r::FileIOError(std::string("Archive ") + path + " does not contain a valid mesh for " + name); // Add this mesh to the model. ModelVolume *volume = nullptr; @@ -303,7 +303,7 @@ bool load_prus(const char *path, Model *model) mz_bool res = MZ_FALSE; try { if (!open_zip_reader(&archive, path)) - throw std::runtime_error(std::string("Unable to init zip reader for ") + path); + throw Slic3r::FileIOError(std::string("Unable to init zip reader for ") + path); std::vector scene_xml_data; // For grouping multiple STLs into a single ModelObject for multi-material prints. std::map group_to_model_object; @@ -316,10 +316,10 @@ bool load_prus(const char *path, Model *model) buffer.assign((size_t)stat.m_uncomp_size, 0); res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (char*)buffer.data(), (size_t)stat.m_uncomp_size, 0); if (res == MZ_FALSE) - std::runtime_error(std::string("Error while extracting a file from ") + path); + throw Slic3r::FileIOError(std::string("Error while extracting a file from ") + path); if (strcmp(stat.m_filename, "scene.xml") == 0) { if (! scene_xml_data.empty()) - throw std::runtime_error(std::string("Multiple scene.xml were found in the archive.") + path); + throw Slic3r::FileIOError(std::string("Multiple scene.xml were found in the archive.") + path); scene_xml_data = std::move(buffer); } else if (boost::iends_with(stat.m_filename, ".stl")) { // May throw std::exception diff --git a/src/libslic3r/Format/SL1.cpp b/src/libslic3r/Format/SL1.cpp index ff1af5d8b..274f84f00 100644 --- a/src/libslic3r/Format/SL1.cpp +++ b/src/libslic3r/Format/SL1.cpp @@ -10,6 +10,7 @@ #include +#include "libslic3r/Exception.hpp" #include "libslic3r/SlicesToTriangleMesh.hpp" #include "libslic3r/MarchingSquares.hpp" #include "libslic3r/ClipperUtils.hpp" @@ -64,7 +65,7 @@ boost::property_tree::ptree read_ini(const mz_zip_archive_file_stat &entry, if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename, buf.data(), buf.size(), 0)) - throw std::runtime_error(zip.get_errorstr()); + throw Slic3r::FileIOError(zip.get_errorstr()); boost::property_tree::ptree tree; std::stringstream ss(buf); @@ -80,7 +81,7 @@ PNGBuffer read_png(const mz_zip_archive_file_stat &entry, if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename, buf.data(), buf.size(), 0)) - throw std::runtime_error(zip.get_errorstr()); + throw Slic3r::FileIOError(zip.get_errorstr()); return {std::move(buf), (name.empty() ? entry.m_filename : name)}; } @@ -94,7 +95,7 @@ ArchiveData extract_sla_archive(const std::string &zipfname, struct Arch: public MZ_Archive { Arch(const std::string &fname) { if (!open_zip_reader(&arch, fname)) - throw std::runtime_error(get_errorstr()); + throw Slic3r::FileIOError(get_errorstr()); } ~Arch() { close_zip_reader(&arch); } @@ -202,7 +203,7 @@ RasterParams get_raster_params(const DynamicPrintConfig &cfg) if (!opt_disp_cols || !opt_disp_rows || !opt_disp_w || !opt_disp_h || !opt_mirror_x || !opt_mirror_y || !opt_orient) - throw std::runtime_error("Invalid SL1 file"); + throw Slic3r::FileIOError("Invalid SL1 file"); RasterParams rstp; @@ -228,7 +229,7 @@ SliceParams get_slice_params(const DynamicPrintConfig &cfg) auto *opt_init_layerh = cfg.option("initial_layer_height"); if (!opt_layerh || !opt_init_layerh) - throw std::runtime_error("Invalid SL1 file"); + throw Slic3r::FileIOError("Invalid SL1 file"); return SliceParams{opt_layerh->getFloat(), opt_init_layerh->getFloat()}; } diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 4da1edd7f..4bb5f9cd1 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1,6 +1,7 @@ #include "libslic3r.h" #include "I18N.hpp" #include "GCode.hpp" +#include "Exception.hpp" #include "ExtrusionEntity.hpp" #include "EdgeGrid.hpp" #include "Geometry.hpp" @@ -287,7 +288,7 @@ namespace Slic3r { std::string WipeTowerIntegration::append_tcr(GCode& gcodegen, const WipeTower::ToolChangeResult& tcr, int new_extruder_id, double z) const { if (new_extruder_id != -1 && new_extruder_id != tcr.new_tool) - throw std::invalid_argument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect."); + throw Slic3r::InvalidArgument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect."); std::string gcode; @@ -540,7 +541,7 @@ namespace Slic3r { if (!m_brim_done || gcodegen.writer().need_toolchange(extruder_id) || finish_layer) { if (m_layer_idx < (int)m_tool_changes.size()) { if (!(size_t(m_tool_change_idx) < m_tool_changes[m_layer_idx].size())) - throw std::runtime_error("Wipe tower generation failed, possibly due to empty first layer."); + throw Slic3r::RuntimeError("Wipe tower generation failed, possibly due to empty first layer."); // Calculate where the wipe tower layer will be printed. -1 means that print z will not change, @@ -629,7 +630,7 @@ std::vector GCode::collect_layers_to_print(const PrintObjec // Check that there are extrusions on the very first layer. if (layers_to_print.size() == 1u) { if (!has_extrusions) - throw std::runtime_error(_(L("There is an object with no extrusions on the first layer."))); + throw Slic3r::RuntimeError(_(L("There is an object with no extrusions on the first layer."))); } // In case there are extrusions on this layer, check there is a layer to lay it on. @@ -721,9 +722,9 @@ namespace DoExport { static void update_print_estimated_times_stats(const GCodeProcessor& processor, PrintStatistics& print_statistics) { const GCodeProcessor::Result& result = processor.get_result(); - print_statistics.estimated_normal_print_time = get_time_dhm(result.time_statistics.modes[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Normal)].time); + print_statistics.estimated_normal_print_time = get_time_dhms(result.time_statistics.modes[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Normal)].time); print_statistics.estimated_silent_print_time = processor.is_stealth_time_estimator_enabled() ? - get_time_dhm(result.time_statistics.modes[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].time) : "N/A"; + get_time_dhms(result.time_statistics.modes[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].time) : "N/A"; } } // namespace DoExport @@ -750,7 +751,7 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_ FILE *file = boost::nowide::fopen(path_tmp.c_str(), "wb"); if (file == nullptr) - throw std::runtime_error(std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n"); + throw Slic3r::RuntimeError(std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n"); #if !ENABLE_GCODE_VIEWER m_enable_analyzer = preview_data != nullptr; @@ -763,7 +764,7 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_ if (ferror(file)) { fclose(file); boost::nowide::remove(path_tmp.c_str()); - throw std::runtime_error(std::string("G-code export to ") + path + " failed\nIs the disk full?\n"); + throw Slic3r::RuntimeError(std::string("G-code export to ") + path + " failed\nIs the disk full?\n"); } } catch (std::exception & /* ex */) { // Rethrow on any exception. std::runtime_exception and CanceledException are expected to be thrown. @@ -784,14 +785,16 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_ msg += " !!!!! Failed to process the custom G-code template ...\n"; msg += "and\n"; msg += " !!!!! End of an error report for the custom G-code template ...\n"; - throw std::runtime_error(msg); + throw Slic3r::RuntimeError(msg); } #if ENABLE_GCODE_VIEWER - m_processor.process_file(path_tmp); + BOOST_LOG_TRIVIAL(debug) << "Start processing gcode, " << log_memory_info(); + m_processor.process_file(path_tmp, [print]() { print->throw_if_canceled(); }); DoExport::update_print_estimated_times_stats(m_processor, print->m_print_statistics); if (result != nullptr) *result = std::move(m_processor.extract_result()); + BOOST_LOG_TRIVIAL(debug) << "Finished processing gcode, " << log_memory_info(); #else GCodeTimeEstimator::PostProcessData normal_data = m_normal_time_estimator.get_post_process_data(); GCodeTimeEstimator::PostProcessData silent_data = m_silent_time_estimator.get_post_process_data(); @@ -816,7 +819,7 @@ void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_ #endif // ENABLE_GCODE_VIEWER if (rename_file(path_tmp, path)) - throw std::runtime_error( + throw Slic3r::RuntimeError( std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + path + '\n' + "Is " + path_tmp + " locked?" + '\n'); @@ -1944,8 +1947,8 @@ namespace ProcessLayer #if !ENABLE_GCODE_VIEWER // add tag for time estimator gcode += "; " + GCodeTimeEstimator::Pause_Print_Tag + "\n"; - gcode += config.pause_print_gcode; #endif // !ENABLE_GCODE_VIEWER + gcode += config.pause_print_gcode; } else { @@ -2457,14 +2460,17 @@ void GCode::process_layer( #endif /* HAS_PRESSURE_EQUALIZER */ _write(file, gcode); -#if !ENABLE_GCODE_VIEWER +#if ENABLE_GCODE_VIEWER + BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z << + log_memory_info(); +#else BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z << ", time estimator memory: " << - format_memsize_MB(m_normal_time_estimator.memory_used() + (m_silent_time_estimator_enabled ? m_silent_time_estimator.memory_used() : 0)) << - ", analyzer memory: " << - format_memsize_MB(m_analyzer.memory_used()) << - log_memory_info(); -#endif // !ENABLE_GCODE_VIEWER + format_memsize_MB(m_normal_time_estimator.memory_used() + (m_silent_time_estimator_enabled ? m_silent_time_estimator.memory_used() : 0)) << + ", analyzer memory: " << + format_memsize_MB(m_analyzer.memory_used()) << + log_memory_info(); +#endif // ENABLE_GCODE_VIEWER } void GCode::apply_print_config(const PrintConfig &print_config) @@ -2704,7 +2710,7 @@ std::string GCode::extrude_entity(const ExtrusionEntity &entity, std::string des else if (const ExtrusionLoop* loop = dynamic_cast(&entity)) return this->extrude_loop(*loop, description, speed, lower_layer_edge_grid); else - throw std::invalid_argument("Invalid argument supplied to extrude()"); + throw Slic3r::InvalidArgument("Invalid argument supplied to extrude()"); return ""; } @@ -2909,7 +2915,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, } else if (path.role() == erGapFill) { speed = m_config.get_abs_value("gap_fill_speed"); } else { - throw std::invalid_argument("Invalid speed"); + throw Slic3r::InvalidArgument("Invalid speed"); } } if (this->on_first_layer()) @@ -3330,7 +3336,7 @@ void GCode::ObjectByExtruder::Island::Region::append(const Type type, const Extr perimeters_or_infills_overrides = &infills_overrides; break; default: - throw std::invalid_argument("Unknown parameter!"); + throw Slic3r::InvalidArgument("Unknown parameter!"); } // First we append the entities, there are eec->entities.size() of them: diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 13b1ed1a8..0a5617559 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -11,10 +11,7 @@ #include #if ENABLE_GCODE_VIEWER - -#if ENABLE_GCODE_VIEWER_STATISTICS #include -#endif // ENABLE_GCODE_VIEWER_STATISTICS static const float INCHES_TO_MM = 25.4f; static const float MMMIN_TO_MMSEC = 1.0f / 60.0f; @@ -322,13 +319,13 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) { boost::nowide::ifstream in(filename); if (!in.good()) - throw std::runtime_error(std::string("Time estimator post process export failed.\nCannot open file for reading.\n")); + throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nCannot open file for reading.\n")); // temporary file to contain modified gcode std::string out_path = filename + ".postprocess"; FILE* out = boost::nowide::fopen(out_path.c_str(), "wb"); if (out == nullptr) - throw std::runtime_error(std::string("Time estimator post process export failed.\nCannot open file for writing.\n")); + throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nCannot open file for writing.\n")); auto time_in_minutes = [](float time_in_seconds) { return int(::roundf(time_in_seconds / 60.0f)); @@ -421,7 +418,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) in.close(); fclose(out); boost::nowide::remove(out_path.c_str()); - throw std::runtime_error(std::string("Time estimator post process export failed.\nIs the disk full?\n")); + throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nIs the disk full?\n")); } export_line.clear(); }; @@ -429,7 +426,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) while (std::getline(in, gcode_line)) { if (!in.good()) { fclose(out); - throw std::runtime_error(std::string("Time estimator post process export failed.\nError while reading from file.\n")); + throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nError while reading from file.\n")); } gcode_line += "\n"; @@ -463,7 +460,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) in.close(); if (rename_file(out_path, filename)) - throw std::runtime_error(std::string("Failed to rename the output G-code file from ") + out_path + " to " + filename + '\n' + + throw Slic3r::RuntimeError(std::string("Failed to rename the output G-code file from ") + out_path + " to " + filename + '\n' + "Is " + out_path + " locked?" + '\n'); } @@ -730,8 +727,10 @@ void GCodeProcessor::reset() #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING } -void GCodeProcessor::process_file(const std::string& filename) +void GCodeProcessor::process_file(const std::string& filename, std::function cancel_callback) { + auto last_cancel_callback_time = std::chrono::high_resolution_clock::now(); + #if ENABLE_GCODE_VIEWER_STATISTICS auto start_time = std::chrono::high_resolution_clock::now(); #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -758,9 +757,21 @@ void GCodeProcessor::process_file(const std::string& filename) } } + // process gcode m_result.id = ++s_result_id; + // 1st move must be a dummy move m_result.moves.emplace_back(MoveVertex()); - m_parser.parse_file(filename, [this](GCodeReader& reader, const GCodeReader::GCodeLine& line) { process_gcode_line(line); }); + m_parser.parse_file(filename, [this, cancel_callback, &last_cancel_callback_time](GCodeReader& reader, const GCodeReader::GCodeLine& line) { + if (cancel_callback != nullptr) { + // call the cancel callback every 100 ms + auto curr_time = std::chrono::high_resolution_clock::now(); + if (std::chrono::duration_cast(curr_time - last_cancel_callback_time).count() > 100) { + cancel_callback(); + last_cancel_callback_time = curr_time; + } + } + process_gcode_line(line); + }); // process the time blocks for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { @@ -936,6 +947,20 @@ void GCodeProcessor::process_tags(const std::string& comment) return; } + if (!m_producers_enabled || m_producer == EProducer::PrusaSlicer) { + // height tag + pos = comment.find(Height_Tag); + if (pos != comment.npos) { + try { + m_height = std::stof(comment.substr(pos + Height_Tag.length())); + } + catch (...) { + BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ")."; + } + return; + } + } + #if ENABLE_GCODE_VIEWER_DATA_CHECKING // width tag pos = comment.find(Width_Tag); @@ -948,18 +973,6 @@ void GCodeProcessor::process_tags(const std::string& comment) } return; } - - // height tag - pos = comment.find(Height_Tag); - if (pos != comment.npos) { - try { - m_height_compare.last_tag_value = std::stof(comment.substr(pos + Height_Tag.length())); - } - catch (...) { - BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid value for Height (" << comment << ")."; - } - return; - } #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING // color change tag @@ -1408,12 +1421,12 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) type = EMoveType::Travel; if (type == EMoveType::Extrude) { - float d_xyz = std::sqrt(sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z])); + float delta_xyz = std::sqrt(sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z])); float filament_diameter = (static_cast(m_extruder_id) < m_filament_diameters.size()) ? m_filament_diameters[m_extruder_id] : m_filament_diameters.back(); float filament_radius = 0.5f * filament_diameter; float area_filament_cross_section = static_cast(M_PI) * sqr(filament_radius); float volume_extruded_filament = area_filament_cross_section * delta_pos[E]; - float area_toolpath_cross_section = volume_extruded_filament / d_xyz; + float area_toolpath_cross_section = volume_extruded_filament / delta_xyz; // volume extruded filament / tool displacement = area toolpath cross section m_mm3_per_mm = area_toolpath_cross_section; @@ -1421,23 +1434,28 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) m_mm3_per_mm_compare.update(area_toolpath_cross_section, m_extrusion_role); #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING - if (m_end_position[Z] > m_extruded_last_z + EPSILON) { - m_height = m_end_position[Z] - m_extruded_last_z; + if ((m_producers_enabled && m_producer != EProducer::PrusaSlicer) || m_height == 0.0f) { + if (m_end_position[Z] > m_extruded_last_z + EPSILON) { + m_height = m_end_position[Z] - m_extruded_last_z; #if ENABLE_GCODE_VIEWER_DATA_CHECKING - m_height_compare.update(m_height, m_extrusion_role); + m_height_compare.update(m_height, m_extrusion_role); #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING - m_extruded_last_z = m_end_position[Z]; + m_extruded_last_z = m_end_position[Z]; + } } if (m_extrusion_role == erExternalPerimeter) // cross section: rectangle - m_width = delta_pos[E] * static_cast(M_PI * sqr(1.05 * filament_radius)) / (d_xyz * m_height); + m_width = delta_pos[E] * static_cast(M_PI * sqr(1.05 * filament_radius)) / (delta_xyz * m_height); else if (m_extrusion_role == erBridgeInfill || m_extrusion_role == erNone) // cross section: circle - m_width = static_cast(m_filament_diameters[m_extruder_id]) * std::sqrt(delta_pos[E] / d_xyz); + m_width = static_cast(m_filament_diameters[m_extruder_id]) * std::sqrt(delta_pos[E] / delta_xyz); else // cross section: rectangle + 2 semicircles - m_width = delta_pos[E] * static_cast(M_PI * sqr(filament_radius)) / (d_xyz * m_height) + static_cast(1.0 - 0.25 * M_PI) * m_height; + m_width = delta_pos[E] * static_cast(M_PI * sqr(filament_radius)) / (delta_xyz * m_height) + static_cast(1.0 - 0.25 * M_PI) * m_height; + + // clamp width to avoid artifacts which may arise from wrong values of m_height + m_width = std::min(m_width, 4.0f * m_height); #if ENABLE_GCODE_VIEWER_DATA_CHECKING m_width_compare.update(m_width, m_extrusion_role); diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 22aeed762..b31591ca8 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -419,7 +419,8 @@ namespace Slic3r { Result&& extract_result() { return std::move(m_result); } // Process the gcode contained in the file with the given filename - void process_file(const std::string& filename); + // throws CanceledException through print->throw_if_canceled() (sent by the caller as callback). + void process_file(const std::string& filename, std::function cancel_callback = nullptr); float get_time(PrintEstimatedTimeStatistics::ETimeMode mode) const; std::string get_time_dhm(PrintEstimatedTimeStatistics::ETimeMode mode) const; diff --git a/src/libslic3r/GCode/PostProcessor.cpp b/src/libslic3r/GCode/PostProcessor.cpp index 25982959b..17aa76fb9 100644 --- a/src/libslic3r/GCode/PostProcessor.cpp +++ b/src/libslic3r/GCode/PostProcessor.cpp @@ -79,7 +79,7 @@ static DWORD execute_process_winapi(const std::wstring &command_line) if (! ::CreateProcessW( nullptr /* lpApplicationName */, (LPWSTR)command_line.c_str(), nullptr /* lpProcessAttributes */, nullptr /* lpThreadAttributes */, false /* bInheritHandles */, CREATE_UNICODE_ENVIRONMENT /* | CREATE_NEW_CONSOLE */ /* dwCreationFlags */, (LPVOID)envstr.c_str(), nullptr /* lpCurrentDirectory */, &startup_info, &process_info)) - throw std::runtime_error(std::string("Failed starting the script ") + boost::nowide::narrow(command_line) + ", Win32 error: " + std::to_string(int(::GetLastError()))); + throw Slic3r::RuntimeError(std::string("Failed starting the script ") + boost::nowide::narrow(command_line) + ", Win32 error: " + std::to_string(int(::GetLastError()))); ::WaitForSingleObject(process_info.hProcess, INFINITE); ULONG rc = 0; ::GetExitCodeProcess(process_info.hProcess, &rc); @@ -98,13 +98,13 @@ static int run_script(const std::string &script, const std::string &gcode, std:: LPWSTR *szArglist = CommandLineToArgvW(boost::nowide::widen(script).c_str(), &nArgs); if (szArglist == nullptr || nArgs <= 0) { // CommandLineToArgvW failed. Maybe the command line escapment is invalid? - throw std::runtime_error(std::string("Post processing script ") + script + " on file " + gcode + " failed. CommandLineToArgvW() refused to parse the command line path."); + throw Slic3r::RuntimeError(std::string("Post processing script ") + script + " on file " + gcode + " failed. CommandLineToArgvW() refused to parse the command line path."); } std::wstring command_line; std::wstring command = szArglist[0]; if (! boost::filesystem::exists(boost::filesystem::path(command))) - throw std::runtime_error(std::string("The configured post-processing script does not exist: ") + boost::nowide::narrow(command)); + throw Slic3r::RuntimeError(std::string("The configured post-processing script does not exist: ") + boost::nowide::narrow(command)); if (boost::iends_with(command, L".pl")) { // This is a perl script. Run it through the perl interpreter. // The current process may be slic3r.exe or slic3r-console.exe. @@ -115,7 +115,7 @@ static int run_script(const std::string &script, const std::string &gcode, std:: boost::filesystem::path path_perl = path_exe.parent_path() / "perl" / "perl.exe"; if (! boost::filesystem::exists(path_perl)) { LocalFree(szArglist); - throw std::runtime_error(std::string("Perl interpreter ") + path_perl.string() + " does not exist."); + throw Slic3r::RuntimeError(std::string("Perl interpreter ") + path_perl.string() + " does not exist."); } // Replace it with the current perl interpreter. quote_argv_winapi(boost::nowide::widen(path_perl.string()), command_line); @@ -187,7 +187,7 @@ void run_post_process_scripts(const std::string &path, const PrintConfig &config config.setenv_(); auto gcode_file = boost::filesystem::path(path); if (! boost::filesystem::exists(gcode_file)) - throw std::runtime_error(std::string("Post-processor can't find exported gcode file")); + throw Slic3r::RuntimeError(std::string("Post-processor can't find exported gcode file")); for (const std::string &scripts : config.post_process.values) { std::vector lines; @@ -205,7 +205,7 @@ void run_post_process_scripts(const std::string &path, const PrintConfig &config const std::string msg = std_err.empty() ? (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%") % script % path % result).str() : (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%\nOutput:\n%4%") % script % path % result % std_err).str(); BOOST_LOG_TRIVIAL(error) << msg; - throw std::runtime_error(msg); + throw Slic3r::RuntimeError(msg); } } } diff --git a/src/libslic3r/GCode/PressureEqualizer.cpp b/src/libslic3r/GCode/PressureEqualizer.cpp index 3b2a58a88..33601e5e9 100644 --- a/src/libslic3r/GCode/PressureEqualizer.cpp +++ b/src/libslic3r/GCode/PressureEqualizer.cpp @@ -148,7 +148,7 @@ static inline int parse_int(const char *&line) char *endptr = NULL; long result = strtol(line, &endptr, 10); if (endptr == NULL || !is_ws_or_eol(*endptr)) - throw std::runtime_error("PressureEqualizer: Error parsing an int"); + throw Slic3r::RuntimeError("PressureEqualizer: Error parsing an int"); line = endptr; return int(result); }; @@ -160,7 +160,7 @@ static inline float parse_float(const char *&line) char *endptr = NULL; float result = strtof(line, &endptr); if (endptr == NULL || !is_ws_or_eol(*endptr)) - throw std::runtime_error("PressureEqualizer: Error parsing a float"); + throw Slic3r::RuntimeError("PressureEqualizer: Error parsing a float"); line = endptr; return result; }; @@ -229,7 +229,7 @@ bool PressureEqualizer::process_line(const char *line, const size_t len, GCodeLi assert(false); } if (i == -1) - throw std::runtime_error(std::string("GCode::PressureEqualizer: Invalid axis for G0/G1: ") + axis); + throw Slic3r::RuntimeError(std::string("GCode::PressureEqualizer: Invalid axis for G0/G1: ") + axis); buf.pos_provided[i] = true; new_pos[i] = parse_float(line); if (i == 3 && m_config->use_relative_e_distances.value) @@ -298,7 +298,7 @@ bool PressureEqualizer::process_line(const char *line, const size_t len, GCodeLi set = true; break; default: - throw std::runtime_error(std::string("GCode::PressureEqualizer: Incorrect axis in a G92 G-code: ") + axis); + throw Slic3r::RuntimeError(std::string("GCode::PressureEqualizer: Incorrect axis in a G92 G-code: ") + axis); } eatws(line); } diff --git a/src/libslic3r/GCode/PrintExtents.cpp b/src/libslic3r/GCode/PrintExtents.cpp index 4a6624531..a86411519 100644 --- a/src/libslic3r/GCode/PrintExtents.cpp +++ b/src/libslic3r/GCode/PrintExtents.cpp @@ -94,7 +94,7 @@ static BoundingBoxf extrusionentity_extents(const ExtrusionEntity *extrusion_ent auto *extrusion_entity_collection = dynamic_cast(extrusion_entity); if (extrusion_entity_collection != nullptr) return extrusionentity_extents(*extrusion_entity_collection); - throw std::runtime_error("Unexpected extrusion_entity type in extrusionentity_extents()"); + throw Slic3r::RuntimeError("Unexpected extrusion_entity type in extrusionentity_extents()"); return BoundingBoxf(); } diff --git a/src/libslic3r/GCodeReader.cpp b/src/libslic3r/GCodeReader.cpp index ab77b0141..ee24d5bb7 100644 --- a/src/libslic3r/GCodeReader.cpp +++ b/src/libslic3r/GCodeReader.cpp @@ -1,6 +1,9 @@ #include "GCodeReader.hpp" #include #include +#if ENABLE_GCODE_VIEWER +#include +#endif // ENABLE_GCODE_VIEWER #include #include #include @@ -113,7 +116,11 @@ void GCodeReader::update_coordinates(GCodeLine &gline, std::pair @@ -254,13 +255,13 @@ namespace Slic3r { { boost::nowide::ifstream in(filename); if (!in.good()) - throw std::runtime_error(std::string("Time estimator post process export failed.\nCannot open file for reading.\n")); + throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nCannot open file for reading.\n")); std::string path_tmp = filename + ".postprocess"; FILE* out = boost::nowide::fopen(path_tmp.c_str(), "wb"); if (out == nullptr) - throw std::runtime_error(std::string("Time estimator post process export failed.\nCannot open file for writing.\n")); + throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nCannot open file for writing.\n")); std::string normal_time_mask = "M73 P%s R%s\n"; std::string silent_time_mask = "M73 Q%s S%s\n"; @@ -278,7 +279,7 @@ namespace Slic3r { in.close(); fclose(out); boost::nowide::remove(path_tmp.c_str()); - throw std::runtime_error(std::string("Time estimator post process export failed.\nIs the disk full?\n")); + throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nIs the disk full?\n")); } export_line.clear(); }; @@ -326,7 +327,7 @@ namespace Slic3r { if (!in.good()) { fclose(out); - throw std::runtime_error(std::string("Time estimator post process export failed.\nError while reading from file.\n")); + throw Slic3r::RuntimeError(std::string("Time estimator post process export failed.\nError while reading from file.\n")); } // check tags @@ -383,7 +384,7 @@ namespace Slic3r { in.close(); if (rename_file(path_tmp, filename)) - throw std::runtime_error(std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + filename + '\n' + + throw Slic3r::RuntimeError(std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + filename + '\n' + "Is " + path_tmp + " locked?" + '\n'); return true; diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 00a4ad47c..3b9fcd617 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -1,4 +1,5 @@ #include "libslic3r.h" +#include "Exception.hpp" #include "Geometry.hpp" #include "ClipperUtils.hpp" #include "ExPolygon.hpp" @@ -471,7 +472,7 @@ Pointfs arrange(size_t num_parts, const Vec2d &part_size, coordf_t gap, const Bo size_t cellw = size_t(floor((bed_bbox.size()(0) + gap) / cell_size(0))); size_t cellh = size_t(floor((bed_bbox.size()(1) + gap) / cell_size(1))); if (num_parts > cellw * cellh) - throw std::invalid_argument("%zu parts won't fit in your print area!\n", num_parts); + throw Slic3r::InvalidArgument("%zu parts won't fit in your print area!\n", num_parts); // Get a bounding box of cellw x cellh cells, centered at the center of the bed. Vec2d cells_size(cellw * cell_size(0) - gap, cellh * cell_size(1) - gap); diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index 75f3708d2..b690b478d 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -281,7 +281,7 @@ bool directions_parallel(double angle1, double angle2, double max_diff = 0); template bool contains(const std::vector &vector, const Point &point); template T rad2deg(T angle) { return T(180.0) * angle / T(PI); } double rad2deg_dir(double angle); -template T deg2rad(T angle) { return T(PI) * angle / T(180.0); } +template constexpr T deg2rad(const T angle) { return T(PI) * angle / T(180.0); } template T angle_to_0_2PI(T angle) { static const T TWO_PI = T(2) * T(PI); diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index c104d46da..8285b5493 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -13,6 +13,10 @@ class Layer; class PrintRegion; class PrintObject; +namespace FillAdaptive { + struct Octree; +}; + class LayerRegion { public: @@ -134,7 +138,8 @@ public: return false; } void make_perimeters(); - void make_fills(); + void make_fills() { this->make_fills(nullptr, nullptr); }; + void make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree); void make_ironing(); void export_region_slices_to_svg(const char *path) const; diff --git a/src/libslic3r/MeshBoolean.cpp b/src/libslic3r/MeshBoolean.cpp index 66167c720..dd34b2830 100644 --- a/src/libslic3r/MeshBoolean.cpp +++ b/src/libslic3r/MeshBoolean.cpp @@ -1,3 +1,4 @@ +#include "Exception.hpp" #include "MeshBoolean.hpp" #include "libslic3r/TriangleMesh.hpp" #undef PI @@ -136,7 +137,7 @@ template void triangle_mesh_to_cgal(const TriangleMesh &M, _Mesh &o if(CGAL::is_closed(out)) CGALProc::orient_to_bound_a_volume(out); else - std::runtime_error("Mesh not watertight"); + throw Slic3r::RuntimeError("Mesh not watertight"); } inline Vec3d to_vec3d(const _EpicMesh::Point &v) @@ -222,7 +223,7 @@ template void _cgal_do(Op &&op, CGALMesh &A, CGALMesh &B) } if (! success) - throw std::runtime_error("CGAL mesh boolean operation failed."); + throw Slic3r::RuntimeError("CGAL mesh boolean operation failed."); } void minus(CGALMesh &A, CGALMesh &B) { _cgal_do(_cgal_diff, A, B); } diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 67fe63871..3539cc0fa 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1,3 +1,4 @@ +#include "Exception.hpp" #include "Model.hpp" #include "ModelArrange.hpp" #include "Geometry.hpp" @@ -116,13 +117,13 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c else if (boost::algorithm::iends_with(input_file, ".prusa")) result = load_prus(input_file.c_str(), &model); else - throw std::runtime_error("Unknown file format. Input file must have .stl, .obj, .amf(.xml) or .prusa extension."); + throw Slic3r::RuntimeError("Unknown file format. Input file must have .stl, .obj, .amf(.xml) or .prusa extension."); if (! result) - throw std::runtime_error("Loading of a model file failed."); + throw Slic3r::RuntimeError("Loading of a model file failed."); if (model.objects.empty()) - throw std::runtime_error("The supplied file couldn't be read because it's empty"); + throw Slic3r::RuntimeError("The supplied file couldn't be read because it's empty"); for (ModelObject *o : model.objects) o->input_file = input_file; @@ -146,13 +147,13 @@ Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig else if (boost::algorithm::iends_with(input_file, ".zip.amf")) result = load_amf(input_file.c_str(), config, &model, check_version); else - throw std::runtime_error("Unknown file format. Input file must have .3mf or .zip.amf extension."); + throw Slic3r::RuntimeError("Unknown file format. Input file must have .3mf or .zip.amf extension."); if (!result) - throw std::runtime_error("Loading of a model file failed."); + throw Slic3r::RuntimeError("Loading of a model file failed."); if (model.objects.empty()) - throw std::runtime_error("The supplied file couldn't be read because it's empty"); + throw Slic3r::RuntimeError("The supplied file couldn't be read because it's empty"); for (ModelObject *o : model.objects) { @@ -776,6 +777,38 @@ TriangleMesh ModelObject::raw_mesh() const return mesh; } +// Non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes. +// Currently used by ModelObject::mesh(), to calculate the 2D envelope for 2D plater +// and to display the object statistics at ModelObject::print_info(). +indexed_triangle_set ModelObject::raw_indexed_triangle_set() const +{ + size_t num_vertices = 0; + size_t num_faces = 0; + for (const ModelVolume *v : this->volumes) + if (v->is_model_part()) { + num_vertices += v->mesh().its.vertices.size(); + num_faces += v->mesh().its.indices.size(); + } + indexed_triangle_set out; + out.vertices.reserve(num_vertices); + out.indices.reserve(num_faces); + for (const ModelVolume *v : this->volumes) + if (v->is_model_part()) { + size_t i = out.vertices.size(); + size_t j = out.indices.size(); + append(out.vertices, v->mesh().its.vertices); + append(out.indices, v->mesh().its.indices); + auto m = v->get_matrix(); + for (; i < out.vertices.size(); ++ i) + out.vertices[i] = (m * out.vertices[i].cast()).cast().eval(); + if (v->is_left_handed()) { + for (; j < out.indices.size(); ++ j) + std::swap(out.indices[j][0], out.indices[j][1]); + } + } + return out; +} + // Non-transformed (non-rotated, non-scaled, non-translated) sum of all object volumes. TriangleMesh ModelObject::full_raw_mesh() const { @@ -817,7 +850,7 @@ const BoundingBoxf3& ModelObject::raw_bounding_box() const m_raw_bounding_box_valid = true; m_raw_bounding_box.reset(); if (this->instances.empty()) - throw std::invalid_argument("Can't call raw_bounding_box() with no instances"); + throw Slic3r::InvalidArgument("Can't call raw_bounding_box() with no instances"); const Transform3d& inst_matrix = this->instances.front()->get_transformation().get_matrix(true); for (const ModelVolume *v : this->volumes) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index a623f5cca..b9045e28b 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -244,6 +244,8 @@ public: // Non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes. // Currently used by ModelObject::mesh() and to calculate the 2D envelope for 2D plater. TriangleMesh raw_mesh() const; + // The same as above, but producing a lightweight indexed_triangle_set. + indexed_triangle_set raw_indexed_triangle_set() const; // Non-transformed (non-rotated, non-scaled, non-translated) sum of all object volumes. TriangleMesh full_raw_mesh() const; // A transformed snug bounding box around the non-modifier object volumes, without the translation applied. diff --git a/src/libslic3r/ModelArrange.hpp b/src/libslic3r/ModelArrange.hpp index afe146d43..124c5c018 100644 --- a/src/libslic3r/ModelArrange.hpp +++ b/src/libslic3r/ModelArrange.hpp @@ -20,7 +20,7 @@ using VirtualBedFn = std::function; [[noreturn]] inline void throw_if_out_of_bed(arrangement::ArrangePolygon&) { - throw std::runtime_error("Objects could not fit on the bed"); + throw Slic3r::RuntimeError("Objects could not fit on the bed"); } ArrangePolygons get_arrange_polys(const Model &model, ModelInstancePtrs &instances); diff --git a/src/libslic3r/OpenVDBUtils.cpp b/src/libslic3r/OpenVDBUtils.cpp index c30052036..31ae203dd 100644 --- a/src/libslic3r/OpenVDBUtils.cpp +++ b/src/libslic3r/OpenVDBUtils.cpp @@ -2,6 +2,7 @@ #include "OpenVDBUtils.hpp" #include #include +#include #include //#include "MTUtils.hpp" @@ -57,7 +58,6 @@ void Contour3DDataAdapter::getIndexSpacePoint(size_t n, // TODO: Do I need to call initialize? Seems to work without it as well but the // docs say it should be called ones. It does a mutex lock-unlock sequence all // even if was called previously. - openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh &mesh, const openvdb::math::Transform &tr, float exteriorBandWidth, @@ -65,9 +65,38 @@ openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh &mesh, int flags) { openvdb::initialize(); - return openvdb::tools::meshToVolume( - TriangleMeshDataAdapter{mesh}, tr, exteriorBandWidth, - interiorBandWidth, flags); + + TriangleMeshPtrs meshparts = mesh.split(); + + auto it = std::remove_if(meshparts.begin(), meshparts.end(), + [](TriangleMesh *m){ + m->require_shared_vertices(); + return !m->is_manifold() || m->volume() < EPSILON; + }); + + meshparts.erase(it, meshparts.end()); + + openvdb::FloatGrid::Ptr grid; + for (TriangleMesh *m : meshparts) { + auto subgrid = openvdb::tools::meshToVolume( + TriangleMeshDataAdapter{*m}, tr, exteriorBandWidth, + interiorBandWidth, flags); + + if (grid && subgrid) openvdb::tools::csgUnion(*grid, *subgrid); + else if (subgrid) grid = std::move(subgrid); + } + + if (grid) { + grid = openvdb::tools::levelSetRebuild(*grid, 0., exteriorBandWidth, + interiorBandWidth); + } else if(meshparts.empty()) { + // Splitting failed, fall back to hollow the original mesh + grid = openvdb::tools::meshToVolume( + TriangleMeshDataAdapter{mesh}, tr, exteriorBandWidth, + interiorBandWidth, flags); + } + + return grid; } openvdb::FloatGrid::Ptr mesh_to_grid(const sla::Contour3D &mesh, diff --git a/src/libslic3r/Optimize/BruteforceOptimizer.hpp b/src/libslic3r/Optimize/BruteforceOptimizer.hpp new file mode 100644 index 000000000..2daef538e --- /dev/null +++ b/src/libslic3r/Optimize/BruteforceOptimizer.hpp @@ -0,0 +1,140 @@ +#ifndef BRUTEFORCEOPTIMIZER_HPP +#define BRUTEFORCEOPTIMIZER_HPP + +#include + +namespace Slic3r { namespace opt { + +namespace detail { +// Implementing a bruteforce optimizer + +// Return the number of iterations needed to reach a specific grid position (idx) +template +long num_iter(const std::array &idx, size_t gridsz) +{ + long ret = 0; + for (size_t i = 0; i < N; ++i) ret += idx[i] * std::pow(gridsz, i); + return ret; +} + +// Implementation of a grid search where the search interval is sampled in +// equidistant points for each dimension. Grid size determines the number of +// samples for one dimension so the number of function calls is gridsize ^ dimension. +struct AlgBurteForce { + bool to_min; + StopCriteria stc; + size_t gridsz; + + AlgBurteForce(const StopCriteria &cr, size_t gs): stc{cr}, gridsz{gs} {} + + // This function is called recursively for each dimension and generates + // the grid values for the particular dimension. If D is less than zero, + // the object function input values are generated for each dimension and it + // can be evaluated. The current best score is compared with the newly + // returned score and changed appropriately. + template + bool run(std::array &idx, + Result &result, + const Bounds &bounds, + Fn &&fn, + Cmp &&cmp) + { + if (stc.stop_condition()) return false; + + if constexpr (D < 0) { // Let's evaluate fn + Input inp; + + auto max_iter = stc.max_iterations(); + if (max_iter && num_iter(idx, gridsz) >= max_iter) + return false; + + for (size_t d = 0; d < N; ++d) { + const Bound &b = bounds[d]; + double step = (b.max() - b.min()) / (gridsz - 1); + inp[d] = b.min() + idx[d] * step; + } + + auto score = fn(inp); + if (cmp(score, result.score)) { // Change current score to the new + double absdiff = std::abs(score - result.score); + + result.score = score; + result.optimum = inp; + + // Check if the required precision is reached. + if (absdiff < stc.abs_score_diff() || + absdiff < stc.rel_score_diff() * std::abs(score)) + return false; + } + + } else { + for (size_t i = 0; i < gridsz; ++i) { + idx[D] = i; // Mark the current grid position and dig down + if (!run(idx, result, bounds, std::forward(fn), + std::forward(cmp))) + return false; + } + } + + return true; + } + + template + Result optimize(Fn&& fn, + const Input &/*initvals*/, + const Bounds& bounds) + { + std::array idx = {}; + Result result; + + if (to_min) { + result.score = std::numeric_limits::max(); + run(idx, result, bounds, std::forward(fn), + std::less{}); + } + else { + result.score = std::numeric_limits::lowest(); + run(idx, result, bounds, std::forward(fn), + std::greater{}); + } + + return result; + } +}; + +} // namespace detail + +using AlgBruteForce = detail::AlgBurteForce; + +template<> +class Optimizer { + AlgBruteForce m_alg; + +public: + + Optimizer(const StopCriteria &cr = {}, size_t gridsz = 100) + : m_alg{cr, gridsz} + {} + + Optimizer& to_max() { m_alg.to_min = false; return *this; } + Optimizer& to_min() { m_alg.to_min = true; return *this; } + + template + Result optimize(Func&& func, + const Input &initvals, + const Bounds& bounds) + { + return m_alg.optimize(std::forward(func), initvals, bounds); + } + + Optimizer &set_criteria(const StopCriteria &cr) + { + m_alg.stc = cr; return *this; + } + + const StopCriteria &get_criteria() const { return m_alg.stc; } +}; + +}} // namespace Slic3r::opt + +#endif // BRUTEFORCEOPTIMIZER_HPP diff --git a/src/libslic3r/Optimizer.hpp b/src/libslic3r/Optimize/NLoptOptimizer.hpp similarity index 59% rename from src/libslic3r/Optimizer.hpp rename to src/libslic3r/Optimize/NLoptOptimizer.hpp index 6495ae7ff..826b1632a 100644 --- a/src/libslic3r/Optimizer.hpp +++ b/src/libslic3r/Optimize/NLoptOptimizer.hpp @@ -12,134 +12,11 @@ #endif #include -#include -#include -#include -#include -#include -#include + +#include namespace Slic3r { namespace opt { -// A type to hold the complete result of the optimization. -template struct Result { - int resultcode; - std::array optimum; - double score; -}; - -// An interval of possible input values for optimization -class Bound { - double m_min, m_max; - -public: - Bound(double min = std::numeric_limits::min(), - double max = std::numeric_limits::max()) - : m_min(min), m_max(max) - {} - - double min() const noexcept { return m_min; } - double max() const noexcept { return m_max; } -}; - -// Helper types for optimization function input and bounds -template using Input = std::array; -template using Bounds = std::array; - -// A type for specifying the stop criteria. Setter methods can be concatenated -class StopCriteria { - - // If the absolute value difference between two scores. - double m_abs_score_diff = std::nan(""); - - // If the relative value difference between two scores. - double m_rel_score_diff = std::nan(""); - - // Stop if this value or better is found. - double m_stop_score = std::nan(""); - - // A predicate that if evaluates to true, the optimization should terminate - // and the best result found prior to termination should be returned. - std::function m_stop_condition = [] { return false; }; - - // The max allowed number of iterations. - unsigned m_max_iterations = 0; - -public: - - StopCriteria & abs_score_diff(double val) - { - m_abs_score_diff = val; return *this; - } - - double abs_score_diff() const { return m_abs_score_diff; } - - StopCriteria & rel_score_diff(double val) - { - m_rel_score_diff = val; return *this; - } - - double rel_score_diff() const { return m_rel_score_diff; } - - StopCriteria & stop_score(double val) - { - m_stop_score = val; return *this; - } - - double stop_score() const { return m_stop_score; } - - StopCriteria & max_iterations(double val) - { - m_max_iterations = val; return *this; - } - - double max_iterations() const { return m_max_iterations; } - - template StopCriteria & stop_condition(Fn &&cond) - { - m_stop_condition = cond; return *this; - } - - bool stop_condition() { return m_stop_condition(); } -}; - -// Helper class to use optimization methods involving gradient. -template struct ScoreGradient { - double score; - std::optional> gradient; - - ScoreGradient(double s, const std::array &grad) - : score{s}, gradient{grad} - {} -}; - -// Helper to be used in static_assert. -template struct always_false { enum { value = false }; }; - -// Basic interface to optimizer object -template class Optimizer { -public: - - Optimizer(const StopCriteria &) - { - static_assert (always_false::value, - "Optimizer unimplemented for given method!"); - } - - Optimizer &to_min() { return *this; } - Optimizer &to_max() { return *this; } - Optimizer &set_criteria(const StopCriteria &) { return *this; } - StopCriteria get_criteria() const { return {}; }; - - template - Result optimize(Func&& func, - const Input &initvals, - const Bounds& bounds) { return {}; } - - // optional for randomized methods: - void seed(long /*s*/) {} -}; - namespace detail { // Helper types for NLopt algorithm selection in template contexts @@ -166,19 +43,6 @@ struct IsNLoptAlg> { template using NLoptOnly = std::enable_if_t::value, T>; -// Helper to convert C style array to std::array. The copy should be optimized -// away with modern compilers. -template auto to_arr(const T *a) -{ - std::array r; - std::copy(a, a + N, std::begin(r)); - return r; -} - -template auto to_arr(const T (&a) [N]) -{ - return to_arr(static_cast(a)); -} enum class OptDir { MIN, MAX }; // Where to optimize @@ -357,23 +221,12 @@ public: void seed(long s) { m_opt.seed(s); } }; -template Bounds bounds(const Bound (&b) [N]) { return detail::to_arr(b); } -template Input initvals(const double (&a) [N]) { return detail::to_arr(a); } -template auto score_gradient(double s, const double (&grad)[N]) -{ - return ScoreGradient(s, detail::to_arr(grad)); -} - -// Predefinded NLopt algorithms that are used in the codebase +// Predefinded NLopt algorithms using AlgNLoptGenetic = detail::NLoptAlgComb; using AlgNLoptSubplex = detail::NLoptAlg; using AlgNLoptSimplex = detail::NLoptAlg; - -// TODO: define others if needed... - -// Helper defs for pre-crafted global and local optimizers that work well. -using DefaultGlobalOptimizer = Optimizer; -using DefaultLocalOptimizer = Optimizer; +using AlgNLoptDIRECT = detail::NLoptAlg; +using AlgNLoptMLSL = detail::NLoptAlg; }} // namespace Slic3r::opt diff --git a/src/libslic3r/Optimize/Optimizer.hpp b/src/libslic3r/Optimize/Optimizer.hpp new file mode 100644 index 000000000..05191eba2 --- /dev/null +++ b/src/libslic3r/Optimize/Optimizer.hpp @@ -0,0 +1,182 @@ +#ifndef OPTIMIZER_HPP +#define OPTIMIZER_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace Slic3r { namespace opt { + +// A type to hold the complete result of the optimization. +template struct Result { + int resultcode; // Method dependent + std::array optimum; + double score; +}; + +// An interval of possible input values for optimization +class Bound { + double m_min, m_max; + +public: + Bound(double min = std::numeric_limits::min(), + double max = std::numeric_limits::max()) + : m_min(min), m_max(max) + {} + + double min() const noexcept { return m_min; } + double max() const noexcept { return m_max; } +}; + +// Helper types for optimization function input and bounds +template using Input = std::array; +template using Bounds = std::array; + +// A type for specifying the stop criteria. Setter methods can be concatenated +class StopCriteria { + + // If the absolute value difference between two scores. + double m_abs_score_diff = std::nan(""); + + // If the relative value difference between two scores. + double m_rel_score_diff = std::nan(""); + + // Stop if this value or better is found. + double m_stop_score = std::nan(""); + + // A predicate that if evaluates to true, the optimization should terminate + // and the best result found prior to termination should be returned. + std::function m_stop_condition = [] { return false; }; + + // The max allowed number of iterations. + unsigned m_max_iterations = 0; + +public: + + StopCriteria & abs_score_diff(double val) + { + m_abs_score_diff = val; return *this; + } + + double abs_score_diff() const { return m_abs_score_diff; } + + StopCriteria & rel_score_diff(double val) + { + m_rel_score_diff = val; return *this; + } + + double rel_score_diff() const { return m_rel_score_diff; } + + StopCriteria & stop_score(double val) + { + m_stop_score = val; return *this; + } + + double stop_score() const { return m_stop_score; } + + StopCriteria & max_iterations(double val) + { + m_max_iterations = val; return *this; + } + + double max_iterations() const { return m_max_iterations; } + + template StopCriteria & stop_condition(Fn &&cond) + { + m_stop_condition = cond; return *this; + } + + bool stop_condition() { return m_stop_condition(); } +}; + +// Helper class to use optimization methods involving gradient. +template struct ScoreGradient { + double score; + std::optional> gradient; + + ScoreGradient(double s, const std::array &grad) + : score{s}, gradient{grad} + {} +}; + +// Helper to be used in static_assert. +template struct always_false { enum { value = false }; }; + +// Basic interface to optimizer object +template class Optimizer { +public: + + Optimizer(const StopCriteria &) + { + static_assert (always_false::value, + "Optimizer unimplemented for given method!"); + } + + // Switch optimization towards function minimum + Optimizer &to_min() { return *this; } + + // Switch optimization towards function maximum + Optimizer &to_max() { return *this; } + + // Set criteria for successive optimizations + Optimizer &set_criteria(const StopCriteria &) { return *this; } + + // Get current criteria + StopCriteria get_criteria() const { return {}; }; + + // Find function minimum or maximum for Func which has has signature: + // double(const Input &input) and input with dimension N + // + // Initial starting point can be given as the second parameter. + // + // For each dimension an interval (Bound) has to be given marking the bounds + // for that dimension. + // + // initvals have to be within the specified bounds, otherwise its undefined + // behavior. + // + // Func can return a score of type double or optionally a ScoreGradient + // class to indicate the function gradient for a optimization methods that + // make use of the gradient. + template + Result optimize(Func&& /*func*/, + const Input &/*initvals*/, + const Bounds& /*bounds*/) { return {}; } + + // optional for randomized methods: + void seed(long /*s*/) {} +}; + +namespace detail { + +// Helper to convert C style array to std::array. The copy should be optimized +// away with modern compilers. +template auto to_arr(const T *a) +{ + std::array r; + std::copy(a, a + N, std::begin(r)); + return r; +} + +template auto to_arr(const T (&a) [N]) +{ + return to_arr(static_cast(a)); +} + +} // namespace detail + +// Helper functions to create bounds, initial value +template Bounds bounds(const Bound (&b) [N]) { return detail::to_arr(b); } +template Input initvals(const double (&a) [N]) { return detail::to_arr(a); } +template auto score_gradient(double s, const double (&grad)[N]) +{ + return ScoreGradient(s, detail::to_arr(grad)); +} + +}} // namespace Slic3r::opt + +#endif // OPTIMIZER_HPP diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index 527d82b4c..0434e3a0a 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -1,4 +1,5 @@ #include "PlaceholderParser.hpp" +#include "Exception.hpp" #include "Flow.hpp" #include #include @@ -1303,7 +1304,7 @@ static std::string process_macro(const std::string &templ, client::MyContext &co if (!context.error_message.empty()) { if (context.error_message.back() != '\n' && context.error_message.back() != '\r') context.error_message += '\n'; - throw std::runtime_error(context.error_message); + throw Slic3r::RuntimeError(context.error_message); } return output; } @@ -1319,7 +1320,7 @@ std::string PlaceholderParser::process(const std::string &templ, unsigned int cu } // Evaluate a boolean expression using the full expressive power of the PlaceholderParser boolean expression syntax. -// Throws std::runtime_error on syntax or runtime error. +// Throws Slic3r::RuntimeError on syntax or runtime error. bool PlaceholderParser::evaluate_boolean_expression(const std::string &templ, const DynamicConfig &config, const DynamicConfig *config_override) { client::MyContext context; diff --git a/src/libslic3r/PlaceholderParser.hpp b/src/libslic3r/PlaceholderParser.hpp index d744dba22..14be020ac 100644 --- a/src/libslic3r/PlaceholderParser.hpp +++ b/src/libslic3r/PlaceholderParser.hpp @@ -40,11 +40,11 @@ public: const DynamicConfig* external_config() const { return m_external_config; } // Fill in the template using a macro processing language. - // Throws std::runtime_error on syntax or runtime error. + // Throws Slic3r::RuntimeError on syntax or runtime error. std::string process(const std::string &templ, unsigned int current_extruder_id = 0, const DynamicConfig *config_override = nullptr) const; // Evaluate a boolean expression using the full expressive power of the PlaceholderParser boolean expression syntax. - // Throws std::runtime_error on syntax or runtime error. + // Throws Slic3r::RuntimeError on syntax or runtime error. static bool evaluate_boolean_expression(const std::string &templ, const DynamicConfig &config, const DynamicConfig *config_override = nullptr); // Update timestamp, year, month, day, hour, minute, second variables at the provided config. diff --git a/src/libslic3r/Point.cpp b/src/libslic3r/Point.cpp index c2417d0dc..f3ed41342 100644 --- a/src/libslic3r/Point.cpp +++ b/src/libslic3r/Point.cpp @@ -44,16 +44,6 @@ Pointf3s transform(const Pointf3s& points, const Transform3d& t) return ret_points; } -void Point::rotate(double angle) -{ - double cur_x = (double)(*this)(0); - double cur_y = (double)(*this)(1); - double s = ::sin(angle); - double c = ::cos(angle); - (*this)(0) = (coord_t)round(c * cur_x - s * cur_y); - (*this)(1) = (coord_t)round(c * cur_y + s * cur_x); -} - void Point::rotate(double angle, const Point ¢er) { double cur_x = (double)(*this)(0); diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index 30a1a4942..5082bb746 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -105,6 +105,7 @@ public: template Point(const Eigen::MatrixBase &other) : Vec2crd(other) {} static Point new_scale(coordf_t x, coordf_t y) { return Point(coord_t(scale_(x)), coord_t(scale_(y))); } + static Point new_scale(const Vec2d &v) { return Point(coord_t(scale_(v.x())), coord_t(scale_(v.y()))); } // This method allows you to assign Eigen expressions to MyVectorType template @@ -121,7 +122,14 @@ public: Point& operator*=(const double &rhs) { (*this)(0) = coord_t((*this)(0) * rhs); (*this)(1) = coord_t((*this)(1) * rhs); return *this; } Point operator*(const double &rhs) { return Point((*this)(0) * rhs, (*this)(1) * rhs); } - void rotate(double angle); + void rotate(double angle) { this->rotate(std::cos(angle), std::sin(angle)); } + void rotate(double cos_a, double sin_a) { + double cur_x = (double)(*this)(0); + double cur_y = (double)(*this)(1); + (*this)(0) = (coord_t)round(cos_a * cur_x - sin_a * cur_y); + (*this)(1) = (coord_t)round(cos_a * cur_y + sin_a * cur_x); + } + void rotate(double angle, const Point ¢er); Point rotated(double angle) const { Point res(*this); res.rotate(angle); return res; } Point rotated(double angle, const Point ¢er) const { Point res(*this); res.rotate(angle, center); return res; } diff --git a/src/libslic3r/Polygon.cpp b/src/libslic3r/Polygon.cpp index 5a6ce107b..13cdf65b4 100644 --- a/src/libslic3r/Polygon.cpp +++ b/src/libslic3r/Polygon.cpp @@ -1,5 +1,6 @@ #include "BoundingBox.hpp" #include "ClipperUtils.hpp" +#include "Exception.hpp" #include "Polygon.hpp" #include "Polyline.hpp" @@ -16,7 +17,7 @@ Polyline Polygon::split_at_vertex(const Point &point) const for (const Point &pt : this->points) if (pt == point) return this->split_at_index(int(&pt - &this->points.front())); - throw std::invalid_argument("Point not found"); + throw Slic3r::InvalidArgument("Point not found"); return Polyline(); } diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index ae3a71d41..f5aa28914 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -88,6 +88,14 @@ inline double total_length(const Polygons &polylines) { return total; } +inline double area(const Polygons &polys) +{ + double s = 0.; + for (auto &p : polys) s += p.area(); + + return s; +} + // Remove sticks (tentacles with zero area) from the polygon. extern bool remove_sticks(Polygon &poly); extern bool remove_sticks(Polygons &polys); diff --git a/src/libslic3r/Polyline.cpp b/src/libslic3r/Polyline.cpp index 26aad83d2..d24788c7b 100644 --- a/src/libslic3r/Polyline.cpp +++ b/src/libslic3r/Polyline.cpp @@ -1,5 +1,6 @@ #include "BoundingBox.hpp" #include "Polyline.hpp" +#include "Exception.hpp" #include "ExPolygon.hpp" #include "ExPolygonCollection.hpp" #include "Line.hpp" @@ -19,7 +20,7 @@ Polyline::operator Polylines() const Polyline::operator Line() const { if (this->points.size() > 2) - throw std::invalid_argument("Can't convert polyline with more than two points to a line"); + throw Slic3r::InvalidArgument("Can't convert polyline with more than two points to a line"); return Line(this->points.front(), this->points.back()); } @@ -207,7 +208,7 @@ BoundingBox get_extents(const Polylines &polylines) const Point& leftmost_point(const Polylines &polylines) { if (polylines.empty()) - throw std::invalid_argument("leftmost_point() called on empty PolylineCollection"); + throw Slic3r::InvalidArgument("leftmost_point() called on empty PolylineCollection"); Polylines::const_iterator it = polylines.begin(); const Point *p = &it->leftmost_point(); for (++ it; it != polylines.end(); ++it) { diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 7aaa96c8c..18a6e387c 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1,5 +1,6 @@ #include +#include "Exception.hpp" #include "Preset.hpp" #include "AppConfig.hpp" @@ -107,7 +108,7 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem const std::string id = path.stem().string(); if (! boost::filesystem::exists(path)) { - throw std::runtime_error((boost::format("Cannot load Vendor Config Bundle `%1%`: File not found: `%2%`.") % id % path).str()); + throw Slic3r::RuntimeError((boost::format("Cannot load Vendor Config Bundle `%1%`: File not found: `%2%`.") % id % path).str()); } VendorProfile res(id); @@ -117,7 +118,7 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem { auto res = tree.find(key); if (res == tree.not_found()) { - throw std::runtime_error((boost::format("Vendor Config Bundle `%1%` is not valid: Missing secion or key: `%2%`.") % id % key).str()); + throw Slic3r::RuntimeError((boost::format("Vendor Config Bundle `%1%` is not valid: Missing secion or key: `%2%`.") % id % key).str()); } return res; }; @@ -129,7 +130,7 @@ VendorProfile VendorProfile::from_ini(const ptree &tree, const boost::filesystem auto config_version_str = get_or_throw(vendor_section, "config_version")->second.data(); auto config_version = Semver::parse(config_version_str); if (! config_version) { - throw std::runtime_error((boost::format("Vendor Config Bundle `%1%` is not valid: Cannot parse config_version: `%2%`.") % id % config_version_str).str()); + throw Slic3r::RuntimeError((boost::format("Vendor Config Bundle `%1%` is not valid: Cannot parse config_version: `%2%`.") % id % config_version_str).str()); } else { res.config_version = std::move(*config_version); } @@ -672,9 +673,9 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri preset.file << "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed"; preset.loaded = true; } catch (const std::ifstream::failure &err) { - throw std::runtime_error(std::string("The selected preset cannot be loaded: ") + preset.file + "\n\tReason: " + err.what()); + throw Slic3r::RuntimeError(std::string("The selected preset cannot be loaded: ") + preset.file + "\n\tReason: " + err.what()); } catch (const std::runtime_error &err) { - throw std::runtime_error(std::string("Failed loading the preset file: ") + preset.file + "\n\tReason: " + err.what()); + throw Slic3r::RuntimeError(std::string("Failed loading the preset file: ") + preset.file + "\n\tReason: " + err.what()); } presets_loaded.emplace_back(preset); } catch (const std::runtime_error &err) { @@ -686,7 +687,7 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri std::sort(m_presets.begin() + m_num_default_presets, m_presets.end()); this->select_preset(first_visible_idx()); if (! errors_cummulative.empty()) - throw std::runtime_error(errors_cummulative); + throw Slic3r::RuntimeError(errors_cummulative); } // Load a preset from an already parsed config file, insert it into the sorted sequence of presets @@ -1365,9 +1366,10 @@ const std::vector& PhysicalPrinter::printer_options() "print_host", "printhost_apikey", "printhost_cafile", - "authorization_type", - "login", - "password" + "printhost_authorization_type", + // HTTP digest authentization (RFC 2617) + "printhost_user", + "printhost_password" }; } return s_opts; @@ -1412,11 +1414,11 @@ const std::set& PhysicalPrinter::get_preset_names() const bool PhysicalPrinter::has_empty_config() const { - return config.opt_string("print_host" ).empty() && - config.opt_string("printhost_apikey").empty() && - config.opt_string("printhost_cafile").empty() && - config.opt_string("login" ).empty() && - config.opt_string("password" ).empty(); + return config.opt_string("print_host" ).empty() && + config.opt_string("printhost_apikey" ).empty() && + config.opt_string("printhost_cafile" ).empty() && + config.opt_string("printhost_user" ).empty() && + config.opt_string("printhost_password").empty(); } void PhysicalPrinter::update_preset_names_in_config() @@ -1441,7 +1443,7 @@ void PhysicalPrinter::save(const std::string& file_name_from, const std::string& void PhysicalPrinter::update_from_preset(const Preset& preset) { - config.apply_only(preset.config, printer_options(), false); + config.apply_only(preset.config, printer_options(), true); // add preset names to the options list auto ret = preset_names.emplace(preset.name); update_preset_names_in_config(); @@ -1476,8 +1478,8 @@ bool PhysicalPrinter::delete_preset(const std::string& preset_name) return preset_names.erase(preset_name) > 0; } -PhysicalPrinter::PhysicalPrinter(const std::string& name, const Preset& preset) : - name(name) +PhysicalPrinter::PhysicalPrinter(const std::string& name, const DynamicPrintConfig &default_config, const Preset& preset) : + name(name), config(default_config) { update_from_preset(preset); } @@ -1514,6 +1516,13 @@ std::string PhysicalPrinter::get_preset_name(std::string name) PhysicalPrinterCollection::PhysicalPrinterCollection( const std::vector& keys) { + // Default config for a physical printer containing all key/value pairs of PhysicalPrinter::printer_options(). + for (const std::string &key : keys) { + const ConfigOptionDef *opt = print_config_def.get(key); + assert(opt); + assert(opt->default_value); + m_default_config.set_key_value(key, opt->default_value->clone()); + } } // Load all printers found in dir_path. @@ -1539,7 +1548,7 @@ void PhysicalPrinterCollection::load_printers(const std::string& dir_path, const continue; } try { - PhysicalPrinter printer(name); + PhysicalPrinter printer(name, this->default_config()); printer.file = dir_entry.path().string(); // Load the preset file, apply preset values on top of defaults. try { @@ -1549,10 +1558,10 @@ void PhysicalPrinterCollection::load_printers(const std::string& dir_path, const printer.loaded = true; } catch (const std::ifstream::failure& err) { - throw std::runtime_error(std::string("The selected preset cannot be loaded: ") + printer.file + "\n\tReason: " + err.what()); + throw Slic3r::RuntimeError(std::string("The selected preset cannot be loaded: ") + printer.file + "\n\tReason: " + err.what()); } catch (const std::runtime_error& err) { - throw std::runtime_error(std::string("Failed loading the preset file: ") + printer.file + "\n\tReason: " + err.what()); + throw Slic3r::RuntimeError(std::string("Failed loading the preset file: ") + printer.file + "\n\tReason: " + err.what()); } printers_loaded.emplace_back(printer); } @@ -1564,7 +1573,7 @@ void PhysicalPrinterCollection::load_printers(const std::string& dir_path, const m_printers.insert(m_printers.end(), std::make_move_iterator(printers_loaded.begin()), std::make_move_iterator(printers_loaded.end())); std::sort(m_printers.begin(), m_printers.end()); if (!errors_cummulative.empty()) - throw std::runtime_error(errors_cummulative); + throw Slic3r::RuntimeError(errors_cummulative); } // if there is saved user presets, contains information about "Print Host upload", @@ -1590,7 +1599,7 @@ void PhysicalPrinterCollection::load_printers_from_presets(PrinterPresetCollecti new_printer_name = (boost::format("Printer %1%") % ++cnt).str(); // create new printer from this preset - PhysicalPrinter printer(new_printer_name, preset); + PhysicalPrinter printer(new_printer_name, this->default_config(), preset); printer.loaded = true; save_printer(printer); } diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 30edfc859..ec11d59fa 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -460,8 +460,7 @@ private: // If a preset does not exist, an iterator is returned indicating where to insert a preset with the same name. std::deque::iterator find_preset_internal(const std::string &name) { - Preset key(m_type, name); - auto it = std::lower_bound(m_presets.begin() + m_num_default_presets, m_presets.end(), key); + auto it = Slic3r::lower_bound_by_predicate(m_presets.begin() + m_num_default_presets, m_presets.end(), [&name](const auto& l) { return l.name < name; }); if (it == m_presets.end() || it->name != name) { // Preset has not been not found in the sorted list of non-default presets. Try the defaults. for (size_t i = 0; i < m_num_default_presets; ++ i) @@ -539,9 +538,8 @@ namespace PresetUtils { class PhysicalPrinter { public: - PhysicalPrinter() {} - PhysicalPrinter(const std::string& name) : name(name){} - PhysicalPrinter(const std::string& name, const Preset& preset); + PhysicalPrinter(const std::string& name, const DynamicPrintConfig &default_config) : name(name), config(default_config) {} + PhysicalPrinter(const std::string& name, const DynamicPrintConfig &default_config, const Preset& preset); void set_name(const std::string &name); // Name of the Physical Printer, usually derived form the file name. @@ -698,6 +696,8 @@ public: // Generate a file path from a profile name. Add the ".ini" suffix if it is missing. std::string path_from_name(const std::string& new_name) const; + const DynamicPrintConfig& default_config() const { return m_default_config; } + private: PhysicalPrinterCollection& operator=(const PhysicalPrinterCollection& other); @@ -707,9 +707,7 @@ private: // If a preset does not exist, an iterator is returned indicating where to insert a preset with the same name. std::deque::iterator find_printer_internal(const std::string& name) { - PhysicalPrinter printer(name); - auto it = std::lower_bound(m_printers.begin(), m_printers.end(), printer); - return it; + return Slic3r::lower_bound_by_predicate(m_printers.begin(), m_printers.end(), [&name](const auto& l) { return l.name < name; }); } std::deque::const_iterator find_printer_internal(const std::string& name) const { @@ -723,6 +721,9 @@ private: // so that the addresses of the presets don't change during resizing of the container. std::deque m_printers; + // Default config for a physical printer containing all key/value pairs of PhysicalPrinter::printer_options(). + DynamicPrintConfig m_default_config; + // Selected printer. size_t m_idx_selected = size_t(-1); // The name of the preset which is currently select for this printer diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 108985704..c100e6971 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -157,7 +157,7 @@ void PresetBundle::setup_directories() subdir.make_preferred(); if (! boost::filesystem::is_directory(subdir) && ! boost::filesystem::create_directory(subdir)) - throw std::runtime_error(std::string("Slic3r was unable to create its data directory at ") + subdir.string()); + throw Slic3r::RuntimeError(std::string("Slic3r was unable to create its data directory at ") + subdir.string()); } } @@ -207,7 +207,7 @@ void PresetBundle::load_presets(AppConfig &config, const std::string &preferred_ this->update_multi_material_filament_presets(); this->update_compatible(PresetSelectCompatibleType::Never); if (! errors_cummulative.empty()) - throw std::runtime_error(errors_cummulative); + throw Slic3r::RuntimeError(errors_cummulative); this->load_selections(config, preferred_model_id); } @@ -327,6 +327,32 @@ const std::string& PresetBundle::get_preset_name_by_alias( const Preset::Type& p return presets.get_preset_name_by_alias(alias); } +void PresetBundle::save_changes_for_preset(const std::string& new_name, Preset::Type type, + const std::vector& unselected_options) +{ + PresetCollection& presets = type == Preset::TYPE_PRINT ? prints : + type == Preset::TYPE_SLA_PRINT ? sla_prints : + type == Preset::TYPE_FILAMENT ? filaments : + type == Preset::TYPE_SLA_MATERIAL ? sla_materials : printers; + + // if we want to save just some from selected options + if (!unselected_options.empty()) { + // revert unselected options to the old values + presets.get_edited_preset().config.apply_only(presets.get_selected_preset().config, unselected_options); + } + + // Save the preset into Slic3r::data_dir / presets / section_name / preset_name.ini + presets.save_current_preset(new_name); + // Mark the print & filament enabled if they are compatible with the currently selected preset. + // If saving the preset changes compatibility with other presets, keep the now incompatible dependent presets selected, however with a "red flag" icon showing that they are no more compatible. + update_compatible(PresetSelectCompatibleType::Never); + + if (type == Preset::TYPE_FILAMENT) { + // synchronize the first filament presets. + set_filament_preset(0, filaments.get_selected_preset_name()); + } +} + void PresetBundle::load_installed_filaments(AppConfig &config) { if (! config.has_section(AppConfig::SECTION_FILAMENTS)) { @@ -653,21 +679,21 @@ void PresetBundle::load_config_file(const std::string &path) boost::nowide::ifstream ifs(path); boost::property_tree::read_ini(ifs, tree); } catch (const std::ifstream::failure &err) { - throw std::runtime_error(std::string("The Config Bundle cannot be loaded: ") + path + "\n\tReason: " + err.what()); + throw Slic3r::RuntimeError(std::string("The Config Bundle cannot be loaded: ") + path + "\n\tReason: " + err.what()); } catch (const boost::property_tree::file_parser_error &err) { - throw std::runtime_error((boost::format("Failed loading the Config Bundle \"%1%\": %2% at line %3%") + throw Slic3r::RuntimeError((boost::format("Failed loading the Config Bundle \"%1%\": %2% at line %3%") % err.filename() % err.message() % err.line()).str()); } catch (const std::runtime_error &err) { - throw std::runtime_error(std::string("Failed loading the preset file: ") + path + "\n\tReason: " + err.what()); + throw Slic3r::RuntimeError(std::string("Failed loading the preset file: ") + path + "\n\tReason: " + err.what()); } // 2) Continue based on the type of the configuration file. ConfigFileType config_file_type = guess_config_file_type(tree); switch (config_file_type) { case CONFIG_FILE_TYPE_UNKNOWN: - throw std::runtime_error(std::string("Unknown configuration file type: ") + path); + throw Slic3r::RuntimeError(std::string("Unknown configuration file type: ") + path); case CONFIG_FILE_TYPE_APP_CONFIG: - throw std::runtime_error(std::string("Invalid configuration file: ") + path + ". This is an application config file."); + throw Slic3r::RuntimeError(std::string("Invalid configuration file: ") + path + ". This is an application config file."); case CONFIG_FILE_TYPE_CONFIG: { // Initialize a config from full defaults. diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index 567a12331..ff02bbeae 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -130,6 +130,10 @@ public: const std::string& get_preset_name_by_alias(const Preset::Type& preset_type, const std::string& alias) const; + // Save current preset of a required type under a new name. If the name is different from the old one, + // Unselected option would be reverted to the beginning values + void save_changes_for_preset(const std::string& new_name, Preset::Type type, const std::vector& unselected_options); + static const char *PRUSA_BUNDLE; private: std::string load_system_presets(); diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index c405c764e..a82ab3ddd 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1,5 +1,6 @@ #include "clipper/clipper_z.hpp" +#include "Exception.hpp" #include "Print.hpp" #include "BoundingBox.hpp" #include "ClipperUtils.hpp" @@ -1507,7 +1508,7 @@ BoundingBox Print::total_bounding_box() const double Print::skirt_first_layer_height() const { if (m_objects.empty()) - throw std::invalid_argument("skirt_first_layer_height() can't be called without PrintObjects"); + throw Slic3r::InvalidArgument("skirt_first_layer_height() can't be called without PrintObjects"); return m_objects.front()->config().get_abs_value("first_layer_height"); } @@ -1583,7 +1584,7 @@ void Print::auto_assign_extruders(ModelObject* model_object) const // Slicing process, running at a background thread. void Print::process() { - BOOST_LOG_TRIVIAL(info) << "Staring the slicing process." << log_memory_info(); + BOOST_LOG_TRIVIAL(info) << "Starting the slicing process." << log_memory_info(); for (PrintObject *obj : m_objects) obj->make_perimeters(); this->set_status(70, L("Infilling layers")); @@ -1603,7 +1604,7 @@ void Print::process() // Initialize the tool ordering, so it could be used by the G-code preview slider for planning tool changes and filament switches. m_tool_ordering = ToolOrdering(*this, -1, false); if (m_tool_ordering.empty() || m_tool_ordering.last_extruder() == unsigned(-1)) - throw std::runtime_error("The print is empty. The model is not printable with current print settings."); + throw Slic3r::SlicingError("The print is empty. The model is not printable with current print settings."); } this->set_done(psWipeTower); } diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index d436f90bf..a389ef00d 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -30,6 +30,12 @@ enum class SlicingMode : uint32_t; class Layer; class SupportLayer; +namespace FillAdaptive { + struct Octree; + struct OctreeDeleter; + using OctreePtr = std::unique_ptr; +}; + // Print step IDs for keeping track of the print state. enum PrintStep { psSkirt, @@ -235,6 +241,7 @@ private: void discover_horizontal_shells(); void combine_infill(); void _generate_support_material(); + std::pair prepare_adaptive_infill_data(); // XYZ in scaled coordinates Vec3crd m_size; diff --git a/src/libslic3r/PrintBase.cpp b/src/libslic3r/PrintBase.cpp index ab6ca3d35..7cdf6448c 100644 --- a/src/libslic3r/PrintBase.cpp +++ b/src/libslic3r/PrintBase.cpp @@ -1,3 +1,4 @@ +#include "Exception.hpp" #include "PrintBase.hpp" #include @@ -68,7 +69,7 @@ std::string PrintBase::output_filename(const std::string &format, const std::str filename = boost::filesystem::change_extension(filename, default_ext); return filename.string(); } catch (std::runtime_error &err) { - throw std::runtime_error(L("Failed processing of the output_filename_format template.") + "\n" + err.what()); + throw Slic3r::RuntimeError(L("Failed processing of the output_filename_format template.") + "\n" + err.what()); } } diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 770983ad5..72393a3f5 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -133,13 +133,13 @@ void PrintConfigDef::init_common_params() // Options used by physical printers - def = this->add("login", coString); - def->label = L("Login"); + def = this->add("printhost_user", coString); + def->label = L("User"); // def->tooltip = L(""); def->mode = comAdvanced; def->set_default_value(new ConfigOptionString("")); - def = this->add("password", coString); + def = this->add("printhost_password", coString); def->label = L("Password"); // def->tooltip = L(""); def->mode = comAdvanced; @@ -151,7 +151,7 @@ void PrintConfigDef::init_common_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionString("")); - def = this->add("authorization_type", coEnum); + def = this->add("printhost_authorization_type", coEnum); def->label = L("Authorization Type"); // def->tooltip = L(""); def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); @@ -881,6 +881,8 @@ void PrintConfigDef::init_fff_params() def->enum_values.push_back("hilbertcurve"); def->enum_values.push_back("archimedeanchords"); def->enum_values.push_back("octagramspiral"); + def->enum_values.push_back("adaptivecubic"); + def->enum_values.push_back("supportcubic"); def->enum_labels.push_back(L("Rectilinear")); def->enum_labels.push_back(L("Grid")); def->enum_labels.push_back(L("Triangles")); @@ -894,6 +896,8 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back(L("Hilbert Curve")); def->enum_labels.push_back(L("Archimedean Chords")); def->enum_labels.push_back(L("Octagram Spiral")); + def->enum_labels.push_back(L("Adaptive Cubic")); + def->enum_labels.push_back(L("Support Cubic")); def->set_default_value(new ConfigOptionEnum(ipStars)); def = this->add("first_layer_acceleration", coFloat); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index b133a2e4e..fa7edd10e 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -39,7 +39,7 @@ enum AuthorizationType { enum InfillPattern : int { ipRectilinear, ipMonotonous, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb, - ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipCount, + ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipAdaptiveCubic, ipSupportCubic, ipCount, }; enum class IroningType { @@ -139,6 +139,8 @@ template<> inline const t_config_enum_values& ConfigOptionEnum::g keys_map["hilbertcurve"] = ipHilbertCurve; keys_map["archimedeanchords"] = ipArchimedeanChords; keys_map["octagramspiral"] = ipOctagramSpiral; + keys_map["adaptivecubic"] = ipAdaptiveCubic; + keys_map["supportcubic"] = ipSupportCubic; } return keys_map; } diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index aecf90771..ac47e1d10 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1,3 +1,4 @@ +#include "Exception.hpp" #include "Print.hpp" #include "BoundingBox.hpp" #include "ClipperUtils.hpp" @@ -9,6 +10,9 @@ #include "Surface.hpp" #include "Slicing.hpp" #include "Utils.hpp" +#include "AABBTreeIndirect.hpp" +#include "Fill/FillAdaptive.hpp" +#include "Format/STL.hpp" #include #include @@ -135,7 +139,7 @@ void PrintObject::slice() } }); if (m_layers.empty()) - throw std::runtime_error("No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n"); + throw Slic3r::SlicingError("No layers were detected. You might want to repair your STL file(s) or check their size or thickness and retry.\n"); this->set_done(posSlice); } @@ -369,13 +373,15 @@ void PrintObject::infill() this->prepare_infill(); if (this->set_started(posInfill)) { + auto [adaptive_fill_octree, support_fill_octree] = this->prepare_adaptive_infill_data(); + BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - start"; tbb::parallel_for( tbb::blocked_range(0, m_layers.size()), - [this](const tbb::blocked_range& range) { + [this, &adaptive_fill_octree = adaptive_fill_octree, &support_fill_octree = support_fill_octree](const tbb::blocked_range& range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { m_print->throw_if_canceled(); - m_layers[layer_idx]->make_fills(); + m_layers[layer_idx]->make_fills(adaptive_fill_octree.get(), support_fill_octree.get()); } } ); @@ -421,13 +427,31 @@ void PrintObject::generate_support_material() // therefore they cannot be printed without supports. for (const Layer *layer : m_layers) if (layer->empty()) - throw std::runtime_error("Levitating objects cannot be printed without supports."); + throw Slic3r::SlicingError("Levitating objects cannot be printed without supports."); #endif } this->set_done(posSupportMaterial); } } +std::pair PrintObject::prepare_adaptive_infill_data() +{ + using namespace FillAdaptive; + + auto [adaptive_line_spacing, support_line_spacing] = adaptive_fill_line_spacing(*this); + if (adaptive_line_spacing == 0. && support_line_spacing == 0.) + return std::make_pair(OctreePtr(), OctreePtr()); + + indexed_triangle_set mesh = this->model_object()->raw_indexed_triangle_set(); + // Rotate mesh and build octree on it with axis-aligned (standart base) cubes. + Transform3d m = m_trafo; + m.translate(Vec3d(- unscale(m_center_offset.x()), - unscale(m_center_offset.y()), 0)); + its_transform(mesh, transform_to_octree().toRotationMatrix() * m, true); + return std::make_pair( + adaptive_line_spacing ? build_octree(mesh, adaptive_line_spacing, false) : OctreePtr(), + support_line_spacing ? build_octree(mesh, support_line_spacing, true) : OctreePtr()); +} + void PrintObject::clear_layers() { for (Layer *l : m_layers) diff --git a/src/libslic3r/PrintRegion.cpp b/src/libslic3r/PrintRegion.cpp index b3ac6a4a5..2a75cd621 100644 --- a/src/libslic3r/PrintRegion.cpp +++ b/src/libslic3r/PrintRegion.cpp @@ -1,3 +1,4 @@ +#include "Exception.hpp" #include "Print.hpp" namespace Slic3r { @@ -13,7 +14,7 @@ unsigned int PrintRegion::extruder(FlowRole role) const else if (role == frSolidInfill || role == frTopSolidInfill) extruder = m_config.solid_infill_extruder; else - throw std::invalid_argument("Unknown role"); + throw Slic3r::InvalidArgument("Unknown role"); return extruder; } @@ -40,7 +41,7 @@ Flow PrintRegion::flow(FlowRole role, double layer_height, bool bridge, bool fir } else if (role == frTopSolidInfill) { config_width = m_config.top_infill_extrusion_width; } else { - throw std::invalid_argument("Unknown role"); + throw Slic3r::InvalidArgument("Unknown role"); } } diff --git a/src/libslic3r/SLA/Concurrency.hpp b/src/libslic3r/SLA/Concurrency.hpp index 93ba8c4eb..300024c76 100644 --- a/src/libslic3r/SLA/Concurrency.hpp +++ b/src/libslic3r/SLA/Concurrency.hpp @@ -4,7 +4,11 @@ #include #include #include +#include + #include +#include + #include namespace Slic3r { @@ -21,28 +25,56 @@ template<> struct _ccr using SpinningMutex = tbb::spin_mutex; using BlockingMutex = tbb::mutex; + template + static IteratorOnly loop_(const tbb::blocked_range &range, Fn &&fn) + { + for (auto &el : range) fn(el); + } + + template + static IntegerOnly loop_(const tbb::blocked_range &range, Fn &&fn) + { + for (I i = range.begin(); i < range.end(); ++i) fn(i); + } + template - static IteratorOnly for_each(It from, - It to, - Fn && fn, - size_t granularity = 1) + static void for_each(It from, It to, Fn &&fn, size_t granularity = 1) { tbb::parallel_for(tbb::blocked_range{from, to, granularity}, [&fn, from](const auto &range) { - for (auto &el : range) fn(el); + loop_(range, std::forward(fn)); }); } - template - static IntegerOnly for_each(I from, - I to, - Fn && fn, - size_t granularity = 1) + template + static T reduce(I from, + I to, + const T &init, + MergeFn &&mergefn, + AccessFn &&access, + size_t granularity = 1 + ) { - tbb::parallel_for(tbb::blocked_range{from, to, granularity}, - [&fn](const auto &range) { - for (I i = range.begin(); i < range.end(); ++i) fn(i); - }); + return tbb::parallel_reduce( + tbb::blocked_range{from, to, granularity}, init, + [&](const auto &range, T subinit) { + T acc = subinit; + loop_(range, [&](auto &i) { acc = mergefn(acc, access(i)); }); + return acc; + }, + std::forward(mergefn)); + } + + template + static IteratorOnly reduce(I from, + I to, + const T & init, + MergeFn &&mergefn, + size_t granularity = 1) + { + return reduce( + from, to, init, std::forward(mergefn), + [](typename I::value_type &i) { return i; }, granularity); } }; @@ -55,23 +87,52 @@ public: using SpinningMutex = _Mtx; using BlockingMutex = _Mtx; - template - static IteratorOnly for_each(It from, - It to, - Fn &&fn, - size_t /* ignore granularity */ = 1) + template + static IteratorOnly loop_(It from, It to, Fn &&fn) { for (auto it = from; it != to; ++it) fn(*it); } - template - static IntegerOnly for_each(I from, - I to, - Fn &&fn, - size_t /* ignore granularity */ = 1) + template + static IntegerOnly loop_(I from, I to, Fn &&fn) { for (I i = from; i < to; ++i) fn(i); } + + template + static void for_each(It from, + It to, + Fn &&fn, + size_t /* ignore granularity */ = 1) + { + loop_(from, to, std::forward(fn)); + } + + template + static T reduce(I from, + I to, + const T & init, + MergeFn &&mergefn, + AccessFn &&access, + size_t /*granularity*/ = 1 + ) + { + T acc = init; + loop_(from, to, [&](auto &i) { acc = mergefn(acc, access(i)); }); + return acc; + } + + template + static IteratorOnly reduce(I from, + I to, + const T &init, + MergeFn &&mergefn, + size_t /*granularity*/ = 1 + ) + { + return reduce(from, to, init, std::forward(mergefn), + [](typename I::value_type &i) { return i; }); + } }; using ccr = _ccr; diff --git a/src/libslic3r/SLA/Rotfinder.cpp b/src/libslic3r/SLA/Rotfinder.cpp index 81ef00e6b..937897766 100644 --- a/src/libslic3r/SLA/Rotfinder.cpp +++ b/src/libslic3r/SLA/Rotfinder.cpp @@ -1,35 +1,259 @@ #include -#include -#include #include -#include +#include + +#include + +#include "libslic3r/SLAPrint.hpp" +#include "libslic3r/PrintConfig.hpp" + +#include #include "Model.hpp" -namespace Slic3r { -namespace sla { +#include -std::array find_best_rotation(const ModelObject& modelobj, - float accuracy, - std::function statuscb, - std::function stopcond) +namespace Slic3r { namespace sla { + +inline bool is_on_floor(const SLAPrintObject &mo) { - using libnest2d::opt::Method; - using libnest2d::opt::bound; - using libnest2d::opt::Optimizer; - using libnest2d::opt::TOptimizer; - using libnest2d::opt::StopCriteria; + auto opt_elevation = mo.config().support_object_elevation.getFloat(); + auto opt_padaround = mo.config().pad_around_object.getBool(); - static const unsigned MAX_TRIES = 100000; + return opt_elevation < EPSILON || opt_padaround; +} + +// Find transformed mesh ground level without copy and with parallel reduce. +double find_ground_level(const TriangleMesh &mesh, + const Transform3d & tr, + size_t threads) +{ + size_t vsize = mesh.its.vertices.size(); + + auto minfn = [](double a, double b) { return std::min(a, b); }; + + auto accessfn = [&mesh, &tr] (size_t vi) { + return (tr * mesh.its.vertices[vi].template cast()).z(); + }; + + double zmin = std::numeric_limits::max(); + size_t granularity = vsize / threads; + return ccr_par::reduce(size_t(0), vsize, zmin, minfn, accessfn, granularity); +} + +// Get the vertices of a triangle directly in an array of 3 points +std::array get_triangle_vertices(const TriangleMesh &mesh, + size_t faceidx) +{ + const auto &face = mesh.its.indices[faceidx]; + return {Vec3d{mesh.its.vertices[face(0)].cast()}, + Vec3d{mesh.its.vertices[face(1)].cast()}, + Vec3d{mesh.its.vertices[face(2)].cast()}}; +} + +std::array get_transformed_triangle(const TriangleMesh &mesh, + const Transform3d & tr, + size_t faceidx) +{ + const auto &tri = get_triangle_vertices(mesh, faceidx); + return {tr * tri[0], tr * tri[1], tr * tri[2]}; +} + +// Get area and normal of a triangle +struct Facestats { + Vec3d normal; + double area; + + explicit Facestats(const std::array &triangle) + { + Vec3d U = triangle[1] - triangle[0]; + Vec3d V = triangle[2] - triangle[0]; + Vec3d C = U.cross(V); + normal = C.normalized(); + area = 0.5 * C.norm(); + } +}; + +inline const Vec3d DOWN = {0., 0., -1.}; +constexpr double POINTS_PER_UNIT_AREA = 1.; + +// The score function for a particular face +inline double get_score(const Facestats &fc) +{ + // Simply get the angle (acos of dot product) between the face normal and + // the DOWN vector. + double phi = 1. - std::acos(fc.normal.dot(DOWN)) / PI; + + // Only consider faces that have have slopes below 90 deg: + phi = phi * (phi > 0.5); + + // Make the huge slopes more significant than the smaller slopes + phi = phi * phi * phi; + + // Multiply with the area of the current face + return fc.area * POINTS_PER_UNIT_AREA * phi; +} + +template +double sum_score(AccessFn &&accessfn, size_t facecount, size_t Nthreads) +{ + double initv = 0.; + auto mergefn = std::plus{}; + size_t grainsize = facecount / Nthreads; + size_t from = 0, to = facecount; + + return ccr_par::reduce(from, to, initv, mergefn, accessfn, grainsize); +} + +// Try to guess the number of support points needed to support a mesh +double get_model_supportedness(const TriangleMesh &mesh, const Transform3d &tr) +{ + if (mesh.its.vertices.empty()) return std::nan(""); + + auto accessfn = [&mesh, &tr](size_t fi) { + Facestats fc{get_transformed_triangle(mesh, tr, fi)}; + return get_score(fc); + }; + + size_t facecount = mesh.its.indices.size(); + size_t Nthreads = std::thread::hardware_concurrency(); + return sum_score(accessfn, facecount, Nthreads) / facecount; +} + +double get_model_supportedness_onfloor(const TriangleMesh &mesh, + const Transform3d & tr) +{ + if (mesh.its.vertices.empty()) return std::nan(""); + + size_t Nthreads = std::thread::hardware_concurrency(); + + double zmin = find_ground_level(mesh, tr, Nthreads); + double zlvl = zmin + 0.1; // Set up a slight tolerance from z level + + auto accessfn = [&mesh, &tr, zlvl](size_t fi) { + std::array tri = get_transformed_triangle(mesh, tr, fi); + Facestats fc{tri}; + + if (tri[0].z() <= zlvl && tri[1].z() <= zlvl && tri[2].z() <= zlvl) + return -fc.area * POINTS_PER_UNIT_AREA; + + return get_score(fc); + }; + + size_t facecount = mesh.its.indices.size(); + return sum_score(accessfn, facecount, Nthreads) / facecount; +} + +using XYRotation = std::array; + +// prepare the rotation transformation +Transform3d to_transform3d(const XYRotation &rot) +{ + Transform3d rt = Transform3d::Identity(); + rt.rotate(Eigen::AngleAxisd(rot[1], Vec3d::UnitY())); + rt.rotate(Eigen::AngleAxisd(rot[0], Vec3d::UnitX())); + return rt; +} + +XYRotation from_transform3d(const Transform3d &tr) +{ + Vec3d rot3d = Geometry::Transformation {tr}.get_rotation(); + return {rot3d.x(), rot3d.y()}; +} + +// Find the best score from a set of function inputs. Evaluate for every point. +template +std::array find_min_score(Fn &&fn, It from, It to, StopCond &&stopfn) +{ + std::array ret; + + double score = std::numeric_limits::max(); + + size_t Nthreads = std::thread::hardware_concurrency(); + size_t dist = std::distance(from, to); + std::vector scores(dist, score); + + ccr_par::for_each(size_t(0), dist, [&stopfn, &scores, &fn, &from](size_t i) { + if (stopfn()) return; + + scores[i] = fn(*(from + i)); + }, dist / Nthreads); + + auto it = std::min_element(scores.begin(), scores.end()); + + if (it != scores.end()) ret = *(from + std::distance(scores.begin(), it)); + + return ret; +} + +// collect the rotations for each face of the convex hull +std::vector get_chull_rotations(const TriangleMesh &mesh, size_t max_count) +{ + TriangleMesh chull = mesh.convex_hull_3d(); + chull.require_shared_vertices(); + double chull2d_area = chull.convex_hull().area(); + double area_threshold = chull2d_area / (scaled(1e3) * scaled(1.)); + + size_t facecount = chull.its.indices.size(); + + struct RotArea { XYRotation rot; double area; }; + + auto inputs = reserve_vector(facecount); + + auto rotcmp = [](const RotArea &r1, const RotArea &r2) { + double xdiff = r1.rot[X] - r2.rot[X], ydiff = r1.rot[Y] - r2.rot[Y]; + return std::abs(xdiff) < EPSILON ? ydiff < 0. : xdiff < 0.; + }; + + auto eqcmp = [](const XYRotation &r1, const XYRotation &r2) { + double xdiff = r1[X] - r2[X], ydiff = r1[Y] - r2[Y]; + return std::abs(xdiff) < EPSILON && std::abs(ydiff) < EPSILON; + }; + + for (size_t fi = 0; fi < facecount; ++fi) { + Facestats fc{get_triangle_vertices(chull, fi)}; + + if (fc.area > area_threshold) { + auto q = Eigen::Quaterniond{}.FromTwoVectors(fc.normal, DOWN); + XYRotation rot = from_transform3d(Transform3d::Identity() * q); + RotArea ra = {rot, fc.area}; + + auto it = std::lower_bound(inputs.begin(), inputs.end(), ra, rotcmp); + + if (it == inputs.end() || !eqcmp(it->rot, rot)) + inputs.insert(it, ra); + } + } + + inputs.shrink_to_fit(); + if (!max_count) max_count = inputs.size(); + std::sort(inputs.begin(), inputs.end(), + [](const RotArea &ra, const RotArea &rb) { + return ra.area > rb.area; + }); + + auto ret = reserve_vector(std::min(max_count, inputs.size())); + for (const RotArea &ra : inputs) ret.emplace_back(ra.rot); + + return ret; +} + +Vec2d find_best_rotation(const SLAPrintObject & po, + float accuracy, + std::function statuscb, + std::function stopcond) +{ + static const unsigned MAX_TRIES = 1000; // return value - std::array rot; + XYRotation rot; // We will use only one instance of this converted mesh to examine different // rotations - const TriangleMesh& mesh = modelobj.raw_mesh(); + TriangleMesh mesh = po.model_object()->raw_mesh(); + mesh.require_shared_vertices(); - // For current iteration number + // To keep track of the number of iterations unsigned status = 0; // The maximum number of iterations @@ -38,77 +262,61 @@ std::array find_best_rotation(const ModelObject& modelobj, // call status callback with zero, because we are at the start statuscb(status); - // So this is the object function which is called by the solver many times - // It has to yield a single value representing the current score. We will - // call the status callback in each iteration but the actual value may be - // the same for subsequent iterations (status goes from 0 to 100 but - // iterations can be many more) - auto objfunc = [&mesh, &status, &statuscb, &stopcond, max_tries] - (double rx, double ry, double rz) - { - const TriangleMesh& m = mesh; - - // prepare the rotation transformation - Transform3d rt = Transform3d::Identity(); - - rt.rotate(Eigen::AngleAxisd(rz, Vec3d::UnitZ())); - rt.rotate(Eigen::AngleAxisd(ry, Vec3d::UnitY())); - rt.rotate(Eigen::AngleAxisd(rx, Vec3d::UnitX())); - - double score = 0; - - // For all triangles we calculate the normal and sum up the dot product - // (a scalar indicating how much are two vectors aligned) with each axis - // this will result in a value that is greater if a normal is aligned - // with all axes. If the normal is aligned than the triangle itself is - // orthogonal to the axes and that is good for print quality. - - // TODO: some applications optimize for minimum z-axis cross section - // area. The current function is only an example of how to optimize. - - // Later we can add more criteria like the number of overhangs, etc... - for(size_t i = 0; i < m.stl.facet_start.size(); i++) { - Vec3d n = m.stl.facet_start[i].normal.cast(); - - // rotate the normal with the current rotation given by the solver - n = rt * n; - - // We should score against the alignment with the reference planes - score += std::abs(n.dot(Vec3d::UnitX())); - score += std::abs(n.dot(Vec3d::UnitY())); - score += std::abs(n.dot(Vec3d::UnitZ())); - } - + auto statusfn = [&statuscb, &status, &max_tries] { // report status - if(!stopcond()) statuscb( unsigned(++status * 100.0/max_tries) ); - - return score; + statuscb(unsigned(++status * 100.0/max_tries) ); }; - // Firing up the genetic optimizer. For now it uses the nlopt library. - StopCriteria stc; - stc.max_iterations = max_tries; - stc.relative_score_difference = 1e-3; - stc.stop_condition = stopcond; // stop when stopcond returns true - TOptimizer solver(stc); + // Different search methods have to be used depending on the model elevation + if (is_on_floor(po)) { - // We are searching rotations around the three axes x, y, z. Thus the - // problem becomes a 3 dimensional optimization task. - // We can specify the bounds for a dimension in the following way: - auto b = bound(-PI/2, PI/2); + std::vector inputs = get_chull_rotations(mesh, max_tries); + max_tries = inputs.size(); - // Now we start the optimization process with initial angles (0, 0, 0) - auto result = solver.optimize_max(objfunc, - libnest2d::opt::initvals(0.0, 0.0, 0.0), - b, b, b); + // If the model can be placed on the bed directly, we only need to + // check the 3D convex hull face rotations. - // Save the result and fck off - rot[0] = std::get<0>(result.optimum); - rot[1] = std::get<1>(result.optimum); - rot[2] = std::get<2>(result.optimum); + auto objfn = [&mesh, &statusfn](const XYRotation &rot) { + statusfn(); + Transform3d tr = to_transform3d(rot); + return get_model_supportedness_onfloor(mesh, tr); + }; - return rot; + rot = find_min_score<2>(objfn, inputs.begin(), inputs.end(), stopcond); + } else { + // Preparing the optimizer. + size_t gridsize = std::sqrt(max_tries); // 2D grid has gridsize^2 calls + opt::Optimizer solver(opt::StopCriteria{} + .max_iterations(max_tries) + .stop_condition(stopcond), + gridsize); + + // We are searching rotations around only two axes x, y. Thus the + // problem becomes a 2 dimensional optimization task. + // We can specify the bounds for a dimension in the following way: + auto bounds = opt::bounds({ {-PI, PI}, {-PI, PI} }); + + auto result = solver.to_min().optimize( + [&mesh, &statusfn] (const XYRotation &rot) + { + statusfn(); + return get_model_supportedness(mesh, to_transform3d(rot)); + }, opt::initvals({0., 0.}), bounds); + + // Save the result and fck off + rot = result.optimum; + } + + return {rot[0], rot[1]}; } +double get_model_supportedness(const SLAPrintObject &po, const Transform3d &tr) +{ + TriangleMesh mesh = po.model_object()->raw_mesh(); + mesh.require_shared_vertices(); + + return is_on_floor(po) ? get_model_supportedness_onfloor(mesh, tr) : + get_model_supportedness(mesh, tr); } -} + +}} // namespace Slic3r::sla diff --git a/src/libslic3r/SLA/Rotfinder.hpp b/src/libslic3r/SLA/Rotfinder.hpp index 4469f9731..96561a890 100644 --- a/src/libslic3r/SLA/Rotfinder.hpp +++ b/src/libslic3r/SLA/Rotfinder.hpp @@ -4,9 +4,11 @@ #include #include +#include + namespace Slic3r { -class ModelObject; +class SLAPrintObject; namespace sla { @@ -25,14 +27,17 @@ namespace sla { * * @return Returns the rotations around each axis (x, y, z) */ -std::array find_best_rotation( - const ModelObject& modelobj, +Vec2d find_best_rotation( + const SLAPrintObject& modelobj, float accuracy = 1.0f, std::function statuscb = [] (unsigned) {}, std::function stopcond = [] () { return false; } ); -} -} +double get_model_supportedness(const SLAPrintObject &mesh, + const Transform3d & tr); + +} // namespace sla +} // namespace Slic3r #endif // SLAROTFINDER_HPP diff --git a/src/libslic3r/SLA/SupportPointGenerator.cpp b/src/libslic3r/SLA/SupportPointGenerator.cpp index 449269445..7e884b6e3 100644 --- a/src/libslic3r/SLA/SupportPointGenerator.cpp +++ b/src/libslic3r/SLA/SupportPointGenerator.cpp @@ -163,10 +163,10 @@ static std::vector make_layers( SupportPointGenerator::MyLayer &layer_below = layers[layer_id - 1]; //FIXME WTF? const float layer_height = (layer_id!=0 ? heights[layer_id]-heights[layer_id-1] : heights[0]); - const float safe_angle = 5.f * (float(M_PI)/180.f); // smaller number - less supports - const float between_layers_offset = float(scale_(layer_height / std::tan(safe_angle))); + const float safe_angle = 35.f * (float(M_PI)/180.f); // smaller number - less supports + const float between_layers_offset = scaled(layer_height * std::tan(safe_angle)); const float slope_angle = 75.f * (float(M_PI)/180.f); // smaller number - less supports - const float slope_offset = float(scale_(layer_height / std::tan(slope_angle))); + const float slope_offset = scaled(layer_height * std::tan(slope_angle)); //FIXME This has a quadratic time complexity, it will be excessively slow for many tiny islands. for (SupportPointGenerator::Structure &top : layer_above.islands) { for (SupportPointGenerator::Structure &bottom : layer_below.islands) { @@ -181,6 +181,25 @@ static std::vector make_layers( Polygons bottom_polygons = top.polygons_below(); top.overhangs = diff_ex(top_polygons, bottom_polygons); if (! top.overhangs.empty()) { + + // Produce 2 bands around the island, a safe band for dangling overhangs + // and an unsafe band for sloped overhangs. + // These masks include the original island + auto dangl_mask = offset(bottom_polygons, between_layers_offset, ClipperLib::jtSquare); + auto overh_mask = offset(bottom_polygons, slope_offset, ClipperLib::jtSquare); + + // Absolutely hopeless overhangs are those outside the unsafe band + top.overhangs = diff_ex(top_polygons, overh_mask); + + // Now cut out the supported core from the safe band + // and cut the safe band from the unsafe band to get distinct + // zones. + overh_mask = diff(overh_mask, dangl_mask); + dangl_mask = diff(dangl_mask, bottom_polygons); + + top.dangling_areas = intersection_ex(top_polygons, dangl_mask); + top.overhangs_slopes = intersection_ex(top_polygons, overh_mask); + top.overhangs_area = 0.f; std::vector> expolys_with_areas; for (ExPolygon &ex : top.overhangs) { @@ -196,8 +215,6 @@ static std::vector make_layers( overhangs_sorted.emplace_back(std::move(*p.first)); top.overhangs = std::move(overhangs_sorted); top.overhangs_area *= float(SCALING_FACTOR * SCALING_FACTOR); - top.overhangs_slopes = diff_ex(top_polygons, offset(bottom_polygons, slope_offset)); - top.dangling_areas = diff_ex(top_polygons, offset(bottom_polygons, between_layers_offset)); } } } @@ -256,21 +273,9 @@ void SupportPointGenerator::process(const std::vector& slices, const // Now iterate over all polygons and append new points if needed. for (Structure &s : layer_top->islands) { // Penalization resulting from large diff from the last layer: -// s.supports_force_inherited /= std::max(1.f, (layer_height / 0.3f) * e_area / s.area); s.supports_force_inherited /= std::max(1.f, 0.17f * (s.overhangs_area) / s.area); - //float force_deficit = s.support_force_deficit(m_config.tear_pressure()); - if (s.islands_below.empty()) { // completely new island - needs support no doubt - uniformly_cover({ *s.polygon }, s, point_grid, true); - } else if (! s.dangling_areas.empty()) { - // Let's see if there's anything that overlaps enough to need supports: - // What we now have in polygons needs support, regardless of what the forces are, so we can add them. - //FIXME is it an island point or not? Vojtech thinks it is. - uniformly_cover(s.dangling_areas, s, point_grid); - } else if (! s.overhangs_slopes.empty()) { - //FIXME add the support force deficit as a parameter, only cover until the defficiency is covered. - uniformly_cover(s.overhangs_slopes, s, point_grid); - } + add_support_points(s, point_grid); } m_throw_on_cancel(); @@ -288,6 +293,42 @@ void SupportPointGenerator::process(const std::vector& slices, const } } +void SupportPointGenerator::add_support_points(SupportPointGenerator::Structure &s, SupportPointGenerator::PointGrid3D &grid3d) +{ + // Select each type of surface (overrhang, dangling, slope), derive the support + // force deficit for it and call uniformly conver with the right params + + float tp = m_config.tear_pressure(); + float current = s.supports_force_total(); + static constexpr float SLOPE_DAMPING = .0015f; + static constexpr float DANGL_DAMPING = .09f; + + if (s.islands_below.empty()) { + // completely new island - needs support no doubt + // deficit is full, there is nothing below that would hold this island + uniformly_cover({ *s.polygon }, s, s.area * tp, grid3d, IslandCoverageFlags(icfIsNew | icfBoundaryOnly) ); + return; + } + + auto areafn = [](double sum, auto &p) { return sum + p.area() * SCALING_FACTOR * SCALING_FACTOR; }; + if (! s.dangling_areas.empty()) { + // Let's see if there's anything that overlaps enough to need supports: + // What we now have in polygons needs support, regardless of what the forces are, so we can add them. + + double a = std::accumulate(s.dangling_areas.begin(), s.dangling_areas.end(), 0., areafn); + uniformly_cover(s.dangling_areas, s, a * tp - current * DANGL_DAMPING * std::sqrt(1. - a / s.area), grid3d); + } + + if (! s.overhangs_slopes.empty()) { + double a = std::accumulate(s.overhangs_slopes.begin(), s.overhangs_slopes.end(), 0., areafn); + uniformly_cover(s.overhangs_slopes, s, a * tp - current * SLOPE_DAMPING * std::sqrt(1. - a / s.area), grid3d); + } + + if (! s.overhangs.empty()) { + uniformly_cover(s.overhangs, s, s.overhangs_area * tp, grid3d); + } +} + std::vector sample_expolygon(const ExPolygon &expoly, float samples_per_mm2, std::mt19937 &rng) { // Triangulate the polygon with holes into triplets of 3D points. @@ -297,16 +338,16 @@ std::vector sample_expolygon(const ExPolygon &expoly, float samples_per_m if (! triangles.empty()) { // Calculate area of each triangle. - std::vector areas; - areas.reserve(triangles.size() / 3); + auto areas = reserve_vector(triangles.size() / 3); + double aback = 0.; for (size_t i = 0; i < triangles.size(); ) { const Vec2f &a = triangles[i ++]; const Vec2f v1 = triangles[i ++] - a; const Vec2f v2 = triangles[i ++] - a; - areas.emplace_back(0.5f * std::abs(cross2(v1, v2))); - if (i != 3) - // Prefix sum of the areas. - areas.back() += areas[areas.size() - 2]; + + // Prefix sum of the areas. + areas.emplace_back(aback + 0.5f * std::abs(cross2(v1, v2))); + aback = areas.back(); } size_t num_samples = size_t(ceil(areas.back() * samples_per_mm2)); @@ -316,7 +357,7 @@ std::vector sample_expolygon(const ExPolygon &expoly, float samples_per_m double r = random_triangle(rng); size_t idx_triangle = std::min(std::upper_bound(areas.begin(), areas.end(), (float)r) - areas.begin(), areas.size() - 1) * 3; // Select a random point on the triangle. - double u = float(sqrt(random_float(rng))); + double u = float(std::sqrt(random_float(rng))); double v = float(random_float(rng)); const Vec2f &a = triangles[idx_triangle ++]; const Vec2f &b = triangles[idx_triangle++]; @@ -328,16 +369,37 @@ std::vector sample_expolygon(const ExPolygon &expoly, float samples_per_m return out; } + +std::vector sample_expolygon(const ExPolygons &expolys, float samples_per_mm2, std::mt19937 &rng) +{ + std::vector out; + for (const ExPolygon &expoly : expolys) + append(out, sample_expolygon(expoly, samples_per_mm2, rng)); + + return out; +} + +void sample_expolygon_boundary(const ExPolygon & expoly, + float samples_per_mm, + std::vector &out, + std::mt19937 & rng) +{ + double point_stepping_scaled = scale_(1.f) / samples_per_mm; + for (size_t i_contour = 0; i_contour <= expoly.holes.size(); ++ i_contour) { + const Polygon &contour = (i_contour == 0) ? expoly.contour : + expoly.holes[i_contour - 1]; + + const Points pts = contour.equally_spaced_points(point_stepping_scaled); + for (size_t i = 0; i < pts.size(); ++ i) + out.emplace_back(unscale(pts[i].x()), + unscale(pts[i].y())); + } +} + std::vector sample_expolygon_with_boundary(const ExPolygon &expoly, float samples_per_mm2, float samples_per_mm_boundary, std::mt19937 &rng) { std::vector out = sample_expolygon(expoly, samples_per_mm2, rng); - double point_stepping_scaled = scale_(1.f) / samples_per_mm_boundary; - for (size_t i_contour = 0; i_contour <= expoly.holes.size(); ++ i_contour) { - const Polygon &contour = (i_contour == 0) ? expoly.contour : expoly.holes[i_contour - 1]; - const Points pts = contour.equally_spaced_points(point_stepping_scaled); - for (size_t i = 0; i < pts.size(); ++ i) - out.emplace_back(unscale(pts[i].x()), unscale(pts[i].y())); - } + sample_expolygon_boundary(expoly, samples_per_mm_boundary, out, rng); return out; } @@ -359,17 +421,17 @@ static inline std::vector poisson_disk_from_samples(const std::vector raw_samples_sorted; - RawSample sample; - for (const Vec2f &pt : raw_samples) { - sample.coord = pt; - sample.cell_id = ((pt - corner_min) / radius).cast(); - raw_samples_sorted.emplace_back(sample); - } + + auto raw_samples_sorted = reserve_vector(raw_samples.size()); + for (const Vec2f &pt : raw_samples) + raw_samples_sorted.emplace_back(pt, ((pt - corner_min) / radius).cast()); + std::sort(raw_samples_sorted.begin(), raw_samples_sorted.end(), [](const RawSample &lhs, const RawSample &rhs) { return lhs.cell_id.x() < rhs.cell_id.x() || (lhs.cell_id.x() == rhs.cell_id.x() && lhs.cell_id.y() < rhs.cell_id.y()); }); @@ -464,11 +526,22 @@ static inline std::vector poisson_disk_from_samples(const std::vector bbdim.y()) std::swap(bbdim.x(), bbdim.y()); + double aspectr = bbdim.y() / bbdim.x(); + + support_force_deficit *= (1 + aspectr / 2.); + } + if (support_force_deficit < 0) return; @@ -485,13 +558,18 @@ void SupportPointGenerator::uniformly_cover(const ExPolygons& islands, Structure float min_spacing = poisson_radius; //FIXME share the random generator. The random generator may be not so cheap to initialize, also we don't want the random generator to be restarted for each polygon. - - std::vector raw_samples = sample_expolygon_with_boundary(islands, samples_per_mm2, 5.f / poisson_radius, m_rng); + + std::vector raw_samples = + flags & icfBoundaryOnly ? + sample_expolygon_with_boundary(islands, samples_per_mm2, + 5.f / poisson_radius, m_rng) : + sample_expolygon(islands, samples_per_mm2, m_rng); + std::vector poisson_samples; for (size_t iter = 0; iter < 4; ++ iter) { poisson_samples = poisson_disk_from_samples(raw_samples, poisson_radius, [&structure, &grid3d, min_spacing](const Vec2f &pos) { - return grid3d.collides_with(pos, &structure, min_spacing); + return grid3d.collides_with(pos, structure.layer->print_z, min_spacing); }); if (poisson_samples.size() >= poisson_samples_target || m_config.minimal_distance > poisson_radius-EPSILON) break; @@ -521,12 +599,13 @@ void SupportPointGenerator::uniformly_cover(const ExPolygons& islands, Structure poisson_samples.erase(poisson_samples.begin() + poisson_samples_target, poisson_samples.end()); } for (const Vec2f &pt : poisson_samples) { - m_output.emplace_back(float(pt(0)), float(pt(1)), structure.height, m_config.head_diameter/2.f, is_new_island); + m_output.emplace_back(float(pt(0)), float(pt(1)), structure.zlevel, m_config.head_diameter/2.f, flags & icfIsNew); structure.supports_force_this_layer += m_config.support_force(); grid3d.insert(pt, &structure); } } + void remove_bottom_points(std::vector &pts, float lvl) { // get iterator to the reorganized vector end diff --git a/src/libslic3r/SLA/SupportPointGenerator.hpp b/src/libslic3r/SLA/SupportPointGenerator.hpp index f1b377025..30c221c04 100644 --- a/src/libslic3r/SLA/SupportPointGenerator.hpp +++ b/src/libslic3r/SLA/SupportPointGenerator.hpp @@ -22,8 +22,9 @@ public: float density_relative {1.f}; float minimal_distance {1.f}; float head_diameter {0.4f}; - /////////////// - inline float support_force() const { return 7.7f / density_relative; } // a force one point can support (arbitrary force unit) + + // Originally calibrated to 7.7f, reduced density by Tamas to 70% which is 11.1 (7.7 / 0.7) to adjust for new algorithm changes in tm_suppt_gen_improve + inline float support_force() const { return 11.1f / density_relative; } // a force one point can support (arbitrary force unit) inline float tear_pressure() const { return 1.f; } // pressure that the display exerts (the force unit per mm2) }; @@ -38,8 +39,8 @@ public: struct MyLayer; struct Structure { - Structure(MyLayer &layer, const ExPolygon& poly, const BoundingBox &bbox, const Vec2f ¢roid, float area, float h) : - layer(&layer), polygon(&poly), bbox(bbox), centroid(centroid), area(area), height(h) + Structure(MyLayer &layer, const ExPolygon& poly, const BoundingBox &bbox, const Vec2f ¢roid, float area, float h) : + layer(&layer), polygon(&poly), bbox(bbox), centroid(centroid), area(area), zlevel(h) #ifdef SLA_SUPPORTPOINTGEN_DEBUG , unique_id(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch())) #endif /* SLA_SUPPORTPOINTGEN_DEBUG */ @@ -49,7 +50,7 @@ public: const BoundingBox bbox; const Vec2f centroid = Vec2f::Zero(); const float area = 0.f; - float height = 0; + float zlevel = 0; // How well is this ExPolygon held to the print base? // Positive number, the higher the better. float supports_force_this_layer = 0.f; @@ -159,8 +160,8 @@ public: grid.emplace(cell_id(pt.position), pt); } - bool collides_with(const Vec2f &pos, Structure *island, float radius) { - Vec3f pos3d(pos.x(), pos.y(), float(island->layer->print_z)); + bool collides_with(const Vec2f &pos, float print_z, float radius) { + Vec3f pos3d(pos.x(), pos.y(), print_z); Vec3i cell = cell_id(pos3d); std::pair it_pair = grid.equal_range(cell); if (collides_with(pos3d, radius, it_pair.first, it_pair.second)) @@ -198,7 +199,16 @@ private: SupportPointGenerator::Config m_config; void process(const std::vector& slices, const std::vector& heights); - void uniformly_cover(const ExPolygons& islands, Structure& structure, PointGrid3D &grid3d, bool is_new_island = false, bool just_one = false); + +public: + enum IslandCoverageFlags : uint8_t { icfNone = 0x0, icfIsNew = 0x1, icfBoundaryOnly = 0x2 }; + +private: + + void uniformly_cover(const ExPolygons& islands, Structure& structure, float deficit, PointGrid3D &grid3d, IslandCoverageFlags flags = icfNone); + + void add_support_points(Structure& structure, PointGrid3D &grid3d); + void project_onto_mesh(std::vector& points) const; #ifdef SLA_SUPPORTPOINTGEN_DEBUG @@ -215,6 +225,9 @@ private: void remove_bottom_points(std::vector &pts, float lvl); +std::vector sample_expolygon(const ExPolygon &expoly, float samples_per_mm2, std::mt19937 &rng); +void sample_expolygon_boundary(const ExPolygon &expoly, float samples_per_mm, std::vector &out, std::mt19937 &rng); + }} // namespace Slic3r::sla #endif // SUPPORTPOINTGENERATOR_HPP diff --git a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp index 0e7af8d50..3c39c64e6 100644 --- a/src/libslic3r/SLA/SupportTreeBuildsteps.cpp +++ b/src/libslic3r/SLA/SupportTreeBuildsteps.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include #include namespace Slic3r { diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index eaf969819..d94bc682b 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -187,7 +188,7 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) } if (MeshBoolean::cgal::does_self_intersect(*holes_mesh_cgal)) - throw std::runtime_error(L("Too much overlapping holes.")); + throw Slic3r::SlicingError(L("Too many overlapping holes.")); auto hollowed_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal(hollowed_mesh); @@ -195,7 +196,7 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) MeshBoolean::cgal::minus(*hollowed_mesh_cgal, *holes_mesh_cgal); hollowed_mesh = MeshBoolean::cgal::cgal_to_triangle_mesh(*hollowed_mesh_cgal); } catch (const std::runtime_error &) { - throw std::runtime_error(L( + throw Slic3r::SlicingError(L( "Drilling holes into the mesh failed. " "This is usually caused by broken model. Try to fix it first.")); } @@ -241,7 +242,7 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po) if(slindex_it == po.m_slice_index.end()) //TRN To be shown at the status bar on SLA slicing error. - throw std::runtime_error( + throw Slic3r::RuntimeError( L("Slicing had to be stopped due to an internal error: " "Inconsistent slice index.")); @@ -445,7 +446,7 @@ void SLAPrint::Steps::generate_pad(SLAPrintObject &po) { auto &pad_mesh = po.m_supportdata->support_tree_ptr->retrieve_mesh(sla::MeshType::Pad); if (!validate_pad(pad_mesh, pcfg)) - throw std::runtime_error( + throw Slic3r::SlicingError( L("No pad can be generated for this model with the " "current configuration")); @@ -613,7 +614,7 @@ void SLAPrint::Steps::initialize_printer_input() for(const SliceRecord& slicerecord : o->get_slice_index()) { if (!slicerecord.is_valid()) - throw std::runtime_error( + throw Slic3r::SlicingError( L("There are unprintable objects. Try to " "adjust support settings to make the " "objects printable.")); diff --git a/src/libslic3r/Semver.hpp b/src/libslic3r/Semver.hpp index 24ca74f83..f55fa9f9f 100644 --- a/src/libslic3r/Semver.hpp +++ b/src/libslic3r/Semver.hpp @@ -10,6 +10,8 @@ #include "semver/semver.h" +#include "Exception.hpp" + namespace Slic3r { @@ -38,7 +40,7 @@ public: { auto parsed = parse(str); if (! parsed) { - throw std::runtime_error(std::string("Could not parse version string: ") + str); + throw Slic3r::RuntimeError(std::string("Could not parse version string: ") + str); } ver = parsed->ver; parsed->ver = semver_zero(); diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 17edf1b5a..8ba34e516 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -1,3 +1,4 @@ +#include "Exception.hpp" #include "TriangleMesh.hpp" #include "ClipperUtils.hpp" #include "Geometry.hpp" @@ -70,7 +71,7 @@ TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector &fac stl_get_size(&stl); } -TriangleMesh::TriangleMesh(const indexed_triangle_set &M) +TriangleMesh::TriangleMesh(const indexed_triangle_set &M) : repaired(false) { stl.stats.type = inmemory; @@ -420,7 +421,7 @@ std::deque TriangleMesh::find_unvisited_neighbors(std::vectorrepaired) - throw std::runtime_error("find_unvisited_neighbors() requires repair()"); + throw Slic3r::RuntimeError("find_unvisited_neighbors() requires repair()"); // If the visited list is empty, populate it with false for every facet. if (facet_visited.empty()) @@ -683,7 +684,7 @@ void TriangleMeshSlicer::init(const TriangleMesh *_mesh, throw_on_cancel_callbac { mesh = _mesh; if (! mesh->has_shared_vertices()) - throw std::invalid_argument("TriangleMeshSlicer was passed a mesh without shared vertices."); + throw Slic3r::InvalidArgument("TriangleMeshSlicer was passed a mesh without shared vertices."); throw_on_cancel(); facets_edges.assign(_mesh->stl.stats.number_of_facets * 3, -1); diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp index ef531169d..99b105a6b 100644 --- a/src/libslic3r/Utils.hpp +++ b/src/libslic3r/Utils.hpp @@ -348,11 +348,11 @@ inline std::string get_time_dhm(float time_in_secs) char buffer[64]; if (days > 0) - ::sprintf(buffer, "%dd %dh %dm %ds", days, hours, minutes, (int)time_in_secs); + ::sprintf(buffer, "%dd %dh %dm", days, hours, minutes); else if (hours > 0) - ::sprintf(buffer, "%dh %dm %ds", hours, minutes, (int)time_in_secs); + ::sprintf(buffer, "%dh %dm", hours, minutes); else if (minutes > 0) - ::sprintf(buffer, "%dm %ds", minutes, (int)time_in_secs); + ::sprintf(buffer, "%dm", minutes); return buffer; } diff --git a/src/libslic3r/Zipper.cpp b/src/libslic3r/Zipper.cpp index 02f022083..7a95829cd 100644 --- a/src/libslic3r/Zipper.cpp +++ b/src/libslic3r/Zipper.cpp @@ -1,5 +1,6 @@ #include +#include "Exception.hpp" #include "Zipper.hpp" #include "miniz_extension.hpp" #include @@ -29,7 +30,7 @@ public: SLIC3R_NORETURN void blow_up() const { - throw std::runtime_error(formatted_errorstr()); + throw Slic3r::RuntimeError(formatted_errorstr()); } bool is_alive() diff --git a/src/libslic3r/libslic3r_version.h.in b/src/libslic3r/libslic3r_version.h.in index 26a67362b..90f0812ab 100644 --- a/src/libslic3r/libslic3r_version.h.in +++ b/src/libslic3r/libslic3r_version.h.in @@ -6,4 +6,7 @@ #define SLIC3R_VERSION "@SLIC3R_VERSION@" #define SLIC3R_BUILD_ID "@SLIC3R_BUILD_ID@" +#define GCODEVIEWER_APP_NAME "@GCODEVIEWER_APP_NAME@" +#define GCODEVIEWER_BUILD_ID "@GCODEVIEWER_BUILD_ID@" + #endif /* __SLIC3R_VERSION_H */ diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index b452c8ffb..1c3007810 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -173,6 +173,10 @@ set(SLIC3R_GUI_SOURCES GUI/Search.hpp GUI/NotificationManager.cpp GUI/NotificationManager.hpp + GUI/UnsavedChangesDialog.cpp + GUI/UnsavedChangesDialog.hpp + GUI/ExtraRenderers.cpp + GUI/ExtraRenderers.hpp Utils/Http.cpp Utils/Http.hpp Utils/FixModelByWin10.cpp @@ -191,6 +195,8 @@ set(SLIC3R_GUI_SOURCES Utils/Bonjour.hpp Utils/PresetUpdater.cpp Utils/PresetUpdater.hpp + Utils/Process.cpp + Utils/Process.hpp Utils/Profile.hpp Utils/UndoRedo.cpp Utils/UndoRedo.hpp diff --git a/src/slic3r/Config/Snapshot.cpp b/src/slic3r/Config/Snapshot.cpp index 30596b614..45dc99874 100644 --- a/src/slic3r/Config/Snapshot.cpp +++ b/src/slic3r/Config/Snapshot.cpp @@ -315,7 +315,7 @@ size_t SnapshotDB::load_db() // Sort the snapshots by their date/time. std::sort(m_snapshots.begin(), m_snapshots.end(), [](const Snapshot &s1, const Snapshot &s2) { return s1.time_captured < s2.time_captured; }); if (! errors_cummulative.empty()) - throw std::runtime_error(errors_cummulative); + throw Slic3r::RuntimeError(errors_cummulative); return m_snapshots.size(); } @@ -339,7 +339,7 @@ static void copy_config_dir_single_level(const boost::filesystem::path &path_src { if (! boost::filesystem::is_directory(path_dst) && ! boost::filesystem::create_directory(path_dst)) - throw std::runtime_error(std::string("Slic3r was unable to create a directory at ") + path_dst.string()); + throw Slic3r::RuntimeError(std::string("Slic3r was unable to create a directory at ") + path_dst.string()); for (auto &dir_entry : boost::filesystem::directory_iterator(path_src)) if (Slic3r::is_ini_file(dir_entry)) @@ -429,7 +429,7 @@ const Snapshot& SnapshotDB::restore_snapshot(const std::string &id, AppConfig &a this->restore_snapshot(snapshot, app_config); return snapshot; } - throw std::runtime_error(std::string("Snapshot with id " + id + " was not found.")); + throw Slic3r::RuntimeError(std::string("Snapshot with id " + id + " was not found.")); } void SnapshotDB::restore_snapshot(const Snapshot &snapshot, AppConfig &app_config) @@ -501,7 +501,7 @@ boost::filesystem::path SnapshotDB::create_db_dir() subdir.make_preferred(); if (! boost::filesystem::is_directory(subdir) && ! boost::filesystem::create_directory(subdir)) - throw std::runtime_error(std::string("Slic3r was unable to create a directory at ") + subdir.string()); + throw Slic3r::RuntimeError(std::string("Slic3r was unable to create a directory at ") + subdir.string()); } return snapshots_dir; } diff --git a/src/slic3r/Config/Version.cpp b/src/slic3r/Config/Version.cpp index d00e4a2ab..04ce05ab5 100644 --- a/src/slic3r/Config/Version.cpp +++ b/src/slic3r/Config/Version.cpp @@ -324,7 +324,7 @@ std::vector Index::load_db() } if (! errors_cummulative.empty()) - throw std::runtime_error(errors_cummulative); + throw Slic3r::RuntimeError(errors_cummulative); return index_db; } diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 70fec670c..ca96af49c 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -1926,7 +1926,7 @@ void _3DScene::extrusionentity_to_verts(const ExtrusionEntity *extrusion_entity, if (extrusion_entity_collection != nullptr) extrusionentity_to_verts(*extrusion_entity_collection, print_z, copy, volume); else { - throw std::runtime_error("Unexpected extrusion_entity type in to_verts()"); + throw Slic3r::RuntimeError("Unexpected extrusion_entity type in to_verts()"); } } } diff --git a/src/slic3r/GUI/AboutDialog.cpp b/src/slic3r/GUI/AboutDialog.cpp index 92f8d1fdb..8d9ea97b9 100644 --- a/src/slic3r/GUI/AboutDialog.cpp +++ b/src/slic3r/GUI/AboutDialog.cpp @@ -37,10 +37,17 @@ void AboutDialogLogo::onRepaint(wxEvent &event) // CopyrightsDialog // ----------------------------------------- CopyrightsDialog::CopyrightsDialog() +#if ENABLE_GCODE_VIEWER + : DPIDialog(NULL, wxID_ANY, from_u8((boost::format("%1% - %2%") + % (wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME) + % _utf8(L("Portions copyright"))).str()), + wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) +#else : DPIDialog(NULL, wxID_ANY, from_u8((boost::format("%1% - %2%") % SLIC3R_APP_NAME % _utf8(L("Portions copyright"))).str()), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) +#endif // ENABLE_GCODE_VIEWER { this->SetFont(wxGetApp().normal_font()); this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); @@ -52,7 +59,7 @@ CopyrightsDialog::CopyrightsDialog() m_html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxSize(40 * em_unit(), 20 * em_unit()), wxHW_SCROLLBAR_AUTO); - wxFont font = GetFont(); + wxFont font = get_default_font_for_dpi(get_dpi_for_window(this));// GetFont(); const int fs = font.GetPointSize(); const int fs2 = static_cast(1.2f*fs); int size[] = { fs, fs, fs, fs, fs2, fs2, fs2 }; @@ -201,8 +208,13 @@ void CopyrightsDialog::onCloseDialog(wxEvent &) } AboutDialog::AboutDialog() +#if ENABLE_GCODE_VIEWER + : DPIDialog(NULL, wxID_ANY, from_u8((boost::format(_utf8(L("About %s"))) % (wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME)).str()), wxDefaultPosition, + wxDefaultSize, /*wxCAPTION*/wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) +#else : DPIDialog(NULL, wxID_ANY, from_u8((boost::format(_utf8(L("About %s"))) % SLIC3R_APP_NAME).str()), wxDefaultPosition, wxDefaultSize, /*wxCAPTION*/wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) +#endif // ENABLE_GCODE_VIEWER { SetFont(wxGetApp().normal_font()); @@ -214,7 +226,11 @@ AboutDialog::AboutDialog() main_sizer->Add(hsizer, 0, wxEXPAND | wxALL, 20); // logo +#if ENABLE_GCODE_VIEWER + m_logo_bitmap = ScalableBitmap(this, wxGetApp().is_editor() ? "PrusaSlicer_192px.png" : "PrusaSlicer-gcodeviewer_192px.png", 192); +#else m_logo_bitmap = ScalableBitmap(this, "PrusaSlicer_192px.png", 192); +#endif // ENABLE_GCODE_VIEWER m_logo = new wxStaticBitmap(this, wxID_ANY, m_logo_bitmap.bmp()); hsizer->Add(m_logo, 1, wxALIGN_CENTER_VERTICAL); @@ -223,7 +239,11 @@ AboutDialog::AboutDialog() // title { +#if ENABLE_GCODE_VIEWER + wxStaticText* title = new wxStaticText(this, wxID_ANY, wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME, wxDefaultPosition, wxDefaultSize); +#else wxStaticText* title = new wxStaticText(this, wxID_ANY, SLIC3R_APP_NAME, wxDefaultPosition, wxDefaultSize); +#endif // ENABLE_GCODE_VIEWER wxFont title_font = GUI::wxGetApp().bold_font(); title_font.SetFamily(wxFONTFAMILY_ROMAN); title_font.SetPointSize(24); @@ -233,7 +253,7 @@ AboutDialog::AboutDialog() // version { - auto version_string = _(L("Version"))+ " " + std::string(SLIC3R_VERSION); + auto version_string = _L("Version")+ " " + std::string(SLIC3R_VERSION); wxStaticText* version = new wxStaticText(this, wxID_ANY, version_string.c_str(), wxDefaultPosition, wxDefaultSize); wxFont version_font = GetFont(); #ifdef __WXMSW__ @@ -249,7 +269,7 @@ AboutDialog::AboutDialog() m_html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO/*NEVER*/); { m_html->SetMinSize(wxSize(-1, 16 * wxGetApp().em_unit())); - wxFont font = GetFont(); + wxFont font = get_default_font_for_dpi(get_dpi_for_window(this));// GetFont(); const auto text_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue()); auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue()); @@ -294,7 +314,7 @@ AboutDialog::AboutDialog() wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxCLOSE); m_copy_rights_btn_id = NewControlId(); - auto copy_rights_btn = new wxButton(this, m_copy_rights_btn_id, _(L("Portions copyright"))+dots); + auto copy_rights_btn = new wxButton(this, m_copy_rights_btn_id, _L("Portions copyright")+dots); buttons->Insert(0, copy_rights_btn, 0, wxLEFT, 5); copy_rights_btn->Bind(wxEVT_BUTTON, &AboutDialog::onCopyrightBtn, this); diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 9675db10e..605a98eea 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -41,6 +41,36 @@ namespace Slic3r { +bool SlicingProcessCompletedEvent::critical_error() const +{ + try { + this->rethrow_exception(); + } catch (const Slic3r::SlicingError &ex) { + // Exception derived from SlicingError is non-critical. + return false; + } catch (...) { + } + return true; +} + +std::string SlicingProcessCompletedEvent::format_error_message() const +{ + std::string error; + try { + this->rethrow_exception(); + } catch (const std::bad_alloc& ex) { + wxString errmsg = GUI::from_u8((boost::format(_utf8(L("%s has encountered an error. It was likely caused by running out of memory. " + "If you are sure you have enough RAM on your system, this may also be a bug and we would " + "be glad if you reported it."))) % SLIC3R_APP_NAME).str()); + error = std::string(errmsg.ToUTF8()) + "\n\n" + std::string(ex.what()); + } catch (std::exception &ex) { + error = ex.what(); + } catch (...) { + error = "Unknown C++ exception."; + } + return error; +} + BackgroundSlicingProcess::BackgroundSlicingProcess() { boost::filesystem::path temp_path(wxStandardPaths::Get().GetTempDir().utf8_str().data()); @@ -109,19 +139,19 @@ void BackgroundSlicingProcess::process_fff() switch (copy_ret_val) { case SUCCESS: break; // no error case FAIL_COPY_FILE: - throw std::runtime_error(_utf8(L("Copying of the temporary G-code to the output G-code failed. Maybe the SD card is write locked?"))); + throw Slic3r::RuntimeError(_utf8(L("Copying of the temporary G-code to the output G-code failed. Maybe the SD card is write locked?"))); break; case FAIL_FILES_DIFFERENT: - throw std::runtime_error((boost::format(_utf8(L("Copying of the temporary G-code to the output G-code failed. There might be problem with target device, please try exporting again or using different device. The corrupted output G-code is at %1%.tmp."))) % export_path).str()); + throw Slic3r::RuntimeError((boost::format(_utf8(L("Copying of the temporary G-code to the output G-code failed. There might be problem with target device, please try exporting again or using different device. The corrupted output G-code is at %1%.tmp."))) % export_path).str()); break; case FAIL_RENAMING: - throw std::runtime_error((boost::format(_utf8(L("Renaming of the G-code after copying to the selected destination folder has failed. Current path is %1%.tmp. Please try exporting again."))) % export_path).str()); + throw Slic3r::RuntimeError((boost::format(_utf8(L("Renaming of the G-code after copying to the selected destination folder has failed. Current path is %1%.tmp. Please try exporting again."))) % export_path).str()); break; case FAIL_CHECK_ORIGIN_NOT_OPENED: - throw std::runtime_error((boost::format(_utf8(L("Copying of the temporary G-code has finished but the original code at %1% couldn't be opened during copy check. The output G-code is at %2%.tmp."))) % m_temp_output_path % export_path).str()); + throw Slic3r::RuntimeError((boost::format(_utf8(L("Copying of the temporary G-code has finished but the original code at %1% couldn't be opened during copy check. The output G-code is at %2%.tmp."))) % m_temp_output_path % export_path).str()); break; case FAIL_CHECK_TARGET_NOT_OPENED: - throw std::runtime_error((boost::format(_utf8(L("Copying of the temporary G-code has finished but the exported code couldn't be opened during copy check. The output G-code is at %1%.tmp."))) % export_path).str()); + throw Slic3r::RuntimeError((boost::format(_utf8(L("Copying of the temporary G-code has finished but the exported code couldn't be opened during copy check. The output G-code is at %1%.tmp."))) % export_path).str()); break; default: BOOST_LOG_TRIVIAL(warning) << "Unexpected fail code(" << (int)copy_ret_val << ") durring copy_file() to " << export_path << "."; @@ -210,7 +240,7 @@ void BackgroundSlicingProcess::thread_proc() // Process the background slicing task. m_state = STATE_RUNNING; lck.unlock(); - std::string error; + std::exception_ptr exception; try { assert(m_print != nullptr); switch(m_print->technology()) { @@ -221,15 +251,8 @@ void BackgroundSlicingProcess::thread_proc() } catch (CanceledException & /* ex */) { // Canceled, this is all right. assert(m_print->canceled()); - } catch (const std::bad_alloc& ex) { - wxString errmsg = GUI::from_u8((boost::format(_utf8(L("%s has encountered an error. It was likely caused by running out of memory. " - "If you are sure you have enough RAM on your system, this may also be a bug and we would " - "be glad if you reported it."))) % SLIC3R_APP_NAME).str()); - error = std::string(errmsg.ToUTF8()) + "\n\n" + std::string(ex.what()); - } catch (std::exception &ex) { - error = ex.what(); } catch (...) { - error = "Unknown C++ exception."; + exception = std::current_exception(); } m_print->finalize(); lck.lock(); @@ -237,9 +260,9 @@ void BackgroundSlicingProcess::thread_proc() if (m_print->cancel_status() != Print::CANCELED_INTERNAL) { // Only post the canceled event, if canceled by user. // Don't post the canceled event, if canceled from Print::apply(). - wxCommandEvent evt(m_event_finished_id); - evt.SetString(GUI::from_u8(error)); - evt.SetInt(m_print->canceled() ? -1 : (error.empty() ? 1 : 0)); + SlicingProcessCompletedEvent evt(m_event_finished_id, 0, + (m_state == STATE_CANCELED) ? SlicingProcessCompletedEvent::Cancelled : + exception ? SlicingProcessCompletedEvent::Error : SlicingProcessCompletedEvent::Finished, exception); wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt.Clone()); } m_print->restart(); @@ -299,7 +322,7 @@ bool BackgroundSlicingProcess::start() // The background processing thread is already running. return false; if (! this->idle()) - throw std::runtime_error("Cannot start a background task, the worker thread is not idle."); + throw Slic3r::RuntimeError("Cannot start a background task, the worker thread is not idle."); m_state = STATE_STARTED; m_print->set_cancel_callback([this](){ this->stop_internal(); }); lck.unlock(); @@ -494,7 +517,7 @@ void BackgroundSlicingProcess::prepare_upload() if (m_print == m_fff_print) { m_print->set_status(95, _utf8(L("Running post-processing scripts"))); if (copy_file(m_temp_output_path, source_path.string()) != SUCCESS) { - throw std::runtime_error(_utf8(L("Copying of the temporary G-code to the output G-code failed"))); + throw Slic3r::RuntimeError(_utf8(L("Copying of the temporary G-code to the output G-code failed"))); } run_post_process_scripts(source_path.string(), m_fff_print->config()); m_upload_job.upload_data.upload_path = m_fff_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string()); diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp index 9fe1157b6..1b2687e63 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp @@ -37,6 +37,36 @@ public: PrintBase::SlicingStatus status; }; +class SlicingProcessCompletedEvent : public wxEvent +{ +public: + enum StatusType { + Finished, + Cancelled, + Error + }; + + SlicingProcessCompletedEvent(wxEventType eventType, int winid, StatusType status, std::exception_ptr exception) : + wxEvent(winid, eventType), m_status(status), m_exception(exception) {} + virtual wxEvent* Clone() const { return new SlicingProcessCompletedEvent(*this); } + + StatusType status() const { return m_status; } + bool finished() const { return m_status == Finished; } + bool success() const { return m_status == Finished; } + bool cancelled() const { return m_status == Cancelled; } + bool error() const { return m_status == Error; } + // Unhandled error produced by stdlib or a Win32 structured exception, or unhandled Slic3r's own critical exception. + bool critical_error() const; + // Only valid if error() + void rethrow_exception() const { assert(this->error()); assert(m_exception); std::rethrow_exception(m_exception); } + // Produce a human readable message to be displayed by a notification or a message box. + std::string format_error_message() const; + +private: + StatusType m_status; + std::exception_ptr m_exception; +}; + wxDEFINE_EVENT(EVT_SLICING_UPDATE, SlicingStatusEvent); // Print step IDs for keeping track of the print state. diff --git a/src/slic3r/GUI/BedShapeDialog.cpp b/src/slic3r/GUI/BedShapeDialog.cpp index f08b1a379..2fc7d1036 100644 --- a/src/slic3r/GUI/BedShapeDialog.cpp +++ b/src/slic3r/GUI/BedShapeDialog.cpp @@ -21,6 +21,160 @@ namespace Slic3r { namespace GUI { +BedShape::BedShape(const ConfigOptionPoints& points) +{ + auto polygon = Polygon::new_scale(points.values); + + // is this a rectangle ? + if (points.size() == 4) { + auto lines = polygon.lines(); + if (lines[0].parallel_to(lines[2]) && lines[1].parallel_to(lines[3])) { + // okay, it's a rectangle + // find origin + coordf_t x_min, x_max, y_min, y_max; + x_max = x_min = points.values[0](0); + y_max = y_min = points.values[0](1); + for (auto pt : points.values) + { + x_min = std::min(x_min, pt(0)); + x_max = std::max(x_max, pt(0)); + y_min = std::min(y_min, pt(1)); + y_max = std::max(y_max, pt(1)); + } + + m_type = Type::Rectangular; + m_rectSize = Vec2d(x_max - x_min, y_max - y_min); + m_rectOrigin = Vec2d(-x_min, -y_min); + + return; + } + } + + // is this a circle ? + { + // Analyze the array of points.Do they reside on a circle ? + auto center = polygon.bounding_box().center(); + std::vector vertex_distances; + double avg_dist = 0; + for (auto pt : polygon.points) + { + double distance = (pt - center).cast().norm(); + vertex_distances.push_back(distance); + avg_dist += distance; + } + + avg_dist /= vertex_distances.size(); + bool defined_value = true; + for (auto el : vertex_distances) + { + if (abs(el - avg_dist) > 10 * SCALED_EPSILON) + defined_value = false; + break; + } + if (defined_value) { + // all vertices are equidistant to center + m_type = Type::Circular; + m_diameter = unscale(avg_dist * 2); + + return; + } + } + + if (points.size() < 3) + return; + + // This is a custom bed shape, use the polygon provided. + m_type = Type::Custom; +} + +static std::string get_option_label(BedShape::Parameter param) +{ + switch (param) { + case BedShape::Parameter::RectSize : return L("Size"); + case BedShape::Parameter::RectOrigin: return L("Origin"); + case BedShape::Parameter::Diameter : return L("Diameter"); + default: return ""; + } +} + +void BedShape::append_option_line(ConfigOptionsGroupShp optgroup, Parameter param) +{ + ConfigOptionDef def; + + if (param == Parameter::RectSize) { + def.type = coPoints; + def.set_default_value(new ConfigOptionPoints{ Vec2d(200, 200) }); + def.min = 0; + def.max = 1200; + def.label = get_option_label(param); + def.tooltip = L("Size in X and Y of the rectangular plate."); + + Option option(def, "rect_size"); + optgroup->append_single_option_line(option); + } + else if (param == Parameter::RectOrigin) { + def.type = coPoints; + def.set_default_value(new ConfigOptionPoints{ Vec2d(0, 0) }); + def.min = -600; + def.max = 600; + def.label = get_option_label(param); + def.tooltip = L("Distance of the 0,0 G-code coordinate from the front left corner of the rectangle."); + + Option option(def, "rect_origin"); + optgroup->append_single_option_line(option); + } + else if (param == Parameter::Diameter) { + def.type = coFloat; + def.set_default_value(new ConfigOptionFloat(200)); + def.sidetext = L("mm"); + def.label = get_option_label(param); + def.tooltip = L("Diameter of the print bed. It is assumed that origin (0,0) is located in the center."); + + Option option(def, "diameter"); + optgroup->append_single_option_line(option); + } +} + +wxString BedShape::get_name(Type type) +{ + switch (type) { + case Type::Rectangular : return _L("Rectangular"); + case Type::Circular : return _L("Circular"); + case Type::Custom : return _L("Custom"); + case Type::Invalid : + default : return _L("Invalid"); + } +} + +size_t BedShape::get_type() +{ + return static_cast(m_type == Type::Invalid ? Type::Rectangular : m_type); +} + +wxString BedShape::get_full_name_with_params() +{ + wxString out = _L("Shape") + ": " + get_name(m_type); + + if (m_type == Type::Rectangular) { + out += "\n" + _(get_option_label(Parameter::RectSize)) + ": [" + ConfigOptionPoint(m_rectSize).serialize() + "]"; + out += "\n" + _(get_option_label(Parameter::RectOrigin))+ ": [" + ConfigOptionPoint(m_rectOrigin).serialize() + "]"; + } + else if (m_type == Type::Circular) + out += "\n" + _L(get_option_label(Parameter::Diameter)) + ": [" + double_to_string(m_diameter) + "]"; + + return out; +} + +void BedShape::apply_optgroup_values(ConfigOptionsGroupShp optgroup) +{ + if (m_type == Type::Rectangular || m_type == Type::Invalid) { + optgroup->set_value("rect_size" , new ConfigOptionPoints{ m_rectSize }); + optgroup->set_value("rect_origin" , new ConfigOptionPoints{ m_rectOrigin }); + } + else if (m_type == Type::Circular) + optgroup->set_value("diameter", double_to_string(m_diameter)); +} + void BedShapeDialog::build_dialog(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture, const ConfigOptionString& custom_model) { SetFont(wxGetApp().normal_font()); @@ -72,50 +226,28 @@ void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const Conf m_shape_options_book = new wxChoicebook(this, wxID_ANY, wxDefaultPosition, wxSize(25*wxGetApp().em_unit(), -1), wxCHB_TOP); sbsizer->Add(m_shape_options_book); - auto optgroup = init_shape_options_page(_(L("Rectangular"))); - ConfigOptionDef def; - def.type = coPoints; - def.set_default_value(new ConfigOptionPoints{ Vec2d(200, 200) }); - def.min = 0; - def.max = 1200; - def.label = L("Size"); - def.tooltip = L("Size in X and Y of the rectangular plate."); - Option option(def, "rect_size"); - optgroup->append_single_option_line(option); + auto optgroup = init_shape_options_page(BedShape::get_name(BedShape::Type::Rectangular)); + BedShape::append_option_line(optgroup, BedShape::Parameter::RectSize); + BedShape::append_option_line(optgroup, BedShape::Parameter::RectOrigin); - def.type = coPoints; - def.set_default_value(new ConfigOptionPoints{ Vec2d(0, 0) }); - def.min = -600; - def.max = 600; - def.label = L("Origin"); - def.tooltip = L("Distance of the 0,0 G-code coordinate from the front left corner of the rectangle."); - option = Option(def, "rect_origin"); - optgroup->append_single_option_line(option); + optgroup = init_shape_options_page(BedShape::get_name(BedShape::Type::Circular)); + BedShape::append_option_line(optgroup, BedShape::Parameter::Diameter); - optgroup = init_shape_options_page(_(L("Circular"))); - def.type = coFloat; - def.set_default_value(new ConfigOptionFloat(200)); - def.sidetext = L("mm"); - def.label = L("Diameter"); - def.tooltip = L("Diameter of the print bed. It is assumed that origin (0,0) is located in the center."); - option = Option(def, "diameter"); - optgroup->append_single_option_line(option); + optgroup = init_shape_options_page(BedShape::get_name(BedShape::Type::Custom)); - optgroup = init_shape_options_page(_(L("Custom"))); Line line{ "", "" }; line.full_width = 1; line.widget = [this](wxWindow* parent) { - wxButton* shape_btn = new wxButton(parent, wxID_ANY, _(L("Load shape from STL..."))); + wxButton* shape_btn = new wxButton(parent, wxID_ANY, _L("Load shape from STL...")); wxSizer* shape_sizer = new wxBoxSizer(wxHORIZONTAL); shape_sizer->Add(shape_btn, 1, wxEXPAND); wxSizer* sizer = new wxBoxSizer(wxVERTICAL); sizer->Add(shape_sizer, 1, wxEXPAND); - shape_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) - { + shape_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) { load_stl(); - })); + }); return sizer; }; @@ -149,10 +281,6 @@ void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const Conf update_preview(); } -#define SHAPE_RECTANGULAR 0 -#define SHAPE_CIRCULAR 1 -#define SHAPE_CUSTOM 2 - // Called from the constructor. // Create a panel for a rectangular / circular / custom bed shape. ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(const wxString& title) @@ -337,83 +465,18 @@ wxPanel* BedShapePanel::init_model_panel() // with the list of points in the ini file directly. void BedShapePanel::set_shape(const ConfigOptionPoints& points) { - auto polygon = Polygon::new_scale(points.values); + BedShape shape(points); - // is this a rectangle ? - if (points.size() == 4) { - auto lines = polygon.lines(); - if (lines[0].parallel_to(lines[2]) && lines[1].parallel_to(lines[3])) { - // okay, it's a rectangle - // find origin - coordf_t x_min, x_max, y_min, y_max; - x_max = x_min = points.values[0](0); - y_max = y_min = points.values[0](1); - for (auto pt : points.values) - { - x_min = std::min(x_min, pt(0)); - x_max = std::max(x_max, pt(0)); - y_min = std::min(y_min, pt(1)); - y_max = std::max(y_max, pt(1)); - } + m_shape_options_book->SetSelection(shape.get_type()); + shape.apply_optgroup_values(m_optgroups[shape.get_type()]); - auto origin = new ConfigOptionPoints{ Vec2d(-x_min, -y_min) }; + // Copy the polygon to the canvas, make a copy of the array, if custom shape is selected + if (shape.is_custom()) + m_loaded_shape = points.values; - m_shape_options_book->SetSelection(SHAPE_RECTANGULAR); - auto optgroup = m_optgroups[SHAPE_RECTANGULAR]; - optgroup->set_value("rect_size", new ConfigOptionPoints{ Vec2d(x_max - x_min, y_max - y_min) });//[x_max - x_min, y_max - y_min]); - optgroup->set_value("rect_origin", origin); - update_shape(); - return; - } - } - - // is this a circle ? - { - // Analyze the array of points.Do they reside on a circle ? - auto center = polygon.bounding_box().center(); - std::vector vertex_distances; - double avg_dist = 0; - for (auto pt: polygon.points) - { - double distance = (pt - center).cast().norm(); - vertex_distances.push_back(distance); - avg_dist += distance; - } - - avg_dist /= vertex_distances.size(); - bool defined_value = true; - for (auto el: vertex_distances) - { - if (abs(el - avg_dist) > 10 * SCALED_EPSILON) - defined_value = false; - break; - } - if (defined_value) { - // all vertices are equidistant to center - m_shape_options_book->SetSelection(SHAPE_CIRCULAR); - auto optgroup = m_optgroups[SHAPE_CIRCULAR]; - boost::any ret = wxNumberFormatter::ToString(unscale(avg_dist * 2), 0); - optgroup->set_value("diameter", ret); - update_shape(); - return; - } - } - - if (points.size() < 3) { - // Invalid polygon.Revert to default bed dimensions. - m_shape_options_book->SetSelection(SHAPE_RECTANGULAR); - auto optgroup = m_optgroups[SHAPE_RECTANGULAR]; - optgroup->set_value("rect_size", new ConfigOptionPoints{ Vec2d(200, 200) }); - optgroup->set_value("rect_origin", new ConfigOptionPoints{ Vec2d(0, 0) }); - update_shape(); - return; - } - - // This is a custom bed shape, use the polygon provided. - m_shape_options_book->SetSelection(SHAPE_CUSTOM); - // Copy the polygon to the canvas, make a copy of the array. - m_loaded_shape = points.values; update_shape(); + + return; } void BedShapePanel::update_preview() @@ -426,21 +489,20 @@ void BedShapePanel::update_preview() void BedShapePanel::update_shape() { auto page_idx = m_shape_options_book->GetSelection(); - if (page_idx == SHAPE_RECTANGULAR) { + auto opt_group = m_optgroups[page_idx]; + + BedShape::Type page_type = static_cast(page_idx); + + if (page_type == BedShape::Type::Rectangular) { Vec2d rect_size(Vec2d::Zero()); Vec2d rect_origin(Vec2d::Zero()); - try{ - rect_size = boost::any_cast(m_optgroups[SHAPE_RECTANGULAR]->get_value("rect_size")); } - catch (const std::exception & /* e */) { - return; - } - try { - rect_origin = boost::any_cast(m_optgroups[SHAPE_RECTANGULAR]->get_value("rect_origin")); - } - catch (const std::exception & /* e */) { - return; - } - + + try { rect_size = boost::any_cast(opt_group->get_value("rect_size")); } + catch (const std::exception& /* e */) { return; } + + try { rect_origin = boost::any_cast(opt_group->get_value("rect_origin")); } + catch (const std::exception & /* e */) { return; } + auto x = rect_size(0); auto y = rect_size(1); // empty strings or '-' or other things @@ -462,14 +524,11 @@ void BedShapePanel::update_shape() Vec2d(x1, y1), Vec2d(x0, y1) }; } - else if(page_idx == SHAPE_CIRCULAR) { + else if (page_type == BedShape::Type::Circular) { double diameter; - try{ - diameter = boost::any_cast(m_optgroups[SHAPE_CIRCULAR]->get_value("diameter")); - } - catch (const std::exception & /* e */) { - return; - } + try { diameter = boost::any_cast(opt_group->get_value("diameter")); } + catch (const std::exception & /* e */) { return; } + if (diameter == 0.0) return ; auto r = diameter / 2; auto twopi = 2 * PI; @@ -481,7 +540,7 @@ void BedShapePanel::update_shape() } m_shape = points; } - else if (page_idx == SHAPE_CUSTOM) + else if (page_type == BedShape::Type::Custom) m_shape = m_loaded_shape; update_preview(); diff --git a/src/slic3r/GUI/BedShapeDialog.hpp b/src/slic3r/GUI/BedShapeDialog.hpp index b583eca4a..2cfbc73ae 100644 --- a/src/slic3r/GUI/BedShapeDialog.hpp +++ b/src/slic3r/GUI/BedShapeDialog.hpp @@ -16,6 +16,42 @@ namespace GUI { class ConfigOptionsGroup; using ConfigOptionsGroupShp = std::shared_ptr; + +struct BedShape +{ + enum class Type { + Rectangular = 0, + Circular, + Custom, + Invalid + }; + + enum class Parameter { + RectSize, + RectOrigin, + Diameter + }; + + BedShape(const ConfigOptionPoints& points); + + bool is_custom() { return m_type == Type::Custom; } + + static void append_option_line(ConfigOptionsGroupShp optgroup, Parameter param); + static wxString get_name(Type type); + + // convert Type to size_t + size_t get_type(); + + wxString get_full_name_with_params(); + void apply_optgroup_values(ConfigOptionsGroupShp optgroup); + +private: + Type m_type {Type::Invalid}; + Vec2d m_rectSize {200, 200}; + Vec2d m_rectOrigin {0, 0}; + double m_diameter {0}; +}; + class BedShapePanel : public wxPanel { static const std::string NONE; diff --git a/src/slic3r/GUI/ConfigExceptions.hpp b/src/slic3r/GUI/ConfigExceptions.hpp index 9038d3445..181442d4e 100644 --- a/src/slic3r/GUI/ConfigExceptions.hpp +++ b/src/slic3r/GUI/ConfigExceptions.hpp @@ -1,15 +1,15 @@ #include namespace Slic3r { -class ConfigError : public std::runtime_error { -using std::runtime_error::runtime_error; +class ConfigError : public Slic3r::RuntimeError { + using Slic3r::RuntimeError::RuntimeError; }; namespace GUI { class ConfigGUITypeError : public ConfigError { -using ConfigError::ConfigError; + using ConfigError::ConfigError; }; -} -} +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/ConfigSnapshotDialog.cpp b/src/slic3r/GUI/ConfigSnapshotDialog.cpp index 6a44b96dc..4855bea81 100644 --- a/src/slic3r/GUI/ConfigSnapshotDialog.cpp +++ b/src/slic3r/GUI/ConfigSnapshotDialog.cpp @@ -114,7 +114,7 @@ ConfigSnapshotDialog::ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db // text html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO); { - wxFont font = wxGetApp().normal_font();//wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + wxFont font = get_default_font_for_dpi(get_dpi_for_window(this));// wxGetApp().normal_font();//wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); #ifdef __WXMSW__ const int fs = font.GetPointSize(); const int fs1 = static_cast(0.8f*fs); @@ -140,7 +140,7 @@ ConfigSnapshotDialog::ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db void ConfigSnapshotDialog::on_dpi_changed(const wxRect &suggested_rect) { - wxFont font = GetFont(); + wxFont font = get_default_font_for_dpi(get_dpi_for_window(this));// GetFont(); const int fs = font.GetPointSize(); const int fs1 = static_cast(0.8f*fs); const int fs2 = static_cast(1.1f*fs); diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 2cedbfdf7..c98b736b7 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -123,7 +123,7 @@ Bundle& BundleMap::prusa_bundle() { auto it = find(PresetBundle::PRUSA_BUNDLE); if (it == end()) { - throw std::runtime_error("ConfigWizard: Internal error in BundleMap: PRUSA_BUNDLE not loaded"); + throw Slic3r::RuntimeError("ConfigWizard: Internal error in BundleMap: PRUSA_BUNDLE not loaded"); } return it->second; diff --git a/src/slic3r/GUI/ExtraRenderers.cpp b/src/slic3r/GUI/ExtraRenderers.cpp new file mode 100644 index 000000000..2915d498c --- /dev/null +++ b/src/slic3r/GUI/ExtraRenderers.cpp @@ -0,0 +1,333 @@ +#include "ExtraRenderers.hpp" +#include "wxExtensions.hpp" +#include "GUI.hpp" +#include "I18N.hpp" + +#include +#ifdef wxHAS_GENERIC_DATAVIEWCTRL +#include "wx/generic/private/markuptext.h" +#include "wx/generic/private/rowheightcache.h" +#include "wx/generic/private/widthcalc.h" +#endif +/* +#ifdef __WXGTK__ +#include "wx/gtk/private.h" +#include "wx/gtk/private/value.h" +#endif +*/ +#if wxUSE_ACCESSIBILITY +#include "wx/private/markupparser.h" +#endif // wxUSE_ACCESSIBILITY + +using Slic3r::GUI::from_u8; +using Slic3r::GUI::into_u8; + + +//----------------------------------------------------------------------------- +// DataViewBitmapText +//----------------------------------------------------------------------------- + +wxIMPLEMENT_DYNAMIC_CLASS(DataViewBitmapText, wxObject) + +IMPLEMENT_VARIANT_OBJECT(DataViewBitmapText) + +// --------------------------------------------------------- +// BitmapTextRenderer +// --------------------------------------------------------- + +#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING +BitmapTextRenderer::BitmapTextRenderer(wxDataViewCellMode mode /*= wxDATAVIEW_CELL_EDITABLE*/, + int align /*= wxDVR_DEFAULT_ALIGNMENT*/): +wxDataViewRenderer(wxT("PrusaDataViewBitmapText"), mode, align) +{ + SetMode(mode); + SetAlignment(align); +} +#endif // ENABLE_NONCUSTOM_DATA_VIEW_RENDERING + +BitmapTextRenderer::~BitmapTextRenderer() +{ +#ifdef SUPPORTS_MARKUP + #ifdef wxHAS_GENERIC_DATAVIEWCTRL + if (m_markupText) + delete m_markupText; + #endif //wxHAS_GENERIC_DATAVIEWCTRL +#endif // SUPPORTS_MARKUP +} + +void BitmapTextRenderer::EnableMarkup(bool enable) +{ +#ifdef SUPPORTS_MARKUP +#ifdef wxHAS_GENERIC_DATAVIEWCTRL + if (enable) { + if (!m_markupText) + m_markupText = new wxItemMarkupText(wxString()); + } + else { + if (m_markupText) { + delete m_markupText; + m_markupText = nullptr; + } + } +#else + is_markupText = enable; +#endif //wxHAS_GENERIC_DATAVIEWCTRL +#endif // SUPPORTS_MARKUP +} + +bool BitmapTextRenderer::SetValue(const wxVariant &value) +{ + m_value << value; + +#ifdef SUPPORTS_MARKUP +#ifdef wxHAS_GENERIC_DATAVIEWCTRL + if (m_markupText) + m_markupText->SetMarkup(m_value.GetText()); + /* +#else +#if defined(__WXGTK__) + GValue gvalue = G_VALUE_INIT; + g_value_init(&gvalue, G_TYPE_STRING); + g_value_set_string(&gvalue, wxGTK_CONV_FONT(str.GetText(), GetOwner()->GetOwner()->GetFont())); + g_object_set_property(G_OBJECT(m_renderer/ *.GetText()* /), is_markupText ? "markup" : "text", &gvalue); + g_value_unset(&gvalue); +#endif // __WXGTK__ + */ +#endif // wxHAS_GENERIC_DATAVIEWCTRL +#endif // SUPPORTS_MARKUP + + return true; +} + +bool BitmapTextRenderer::GetValue(wxVariant& WXUNUSED(value)) const +{ + return false; +} + +#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING && wxUSE_ACCESSIBILITY +wxString BitmapTextRenderer::GetAccessibleDescription() const +{ +#ifdef SUPPORTS_MARKUP + if (m_markupText) + return wxMarkupParser::Strip(m_text); +#endif // SUPPORTS_MARKUP + + return m_value.GetText(); +} +#endif // wxUSE_ACCESSIBILITY && ENABLE_NONCUSTOM_DATA_VIEW_RENDERING + +bool BitmapTextRenderer::Render(wxRect rect, wxDC *dc, int state) +{ + int xoffset = 0; + + const wxBitmap& icon = m_value.GetBitmap(); + if (icon.IsOk()) + { +#ifdef __APPLE__ + wxSize icon_sz = icon.GetScaledSize(); +#else + wxSize icon_sz = icon.GetSize(); +#endif + dc->DrawBitmap(icon, rect.x, rect.y + (rect.height - icon_sz.y) / 2); + xoffset = icon_sz.x + 4; + } + +#if defined(SUPPORTS_MARKUP) && defined(wxHAS_GENERIC_DATAVIEWCTRL) + if (m_markupText) + { + rect.x += xoffset; + m_markupText->Render(GetView(), *dc, rect, 0, GetEllipsizeMode()); + } + else +#endif // SUPPORTS_MARKUP && wxHAS_GENERIC_DATAVIEWCTRL + RenderText(m_value.GetText(), xoffset, rect, dc, state); + + return true; +} + +wxSize BitmapTextRenderer::GetSize() const +{ + if (!m_value.GetText().empty()) + { + wxSize size; +#if defined(SUPPORTS_MARKUP) && defined(wxHAS_GENERIC_DATAVIEWCTRL) + if (m_markupText) + { + wxDataViewCtrl* const view = GetView(); + wxClientDC dc(view); + if (GetAttr().HasFont()) + dc.SetFont(GetAttr().GetEffectiveFont(view->GetFont())); + + size = m_markupText->Measure(dc); + + int lines = m_value.GetText().Freq('\n') + 1; + size.SetHeight(size.GetHeight() * lines); + } + else +#endif // SUPPORTS_MARKUP && wxHAS_GENERIC_DATAVIEWCTRL + size = GetTextExtent(m_value.GetText()); + + if (m_value.GetBitmap().IsOk()) + size.x += m_value.GetBitmap().GetWidth() + 4; + return size; + } + return wxSize(80, 20); +} + + +wxWindow* BitmapTextRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelRect, const wxVariant& value) +{ + if (can_create_editor_ctrl && !can_create_editor_ctrl()) + return nullptr; + + DataViewBitmapText data; + data << value; + + m_was_unusable_symbol = false; + + wxPoint position = labelRect.GetPosition(); + if (data.GetBitmap().IsOk()) { + const int bmp_width = data.GetBitmap().GetWidth(); + position.x += bmp_width; + labelRect.SetWidth(labelRect.GetWidth() - bmp_width); + } + + wxTextCtrl* text_editor = new wxTextCtrl(parent, wxID_ANY, data.GetText(), + position, labelRect.GetSize(), wxTE_PROCESS_ENTER); + text_editor->SetInsertionPointEnd(); + text_editor->SelectAll(); + + return text_editor; +} + +bool BitmapTextRenderer::GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) +{ + wxTextCtrl* text_editor = wxDynamicCast(ctrl, wxTextCtrl); + if (!text_editor || text_editor->GetValue().IsEmpty()) + return false; + + std::string chosen_name = into_u8(text_editor->GetValue()); + const char* unusable_symbols = "<>:/\\|?*\""; + for (size_t i = 0; i < std::strlen(unusable_symbols); i++) { + if (chosen_name.find_first_of(unusable_symbols[i]) != std::string::npos) { + m_was_unusable_symbol = true; + return false; + } + } + + // The icon can't be edited so get its old value and reuse it. + wxVariant valueOld; + GetView()->GetModel()->GetValue(valueOld, m_item, /*colName*/0); + + DataViewBitmapText bmpText; + bmpText << valueOld; + + // But replace the text with the value entered by user. + bmpText.SetText(text_editor->GetValue()); + + value << bmpText; + return true; +} + +// ---------------------------------------------------------------------------- +// BitmapChoiceRenderer +// ---------------------------------------------------------------------------- + +bool BitmapChoiceRenderer::SetValue(const wxVariant& value) +{ + m_value << value; + return true; +} + +bool BitmapChoiceRenderer::GetValue(wxVariant& value) const +{ + value << m_value; + return true; +} + +bool BitmapChoiceRenderer::Render(wxRect rect, wxDC* dc, int state) +{ + int xoffset = 0; + + const wxBitmap& icon = m_value.GetBitmap(); + if (icon.IsOk()) + { + dc->DrawBitmap(icon, rect.x, rect.y + (rect.height - icon.GetHeight()) / 2); + xoffset = icon.GetWidth() + 4; + + if (rect.height==0) + rect.height= icon.GetHeight(); + } + + RenderText(m_value.GetText(), xoffset, rect, dc, state); + + return true; +} + +wxSize BitmapChoiceRenderer::GetSize() const +{ + wxSize sz = GetTextExtent(m_value.GetText()); + + if (m_value.GetBitmap().IsOk()) + sz.x += m_value.GetBitmap().GetWidth() + 4; + + return sz; +} + + +wxWindow* BitmapChoiceRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelRect, const wxVariant& value) +{ + if (can_create_editor_ctrl && !can_create_editor_ctrl()) + return nullptr; + + std::vector icons = get_extruder_color_icons(); + if (icons.empty()) + return nullptr; + + DataViewBitmapText data; + data << value; + + auto c_editor = new wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, + labelRect.GetTopLeft(), wxSize(labelRect.GetWidth(), -1), + 0, nullptr , wxCB_READONLY); + + int i=0; + for (wxBitmap* bmp : icons) { + if (i==0) { + c_editor->Append(_L("default"), *bmp); + ++i; + } + + c_editor->Append(wxString::Format("%d", i), *bmp); + ++i; + } + c_editor->SetSelection(atoi(data.GetText().c_str())); + + // to avoid event propagation to other sidebar items + c_editor->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) { + evt.StopPropagation(); + // FinishEditing grabs new selection and triggers config update. We better call + // it explicitly, automatic update on KILL_FOCUS didn't work on Linux. + this->FinishEditing(); + }); + + return c_editor; +} + +bool BitmapChoiceRenderer::GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) +{ + wxBitmapComboBox* c = (wxBitmapComboBox*)ctrl; + int selection = c->GetSelection(); + if (selection < 0) + return false; + + DataViewBitmapText bmpText; + + bmpText.SetText(c->GetString(selection)); + bmpText.SetBitmap(c->GetItemBitmap(selection)); + + value << bmpText; + return true; +} + + diff --git a/src/slic3r/GUI/ExtraRenderers.hpp b/src/slic3r/GUI/ExtraRenderers.hpp new file mode 100644 index 000000000..4c1fb09de --- /dev/null +++ b/src/slic3r/GUI/ExtraRenderers.hpp @@ -0,0 +1,160 @@ +#ifndef slic3r_GUI_ExtraRenderers_hpp_ +#define slic3r_GUI_ExtraRenderers_hpp_ + +#include + +#if wxUSE_MARKUP && wxCHECK_VERSION(3, 1, 1) + #define SUPPORTS_MARKUP +#endif + +// ---------------------------------------------------------------------------- +// DataViewBitmapText: helper class used by BitmapTextRenderer +// ---------------------------------------------------------------------------- + +class DataViewBitmapText : public wxObject +{ +public: + DataViewBitmapText( const wxString &text = wxEmptyString, + const wxBitmap& bmp = wxNullBitmap) : + m_text(text), + m_bmp(bmp) + { } + + DataViewBitmapText(const DataViewBitmapText &other) + : wxObject(), + m_text(other.m_text), + m_bmp(other.m_bmp) + { } + + void SetText(const wxString &text) { m_text = text; } + wxString GetText() const { return m_text; } + void SetBitmap(const wxBitmap &bmp) { m_bmp = bmp; } + const wxBitmap &GetBitmap() const { return m_bmp; } + + bool IsSameAs(const DataViewBitmapText& other) const { + return m_text == other.m_text && m_bmp.IsSameAs(other.m_bmp); + } + + bool operator==(const DataViewBitmapText& other) const { + return IsSameAs(other); + } + + bool operator!=(const DataViewBitmapText& other) const { + return !IsSameAs(other); + } + +private: + wxString m_text; + wxBitmap m_bmp; + + wxDECLARE_DYNAMIC_CLASS(DataViewBitmapText); +}; +DECLARE_VARIANT_OBJECT(DataViewBitmapText) + +// ---------------------------------------------------------------------------- +// BitmapTextRenderer +// ---------------------------------------------------------------------------- +#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING +class BitmapTextRenderer : public wxDataViewRenderer +#else +class BitmapTextRenderer : public wxDataViewCustomRenderer +#endif //ENABLE_NONCUSTOM_DATA_VIEW_RENDERING +{ +public: + BitmapTextRenderer(bool use_markup = false, + wxDataViewCellMode mode = +#ifdef __WXOSX__ + wxDATAVIEW_CELL_INERT +#else + wxDATAVIEW_CELL_EDITABLE +#endif + + , int align = wxDVR_DEFAULT_ALIGNMENT +#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING + ); +#else + ) : + wxDataViewCustomRenderer(wxT("DataViewBitmapText"), mode, align) + { + EnableMarkup(use_markup); + } +#endif //ENABLE_NONCUSTOM_DATA_VIEW_RENDERING + + ~BitmapTextRenderer(); + + void EnableMarkup(bool enable = true); + + bool SetValue(const wxVariant& value) override; + bool GetValue(wxVariant& value) const override; +#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING && wxUSE_ACCESSIBILITY + virtual wxString GetAccessibleDescription() const override; +#endif // wxUSE_ACCESSIBILITY && ENABLE_NONCUSTOM_DATA_VIEW_RENDERING + + virtual bool Render(wxRect cell, wxDC* dc, int state) override; + virtual wxSize GetSize() const override; + + bool HasEditorCtrl() const override + { +#ifdef __WXOSX__ + return false; +#else + return true; +#endif + } + wxWindow* CreateEditorCtrl(wxWindow* parent, wxRect labelRect, const wxVariant& value) override; + bool GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) override; + bool WasCanceled() const { return m_was_unusable_symbol; } + + void set_can_create_editor_ctrl_function(std::function can_create_fn) { can_create_editor_ctrl = can_create_fn; } + +private: + DataViewBitmapText m_value; + bool m_was_unusable_symbol{ false }; + + std::function can_create_editor_ctrl { nullptr }; + +#ifdef SUPPORTS_MARKUP + #ifdef wxHAS_GENERIC_DATAVIEWCTRL + class wxItemMarkupText* m_markupText { nullptr };; + #else + bool is_markupText {false}; + #endif +#endif // SUPPORTS_MARKUP +}; + + +// ---------------------------------------------------------------------------- +// BitmapChoiceRenderer +// ---------------------------------------------------------------------------- + +class BitmapChoiceRenderer : public wxDataViewCustomRenderer +{ +public: + BitmapChoiceRenderer(wxDataViewCellMode mode = +#ifdef __WXOSX__ + wxDATAVIEW_CELL_INERT +#else + wxDATAVIEW_CELL_EDITABLE +#endif + , int align = wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL + ) : wxDataViewCustomRenderer(wxT("DataViewBitmapText"), mode, align) {} + + bool SetValue(const wxVariant& value); + bool GetValue(wxVariant& value) const; + + virtual bool Render(wxRect cell, wxDC* dc, int state) override; + virtual wxSize GetSize() const override; + + bool HasEditorCtrl() const override { return true; } + wxWindow* CreateEditorCtrl(wxWindow* parent, wxRect labelRect, const wxVariant& value) override; + bool GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) override; + + void set_can_create_editor_ctrl_function(std::function can_create_fn) { can_create_editor_ctrl = can_create_fn; } + +private: + DataViewBitmapText m_value; + std::function can_create_editor_ctrl { nullptr }; +}; + + +#endif // slic3r_GUI_ExtraRenderers_hpp_ diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 9cb3d726d..a21826205 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -1080,7 +1080,7 @@ boost::any& Choice::get_value() m_value = static_cast(ret_enum); else if (m_opt_id.compare("support_pillar_connection_mode") == 0) m_value = static_cast(ret_enum); - else if (m_opt_id == "authorization_type") + else if (m_opt_id == "printhost_authorization_type") m_value = static_cast(ret_enum); } else if (m_opt.gui_type == "f_enum_open") { diff --git a/src/slic3r/GUI/FirmwareDialog.cpp b/src/slic3r/GUI/FirmwareDialog.cpp index fe7ff4e5d..5441c84ed 100644 --- a/src/slic3r/GUI/FirmwareDialog.cpp +++ b/src/slic3r/GUI/FirmwareDialog.cpp @@ -766,7 +766,7 @@ const char* FirmwareDialog::priv::avr109_dev_name(Avr109Pid usb_pid) { return "Original Prusa CW1"; break; - default: throw std::runtime_error((boost::format("Invalid avr109 device USB PID: %1%") % usb_pid.boot).str()); + default: throw Slic3r::RuntimeError((boost::format("Invalid avr109 device USB PID: %1%") % usb_pid.boot).str()); } } diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 772b290ea..6bb62dbf0 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -129,16 +129,19 @@ void GCodeViewer::TBuffer::reset() { // release gpu memory vertices.reset(); - indices.reset(); + for (IBuffer& buffer : indices) { + buffer.reset(); + } // release cpu memory + indices = std::vector(); paths = std::vector(); render_paths = std::vector(); } -void GCodeViewer::TBuffer::add_path(const GCodeProcessor::MoveVertex& move, unsigned int i_id, unsigned int s_id) +void GCodeViewer::TBuffer::add_path(const GCodeProcessor::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id) { - Path::Endpoint endpoint = { i_id, s_id, move.position }; + Path::Endpoint endpoint = { b_id, i_id, s_id, move.position }; // use rounding to reduce the number of generated paths paths.push_back({ move.type, move.extrusion_role, endpoint, endpoint, move.delta_extruder, round_to_nearest(move.height, 2), round_to_nearest(move.width, 2), move.feedrate, move.fan_speed, @@ -236,12 +239,12 @@ void GCodeViewer::SequentialView::Marker::render() const const std::vector GCodeViewer::Extrusion_Role_Colors {{ { 0.75f, 0.75f, 0.75f }, // erNone - { 1.00f, 0.90f, 0.43f }, // erPerimeter + { 1.00f, 0.90f, 0.30f }, // erPerimeter { 1.00f, 0.49f, 0.22f }, // erExternalPerimeter { 0.12f, 0.12f, 1.00f }, // erOverhangPerimeter { 0.69f, 0.19f, 0.16f }, // erInternalInfill { 0.59f, 0.33f, 0.80f }, // erSolidInfill - { 0.94f, 0.33f, 0.33f }, // erTopSolidInfill + { 0.94f, 0.25f, 0.25f }, // erTopSolidInfill { 1.00f, 0.55f, 0.41f }, // erIroning { 0.30f, 0.50f, 0.73f }, // erBridgeInfill { 1.00f, 1.00f, 1.00f }, // erGapFill @@ -284,8 +287,7 @@ const std::vector GCodeViewer::Range_Colors {{ bool GCodeViewer::init() { - for (size_t i = 0; i < m_buffers.size(); ++i) - { + for (size_t i = 0; i < m_buffers.size(); ++i) { TBuffer& buffer = m_buffers[i]; switch (buffer_type(i)) { @@ -299,18 +301,21 @@ bool GCodeViewer::init() { buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Point; buffer.vertices.format = VBuffer::EFormat::Position; + buffer.shader = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20) ? "options_120" : "options_110"; break; } case EMoveType::Extrude: { buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Triangle; buffer.vertices.format = VBuffer::EFormat::PositionNormal3; + buffer.shader = "gouraud_light"; break; } case EMoveType::Travel: { buffer.render_primitive_type = TBuffer::ERenderPrimitiveType::Line; buffer.vertices.format = VBuffer::EFormat::PositionNormal1; + buffer.shader = "toolpaths_lines"; break; } } @@ -318,7 +323,6 @@ bool GCodeViewer::init() set_toolpath_move_type_visible(EMoveType::Extrude, true); m_sequential_view.marker.init(); - init_shaders(); std::array point_sizes; ::glGetIntegerv(GL_ALIASED_POINT_SIZE_RANGE, point_sizes.data()); @@ -339,7 +343,7 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& reset(); load_toolpaths(gcode_result); - if (wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer) + if (wxGetApp().is_editor()) load_shells(print, initialized); else { Pointfs bed_shape; @@ -392,7 +396,7 @@ void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std: auto start_time = std::chrono::high_resolution_clock::now(); #endif // ENABLE_GCODE_VIEWER_STATISTICS - if (m_vertices_count == 0) + if (m_moves_count == 0) return; wxBusyCursor busy; @@ -406,7 +410,7 @@ void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std: // update ranges for coloring / legend m_extrusions.reset_ranges(); - for (size_t i = 0; i < m_vertices_count; ++i) { + for (size_t i = 0; i < m_moves_count; ++i) { // skip first vertex if (i == 0) continue; @@ -440,11 +444,13 @@ void GCodeViewer::refresh(const GCodeProcessor::Result& gcode_result, const std: // update buffers' render paths refresh_render_paths(false, false); + + log_memory_used("Refreshed G-code extrusion paths, "); } void GCodeViewer::reset() { - m_vertices_count = 0; + m_moves_count = 0; for (TBuffer& buffer : m_buffers) { buffer.reset(); } @@ -559,7 +565,7 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const // the data needed is contained into the Extrude TBuffer const TBuffer& buffer = m_buffers[buffer_id(EMoveType::Extrude)]; - if (buffer.vertices.id == 0 || buffer.indices.id == 0) + if (!buffer.has_data()) return; // collect color information to generate materials @@ -609,10 +615,13 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); // get indices data from index buffer on gpu - std::vector indices = std::vector(buffer.indices.count); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.indices.id)); - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, static_cast(indices.size() * sizeof(unsigned int)), indices.data())); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + MultiIndexBuffer indices; + for (size_t i = 0; i < buffer.indices.size(); ++i) { + indices.push_back(IndexBuffer(buffer.indices[i].count)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.indices[i].id)); + glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, static_cast(indices.back().size() * sizeof(unsigned int)), indices.back().data())); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } auto get_vertex = [&vertices, floats_per_vertex](unsigned int id) { // extract vertex from vector of floats @@ -670,6 +679,7 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const for (size_t i = 0; i < buffer.render_paths.size(); ++i) { // get paths segments from buffer paths const RenderPath& render_path = buffer.render_paths[i]; + const IndexBuffer& ibuffer = indices[render_path.index_buffer_id]; const Path& path = buffer.paths[render_path.path_id]; float half_width = 0.5f * path.width; // clamp height to avoid artifacts due to z-fighting when importing the obj file into blender and similar @@ -685,7 +695,7 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const unsigned int end = start + render_path.sizes[j]; for (size_t k = start; k < end; k += static_cast(indices_per_segment)) { - Segment curr = generate_segment(indices[k + start_vertex_offset], indices[k + end_vertex_offset], half_width, half_height); + Segment curr = generate_segment(ibuffer[k + start_vertex_offset], ibuffer[k + end_vertex_offset], half_width, half_height); if (k == start) { // starting endpoint vertices/normals out_vertices.push_back(curr.v1 + curr.rl_displacement); out_normals.push_back(curr.right); // right @@ -708,13 +718,19 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const out_vertices_count += 2; size_t first_vertex_id = k - static_cast(indices_per_segment); - Segment prev = generate_segment(indices[first_vertex_id + start_vertex_offset], indices[first_vertex_id + end_vertex_offset], half_width, half_height); - Vec3f med_dir = (prev.dir + curr.dir).normalized(); - float disp = half_width * ::tan(::acos(std::clamp(curr.dir.dot(med_dir), -1.0f, 1.0f))); + Segment prev = generate_segment(ibuffer[first_vertex_id + start_vertex_offset], ibuffer[first_vertex_id + end_vertex_offset], half_width, half_height); + float disp = 0.0f; + float cos_dir = prev.dir.dot(curr.dir); + if (cos_dir > -0.9998477f) { + // if the angle between adjacent segments is smaller than 179 degrees + Vec3f med_dir = (prev.dir + curr.dir).normalized(); + disp = half_width * ::tan(::acos(std::clamp(curr.dir.dot(med_dir), -1.0f, 1.0f))); + } + Vec3f disp_vec = disp * prev.dir; bool is_right_turn = prev.up.dot(prev.dir.cross(curr.dir)) <= 0.0f; - if (prev.dir.dot(curr.dir) < 0.7071068f) { + if (cos_dir < 0.7071068f) { // if the angle between two consecutive segments is greater than 45 degrees // we add a cap in the outside corner // and displace the vertices in the inside corner to the same position, if possible @@ -725,7 +741,7 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const out_triangles.push_back({ base_id + 5, base_id + 3, base_id + 2 }); // update right vertices - if (disp < prev.length && disp < curr.length) { + if (disp > 0.0f && disp < prev.length && disp < curr.length) { base_id = out_vertices.size() - 6; out_vertices[base_id + 0] -= disp_vec; out_vertices[base_id + 4] = out_vertices[base_id + 0]; @@ -738,7 +754,7 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const out_triangles.push_back({ base_id + 0, base_id + 3, base_id + 4 }); // update left vertices - if (disp < prev.length && disp < curr.length) { + if (disp > 0.0f && disp < prev.length && disp < curr.length) { base_id = out_vertices.size() - 6; out_vertices[base_id + 2] -= disp_vec; out_vertices[base_id + 5] = out_vertices[base_id + 2]; @@ -832,28 +848,6 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const fclose(fp); } -void GCodeViewer::init_shaders() -{ - unsigned char begin_id = buffer_id(EMoveType::Retract); - unsigned char end_id = buffer_id(EMoveType::Count); - - bool is_glsl_120 = wxGetApp().is_glsl_version_greater_or_equal_to(1, 20); - for (unsigned char i = begin_id; i < end_id; ++i) { - switch (buffer_type(i)) - { - case EMoveType::Tool_change: { m_buffers[i].shader = is_glsl_120 ? "options_120" : "options_110"; break; } - case EMoveType::Color_change: { m_buffers[i].shader = is_glsl_120 ? "options_120" : "options_110"; break; } - case EMoveType::Pause_Print: { m_buffers[i].shader = is_glsl_120 ? "options_120" : "options_110"; break; } - case EMoveType::Custom_GCode: { m_buffers[i].shader = is_glsl_120 ? "options_120" : "options_110"; break; } - case EMoveType::Retract: { m_buffers[i].shader = is_glsl_120 ? "options_120" : "options_110"; break; } - case EMoveType::Unretract: { m_buffers[i].shader = is_glsl_120 ? "options_120" : "options_110"; break; } - case EMoveType::Extrude: { m_buffers[i].shader = "gouraud_light"; break; } - case EMoveType::Travel: { m_buffers[i].shader = "toolpaths_lines"; break; } - default: { break; } - } - } -} - void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) { #if ENABLE_GCODE_VIEWER_STATISTICS @@ -863,13 +857,13 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) #endif // ENABLE_GCODE_VIEWER_STATISTICS // vertices data - m_vertices_count = gcode_result.moves.size(); - if (m_vertices_count == 0) + m_moves_count = gcode_result.moves.size(); + if (m_moves_count == 0) return; - for (size_t i = 0; i < m_vertices_count; ++i) { + for (size_t i = 0; i < m_moves_count; ++i) { const GCodeProcessor::MoveVertex& move = gcode_result.moves[i]; - if (wxGetApp().mainframe->get_mode() == MainFrame::EMode::GCodeViewer) + if (wxGetApp().is_gcode_viewer()) // for the gcode viewer we need all moves to correctly size the printbed m_paths_bounding_box.merge(move.position.cast()); else { @@ -885,271 +879,364 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) m_max_bounding_box = m_paths_bounding_box; m_max_bounding_box.merge(m_paths_bounding_box.max + m_sequential_view.marker.get_bounding_box().size()[2] * Vec3d::UnitZ()); + auto log_memory_usage = [this](const std::string& label, const std::vector>& vertices, const std::vector& indices) { + long long vertices_size = 0; + for (size_t i = 0; i < vertices.size(); ++i) { + vertices_size += SLIC3R_STDVEC_MEMSIZE(vertices[i], float); + } + long long indices_size = 0; + for (size_t i = 0; i < indices.size(); ++i) { + for (size_t j = 0; j < indices[i].size(); ++j) { + indices_size += SLIC3R_STDVEC_MEMSIZE(indices[i][j], unsigned int); + } + } + log_memory_used(label, vertices_size + indices_size); + }; + // format data into the buffers to be rendered as points - auto add_as_point = [](const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, - std::vector& buffer_vertices, std::vector& buffer_indices, size_t move_id) { + auto add_vertices_as_point = [](const GCodeProcessor::MoveVertex& curr, std::vector& buffer_vertices) { for (int j = 0; j < 3; ++j) { buffer_vertices.push_back(curr.position[j]); } - buffer.add_path(curr, static_cast(buffer_indices.size()), static_cast(move_id)); - buffer_indices.push_back(static_cast(buffer_indices.size())); + }; + auto add_indices_as_point = [](const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, + unsigned int index_buffer_id, IndexBuffer& buffer_indices, size_t move_id) { + buffer.add_path(curr, index_buffer_id, buffer_indices.size(), move_id); + buffer_indices.push_back(static_cast(buffer_indices.size())); }; // format data into the buffers to be rendered as lines - auto add_as_line = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, - std::vector& buffer_vertices, std::vector& buffer_indices, size_t move_id) { - // x component of the normal to the current segment (the normal is parallel to the XY plane) - float normal_x = (curr.position - prev.position).normalized()[1]; + auto add_vertices_as_line = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, + TBuffer& buffer, std::vector& buffer_vertices) { + // x component of the normal to the current segment (the normal is parallel to the XY plane) + float normal_x = (curr.position - prev.position).normalized()[1]; - if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { - // add starting vertex position - for (int j = 0; j < 3; ++j) { - buffer_vertices.push_back(prev.position[j]); + auto add_vertex = [&buffer_vertices, normal_x](const GCodeProcessor::MoveVertex& vertex) { + // add position + for (int j = 0; j < 3; ++j) { + buffer_vertices.push_back(vertex.position[j]); + } + // add normal x component + buffer_vertices.push_back(normal_x); + }; + + // add previous vertex + add_vertex(prev); + // add current vertex + add_vertex(curr); + }; + auto add_indices_as_line = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, + unsigned int index_buffer_id, IndexBuffer& buffer_indices, size_t move_id) { + // x component of the normal to the current segment (the normal is parallel to the XY plane) + float normal_x = (curr.position - prev.position).normalized()[1]; + + if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { + // add starting index + buffer_indices.push_back(static_cast(buffer_indices.size())); + buffer.add_path(curr, index_buffer_id, buffer_indices.size() - 1, move_id - 1); + buffer.paths.back().first.position = prev.position; } - // add starting vertex normal x component - buffer_vertices.push_back(normal_x); - // add starting index - buffer_indices.push_back(static_cast(buffer_indices.size())); - buffer.add_path(curr, static_cast(buffer_indices.size() - 1), static_cast(move_id - 1)); - buffer.paths.back().first.position = prev.position; - } - Path& last_path = buffer.paths.back(); - if (last_path.first.i_id != last_path.last.i_id) { - // add previous vertex position - for (int j = 0; j < 3; ++j) { - buffer_vertices.push_back(prev.position[j]); + Path& last_path = buffer.paths.back(); + if (last_path.first.i_id != last_path.last.i_id) { + // add previous index + buffer_indices.push_back(static_cast(buffer_indices.size())); } - // add previous vertex normal x component - buffer_vertices.push_back(normal_x); - // add previous index - buffer_indices.push_back(static_cast(buffer_indices.size())); - } - // add current vertex position - for (int j = 0; j < 3; ++j) { - buffer_vertices.push_back(curr.position[j]); - } - // add current vertex normal x component - buffer_vertices.push_back(normal_x); - // add current index - buffer_indices.push_back(static_cast(buffer_indices.size())); - last_path.last = { static_cast(buffer_indices.size() - 1), static_cast(move_id), curr.position }; + // add current index + buffer_indices.push_back(static_cast(buffer_indices.size())); + last_path.last = { index_buffer_id, buffer_indices.size() - 1, move_id, curr.position }; }; // format data into the buffers to be rendered as solid - auto add_as_solid = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, - std::vector& buffer_vertices, std::vector& buffer_indices, size_t move_id) { - static Vec3f prev_dir; - static Vec3f prev_up; - static float prev_length; - auto store_vertex = [](std::vector& buffer_vertices, const Vec3f& position, const Vec3f& normal) { - // append position - for (int j = 0; j < 3; ++j) { - buffer_vertices.push_back(position[j]); - } - // append normal - for (int j = 0; j < 3; ++j) { - buffer_vertices.push_back(normal[j]); - } - }; - auto store_triangle = [](std::vector& buffer_indices, unsigned int i1, unsigned int i2, unsigned int i3) { - buffer_indices.push_back(i1); - buffer_indices.push_back(i2); - buffer_indices.push_back(i3); - }; - auto extract_position_at = [](const std::vector& vertices, size_t id) { - return Vec3f(vertices[id + 0], vertices[id + 1], vertices[id + 2]); - }; - auto update_position_at = [](std::vector& vertices, size_t id, const Vec3f& position) { - vertices[id + 0] = position[0]; - vertices[id + 1] = position[1]; - vertices[id + 2] = position[2]; - }; - auto append_dummy_cap = [store_triangle](std::vector& buffer_indices, unsigned int id) { - store_triangle(buffer_indices, id, id, id); - store_triangle(buffer_indices, id, id, id); - }; - - if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { - buffer.add_path(curr, static_cast(buffer_indices.size()), static_cast(move_id - 1)); - buffer.paths.back().first.position = prev.position; - } - - unsigned int starting_vertices_size = static_cast(buffer_vertices.size() / buffer.vertices.vertex_size_floats()); - - Vec3f dir = (curr.position - prev.position).normalized(); - Vec3f right = (std::abs(std::abs(dir.dot(Vec3f::UnitZ())) - 1.0f) < EPSILON) ? -Vec3f::UnitY() : Vec3f(dir[1], -dir[0], 0.0f).normalized(); - Vec3f left = -right; - Vec3f up = right.cross(dir); - Vec3f bottom = -up; - - Path& last_path = buffer.paths.back(); - - float half_width = 0.5f * last_path.width; - float half_height = 0.5f * last_path.height; - - Vec3f prev_pos = prev.position - half_height * up; - Vec3f curr_pos = curr.position - half_height * up; - - float length = (curr_pos - prev_pos).norm(); - if (last_path.vertices_count() == 1) { - // 1st segment - - // vertices 1st endpoint - store_vertex(buffer_vertices, prev_pos + half_height * up, up); - store_vertex(buffer_vertices, prev_pos + half_width * right, right); - store_vertex(buffer_vertices, prev_pos + half_height * bottom, bottom); - store_vertex(buffer_vertices, prev_pos + half_width * left, left); - - // vertices 2nd endpoint - store_vertex(buffer_vertices, curr_pos + half_height * up, up); - store_vertex(buffer_vertices, curr_pos + half_width * right, right); - store_vertex(buffer_vertices, curr_pos + half_height * bottom, bottom); - store_vertex(buffer_vertices, curr_pos + half_width * left, left); - - // triangles starting cap - store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 2, starting_vertices_size + 1); - store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 3, starting_vertices_size + 2); - - // dummy triangles outer corner cap - append_dummy_cap(buffer_indices, starting_vertices_size); - - // triangles sides - store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 1, starting_vertices_size + 4); - store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size + 5, starting_vertices_size + 4); - store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size + 2, starting_vertices_size + 5); - store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 6, starting_vertices_size + 5); - store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 3, starting_vertices_size + 6); - store_triangle(buffer_indices, starting_vertices_size + 3, starting_vertices_size + 7, starting_vertices_size + 6); - store_triangle(buffer_indices, starting_vertices_size + 3, starting_vertices_size + 0, starting_vertices_size + 7); - store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 4, starting_vertices_size + 7); - - // triangles ending cap - store_triangle(buffer_indices, starting_vertices_size + 4, starting_vertices_size + 6, starting_vertices_size + 7); - store_triangle(buffer_indices, starting_vertices_size + 4, starting_vertices_size + 5, starting_vertices_size + 6); - } - else { - // any other segment - Vec3f med_dir = (prev_dir + dir).normalized(); - float displacement = half_width * ::tan(::acos(std::clamp(dir.dot(med_dir), -1.0f, 1.0f))); - Vec3f displacement_vec = displacement * prev_dir; - bool can_displace = displacement < prev_length && displacement < length; - - size_t prev_right_id = (starting_vertices_size - 3) * buffer.vertices.vertex_size_floats(); - size_t prev_left_id = (starting_vertices_size - 1) * buffer.vertices.vertex_size_floats(); - Vec3f prev_right_pos = extract_position_at(buffer_vertices, prev_right_id); - Vec3f prev_left_pos = extract_position_at(buffer_vertices, prev_left_id); - - bool is_right_turn = prev_up.dot(prev_dir.cross(dir)) <= 0.0f; - // whether the angle between adjacent segments is greater than 45 degrees - bool is_sharp = prev_dir.dot(dir) < 0.7071068f; - - bool right_displaced = false; - bool left_displaced = false; - - // displace the vertex (inner with respect to the corner) of the previous segment 2nd enpoint, if possible - if (can_displace) { - if (is_right_turn) { - prev_right_pos -= displacement_vec; - update_position_at(buffer_vertices, prev_right_id, prev_right_pos); - right_displaced = true; + auto add_vertices_as_solid = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, + std::vector& buffer_vertices, size_t move_id) { + static Vec3f prev_dir; + static Vec3f prev_up; + static float prev_length; + auto store_vertex = [](std::vector& buffer_vertices, const Vec3f& position, const Vec3f& normal) { + // append position + for (int j = 0; j < 3; ++j) { + buffer_vertices.push_back(position[j]); } - else { - prev_left_pos -= displacement_vec; - update_position_at(buffer_vertices, prev_left_id, prev_left_pos); - left_displaced = true; + // append normal + for (int j = 0; j < 3; ++j) { + buffer_vertices.push_back(normal[j]); } + }; + auto extract_position_at = [](const std::vector& vertices, size_t id) { + return Vec3f(vertices[id + 0], vertices[id + 1], vertices[id + 2]); + }; + auto update_position_at = [](std::vector& vertices, size_t id, const Vec3f& position) { + vertices[id + 0] = position[0]; + vertices[id + 1] = position[1]; + vertices[id + 2] = position[2]; + }; + + if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { + buffer.add_path(curr, 0, 0, move_id - 1); + buffer.paths.back().first.position = prev.position; } - if (!is_sharp) { - // displace the vertex (outer with respect to the corner) of the previous segment 2nd enpoint, if possible + unsigned int starting_vertices_size = static_cast(buffer_vertices.size() / buffer.vertices.vertex_size_floats()); + + Vec3f dir = (curr.position - prev.position).normalized(); + Vec3f right = (std::abs(std::abs(dir.dot(Vec3f::UnitZ())) - 1.0f) < EPSILON) ? -Vec3f::UnitY() : Vec3f(dir[1], -dir[0], 0.0f).normalized(); + Vec3f left = -right; + Vec3f up = right.cross(dir); + Vec3f down = -up; + + Path& last_path = buffer.paths.back(); + + float half_width = 0.5f * last_path.width; + float half_height = 0.5f * last_path.height; + + Vec3f prev_pos = prev.position - half_height * up; + Vec3f curr_pos = curr.position - half_height * up; + + float length = (curr_pos - prev_pos).norm(); + if (last_path.vertices_count() == 1) { + // 1st segment + + // vertices 1st endpoint + store_vertex(buffer_vertices, prev_pos + half_height * up, up); + store_vertex(buffer_vertices, prev_pos + half_width * right, right); + store_vertex(buffer_vertices, prev_pos + half_height * down, down); + store_vertex(buffer_vertices, prev_pos + half_width * left, left); + + // vertices 2nd endpoint + store_vertex(buffer_vertices, curr_pos + half_height * up, up); + store_vertex(buffer_vertices, curr_pos + half_width * right, right); + store_vertex(buffer_vertices, curr_pos + half_height * down, down); + store_vertex(buffer_vertices, curr_pos + half_width * left, left); + } + else { + // any other segment + float displacement = 0.0f; + float cos_dir = prev_dir.dot(dir); + if (cos_dir > -0.9998477f) { + // if the angle between adjacent segments is smaller than 179 degrees + Vec3f med_dir = (prev_dir + dir).normalized(); + displacement = half_width * ::tan(::acos(std::clamp(dir.dot(med_dir), -1.0f, 1.0f))); + } + + Vec3f displacement_vec = displacement * prev_dir; + bool can_displace = displacement > 0.0f && displacement < prev_length&& displacement < length; + + size_t prev_right_id = (starting_vertices_size - 3) * buffer.vertices.vertex_size_floats(); + size_t prev_left_id = (starting_vertices_size - 1) * buffer.vertices.vertex_size_floats(); + Vec3f prev_right_pos = extract_position_at(buffer_vertices, prev_right_id); + Vec3f prev_left_pos = extract_position_at(buffer_vertices, prev_left_id); + + bool is_right_turn = prev_up.dot(prev_dir.cross(dir)) <= 0.0f; + // whether the angle between adjacent segments is greater than 45 degrees + bool is_sharp = cos_dir < 0.7071068f; + + bool right_displaced = false; + bool left_displaced = false; + + // displace the vertex (inner with respect to the corner) of the previous segment 2nd enpoint, if possible if (can_displace) { if (is_right_turn) { - prev_left_pos += displacement_vec; - update_position_at(buffer_vertices, prev_left_id, prev_left_pos); - left_displaced = true; - } - else { - prev_right_pos += displacement_vec; + prev_right_pos -= displacement_vec; update_position_at(buffer_vertices, prev_right_id, prev_right_pos); right_displaced = true; } + else { + prev_left_pos -= displacement_vec; + update_position_at(buffer_vertices, prev_left_id, prev_left_pos); + left_displaced = true; + } } - // vertices 1st endpoint (top and bottom are from previous segment 2nd endpoint) - // vertices position matches that of the previous segment 2nd endpoint, if displaced - store_vertex(buffer_vertices, right_displaced ? prev_right_pos : prev_pos + half_width * right, right); - store_vertex(buffer_vertices, left_displaced ? prev_left_pos : prev_pos + half_width * left, left); - } - else { - // vertices 1st endpoint (top and bottom are from previous segment 2nd endpoint) - // the inner corner vertex position matches that of the previous segment 2nd endpoint, if displaced - if (is_right_turn) { + if (!is_sharp) { + // displace the vertex (outer with respect to the corner) of the previous segment 2nd enpoint, if possible + if (can_displace) { + if (is_right_turn) { + prev_left_pos += displacement_vec; + update_position_at(buffer_vertices, prev_left_id, prev_left_pos); + left_displaced = true; + } + else { + prev_right_pos += displacement_vec; + update_position_at(buffer_vertices, prev_right_id, prev_right_pos); + right_displaced = true; + } + } + + // vertices 1st endpoint (top and bottom are from previous segment 2nd endpoint) + // vertices position matches that of the previous segment 2nd endpoint, if displaced store_vertex(buffer_vertices, right_displaced ? prev_right_pos : prev_pos + half_width * right, right); - store_vertex(buffer_vertices, prev_pos + half_width * left, left); - } - else { - store_vertex(buffer_vertices, prev_pos + half_width * right, right); store_vertex(buffer_vertices, left_displaced ? prev_left_pos : prev_pos + half_width * left, left); } + else { + // vertices 1st endpoint (top and bottom are from previous segment 2nd endpoint) + // the inner corner vertex position matches that of the previous segment 2nd endpoint, if displaced + if (is_right_turn) { + store_vertex(buffer_vertices, right_displaced ? prev_right_pos : prev_pos + half_width * right, right); + store_vertex(buffer_vertices, prev_pos + half_width * left, left); + } + else { + store_vertex(buffer_vertices, prev_pos + half_width * right, right); + store_vertex(buffer_vertices, left_displaced ? prev_left_pos : prev_pos + half_width * left, left); + } + } + + // vertices 2nd endpoint + store_vertex(buffer_vertices, curr_pos + half_height * up, up); + store_vertex(buffer_vertices, curr_pos + half_width * right, right); + store_vertex(buffer_vertices, curr_pos + half_height * down, down); + store_vertex(buffer_vertices, curr_pos + half_width * left, left); } - // vertices 2nd endpoint - store_vertex(buffer_vertices, curr_pos + half_height * up, up); - store_vertex(buffer_vertices, curr_pos + half_width * right, right); - store_vertex(buffer_vertices, curr_pos + half_height * bottom, bottom); - store_vertex(buffer_vertices, curr_pos + half_width * left, left); + last_path.last = { 0, 0, move_id, curr.position }; + prev_dir = dir; + prev_up = up; + prev_length = length; + }; + auto add_indices_as_solid = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, + size_t& buffer_vertices_size, unsigned int index_buffer_id, IndexBuffer& buffer_indices, size_t move_id) { + static Vec3f prev_dir; + static Vec3f prev_up; + static float prev_length; + auto store_triangle = [](IndexBuffer& buffer_indices, unsigned int i1, unsigned int i2, unsigned int i3) { + buffer_indices.push_back(i1); + buffer_indices.push_back(i2); + buffer_indices.push_back(i3); + }; + auto append_dummy_cap = [store_triangle](IndexBuffer& buffer_indices, unsigned int id) { + store_triangle(buffer_indices, id, id, id); + store_triangle(buffer_indices, id, id, id); + }; - // triangles starting cap - store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size - 2, starting_vertices_size + 0); - store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size + 1, starting_vertices_size - 2); + if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { + buffer.add_path(curr, index_buffer_id, buffer_indices.size(), move_id - 1); + buffer.paths.back().first.position = prev.position; + } - // triangles outer corner cap - if (is_right_turn) { - if (left_displaced) - // dummy triangles - append_dummy_cap(buffer_indices, starting_vertices_size); - else { - store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size + 1, starting_vertices_size - 1); - store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size - 2, starting_vertices_size - 1); - } + unsigned int starting_vertices_size = static_cast(buffer_vertices_size); + + Vec3f dir = (curr.position - prev.position).normalized(); + Vec3f right = (std::abs(std::abs(dir.dot(Vec3f::UnitZ())) - 1.0f) < EPSILON) ? -Vec3f::UnitY() : Vec3f(dir[1], -dir[0], 0.0f).normalized(); + Vec3f up = right.cross(dir); + + Path& last_path = buffer.paths.back(); + + float half_width = 0.5f * last_path.width; + float half_height = 0.5f * last_path.height; + + Vec3f prev_pos = prev.position - half_height * up; + Vec3f curr_pos = curr.position - half_height * up; + + float length = (curr_pos - prev_pos).norm(); + if (last_path.vertices_count() == 1) { + // 1st segment + buffer_vertices_size += 8; + + // triangles starting cap + store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 2, starting_vertices_size + 1); + store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 3, starting_vertices_size + 2); + + // dummy triangles outer corner cap + append_dummy_cap(buffer_indices, starting_vertices_size); + + // triangles sides + store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 1, starting_vertices_size + 4); + store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size + 5, starting_vertices_size + 4); + store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size + 2, starting_vertices_size + 5); + store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 6, starting_vertices_size + 5); + store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 3, starting_vertices_size + 6); + store_triangle(buffer_indices, starting_vertices_size + 3, starting_vertices_size + 7, starting_vertices_size + 6); + store_triangle(buffer_indices, starting_vertices_size + 3, starting_vertices_size + 0, starting_vertices_size + 7); + store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 4, starting_vertices_size + 7); + + // triangles ending cap + store_triangle(buffer_indices, starting_vertices_size + 4, starting_vertices_size + 6, starting_vertices_size + 7); + store_triangle(buffer_indices, starting_vertices_size + 4, starting_vertices_size + 5, starting_vertices_size + 6); } else { - if (right_displaced) - // dummy triangles - append_dummy_cap(buffer_indices, starting_vertices_size); - else { - store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size - 3, starting_vertices_size + 0); - store_triangle(buffer_indices, starting_vertices_size - 3, starting_vertices_size - 2, starting_vertices_size + 0); + // any other segment + float displacement = 0.0f; + float cos_dir = prev_dir.dot(dir); + if (cos_dir > -0.9998477f) { + // if the angle between adjacent segments is smaller than 179 degrees + Vec3f med_dir = (prev_dir + dir).normalized(); + displacement = half_width * ::tan(::acos(std::clamp(dir.dot(med_dir), -1.0f, 1.0f))); } + + Vec3f displacement_vec = displacement * prev_dir; + bool can_displace = displacement > 0.0f && displacement < prev_length && displacement < length; + + bool is_right_turn = prev_up.dot(prev_dir.cross(dir)) <= 0.0f; + // whether the angle between adjacent segments is greater than 45 degrees + bool is_sharp = cos_dir < 0.7071068f; + + bool right_displaced = false; + bool left_displaced = false; + + if (!is_sharp) { + if (can_displace) { + if (is_right_turn) + left_displaced = true; + else + right_displaced = true; + } + } + + buffer_vertices_size += 6; + + // triangles starting cap + store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size - 2, starting_vertices_size + 0); + store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size + 1, starting_vertices_size - 2); + + // triangles outer corner cap + if (is_right_turn) { + if (left_displaced) + // dummy triangles + append_dummy_cap(buffer_indices, starting_vertices_size); + else { + store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size + 1, starting_vertices_size - 1); + store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size - 2, starting_vertices_size - 1); + } + } + else { + if (right_displaced) + // dummy triangles + append_dummy_cap(buffer_indices, starting_vertices_size); + else { + store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size - 3, starting_vertices_size + 0); + store_triangle(buffer_indices, starting_vertices_size - 3, starting_vertices_size - 2, starting_vertices_size + 0); + } + } + + // triangles sides + store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size + 0, starting_vertices_size + 2); + store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 3, starting_vertices_size + 2); + store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size - 2, starting_vertices_size + 3); + store_triangle(buffer_indices, starting_vertices_size - 2, starting_vertices_size + 4, starting_vertices_size + 3); + store_triangle(buffer_indices, starting_vertices_size - 2, starting_vertices_size + 1, starting_vertices_size + 4); + store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size + 5, starting_vertices_size + 4); + store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size - 4, starting_vertices_size + 5); + store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size + 2, starting_vertices_size + 5); + + // triangles ending cap + store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 4, starting_vertices_size + 5); + store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 3, starting_vertices_size + 4); } - // triangles sides - store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size + 0, starting_vertices_size + 2); - store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size + 3, starting_vertices_size + 2); - store_triangle(buffer_indices, starting_vertices_size + 0, starting_vertices_size - 2, starting_vertices_size + 3); - store_triangle(buffer_indices, starting_vertices_size - 2, starting_vertices_size + 4, starting_vertices_size + 3); - store_triangle(buffer_indices, starting_vertices_size - 2, starting_vertices_size + 1, starting_vertices_size + 4); - store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size + 5, starting_vertices_size + 4); - store_triangle(buffer_indices, starting_vertices_size + 1, starting_vertices_size - 4, starting_vertices_size + 5); - store_triangle(buffer_indices, starting_vertices_size - 4, starting_vertices_size + 2, starting_vertices_size + 5); - - // triangles ending cap - store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 4, starting_vertices_size + 5); - store_triangle(buffer_indices, starting_vertices_size + 2, starting_vertices_size + 3, starting_vertices_size + 4); - } - - last_path.last = { static_cast(buffer_indices.size() - 1), static_cast(move_id), curr.position }; - prev_dir = dir; - prev_up = up; - prev_length = length; + last_path.last = { index_buffer_id, buffer_indices.size() - 1, move_id, curr.position }; + prev_dir = dir; + prev_up = up; + prev_length = length; }; - // toolpaths data -> extract from result + // to reduce the peak in memory usage, we split the generation of the vertex and index buffers in two steps. + // the data are deleted as soon as they are sent to the gpu. std::vector> vertices(m_buffers.size()); - std::vector> indices(m_buffers.size()); - for (size_t i = 0; i < m_vertices_count; ++i) { + std::vector indices(m_buffers.size()); + + // toolpaths data -> extract vertices from result + for (size_t i = 0; i < m_moves_count; ++i) { // skip first vertex if (i == 0) continue; @@ -1160,62 +1247,142 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) unsigned char id = buffer_id(curr.type); TBuffer& buffer = m_buffers[id]; std::vector& buffer_vertices = vertices[id]; - std::vector& buffer_indices = indices[id]; - switch (curr.type) + switch (buffer.render_primitive_type) { - case EMoveType::Tool_change: - case EMoveType::Color_change: - case EMoveType::Pause_Print: - case EMoveType::Custom_GCode: - case EMoveType::Retract: - case EMoveType::Unretract: + case TBuffer::ERenderPrimitiveType::Point: { - add_as_point(curr, buffer, buffer_vertices, buffer_indices, i); + add_vertices_as_point(curr, buffer_vertices); break; } - case EMoveType::Extrude: + case TBuffer::ERenderPrimitiveType::Line: { - add_as_solid(prev, curr, buffer, buffer_vertices, buffer_indices, i); + add_vertices_as_line(prev, curr, buffer, buffer_vertices); break; } - case EMoveType::Travel: + case TBuffer::ERenderPrimitiveType::Triangle: { - add_as_line(prev, curr, buffer, buffer_vertices, buffer_indices, i); + add_vertices_as_solid(prev, curr, buffer, buffer_vertices, i); break; } - default: { break; } } } - // toolpaths data -> send data to gpu + log_memory_usage("Loaded G-code generated vertex buffers, ", vertices, indices); + + // toolpaths data -> send vertices data to gpu for (size_t i = 0; i < m_buffers.size(); ++i) { TBuffer& buffer = m_buffers[i]; - // vertices const std::vector& buffer_vertices = vertices[i]; buffer.vertices.count = buffer_vertices.size() / buffer.vertices.vertex_size_floats(); #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.vertices_gpu_size += buffer_vertices.size() * sizeof(float); + m_statistics.max_vertices_in_vertex_buffer = std::max(m_statistics.max_vertices_in_vertex_buffer, static_cast(buffer.vertices.count)); #endif // ENABLE_GCODE_VIEWER_STATISTICS glsafe(::glGenBuffers(1, &buffer.vertices.id)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, buffer.vertices.id)); glsafe(::glBufferData(GL_ARRAY_BUFFER, buffer_vertices.size() * sizeof(float), buffer_vertices.data(), GL_STATIC_DRAW)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + } - // indices - const std::vector& buffer_indices = indices[i]; - buffer.indices.count = buffer_indices.size(); + // dismiss vertices data, no more needed + std::vector>().swap(vertices); + + // toolpaths data -> extract indices from result + // ensure that at least one index buffer is defined for each multibuffer + for (auto i : indices) { + i.push_back(IndexBuffer()); + } + // paths may have been filled while extracting vertices, + // so reset them, they will be filled again while extracting indices + for (TBuffer& buffer : m_buffers) { + buffer.paths.clear(); + } + // variable used to keep track of the current size (in vertices) of the vertex buffer + size_t curr_buffer_vertices_size = 0; + for (size_t i = 0; i < m_moves_count; ++i) { + // skip first vertex + if (i == 0) + continue; + + const GCodeProcessor::MoveVertex& prev = gcode_result.moves[i - 1]; + const GCodeProcessor::MoveVertex& curr = gcode_result.moves[i]; + + unsigned char id = buffer_id(curr.type); + TBuffer& buffer = m_buffers[id]; + MultiIndexBuffer& buffer_indices = indices[id]; + if (buffer_indices.empty()) + buffer_indices.push_back(IndexBuffer()); + + static const size_t THRESHOLD = 1024 * 1024 * 128; + // if adding the indices for the current segment exceeds the threshold size of the current index buffer + // create another index buffer, and move the current path indices into it + if (buffer_indices.back().size() >= THRESHOLD - static_cast(buffer.indices_per_segment())) { + buffer_indices.push_back(IndexBuffer()); + if (curr.type == EMoveType::Extrude || curr.type == EMoveType::Travel) { + if (!(prev.type != curr.type || !buffer.paths.back().matches(curr))) { + Path& last_path = buffer.paths.back(); + size_t delta_id = last_path.last.i_id - last_path.first.i_id; + + // move indices of the last path from the previous into the new index buffer + IndexBuffer& src_buffer = buffer_indices[buffer_indices.size() - 2]; + IndexBuffer& dst_buffer = buffer_indices[buffer_indices.size() - 1]; + std::move(src_buffer.begin() + last_path.first.i_id, src_buffer.end(), std::back_inserter(dst_buffer)); + src_buffer.erase(src_buffer.begin() + last_path.first.i_id, src_buffer.end()); + + // updates path indices + last_path.first.b_id = buffer_indices.size() - 1; + last_path.first.i_id = 0; + last_path.last.b_id = buffer_indices.size() - 1; + last_path.last.i_id = delta_id; + } + } + } + + switch (buffer.render_primitive_type) + { + case TBuffer::ERenderPrimitiveType::Point: + { + add_indices_as_point(curr, buffer, static_cast(buffer_indices.size()) - 1, buffer_indices.back(), i); + break; + } + case TBuffer::ERenderPrimitiveType::Line: + { + add_indices_as_line(prev, curr, buffer, static_cast(buffer_indices.size()) - 1, buffer_indices.back(), i); + break; + } + case TBuffer::ERenderPrimitiveType::Triangle: + { + add_indices_as_solid(prev, curr, buffer, curr_buffer_vertices_size, static_cast(buffer_indices.size()) - 1, buffer_indices.back(), i); + break; + } + } + } + + log_memory_usage("Loaded G-code generated indices buffers, ", vertices, indices); + + // toolpaths data -> send indices data to gpu + for (size_t i = 0; i < m_buffers.size(); ++i) { + TBuffer& buffer = m_buffers[i]; + + for (size_t j = 0; j < indices[i].size(); ++j) { + const IndexBuffer& buffer_indices = indices[i][j]; + buffer.indices.push_back(IBuffer()); + IBuffer& ibuffer = buffer.indices.back(); + ibuffer.count = buffer_indices.size(); #if ENABLE_GCODE_VIEWER_STATISTICS - m_statistics.indices_gpu_size += buffer.indices.count * sizeof(unsigned int); + m_statistics.indices_gpu_size += ibuffer.count * sizeof(unsigned int); + m_statistics.max_indices_in_index_buffer = std::max(m_statistics.max_indices_in_index_buffer, static_cast(ibuffer.count)); #endif // ENABLE_GCODE_VIEWER_STATISTICS - if (buffer.indices.count > 0) { - glsafe(::glGenBuffers(1, &buffer.indices.id)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.indices.id)); - glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, buffer.indices.count * sizeof(unsigned int), buffer_indices.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + if (ibuffer.count > 0) { + glsafe(::glGenBuffers(1, &ibuffer.id)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibuffer.id)); + glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, buffer_indices.size() * sizeof(unsigned int), buffer_indices.data(), GL_STATIC_DRAW)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } } } @@ -1224,13 +1391,22 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) m_statistics.paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.paths, Path); } unsigned int travel_buffer_id = buffer_id(EMoveType::Travel); - m_statistics.travel_segments_count = indices[travel_buffer_id].size() / m_buffers[travel_buffer_id].indices_per_segment(); + const MultiIndexBuffer& travel_buffer_indices = indices[travel_buffer_id]; + for (size_t i = 0; i < travel_buffer_indices.size(); ++i) { + m_statistics.travel_segments_count = travel_buffer_indices[i].size() / m_buffers[travel_buffer_id].indices_per_segment(); + } unsigned int extrude_buffer_id = buffer_id(EMoveType::Extrude); - m_statistics.extrude_segments_count = indices[extrude_buffer_id].size() / m_buffers[extrude_buffer_id].indices_per_segment(); + const MultiIndexBuffer& extrude_buffer_indices = indices[extrude_buffer_id]; + for (size_t i = 0; i < extrude_buffer_indices.size(); ++i) { + m_statistics.extrude_segments_count = extrude_buffer_indices[i].size() / m_buffers[extrude_buffer_id].indices_per_segment(); + } #endif // ENABLE_GCODE_VIEWER_STATISTICS + // dismiss indices data, no more needed + std::vector().swap(indices); + // layers zs / roles / extruder ids / cp color ids -> extract from result - for (size_t i = 0; i < m_vertices_count; ++i) { + for (size_t i = 0; i < m_moves_count; ++i) { const GCodeProcessor::MoveVertex& move = gcode_result.moves[i]; if (move.type == EMoveType::Extrude) m_layers_zs.emplace_back(static_cast(move.position[2])); @@ -1252,8 +1428,10 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) m_layers_zs[k++] = (j > i + 1) ? (0.5 * (m_layers_zs[i] + m_layers_zs[j - 1])) : m_layers_zs[i]; i = j; } - if (k < n) + if (k < n) { m_layers_zs.erase(m_layers_zs.begin() + k, m_layers_zs.end()); + m_layers_zs.shrink_to_fit(); + } // set layers z range m_layers_z_range = { m_layers_zs.front(), m_layers_zs.back() }; @@ -1261,10 +1439,14 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) // roles -> remove duplicates std::sort(m_roles.begin(), m_roles.end()); m_roles.erase(std::unique(m_roles.begin(), m_roles.end()), m_roles.end()); + m_roles.shrink_to_fit(); // extruder ids -> remove duplicates std::sort(m_extruder_ids.begin(), m_extruder_ids.end()); m_extruder_ids.erase(std::unique(m_extruder_ids.begin(), m_extruder_ids.end()), m_extruder_ids.end()); + m_extruder_ids.shrink_to_fit(); + + log_memory_usage("Loaded G-code generated extrusion paths, ", vertices, indices); #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.load_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); @@ -1354,7 +1536,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool auto travel_color = [this](const Path& path) { return (path.delta_extruder < 0.0f) ? Travel_Colors[2] /* Retract */ : - ((path.delta_extruder > 0.0f) ? Travel_Colors[1] /* Extrude */ : + ((path.delta_extruder > 0.0f) ? Travel_Colors[1] /* Extrude */ : Travel_Colors[0] /* Move */); }; @@ -1362,15 +1544,15 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool m_statistics.render_paths_size = 0; #endif // ENABLE_GCODE_VIEWER_STATISTICS - m_sequential_view.endpoints.first = m_vertices_count; + m_sequential_view.endpoints.first = m_moves_count; m_sequential_view.endpoints.last = 0; if (!keep_sequential_current_first) m_sequential_view.current.first = 0; if (!keep_sequential_current_last) - m_sequential_view.current.last = m_vertices_count; + m_sequential_view.current.last = m_moves_count; // first pass: collect visible paths and update sequential view data - std::vector> paths; + std::vector> paths; for (TBuffer& buffer : m_buffers) { // reset render paths buffer.render_paths.clear(); @@ -1391,7 +1573,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool continue; // store valid path - paths.push_back({ &buffer, i }); + paths.push_back({ &buffer, path.first.b_id, static_cast(i) }); m_sequential_view.endpoints.first = std::min(m_sequential_view.endpoints.first, path.first.s_id); m_sequential_view.endpoints.last = std::max(m_sequential_view.endpoints.last, path.last.s_id); @@ -1408,7 +1590,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool // searches the path containing the current position for (const Path& path : buffer.paths) { if (path.contains(m_sequential_view.current.last)) { - unsigned int offset = m_sequential_view.current.last - path.first.s_id; + unsigned int offset = static_cast(m_sequential_view.current.last - path.first.s_id); if (offset > 0) { if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Line) offset = 2 * offset - 1; @@ -1417,11 +1599,11 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool offset = indices_count * (offset - 1) + (indices_count - 6); } } - offset += path.first.i_id; + offset += static_cast(path.first.i_id); // gets the index from the index buffer on gpu unsigned int index = 0; - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.indices.id)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.indices[path.first.b_id].id)); glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast(offset * sizeof(unsigned int)), static_cast(sizeof(unsigned int)), static_cast(&index))); glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); @@ -1438,8 +1620,8 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool } // second pass: filter paths by sequential data and collect them by color - for (const auto& [buffer, id] : paths) { - const Path& path = buffer->paths[id]; + for (const auto& [buffer, index_buffer_id, path_id] : paths) { + const Path& path = buffer->paths[path_id]; if (m_sequential_view.current.last <= path.first.s_id || path.last.s_id <= m_sequential_view.current.first) continue; @@ -1451,20 +1633,23 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool default: { color = { 0.0f, 0.0f, 0.0f }; break; } } - auto it = std::find_if(buffer->render_paths.begin(), buffer->render_paths.end(), [color](const RenderPath& path) { return path.color == color; }); + unsigned int ibuffer_id = index_buffer_id; + auto it = std::find_if(buffer->render_paths.begin(), buffer->render_paths.end(), + [color, ibuffer_id](const RenderPath& path) { return path.index_buffer_id == ibuffer_id && path.color == color; }); if (it == buffer->render_paths.end()) { it = buffer->render_paths.insert(buffer->render_paths.end(), RenderPath()); it->color = color; - it->path_id = id; + it->path_id = path_id; + it->index_buffer_id = index_buffer_id; } - unsigned int size_in_vertices = std::min(m_sequential_view.current.last, path.last.s_id) - std::max(m_sequential_view.current.first, path.first.s_id) + 1; + unsigned int segments_count = std::min(m_sequential_view.current.last, path.last.s_id) - std::max(m_sequential_view.current.first, path.first.s_id) + 1; unsigned int size_in_indices = 0; switch (buffer->render_primitive_type) { - case TBuffer::ERenderPrimitiveType::Point: { size_in_indices = size_in_vertices; break; } + case TBuffer::ERenderPrimitiveType::Point: { size_in_indices = segments_count; break; } case TBuffer::ERenderPrimitiveType::Line: - case TBuffer::ERenderPrimitiveType::Triangle: { size_in_indices = buffer->indices_per_segment() * (size_in_vertices - 1); break; } + case TBuffer::ERenderPrimitiveType::Triangle: { size_in_indices = buffer->indices_per_segment() * (segments_count - 1); break; } } it->sizes.push_back(size_in_indices); @@ -1505,7 +1690,8 @@ void GCodeViewer::render_toolpaths() const shader.set_uniform("uniform_color", color4); }; - auto render_as_points = [this, zoom, point_size, near_plane_height, set_uniform_color](const TBuffer& buffer, EOptionsColors color_id, GLShaderProgram& shader) { + auto render_as_points = [this, zoom, point_size, near_plane_height, set_uniform_color] + (const TBuffer& buffer, unsigned int index_buffer_id, EOptionsColors color_id, GLShaderProgram& shader) { set_uniform_color(Options_Colors[static_cast(color_id)], shader); shader.set_uniform("zoom", zoom); shader.set_uniform("percent_outline_radius", 0.0f); @@ -1517,34 +1703,40 @@ void GCodeViewer::render_toolpaths() const glsafe(::glEnable(GL_POINT_SPRITE)); for (const RenderPath& path : buffer.render_paths) { - glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); + if (path.index_buffer_id == index_buffer_id) { + glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); #if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_multi_points_calls_count; + ++m_statistics.gl_multi_points_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS + } } glsafe(::glDisable(GL_POINT_SPRITE)); glsafe(::glDisable(GL_VERTEX_PROGRAM_POINT_SIZE)); }; - auto render_as_lines = [this, light_intensity, set_uniform_color](const TBuffer& buffer, GLShaderProgram& shader) { + auto render_as_lines = [this, light_intensity, set_uniform_color](const TBuffer& buffer, unsigned int index_buffer_id, GLShaderProgram& shader) { shader.set_uniform("light_intensity", light_intensity); for (const RenderPath& path : buffer.render_paths) { - set_uniform_color(path.color, shader); - glsafe(::glMultiDrawElements(GL_LINES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); + if (path.index_buffer_id == index_buffer_id) { + set_uniform_color(path.color, shader); + glsafe(::glMultiDrawElements(GL_LINES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); #if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_multi_lines_calls_count; + ++m_statistics.gl_multi_lines_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS + } } }; - auto render_as_triangles = [this, set_uniform_color](const TBuffer& buffer, GLShaderProgram& shader) { + auto render_as_triangles = [this, set_uniform_color](const TBuffer& buffer, unsigned int index_buffer_id, GLShaderProgram& shader) { for (const RenderPath& path : buffer.render_paths) { - set_uniform_color(path.color, shader); - glsafe(::glMultiDrawElements(GL_TRIANGLES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); + if (path.index_buffer_id == index_buffer_id) { + set_uniform_color(path.color, shader); + glsafe(::glMultiDrawElements(GL_TRIANGLES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); #if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_multi_triangles_calls_count; + ++m_statistics.gl_multi_triangles_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS + } } }; @@ -1559,10 +1751,7 @@ void GCodeViewer::render_toolpaths() const for (unsigned char i = begin_id; i < end_id; ++i) { const TBuffer& buffer = m_buffers[i]; - if (!buffer.visible) - continue; - - if (buffer.vertices.id == 0 || buffer.indices.id == 0) + if (!buffer.visible || !buffer.has_data()) continue; GLShaderProgram* shader = wxGetApp().get_shader(buffer.shader.c_str()); @@ -1578,38 +1767,40 @@ void GCodeViewer::render_toolpaths() const glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); } - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.indices.id)); + for (size_t j = 0; j < buffer.indices.size(); ++j) { + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.indices[j].id)); - switch (buffer.render_primitive_type) - { - case TBuffer::ERenderPrimitiveType::Point: - { - EOptionsColors color; - switch (buffer_type(i)) + switch (buffer.render_primitive_type) { - case EMoveType::Tool_change: { color = EOptionsColors::ToolChanges; break; } - case EMoveType::Color_change: { color = EOptionsColors::ColorChanges; break; } - case EMoveType::Pause_Print: { color = EOptionsColors::PausePrints; break; } - case EMoveType::Custom_GCode: { color = EOptionsColors::CustomGCodes; break; } - case EMoveType::Retract: { color = EOptionsColors::Retractions; break; } - case EMoveType::Unretract: { color = EOptionsColors::Unretractions; break; } + case TBuffer::ERenderPrimitiveType::Point: + { + EOptionsColors color; + switch (buffer_type(i)) + { + case EMoveType::Tool_change: { color = EOptionsColors::ToolChanges; break; } + case EMoveType::Color_change: { color = EOptionsColors::ColorChanges; break; } + case EMoveType::Pause_Print: { color = EOptionsColors::PausePrints; break; } + case EMoveType::Custom_GCode: { color = EOptionsColors::CustomGCodes; break; } + case EMoveType::Retract: { color = EOptionsColors::Retractions; break; } + case EMoveType::Unretract: { color = EOptionsColors::Unretractions; break; } + } + render_as_points(buffer, static_cast(j), color, *shader); + break; + } + case TBuffer::ERenderPrimitiveType::Line: + { + render_as_lines(buffer, static_cast(j), *shader); + break; + } + case TBuffer::ERenderPrimitiveType::Triangle: + { + render_as_triangles(buffer, static_cast(j), *shader); + break; + } } - render_as_points(buffer, color, *shader); - break; - } - case TBuffer::ERenderPrimitiveType::Line: - { - render_as_lines(buffer, *shader); - break; - } - case TBuffer::ERenderPrimitiveType::Triangle: - { - render_as_triangles(buffer, *shader); - break; - } - } - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + } if (has_normals) glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); @@ -1672,90 +1863,90 @@ void GCodeViewer::render_legend() const std::function callback = nullptr) { if (!visible) ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); - ImVec2 pos = ImGui::GetCursorScreenPos(); - switch (type) - { - default: - case EItemType::Rect: - { - draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, - ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f })); - break; - } - case EItemType::Circle: - { - ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); - if (m_buffers[buffer_id(EMoveType::Retract)].shader == "options_120") { - draw_list->AddCircleFilled(center, 0.5f * icon_size, - ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); - float radius = 0.5f * icon_size; - draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); - radius = 0.5f * icon_size * 0.01f * 33.0f; - draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); + ImVec2 pos = ImGui::GetCursorScreenPos(); + switch (type) + { + default: + case EItemType::Rect: + { + draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, + ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f })); + break; } - else - draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); + case EItemType::Circle: + { + ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); + if (m_buffers[buffer_id(EMoveType::Retract)].shader == "options_120") { + draw_list->AddCircleFilled(center, 0.5f * icon_size, + ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); + float radius = 0.5f * icon_size; + draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); + radius = 0.5f * icon_size * 0.01f * 33.0f; + draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); + } + else + draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); - break; - } - case EItemType::Hexagon: - { - ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); - draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 6); - break; - } - case EItemType::Line: - { - draw_list->AddLine({ pos.x + 1, pos.y + icon_size - 1}, { pos.x + icon_size - 1, pos.y + 1 }, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 3.0f); - break; - } - } + break; + } + case EItemType::Hexagon: + { + ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); + draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 6); + break; + } + case EItemType::Line: + { + draw_list->AddLine({ pos.x + 1, pos.y + icon_size - 1 }, { pos.x + icon_size - 1, pos.y + 1 }, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 3.0f); + break; + } + } - // draw text - ImGui::Dummy({ icon_size, icon_size }); - ImGui::SameLine(); - if (callback != nullptr) { - if (ImGui::MenuItem(label.c_str())) - callback(); - else { - // show tooltip - if (ImGui::IsItemHovered()) { - if (!visible) - ImGui::PopStyleVar(); - ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); - ImGui::BeginTooltip(); - imgui.text(visible ? _u8L("Click to hide") : _u8L("Click to show")); - ImGui::EndTooltip(); - ImGui::PopStyleColor(); - if (!visible) - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); + // draw text + ImGui::Dummy({ icon_size, icon_size }); + ImGui::SameLine(); + if (callback != nullptr) { + if (ImGui::MenuItem(label.c_str())) + callback(); + else { + // show tooltip + if (ImGui::IsItemHovered()) { + if (!visible) + ImGui::PopStyleVar(); + ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); + ImGui::BeginTooltip(); + imgui.text(visible ? _u8L("Click to hide") : _u8L("Click to show")); + ImGui::EndTooltip(); + ImGui::PopStyleColor(); + if (!visible) + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); - // to avoid the tooltip to change size when moving the mouse - wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); - wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); + // to avoid the tooltip to change size when moving the mouse + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); + } + } + + if (!time.empty()) { + ImGui::SameLine(offsets[0]); + imgui.text(time); + ImGui::SameLine(offsets[1]); + pos = ImGui::GetCursorScreenPos(); + float width = std::max(1.0f, percent_bar_size * percent / max_percent); + draw_list->AddRectFilled({ pos.x, pos.y + 2.0f }, { pos.x + width, pos.y + icon_size - 2.0f }, + ImGui::GetColorU32(ImGuiWrapper::COL_ORANGE_LIGHT)); + ImGui::Dummy({ percent_bar_size, icon_size }); + ImGui::SameLine(); + char buf[64]; + ::sprintf(buf, "%.1f%%", 100.0f * percent); + ImGui::TextUnformatted((percent > 0.0f) ? buf : ""); } } + else + imgui.text(label); - if (!time.empty()) { - ImGui::SameLine(offsets[0]); - imgui.text(time); - ImGui::SameLine(offsets[1]); - pos = ImGui::GetCursorScreenPos(); - float width = std::max(1.0f, percent_bar_size * percent / max_percent); - draw_list->AddRectFilled({ pos.x, pos.y + 2.0f }, { pos.x + width, pos.y + icon_size - 2.0f }, - ImGui::GetColorU32(ImGuiWrapper::COL_ORANGE_LIGHT)); - ImGui::Dummy({ percent_bar_size, icon_size }); - ImGui::SameLine(); - char buf[64]; - ::sprintf(buf, "%.1f%%", 100.0f * percent); - ImGui::TextUnformatted((percent > 0.0f) ? buf : ""); - } - } - else - imgui.text(label); - - if (!visible) - ImGui::PopStyleVar(); + if (!visible) + ImGui::PopStyleVar(); }; auto append_range = [this, draw_list, &imgui, append_item](const Extrusions::Range& range, unsigned int decimals) { @@ -1881,60 +2072,6 @@ void GCodeViewer::render_legend() const offsets = calculate_offsets(labels, times, { _u8L("Feature type"), _u8L("Time") }, icon_size); } - // total estimated printing time section - if (time_mode.time > 0.0f && (m_view_type == EViewType::FeatureType || - (m_view_type == EViewType::ColorPrint && !time_mode.custom_gcode_times.empty()))) { - ImGui::AlignTextToFramePadding(); - switch (m_time_estimate_mode) - { - case PrintEstimatedTimeStatistics::ETimeMode::Normal: - { - imgui.text(_u8L("Estimated printing time") + " [" + _u8L("Normal mode") + "]:"); - break; - } - case PrintEstimatedTimeStatistics::ETimeMode::Stealth: - { - imgui.text(_u8L("Estimated printing time") + " [" + _u8L("Stealth mode") + "]:"); - break; - } - } - ImGui::SameLine(); - imgui.text(short_time(get_time_dhms(time_mode.time))); - - auto show_mode_button = [this, &imgui](const std::string& label, PrintEstimatedTimeStatistics::ETimeMode mode) { - bool show = false; - for (size_t i = 0; i < m_time_statistics.modes.size(); ++i) { - if (i != static_cast(mode) && - short_time(get_time_dhms(m_time_statistics.modes[static_cast(mode)].time)) != short_time(get_time_dhms(m_time_statistics.modes[i].time))) { - show = true; - break; - } - } - if (show && m_time_statistics.modes[static_cast(mode)].roles_times.size() > 0) { - if (imgui.button(label)) { - m_time_estimate_mode = mode; - wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); - wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); - } - } - }; - - switch (m_time_estimate_mode) - { - case PrintEstimatedTimeStatistics::ETimeMode::Normal: - { - show_mode_button(_u8L("Show stealth mode"), PrintEstimatedTimeStatistics::ETimeMode::Stealth); - break; - } - case PrintEstimatedTimeStatistics::ETimeMode::Stealth: - { - show_mode_button(_u8L("Show normal mode"), PrintEstimatedTimeStatistics::ETimeMode::Normal); - break; - } - } - ImGui::Spacing(); - } - // extrusion paths section -> title switch (m_view_type) { @@ -1975,10 +2112,10 @@ void GCodeViewer::render_legend() const } break; } - case EViewType::Height: { append_range(m_extrusions.ranges.height, 3); break; } - case EViewType::Width: { append_range(m_extrusions.ranges.width, 3); break; } - case EViewType::Feedrate: { append_range(m_extrusions.ranges.feedrate, 1); break; } - case EViewType::FanSpeed: { append_range(m_extrusions.ranges.fan_speed, 0); break; } + case EViewType::Height: { append_range(m_extrusions.ranges.height, 3); break; } + case EViewType::Width: { append_range(m_extrusions.ranges.width, 3); break; } + case EViewType::Feedrate: { append_range(m_extrusions.ranges.feedrate, 1); break; } + case EViewType::FanSpeed: { append_range(m_extrusions.ranges.fan_speed, 0); break; } case EViewType::VolumetricRate: { append_range(m_extrusions.ranges.volumetric_rate, 3); break; } case EViewType::Tool: { @@ -2211,7 +2348,7 @@ void GCodeViewer::render_legend() const auto any_option_available = [this]() { auto available = [this](EMoveType type) { const TBuffer& buffer = m_buffers[buffer_id(type)]; - return buffer.visible && buffer.indices.count > 0; + return buffer.visible && buffer.has_data(); }; return available(EMoveType::Color_change) || @@ -2224,7 +2361,7 @@ void GCodeViewer::render_legend() const auto add_option = [this, append_item](EMoveType move_type, EOptionsColors color, const std::string& text) { const TBuffer& buffer = m_buffers[buffer_id(move_type)]; - if (buffer.visible && buffer.indices.count > 0) + if (buffer.visible && buffer.has_data()) append_item((buffer.shader == "options_110") ? EItemType::Rect : EItemType::Circle, Options_Colors[static_cast(color)], text); }; @@ -2243,6 +2380,67 @@ void GCodeViewer::render_legend() const add_option(EMoveType::Custom_GCode, EOptionsColors::CustomGCodes, _u8L("Custom GCodes")); } + // total estimated printing time section + if (time_mode.time > 0.0f && (m_view_type == EViewType::FeatureType || + (m_view_type == EViewType::ColorPrint && !time_mode.custom_gcode_times.empty()))) { + + ImGui::Spacing(); + ImGui::Spacing(); + ImGui::PushStyleColor(ImGuiCol_Separator, { 1.0f, 1.0f, 1.0f, 1.0f }); + ImGui::Separator(); + ImGui::PopStyleColor(); + ImGui::Spacing(); + + ImGui::AlignTextToFramePadding(); + switch (m_time_estimate_mode) + { + case PrintEstimatedTimeStatistics::ETimeMode::Normal: + { + imgui.text(_u8L("Estimated printing time") + " [" + _u8L("Normal mode") + "]:"); + break; + } + case PrintEstimatedTimeStatistics::ETimeMode::Stealth: + { + imgui.text(_u8L("Estimated printing time") + " [" + _u8L("Stealth mode") + "]:"); + break; + } + } + ImGui::SameLine(); + imgui.text(short_time(get_time_dhms(time_mode.time))); + + auto show_mode_button = [this, &imgui](const std::string& label, PrintEstimatedTimeStatistics::ETimeMode mode) { + bool show = false; + for (size_t i = 0; i < m_time_statistics.modes.size(); ++i) { + if (i != static_cast(mode) && + short_time(get_time_dhms(m_time_statistics.modes[static_cast(mode)].time)) != short_time(get_time_dhms(m_time_statistics.modes[i].time))) { + show = true; + break; + } + } + if (show && m_time_statistics.modes[static_cast(mode)].roles_times.size() > 0) { + if (imgui.button(label)) { + m_time_estimate_mode = mode; + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); + } + } + }; + + switch (m_time_estimate_mode) + { + case PrintEstimatedTimeStatistics::ETimeMode::Normal: + { + show_mode_button(_u8L("Show stealth mode"), PrintEstimatedTimeStatistics::ETimeMode::Stealth); + break; + } + case PrintEstimatedTimeStatistics::ETimeMode::Stealth: + { + show_mode_button(_u8L("Show normal mode"), PrintEstimatedTimeStatistics::ETimeMode::Normal); + break; + } + } + } + imgui.end(); ImGui::PopStyleVar(); } @@ -2250,81 +2448,91 @@ void GCodeViewer::render_legend() const #if ENABLE_GCODE_VIEWER_STATISTICS void GCodeViewer::render_statistics() const { - static const float offset = 230.0f; + static const float offset = 250.0f; ImGuiWrapper& imgui = *wxGetApp().imgui(); + auto add_time = [this, &imgui](const std::string& label, long long time) { + char buf[1024]; + sprintf(buf, "%lld ms (%s)", time, get_time_dhms(static_cast(time) * 0.001f).c_str()); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label); + ImGui::SameLine(offset); + imgui.text(buf); + }; + + auto add_memory = [this, &imgui](const std::string& label, long long memory) { + static const float mb = 1024.0f * 1024.0f; + static const float inv_mb = 1.0f / mb; + static const float gb = 1024.0f * mb; + static const float inv_gb = 1.0f / gb; + char buf[1024]; + if (static_cast(memory) < gb) + sprintf(buf, "%lld bytes (%.3f MB)", memory, static_cast(memory) * inv_mb); + else + sprintf(buf, "%lld bytes (%.3f GB)", memory, static_cast(memory) * inv_gb); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label); + ImGui::SameLine(offset); + imgui.text(buf); + }; + + auto add_counter = [this, &imgui](const std::string& label, long long counter) { + char buf[1024]; + sprintf(buf, "%lld", counter); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label); + ImGui::SameLine(offset); + imgui.text(buf); + }; + imgui.set_next_window_pos(0.5f * wxGetApp().plater()->get_current_canvas3D()->get_canvas_size().get_width(), 0.0f, ImGuiCond_Once, 0.5f, 0.0f); + ImGui::SetNextWindowSizeConstraints({ 300, -1 }, { 600, -1 }); imgui.begin(std::string("GCodeViewer Statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize); ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow()); - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("GCodeProcessor time:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.results_time) + " ms"); + if (ImGui::CollapsingHeader("Time")) { + add_time(std::string("GCodeProcessor:"), m_statistics.results_time); - ImGui::Separator(); + ImGui::Separator(); + add_time(std::string("Load:"), m_statistics.load_time); + add_time(std::string("Refresh:"), m_statistics.refresh_time); + add_time(std::string("Refresh paths:"), m_statistics.refresh_paths_time); + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); + } - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Load time:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.load_time) + " ms"); + if (ImGui::CollapsingHeader("OpenGL calls")) { + add_counter(std::string("Multi GL_POINTS:"), m_statistics.gl_multi_points_calls_count); + add_counter(std::string("Multi GL_LINES:"), m_statistics.gl_multi_lines_calls_count); + add_counter(std::string("Multi GL_TRIANGLES:"), m_statistics.gl_multi_triangles_calls_count); + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); + } - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Refresh time:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.refresh_time) + " ms"); + if (ImGui::CollapsingHeader("CPU memory")) { + add_memory(std::string("GCodeProcessor results:"), m_statistics.results_size); - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Refresh paths time:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.refresh_paths_time) + " ms"); + ImGui::Separator(); + add_memory(std::string("Paths:"), m_statistics.paths_size); + add_memory(std::string("Render paths:"), m_statistics.render_paths_size); + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); + } - ImGui::Separator(); + if (ImGui::CollapsingHeader("GPU memory")) { + add_memory(std::string("Vertices:"), m_statistics.vertices_gpu_size); + add_memory(std::string("Indices:"), m_statistics.indices_gpu_size); + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); + } - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Multi GL_POINTS calls:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.gl_multi_points_calls_count)); + if (ImGui::CollapsingHeader("Other")) { + add_counter(std::string("Travel segments count:"), m_statistics.travel_segments_count); + add_counter(std::string("Extrude segments count:"), m_statistics.extrude_segments_count); + add_counter(std::string("Max vertices in vertex buffer:"), m_statistics.max_vertices_in_vertex_buffer); + add_counter(std::string("Max indices in index buffer:"), m_statistics.max_indices_in_index_buffer); - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Multi GL_LINES calls:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.gl_multi_lines_calls_count)); - - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Multi GL_TRIANGLES calls:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.gl_multi_triangles_calls_count)); - - ImGui::Separator(); - - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("GCodeProcessor results:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.results_size) + " bytes"); - - ImGui::Separator(); - - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Paths CPU:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.paths_size) + " bytes"); - - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Render paths CPU:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.render_paths_size) + " bytes"); - - ImGui::Separator(); - - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Vertices GPU:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.vertices_gpu_size) + " bytes"); - - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Indices GPU:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.indices_gpu_size) + " bytes"); - - ImGui::Separator(); - - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Travel segments count:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.travel_segments_count)); - - imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, std::string("Extrude segments count:")); - ImGui::SameLine(offset); - imgui.text(std::to_string(m_statistics.extrude_segments_count)); + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); + } imgui.end(); } @@ -2353,6 +2561,26 @@ bool GCodeViewer::is_travel_in_z_range(size_t id) const return is_in_z_range(path); } +void GCodeViewer::log_memory_used(const std::string& label, long long additional) const +{ + if (Slic3r::get_logging_level() >= 5) { + long long paths_size = 0; + long long render_paths_size = 0; + for (const TBuffer& buffer : m_buffers) { + paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.paths, Path); + render_paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.render_paths, RenderPath); + for (const RenderPath& path : buffer.render_paths) { + render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.sizes, unsigned int); + render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.offsets, size_t); + } + } + long long layers_zs_size = SLIC3R_STDVEC_MEMSIZE(m_layers_zs, double); + BOOST_LOG_TRIVIAL(trace) << label + << format_memsize_MB(additional + paths_size + render_paths_size + layers_zs_size) + << log_memory_info(); + } +} + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 68fed6f33..8c5d8b743 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -18,6 +18,9 @@ namespace GUI { class GCodeViewer { using Color = std::array; + using IndexBuffer = std::vector; + using MultiIndexBuffer = std::vector; + static const std::vector Extrusion_Role_Colors; static const std::vector Options_Colors; static const std::vector Travel_Colors; @@ -112,10 +115,12 @@ class GCodeViewer { struct Endpoint { - // index into the indices buffer - unsigned int i_id{ 0u }; - // sequential id - unsigned int s_id{ 0u }; + // index of the index buffer + unsigned int b_id{ 0 }; + // index into the index buffer + size_t i_id{ 0 }; + // sequential id (index into the vertex buffer) + size_t s_id{ 0 }; Vec3f position{ Vec3f::Zero() }; }; @@ -134,16 +139,17 @@ class GCodeViewer bool matches(const GCodeProcessor::MoveVertex& move) const; size_t vertices_count() const { return last.s_id - first.s_id + 1; } - bool contains(unsigned int id) const { return first.s_id <= id && id <= last.s_id; } + bool contains(size_t id) const { return first.s_id <= id && id <= last.s_id; } }; // Used to batch the indices needed to render paths struct RenderPath { Color color; - size_t path_id; + unsigned int path_id; + unsigned int index_buffer_id; std::vector sizes; - std::vector offsets; // use size_t because we need the pointer's size (used in the call glMultiDrawElements()) + std::vector offsets; // use size_t because we need an unsigned int whose size matches pointer's size (used in the call glMultiDrawElements()) }; // buffer containing data for rendering a specific toolpath type @@ -158,7 +164,7 @@ class GCodeViewer ERenderPrimitiveType render_primitive_type; VBuffer vertices; - IBuffer indices; + std::vector indices; std::string shader; std::vector paths; @@ -166,7 +172,10 @@ class GCodeViewer bool visible{ false }; void reset(); - void add_path(const GCodeProcessor::MoveVertex& move, unsigned int i_id, unsigned int s_id); + // b_id index of buffer contained in this->indices + // i_id index of first index contained in this->indices[b_id] + // s_id index of first vertex contained in this->vertices + void add_path(const GCodeProcessor::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id); unsigned int indices_per_segment() const { switch (render_primitive_type) { @@ -194,6 +203,8 @@ class GCodeViewer default: { return 0; } } } + + bool has_data() const { return vertices.id != 0 && !indices.empty() && indices.front().id != 0; } }; // helper to render shells @@ -264,7 +275,7 @@ class GCodeViewer #if ENABLE_GCODE_VIEWER_STATISTICS struct Statistics { - // times + // time long long results_time{ 0 }; long long load_time{ 0 }; long long refresh_time{ 0 }; @@ -279,15 +290,17 @@ class GCodeViewer long long indices_gpu_size{ 0 }; long long paths_size{ 0 }; long long render_paths_size{ 0 }; - // others + // other long long travel_segments_count{ 0 }; long long extrude_segments_count{ 0 }; + long long max_vertices_in_vertex_buffer{ 0 }; + long long max_indices_in_index_buffer{ 0 }; void reset_all() { reset_times(); reset_opengl(); reset_sizes(); - reset_counters(); + reset_others(); } void reset_times() { @@ -311,9 +324,11 @@ class GCodeViewer render_paths_size = 0; } - void reset_counters() { + void reset_others() { travel_segments_count = 0; extrude_segments_count = 0; + max_vertices_in_vertex_buffer = 0; + max_indices_in_index_buffer = 0; } }; #endif // ENABLE_GCODE_VIEWER_STATISTICS @@ -346,8 +361,8 @@ public: struct Endpoints { - unsigned int first{ 0 }; - unsigned int last{ 0 }; + size_t first{ 0 }; + size_t last{ 0 }; }; Endpoints endpoints; @@ -371,7 +386,7 @@ public: private: unsigned int m_last_result_id{ 0 }; - size_t m_vertices_count{ 0 }; + size_t m_moves_count{ 0 }; mutable std::vector m_buffers{ static_cast(EMoveType::Extrude) }; // bounding box of toolpaths BoundingBoxf3 m_paths_bounding_box; @@ -444,7 +459,6 @@ public: void export_toolpaths_to_obj(const char* filename) const; private: - void init_shaders(); void load_toolpaths(const GCodeProcessor::Result& gcode_result); void load_shells(const Print& print, bool initialized); void refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const; @@ -466,6 +480,7 @@ private: return in_z_range(path.first.position[2]) || in_z_range(path.last.position[2]); } bool is_travel_in_z_range(size_t id) const; + void log_memory_used(const std::string& label, long long additional = 0) const; }; } // namespace GUI diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index e7f0f094d..e0c8c4c5b 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2732,7 +2732,7 @@ static void load_gcode_retractions(const GCodePreviewData::Retraction& retractio void GLCanvas3D::load_gcode_preview(const GCodeProcessor::Result& gcode_result) { m_gcode_viewer.load(gcode_result, *this->fff_print(), m_initialized); - if (wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer) + if (wxGetApp().is_editor()) _show_warning_texture_if_needed(WarningTexture::ToolpathOutside); } @@ -4302,7 +4302,7 @@ void GLCanvas3D::update_ui_from_settings() #endif // ENABLE_RETINA_GL #if ENABLE_GCODE_VIEWER - if (wxGetApp().mainframe != nullptr && wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer) + if (wxGetApp().is_editor()) wxGetApp().plater()->get_collapse_toolbar().set_enabled(wxGetApp().app_config->get("show_collapse_button") == "1"); #else bool enable_collapse = wxGetApp().app_config->get("show_collapse_button") == "1"; @@ -4999,7 +4999,7 @@ bool GLCanvas3D::_init_main_toolbar() return false; item.name = "settings"; - item.icon_filename = "cog_.svg"; + item.icon_filename = "settings.svg"; item.tooltip = _u8L("Switch to Settings") + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "2] - " + _u8L("Print Settings Tab") + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "3] - " + (m_process->current_printer_technology() == ptFFF ? _u8L("Filament Settings Tab") : _u8L("Material Settings Tab")) + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "4] - " + _u8L("Printer Settings Tab") ; @@ -5011,35 +5011,16 @@ bool GLCanvas3D::_init_main_toolbar() if (!m_main_toolbar.add_item(item)) return false; + /* if (!m_main_toolbar.add_separator()) return false; - - item.name = "layersediting"; - item.icon_filename = "layers_white.svg"; - item.tooltip = _utf8(L("Variable layer height")); - item.sprite_id = 11; - item.left.toggable = true; - item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); }; - item.visibility_callback = [this]()->bool - { - bool res = m_process->current_printer_technology() == ptFFF; - // turns off if changing printer technology - if (!res && m_main_toolbar.is_item_visible("layersediting") && m_main_toolbar.is_item_pressed("layersediting")) - force_main_toolbar_left_action(get_main_toolbar_item_id("layersediting")); - - return res; - }; - item.enabling_callback = []()->bool { return wxGetApp().plater()->can_layers_editing(); }; - if (!m_main_toolbar.add_item(item)) - return false; - - if (!m_main_toolbar.add_separator()) - return false; + */ item.name = "search"; item.icon_filename = "search_.svg"; item.tooltip = _utf8(L("Search")) + " [" + GUI::shortkey_ctrl_prefix() + "F]"; - item.sprite_id = 12; + item.sprite_id = 11; + item.left.toggable = true; item.left.render_callback = [this](float left, float right, float, float) { if (m_canvas != nullptr) { @@ -5053,6 +5034,27 @@ bool GLCanvas3D::_init_main_toolbar() if (!m_main_toolbar.add_item(item)) return false; + if (!m_main_toolbar.add_separator()) + return false; + + item.name = "layersediting"; + item.icon_filename = "layers_white.svg"; + item.tooltip = _utf8(L("Variable layer height")); + item.sprite_id = 12; + item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); }; + item.visibility_callback = [this]()->bool { + bool res = m_process->current_printer_technology() == ptFFF; + // turns off if changing printer technology + if (!res && m_main_toolbar.is_item_visible("layersediting") && m_main_toolbar.is_item_pressed("layersediting")) + force_main_toolbar_left_action(get_main_toolbar_item_id("layersediting")); + + return res; + }; + item.enabling_callback = []()->bool { return wxGetApp().plater()->can_layers_editing(); }; + item.left.render_callback = GLToolbarItem::Default_Render_Callback; + if (!m_main_toolbar.add_item(item)) + return false; + return true; } @@ -5161,10 +5163,10 @@ bool GLCanvas3D::_init_undoredo_toolbar() if (!m_undoredo_toolbar.add_item(item)) return false; - + /* if (!m_undoredo_toolbar.add_separator()) return false; - + */ return true; } @@ -5405,7 +5407,7 @@ void GLCanvas3D::_render_background() const { #if ENABLE_GCODE_VIEWER bool use_error_color = false; - if (wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer) { + if (wxGetApp().is_editor()) { use_error_color = m_dynamic_background_enabled; if (!m_volumes.empty()) use_error_color &= _is_any_volume_outside(); @@ -5553,6 +5555,10 @@ void GLCanvas3D::_render_selection_center() const void GLCanvas3D::_check_and_update_toolbar_icon_scale() const { + // Don't update a toolbar scale, when we are on a Preview + if (wxGetApp().plater()->is_preview_shown()) + return; + float scale = wxGetApp().toolbar_icon_scale(); Size cnv_size = get_canvas_size(); @@ -7134,7 +7140,7 @@ void GLCanvas3D::_show_warning_texture_if_needed(WarningTexture::Warning warning if (!m_volumes.empty()) show = _is_any_volume_outside(); else { - if (wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer) { + if (wxGetApp().is_editor()) { BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3(); const BoundingBoxf3& paths_volume = m_gcode_viewer.get_paths_bounding_box(); if (test_volume.radius() > 0.0 && paths_volume.radius() > 0.0) diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index 913716dfd..6c76b6227 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -194,7 +194,7 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value))); else if(opt_key.compare("support_pillar_connection_mode") == 0) config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value))); - else if(opt_key == "authorization_type") + else if(opt_key == "printhost_authorization_type") config.set_key_value(opt_key, new ConfigOptionEnum(boost::any_cast(value))); } break; diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index ff6b72a52..7b6b38b5d 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -1,3 +1,4 @@ +#include "libslic3r/Technologies.hpp" #include "GUI_App.hpp" #include "GUI_ObjectList.hpp" #include "GUI_ObjectManipulation.hpp" @@ -30,6 +31,7 @@ #include #include +#include #include "libslic3r/Utils.hpp" #include "libslic3r/Model.hpp" @@ -58,6 +60,8 @@ #include "RemovableDriveManager.hpp" #include "InstanceCheck.hpp" #include "NotificationManager.hpp" +#include "UnsavedChangesDialog.hpp" +#include "PresetComboBoxes.hpp" #ifdef __WXMSW__ #include @@ -74,6 +78,231 @@ namespace GUI { class MainFrame; +class SplashScreen : public wxSplashScreen +{ +public: + SplashScreen(const wxBitmap& bitmap, long splashStyle, int milliseconds, wxPoint pos = wxDefaultPosition, bool is_decorated = false) + : wxSplashScreen(bitmap, splashStyle, milliseconds, nullptr, wxID_ANY, + wxDefaultPosition, wxDefaultSize, wxSIMPLE_BORDER | wxFRAME_NO_TASKBAR ) + { + wxASSERT(bitmap.IsOk()); + m_main_bitmap = bitmap; + + if (!is_decorated) + Decorate(m_main_bitmap, pos, true); + + m_scale = get_display_scale(pos); + m_font = get_scaled_sys_font(get_splashscreen_display_scale_factor(pos)).Bold().Larger(); + + if (pos != wxDefaultPosition) { + this->SetPosition(pos); + this->CenterOnScreen(); + } + } + + void SetText(const wxString& text) + { + set_bitmap(m_main_bitmap); + if (!text.empty()) { + wxBitmap bitmap(m_main_bitmap); + + wxMemoryDC memDC; + memDC.SelectObject(bitmap); + + wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + memDC.SetFont(m_font); + memDC.SetTextForeground(wxColour(237, 107, 33)); + memDC.DrawText(text, int(m_scale * 45), int(m_scale * 200)); + + memDC.SelectObject(wxNullBitmap); + set_bitmap(bitmap); + } + } + + static bool Decorate(wxBitmap& bmp, wxPoint screen_pos = wxDefaultPosition, bool force_decor = false) + { + if (!bmp.IsOk()) + return false; + + float screen_sf = get_splashscreen_display_scale_factor(screen_pos); + float screen_scale = get_display_scale(screen_pos); + + if (screen_sf == 1.0) { + // it means that we have just one display or all displays have a same scale + // Scale bitmap for this display and continue the decoration + scale_bitmap(bmp, screen_scale); + } + else if (force_decor) { + // if we are here, it means that bitmap is already scaled for the main display + // and now we should just scale it th the secondary monitor and continue the decoration + scale_bitmap(bmp, screen_sf); + } + else { + // if screens have different scale and this function is called with force_decor == false + // then just rescale the bitmap for the main display scale + scale_bitmap(bmp, get_display_scale()); + return false; + // Decoration will be continued later, from the SplashScreen constructor + } + + // use a memory DC to draw directly onto the bitmap + wxMemoryDC memDc(bmp); + + // draw an dark grey box at the left of the splashscreen. + // this box will be 2/5 of the weight of the bitmap, and be at the left. + int banner_width = (bmp.GetWidth() / 5) * 2 - 2; + const wxRect banner_rect(wxPoint(0, (bmp.GetHeight() / 9) * 2), wxPoint(banner_width, bmp.GetHeight())); + wxDCBrushChanger bc(memDc, wxBrush(wxColour(51, 51, 51))); + wxDCPenChanger pc(memDc, wxPen(wxColour(51, 51, 51))); + memDc.DrawRectangle(banner_rect); + + wxFont sys_font = get_scaled_sys_font(screen_sf); + + // title +#if ENABLE_GCODE_VIEWER + wxString title_string = wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME; +#else + wxString title_string = SLIC3R_APP_NAME; +#endif // ENABLE_GCODE_VIEWER + + wxFont title_font = sys_font; + title_font.SetPointSize(3 * sys_font.GetPointSize()); + + // dynamically get the version to display + wxString version_string = _L("Version") + " " + std::string(SLIC3R_VERSION); + wxFont version_font = sys_font.Larger().Larger(); + + // create a copyright notice that uses the year that this file was compiled + wxString year(__DATE__); + wxString cr_symbol = wxString::FromUTF8("\xc2\xa9"); + wxString copyright_string = wxString::Format("%s 2016-%s Prusa Research.\n" + "%s 2011-2018 Alessandro Ranellucci.", + cr_symbol, year.Mid(year.Length() - 4), cr_symbol) + "\n\n"; + wxFont copyright_font = sys_font.Larger(); + + copyright_string += //"Slic3r" + _L("is licensed under the") + _L("GNU Affero General Public License, version 3") + "\n\n" + + _L("PrusaSlicer is based on Slic3r by Alessandro Ranellucci and the RepRap community.") + "\n\n" + + _L("Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, Petr Ledvina, Joseph Lenox, Y. Sapir, Mike Sheldrake, Vojtech Bubnik and numerous others.") + "\n\n" + + _L("Splash screen can be disabled from the \"Preferences\""); + + word_wrap_string(copyright_string, banner_width, screen_scale); + + wxCoord margin = int(screen_scale * 20); + + // draw the (orange) labels inside of our black box (at the left of the splashscreen) + memDc.SetTextForeground(wxColour(237, 107, 33)); + + memDc.SetFont(title_font); + memDc.DrawLabel(title_string, banner_rect.Deflate(margin, 0), wxALIGN_TOP | wxALIGN_LEFT); + + memDc.SetFont(version_font); + memDc.DrawLabel(version_string, banner_rect.Deflate(margin, 2 * margin), wxALIGN_TOP | wxALIGN_LEFT); + + memDc.SetFont(copyright_font); + memDc.DrawLabel(copyright_string, banner_rect.Deflate(margin, margin), wxALIGN_BOTTOM | wxALIGN_LEFT); + + return true; + } + +private: + wxBitmap m_main_bitmap; + wxFont m_font; + float m_scale {1.0}; + + void set_bitmap(wxBitmap& bmp) + { + m_window->SetBitmap(bmp); + m_window->Refresh(); + m_window->Update(); + } + + static float get_splashscreen_display_scale_factor(wxPoint pos = wxDefaultPosition) + { + if (wxDisplay::GetCount() == 1) + return 1.0; + + wxFrame main_screen_fr(nullptr, wxID_ANY, wxEmptyString); + wxFrame splash_screen_fr(nullptr, wxID_ANY, wxEmptyString, pos); + +#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT && !defined(__WXGTK__) + int main_dpi = get_dpi_for_window(&main_screen_fr); + int splash_dpi = get_dpi_for_window(&splash_screen_fr); + float sf = (float)splash_dpi / (float)main_dpi; +#else + // initialize default width_unit according to the width of the one symbol ("m") of the currently active font of this window. + float sf = (float)splash_screen_fr.GetTextExtent("m").x / (float)main_screen_fr.GetTextExtent("m").x; +#endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT + + return sf; + } + + static float get_display_scale(wxPoint pos = wxDefaultPosition) + { + // pos equals to wxDefaultPosition, when display is main + wxFrame fr(nullptr, wxID_ANY, wxEmptyString, pos); + +#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT && !defined(__WXGTK__) + int dpi = get_dpi_for_window(&fr); + float scale = dpi != DPI_DEFAULT ? (float)dpi / DPI_DEFAULT : 1.0; +#else + // initialize default width_unit according to the width of the one symbol ("m") of the currently active font of this window. + float scale = 0.1 * std::max(10, fr.GetTextExtent("m").x - 1); +#endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT + + return scale; + } + + static void scale_bitmap(wxBitmap& bmp, float scale) + { + if (scale == 1.0) + return; + + wxImage image = bmp.ConvertToImage(); + if (!image.IsOk() || image.GetWidth() == 0 || image.GetHeight() == 0) + return; + + int width = int(scale * image.GetWidth()); + int height = int(scale * image.GetHeight()); + image.Rescale(width, height, wxIMAGE_QUALITY_BILINEAR); + + bmp = wxBitmap(std::move(image)); + } + + static wxFont get_scaled_sys_font(float screen_sf) + { + wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + if (screen_sf != 1.0) + font.SetPointSize(int(screen_sf * (float)font.GetPointSize())); + + return font; + } + + static void word_wrap_string(wxString& input, int line_px_len, float scalef) + { + // calculate count od symbols in one line according to the scale + int line_len = int((float)line_px_len / (scalef * 10) + 0.5) + 10; + + int idx = -1; + int cur_len = 0; + for (size_t i = 0; i < input.Len(); i++) + { + cur_len++; + if (input[i] == ' ') + idx = i; + if (input[i] == '\n') + { + idx = -1; + cur_len = 0; + } + if (cur_len >= line_len && idx >= 0) + { + input[idx] = '\n'; + cur_len = static_cast(i) - idx; + } + } + } +}; + wxString file_wildcards(FileType file_type, const std::string &custom_extension) { static const std::string defaults[FT_SIZE] = { @@ -266,8 +495,15 @@ static void generic_exception_handle() IMPLEMENT_APP(GUI_App) +#if ENABLE_GCODE_VIEWER +GUI_App::GUI_App(EAppMode mode) +#else GUI_App::GUI_App() +#endif // ENABLE_GCODE_VIEWER : wxApp() +#if ENABLE_GCODE_VIEWER + , m_app_mode(mode) +#endif // ENABLE_GCODE_VIEWER , m_em_unit(10) , m_imgui(new ImGuiWrapper()) , m_wizard(nullptr) @@ -323,13 +559,19 @@ void GUI_App::init_app_config() if (!app_config) app_config = new AppConfig(); +#if ENABLE_GCODE_VIEWER + if (is_gcode_viewer()) + // disable config save to avoid to mess it up for the editor + app_config->enable_save(false); +#endif // ENABLE_GCODE_VIEWER + // load settings app_conf_exists = app_config->exists(); if (app_conf_exists) { std::string error = app_config->load(); if (!error.empty()) // Error while parsing config file. We'll customize the error message and rethrow to be displayed. - throw std::runtime_error( + throw Slic3r::RuntimeError( _u8L("Error parsing PrusaSlicer config file, it is probably corrupted. " "Try to manually delete the file to recover from the error. Your user profiles will not be affected.") + "\n\n" + AppConfig::config_path() + "\n\n" + error); @@ -359,18 +601,18 @@ bool GUI_App::on_init_inner() wxCHECK_MSG(wxDirExists(resources_dir), false, wxString::Format("Resources path does not exist or is not a directory: %s", resources_dir)); - // Enable this to get the default Win32 COMCTRL32 behavior of static boxes. + // Enable this to get the default Win32 COMCTRL32 behavior of static boxes. // wxSystemOptions::SetOption("msw.staticbox.optimized-paint", 0); // Enable this to disable Windows Vista themes for all wxNotebooks. The themes seem to lead to terrible // performance when working on high resolution multi-display setups. // wxSystemOptions::SetOption("msw.notebook.themed-background", 0); // Slic3r::debugf "wxWidgets version %s, Wx version %s\n", wxVERSION_STRING, wxVERSION; - + std::string msg = Http::tls_global_init(); std::string ssl_cert_store = app_config->get("tls_accepted_cert_store_location"); bool ssl_accept = app_config->get("tls_cert_store_accepted") == "yes" && ssl_cert_store == Http::tls_system_cert_store(); - + if (!msg.empty() && !ssl_accept) { wxRichMessageDialog dlg(nullptr, @@ -380,34 +622,74 @@ bool GUI_App::on_init_inner() if (dlg.ShowModal() != wxID_YES) return false; app_config->set("tls_cert_store_accepted", - dlg.IsCheckBoxChecked() ? "yes" : "no"); + dlg.IsCheckBoxChecked() ? "yes" : "no"); app_config->set("tls_accepted_cert_store_location", - dlg.IsCheckBoxChecked() ? Http::tls_system_cert_store() : ""); + dlg.IsCheckBoxChecked() ? Http::tls_system_cert_store() : ""); } - + app_config->set("version", SLIC3R_VERSION); app_config->save(); - +/* + if (wxImage::FindHandler(wxBITMAP_TYPE_JPEG) == nullptr) + wxImage::AddHandler(new wxJPEGHandler()); + if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr) + wxImage::AddHandler(new wxPNGHandler()); +*/ + wxInitAllImageHandlers(); + + SplashScreen* scrn = nullptr; + if (app_config->get("show_splash_screen") == "1") + { +#if ENABLE_GCODE_VIEWER + wxBitmap bmp(is_editor() ? from_u8(var("splashscreen.jpg")) : from_u8(var("splashscreen-gcodeviewer.jpg")), wxBITMAP_TYPE_JPEG); +#else + wxBitmap bmp(from_u8(var("splashscreen.jpg")), wxBITMAP_TYPE_JPEG); +#endif // ENABLE_GCODE_VIEWER + + // Detect position (display) to show the splash screen + // Now this position is equal to the mainframe position + wxPoint splashscreen_pos = wxDefaultPosition; + if (app_config->has("window_mainframe")) { + auto metrics = WindowMetrics::deserialize(app_config->get("window_mainframe")); + if (metrics) + splashscreen_pos = metrics->get_rect().GetPosition(); + } + + // try to decorate and/or scale the bitmap before splash screen creation + bool is_decorated = SplashScreen::Decorate(bmp, splashscreen_pos); + + // create splash screen with updated bmp + scrn = new SplashScreen(bmp.IsOk() ? bmp : create_scaled_bitmap("prusa_slicer_logo", nullptr, 400), + wxSPLASH_CENTRE_ON_SCREEN | wxSPLASH_TIMEOUT, 4000, splashscreen_pos, is_decorated); + scrn->SetText(_L("Loading configuration...")); + } + preset_bundle = new PresetBundle(); - + // just checking for existence of Slic3r::data_dir is not enough : it may be an empty directory // supplied as argument to --datadir; in that case we should still run the wizard preset_bundle->setup_directories(); +#if ENABLE_GCODE_VIEWER + if (is_editor()) { +#endif // ENABLE_GCODE_VIEWER #ifdef __WXMSW__ - associate_3mf_files(); + associate_3mf_files(); #endif // __WXMSW__ - preset_updater = new PresetUpdater(); - Bind(EVT_SLIC3R_VERSION_ONLINE, [this](const wxCommandEvent &evt) { - app_config->set("version_online", into_u8(evt.GetString())); - app_config->save(); - if(this->plater_ != nullptr) { - if (*Semver::parse(SLIC3R_VERSION) < * Semver::parse(into_u8(evt.GetString()))) { - this->plater_->get_notification_manager()->push_notification(NotificationType::NewAppAviable, *(this->plater_->get_current_canvas3D())); - } - } - }); + preset_updater = new PresetUpdater(); + Bind(EVT_SLIC3R_VERSION_ONLINE, [this](const wxCommandEvent& evt) { + app_config->set("version_online", into_u8(evt.GetString())); + app_config->save(); + if (this->plater_ != nullptr) { + if (*Semver::parse(SLIC3R_VERSION) < *Semver::parse(into_u8(evt.GetString()))) { + this->plater_->get_notification_manager()->push_notification(NotificationType::NewAppAviable, *(this->plater_->get_current_canvas3D())); + } + } + }); +#if ENABLE_GCODE_VIEWER + } +#endif // ENABLE_GCODE_VIEWER // initialize label colors and fonts init_label_colours(); @@ -435,8 +717,13 @@ bool GUI_App::on_init_inner() Slic3r::I18N::set_translate_callback(libslic3r_translate_callback); // application frame - if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr) - wxImage::AddHandler(new wxPNGHandler()); +#if ENABLE_GCODE_VIEWER + if (scrn && is_editor()) +#else + if (scrn) +#endif // ENABLE_GCODE_VIEWER + scrn->SetText(_L("Creating settings tabs...")); + mainframe = new MainFrame(); // hide settings tabs after first Layout mainframe->select_tab(0); @@ -470,13 +757,20 @@ bool GUI_App::on_init_inner() static bool once = true; if (once) { once = false; - check_updates(false); +#if ENABLE_GCODE_VIEWER + if (preset_updater != nullptr) { +#endif // ENABLE_GCODE_VIEWER + check_updates(false); + + CallAfter([this] { + config_wizard_startup(); + preset_updater->slic3r_update_notify(); + preset_updater->sync(preset_bundle); + }); +#if ENABLE_GCODE_VIEWER + } +#endif // ENABLE_GCODE_VIEWER - CallAfter([this] { - config_wizard_startup(); - preset_updater->slic3r_update_notify(); - preset_updater->sync(preset_bundle); - }); #ifdef _WIN32 //sets window property to mainframe so other instances can indentify it OtherInstanceMessageHandler::init_windows_properties(mainframe, m_instance_hash_int); @@ -484,8 +778,16 @@ bool GUI_App::on_init_inner() } }); - load_current_presets(); - +#if ENABLE_GCODE_VIEWER + if (is_gcode_viewer()) { + mainframe->update_layout(); + if (plater_ != nullptr) + // ensure the selected technology is ptFFF + plater_->set_printer_technology(ptFFF); + } + else +#endif // ENABLE_GCODE_VIEWER + load_current_presets(); mainframe->Show(true); /* Temporary workaround for the correct behavior of the Scrolled sidebar panel: @@ -1122,10 +1424,10 @@ void GUI_App::add_config_menu(wxMenuBar *menu) app_layout_changed = dlg.settings_layout_changed(); } if (app_layout_changed) { - mainframe->GetSizer()->Hide((size_t)0); + // hide full main_sizer for mainFrame + mainframe->GetSizer()->Show(false); mainframe->update_layout(); mainframe->select_tab(0); - mainframe->GetSizer()->Show((size_t)0); } break; } @@ -1173,29 +1475,66 @@ void GUI_App::add_config_menu(wxMenuBar *menu) // to notify the user whether he is aware that some preset changes will be lost. bool GUI_App::check_unsaved_changes(const wxString &header) { - wxString dirty; PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); - for (Tab *tab : tabs_list) + + bool has_unsaved_changes = false; + for (Tab* tab : tabs_list) if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty()) { - if (dirty.empty()) - dirty = tab->title(); - else - dirty += wxString(", ") + tab->title(); + has_unsaved_changes = true; + break; } - if (dirty.empty()) - // No changes, the application may close or reload presets. - return true; - // Ask the user. - wxString message; - if (! header.empty()) - message = header + "\n\n"; - message += _(L("The presets on the following tabs were modified")) + ": " + dirty + "\n\n" + _(L("Discard changes and continue anyway?")); - wxMessageDialog dialog(mainframe, - message, - wxString(SLIC3R_APP_NAME) + " - " + _(L("Unsaved Presets")), - wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT); - return dialog.ShowModal() == wxID_YES; + if (has_unsaved_changes) + { + UnsavedChangesDialog dlg(header); + if (dlg.ShowModal() == wxID_CANCEL) + return false; + + if (dlg.save_preset()) // save selected changes + { + struct NameType + { + std::string name; + Preset::Type type {Preset::TYPE_INVALID}; + }; + + std::vector names_and_types; + + // for system/default/external presets we should take an edited name + std::vector types; + for (Tab* tab : tabs_list) + if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty()) + { + const Preset& preset = tab->get_presets()->get_edited_preset(); + if (preset.is_system || preset.is_default || preset.is_external) + types.emplace_back(preset.type); + + names_and_types.emplace_back(NameType{ preset.name, preset.type }); + } + + + if (!types.empty()) { + SavePresetDialog save_dlg(types); + if (save_dlg.ShowModal() != wxID_OK) + return false; + + for (NameType& nt : names_and_types) { + const std::string name = save_dlg.get_name(nt.type); + if (!name.empty()) + nt.name = name; + } + } + + for (const NameType& nt : names_and_types) + preset_bundle->save_changes_for_preset(nt.name, nt.type, dlg.get_unselected_options(nt.type)); + + // if we saved changes to the new presets, we should to + // synchronize config.ini with the current selections. + preset_bundle->export_selections(*app_config); + } + } + + return true; } bool GUI_App::checked_tab(Tab* tab) diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 191d7c264..9bf470a42 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -86,10 +86,30 @@ class ConfigWizard; static wxString dots("…", wxConvUTF8); +// Does our wxWidgets version support markup? +// https://github.com/prusa3d/PrusaSlicer/issues/4282#issuecomment-634676371 +#if wxUSE_MARKUP && wxCHECK_VERSION(3, 1, 1) + #define SUPPORTS_MARKUP +#endif + class GUI_App : public wxApp { +#if ENABLE_GCODE_VIEWER +public: + enum class EAppMode : unsigned char + { + Editor, + GCodeViewer + }; + +private: +#endif // ENABLE_GCODE_VIEWER + bool m_initialized { false }; bool app_conf_exists{ false }; +#if ENABLE_GCODE_VIEWER + EAppMode m_app_mode{ EAppMode::Editor }; +#endif // ENABLE_GCODE_VIEWER wxColour m_color_label_modified; wxColour m_color_label_sys; @@ -119,13 +139,24 @@ class GUI_App : public wxApp std::unique_ptr m_single_instance_checker; std::string m_instance_hash_string; size_t m_instance_hash_int; + public: bool OnInit() override; bool initialized() const { return m_initialized; } +#if ENABLE_GCODE_VIEWER + explicit GUI_App(EAppMode mode = EAppMode::Editor); +#else GUI_App(); +#endif // ENABLE_GCODE_VIEWER ~GUI_App() override; +#if ENABLE_GCODE_VIEWER + EAppMode get_app_mode() const { return m_app_mode; } + bool is_editor() const { return m_app_mode == EAppMode::Editor; } + bool is_gcode_viewer() const { return m_app_mode == EAppMode::GCodeViewer; } +#endif // ENABLE_GCODE_VIEWER + static std::string get_gl_info(bool format_as_html, bool extensions); wxGLContext* init_glcontext(wxGLCanvas& canvas); bool init_opengl(); diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 44b2b6186..4973abca5 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -277,7 +277,11 @@ void ObjectList::create_objects_ctrl() // column ItemName(Icon+Text) of the view control: // And Icon can be consisting of several bitmaps - AppendColumn(new wxDataViewColumn(_(L("Name")), new BitmapTextRenderer(this), + BitmapTextRenderer* bmp_text_renderer = new BitmapTextRenderer(); + bmp_text_renderer->set_can_create_editor_ctrl_function([this]() { + return m_objects_model->GetItemType(GetSelection()) & (itVolume | itObject); + }); + AppendColumn(new wxDataViewColumn(_L("Name"), bmp_text_renderer, colName, 20*em, wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE)); // column PrintableProperty (Icon) of the view control: @@ -285,11 +289,15 @@ void ObjectList::create_objects_ctrl() wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); // column Extruder of the view control: - AppendColumn(new wxDataViewColumn(_(L("Extruder")), new BitmapChoiceRenderer(), + BitmapChoiceRenderer* bmp_choice_renderer = new BitmapChoiceRenderer(); + bmp_choice_renderer->set_can_create_editor_ctrl_function([this]() { + return m_objects_model->GetItemType(GetSelection()) & (itVolume | itLayer | itObject); + }); + AppendColumn(new wxDataViewColumn(_L("Extruder"), bmp_choice_renderer, colExtruder, 8*em, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE)); // column ItemEditing of the view control: - AppendBitmapColumn(_(L("Editing")), colEditing, wxDATAVIEW_CELL_INERT, 3*em, + AppendBitmapColumn(_L("Editing"), colEditing, wxDATAVIEW_CELL_INERT, 3*em, wxALIGN_CENTER_HORIZONTAL, wxDATAVIEW_COL_RESIZABLE); // For some reason under OSX on 4K(5K) monitors in wxDataViewColumn constructor doesn't set width of column. diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 5dcd26a87..8ea54c6f1 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -1234,7 +1234,7 @@ void Preview::load_print_as_fff(bool keep_z_range) } #if ENABLE_GCODE_VIEWER - if (wxGetApp().mainframe->get_mode() != MainFrame::EMode::GCodeViewer && !has_layers) + if (wxGetApp().is_editor() && !has_layers) #else if (! has_layers) #endif // ENABLE_GCODE_VIEWER diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index d9ce44bd6..c0a457d9c 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -194,6 +194,7 @@ Preview(wxWindow* parent, Model* model, DynamicPrintConfig* config, #if ENABLE_GCODE_VIEWER void update_bottom_toolbar(); void update_moves_slider(); + void hide_layers_slider(); #endif // ENABLE_GCODE_VIEWER private: @@ -202,14 +203,12 @@ private: void bind_event_handlers(); void unbind_event_handlers(); -#if ENABLE_GCODE_VIEWER - void hide_layers_slider(); -#else +#if !ENABLE_GCODE_VIEWER void show_hide_ui_elements(const std::string& what); void reset_sliders(bool reset_all); void update_sliders(const std::vector& layers_z, bool keep_z_range = false); -#endif // ENABLE_GCODE_VIEWER +#endif // !ENABLE_GCODE_VIEWER void on_size(wxSizeEvent& evt); void on_choice_view_type(wxCommandEvent& evt); diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp index 96b24524c..1c88de570 100644 --- a/src/slic3r/GUI/GUI_Utils.hpp +++ b/src/slic3r/GUI/GUI_Utils.hpp @@ -92,10 +92,13 @@ public: #ifndef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList this->SetFont(m_normal_font); #endif - // initialize default width_unit according to the width of the one symbol ("m") of the currently active font of this window. -#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT + + // Linux specific issue : get_dpi_for_window(this) still doesn't responce to the Display's scale in new wxWidgets(3.1.3). + // So, calculate the m_em_unit value from the font size, as before +#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT && !defined(__WXGTK__) m_em_unit = std::max(10, 10.0f * m_scale_factor); #else + // initialize default width_unit according to the width of the one symbol ("m") of the currently active font of this window. m_em_unit = std::max(10, this->GetTextExtent("m").x - 1); #endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT @@ -219,16 +222,12 @@ private: { this->Freeze(); -#if wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) - if (m_force_rescale) { -#endif // wxVERSION_EQUAL_OR_GREATER_THAN - // rescale fonts of all controls - scale_controls_fonts(this, m_new_font_point_size); - // rescale current window font - scale_win_font(this, m_new_font_point_size); -#if wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) - m_force_rescale = false; - } + m_force_rescale = false; +#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) + // rescale fonts of all controls + scale_controls_fonts(this, m_new_font_point_size); + // rescale current window font + scale_win_font(this, m_new_font_point_size); #endif // wxVERSION_EQUAL_OR_GREATER_THAN // set normal application font as a current window font diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index af1517637..6b3456b60 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -285,5 +285,12 @@ void GLGizmoFdmSupports::update_from_model_object() } + +PainterGizmoType GLGizmoFdmSupports::get_painter_type() const +{ + return PainterGizmoType::FDM_SUPPORTS; +} + + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index 913133617..0c39992f0 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -27,6 +27,7 @@ private: void on_opening() override {} void on_shutdown() override; + PainterGizmoType get_painter_type() const override; void select_facets_by_angle(float threshold, bool block); float m_angle_threshold_deg = 45.f; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 1809b417c..ed98bf71d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -28,13 +28,19 @@ GLGizmoPainterBase::GLGizmoPainterBase(GLCanvas3D& parent, const std::string& ic void GLGizmoPainterBase::activate_internal_undo_redo_stack(bool activate) { if (activate && ! m_internal_stack_active) { - Plater::TakeSnapshot(wxGetApp().plater(), _L("FDM gizmo turned on")); + wxString str = get_painter_type() == PainterGizmoType::FDM_SUPPORTS + ? _L("Supports gizmo turned on") + : _L("Seam gizmo turned on"); + Plater::TakeSnapshot(wxGetApp().plater(), str); wxGetApp().plater()->enter_gizmos_stack(); m_internal_stack_active = true; } if (! activate && m_internal_stack_active) { + wxString str = get_painter_type() == PainterGizmoType::SEAM + ? _L("Seam gizmo turned off") + : _L("Supports gizmo turned off"); wxGetApp().plater()->leave_gizmos_stack(); - Plater::TakeSnapshot(wxGetApp().plater(), _L("FDM gizmo turned off")); + Plater::TakeSnapshot(wxGetApp().plater(), str); m_internal_stack_active = false; } } @@ -356,11 +362,28 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::RightUp) && m_button_down != Button::None) { // Take snapshot and update ModelVolume data. - wxString action_name = shift_down - ? _L("Remove selection") - : (m_button_down == Button::Left - ? _L("Add supports") - : _L("Block supports")); + wxString action_name; + if (get_painter_type() == PainterGizmoType::FDM_SUPPORTS) { + if (shift_down) + action_name = _L("Remove selection"); + else { + if (m_button_down == Button::Left) + action_name = _L("Add supports"); + else + action_name = _L("Block supports"); + } + } + if (get_painter_type() == PainterGizmoType::SEAM) { + if (shift_down) + action_name = _L("Remove selection"); + else { + if (m_button_down == Button::Left) + action_name = _L("Enforce seam"); + else + action_name = _L("Block seam"); + } + } + activate_internal_undo_redo_stack(true); Plater::TakeSnapshot(wxGetApp().plater(), action_name); update_model_object(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp index da9b37895..b3e2b65f1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -22,6 +22,10 @@ namespace GUI { enum class SLAGizmoEventType : unsigned char; class ClippingPlane; +enum class PainterGizmoType { + FDM_SUPPORTS, + SEAM +}; class TriangleSelectorGUI : public TriangleSelector { @@ -103,6 +107,7 @@ protected: virtual void on_opening() = 0; virtual void on_shutdown() = 0; + virtual PainterGizmoType get_painter_type() const = 0; bool on_is_activable() const override; bool on_is_selectable() const override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index f3e565686..77366c633 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -1,9 +1,15 @@ // Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. #include "GLGizmoRotate.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/ImGuiWrapper.hpp" #include +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/GUI.hpp" +#include "libslic3r/PresetBundle.hpp" + +#include "libslic3r/SLA/Rotfinder.hpp" namespace Slic3r { namespace GUI { @@ -194,6 +200,64 @@ void GLGizmoRotate::on_render_for_picking() const glsafe(::glPopMatrix()); } + + +GLGizmoRotate3D::RotoptimzeWindow::RotoptimzeWindow(ImGuiWrapper * imgui, + State & state, + const Alignment &alignment) + : m_imgui{imgui} +{ + imgui->begin(_L("Rotation"), ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_NoCollapse); + + // adjust window position to avoid overlap the view toolbar + float win_h = ImGui::GetWindowHeight(); + float x = alignment.x, y = alignment.y; + y = std::min(y, alignment.bottom_limit - win_h); + ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always); + + static constexpr const char * button_txt = L("Optimize orientation"); + static constexpr const char * slider_txt = L("Accuracy"); + + float button_width = imgui->calc_text_size(_(button_txt)).x; + ImGui::PushItemWidth(100.); + //if (imgui->button(_(button_txt))) { + if (ImGui::ArrowButton(_(button_txt).c_str(), ImGuiDir_Down)){ + std::cout << "Blip" << std::endl; + } + + ImGui::SliderFloat(_(slider_txt).c_str(), &state.accuracy, 0.01f, 1.f, "%.1f"); + + static const std::vector options = { + _L("Least supports").ToStdString(), + _L("Suface quality").ToStdString() + }; + +// if (imgui->combo(_L("Choose method"), options, state.method) ) { +// std::cout << "method: " << state.method << std::endl; +// } + + +} + +GLGizmoRotate3D::RotoptimzeWindow::~RotoptimzeWindow() +{ + m_imgui->end(); +} + +void GLGizmoRotate3D::on_render_input_window(float x, float y, float bottom_limit) +{ + if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA) + return; + +// TODO: + +// m_rotoptimizewin_state.mobj = ?; +// RotoptimzeWindow popup{m_imgui, m_rotoptimizewin_state, {x, y, bottom_limit}}; + +} + void GLGizmoRotate::render_circle() const { ::glBegin(GL_LINE_LOOP); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index 7365a20c3..c418c4b31 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -52,12 +52,12 @@ public: std::string get_tooltip() const override; protected: - virtual bool on_init(); - virtual std::string on_get_name() const { return ""; } - virtual void on_start_dragging(); - virtual void on_update(const UpdateData& data); - virtual void on_render() const; - virtual void on_render_for_picking() const; + bool on_init() override; + std::string on_get_name() const override { return ""; } + void on_start_dragging() override; + void on_update(const UpdateData& data) override; + void on_render() const override; + void on_render_for_picking() const override; private: void render_circle() const; @@ -94,46 +94,79 @@ public: } protected: - virtual bool on_init(); - virtual std::string on_get_name() const; - virtual void on_set_state() + bool on_init() override; + std::string on_get_name() const override; + void on_set_state() override { for (GLGizmoRotate& g : m_gizmos) g.set_state(m_state); } - virtual void on_set_hover_id() + void on_set_hover_id() override { for (int i = 0; i < 3; ++i) m_gizmos[i].set_hover_id((m_hover_id == i) ? 0 : -1); } - virtual void on_enable_grabber(unsigned int id) + void on_enable_grabber(unsigned int id) override { if (id < 3) m_gizmos[id].enable_grabber(0); } - virtual void on_disable_grabber(unsigned int id) + void on_disable_grabber(unsigned int id) override { if (id < 3) m_gizmos[id].disable_grabber(0); } - virtual bool on_is_activable() const; - virtual void on_start_dragging(); - virtual void on_stop_dragging(); - virtual void on_update(const UpdateData& data) + bool on_is_activable() const override; + void on_start_dragging() override; + void on_stop_dragging() override; + void on_update(const UpdateData& data) override { for (GLGizmoRotate& g : m_gizmos) { g.update(data); } } - virtual void on_render() const; - virtual void on_render_for_picking() const + void on_render() const override; + void on_render_for_picking() const override { for (const GLGizmoRotate& g : m_gizmos) { g.render_for_picking(); } } + + void on_render_input_window(float x, float y, float bottom_limit) override; + +private: + + class RotoptimzeWindow { + ImGuiWrapper *m_imgui = nullptr; + + public: + + struct State { + enum Metods { mMinSupportPoints, mLegacy }; + + float accuracy = 1.f; + int method = mMinSupportPoints; + ModelObject *mobj = nullptr; + }; + + struct Alignment { float x, y, bottom_limit; }; + + RotoptimzeWindow(ImGuiWrapper * imgui, + State & state, + const Alignment &bottom_limit); + + ~RotoptimzeWindow(); + + RotoptimzeWindow(const RotoptimzeWindow&) = delete; + RotoptimzeWindow(RotoptimzeWindow &&) = delete; + RotoptimzeWindow& operator=(const RotoptimzeWindow &) = delete; + RotoptimzeWindow& operator=(RotoptimzeWindow &&) = delete; + }; + + RotoptimzeWindow::State m_rotoptimizewin_state = {}; }; } // namespace GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp index 3c7d180a7..d0edfba13 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp @@ -204,5 +204,12 @@ void GLGizmoSeam::update_from_model_object() } +PainterGizmoType GLGizmoSeam::get_painter_type() const +{ + return PainterGizmoType::SEAM; +} + + + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp index 469ec9180..c3eb98c80 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp @@ -16,6 +16,7 @@ public: protected: void on_render_input_window(float x, float y, float bottom_limit) override; std::string on_get_name() const override; + PainterGizmoType get_painter_type() const override; private: bool on_init() override; diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index e839fdf9b..f9a23cb51 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -425,10 +425,10 @@ bool ImGuiWrapper::combo(const wxString& label, const std::vector& text(label); ImGui::SameLine(); - int selection_out = -1; + int selection_out = selection; bool res = false; - const char *selection_str = selection < (int)options.size() ? options[selection].c_str() : ""; + const char *selection_str = selection < int(options.size()) && selection >= 0 ? options[selection].c_str() : ""; if (ImGui::BeginCombo("", selection_str)) { for (int i = 0; i < (int)options.size(); i++) { if (ImGui::Selectable(options[i].c_str(), i == selection)) { @@ -927,7 +927,7 @@ void ImGuiWrapper::init_font(bool compress) if (font == nullptr) { font = io.Fonts->AddFontDefault(); if (font == nullptr) { - throw std::runtime_error("ImGui: Could not load deafult font"); + throw Slic3r::RuntimeError("ImGui: Could not load deafult font"); } } diff --git a/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp index c847c84b4..978ccf8fc 100644 --- a/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp +++ b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp @@ -4,6 +4,7 @@ #include "libslic3r/SLA/Rotfinder.hpp" #include "libslic3r/MinAreaBoundingBox.hpp" #include "libslic3r/Model.hpp" +#include "libslic3r/SLAPrint.hpp" #include "slic3r/GUI/Plater.hpp" @@ -12,26 +13,41 @@ namespace Slic3r { namespace GUI { void RotoptimizeJob::process() { int obj_idx = m_plater->get_selected_object_idx(); - if (obj_idx < 0) { return; } + if (obj_idx < 0 || int(m_plater->sla_print().objects().size()) <= obj_idx) + return; ModelObject *o = m_plater->model().objects[size_t(obj_idx)]; + const SLAPrintObject *po = m_plater->sla_print().objects()[size_t(obj_idx)]; - auto r = sla::find_best_rotation( - *o, - .005f, + if (!o || !po) return; + + TriangleMesh mesh = o->raw_mesh(); + mesh.require_shared_vertices(); + +// for (auto inst : o->instances) { +// Transform3d tr = Transform3d::Identity(); +// tr.rotate(Eigen::AngleAxisd(inst->get_rotation(Z), Vec3d::UnitZ())); +// tr.rotate(Eigen::AngleAxisd(inst->get_rotation(Y), Vec3d::UnitY())); +// tr.rotate(Eigen::AngleAxisd(inst->get_rotation(X), Vec3d::UnitX())); + +// double score = sla::get_model_supportedness(*po, tr); + +// std::cout << "Model supportedness before: " << score << std::endl; +// } + + Vec2d r = sla::find_best_rotation(*po, 0.75f, [this](unsigned s) { if (s < 100) - update_status(int(s), - _(L("Searching for optimal orientation"))); + update_status(int(s), _(L("Searching for optimal orientation"))); }, - [this]() { return was_canceled(); }); + [this] () { return was_canceled(); }); double mindist = 6.0; // FIXME if (!was_canceled()) { for(ModelInstance * oi : o->instances) { - oi->set_rotation({r[X], r[Y], r[Z]}); + oi->set_rotation({r[X], r[Y], 0.}); auto trmatrix = oi->get_transformation().get_matrix(); Polygon trchull = o->convex_hull_2d(trmatrix); diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index 1eceea22e..4affd1326 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -33,7 +33,11 @@ namespace Slic3r { namespace GUI { KBShortcutsDialog::KBShortcutsDialog() +#if ENABLE_GCODE_VIEWER + : DPIDialog(NULL, wxID_ANY, wxString(wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME) + " - " + _L("Keyboard Shortcuts"), +#else : DPIDialog(NULL, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _L("Keyboard Shortcuts"), +#endif // ENABLE_GCODE_VIEWER wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) { SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); @@ -95,9 +99,7 @@ void KBShortcutsDialog::fill_shortcuts() const std::string& alt = GUI::shortkey_alt_prefix(); #if ENABLE_GCODE_VIEWER - bool is_gcode_viewer = wxGetApp().mainframe->get_mode() == MainFrame::EMode::GCodeViewer; - - if (!is_gcode_viewer) { + if (wxGetApp().is_editor()) { #endif // ENABLE_GCODE_VIEWER Shortcuts commands_shortcuts = { // File diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index bab5d7502..fbb7a190f 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -9,7 +9,6 @@ #include //#include #include -#include #include #include @@ -31,6 +30,7 @@ #include "I18N.hpp" #include "GLCanvas3D.hpp" #include "Plater.hpp" +#include "../Utils/Process.hpp" #include #include "GUI_App.hpp" @@ -40,12 +40,6 @@ #include #endif // _WIN32 -// For starting another PrusaSlicer instance on OSX. -// Fails to compile on Windows on the build server. -#ifdef __APPLE__ - #include -#endif - namespace Slic3r { namespace GUI { @@ -55,29 +49,6 @@ enum class ERescaleTarget SettingsDialog }; -static void rescale_dialog_after_dpi_change(MainFrame& mainframe, SettingsDialog& dialog, ERescaleTarget target) -{ - int mainframe_dpi = get_dpi_for_window(&mainframe); - int dialog_dpi = get_dpi_for_window(&dialog); - if (mainframe_dpi != dialog_dpi) { - if (target == ERescaleTarget::SettingsDialog) { - dialog.enable_force_rescale(); -#if wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) - dialog.GetEventHandler()->AddPendingEvent(wxDPIChangedEvent(wxSize(mainframe_dpi, mainframe_dpi), wxSize(dialog_dpi, dialog_dpi))); -#else - dialog.GetEventHandler()->AddPendingEvent(DpiChangedEvent(EVT_DPI_CHANGED_SLICER, dialog_dpi, dialog.GetRect())); -#endif // wxVERSION_EQUAL_OR_GREATER_THAN - } else { -#if wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) - mainframe.GetEventHandler()->AddPendingEvent(wxDPIChangedEvent(wxSize(dialog_dpi, dialog_dpi), wxSize(mainframe_dpi, mainframe_dpi))); -#else - mainframe.enable_force_rescale(); - mainframe.GetEventHandler()->AddPendingEvent(DpiChangedEvent(EVT_DPI_CHANGED_SLICER, mainframe_dpi, mainframe.GetRect())); -#endif // wxVERSION_EQUAL_OR_GREATER_THAN - } - } -} - MainFrame::MainFrame() : DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE, "mainframe"), m_printhost_queue_dlg(new PrintHostQueueDialog(this)) @@ -115,7 +86,6 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S } #endif // ENABLE_GCODE_VIEWER_TASKBAR_ICON -// SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG)); // Load the icon either from the exe, or from the ico file. #if _WIN32 { @@ -125,22 +95,44 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S SetIcon(wxIcon(szExeFileName, wxBITMAP_TYPE_ICO)); } #else - SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG)); +#if ENABLE_GCODE_VIEWER + switch (wxGetApp().get_app_mode()) + { + default: + case GUI_App::EAppMode::Editor: + { +#endif // ENABLE_GCODE_VIEWER + SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG)); +#if ENABLE_GCODE_VIEWER + break; + } + case GUI_App::EAppMode::GCodeViewer: + { + SetIcon(wxIcon(Slic3r::var("PrusaSlicer-gcodeviewer_128px.png"), wxBITMAP_TYPE_PNG)); + break; + } + } +#endif // ENABLE_GCODE_VIEWER #endif // _WIN32 // initialize status bar - m_statusbar = std::make_shared(this); + m_statusbar = std::make_shared(this); m_statusbar->set_font(GUI::wxGetApp().normal_font()); - m_statusbar->embed(this); - m_statusbar->set_status_text(_(L("Version")) + " " + - SLIC3R_VERSION + - _(L(" - Remember to check for updates at https://github.com/prusa3d/PrusaSlicer/releases"))); +#if ENABLE_GCODE_VIEWER + if (wxGetApp().is_editor()) +#endif // ENABLE_GCODE_VIEWER + m_statusbar->embed(this); + m_statusbar->set_status_text(_L("Version") + " " + + SLIC3R_VERSION + + _L(" - Remember to check for updates at https://github.com/prusa3d/PrusaSlicer/releases")); // initialize tabpanel and menubar init_tabpanel(); #if ENABLE_GCODE_VIEWER - init_editor_menubar(); - init_gcodeviewer_menubar(); + if (wxGetApp().is_gcode_viewer()) + init_menubar_as_gcodeviewer(); + else + init_menubar_as_editor(); #if _WIN32 // This is needed on Windows to fake the CTRL+# of the window menu when using the numpad @@ -171,7 +163,10 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S sizer->Add(m_main_sizer, 1, wxEXPAND); SetSizer(sizer); // initialize layout from config - update_layout(); +#if ENABLE_GCODE_VIEWER + if (wxGetApp().is_editor()) +#endif // ENABLE_GCODE_VIEWER + update_layout(); sizer->SetSizeHints(this); Fit(); @@ -310,9 +305,6 @@ void MainFrame::update_layout() m_plater_page = nullptr; } - if (m_layout == ESettingsLayout::Dlg) - rescale_dialog_after_dpi_change(*this, m_settings_dialog, ERescaleTarget::Mainframe); - clean_sizer(m_main_sizer); clean_sizer(m_settings_dialog.GetSizer()); @@ -326,10 +318,10 @@ void MainFrame::update_layout() }; #if ENABLE_GCODE_VIEWER - ESettingsLayout layout = (m_mode == EMode::GCodeViewer) ? ESettingsLayout::GCodeViewer : + ESettingsLayout layout = wxGetApp().is_gcode_viewer() ? ESettingsLayout::GCodeViewer : (wxGetApp().app_config->get("old_settings_layout_mode") == "1" ? ESettingsLayout::Old : - wxGetApp().app_config->get("new_settings_layout_mode") == "1" ? ESettingsLayout::New : - wxGetApp().app_config->get("dlg_settings_layout_mode") == "1" ? ESettingsLayout::Dlg : ESettingsLayout::Old); + wxGetApp().app_config->get("new_settings_layout_mode") == "1" ? ESettingsLayout::New : + wxGetApp().app_config->get("dlg_settings_layout_mode") == "1" ? ESettingsLayout::Dlg : ESettingsLayout::Old); #else ESettingsLayout layout = wxGetApp().app_config->get("old_settings_layout_mode") == "1" ? ESettingsLayout::Old : wxGetApp().app_config->get("new_settings_layout_mode") == "1" ? ESettingsLayout::New : @@ -347,6 +339,16 @@ void MainFrame::update_layout() if (m_layout != ESettingsLayout::Unknown) restore_to_creation(); +#ifdef __WXMSW__ + enum class State { + noUpdate, + fromDlg, + toDlg + }; + State update_scaling_state = m_layout == ESettingsLayout::Dlg ? State::fromDlg : + layout == ESettingsLayout::Dlg ? State::toDlg : State::noUpdate; +#endif //__WXMSW__ + m_layout = layout; // From the very beginning the Print settings should be selected @@ -383,9 +385,6 @@ void MainFrame::update_layout() m_main_sizer->Add(m_plater, 1, wxEXPAND); m_tabpanel->Reparent(&m_settings_dialog); m_settings_dialog.GetSizer()->Add(m_tabpanel, 1, wxEXPAND); - - rescale_dialog_after_dpi_change(*this, m_settings_dialog, ERescaleTarget::SettingsDialog); - m_tabpanel->Show(); m_plater->Show(); break; @@ -394,12 +393,45 @@ void MainFrame::update_layout() case ESettingsLayout::GCodeViewer: { m_main_sizer->Add(m_plater, 1, wxEXPAND); + m_plater->set_bed_shape({ { 0.0, 0.0 }, { 200.0, 0.0 }, { 200.0, 200.0 }, { 0.0, 200.0 } }, "", "", true); + m_plater->get_collapse_toolbar().set_enabled(false); + m_plater->collapse_sidebar(true); m_plater->Show(); break; } #endif // ENABLE_GCODE_VIEWER } +#ifdef __WXMSW__ + if (update_scaling_state != State::noUpdate) + { + int mainframe_dpi = get_dpi_for_window(this); + int dialog_dpi = get_dpi_for_window(&m_settings_dialog); + if (mainframe_dpi != dialog_dpi) { + wxSize oldDPI = update_scaling_state == State::fromDlg ? wxSize(dialog_dpi, dialog_dpi) : wxSize(mainframe_dpi, mainframe_dpi); + wxSize newDPI = update_scaling_state == State::toDlg ? wxSize(dialog_dpi, dialog_dpi) : wxSize(mainframe_dpi, mainframe_dpi); + + if (update_scaling_state == State::fromDlg) + this->enable_force_rescale(); + else + (&m_settings_dialog)->enable_force_rescale(); + + wxWindow* win { nullptr }; + if (update_scaling_state == State::fromDlg) + win = this; + else + win = &m_settings_dialog; + +#if wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) + m_tabpanel->MSWUpdateOnDPIChange(oldDPI, newDPI); + win->GetEventHandler()->AddPendingEvent(wxDPIChangedEvent(oldDPI, newDPI)); +#else + win->GetEventHandler()->AddPendingEvent(DpiChangedEvent(EVT_DPI_CHANGED_SLICER, newDPI, win->GetRect())); +#endif // wxVERSION_EQUAL_OR_GREATER_THAN + } + } +#endif //__WXMSW__ + //#ifdef __APPLE__ // // Using SetMinSize() on Mac messes up the window position in some cases // // cf. https://groups.google.com/forum/#!topic/wx-users/yUKPBBfXWO0 @@ -470,15 +502,6 @@ void MainFrame::shutdown() m_settings_dialog.Close(); if (m_plater != nullptr) { -#if ENABLE_GCODE_VIEWER - // restore sidebar if it was hidden when switching to gcode viewer mode - if (m_restore_from_gcode_viewer.collapsed_sidebar) - m_plater->collapse_sidebar(false); - - // restore sla printer if it was deselected when switching to gcode viewer mode - if (m_restore_from_gcode_viewer.sla_technology) - m_plater->set_printer_technology(ptSLA); -#endif // ENABLE_GCODE_VIEWER // Stop the background thread (Windows and Linux). // Disconnect from a 3DConnextion driver (OSX). m_plater->get_mouse3d_controller().shutdown(); @@ -506,8 +529,7 @@ void MainFrame::shutdown() void MainFrame::update_title() { wxString title = wxEmptyString; - if (m_plater != nullptr) - { + if (m_plater != nullptr) { // m_plater->get_project_filename() produces file name including path, but excluding extension. // Don't try to remove the extension, it would remove part of the file name after the last dot! wxString project = from_path(into_path(m_plater->get_project_filename()).filename()); @@ -515,7 +537,11 @@ void MainFrame::update_title() title += (project + " - "); } +#if ENABLE_GCODE_VIEWER + std::string build_id = wxGetApp().is_editor() ? SLIC3R_BUILD_ID : GCODEVIEWER_BUILD_ID; +#else std::string build_id = SLIC3R_BUILD_ID; +#endif // ENABLE_GCODE_VIEWER size_t idx_plus = build_id.find('+'); if (idx_plus != build_id.npos) { // Parse what is behind the '+'. If there is a number, then it is a build number after the label, and full build ID is shown. @@ -530,7 +556,13 @@ void MainFrame::update_title() #endif } } - title += (wxString(build_id) + " " + _(L("based on Slic3r"))); +#if ENABLE_GCODE_VIEWER + title += wxString(build_id); + if (wxGetApp().is_editor()) + title += (" " + _L("based on Slic3r")); +#else + title += (wxString(build_id) + " " + _L("based on Slic3r")); +#endif // ENABLE_GCODE_VIEWER SetTitle(title); } @@ -579,7 +611,10 @@ void MainFrame::init_tabpanel() // or when the preset's "modified" status changes. Bind(EVT_TAB_PRESETS_CHANGED, &MainFrame::on_presets_changed, this); // #ys_FIXME_to_delete - create_preset_tabs(); +#if ENABLE_GCODE_VIEWER + if (wxGetApp().is_editor()) +#endif // ENABLE_GCODE_VIEWER + create_preset_tabs(); if (m_plater) { // load initial config @@ -718,6 +753,9 @@ bool MainFrame::can_change_view() const int page_id = m_tabpanel->GetSelection(); return page_id != wxNOT_FOUND && dynamic_cast(m_tabpanel->GetPage((size_t)page_id)) != nullptr; } +#if ENABLE_GCODE_VIEWER + case ESettingsLayout::GCodeViewer: { return true; } +#endif // ENABLE_GCODE_VIEWER } } @@ -787,9 +825,6 @@ void MainFrame::on_dpi_changed(const wxRect& suggested_rect) this->SetSize(sz); this->Maximize(is_maximized); - - if (m_layout == ESettingsLayout::Dlg) - rescale_dialog_after_dpi_change(*this, m_settings_dialog, ERescaleTarget::SettingsDialog); } void MainFrame::on_sys_color_changed() @@ -847,9 +882,17 @@ static wxMenu* generate_help_menu() [](wxCommandEvent&) { Slic3r::GUI::desktop_open_datadir_folder(); }); append_menu_item(helpMenu, wxID_ANY, _(L"Report an I&ssue"), wxString::Format(_L("Report an issue on %s"), SLIC3R_APP_NAME), [](wxCommandEvent&) { wxLaunchDefaultBrowser("https://github.com/prusa3d/slic3r/issues/new"); }); - append_menu_item(helpMenu, wxID_ANY, wxString::Format(_L("&About %s"), SLIC3R_APP_NAME), _L("Show about dialog"), - [](wxCommandEvent&) { Slic3r::GUI::about(); }); - helpMenu->AppendSeparator(); +#if ENABLE_GCODE_VIEWER + if (wxGetApp().is_editor()) +#endif // ENABLE_GCODE_VIEWER + append_menu_item(helpMenu, wxID_ANY, wxString::Format(_L("&About %s"), SLIC3R_APP_NAME), _L("Show about dialog"), + [](wxCommandEvent&) { Slic3r::GUI::about(); }); +#if ENABLE_GCODE_VIEWER + else + append_menu_item(helpMenu, wxID_ANY, wxString::Format(_L("&About %s"), GCODEVIEWER_APP_NAME), _L("Show about dialog"), + [](wxCommandEvent&) { Slic3r::GUI::about(); }); +#endif // ENABLE_GCODE_VIEWER + helpMenu->AppendSeparator(); append_menu_item(helpMenu, wxID_ANY, _L("Keyboard Shortcuts") + sep + "&?", _L("Show the list of the keyboard shortcuts"), [](wxCommandEvent&) { wxGetApp().keyboard_shortcuts(); }); #if ENABLE_THUMBNAIL_GENERATOR_DEBUG @@ -883,7 +926,7 @@ static void add_common_view_menu_items(wxMenu* view_menu, MainFrame* mainFrame, "", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame); } -void MainFrame::init_editor_menubar() +void MainFrame::init_menubar_as_editor() #else void MainFrame::init_menubar() #endif // ENABLE_GCODE_VIEWER @@ -1046,16 +1089,9 @@ void MainFrame::init_menubar() append_menu_item(fileMenu, wxID_ANY, _L("&Repair STL file") + dots, _L("Automatically repair an STL file"), [this](wxCommandEvent&) { repair_stl(); }, "wrench", nullptr, [this]() { return true; }, this); -#if ENABLE_GCODE_VIEWER fileMenu->AppendSeparator(); - append_menu_item(fileMenu, wxID_ANY, _L("&G-code preview"), _L("Switch to G-code preview mode"), - [this](wxCommandEvent&) { - if (m_plater->model().objects.empty() || - wxMessageDialog((wxWindow*)this, _L("Switching to G-code preview mode will remove all objects, continue?"), - wxString(SLIC3R_APP_NAME) + " - " + _L("Switch to G-code preview mode"), wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION | wxCENTRE).ShowModal() == wxID_YES) - set_mode(EMode::GCodeViewer); - }, "", nullptr); -#endif // ENABLE_GCODE_VIEWER + append_menu_item(fileMenu, wxID_ANY, _L("&G-code preview") + dots, _L("Open G-code viewer"), + [this](wxCommandEvent&) { start_new_gcodeviewer_open_file(this); }, "", nullptr); fileMenu->AppendSeparator(); append_menu_item(fileMenu, wxID_EXIT, _L("&Quit"), wxString::Format(_L("Quit %s"), SLIC3R_APP_NAME), [this](wxCommandEvent&) { Close(false); }); @@ -1172,20 +1208,11 @@ void MainFrame::init_menubar() windowMenu->AppendSeparator(); append_menu_item(windowMenu, wxID_ANY, _L("Print &Host Upload Queue") + "\tCtrl+J", _L("Display the Print Host Upload Queue window"), - [this](wxCommandEvent&) { m_printhost_queue_dlg->Show(); }, "upload_queue", nullptr, - [this]() {return true; }, this); + [this](wxCommandEvent&) { m_printhost_queue_dlg->Show(); }, "upload_queue", nullptr, [this]() {return true; }, this); windowMenu->AppendSeparator(); append_menu_item(windowMenu, wxID_ANY, _(L("Open new instance")) + "\tCtrl+I", _(L("Open a new PrusaSlicer instance")), - [this](wxCommandEvent&) { - wxString path = wxStandardPaths::Get().GetExecutablePath(); -#ifdef __APPLE__ - boost::process::spawn((const char*)path.c_str()); -#else - wxExecute(wxStandardPaths::Get().GetExecutablePath(), wxEXEC_ASYNC | wxEXEC_HIDE_CONSOLE | wxEXEC_MAKE_GROUP_LEADER); -#endif - }, "upload_queue", nullptr, - [this]() {return true; }, this); + [this](wxCommandEvent&) { start_new_slicer(); }, "", nullptr); } // View menu @@ -1278,24 +1305,24 @@ void MainFrame::init_menubar() // assign menubar to frame after appending items, otherwise special items // will not be handled correctly #if ENABLE_GCODE_VIEWER - m_editor_menubar = new wxMenuBar(); - m_editor_menubar->Append(fileMenu, _L("&File")); - if (editMenu) m_editor_menubar->Append(editMenu, _L("&Edit")); - m_editor_menubar->Append(windowMenu, _L("&Window")); - if (viewMenu) m_editor_menubar->Append(viewMenu, _L("&View")); + m_menubar = new wxMenuBar(); + m_menubar->Append(fileMenu, _L("&File")); + if (editMenu) m_menubar->Append(editMenu, _L("&Edit")); + m_menubar->Append(windowMenu, _L("&Window")); + if (viewMenu) m_menubar->Append(viewMenu, _L("&View")); // Add additional menus from C++ - wxGetApp().add_config_menu(m_editor_menubar); - m_editor_menubar->Append(helpMenu, _L("&Help")); - SetMenuBar(m_editor_menubar); + wxGetApp().add_config_menu(m_menubar); + m_menubar->Append(helpMenu, _L("&Help")); + SetMenuBar(m_menubar); #else auto menubar = new wxMenuBar(); - menubar->Append(fileMenu, _(L("&File"))); - if (editMenu) menubar->Append(editMenu, _(L("&Edit"))); - menubar->Append(windowMenu, _(L("&Window"))); - if (viewMenu) menubar->Append(viewMenu, _(L("&View"))); + menubar->Append(fileMenu, _L("&File")); + if (editMenu) menubar->Append(editMenu, _L("&Edit")); + menubar->Append(windowMenu, _L("&Window")); + if (viewMenu) menubar->Append(viewMenu, _L("&View")); // Add additional menus from C++ wxGetApp().add_config_menu(menubar); - menubar->Append(helpMenu, _(L("&Help"))); + menubar->Append(helpMenu, _L("&Help")); SetMenuBar(menubar); #endif // ENABLE_GCODE_VIEWER @@ -1303,7 +1330,7 @@ void MainFrame::init_menubar() // This fixes a bug on Mac OS where the quit command doesn't emit window close events // wx bug: https://trac.wxwidgets.org/ticket/18328 #if ENABLE_GCODE_VIEWER - wxMenu* apple_menu = m_editor_menubar->OSXGetAppleMenu(); + wxMenu* apple_menu = m_menubar->OSXGetAppleMenu(); #else wxMenu *apple_menu = menubar->OSXGetAppleMenu(); #endif // ENABLE_GCODE_VIEWER @@ -1312,18 +1339,14 @@ void MainFrame::init_menubar() Close(); }, wxID_EXIT); } -#endif +#endif // __APPLE__ if (plater()->printer_technology() == ptSLA) -#if ENABLE_GCODE_VIEWER - update_editor_menubar(); -#else update_menubar(); -#endif // ENABLE_GCODE_VIEWER } #if ENABLE_GCODE_VIEWER -void MainFrame::init_gcodeviewer_menubar() +void MainFrame::init_menubar_as_gcodeviewer() { wxMenu* fileMenu = new wxMenu; { @@ -1335,9 +1358,6 @@ void MainFrame::init_gcodeviewer_menubar() [this](wxCommandEvent&) { if (m_plater != nullptr) m_plater->export_toolpaths_to_obj(); }, "export_plater", nullptr, [this]() {return can_export_toolpaths(); }, this); fileMenu->AppendSeparator(); - append_menu_item(fileMenu, wxID_ANY, _L("Exit &G-code preview"), _L("Switch to editor mode"), - [this](wxCommandEvent&) { set_mode(EMode::Editor); }); - fileMenu->AppendSeparator(); append_menu_item(fileMenu, wxID_EXIT, _L("&Quit"), wxString::Format(_L("Quit %s"), SLIC3R_APP_NAME), [this](wxCommandEvent&) { Close(false); }); } @@ -1352,160 +1372,38 @@ void MainFrame::init_gcodeviewer_menubar() // helpmenu auto helpMenu = generate_help_menu(); - m_gcodeviewer_menubar = new wxMenuBar(); - m_gcodeviewer_menubar->Append(fileMenu, _L("&File")); - if ((viewMenu != nullptr)) - m_gcodeviewer_menubar->Append(viewMenu, _L("&View")); - m_gcodeviewer_menubar->Append(helpMenu, _L("&Help")); -} + m_menubar = new wxMenuBar(); + m_menubar->Append(fileMenu, _L("&File")); + if (viewMenu != nullptr) m_menubar->Append(viewMenu, _L("&View")); + m_menubar->Append(helpMenu, _L("&Help")); + SetMenuBar(m_menubar); -void MainFrame::set_mode(EMode mode) -{ - if (m_mode == mode) - return; - - wxBusyCursor busy; - - m_mode = mode; - switch (m_mode) - { - default: - case EMode::Editor: - { - update_layout(); - select_tab(0); - - m_plater->reset(); - m_plater->reset_gcode_toolpaths(); - - m_plater->Freeze(); - - // reinitialize undo/redo stack - m_plater->clear_undo_redo_stack_main(); - m_plater->take_snapshot(_L("New Project")); - - // restore sla printer if it was deselected when switching to gcode viewer mode - if (m_restore_from_gcode_viewer.sla_technology) { - m_plater->set_printer_technology(ptSLA); - m_restore_from_gcode_viewer.sla_technology = false; - } - - // switch view - m_plater->select_view_3D("3D"); - m_plater->select_view("iso"); - - // switch printbed - m_plater->set_bed_shape(); - - // switch menubar - SetMenuBar(m_editor_menubar); - - // show toolbars - m_plater->enable_view_toolbar(true); - - if (m_restore_from_gcode_viewer.collapse_toolbar_enabled) { - m_plater->get_collapse_toolbar().set_enabled(true); - m_restore_from_gcode_viewer.collapse_toolbar_enabled = false; - } - - // show sidebar - if (m_restore_from_gcode_viewer.collapsed_sidebar) { - m_plater->collapse_sidebar(false); - m_restore_from_gcode_viewer.collapsed_sidebar = false; - } - - m_plater->Thaw(); - -// SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG)); - // Load the icon either from the exe, or from the ico file. -#if _WIN32 - { - - TCHAR szExeFileName[MAX_PATH]; - GetModuleFileName(nullptr, szExeFileName, MAX_PATH); - SetIcon(wxIcon(szExeFileName, wxBITMAP_TYPE_ICO)); - } -#else - SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG)); -#endif // _WIN32 -#if ENABLE_GCODE_VIEWER_TASKBAR_ICON - if (m_taskbar_icon != nullptr) { - m_taskbar_icon->RemoveIcon(); - m_taskbar_icon->SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG), "PrusaSlicer"); - } -#endif // ENABLE_GCODE_VIEWER_TASKBAR_ICON - - break; - } - case EMode::GCodeViewer: - { - update_layout(); - - m_plater->reset(); - m_plater->reset_last_loaded_gcode(); - m_plater->reset_gcode_toolpaths(); - - m_plater->Freeze(); - - // reinitialize undo/redo stack - m_plater->clear_undo_redo_stack_main(); - m_plater->take_snapshot(_L("New Project")); - - // switch to FFF printer mode - m_restore_from_gcode_viewer.sla_technology = m_plater->set_printer_technology(ptFFF); - - // switch view - m_plater->select_view_3D("Preview"); - m_plater->select_view("iso"); - - // switch printbed - m_plater->set_bed_shape({ { 0.0, 0.0 }, { 200.0, 0.0 }, { 200.0, 200.0 }, { 0.0, 200.0 } }, "", "", true); - - // switch menubar - SetMenuBar(m_gcodeviewer_menubar); - - // hide toolbars - m_plater->enable_view_toolbar(false); - - if (wxGetApp().app_config->get("show_collapse_button") == "1") { - m_plater->get_collapse_toolbar().set_enabled(false); - m_restore_from_gcode_viewer.collapse_toolbar_enabled = true; - } - - // hide sidebar - if (wxGetApp().app_config->get("collapsed_sidebar") != "1") { - m_plater->collapse_sidebar(true); - m_restore_from_gcode_viewer.collapsed_sidebar = true; - } - - m_plater->Thaw(); - - SetIcon(wxIcon(Slic3r::var("PrusaSlicerGCodeViewer_128px.png"), wxBITMAP_TYPE_PNG)); -#if ENABLE_GCODE_VIEWER_TASKBAR_ICON - if (m_taskbar_icon != nullptr) { - m_taskbar_icon->RemoveIcon(); - m_taskbar_icon->SetIcon(wxIcon(Slic3r::var("PrusaSlicerGCodeViewer_128px.png"), wxBITMAP_TYPE_PNG), "PrusaSlicer-GCode viewer"); - } -#endif // ENABLE_GCODE_VIEWER_TASKBAR_ICON - - break; - } +#ifdef __APPLE__ + // This fixes a bug on Mac OS where the quit command doesn't emit window close events + // wx bug: https://trac.wxwidgets.org/ticket/18328 + wxMenu* apple_menu = m_menubar->OSXGetAppleMenu(); + if (apple_menu != nullptr) { + apple_menu->Bind(wxEVT_MENU, [this](wxCommandEvent&) { + Close(); + }, wxID_EXIT); } +#endif // __APPLE__ } #endif // ENABLE_GCODE_VIEWER -#if ENABLE_GCODE_VIEWER -void MainFrame::update_editor_menubar() -#else void MainFrame::update_menubar() -#endif // ENABLE_GCODE_VIEWER { +#if ENABLE_GCODE_VIEWER + if (wxGetApp().is_gcode_viewer()) + return; +#endif // ENABLE_GCODE_VIEWER + const bool is_fff = plater()->printer_technology() == ptFFF; - m_changeable_menu_items[miExport] ->SetItemLabel((is_fff ? _(L("Export &G-code")) : _(L("E&xport")) ) + dots + "\tCtrl+G"); - m_changeable_menu_items[miSend] ->SetItemLabel((is_fff ? _(L("S&end G-code")) : _(L("S&end to print"))) + dots + "\tCtrl+Shift+G"); + m_changeable_menu_items[miExport] ->SetItemLabel((is_fff ? _L("Export &G-code") : _L("E&xport")) + dots + "\tCtrl+G"); + m_changeable_menu_items[miSend] ->SetItemLabel((is_fff ? _L("S&end G-code") : _L("S&end to print")) + dots + "\tCtrl+Shift+G"); - m_changeable_menu_items[miMaterialTab] ->SetItemLabel((is_fff ? _(L("&Filament Settings Tab")) : _(L("Mate&rial Settings Tab"))) + "\tCtrl+3"); + m_changeable_menu_items[miMaterialTab] ->SetItemLabel((is_fff ? _L("&Filament Settings Tab") : _L("Mate&rial Settings Tab")) + "\tCtrl+3"); m_changeable_menu_items[miMaterialTab] ->SetBitmap(create_scaled_bitmap(is_fff ? "spool" : "resin")); m_changeable_menu_items[miPrinterTab] ->SetBitmap(create_scaled_bitmap(is_fff ? "printer" : "sla_printer")); @@ -1988,11 +1886,21 @@ SettingsDialog::SettingsDialog(MainFrame* mainframe) wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMINIMIZE_BOX | wxMAXIMIZE_BOX, "settings_dialog"), m_main_frame(mainframe) { +#if ENABLE_GCODE_VIEWER + if (wxGetApp().is_gcode_viewer()) + return; +#endif // ENABLE_GCODE_VIEWER + +#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT && defined(__WXMSW__) + // ys_FIXME! temporary workaround for correct font scaling + // Because of from wxWidgets 3.1.3 auto rescaling is implemented for the Fonts, + // From the very beginning set dialog font to the wxSYS_DEFAULT_GUI_FONT + this->SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); +#else this->SetFont(wxGetApp().normal_font()); +#endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); - -// SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG)); // Load the icon either from the exe, or from the ico file. #if _WIN32 { @@ -2055,6 +1963,11 @@ SettingsDialog::SettingsDialog(MainFrame* mainframe) void SettingsDialog::on_dpi_changed(const wxRect& suggested_rect) { +#if ENABLE_GCODE_VIEWER + if (wxGetApp().is_gcode_viewer()) + return; +#endif // ENABLE_GCODE_VIEWER + const int& em = em_unit(); const wxSize& size = wxSize(85 * em, 50 * em); diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 7777a053d..868a68492 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -57,7 +57,7 @@ class SettingsDialog : public DPIDialog MainFrame* m_main_frame { nullptr }; public: SettingsDialog(MainFrame* mainframe); - ~SettingsDialog() {} + ~SettingsDialog() = default; void set_tabpanel(wxNotebook* tabpanel) { m_tabpanel = tabpanel; } protected: @@ -72,17 +72,7 @@ class MainFrame : public DPIFrame wxString m_qs_last_output_file = wxEmptyString; wxString m_last_config = wxEmptyString; #if ENABLE_GCODE_VIEWER - wxMenuBar* m_editor_menubar{ nullptr }; - wxMenuBar* m_gcodeviewer_menubar{ nullptr }; - - struct RestoreFromGCodeViewer - { - bool collapsed_sidebar{ false }; - bool collapse_toolbar_enabled{ false }; - bool sla_technology{ false }; - }; - - RestoreFromGCodeViewer m_restore_from_gcode_viewer; + wxMenuBar* m_menubar{ nullptr }; #endif // ENABLE_GCODE_VIEWER #if 0 @@ -145,18 +135,6 @@ class MainFrame : public DPIFrame ESettingsLayout m_layout{ ESettingsLayout::Unknown }; -#if ENABLE_GCODE_VIEWER -public: - enum class EMode : unsigned char - { - Editor, - GCodeViewer - }; - -private: - EMode m_mode{ EMode::Editor }; -#endif // ENABLE_GCODE_VIEWER - protected: virtual void on_dpi_changed(const wxRect &suggested_rect); virtual void on_sys_color_changed() override; @@ -182,16 +160,12 @@ public: void create_preset_tabs(); void add_created_tab(Tab* panel); #if ENABLE_GCODE_VIEWER - void init_editor_menubar(); - void update_editor_menubar(); - void init_gcodeviewer_menubar(); - - EMode get_mode() const { return m_mode; } - void set_mode(EMode mode); + void init_menubar_as_editor(); + void init_menubar_as_gcodeviewer(); #else void init_menubar(); - void update_menubar(); #endif // ENABLE_GCODE_VIEWER + void update_menubar(); void update_ui_from_settings(); bool is_loaded() const { return m_loaded; } diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index 336475d2e..79fedfa52 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -1549,245 +1549,6 @@ void ObjectDataViewModel::DeleteWarningIcon(const wxDataViewItem& item, const bo DeleteWarningIcon(child); } } -/* -} -} -*/ -//----------------------------------------------------------------------------- -// DataViewBitmapText -//----------------------------------------------------------------------------- - -wxIMPLEMENT_DYNAMIC_CLASS(DataViewBitmapText, wxObject) - -IMPLEMENT_VARIANT_OBJECT(DataViewBitmapText) - -// --------------------------------------------------------- -// BitmapTextRenderer -// --------------------------------------------------------- - -#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING -BitmapTextRenderer::BitmapTextRenderer(wxDataViewCellMode mode /*= wxDATAVIEW_CELL_EDITABLE*/, - int align /*= wxDVR_DEFAULT_ALIGNMENT*/): -wxDataViewRenderer(wxT("PrusaDataViewBitmapText"), mode, align) -{ - SetMode(mode); - SetAlignment(align); -} -#endif // ENABLE_NONCUSTOM_DATA_VIEW_RENDERING - -bool BitmapTextRenderer::SetValue(const wxVariant &value) -{ - m_value << value; - return true; -} - -bool BitmapTextRenderer::GetValue(wxVariant& WXUNUSED(value)) const -{ - return false; -} - -#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING && wxUSE_ACCESSIBILITY -wxString BitmapTextRenderer::GetAccessibleDescription() const -{ - return m_value.GetText(); -} -#endif // wxUSE_ACCESSIBILITY && ENABLE_NONCUSTOM_DATA_VIEW_RENDERING - -bool BitmapTextRenderer::Render(wxRect rect, wxDC *dc, int state) -{ - int xoffset = 0; - - const wxBitmap& icon = m_value.GetBitmap(); - if (icon.IsOk()) - { -#ifdef __APPLE__ - wxSize icon_sz = icon.GetScaledSize(); -#else - wxSize icon_sz = icon.GetSize(); -#endif - dc->DrawBitmap(icon, rect.x, rect.y + (rect.height - icon_sz.y) / 2); - xoffset = icon_sz.x + 4; - } - - RenderText(m_value.GetText(), xoffset, rect, dc, state); - - return true; -} - -wxSize BitmapTextRenderer::GetSize() const -{ - if (!m_value.GetText().empty()) - { - wxSize size = GetTextExtent(m_value.GetText()); - - if (m_value.GetBitmap().IsOk()) - size.x += m_value.GetBitmap().GetWidth() + 4; - return size; - } - return wxSize(80, 20); -} - - -wxWindow* BitmapTextRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelRect, const wxVariant& value) -{ - wxDataViewCtrl* const dv_ctrl = GetOwner()->GetOwner(); - ObjectDataViewModel* const model = dynamic_cast(dv_ctrl->GetModel()); - - if ( !(model->GetItemType(dv_ctrl->GetSelection()) & (itVolume | itObject)) ) - return nullptr; - - DataViewBitmapText data; - data << value; - - m_was_unusable_symbol = false; - - wxPoint position = labelRect.GetPosition(); - if (data.GetBitmap().IsOk()) { - const int bmp_width = data.GetBitmap().GetWidth(); - position.x += bmp_width; - labelRect.SetWidth(labelRect.GetWidth() - bmp_width); - } - - wxTextCtrl* text_editor = new wxTextCtrl(parent, wxID_ANY, data.GetText(), - position, labelRect.GetSize(), wxTE_PROCESS_ENTER); - text_editor->SetInsertionPointEnd(); - text_editor->SelectAll(); - - return text_editor; -} - -bool BitmapTextRenderer::GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) -{ - wxTextCtrl* text_editor = wxDynamicCast(ctrl, wxTextCtrl); - if (!text_editor || text_editor->GetValue().IsEmpty()) - return false; - - std::string chosen_name = Slic3r::normalize_utf8_nfc(text_editor->GetValue().ToUTF8()); - const char* unusable_symbols = "<>:/\\|?*\""; - for (size_t i = 0; i < std::strlen(unusable_symbols); i++) { - if (chosen_name.find_first_of(unusable_symbols[i]) != std::string::npos) { - m_was_unusable_symbol = true; - return false; - } - } - - // The icon can't be edited so get its old value and reuse it. - wxVariant valueOld; - GetView()->GetModel()->GetValue(valueOld, m_item, colName); - - DataViewBitmapText bmpText; - bmpText << valueOld; - - // But replace the text with the value entered by user. - bmpText.SetText(text_editor->GetValue()); - - value << bmpText; - return true; -} - -// ---------------------------------------------------------------------------- -// BitmapChoiceRenderer -// ---------------------------------------------------------------------------- - -bool BitmapChoiceRenderer::SetValue(const wxVariant& value) -{ - m_value << value; - return true; -} - -bool BitmapChoiceRenderer::GetValue(wxVariant& value) const -{ - value << m_value; - return true; -} - -bool BitmapChoiceRenderer::Render(wxRect rect, wxDC* dc, int state) -{ - int xoffset = 0; - - const wxBitmap& icon = m_value.GetBitmap(); - if (icon.IsOk()) - { - dc->DrawBitmap(icon, rect.x, rect.y + (rect.height - icon.GetHeight()) / 2); - xoffset = icon.GetWidth() + 4; - - if (rect.height==0) - rect.height= icon.GetHeight(); - } - - RenderText(m_value.GetText(), xoffset, rect, dc, state); - - return true; -} - -wxSize BitmapChoiceRenderer::GetSize() const -{ - wxSize sz = GetTextExtent(m_value.GetText()); - - if (m_value.GetBitmap().IsOk()) - sz.x += m_value.GetBitmap().GetWidth() + 4; - - return sz; -} - - -wxWindow* BitmapChoiceRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelRect, const wxVariant& value) -{ - wxDataViewCtrl* const dv_ctrl = GetOwner()->GetOwner(); - ObjectDataViewModel* const model = dynamic_cast(dv_ctrl->GetModel()); - - if (!(model->GetItemType(dv_ctrl->GetSelection()) & (itVolume | itLayer | itObject))) - return nullptr; - - std::vector icons = get_extruder_color_icons(); - if (icons.empty()) - return nullptr; - - DataViewBitmapText data; - data << value; - - auto c_editor = new wxBitmapComboBox(parent, wxID_ANY, wxEmptyString, - labelRect.GetTopLeft(), wxSize(labelRect.GetWidth(), -1), - 0, nullptr , wxCB_READONLY); - - int i=0; - for (wxBitmap* bmp : icons) { - if (i==0) { - c_editor->Append(_(L("default")), *bmp); - ++i; - } - - c_editor->Append(wxString::Format("%d", i), *bmp); - ++i; - } - c_editor->SetSelection(atoi(data.GetText().c_str())); - - // to avoid event propagation to other sidebar items - c_editor->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) { - evt.StopPropagation(); - // FinishEditing grabs new selection and triggers config update. We better call - // it explicitly, automatic update on KILL_FOCUS didn't work on Linux. - this->FinishEditing(); - }); - - return c_editor; -} - -bool BitmapChoiceRenderer::GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) -{ - wxBitmapComboBox* c = (wxBitmapComboBox*)ctrl; - int selection = c->GetSelection(); - if (selection < 0) - return false; - - DataViewBitmapText bmpText; - - bmpText.SetText(c->GetString(selection)); - bmpText.SetBitmap(c->GetItemBitmap(selection)); - - value << bmpText; - return true; -} } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/ObjectDataViewModel.hpp b/src/slic3r/GUI/ObjectDataViewModel.hpp index c18484266..12480139d 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.hpp +++ b/src/slic3r/GUI/ObjectDataViewModel.hpp @@ -2,9 +2,10 @@ #define slic3r_GUI_ObjectDataViewModel_hpp_ #include - #include +#include "ExtraRenderers.hpp" + namespace Slic3r { enum class ModelVolumeType : int; @@ -14,143 +15,6 @@ namespace GUI { typedef double coordf_t; typedef std::pair t_layer_height_range; -// ---------------------------------------------------------------------------- -// DataViewBitmapText: helper class used by BitmapTextRenderer -// ---------------------------------------------------------------------------- - -class DataViewBitmapText : public wxObject -{ -public: - DataViewBitmapText( const wxString &text = wxEmptyString, - const wxBitmap& bmp = wxNullBitmap) : - m_text(text), - m_bmp(bmp) - { } - - DataViewBitmapText(const DataViewBitmapText &other) - : wxObject(), - m_text(other.m_text), - m_bmp(other.m_bmp) - { } - - void SetText(const wxString &text) { m_text = text; } - wxString GetText() const { return m_text; } - void SetBitmap(const wxBitmap &bmp) { m_bmp = bmp; } - const wxBitmap &GetBitmap() const { return m_bmp; } - - bool IsSameAs(const DataViewBitmapText& other) const { - return m_text == other.m_text && m_bmp.IsSameAs(other.m_bmp); - } - - bool operator==(const DataViewBitmapText& other) const { - return IsSameAs(other); - } - - bool operator!=(const DataViewBitmapText& other) const { - return !IsSameAs(other); - } - -private: - wxString m_text; - wxBitmap m_bmp; - - wxDECLARE_DYNAMIC_CLASS(DataViewBitmapText); -}; -DECLARE_VARIANT_OBJECT(DataViewBitmapText) - -// ---------------------------------------------------------------------------- -// BitmapTextRenderer -// ---------------------------------------------------------------------------- -#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING -class BitmapTextRenderer : public wxDataViewRenderer -#else -class BitmapTextRenderer : public wxDataViewCustomRenderer -#endif //ENABLE_NONCUSTOM_DATA_VIEW_RENDERING -{ -public: - BitmapTextRenderer(wxWindow* parent, - wxDataViewCellMode mode = -#ifdef __WXOSX__ - wxDATAVIEW_CELL_INERT -#else - wxDATAVIEW_CELL_EDITABLE -#endif - - , int align = wxDVR_DEFAULT_ALIGNMENT -#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING - ); -#else - ) : - wxDataViewCustomRenderer(wxT("DataViewBitmapText"), mode, align), - m_parent(parent) - {} -#endif //ENABLE_NONCUSTOM_DATA_VIEW_RENDERING - - bool SetValue(const wxVariant& value); - bool GetValue(wxVariant& value) const; -#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING && wxUSE_ACCESSIBILITY - virtual wxString GetAccessibleDescription() const override; -#endif // wxUSE_ACCESSIBILITY && ENABLE_NONCUSTOM_DATA_VIEW_RENDERING - - virtual bool Render(wxRect cell, wxDC* dc, int state) override; - virtual wxSize GetSize() const override; - - bool HasEditorCtrl() const override - { -#ifdef __WXOSX__ - return false; -#else - return true; -#endif - } - wxWindow* CreateEditorCtrl(wxWindow* parent, - wxRect labelRect, - const wxVariant& value) override; - bool GetValueFromEditorCtrl(wxWindow* ctrl, - wxVariant& value) override; - bool WasCanceled() const { return m_was_unusable_symbol; } - -private: - DataViewBitmapText m_value; - bool m_was_unusable_symbol{ false }; - wxWindow* m_parent{ nullptr }; -}; - - -// ---------------------------------------------------------------------------- -// BitmapChoiceRenderer -// ---------------------------------------------------------------------------- - -class BitmapChoiceRenderer : public wxDataViewCustomRenderer -{ -public: - BitmapChoiceRenderer(wxDataViewCellMode mode = -#ifdef __WXOSX__ - wxDATAVIEW_CELL_INERT -#else - wxDATAVIEW_CELL_EDITABLE -#endif - , int align = wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL - ) : wxDataViewCustomRenderer(wxT("DataViewBitmapText"), mode, align) {} - - bool SetValue(const wxVariant& value); - bool GetValue(wxVariant& value) const; - - virtual bool Render(wxRect cell, wxDC* dc, int state) override; - virtual wxSize GetSize() const override; - - bool HasEditorCtrl() const override { return true; } - wxWindow* CreateEditorCtrl(wxWindow* parent, - wxRect labelRect, - const wxVariant& value) override; - bool GetValueFromEditorCtrl(wxWindow* ctrl, - wxVariant& value) override; - -private: - DataViewBitmapText m_value; -}; - - // ---------------------------------------------------------------------------- // ObjectDataViewModelNode: a node inside ObjectDataViewModel // ---------------------------------------------------------------------------- diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 1bebb8827..14defd9a9 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -7,6 +7,7 @@ #include #include #include +#include "libslic3r/Exception.hpp" #include "libslic3r/Utils.hpp" #include "I18N.hpp" @@ -64,7 +65,7 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co break; case coNone: break; default: - throw /*//!ConfigGUITypeError("")*/std::logic_error("This control doesn't exist till now"); break; + throw Slic3r::LogicError("This control doesn't exist till now"); break; } } // Grab a reference to fields for convenience @@ -466,7 +467,8 @@ void ConfigOptionsGroup::back_to_config_value(const DynamicPrintConfig& config, } else if (m_opt_map.find(opt_key) == m_opt_map.end() || // This option don't have corresponded field - opt_key == "bed_shape" || opt_key == "compatible_printers" || opt_key == "compatible_prints" ) { + opt_key == "bed_shape" || opt_key == "filament_ramming_parameters" || + opt_key == "compatible_printers" || opt_key == "compatible_prints" ) { value = get_config_value(config, opt_key); change_opt_value(*m_config, opt_key, value); return; @@ -619,7 +621,7 @@ boost::any ConfigOptionsGroup::config_value(const std::string& opt_key, int opt_ // Aggregate the strings the old way. // Currently used for the post_process config value only. if (opt_index != -1) - throw std::out_of_range("Can't deserialize option indexed value"); + throw Slic3r::OutOfRange("Can't deserialize option indexed value"); // return join(';', m_config->get(opt_key)}); return get_config_value(*m_config, opt_key); } @@ -699,6 +701,10 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config ret = config.option(opt_key)->values; break; } + if (opt_key == "filament_ramming_parameters") { + ret = config.opt_string(opt_key, static_cast(idx)); + break; + } if (config.option(opt_key)->values.empty()) ret = text_value; else if (opt->gui_flags.compare("serialized") == 0) { @@ -750,7 +756,7 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config else if (opt_key == "support_pillar_connection_mode") { ret = static_cast(config.option>(opt_key)->value); } - else if (opt_key == "authorization_type") { + else if (opt_key == "printhost_authorization_type") { ret = static_cast(config.option>(opt_key)->value); } } diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp index 12d1cd287..3d832ae56 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -155,8 +155,9 @@ void PresetForPrinter::msw_rescale() // PhysicalPrinterDialog //------------------------------------------ -PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) - : DPIDialog(NULL, wxID_ANY, _L("Physical Printer"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) +PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) : + DPIDialog(NULL, wxID_ANY, _L("Physical Printer"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), + m_printer("", wxGetApp().preset_bundle->physical_printers.default_config()) { SetFont(wxGetApp().normal_font()); SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); @@ -186,7 +187,7 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) PhysicalPrinter* printer = printers.find_printer(into_u8(printer_name)); if (!printer) { const Preset& preset = wxGetApp().preset_bundle->printers.get_edited_preset(); - printer = new PhysicalPrinter(into_u8(printer_name), preset); + m_printer = PhysicalPrinter(into_u8(printer_name), m_printer.config, preset); // if printer_name is empty it means that new printer is created, so enable all items in the preset list m_presets.emplace_back(new PresetForPrinter(this, preset.name)); } @@ -195,9 +196,8 @@ PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name) const std::set& preset_names = printer->get_preset_names(); for (const std::string& preset_name : preset_names) m_presets.emplace_back(new PresetForPrinter(this, preset_name)); + m_printer = *printer; } - assert(printer); - m_printer = *printer; if (m_presets.size() == 1) m_presets.front()->SuppressDelete(); @@ -249,7 +249,7 @@ PhysicalPrinterDialog::~PhysicalPrinterDialog() void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgroup) { m_optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { - if (opt_key == "authorization_type") + if (opt_key == "printhost_authorization_type") this->update(); }; @@ -308,7 +308,7 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr host_line.append_widget(print_host_test); m_optgroup->append_line(host_line); - m_optgroup->append_single_option_line("authorization_type"); + m_optgroup->append_single_option_line("printhost_authorization_type"); option = m_optgroup->get_option("printhost_apikey"); option.opt.width = Field::def_width_wider(); @@ -368,7 +368,7 @@ void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgr m_optgroup->append_line(line); } - for (const std::string& opt_key : std::vector{ "login", "password" }) { + for (const std::string& opt_key : std::vector{ "printhost_user", "printhost_password" }) { option = m_optgroup->get_option(opt_key); option.opt.width = Field::def_width_wider(); m_optgroup->append_single_option_line(option); @@ -385,20 +385,20 @@ void PhysicalPrinterDialog::update() // Only offer the host type selection for FFF, for SLA it's always the SL1 printer (at the moment) if (tech == ptFFF) { m_optgroup->show_field("host_type"); - m_optgroup->hide_field("authorization_type"); - for (const std::string& opt_key : std::vector{ "login", "password" }) + m_optgroup->hide_field("printhost_authorization_type"); + for (const std::string& opt_key : std::vector{ "printhost_user", "printhost_password" }) m_optgroup->hide_field(opt_key); } else { m_optgroup->set_value("host_type", int(PrintHostType::htOctoPrint), false); m_optgroup->hide_field("host_type"); - m_optgroup->show_field("authorization_type"); + m_optgroup->show_field("printhost_authorization_type"); - AuthorizationType auth_type = m_config->option>("authorization_type")->value; + AuthorizationType auth_type = m_config->option>("printhost_authorization_type")->value; m_optgroup->show_field("printhost_apikey", auth_type == AuthorizationType::atKeyPassword); - for (const std::string& opt_key : std::vector{ "login", "password" }) + for (const std::string& opt_key : std::vector{ "printhost_user", "printhost_password" }) m_optgroup->show_field(opt_key, auth_type == AuthorizationType::atUserPassword); } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 45a1f6ea8..8bec7a7a2 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -107,7 +107,7 @@ namespace GUI { wxDEFINE_EVENT(EVT_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent); wxDEFINE_EVENT(EVT_SLICING_UPDATE, SlicingStatusEvent); wxDEFINE_EVENT(EVT_SLICING_COMPLETED, wxCommandEvent); -wxDEFINE_EVENT(EVT_PROCESS_COMPLETED, wxCommandEvent); +wxDEFINE_EVENT(EVT_PROCESS_COMPLETED, SlicingProcessCompletedEvent); wxDEFINE_EVENT(EVT_EXPORT_BEGAN, wxCommandEvent); // Sidebar widgets @@ -1166,8 +1166,27 @@ void Sidebar::update_sliced_info_sizer() p->sliced_info->SetTextAndShow(siCost, info_text, new_label); #if ENABLE_GCODE_VIEWER - // hide the estimate time - p->sliced_info->SetTextAndShow(siEstimatedTime, "N/A"); + if (ps.estimated_normal_print_time == "N/A" && ps.estimated_silent_print_time == "N/A") + p->sliced_info->SetTextAndShow(siEstimatedTime, "N/A"); + else { + info_text = ""; + new_label = _L("Estimated printing time") + ":"; + if (ps.estimated_normal_print_time != "N/A") { + new_label += format_wxstr("\n - %1%", _L("normal mode")); + info_text += format_wxstr("\n%1%", short_time(ps.estimated_normal_print_time)); + + // uncomment next line to not disappear slicing finished notif when colapsing sidebar before time estimate + //if (p->plater->is_sidebar_collapsed()) + p->plater->get_notification_manager()->set_slicing_complete_large(p->plater->is_sidebar_collapsed()); + p->plater->get_notification_manager()->set_slicing_complete_print_time("Estimated printing time: " + ps.estimated_normal_print_time); + + } + if (ps.estimated_silent_print_time != "N/A") { + new_label += format_wxstr("\n - %1%", _L("stealth mode")); + info_text += format_wxstr("\n%1%", short_time(ps.estimated_silent_print_time)); + } + p->sliced_info->SetTextAndShow(siEstimatedTime, info_text, new_label); + } #else if (ps.estimated_normal_print_time == "N/A" && ps.estimated_silent_print_time == "N/A") p->sliced_info->SetTextAndShow(siEstimatedTime, "N/A"); @@ -1369,41 +1388,28 @@ bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &fi this->MSWUpdateDragImageOnLeave(); #endif // WIN32 - // gcode section - for (const auto& filename : filenames) { - fs::path path(into_path(filename)); - if (std::regex_match(path.string(), pattern_gcode_drop)) - paths.push_back(std::move(path)); - } - - if (paths.size() > 1) { - wxMessageDialog((wxWindow*)plater, _L("You can open only one .gcode file at a time."), - wxString(SLIC3R_APP_NAME) + " - " + _L("Drag and drop G-code file"), wxCLOSE | wxICON_WARNING | wxCENTRE).ShowModal(); - return false; - } - else if (paths.size() == 1) { - if (wxGetApp().mainframe->get_mode() == MainFrame::EMode::GCodeViewer) { - plater->load_gcode(from_path(paths.front())); - return true; + if (wxGetApp().is_gcode_viewer()) { + // gcode section + for (const auto& filename : filenames) { + fs::path path(into_path(filename)); + if (std::regex_match(path.string(), pattern_gcode_drop)) + paths.push_back(std::move(path)); } - else { - if (wxMessageDialog((wxWindow*)plater, _L("Do you want to switch to G-code preview ?"), - wxString(SLIC3R_APP_NAME) + " - " + _L("Drag and drop G-code file"), wxYES_NO | wxICON_QUESTION | wxYES_DEFAULT | wxCENTRE).ShowModal() == wxID_YES) { - if (plater->model().objects.empty() || - wxMessageDialog((wxWindow*)plater, _L("Switching to G-code preview mode will remove all objects, continue?"), - wxString(SLIC3R_APP_NAME) + " - " + _L("Switch to G-code preview mode"), wxYES_NO | wxICON_QUESTION | wxYES_DEFAULT | wxCENTRE).ShowModal() == wxID_YES) { - wxGetApp().mainframe->set_mode(MainFrame::EMode::GCodeViewer); - plater->load_gcode(from_path(paths.front())); - return true; - } - } + if (paths.size() > 1) { + wxMessageDialog((wxWindow*)plater, _L("You can open only one .gcode file at a time."), + wxString(SLIC3R_APP_NAME) + " - " + _L("Drag and drop G-code file"), wxCLOSE | wxICON_WARNING | wxCENTRE).ShowModal(); return false; } + else if (paths.size() == 1) { + plater->load_gcode(from_path(paths.front())); + return true; + } + return false; } #endif // ENABLE_GCODE_VIEWER - // model section + // editor section for (const auto &filename : filenames) { fs::path path(into_path(filename)); if (std::regex_match(path.string(), pattern_drop)) @@ -1412,16 +1418,6 @@ bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &fi return false; } -#if ENABLE_GCODE_VIEWER - if (wxGetApp().mainframe->get_mode() == MainFrame::EMode::GCodeViewer) { - if (wxMessageDialog((wxWindow*)plater, _L("Do you want to exit G-code preview ?"), - wxString(SLIC3R_APP_NAME) + " - " + _L("Drag and drop model file"), wxYES_NO | wxICON_QUESTION | wxYES_DEFAULT | wxCENTRE).ShowModal() == wxID_YES) - wxGetApp().mainframe->set_mode(MainFrame::EMode::Editor); - else - return false; - } -#endif // ENABLE_GCODE_VIEWER - wxString snapshot_label; assert(! paths.empty()); if (paths.size() == 1) { @@ -1705,7 +1701,7 @@ struct Plater::priv void on_select_preset(wxCommandEvent&); void on_slicing_update(SlicingStatusEvent&); void on_slicing_completed(wxCommandEvent&); - void on_process_completed(wxCommandEvent&); + void on_process_completed(SlicingProcessCompletedEvent&); void on_export_began(wxCommandEvent&); void on_layer_editing_toggled(bool enable); void on_slicing_began(); @@ -1970,7 +1966,13 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) q->SetDropTarget(new PlaterDropTarget(q)); // if my understanding is right, wxWindow takes the owenership q->Layout(); +#if ENABLE_GCODE_VIEWER + set_current_panel(wxGetApp().is_editor() ? (wxPanel*)view3D : (wxPanel*)preview); + if (wxGetApp().is_gcode_viewer()) + preview->hide_layers_slider(); +#else set_current_panel(view3D); +#endif // ENABLE_GCODE_VIEWER // updates camera type from .ini file camera.set_type(get_config("use_perspective_camera")); @@ -1990,33 +1992,38 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) #endif /* _WIN32 */ notification_manager = new NotificationManager(this->q); - this->q->Bind(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, [this](EjectDriveNotificationClickedEvent&) { this->q->eject_drive(); }); - this->q->Bind(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, [this](ExportGcodeNotificationClickedEvent&) { this->q->export_gcode(true); }); - this->q->Bind(EVT_PRESET_UPDATE_AVIABLE_CLICKED, [this](PresetUpdateAviableClickedEvent&) { wxGetApp().get_preset_updater()->on_update_notification_confirm(); }); - - this->q->Bind(EVT_REMOVABLE_DRIVE_EJECTED, [this, q](RemovableDriveEjectEvent &evt) { - if (evt.data.second) { - this->show_action_buttons(this->ready_to_slice); - notification_manager->push_notification(format(_L("Unmounting successful. The device %s(%s) can now be safely removed from the computer."),evt.data.first.name, evt.data.first.path), - NotificationManager::NotificationLevel::RegularNotification, *q->get_current_canvas3D()); - } else { - notification_manager->push_notification(format(_L("Ejecting of device %s(%s) has failed."), evt.data.first.name, evt.data.first.path), - NotificationManager::NotificationLevel::ErrorNotification, *q->get_current_canvas3D()); - } - }); - this->q->Bind(EVT_REMOVABLE_DRIVES_CHANGED, [this, q](RemovableDrivesChangedEvent &) { - this->show_action_buttons(this->ready_to_slice); - if (!this->sidebar->get_eject_shown()) { - notification_manager->close_notification_of_type(NotificationType::ExportToRemovableFinished); - } - }); - // Start the background thread and register this window as a target for update events. - wxGetApp().removable_drive_manager()->init(this->q); +#if ENABLE_GCODE_VIEWER + if (wxGetApp().is_editor()) { +#endif // ENABLE_GCODE_VIEWER + this->q->Bind(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, [this](EjectDriveNotificationClickedEvent&) { this->q->eject_drive(); }); + this->q->Bind(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, [this](ExportGcodeNotificationClickedEvent&) { this->q->export_gcode(true); }); + this->q->Bind(EVT_PRESET_UPDATE_AVIABLE_CLICKED, [this](PresetUpdateAviableClickedEvent&) { wxGetApp().get_preset_updater()->on_update_notification_confirm(); }); + this->q->Bind(EVT_REMOVABLE_DRIVE_EJECTED, [this, q](RemovableDriveEjectEvent &evt) { + if (evt.data.second) { + this->show_action_buttons(this->ready_to_slice); + notification_manager->push_notification(format(_L("Unmounting successful. The device %s(%s) can now be safely removed from the computer."),evt.data.first.name, evt.data.first.path), + NotificationManager::NotificationLevel::RegularNotification, *q->get_current_canvas3D()); + } else { + notification_manager->push_notification(format(_L("Ejecting of device %s(%s) has failed."), evt.data.first.name, evt.data.first.path), + NotificationManager::NotificationLevel::ErrorNotification, *q->get_current_canvas3D()); + } + }); + this->q->Bind(EVT_REMOVABLE_DRIVES_CHANGED, [this, q](RemovableDrivesChangedEvent &) { + this->show_action_buttons(this->ready_to_slice); + if (!this->sidebar->get_eject_shown()) { + notification_manager->close_notification_of_type(NotificationType::ExportToRemovableFinished); + } + }); + // Start the background thread and register this window as a target for update events. + wxGetApp().removable_drive_manager()->init(this->q); #ifdef _WIN32 - // Trigger enumeration of removable media on Win32 notification. - this->q->Bind(EVT_VOLUME_ATTACHED, [this](VolumeAttachedEvent &evt) { wxGetApp().removable_drive_manager()->volumes_changed(); }); - this->q->Bind(EVT_VOLUME_DETACHED, [this](VolumeDetachedEvent &evt) { wxGetApp().removable_drive_manager()->volumes_changed(); }); + // Trigger enumeration of removable media on Win32 notification. + this->q->Bind(EVT_VOLUME_ATTACHED, [this](VolumeAttachedEvent &evt) { wxGetApp().removable_drive_manager()->volumes_changed(); }); + this->q->Bind(EVT_VOLUME_DETACHED, [this](VolumeDetachedEvent &evt) { wxGetApp().removable_drive_manager()->volumes_changed(); }); #endif /* _WIN32 */ +#if ENABLE_GCODE_VIEWER + } +#endif // ENABLE_GCODE_VIEWER // Initialize the Undo / Redo stack with a first snapshot. this->take_snapshot(_L("New Project")); @@ -2811,7 +2818,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool if (this->preview != nullptr) { // If the preview is not visible, the following line just invalidates the preview, // but the G-code paths or SLA preview are calculated first once the preview is made visible. - this->preview->get_canvas3d()->reset_gcode_toolpaths(); + reset_gcode_toolpaths(); this->preview->reload_print(); } #else @@ -3522,7 +3529,7 @@ bool Plater::priv::warnings_dialog() return res == wxID_OK; } -void Plater::priv::on_process_completed(wxCommandEvent &evt) +void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt) { // Stop the background task, wait until the thread goes into the "Idle" state. // At this point of time the thread should be either finished or canceled, @@ -3531,27 +3538,30 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt) this->statusbar()->reset_cancel_callback(); this->statusbar()->stop_busy(); - const bool canceled = evt.GetInt() < 0; - const bool error = evt.GetInt() == 0; - const bool success = evt.GetInt() > 0; // Reset the "export G-code path" name, so that the automatic background processing will be enabled again. this->background_process.reset_export(); - if (error) { - wxString message = evt.GetString(); - if (message.IsEmpty()) - message = _L("Export failed."); - notification_manager->push_slicing_error_notification(boost::nowide::narrow(message), *q->get_current_canvas3D()); - this->statusbar()->set_status_text(message); + if (evt.error()) { + std::string message = evt.format_error_message(); + if (evt.critical_error()) { + if (q->m_tracking_popup_menu) + // We don't want to pop-up a message box when tracking a pop-up menu. + // We postpone the error message instead. + q->m_tracking_popup_menu_error_message = message; + else + show_error(q, message); + } else + notification_manager->push_slicing_error_notification(message, *q->get_current_canvas3D()); + this->statusbar()->set_status_text(from_u8(message)); const wxString invalid_str = _L("Invalid data"); for (auto btn : { ActionButtonType::abReslice, ActionButtonType::abSendGCode, ActionButtonType::abExport }) sidebar->set_btn_label(btn, invalid_str); process_completed_with_error = true; } - if (canceled) + if (evt.cancelled()) this->statusbar()->set_status_text(_L("Cancelled")); - this->sidebar->show_sliced_info_sizer(success); + this->sidebar->show_sliced_info_sizer(evt.success()); // This updates the "Slice now", "Export G-code", "Arrange" buttons status. // Namely, it refreshes the "Out of print bed" property of all the ModelObjects, and it enables @@ -3572,7 +3582,7 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt) default: break; } - if (canceled) { + if (evt.cancelled()) { if (wxGetApp().get_mode() == comSimple) sidebar->set_btn_label(ActionButtonType::abReslice, "Slice now"); show_action_buttons(true); @@ -4013,7 +4023,11 @@ bool Plater::priv::init_view_toolbar() return false; view_toolbar.select_item("3D"); - view_toolbar.set_enabled(true); + +#if ENABLE_GCODE_VIEWER + if (wxGetApp().is_editor()) +#endif // ENABLE_GCODE_VIEWER + view_toolbar.set_enabled(true); return true; } @@ -5384,6 +5398,10 @@ void Plater::on_config_change(const DynamicPrintConfig &config) this->set_printer_technology(config.opt_enum(opt_key)); // print technology is changed, so we should to update a search list p->sidebar->update_searcher(); + p->sidebar->show_sliced_info_sizer(false); +#if ENABLE_GCODE_VIEWER + p->reset_gcode_toolpaths(); +#endif // ENABLE_GCODE_VIEWER } else if ((opt_key == "bed_shape") || (opt_key == "bed_custom_texture") || (opt_key == "bed_custom_model")) { bed_shape_changed = true; @@ -5628,11 +5646,7 @@ void Plater::set_printer_technology(PrinterTechnology printer_technology) p->label_btn_send = printer_technology == ptFFF ? L("Send G-code") : L("Send to printer"); if (wxGetApp().mainframe != nullptr) -#if ENABLE_GCODE_VIEWER - wxGetApp().mainframe->update_editor_menubar(); -#else wxGetApp().mainframe->update_menubar(); -#endif // ENABLE_GCODE_VIEWER p->update_main_toolbar_tooltips(); diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 242c3d851..a3a23fd85 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -131,6 +131,15 @@ void PreferencesDialog::build() option = Option(def, "use_inches"); m_optgroup_general->append_single_option_line(option); */ + + // Show/Hide splash screen + def.label = L("Show splash screen"); + def.type = coBool; + def.tooltip = L("Show splash screen"); + def.set_default_value(new ConfigOptionBool{ app_config->get("show_splash_screen") == "1" }); + option = Option(def, "show_splash_screen"); + m_optgroup_general->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) { diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp index c62c999f6..8c0fefc76 100644 --- a/src/slic3r/GUI/PresetComboBoxes.cpp +++ b/src/slic3r/GUI/PresetComboBoxes.cpp @@ -741,7 +741,8 @@ void PlaterPresetComboBox::update() if (m_type == Preset::TYPE_FILAMENT) { // Assign an extruder color to the selected item if the extruder color is defined. - filament_rgb = preset.config.opt_string("filament_colour", 0); + filament_rgb = is_selected ? selected_filament_preset->config.opt_string("filament_colour", 0) : + preset.config.opt_string("filament_colour", 0); extruder_rgb = (is_selected && !extruder_color.empty()) ? extruder_color : filament_rgb; single_bar = filament_rgb == extruder_rgb; @@ -1059,7 +1060,7 @@ SavePresetDialog::Item::Item(Preset::Type type, const std::string& suffix, wxBox m_valid_bmp = new wxStaticBitmap(m_parent, wxID_ANY, create_scaled_bitmap("tick_mark", m_parent)); - m_combo = new wxComboBox(m_parent, wxID_ANY, from_u8(preset_name)); + m_combo = new wxComboBox(m_parent, wxID_ANY, from_u8(preset_name), wxDefaultPosition, wxSize(35 * wxGetApp().em_unit(), -1)); for (const std::string& value : values) m_combo->Append(from_u8(value)); @@ -1123,17 +1124,26 @@ void SavePresetDialog::Item::update() info_line = _L("Cannot overwrite a system profile."); m_valid_type = NoValid; } + if (m_valid_type == Valid && existing && (existing->is_external)) { info_line = _L("Cannot overwrite an external profile."); m_valid_type = NoValid; } + if (m_valid_type == Valid && existing && m_preset_name != m_presets->get_selected_preset_name()) { - info_line = from_u8((boost::format(_u8L("Preset with name \"%1%\" already exists.")) % m_preset_name).str()) + "\n" + - _L("Note: This preset will be replaced after saving"); + info_line = from_u8((boost::format(_u8L("Preset with name \"%1%\" already exists.")) % m_preset_name).str()); + if (!existing->is_compatible) + info_line += "\n" + _L("And selected preset is imcopatible with selected printer."); + info_line += "\n" + _L("Note: This preset will be replaced after saving"); m_valid_type = Warning; } + if (m_valid_type == Valid && m_preset_name.empty()) { + info_line = _L("The empty name is not available."); + m_valid_type = NoValid; + } + m_valid_label->SetLabel(info_line); m_valid_label->Show(!info_line.IsEmpty()); @@ -1163,23 +1173,45 @@ void SavePresetDialog::Item::accept() // SavePresetDialog //----------------------------------------------- -SavePresetDialog::SavePresetDialog(Preset::Type type, const std::string& suffix) +SavePresetDialog::SavePresetDialog(Preset::Type type, std::string suffix) : DPIDialog(nullptr, wxID_ANY, _L("Save preset"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), 5 * wxGetApp().em_unit()), wxDEFAULT_DIALOG_STYLE | wxICON_WARNING | wxRESIZE_BORDER) +{ + build(std::vector{type}, suffix); +} + +SavePresetDialog::SavePresetDialog(std::vector types, std::string suffix) + : DPIDialog(nullptr, wxID_ANY, _L("Save preset"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), 5 * wxGetApp().em_unit()), wxDEFAULT_DIALOG_STYLE | wxICON_WARNING | wxRESIZE_BORDER) +{ + build(types, suffix); +} + +SavePresetDialog::~SavePresetDialog() +{ + for (auto item : m_items) { + delete item; + } +} + +void SavePresetDialog::build(std::vector types, std::string suffix) { SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); -#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT +#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT && defined(__WXMSW__) // ys_FIXME! temporary workaround for correct font scaling // Because of from wxWidgets 3.1.3 auto rescaling is implemented for the Fonts, // From the very beginning set dialog font to the wxSYS_DEFAULT_GUI_FONT this->SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); -#endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT +#endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT + + if (suffix.empty()) + suffix = _CTX_utf8(L_CONTEXT("Copy", "PresetName"), "PresetName"); wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); m_presets_sizer = new wxBoxSizer(wxVERTICAL); // Add first item - m_items.emplace_back(type, suffix, m_presets_sizer, this); + for (Preset::Type type : types) + AddItem(type, suffix); // Add dialog's buttons wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL); @@ -1196,26 +1228,26 @@ SavePresetDialog::SavePresetDialog(Preset::Type type, const std::string& suffix) void SavePresetDialog::AddItem(Preset::Type type, const std::string& suffix) { - m_items.emplace_back(type, suffix, m_presets_sizer, this); + m_items.emplace_back(new Item{type, suffix, m_presets_sizer, this}); } std::string SavePresetDialog::get_name() { - return m_items.front().preset_name(); + return m_items.front()->preset_name(); } std::string SavePresetDialog::get_name(Preset::Type type) { - for (Item& item : m_items) - if (item.type() == type) - return item.preset_name(); + for (const Item* item : m_items) + if (item->type() == type) + return item->preset_name(); return ""; } bool SavePresetDialog::enable_ok_btn() const { - for (Item item : m_items) - if (!item.is_valid()) + for (const Item* item : m_items) + if (!item->is_valid()) return false; return true; @@ -1284,8 +1316,8 @@ void SavePresetDialog::on_dpi_changed(const wxRect& suggested_rect) msw_buttons_rescale(this, em, { wxID_OK, wxID_CANCEL }); - for (Item& item : m_items) - item.update_valid_bmp(); + for (Item* item : m_items) + item->update_valid_bmp(); //const wxSize& size = wxSize(45 * em, 35 * em); SetMinSize(/*size*/wxSize(100, 50)); @@ -1324,10 +1356,10 @@ void SavePresetDialog::update_physical_printers(const std::string& preset_name) void SavePresetDialog::accept() { - for (Item& item : m_items) { - item.accept(); - if (item.type() == Preset::TYPE_PRINTER) - update_physical_printers(item.preset_name()); + for (Item* item : m_items) { + item->accept(); + if (item->type() == Preset::TYPE_PRINTER) + update_physical_printers(item->preset_name()); } EndModal(wxID_OK); diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp index 7f51f775e..e33a2d753 100644 --- a/src/slic3r/GUI/PresetComboBoxes.hpp +++ b/src/slic3r/GUI/PresetComboBoxes.hpp @@ -235,7 +235,7 @@ class SavePresetDialog : public DPIDialog void update(); }; - std::vector m_items; + std::vector m_items; wxBoxSizer* m_presets_sizer {nullptr}; wxStaticText* m_label {nullptr}; @@ -248,8 +248,9 @@ class SavePresetDialog : public DPIDialog public: - SavePresetDialog(Preset::Type type, const std::string& suffix); - ~SavePresetDialog() {} + SavePresetDialog(Preset::Type type, std::string suffix = ""); + SavePresetDialog(std::vector types, std::string suffix = ""); + ~SavePresetDialog(); void AddItem(Preset::Type type, const std::string& suffix); @@ -266,6 +267,7 @@ protected: void on_sys_color_changed() override {} private: + void build(std::vector types, std::string suffix = ""); void update_physical_printers(const std::string& preset_name); void accept(); }; diff --git a/src/slic3r/GUI/PrintHostDialogs.cpp b/src/slic3r/GUI/PrintHostDialogs.cpp index 216af5df4..96299c381 100644 --- a/src/slic3r/GUI/PrintHostDialogs.cpp +++ b/src/slic3r/GUI/PrintHostDialogs.cpp @@ -140,8 +140,6 @@ PrintHostQueueDialog::PrintHostQueueDialog(wxWindow *parent) { const auto em = GetTextExtent("m").x; - SetSize(wxSize(HEIGHT * em, WIDTH * em)); - auto *topsizer = new wxBoxSizer(wxVERTICAL); job_list = new wxDataViewListCtrl(this, wxID_ANY); @@ -168,6 +166,8 @@ PrintHostQueueDialog::PrintHostQueueDialog(wxWindow *parent) topsizer->Add(btnsizer, 0, wxEXPAND); SetSizer(topsizer); + SetSize(wxSize(HEIGHT * em, WIDTH * em)); + job_list->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, [this](wxDataViewEvent&) { on_list_select(); }); btn_cancel->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { diff --git a/src/slic3r/GUI/Search.cpp b/src/slic3r/GUI/Search.cpp index 2a2af5336..da9c8fe25 100644 --- a/src/slic3r/GUI/Search.cpp +++ b/src/slic3r/GUI/Search.cpp @@ -28,12 +28,6 @@ using GUI::into_u8; namespace Search { -// Does our wxWidgets version support markup? -// https://github.com/prusa3d/PrusaSlicer/issues/4282#issuecomment-634676371 -#if wxUSE_MARKUP && wxCHECK_VERSION(3, 1, 1) - #define SEARCH_SUPPORTS_MARKUP -#endif - static char marker_by_type(Preset::Type type, PrinterTechnology pt) { switch(type) { @@ -264,7 +258,7 @@ bool OptionsSearcher::search(const std::string& search, bool force/* = false*/) std::string label_u8 = into_u8(label); std::string label_plain = label_u8; -#ifdef SEARCH_SUPPORTS_MARKUP +#ifdef SUPPORTS_MARKUP boost::replace_all(label_plain, std::string(1, char(ImGui::ColorMarkerStart)), ""); boost::replace_all(label_plain, std::string(1, char(ImGui::ColorMarkerEnd)), ""); #else @@ -327,6 +321,14 @@ const Option& OptionsSearcher::get_option(size_t pos_in_filter) const return options[found[pos_in_filter].option_idx]; } +const Option& OptionsSearcher::get_option(const std::string& opt_key) const +{ + auto it = std::lower_bound(options.begin(), options.end(), Option({ boost::nowide::widen(opt_key) })); + assert(it != options.end()); + + return options[it - options.begin()]; +} + void OptionsSearcher::add_key(const std::string& opt_key, const wxString& group, const wxString& category) { groups_and_categories[opt_key] = GroupAndCategory{group, category}; @@ -442,7 +444,7 @@ SearchDialog::SearchDialog(OptionsSearcher* searcher) wxDataViewTextRenderer* const markupRenderer = new wxDataViewTextRenderer(); -#ifdef SEARCH_SUPPORTS_MARKUP +#ifdef SUPPORTS_MARKUP markupRenderer->EnableMarkup(); #endif diff --git a/src/slic3r/GUI/Search.hpp b/src/slic3r/GUI/Search.hpp index 8202222e9..a57e0d015 100644 --- a/src/slic3r/GUI/Search.hpp +++ b/src/slic3r/GUI/Search.hpp @@ -37,8 +37,8 @@ struct GroupAndCategory { }; struct Option { - bool operator<(const Option& other) const { return other.label > this->label; } - bool operator>(const Option& other) const { return other.label < this->label; } +// bool operator<(const Option& other) const { return other.label > this->label; } + bool operator<(const Option& other) const { return other.opt_key > this->opt_key; } // Fuzzy matching works at a character level. Thus matching with wide characters is a safer bet than with short characters, // though for some languages (Chinese?) it may not work correctly. @@ -116,12 +116,18 @@ public: const FoundOption& operator[](const size_t pos) const noexcept { return found[pos]; } const Option& get_option(size_t pos_in_filter) const; + const Option& get_option(const std::string& opt_key) const; const std::vector& found_options() { return found; } const GroupAndCategory& get_group_and_category (const std::string& opt_key) { return groups_and_categories[opt_key]; } std::string& search_string() { return search_line; } void set_printer_technology(PrinterTechnology pt) { printer_technology = pt; } + + void sort_options_by_opt_key() { + std::sort(options.begin(), options.end(), [](const Option& o1, const Option& o2) { + return o1.opt_key < o2.opt_key; }); + } }; diff --git a/src/slic3r/GUI/SysInfoDialog.cpp b/src/slic3r/GUI/SysInfoDialog.cpp index 3bd0fcf9f..34905fa6d 100644 --- a/src/slic3r/GUI/SysInfoDialog.cpp +++ b/src/slic3r/GUI/SysInfoDialog.cpp @@ -34,9 +34,17 @@ std::string get_main_info(bool format_as_html) std::string line_end = format_as_html ? "
" : "\n"; if (!format_as_html) +#if ENABLE_GCODE_VIEWER + out << b_start << (wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME) << b_end << line_end; +#else out << b_start << SLIC3R_APP_NAME << b_end << line_end; +#endif // ENABLE_GCODE_VIEWER out << b_start << "Version: " << b_end << SLIC3R_VERSION << line_end; +#if ENABLE_GCODE_VIEWER + out << b_start << "Build: " << b_end << (wxGetApp().is_editor() ? SLIC3R_BUILD_ID : GCODEVIEWER_BUILD_ID) << line_end; +#else out << b_start << "Build: " << b_end << SLIC3R_BUILD_ID << line_end; +#endif // ENABLE_GCODE_VIEWER out << line_end; out << b_start << "Operating System: " << b_end << wxPlatformInfo::Get().GetOperatingSystemFamilyName() << line_end; out << b_start << "System Architecture: " << b_end << wxPlatformInfo::Get().GetArchName() << line_end; @@ -78,7 +86,11 @@ std::string get_mem_info(bool format_as_html) } SysInfoDialog::SysInfoDialog() - : DPIDialog(NULL, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(L("System Information")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER) +#if ENABLE_GCODE_VIEWER + : DPIDialog(NULL, wxID_ANY, (wxGetApp().is_editor() ? wxString(SLIC3R_APP_NAME) : wxString(GCODEVIEWER_APP_NAME)) + " - " + _L("System Information"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) +#else + : DPIDialog(NULL, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _L("System Information"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER) +#endif // ENABLE_GCODE_VIEWER { wxColour bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); SetBackgroundColour(bgr_clr); @@ -91,7 +103,11 @@ SysInfoDialog::SysInfoDialog() main_sizer->Add(hsizer, 1, wxEXPAND | wxALL, 10); // logo +#if ENABLE_GCODE_VIEWER + m_logo_bmp = ScalableBitmap(this, wxGetApp().is_editor() ? "PrusaSlicer_192px.png" : "PrusaSlicer-gcodeviewer_192px.png", 192); +#else m_logo_bmp = ScalableBitmap(this, "PrusaSlicer_192px.png", 192); +#endif // ENABLE_GCODE_VIEWER m_logo = new wxStaticBitmap(this, wxID_ANY, m_logo_bmp.bmp()); hsizer->Add(m_logo, 0, wxALIGN_CENTER_VERTICAL); @@ -100,7 +116,11 @@ SysInfoDialog::SysInfoDialog() // title { +#if ENABLE_GCODE_VIEWER + wxStaticText* title = new wxStaticText(this, wxID_ANY, wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME, wxDefaultPosition, wxDefaultSize); +#else wxStaticText* title = new wxStaticText(this, wxID_ANY, SLIC3R_APP_NAME, wxDefaultPosition, wxDefaultSize); +#endif // ENABLE_GCODE_VIEWER wxFont title_font = wxGetApp().bold_font(); title_font.SetFamily(wxFONTFAMILY_ROMAN); title_font.SetPointSize(22); @@ -109,7 +129,7 @@ SysInfoDialog::SysInfoDialog() } // main_info_text - wxFont font = wxGetApp().normal_font(); + wxFont font = get_default_font_for_dpi(get_dpi_for_window(this));// wxGetApp().normal_font(); const auto text_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue()); auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue()); @@ -154,7 +174,7 @@ SysInfoDialog::SysInfoDialog() } wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxOK); - m_btn_copy_to_clipboard = new wxButton(this, wxID_ANY, _(L("Copy to Clipboard")), wxDefaultPosition, wxDefaultSize); + m_btn_copy_to_clipboard = new wxButton(this, wxID_ANY, _L("Copy to Clipboard"), wxDefaultPosition, wxDefaultSize); buttons->Insert(0, m_btn_copy_to_clipboard, 0, wxLEFT, 5); m_btn_copy_to_clipboard->Bind(wxEVT_BUTTON, &SysInfoDialog::onCopyToClipboard, this); @@ -175,7 +195,7 @@ void SysInfoDialog::on_dpi_changed(const wxRect &suggested_rect) m_logo_bmp.msw_rescale(); m_logo->SetBitmap(m_logo_bmp.bmp()); - wxFont font = GetFont(); + wxFont font = get_default_font_for_dpi(get_dpi_for_window(this));// GetFont(); const int fs = font.GetPointSize() - 1; int font_size[] = { static_cast(fs*1.5), static_cast(fs*1.4), static_cast(fs*1.3), fs, fs, fs, fs }; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index a56dadf4f..29c9e3302 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -36,6 +36,7 @@ #include "MainFrame.hpp" #include "format.hpp" #include "PhysicalPrinterDialog.hpp" +#include "UnsavedChangesDialog.hpp" namespace Slic3r { namespace GUI { @@ -101,7 +102,7 @@ Tab::Tab(wxNotebook* parent, const wxString& title, Preset::Type type) : wxGetApp().tabs_list.push_back(this); - m_em_unit = wxGetApp().em_unit(); + m_em_unit = em_unit(m_parent); //wxGetApp().em_unit(); m_config_manipulation = get_config_manipulation(); @@ -325,7 +326,7 @@ void Tab::add_scaled_button(wxWindow* parent, const wxString& label/* = wxEmptyString*/, long style /*= wxBU_EXACTFIT | wxNO_BORDER*/) { - *btn = new ScalableButton(parent, wxID_ANY, icon_name, label, wxDefaultSize, wxDefaultPosition, style); + *btn = new ScalableButton(parent, wxID_ANY, icon_name, label, wxDefaultSize, wxDefaultPosition, style, true); m_scaled_buttons.push_back(*btn); } @@ -412,8 +413,9 @@ void Tab::update_labels_colour() else color = &m_modified_label_clr; } - if (opt.first == "bed_shape" || opt.first == "compatible_prints" || opt.first == "compatible_printers") { - wxStaticText* label = (m_colored_Labels.find(opt.first) == m_colored_Labels.end()) ? nullptr : m_colored_Labels.at(opt.first); + if (opt.first == "bed_shape" || opt.first == "filament_ramming_parameters" || + opt.first == "compatible_prints" || opt.first == "compatible_printers" ) { + wxStaticText* label = m_colored_Labels.find(opt.first) == m_colored_Labels.end() ? nullptr : m_colored_Labels.at(opt.first); if (label) { label->SetForegroundColour(*color); label->Refresh(true); @@ -503,7 +505,8 @@ void Tab::update_changed_ui() icon = &m_bmp_white_bullet; tt = &m_tt_white_bullet; } - if (opt.first == "bed_shape" || opt.first == "compatible_prints" || opt.first == "compatible_printers") { + if (opt.first == "bed_shape" || opt.first == "filament_ramming_parameters" || + opt.first == "compatible_prints" || opt.first == "compatible_printers") { wxStaticText* label = (m_colored_Labels.find(opt.first) == m_colored_Labels.end()) ? nullptr : m_colored_Labels.at(opt.first); if (label) { label->SetForegroundColour(*color); @@ -655,6 +658,9 @@ void Tab::update_changed_tree_ui() get_sys_and_mod_flags(opt_key, sys_page, modified_page); } } + if (m_type == Preset::TYPE_FILAMENT && page->title() == "Advanced") { + get_sys_and_mod_flags("filament_ramming_parameters", sys_page, modified_page); + } if (page->title() == "Dependencies") { if (m_type == Slic3r::Preset::TYPE_PRINTER) { sys_page = m_presets->get_selected_preset_parent() != nullptr; @@ -733,7 +739,10 @@ void Tab::on_roll_back_value(const bool to_sys /*= true*/) to_sys ? group->back_to_sys_value("bed_shape") : group->back_to_initial_value("bed_shape"); load_key_value("bed_shape", true/*some value*/, true); } - + } + if (group->title == "Toolchange parameters with single extruder MM printers") { + if ((m_options_list["filament_ramming_parameters"] & os) == 0) + to_sys ? group->back_to_sys_value("filament_ramming_parameters") : group->back_to_initial_value("filament_ramming_parameters"); } if (group->title == "Profile dependencies") { // "compatible_printers" option doesn't exists in Printer Settimgs Tab @@ -1094,6 +1103,21 @@ void Tab::apply_searcher() wxGetApp().sidebar().get_searcher().apply(m_config, m_type, m_mode); } +void Tab::cache_config_diff(const std::vector& selected_options) +{ + m_cache_config.apply_only(m_presets->get_edited_preset().config, selected_options); +} + +void Tab::apply_config_from_cache() +{ + if (!m_cache_config.empty()) { + m_presets->get_edited_preset().config.apply(m_cache_config); + m_cache_config.clear(); + + update_dirty(); + } +} + // Call a callback to update the selection of presets on the plater: // To update the content of the selection boxes, @@ -1113,9 +1137,12 @@ void Tab::on_presets_changed() // Printer selected at the Printer tab, update "compatible" marks at the print and filament selectors. for (auto t: m_dependent_tabs) { + Tab* tab = wxGetApp().get_tab(t); // If the printer tells us that the print or filament/sla_material preset has been switched or invalidated, // refresh the print or filament/sla_material tab page. - wxGetApp().get_tab(t)->load_current_preset(); + // But if there are options, moved from the previously selected preset, update them to edited preset + tab->apply_config_from_cache(); + tab->load_current_preset(); } // clear m_dependent_tabs after first update from select_preset() // to avoid needless preset loading from update() function @@ -1736,22 +1763,21 @@ void TabFilament::build() optgroup->append_single_option_line("filament_cooling_initial_speed"); optgroup->append_single_option_line("filament_cooling_final_speed"); - line = optgroup->create_single_option_line("filament_ramming_parameters");// { _(L("Ramming")), "" }; - line.widget = [this](wxWindow* parent) { + create_line_with_widget(optgroup.get(), "filament_ramming_parameters", [this](wxWindow* parent) { auto ramming_dialog_btn = new wxButton(parent, wxID_ANY, _(L("Ramming settings"))+dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT); ramming_dialog_btn->SetFont(Slic3r::GUI::wxGetApp().normal_font()); auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(ramming_dialog_btn); - ramming_dialog_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) - { + ramming_dialog_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) { RammingDialog dlg(this,(m_config->option("filament_ramming_parameters"))->get_at(0)); - if (dlg.ShowModal() == wxID_OK) - (m_config->option("filament_ramming_parameters"))->get_at(0) = dlg.get_parameters(); - })); + if (dlg.ShowModal() == wxID_OK) { + load_key_value("filament_ramming_parameters", dlg.get_parameters()); + update_changed_ui(); + } + }); return sizer; - }; - optgroup->append_line(line); + }); add_filament_overrides_page(); @@ -2858,7 +2884,7 @@ void Tab::load_current_preset() } on_presets_changed(); if (printer_technology == ptFFF) { - static_cast(this)->m_initial_extruders_count = static_cast(this)->m_extruders_count; + static_cast(this)->m_initial_extruders_count = static_cast(m_presets->get_selected_preset().config.option("nozzle_diameter"))->values.size(); //static_cast(this)->m_extruders_count; const Preset* parent_preset = m_presets->get_selected_preset_parent(); static_cast(this)->m_sys_extruders_count = parent_preset == nullptr ? 0 : static_cast(parent_preset->config.option("nozzle_diameter"))->values.size(); @@ -2989,7 +3015,7 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, bool canceled = false; bool technology_changed = false; m_dependent_tabs.clear(); - if (current_dirty && ! may_discard_current_dirty_preset()) { + if (current_dirty && ! may_discard_current_dirty_preset(nullptr, preset_name)) { canceled = true; } else if (print_tab) { // Before switching the print profile to a new one, verify, whether the currently active filament or SLA material @@ -3123,6 +3149,13 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, m_dependent_tabs = { Preset::Type::TYPE_SLA_PRINT, Preset::Type::TYPE_SLA_MATERIAL }; } + // check and apply extruders count for printer preset + if (m_type == Preset::TYPE_PRINTER) + static_cast(this)->apply_extruder_cnt_from_cache(); + + // check if there is something in the cache to move to the new selected preset + apply_config_from_cache(); + load_current_preset(); } } @@ -3132,41 +3165,65 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/, bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr*/, const std::string& new_printer_name /*= ""*/) { if (presets == nullptr) presets = m_presets; - // Display a dialog showing the dirty options in a human readable form. - const Preset& old_preset = presets->get_edited_preset(); - std::string type_name = presets->name(); - wxString tab = " "; - wxString name = old_preset.is_default ? - from_u8((boost::format(_utf8(L("Default preset (%s)"))) % _utf8(type_name)).str()) : - from_u8((boost::format(_utf8(L("Preset (%s)"))) % _utf8(type_name)).str()) + "\n" + tab + old_preset.name; - // Collect descriptions of the dirty options. - wxString changes; - for (const std::string &opt_key : presets->current_dirty_options()) { - const ConfigOptionDef &opt = m_config->def()->options.at(opt_key); - /*std::string*/wxString name = ""; - if (! opt.category.empty()) - name += _(opt.category) + " > "; - name += !opt.full_label.empty() ? - _(opt.full_label) : - _(opt.label); - changes += tab + /*from_u8*/(name) + "\n"; + UnsavedChangesDialog dlg(m_type, presets, new_printer_name); + if (dlg.ShowModal() == wxID_CANCEL) + return false; + + if (dlg.save_preset()) // save selected changes + { + std::vector unselected_options = dlg.get_unselected_options(presets->type()); + const Preset& preset = presets->get_edited_preset(); + std::string name = preset.name; + + // for system/default/external presets we should take an edited name + if (preset.is_system || preset.is_default || preset.is_external) { + SavePresetDialog save_dlg(preset.type); + if (save_dlg.ShowModal() != wxID_OK) + return false; + name = save_dlg.get_name(); + } + + if (m_type == presets->type()) // save changes for the current preset from this tab + { + // revert unselected options to the old values + presets->get_edited_preset().config.apply_only(presets->get_selected_preset().config, unselected_options); + save_preset(name); + } + else + { + m_preset_bundle->save_changes_for_preset(name, presets->type(), unselected_options); + + /* If filament preset is saved for multi-material printer preset, + * there are cases when filament comboboxs are updated for old (non-modified) colors, + * but in full_config a filament_colors option aren't.*/ + if (presets->type() == Preset::TYPE_FILAMENT && wxGetApp().extruders_edited_cnt() > 1) + wxGetApp().plater()->force_filament_colors_update(); + } } - // Show a confirmation dialog with the list of dirty options. - wxString message = name + "\n\n"; - if (new_printer_name.empty()) - message += _(L("has the following unsaved changes:")); - else { - message += (m_type == Slic3r::Preset::TYPE_PRINTER) ? - _(L("is not compatible with printer")) : - _(L("is not compatible with print profile")); - message += wxString("\n") + tab + from_u8(new_printer_name) + "\n\n"; - message += _(L("and it has the following unsaved changes:")); + else if (dlg.move_preset()) // move selected changes + { + std::vector selected_options = dlg.get_selected_options(); + if (m_type == presets->type()) // move changes for the current preset from this tab + { + if (m_type == Preset::TYPE_PRINTER) { + auto it = std::find(selected_options.begin(), selected_options.end(), "extruders_count"); + if (it != selected_options.end()) { + // erase "extruders_count" option from the list + selected_options.erase(it); + // cache the extruders count + static_cast(this)->cache_extruder_cnt(); + } + } + + // copy selected options to the cache from edited preset + cache_config_diff(selected_options); + } + else + wxGetApp().get_tab(presets->type())->cache_config_diff(selected_options); } - wxMessageDialog confirm(parent(), - message + "\n" + changes + "\n\n" + _(L("Discard changes and continue anyway?")), - _(L("Unsaved Changes")), wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION); - return confirm.ShowModal() == wxID_YES; + + return true; } // If we are switching from the FFF-preset to the SLA, we should to control the printed objects if they have a part(s). @@ -3259,10 +3316,8 @@ void Tab::save_preset(std::string name /*= ""*/, bool detach) // focus currently.is there anything better than this ? //! m_treectrl->OnSetFocus(); - std::string suffix = detach ? _utf8(L("Detached")) : _CTX_utf8(L_CONTEXT("Copy", "PresetName"), "PresetName"); - if (name.empty()) { - SavePresetDialog dlg(m_type, suffix); + SavePresetDialog dlg(m_type, detach ? _u8L("Detached") : ""); if (dlg.ShowModal() != wxID_OK) return; name = dlg.get_name(); @@ -3573,6 +3628,25 @@ wxSizer* TabPrinter::create_bed_shape_widget(wxWindow* parent) return sizer; } +void TabPrinter::cache_extruder_cnt() +{ + if (m_presets->get_edited_preset().printer_technology() == ptSLA) + return; + + m_cache_extruder_count = m_extruders_count; +} + +void TabPrinter::apply_extruder_cnt_from_cache() +{ + if (m_presets->get_edited_preset().printer_technology() == ptSLA) + return; + + if (m_cache_extruder_count > 0) { + m_presets->get_edited_preset().set_num_extruders(m_cache_extruder_count); + m_cache_extruder_count = 0; + } +} + void Tab::compatible_widget_reload(PresetDependencies &deps) { bool has_any = ! m_config->option(deps.key_list)->values.empty(); diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index de4c5ccb9..f0b2e97b3 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -231,6 +231,8 @@ protected: } m_highlighter; + DynamicPrintConfig m_cache_config; + public: PresetBundle* m_preset_bundle; bool m_show_btn_incompatible_presets = false; @@ -329,6 +331,8 @@ public: void update_wiping_button_visibility(); void activate_option(const std::string& opt_key, const wxString& category); void apply_searcher(); + void cache_config_diff(const std::vector& selected_options); + void apply_config_from_cache(); protected: void create_line_with_widget(ConfigOptionsGroup* optgroup, const std::string& opt_key, widget_t widget); @@ -410,6 +414,7 @@ public: size_t m_extruders_count_old = 0; size_t m_initial_extruders_count; size_t m_sys_extruders_count; + size_t m_cache_extruder_count = 0; PrinterTechnology m_printer_technology = ptFFF; @@ -436,6 +441,8 @@ public: bool supports_printer_technology(const PrinterTechnology /* tech */) override { return true; } wxSizer* create_bed_shape_widget(wxWindow* parent); + void cache_extruder_cnt(); + void apply_extruder_cnt_from_cache(); }; class TabSLAMaterial : public Tab diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp new file mode 100644 index 000000000..e7dec9fa8 --- /dev/null +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -0,0 +1,1069 @@ +#include "UnsavedChangesDialog.hpp" + +#include +#include +#include +#include +#include + +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/PresetBundle.hpp" +#include "GUI_App.hpp" +#include "Plater.hpp" +#include "Tab.hpp" +#include "ExtraRenderers.hpp" +#include "wxExtensions.hpp" + +//#define FTS_FUZZY_MATCH_IMPLEMENTATION +//#include "fts_fuzzy_match.h" + +#include "BitmapCache.hpp" + +using boost::optional; + +namespace Slic3r { + +namespace GUI { + +// ---------------------------------------------------------------------------- +// ModelNode: a node inside UnsavedChangesModel +// ---------------------------------------------------------------------------- + +static const std::map type_icon_names = { + {Preset::TYPE_PRINT, "cog" }, + {Preset::TYPE_SLA_PRINT, "cog" }, + {Preset::TYPE_FILAMENT, "spool" }, + {Preset::TYPE_SLA_MATERIAL, "resin" }, + {Preset::TYPE_PRINTER, "printer" }, +}; + +static std::string black = "#000000"; +static std::string grey = "#808080"; +static std::string orange = "#ed6b21"; + +static void color_string(wxString& str, const std::string& color) +{ +#if defined(SUPPORTS_MARKUP) && !defined(__APPLE__) + str = from_u8((boost::format("%2%") % color % into_u8(str)).str()); +#endif +} + +static void make_string_bold(wxString& str) +{ +#if defined(SUPPORTS_MARKUP) && !defined(__APPLE__) + str = from_u8((boost::format("%1%") % into_u8(str)).str()); +#endif +} + +// preset(root) node +ModelNode::ModelNode(Preset::Type preset_type, const wxString& text, wxWindow* parent_win) : + m_parent_win(parent_win), + m_parent(nullptr), + m_preset_type(preset_type), + m_icon_name(type_icon_names.at(preset_type)), + m_text(text) +{ + UpdateIcons(); +} + +// group node +ModelNode::ModelNode(ModelNode* parent, const wxString& text, const std::string& icon_name) : + m_parent_win(parent->m_parent_win), + m_parent(parent), + m_icon_name(icon_name), + m_text(text) +{ + UpdateIcons(); +} + +// category node +ModelNode::ModelNode(ModelNode* parent, const wxString& text) : + m_parent_win(parent->m_parent_win), + m_parent(parent), + m_text(text) +{ +} + +#ifdef __linux__ +wxIcon ModelNode::get_bitmap(const wxString& color) +#else +wxBitmap ModelNode::get_bitmap(const wxString& color) +#endif // __linux__ +{ + /* It's supposed that standard size of an icon is 48px*16px for 100% scaled display. + * So set sizes for solid_colored icons used for filament preset + * and scale them in respect to em_unit value + */ + const double em = em_unit(m_parent_win); + const int icon_width = lround(6.4 * em); + const int icon_height = lround(1.6 * em); + + BitmapCache bmp_cache; + unsigned char rgb[3]; + BitmapCache::parse_color(into_u8(color), rgb); + // there is no need to scale created solid bitmap +#ifndef __linux__ + return bmp_cache.mksolid(icon_width, icon_height, rgb, true); +#else + wxIcon icon; + icon.CopyFromBitmap(bmp_cache.mksolid(icon_width, icon_height, rgb, true)); + return icon; +#endif // __linux__ +} + +// option node +ModelNode::ModelNode(ModelNode* parent, const wxString& text, const wxString& old_value, const wxString& new_value) : + m_parent(parent), + m_old_color(old_value.StartsWith("#") ? old_value : ""), + m_new_color(new_value.StartsWith("#") ? new_value : ""), + m_container(false), + m_text(text), + m_old_value(old_value), + m_new_value(new_value) +{ + // check if old/new_value is color + if (m_old_color.IsEmpty()) { + if (!m_new_color.IsEmpty()) + m_old_value = _L("Undef"); + } + else { + m_old_color_bmp = get_bitmap(m_old_color); + m_old_value.Clear(); + } + + if (m_new_color.IsEmpty()) { + if (!m_old_color.IsEmpty()) + m_new_value = _L("Undef"); + } + else { + m_new_color_bmp = get_bitmap(m_new_color); + m_new_value.Clear(); + } + + // "color" strings + color_string(m_old_value, black); + color_string(m_new_value, orange); +} + +void ModelNode::UpdateEnabling() +{ + auto change_text_color = [](wxString& str, const std::string& clr_from, const std::string& clr_to) + { +#if defined(SUPPORTS_MARKUP) && !defined(__APPLE__) + std::string old_val = into_u8(str); + boost::replace_all(old_val, clr_from, clr_to); + str = from_u8(old_val); +#endif + }; + + if (!m_toggle) { + change_text_color(m_text, black, grey); + change_text_color(m_old_value, black, grey); + change_text_color(m_new_value, orange,grey); + } + else { + change_text_color(m_text, grey, black); + change_text_color(m_old_value, grey, black); + change_text_color(m_new_value, grey, orange); + } + // update icons for the colors + UpdateIcons(); +} + +void ModelNode::UpdateIcons() +{ + // update icons for the colors, if any exists + if (!m_old_color.IsEmpty()) + m_old_color_bmp = get_bitmap(m_toggle ? m_old_color : grey); + if (!m_new_color.IsEmpty()) + m_new_color_bmp = get_bitmap(m_toggle ? m_new_color : grey); + + // update main icon, if any exists + if (m_icon_name.empty()) + return; + +#ifdef __linux__ + m_icon.CopyFromBitmap(create_scaled_bitmap(m_icon_name, m_parent_win, 16, !m_toggle)); +#else + m_icon = create_scaled_bitmap(m_icon_name, m_parent_win, 16, !m_toggle); +#endif //__linux__ +} + + +// ---------------------------------------------------------------------------- +// UnsavedChangesModel +// ---------------------------------------------------------------------------- + +UnsavedChangesModel::UnsavedChangesModel(wxWindow* parent) : + m_parent_win(parent) +{ +} + +UnsavedChangesModel::~UnsavedChangesModel() +{ + for (ModelNode* preset_node : m_preset_nodes) + delete preset_node; +} + +wxDataViewItem UnsavedChangesModel::AddPreset(Preset::Type type, wxString preset_name) +{ + // "color" strings + color_string(preset_name, black); + make_string_bold(preset_name); + + auto preset = new ModelNode(type, preset_name, m_parent_win); + m_preset_nodes.emplace_back(preset); + + wxDataViewItem child((void*)preset); + wxDataViewItem parent(nullptr); + + ItemAdded(parent, child); + return child; +} + +ModelNode* UnsavedChangesModel::AddOption(ModelNode* group_node, wxString option_name, wxString old_value, wxString new_value) +{ + ModelNode* option = new ModelNode(group_node, option_name, old_value, new_value); + group_node->Append(option); + wxDataViewItem group_item = wxDataViewItem((void*)group_node); + ItemAdded(group_item, wxDataViewItem((void*)option)); + + m_ctrl->Expand(group_item); + return option; +} + +ModelNode* UnsavedChangesModel::AddOptionWithGroup(ModelNode* category_node, wxString group_name, wxString option_name, wxString old_value, wxString new_value) +{ + ModelNode* group_node = new ModelNode(category_node, group_name); + category_node->Append(group_node); + ItemAdded(wxDataViewItem((void*)category_node), wxDataViewItem((void*)group_node)); + + return AddOption(group_node, option_name, old_value, new_value); +} + +ModelNode* UnsavedChangesModel::AddOptionWithGroupAndCategory(ModelNode* preset_node, wxString category_name, wxString group_name, wxString option_name, wxString old_value, wxString new_value) +{ + ModelNode* category_node = new ModelNode(preset_node, category_name, "cog"); + preset_node->Append(category_node); + ItemAdded(wxDataViewItem((void*)preset_node), wxDataViewItem((void*)category_node)); + + return AddOptionWithGroup(category_node, group_name, option_name, old_value, new_value); +} + +wxDataViewItem UnsavedChangesModel::AddOption(Preset::Type type, wxString category_name, wxString group_name, wxString option_name, + wxString old_value, wxString new_value) +{ + // "color" strings + color_string(category_name, black); + color_string(group_name, black); + color_string(option_name, black); + + // "make" strings bold + make_string_bold(category_name); + make_string_bold(group_name); + + // add items + for (ModelNode* preset : m_preset_nodes) + if (preset->type() == type) + { + for (ModelNode* category : preset->GetChildren()) + if (category->text() == category_name) + { + for (ModelNode* group : category->GetChildren()) + if (group->text() == group_name) + return wxDataViewItem((void*)AddOption(group, option_name, old_value, new_value)); + + return wxDataViewItem((void*)AddOptionWithGroup(category, group_name, option_name, old_value, new_value)); + } + + return wxDataViewItem((void*)AddOptionWithGroupAndCategory(preset, category_name, group_name, option_name, old_value, new_value)); + } + + return wxDataViewItem(nullptr); +} + +static void update_children(ModelNode* parent) +{ + if (parent->IsContainer()) { + bool toggle = parent->IsToggled(); + for (ModelNode* child : parent->GetChildren()) { + child->Toggle(toggle); + child->UpdateEnabling(); + update_children(child); + } + } +} + +static void update_parents(ModelNode* node) +{ + ModelNode* parent = node->GetParent(); + if (parent) { + bool toggle = false; + for (ModelNode* child : parent->GetChildren()) { + if (child->IsToggled()) { + toggle = true; + break; + } + } + parent->Toggle(toggle); + parent->UpdateEnabling(); + update_parents(parent); + } +} + +void UnsavedChangesModel::UpdateItemEnabling(wxDataViewItem item) +{ + assert(item.IsOk()); + ModelNode* node = (ModelNode*)item.GetID(); + node->UpdateEnabling(); + + update_children(node); + update_parents(node); +} + +bool UnsavedChangesModel::IsEnabledItem(const wxDataViewItem& item) +{ + assert(item.IsOk()); + ModelNode* node = (ModelNode*)item.GetID(); + return node->IsToggled(); +} + +void UnsavedChangesModel::GetValue(wxVariant& variant, const wxDataViewItem& item, unsigned int col) const +{ + assert(item.IsOk()); + + ModelNode* node = (ModelNode*)item.GetID(); + switch (col) + { + case colToggle: + variant = node->m_toggle; + break; +#ifdef __linux__ + case colIconText: + variant << wxDataViewIconText(node->m_text, node->m_icon); + break; + case colOldValue: + variant << wxDataViewIconText(node->m_old_value, node->m_old_color_bmp); + break; + case colNewValue: + variant << wxDataViewIconText(node->m_new_value, node->m_new_color_bmp); + break; +#else + case colIconText: + variant << DataViewBitmapText(node->m_text, node->m_icon); + break; + case colOldValue: + variant << DataViewBitmapText(node->m_old_value, node->m_old_color_bmp); + break; + case colNewValue: + variant << DataViewBitmapText(node->m_new_value, node->m_new_color_bmp); + break; +#endif //__linux__ + + default: + wxLogError("UnsavedChangesModel::GetValue: wrong column %d", col); + } +} + +bool UnsavedChangesModel::SetValue(const wxVariant& variant, const wxDataViewItem& item, unsigned int col) +{ + assert(item.IsOk()); + + ModelNode* node = (ModelNode*)item.GetID(); + switch (col) + { + case colToggle: + node->m_toggle = variant.GetBool(); + return true; +#ifdef __linux__ + case colIconText: { + wxDataViewIconText data; + data << variant; + node->m_icon = data.GetIcon(); + node->m_text = data.GetText(); + return true; } + case colOldValue: { + wxDataViewIconText data; + data << variant; + node->m_old_color_bmp = data.GetIcon(); + node->m_old_value = data.GetText(); + return true; } + case colNewValue: { + wxDataViewIconText data; + data << variant; + node->m_new_color_bmp = data.GetIcon(); + node->m_new_value = data.GetText(); + return true; } +#else + case colIconText: { + DataViewBitmapText data; + data << variant; + node->m_icon = data.GetBitmap(); + node->m_text = data.GetText(); + return true; } + case colOldValue: { + DataViewBitmapText data; + data << variant; + node->m_old_color_bmp = data.GetBitmap(); + node->m_old_value = data.GetText(); + return true; } + case colNewValue: { + DataViewBitmapText data; + data << variant; + node->m_new_color_bmp = data.GetBitmap(); + node->m_new_value = data.GetText(); + return true; } +#endif //__linux__ + default: + wxLogError("UnsavedChangesModel::SetValue: wrong column"); + } + return false; +} + +bool UnsavedChangesModel::IsEnabled(const wxDataViewItem& item, unsigned int col) const +{ + assert(item.IsOk()); + if (col == colToggle) + return true; + + // disable unchecked nodes + return ((ModelNode*)item.GetID())->IsToggled(); +} + +wxDataViewItem UnsavedChangesModel::GetParent(const wxDataViewItem& item) const +{ + // the invisible root node has no parent + if (!item.IsOk()) + return wxDataViewItem(nullptr); + + ModelNode* node = (ModelNode*)item.GetID(); + + // "MyMusic" also has no parent + if (node->IsRoot()) + return wxDataViewItem(nullptr); + + return wxDataViewItem((void*)node->GetParent()); +} + +bool UnsavedChangesModel::IsContainer(const wxDataViewItem& item) const +{ + // the invisble root node can have children + if (!item.IsOk()) + return true; + + ModelNode* node = (ModelNode*)item.GetID(); + return node->IsContainer(); +} + +unsigned int UnsavedChangesModel::GetChildren(const wxDataViewItem& parent, wxDataViewItemArray& array) const +{ + ModelNode* node = (ModelNode*)parent.GetID(); + if (!node) { + for (auto preset_node : m_preset_nodes) + array.Add(wxDataViewItem((void*)preset_node)); + return m_preset_nodes.size(); + } + + if (node->GetChildCount() == 0) + return 0; + + unsigned int count = node->GetChildren().GetCount(); + for (unsigned int pos = 0; pos < count; pos++) { + ModelNode* child = node->GetChildren().Item(pos); + array.Add(wxDataViewItem((void*)child)); + } + + return count; +} + + +wxString UnsavedChangesModel::GetColumnType(unsigned int col) const +{ + switch (col) + { + case colToggle: + return "bool"; + case colIconText: + case colOldValue: + case colNewValue: + default: + return "DataViewBitmapText";//"string"; + } +} + +static void rescale_children(ModelNode* parent) +{ + if (parent->IsContainer()) { + for (ModelNode* child : parent->GetChildren()) { + child->UpdateIcons(); + rescale_children(child); + } + } +} + +void UnsavedChangesModel::Rescale() +{ + for (ModelNode* node : m_preset_nodes) { + node->UpdateIcons(); + rescale_children(node); + } +} + + +//------------------------------------------ +// UnsavedChangesDialog +//------------------------------------------ + +UnsavedChangesDialog::UnsavedChangesDialog(const wxString& header) + : DPIDialog(nullptr, wxID_ANY, _L("Unsaved Changes"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) +{ + build(Preset::TYPE_INVALID, nullptr, "", header); +} + +UnsavedChangesDialog::UnsavedChangesDialog(Preset::Type type, PresetCollection* dependent_presets, const std::string& new_selected_preset) + : DPIDialog(nullptr, wxID_ANY, _L("Unsaved Changes"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) +{ + build(type, dependent_presets, new_selected_preset); +} + +void UnsavedChangesDialog::build(Preset::Type type, PresetCollection* dependent_presets, const std::string& new_selected_preset, const wxString& header) +{ + wxColour bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); + SetBackgroundColour(bgr_clr); + +#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT && defined(__WXMSW__) + // ys_FIXME! temporary workaround for correct font scaling + // Because of from wxWidgets 3.1.3 auto rescaling is implemented for the Fonts, + // From the very beginning set dialog font to the wxSYS_DEFAULT_GUI_FONT + this->SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); +#endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT + + int border = 10; + int em = em_unit(); + + m_action_line = new wxStaticText(this, wxID_ANY, ""); + m_action_line->SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold()); + + m_tree = new wxDataViewCtrl(this, wxID_ANY, wxDefaultPosition, wxSize(em * 80, em * 30), wxBORDER_SIMPLE | wxDV_VARIABLE_LINE_HEIGHT | wxDV_ROW_LINES); + m_tree_model = new UnsavedChangesModel(this); + m_tree->AssociateModel(m_tree_model); + m_tree_model->SetAssociatedControl(m_tree); + + m_tree->AppendToggleColumn(L"\u2714", UnsavedChangesModel::colToggle, wxDATAVIEW_CELL_ACTIVATABLE, 6 * em);//2610,11,12 //2714 + + auto append_bmp_text_column = [this](const wxString& label, unsigned model_column, int width, bool set_expander = false) + { +#ifdef __linux__ + wxDataViewIconTextRenderer* rd = new wxDataViewIconTextRenderer(); +#ifdef SUPPORTS_MARKUP + rd->EnableMarkup(true); +#endif + wxDataViewColumn* column = new wxDataViewColumn(label, rd, model_column, width, wxALIGN_TOP, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_CELL_INERT); +#else + wxDataViewColumn* column = new wxDataViewColumn(label, new BitmapTextRenderer(true), model_column, width, wxALIGN_TOP, wxDATAVIEW_COL_RESIZABLE); +#endif //__linux__ + m_tree->AppendColumn(column); + if (set_expander) + m_tree->SetExpanderColumn(column); + }; + + append_bmp_text_column("", UnsavedChangesModel::colIconText, 30 * em); + append_bmp_text_column(_L("Old Value"), UnsavedChangesModel::colOldValue, 20 * em); + append_bmp_text_column(_L("New Value"), UnsavedChangesModel::colNewValue, 20 * em); + + m_tree->Bind(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, &UnsavedChangesDialog::item_value_changed, this); + m_tree->Bind(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, &UnsavedChangesDialog::context_menu, this); + + // Add Buttons + wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxCANCEL); + + auto add_btn = [this, buttons](ScalableButton** btn, int& btn_id, const std::string& icon_name, Action close_act, int idx, bool process_enable = true) + { + *btn = new ScalableButton(this, btn_id = NewControlId(), icon_name, "", wxDefaultSize, wxDefaultPosition, wxBORDER_DEFAULT, true); + buttons->Insert(idx, *btn, 0, wxLEFT, 5); + + (*btn)->Bind(wxEVT_BUTTON, [this, close_act](wxEvent&) { close(close_act); }); + if (process_enable) + (*btn)->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(!m_empty_selection); }); + (*btn)->Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& e) { show_info_line(Action::Undef); e.Skip(); }); + }; + + int btn_idx = 0; + add_btn(&m_save_btn, m_save_btn_id, "save", Action::Save, btn_idx++); + + const PresetCollection& printers = wxGetApp().preset_bundle->printers; + if (dependent_presets && (type == dependent_presets->type() ? + dependent_presets->get_edited_preset().printer_technology() == dependent_presets->find_preset(new_selected_preset)->printer_technology() : + printers.get_edited_preset().printer_technology() == printers.find_preset(new_selected_preset)->printer_technology() ) ) + add_btn(&m_move_btn, m_move_btn_id, "paste_menu", Action::Move, btn_idx++); + add_btn(&m_continue_btn, m_continue_btn_id, "cross", Action::Continue, btn_idx, false); + + m_info_line = new wxStaticText(this, wxID_ANY, ""); + m_info_line->SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold()); + m_info_line->Hide(); + + wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); + + topSizer->Add(m_action_line,0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); + topSizer->Add(m_tree, 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); + topSizer->Add(m_info_line, 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, 2*border); + topSizer->Add(buttons, 0, wxEXPAND | wxALL, border); + + update(type, dependent_presets, new_selected_preset, header); + + SetSizer(topSizer); + topSizer->SetSizeHints(this); + + show_info_line(Action::Undef); +} + +void UnsavedChangesDialog::item_value_changed(wxDataViewEvent& event) +{ + if (event.GetColumn() != UnsavedChangesModel::colToggle) + return; + + wxDataViewItem item = event.GetItem(); + + m_tree_model->UpdateItemEnabling(item); + m_tree->Refresh(); + + // update an enabling of the "save/move" buttons + m_empty_selection = get_selected_options().empty(); +} + +void UnsavedChangesDialog::context_menu(wxDataViewEvent& event) +{ + if (!m_has_long_strings) + return; + + wxDataViewItem item = event.GetItem(); + if (!item) + { + wxPoint mouse_pos = wxGetMousePosition() - m_tree->GetScreenPosition(); + wxDataViewColumn* col = nullptr; + m_tree->HitTest(mouse_pos, item, col); + + if (!item) + item = m_tree->GetSelection(); + + if (!item) + return; + } + + auto it = m_items_map.find(item); + if (it == m_items_map.end() || !it->second.is_long) + return; + FullCompareDialog(it->second.opt_name, it->second.old_val, it->second.new_val).ShowModal(); +} + +void UnsavedChangesDialog::show_info_line(Action action, std::string preset_name) +{ + if (m_motion_action == action) + return; + if (action == Action::Undef && !m_has_long_strings) + m_info_line->Hide(); + else { + wxString text; + if (action == Action::Undef) + text = _L("Some fields are too long to fit. Right click on it to show full text."); + else if (action == Action::Continue) + text = _L("All changed options will be reverted."); + else { + std::string act_string = action == Action::Save ? _u8L("save") : _u8L("move"); + if (preset_name.empty()) + text = from_u8((boost::format("Press to %1% selected options.") % act_string).str()); + else + text = from_u8((boost::format("Press to %1% selected options to the preset \"%2%\".") % act_string % preset_name).str()); + text += "\n" + _L("Unselected options will be reverted."); + } + m_info_line->SetLabel(text); + m_info_line->Show(); + } + + m_motion_action = action; + + Layout(); + Refresh(); +} + +void UnsavedChangesDialog::close(Action action) +{ + m_exit_action = action; + this->EndModal(wxID_CLOSE); +} + +template +wxString get_string_from_enum(const std::string& opt_key, const DynamicPrintConfig& config, bool is_infill = false) +{ + const ConfigOptionDef& def = config.def()->options.at(opt_key); + const std::vector& names = def.enum_labels;//ConfigOptionEnum::get_enum_names(); + T val = config.option>(opt_key)->value; + + // Each infill doesn't use all list of infill declared in PrintConfig.hpp. + // So we should "convert" val to the correct one + if (is_infill) { + for (auto key_val : *def.enum_keys_map) + if ((int)key_val.second == (int)val) { + auto it = std::find(def.enum_values.begin(), def.enum_values.end(), key_val.first); + if (it == def.enum_values.end()) + return ""; + return from_u8(_utf8(names[it - def.enum_values.begin()])); + } + return _L("Undef"); + } + return from_u8(_utf8(names[static_cast(val)])); +} + +static int get_id_from_opt_key(std::string opt_key) +{ + int pos = opt_key.find("#"); + if (pos > 0) { + boost::erase_head(opt_key, pos + 1); + return atoi(opt_key.c_str()); + } + return 0; +} + +static std::string get_pure_opt_key(std::string opt_key) +{ + int pos = opt_key.find("#"); + if (pos > 0) + boost::erase_tail(opt_key, opt_key.size() - pos); + return opt_key; +} + +static wxString get_string_value(std::string opt_key, const DynamicPrintConfig& config) +{ + int opt_idx = get_id_from_opt_key(opt_key); + opt_key = get_pure_opt_key(opt_key); + + if (config.option(opt_key)->is_nil()) + return _L("N/A"); + + wxString out; + + const ConfigOptionDef* opt = config.def()->get(opt_key); + bool is_nullable = opt->nullable; + + switch (opt->type) { + case coInt: + return from_u8((boost::format("%1%") % config.opt_int(opt_key)).str()); + case coInts: { + int val = is_nullable ? + config.opt(opt_key)->get_at(opt_idx) : + config.opt(opt_key)->get_at(opt_idx); + return from_u8((boost::format("%1%") % val).str()); + } + case coBool: + return config.opt_bool(opt_key) ? "true" : "false"; + case coBools: { + bool val = is_nullable ? + config.opt(opt_key)->get_at(opt_idx) : + config.opt(opt_key)->get_at(opt_idx); + return val ? "true" : "false"; + } + case coPercent: + return from_u8((boost::format("%1%%%") % int(config.optptr(opt_key)->getFloat())).str()); + case coPercents: { + double val = is_nullable ? + config.opt(opt_key)->get_at(opt_idx) : + config.opt(opt_key)->get_at(opt_idx); + return from_u8((boost::format("%1%%%") % int(val)).str()); + } + case coFloat: + return double_to_string(config.opt_float(opt_key)); + case coFloats: { + double val = is_nullable ? + config.opt(opt_key)->get_at(opt_idx) : + config.opt(opt_key)->get_at(opt_idx); + return double_to_string(val); + } + case coString: + return from_u8(config.opt_string(opt_key)); + case coStrings: { + const ConfigOptionStrings* strings = config.opt(opt_key); + if (strings) { + if (opt_key == "compatible_printers" || opt_key == "compatible_prints") { + if (strings->empty()) + return _L("All"); + for (size_t id = 0; id < strings->size(); id++) + out += from_u8(strings->get_at(id)) + "\n"; + out.RemoveLast(1); + return out; + } + if (!strings->empty()) + return from_u8(strings->get_at(opt_idx)); + } + break; + } + case coFloatOrPercent: { + const ConfigOptionFloatOrPercent* opt = config.opt(opt_key); + if (opt) + out = double_to_string(opt->value) + (opt->percent ? "%" : ""); + return out; + } + case coEnum: { + if (opt_key == "top_fill_pattern" || + opt_key == "bottom_fill_pattern" || + opt_key == "fill_pattern") + return get_string_from_enum(opt_key, config, true); + if (opt_key == "gcode_flavor") + return get_string_from_enum(opt_key, config); + if (opt_key == "ironing_type") + return get_string_from_enum(opt_key, config); + if (opt_key == "support_material_pattern") + return get_string_from_enum(opt_key, config); + if (opt_key == "seam_position") + return get_string_from_enum(opt_key, config); + if (opt_key == "display_orientation") + return get_string_from_enum(opt_key, config); + if (opt_key == "support_pillar_connection_mode") + return get_string_from_enum(opt_key, config); + break; + } + case coPoints: { + if (opt_key == "bed_shape") { + BedShape shape(*config.option(opt_key)); + return shape.get_full_name_with_params(); + } + Vec2d val = config.opt(opt_key)->get_at(opt_idx); + return from_u8((boost::format("[%1%]") % ConfigOptionPoint(val).serialize()).str()); + } + default: + break; + } + return out; +} + +wxString UnsavedChangesDialog::get_short_string(wxString full_string) +{ + int max_len = 30; + if (full_string.IsEmpty() || full_string.StartsWith("#") || + (full_string.Find("\n") == wxNOT_FOUND && full_string.Length() < max_len)) + return full_string; + + m_has_long_strings = true; + + int n_pos = full_string.Find("\n"); + if (n_pos != wxNOT_FOUND && n_pos < max_len) + max_len = n_pos; + + full_string.Truncate(max_len); + return full_string + dots; +} + +void UnsavedChangesDialog::update(Preset::Type type, PresetCollection* dependent_presets, const std::string& new_selected_preset, const wxString& header) +{ + PresetCollection* presets = dependent_presets; + + // activate buttons and labels + m_save_btn ->Bind(wxEVT_ENTER_WINDOW, [this, presets] (wxMouseEvent& e) { show_info_line(Action::Save, presets ? presets->get_selected_preset().name : ""); e.Skip(); }); + if (m_move_btn) { + bool is_empty_name = type != dependent_presets->type(); + m_move_btn ->Bind(wxEVT_ENTER_WINDOW, [this, new_selected_preset, is_empty_name] (wxMouseEvent& e) { show_info_line(Action::Move, is_empty_name ? "" : new_selected_preset); e.Skip(); }); + } + m_continue_btn ->Bind(wxEVT_ENTER_WINDOW, [this] (wxMouseEvent& e) { show_info_line(Action::Continue); e.Skip(); }); + + m_continue_btn->SetLabel(_L("Continue without changes")); + + if (type == Preset::TYPE_INVALID) { + m_action_line ->SetLabel(header + "\n" + _L("Next presets have the following unsaved changes:")); + m_save_btn ->SetLabel(_L("Save selected")); + } + else { + wxString action_msg; + if (type == dependent_presets->type()) { + action_msg = _L("has the following unsaved changes:"); + if (m_move_btn) + m_move_btn->SetLabel(from_u8((boost::format(_u8L("Move selected to preset: %1%")) % ("\"" + new_selected_preset + "\"")).str())); + } + else { + action_msg = type == Preset::TYPE_PRINTER ? + _L("is not compatible with printer") : + _L("is not compatible with print profile"); + action_msg += " \"" + from_u8(new_selected_preset) + "\"\n"; + action_msg += _L("and it has the following unsaved changes:"); + + if (m_move_btn) + m_move_btn->SetLabel(_L("Move selected to the first compatible preset")); + } + m_action_line->SetLabel(from_u8((boost::format(_utf8(L("Preset \"%1%\" %2%"))) % _utf8(presets->get_edited_preset().name) % action_msg).str())); + m_save_btn->SetLabel(from_u8((boost::format(_u8L("Save selected to preset: %1%")) % ("\"" + presets->get_selected_preset().name + "\"")).str())); + } + + update_tree(type, presets); +} + +void UnsavedChangesDialog::update_tree(Preset::Type type, PresetCollection* presets_) +{ + Search::OptionsSearcher& searcher = wxGetApp().sidebar().get_searcher(); + searcher.sort_options_by_opt_key(); + + // list of the presets with unsaved changes + std::vector presets_list; + if (type == Preset::TYPE_INVALID) + { + PrinterTechnology printer_technology = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology(); + + for (Tab* tab : wxGetApp().tabs_list) + if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty()) + presets_list.emplace_back(tab->get_presets()); + } + else + presets_list.emplace_back(presets_); + + // Display a dialog showing the dirty options in a human readable form. + for (PresetCollection* presets : presets_list) + { + const DynamicPrintConfig& old_config = presets->get_selected_preset().config; + const DynamicPrintConfig& new_config = presets->get_edited_preset().config; + type = presets->type(); + + m_tree_model->AddPreset(type, from_u8(presets->get_edited_preset().name)); + + // Collect dirty options. + const bool deep_compare = (type == Preset::TYPE_PRINTER || type == Preset::TYPE_SLA_MATERIAL); + auto dirty_options = presets->current_dirty_options(deep_compare); + auto dirty_options_ = presets->current_dirty_options(); + + // process changes of extruders count + if (type == Preset::TYPE_PRINTER && + old_config.opt("extruder_colour")->values.size() != new_config.opt("extruder_colour")->values.size()) { + wxString local_label = _L("Extruders count"); + wxString old_val = from_u8((boost::format("%1%") % old_config.opt("extruder_colour")->values.size()).str()); + wxString new_val = from_u8((boost::format("%1%") % new_config.opt("extruder_colour")->values.size()).str()); + + ItemData item_data = { "extruders_count", local_label, old_val, new_val, type }; + m_items_map.emplace(m_tree_model->AddOption(type, _L("General"), _L("Capabilities"), local_label, old_val, new_val), item_data); + + } + + for (const std::string& opt_key : dirty_options) { + const Search::Option& option = searcher.get_option(opt_key); + + ItemData item_data = { opt_key, option.label_local, get_string_value(opt_key, old_config), get_string_value(opt_key, new_config), type }; + + wxString old_val = get_short_string(item_data.old_val); + wxString new_val = get_short_string(item_data.new_val); + if (old_val != item_data.old_val || new_val != item_data.new_val) + item_data.is_long = true; + + m_items_map.emplace(m_tree_model->AddOption(type, option.category_local, option.group_local, option.label_local, old_val, new_val), item_data); + } + } +} + +std::vector UnsavedChangesDialog::get_unselected_options(Preset::Type type) +{ + std::vector ret; + + for (auto item : m_items_map) { + if (item.second.opt_key == "extruders_count") + continue; + if (item.second.type == type && !m_tree_model->IsEnabledItem(item.first)) + ret.emplace_back(get_pure_opt_key(item.second.opt_key)); + } + + return ret; +} + +std::vector UnsavedChangesDialog::get_selected_options() +{ + std::vector ret; + + for (auto item : m_items_map) + if (m_tree_model->IsEnabledItem(item.first)) + ret.emplace_back(get_pure_opt_key(item.second.opt_key)); + + return ret; +} + +void UnsavedChangesDialog::on_dpi_changed(const wxRect& suggested_rect) +{ + int em = em_unit(); + + msw_buttons_rescale(this, em, { wxID_CANCEL, m_save_btn_id, m_move_btn_id, m_continue_btn_id }); + for (auto btn : { m_save_btn, m_move_btn, m_continue_btn } ) + btn->msw_rescale(); + + const wxSize& size = wxSize(80 * em, 30 * em); + SetMinSize(size); + + m_tree->GetColumn(UnsavedChangesModel::colToggle )->SetWidth(6 * em); + m_tree->GetColumn(UnsavedChangesModel::colIconText)->SetWidth(30 * em); + m_tree->GetColumn(UnsavedChangesModel::colOldValue)->SetWidth(20 * em); + m_tree->GetColumn(UnsavedChangesModel::colNewValue)->SetWidth(20 * em); + + m_tree_model->Rescale(); + m_tree->Refresh(); + + Fit(); + Refresh(); +} + +void UnsavedChangesDialog::on_sys_color_changed() +{ + for (auto btn : { m_save_btn, m_move_btn, m_continue_btn } ) + btn->msw_rescale(); + // msw_rescale updates just icons, so use it + m_tree_model->Rescale(); + m_tree->Refresh(); + + Refresh(); +} + + +//------------------------------------------ +// FullCompareDialog +//------------------------------------------ + +FullCompareDialog::FullCompareDialog(const wxString& option_name, const wxString& old_value, const wxString& new_value) + : wxDialog(nullptr, wxID_ANY, option_name, wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) +{ + wxColour bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); + SetBackgroundColour(bgr_clr); + + int border = 10; + + wxStaticBoxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, this); + + wxFlexGridSizer* grid_sizer = new wxFlexGridSizer(2, 2, 1, 0); + grid_sizer->SetFlexibleDirection(wxBOTH); + grid_sizer->AddGrowableCol(0,1); + grid_sizer->AddGrowableCol(1,1); + grid_sizer->AddGrowableRow(1,1); + + auto add_header = [grid_sizer, border, this](wxString label) { + wxStaticText* text = new wxStaticText(this, wxID_ANY, label); + text->SetFont(this->GetFont().Bold()); + grid_sizer->Add(text, 0, wxALL, border); + }; + + auto add_value = [grid_sizer, border, this](wxString label, bool is_colored = false) { + wxTextCtrl* text = new wxTextCtrl(this, wxID_ANY, label, wxDefaultPosition, wxSize(300, -1), wxTE_MULTILINE | wxTE_READONLY | wxBORDER_NONE | wxTE_RICH); + text->SetStyle(0, label.Len(), wxTextAttr(is_colored ? wxColour(orange) : wxNullColour, wxNullColour, this->GetFont())); + grid_sizer->Add(text, 1, wxALL | wxEXPAND, border); + }; + + add_header(_L("Old value")); + add_header(_L("New value")); + add_value(old_value); + add_value(new_value, true); + + sizer->Add(grid_sizer, 1, wxEXPAND); + + wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxOK); + + wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); + + topSizer->Add(sizer, 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, border); + topSizer->Add(buttons, 0, wxEXPAND | wxALL, border); + + SetSizer(topSizer); + topSizer->SetSizeHints(this); +} + + +} + +} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/UnsavedChangesDialog.hpp b/src/slic3r/GUI/UnsavedChangesDialog.hpp new file mode 100644 index 000000000..f78a1fec0 --- /dev/null +++ b/src/slic3r/GUI/UnsavedChangesDialog.hpp @@ -0,0 +1,272 @@ +#ifndef slic3r_UnsavedChangesDialog_hpp_ +#define slic3r_UnsavedChangesDialog_hpp_ + +#include +#include +#include + +#include "GUI_Utils.hpp" +#include "wxExtensions.hpp" +#include "libslic3r/Preset.hpp" + +class ScalableButton; +class wxStaticText; + +namespace Slic3r { +namespace GUI{ + +// ---------------------------------------------------------------------------- +// ModelNode: a node inside UnsavedChangesModel +// ---------------------------------------------------------------------------- + +class ModelNode; +WX_DEFINE_ARRAY_PTR(ModelNode*, ModelNodePtrArray); + +// On all of 3 different platforms Bitmap+Text icon column looks different +// because of Markup text is missed or not implemented. +// As a temporary workaround, we will use: +// MSW - DataViewBitmapText (our custom renderer wxBitmap + wxString, supported Markup text) +// OSX - -//-, but Markup text is not implemented right now +// GTK - wxDataViewIconText (wxWidgets for GTK renderer wxIcon + wxString, supported Markup text) +class ModelNode +{ + wxWindow* m_parent_win{ nullptr }; + + ModelNode* m_parent; + ModelNodePtrArray m_children; + wxBitmap m_empty_bmp; + Preset::Type m_preset_type {Preset::TYPE_INVALID}; + + std::string m_icon_name; + // saved values for colors if they exist + wxString m_old_color; + wxString m_new_color; + + // TODO/FIXME: + // the GTK version of wxDVC (in particular wxDataViewCtrlInternal::ItemAdded) + // needs to know in advance if a node is or _will be_ a container. + // Thus implementing: + // bool IsContainer() const + // { return m_children.GetCount()>0; } + // doesn't work with wxGTK when UnsavedChangesModel::AddToClassical is called + // AND the classical node was removed (a new node temporary without children + // would be added to the control) + bool m_container {true}; + +#ifdef __linux__ + wxIcon get_bitmap(const wxString& color); +#else + wxBitmap get_bitmap(const wxString& color); +#endif //__linux__ + +public: + + bool m_toggle {true}; +#ifdef __linux__ + wxIcon m_icon; + wxIcon m_old_color_bmp; + wxIcon m_new_color_bmp; +#else + wxBitmap m_icon; + wxBitmap m_old_color_bmp; + wxBitmap m_new_color_bmp; +#endif //__linux__ + wxString m_text; + wxString m_old_value; + wxString m_new_value; + + // preset(root) node + ModelNode(Preset::Type preset_type, const wxString& text, wxWindow* parent_win); + + // category node + ModelNode(ModelNode* parent, const wxString& text, const std::string& icon_name); + + // group node + ModelNode(ModelNode* parent, const wxString& text); + + // option node + ModelNode(ModelNode* parent, const wxString& text, const wxString& old_value, const wxString& new_value); + + ~ModelNode() { + // free all our children nodes + size_t count = m_children.GetCount(); + for (size_t i = 0; i < count; i++) { + ModelNode* child = m_children[i]; + delete child; + } + } + + bool IsContainer() const { return m_container; } + bool IsToggled() const { return m_toggle; } + void Toggle(bool toggle = true) { m_toggle = toggle; } + bool IsRoot() const { return m_parent == nullptr; } + Preset::Type type() const { return m_preset_type; } + const wxString& text() const { return m_text; } + + ModelNode* GetParent() { return m_parent; } + ModelNodePtrArray& GetChildren() { return m_children; } + ModelNode* GetNthChild(unsigned int n) { return m_children.Item(n); } + unsigned int GetChildCount() const { return m_children.GetCount(); } + + void Insert(ModelNode* child, unsigned int n) { m_children.Insert(child, n); } + void Append(ModelNode* child) { m_children.Add(child); } + + void UpdateEnabling(); + void UpdateIcons(); +}; + + +// ---------------------------------------------------------------------------- +// UnsavedChangesModel +// ---------------------------------------------------------------------------- + +class UnsavedChangesModel : public wxDataViewModel +{ + wxWindow* m_parent_win { nullptr }; + std::vector m_preset_nodes; + + wxDataViewCtrl* m_ctrl{ nullptr }; + + ModelNode *AddOption(ModelNode *group_node, + wxString option_name, + wxString old_value, + wxString new_value); + ModelNode *AddOptionWithGroup(ModelNode *category_node, + wxString group_name, + wxString option_name, + wxString old_value, + wxString new_value); + ModelNode *AddOptionWithGroupAndCategory(ModelNode *preset_node, + wxString category_name, + wxString group_name, + wxString option_name, + wxString old_value, + wxString new_value); + +public: + enum { + colToggle, + colIconText, + colOldValue, + colNewValue, + colMax + }; + + UnsavedChangesModel(wxWindow* parent); + ~UnsavedChangesModel(); + + void SetAssociatedControl(wxDataViewCtrl* ctrl) { m_ctrl = ctrl; } + + wxDataViewItem AddPreset(Preset::Type type, wxString preset_name); + wxDataViewItem AddOption(Preset::Type type, wxString category_name, wxString group_name, wxString option_name, + wxString old_value, wxString new_value); + + void UpdateItemEnabling(wxDataViewItem item); + bool IsEnabledItem(const wxDataViewItem& item); + + unsigned int GetColumnCount() const override { return colMax; } + wxString GetColumnType(unsigned int col) const override; + void Rescale(); + + wxDataViewItem GetParent(const wxDataViewItem& item) const override; + unsigned int GetChildren(const wxDataViewItem& parent, wxDataViewItemArray& array) const override; + + void GetValue(wxVariant& variant, const wxDataViewItem& item, unsigned int col) const override; + bool SetValue(const wxVariant& variant, const wxDataViewItem& item, unsigned int col) override; + + bool IsEnabled(const wxDataViewItem& item, unsigned int col) const override; + bool IsContainer(const wxDataViewItem& item) const override; + // Is the container just a header or an item with all columns + // In our case it is an item with all columns + bool HasContainerColumns(const wxDataViewItem& WXUNUSED(item)) const override { return true; } +}; + + +//------------------------------------------ +// UnsavedChangesDialog +//------------------------------------------ +class UnsavedChangesDialog : public DPIDialog +{ + wxDataViewCtrl* m_tree { nullptr }; + UnsavedChangesModel* m_tree_model { nullptr }; + + ScalableButton* m_save_btn { nullptr }; + ScalableButton* m_move_btn { nullptr }; + ScalableButton* m_continue_btn { nullptr }; + wxStaticText* m_action_line { nullptr }; + wxStaticText* m_info_line { nullptr }; + + bool m_empty_selection { false }; + bool m_has_long_strings { false }; + int m_save_btn_id { wxID_ANY }; + int m_move_btn_id { wxID_ANY }; + int m_continue_btn_id { wxID_ANY }; + + enum class Action { + Undef, + Save, + Move, + Continue + }; + + // selected action after Dialog closing + Action m_exit_action {Action::Undef}; + + // Action during mouse motion + Action m_motion_action {Action::Undef}; + + struct ItemData + { + std::string opt_key; + wxString opt_name; + wxString old_val; + wxString new_val; + Preset::Type type; + bool is_long {false}; + }; + // tree items related to the options + std::map m_items_map; + +public: + UnsavedChangesDialog(const wxString& header); + UnsavedChangesDialog(Preset::Type type, PresetCollection* dependent_presets, const std::string& new_selected_preset); + ~UnsavedChangesDialog() {} + + wxString get_short_string(wxString full_string); + + void build(Preset::Type type, PresetCollection* dependent_presets, const std::string& new_selected_preset, const wxString& header = ""); + void update(Preset::Type type, PresetCollection* dependent_presets, const std::string& new_selected_preset, const wxString& header); + void update_tree(Preset::Type type, PresetCollection *presets); + void item_value_changed(wxDataViewEvent &event); + void context_menu(wxDataViewEvent &event); + void show_info_line(Action action, std::string preset_name = ""); + void close(Action action); + + bool save_preset() const { return m_exit_action == Action::Save; } + bool move_preset() const { return m_exit_action == Action::Move; } + bool just_continue() const { return m_exit_action == Action::Continue; } + + std::vector get_unselected_options(Preset::Type type); + std::vector get_selected_options(); + +protected: + void on_dpi_changed(const wxRect& suggested_rect) override; + void on_sys_color_changed() override; +}; + + +//------------------------------------------ +// FullCompareDialog +//------------------------------------------ +class FullCompareDialog : public wxDialog +{ +public: + FullCompareDialog(const wxString& option_name, const wxString& old_value, const wxString& new_value); + ~FullCompareDialog() {} +}; + + +} +} + +#endif //slic3r_UnsavedChangesDialog_hpp_ diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 37794b5af..6f79db591 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -436,7 +436,7 @@ wxBitmap create_scaled_bitmap( const std::string& bmp_name_in, if (bmp == nullptr) { // Neither SVG nor PNG has been found, raise error - throw std::runtime_error("Could not load bitmap: " + bmp_name); + throw Slic3r::RuntimeError("Could not load bitmap: " + bmp_name); } return *bmp; @@ -786,9 +786,11 @@ ScalableButton::ScalableButton( wxWindow * parent, const wxString& label /* = wxEmptyString*/, const wxSize& size /* = wxDefaultSize*/, const wxPoint& pos /* = wxDefaultPosition*/, - long style /*= wxBU_EXACTFIT | wxNO_BORDER*/) : + long style /*= wxBU_EXACTFIT | wxNO_BORDER*/, + bool use_default_disabled_bitmap/* = false*/) : + m_parent(parent), m_current_icon_name(icon_name), - m_parent(parent) + m_use_default_disabled_bitmap (use_default_disabled_bitmap) { Create(parent, id, label, pos, size, style); #ifdef __WXMSW__ @@ -797,6 +799,8 @@ ScalableButton::ScalableButton( wxWindow * parent, #endif // __WXMSW__ SetBitmap(create_scaled_bitmap(icon_name, parent)); + if (m_use_default_disabled_bitmap) + SetBitmapDisabled(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt, true)); if (size != wxDefaultSize) { @@ -846,11 +850,19 @@ int ScalableButton::GetBitmapHeight() #endif } +void ScalableButton::UseDefaultBitmapDisabled() +{ + m_use_default_disabled_bitmap = true; + SetBitmapDisabled(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt, true)); +} + void ScalableButton::msw_rescale() { SetBitmap(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt)); if (!m_disabled_icon_name.empty()) SetBitmapDisabled(create_scaled_bitmap(m_disabled_icon_name, m_parent, m_px_cnt)); + else if (m_use_default_disabled_bitmap) + SetBitmapDisabled(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt, true)); if (m_width > 0 || m_height>0) { diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index b2c71c4a0..4f04bc5b2 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -209,7 +209,8 @@ public: const wxString& label = wxEmptyString, const wxSize& size = wxDefaultSize, const wxPoint& pos = wxDefaultPosition, - long style = wxBU_EXACTFIT | wxNO_BORDER); + long style = wxBU_EXACTFIT | wxNO_BORDER, + bool use_default_disabled_bitmap = false); ScalableButton( wxWindow * parent, @@ -223,6 +224,7 @@ public: void SetBitmap_(const ScalableBitmap& bmp); void SetBitmapDisabled_(const ScalableBitmap &bmp); int GetBitmapHeight(); + void UseDefaultBitmapDisabled(); void msw_rescale(); @@ -233,6 +235,8 @@ private: int m_width {-1}; // should be multiplied to em_unit int m_height{-1}; // should be multiplied to em_unit + bool m_use_default_disabled_bitmap {false}; + // bitmap dimensions int m_px_cnt{ 16 }; }; @@ -334,7 +338,7 @@ class BlinkingBitmap : public wxStaticBitmap { public: BlinkingBitmap() {}; - BlinkingBitmap(wxWindow* parent, const std::string& icon_name = "redo_toolbar"); + BlinkingBitmap(wxWindow* parent, const std::string& icon_name = "search_blink"); ~BlinkingBitmap() {} diff --git a/src/slic3r/Utils/FixModelByWin10.cpp b/src/slic3r/Utils/FixModelByWin10.cpp index 86ff79aaa..bcab6daaf 100644 --- a/src/slic3r/Utils/FixModelByWin10.cpp +++ b/src/slic3r/Utils/FixModelByWin10.cpp @@ -209,10 +209,10 @@ typedef std::functionGetResults(model.GetAddressOf()); else - throw std::runtime_error(L("Failed loading the input model.")); + throw Slic3r::RuntimeError(L("Failed loading the input model.")); Microsoft::WRL::ComPtr> meshes; hr = model->get_Meshes(meshes.GetAddressOf()); @@ -245,7 +245,7 @@ void fix_model_by_win10_sdk(const std::string &path_src, const std::string &path hr = model->RepairAsync(repairAsync.GetAddressOf()); status = winrt_async_await(repairAsync, throw_on_cancel); if (status != AsyncStatus::Completed) - throw std::runtime_error(L("Mesh repair failed.")); + throw Slic3r::RuntimeError(L("Mesh repair failed.")); repairAsync->GetResults(); on_progress(L("Loading repaired model"), 60); @@ -260,14 +260,14 @@ void fix_model_by_win10_sdk(const std::string &path_src, const std::string &path hr = printing3d3mfpackage->SaveModelToPackageAsync(model.Get(), saveToPackageAsync.GetAddressOf()); status = winrt_async_await(saveToPackageAsync, throw_on_cancel); if (status != AsyncStatus::Completed) - throw std::runtime_error(L("Saving mesh into the 3MF container failed.")); + throw Slic3r::RuntimeError(L("Saving mesh into the 3MF container failed.")); hr = saveToPackageAsync->GetResults(); Microsoft::WRL::ComPtr> generatorStreamAsync; hr = printing3d3mfpackage->SaveAsync(generatorStreamAsync.GetAddressOf()); status = winrt_async_await(generatorStreamAsync, throw_on_cancel); if (status != AsyncStatus::Completed) - throw std::runtime_error(L("Saving mesh into the 3MF container failed.")); + throw Slic3r::RuntimeError(L("Saving mesh into the 3MF container failed.")); Microsoft::WRL::ComPtr generatorStream; hr = generatorStreamAsync->GetResults(generatorStream.GetAddressOf()); @@ -299,7 +299,7 @@ void fix_model_by_win10_sdk(const std::string &path_src, const std::string &path hr = inputStream->ReadAsync(buffer.Get(), 65536 * 2048, ABI::Windows::Storage::Streams::InputStreamOptions_ReadAhead, asyncRead.GetAddressOf()); status = winrt_async_await(asyncRead, throw_on_cancel); if (status != AsyncStatus::Completed) - throw std::runtime_error(L("Saving mesh into the 3MF container failed.")); + throw Slic3r::RuntimeError(L("Saving mesh into the 3MF container failed.")); hr = buffer->get_Length(&length); if (length == 0) break; @@ -365,7 +365,7 @@ void fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx) model_object->add_instance(); if (!Slic3r::store_3mf(path_src.string().c_str(), &model, nullptr, false)) { boost::filesystem::remove(path_src); - throw std::runtime_error(L("Export of a temporary 3mf file failed")); + throw Slic3r::RuntimeError(L("Export of a temporary 3mf file failed")); } model.clear_objects(); model.clear_materials(); @@ -380,15 +380,15 @@ void fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx) bool loaded = Slic3r::load_3mf(path_dst.string().c_str(), &config, &model, false); boost::filesystem::remove(path_dst); if (! loaded) - throw std::runtime_error(L("Import of the repaired 3mf file failed")); + throw Slic3r::RuntimeError(L("Import of the repaired 3mf file failed")); if (model.objects.size() == 0) - throw std::runtime_error(L("Repaired 3MF file does not contain any object")); + throw Slic3r::RuntimeError(L("Repaired 3MF file does not contain any object")); if (model.objects.size() > 1) - throw std::runtime_error(L("Repaired 3MF file contains more than one object")); + throw Slic3r::RuntimeError(L("Repaired 3MF file contains more than one object")); if (model.objects.front()->volumes.size() == 0) - throw std::runtime_error(L("Repaired 3MF file does not contain any volume")); + throw Slic3r::RuntimeError(L("Repaired 3MF file does not contain any volume")); if (model.objects.front()->volumes.size() > 1) - throw std::runtime_error(L("Repaired 3MF file contains more than one volume")); + throw Slic3r::RuntimeError(L("Repaired 3MF file contains more than one volume")); meshes_repaired.emplace_back(std::move(model.objects.front()->volumes.front()->mesh())); } for (size_t i = 0; i < volumes.size(); ++ i) { diff --git a/src/slic3r/Utils/Http.cpp b/src/slic3r/Utils/Http.cpp index a16aac5b5..8c79a478a 100644 --- a/src/slic3r/Utils/Http.cpp +++ b/src/slic3r/Utils/Http.cpp @@ -156,7 +156,7 @@ Http::priv::priv(const std::string &url) Http::tls_global_init(); if (curl == nullptr) { - throw std::runtime_error(std::string("Could not construct Curl object")); + throw Slic3r::RuntimeError(std::string("Could not construct Curl object")); } set_timeout_connect(DEFAULT_TIMEOUT_CONNECT); @@ -415,6 +415,16 @@ Http& Http::remove_header(std::string name) return *this; } +// Authorization by HTTP digest, based on RFC2617. +Http& Http::auth_digest(const std::string &user, const std::string &password) +{ + curl_easy_setopt(p->curl, CURLOPT_USERNAME, user.c_str()); + curl_easy_setopt(p->curl, CURLOPT_PASSWORD, password.c_str()); + curl_easy_setopt(p->curl, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); + + return *this; +} + Http& Http::ca_file(const std::string &name) { if (p && priv::ca_file_supported(p->curl)) { diff --git a/src/slic3r/Utils/Http.hpp b/src/slic3r/Utils/Http.hpp index f16236279..ae3660fbf 100644 --- a/src/slic3r/Utils/Http.hpp +++ b/src/slic3r/Utils/Http.hpp @@ -64,6 +64,8 @@ public: Http& header(std::string name, const std::string &value); // Removes a header field. Http& remove_header(std::string name); + // Authorization by HTTP digest, based on RFC2617. + Http& auth_digest(const std::string &user, const std::string &password); // Sets a CA certificate file for usage with HTTPS. This is only supported on some backends, // specifically, this is supported with OpenSSL and NOT supported with Windows and OS X native certificate store. // See also ca_file_supported(). diff --git a/src/slic3r/Utils/OctoPrint.cpp b/src/slic3r/Utils/OctoPrint.cpp index ee87f822f..fad45f822 100644 --- a/src/slic3r/Utils/OctoPrint.cpp +++ b/src/slic3r/Utils/OctoPrint.cpp @@ -11,7 +11,6 @@ #include -#include "libslic3r/PrintConfig.hpp" #include "slic3r/GUI/I18N.hpp" #include "slic3r/GUI/GUI.hpp" #include "Http.hpp" @@ -170,6 +169,13 @@ std::string OctoPrint::make_url(const std::string &path) const } } +SL1Host::SL1Host(DynamicPrintConfig *config) : + OctoPrint(config), + authorization_type(dynamic_cast*>(config->option("printhost_authorization_type"))->value), + username(config->opt_string("printhost_user")), + password(config->opt_string("printhost_password")) +{ +} // SL1Host const char* SL1Host::get_name() const { return "SL1Host"; } @@ -191,4 +197,20 @@ bool SL1Host::validate_version_text(const boost::optional &version_ return version_text ? boost::starts_with(*version_text, "Prusa SLA") : false; } +void SL1Host::set_auth(Http &http) const +{ + switch (authorization_type) { + case atKeyPassword: + http.header("X-Api-Key", get_apikey()); + break; + case atUserPassword: + http.auth_digest(username, password); + break; + } + + if (! get_cafile().empty()) { + http.ca_file(get_cafile()); + } +} + } diff --git a/src/slic3r/Utils/OctoPrint.hpp b/src/slic3r/Utils/OctoPrint.hpp index 965019d85..ed1c61bd6 100644 --- a/src/slic3r/Utils/OctoPrint.hpp +++ b/src/slic3r/Utils/OctoPrint.hpp @@ -6,6 +6,7 @@ #include #include "PrintHost.hpp" +#include "libslic3r/PrintConfig.hpp" namespace Slic3r { @@ -29,6 +30,8 @@ public: bool can_test() const override { return true; } bool can_start_print() const override { return true; } std::string get_host() const override { return host; } + const std::string& get_apikey() const { return apikey; } + const std::string& get_cafile() const { return cafile; } protected: virtual bool validate_version_text(const boost::optional &version_text) const; @@ -38,14 +41,14 @@ private: std::string apikey; std::string cafile; - void set_auth(Http &http) const; + virtual void set_auth(Http &http) const; std::string make_url(const std::string &path) const; }; class SL1Host: public OctoPrint { public: - SL1Host(DynamicPrintConfig *config) : OctoPrint(config) {} + SL1Host(DynamicPrintConfig *config); ~SL1Host() override = default; const char* get_name() const override; @@ -56,6 +59,15 @@ public: protected: bool validate_version_text(const boost::optional &version_text) const override; + +private: + void set_auth(Http &http) const override; + + // Host authorization type. + AuthorizationType authorization_type; + // username and password for HTTP Digest Authentization (RFC RFC2617) + std::string username; + std::string password; }; } diff --git a/src/slic3r/Utils/Process.cpp b/src/slic3r/Utils/Process.cpp new file mode 100644 index 000000000..375c10a6a --- /dev/null +++ b/src/slic3r/Utils/Process.cpp @@ -0,0 +1,131 @@ +#include "Process.hpp" + +#include + +#include "../GUI/GUI.hpp" +// for file_wildcards() +#include "../GUI/GUI_App.hpp" +// localization +#include "../GUI/I18N.hpp" + +#include +#include + +#include +#include + +// For starting another PrusaSlicer instance on OSX. +// Fails to compile on Windows on the build server. +#ifdef __APPLE__ + #include + #include +#endif + +#include + +namespace Slic3r { +namespace GUI { + +enum class NewSlicerInstanceType { + Slicer, + GCodeViewer +}; + +// Start a new Slicer process instance either in a Slicer mode or in a G-code mode. +// Optionally load a 3MF, STL or a G-code on start. +static void start_new_slicer_or_gcodeviewer(const NewSlicerInstanceType instance_type, const wxString *path_to_open) +{ +#ifdef _WIN32 + wxString path; + wxFileName::SplitPath(wxStandardPaths::Get().GetExecutablePath(), &path, nullptr, nullptr, wxPATH_NATIVE); + path += "\\"; + path += (instance_type == NewSlicerInstanceType::Slicer) ? "prusa-slicer.exe" : "prusa-gcodeviewer.exe"; + std::vector args; + args.reserve(3); + args.emplace_back(path.wc_str()); + if (path_to_open != nullptr) + args.emplace_back(path_to_open->wc_str()); + args.emplace_back(nullptr); + BOOST_LOG_TRIVIAL(info) << "Trying to spawn a new slicer \"" << into_u8(path) << "\""; + // Don't call with wxEXEC_HIDE_CONSOLE, PrusaSlicer in GUI mode would just show the splash screen. It would not open the main window though, it would + // just hang in the background. + if (wxExecute(const_cast(args.data()), wxEXEC_ASYNC) <= 0) + BOOST_LOG_TRIVIAL(error) << "Failed to spawn a new slicer \"" << into_u8(path); +#else + // Own executable path. + boost::filesystem::path bin_path = into_path(wxStandardPaths::Get().GetExecutablePath()); + #if defined(__APPLE__) + { + bin_path = bin_path.parent_path() / ((instance_type == NewSlicerInstanceType::Slicer) ? "PrusaSlicer" : "PrusaGCodeViewer"); + // On Apple the wxExecute fails, thus we use boost::process instead. + BOOST_LOG_TRIVIAL(info) << "Trying to spawn a new slicer \"" << bin_path.string() << "\""; + try { + std::vector args; + if (path_to_open) + args.emplace_back(into_u8(*path_to_open)); + boost::process::spawn(bin_path, args); + } catch (const std::exception &ex) { + BOOST_LOG_TRIVIAL(error) << "Failed to spawn a new slicer \"" << bin_path.string() << "\": " << ex.what(); + } + } + #else // Linux or Unix + { + std::vector args; + args.reserve(3); + #ifdef __linux + static const char *gcodeviewer_param = "--gcodeviewer"; + { + // If executed by an AppImage, start the AppImage, not the main process. + // see https://docs.appimage.org/packaging-guide/environment-variables.html#id2 + const char *appimage_binary = std::getenv("APPIMAGE"); + if (appimage_binary) { + args.emplace_back(appimage_binary); + if (instance_type == NewSlicerInstanceType::GCodeViewer) + args.emplace_back(gcodeviewer_param); + } + } + #endif // __linux + std::string my_path; + if (args.empty()) { + // Binary path was not set to the AppImage in the Linux specific block above, call the application directly. + my_path = (bin_path.parent_path() / ((instance_type == NewSlicerInstanceType::Slicer) ? "prusa-slicer" : "prusa-gcodeviewer")).string(); + args.emplace_back(my_path.c_str()); + } + std::string to_open; + if (path_to_open) { + to_open = into_u8(*path_to_open); + args.emplace_back(to_open.c_str()); + } + args.emplace_back(nullptr); + BOOST_LOG_TRIVIAL(info) << "Trying to spawn a new slicer \"" << args[0] << "\""; + if (wxExecute(const_cast(args.data()), wxEXEC_ASYNC | wxEXEC_MAKE_GROUP_LEADER) <= 0) + BOOST_LOG_TRIVIAL(error) << "Failed to spawn a new slicer \"" << args[0]; + } + #endif // Linux or Unix +#endif // Win32 +} + +void start_new_slicer(const wxString *path_to_open) +{ + start_new_slicer_or_gcodeviewer(NewSlicerInstanceType::Slicer, path_to_open); +} + +void start_new_gcodeviewer(const wxString *path_to_open) +{ + start_new_slicer_or_gcodeviewer(NewSlicerInstanceType::GCodeViewer, path_to_open); +} + +void start_new_gcodeviewer_open_file(wxWindow *parent) +{ + wxFileDialog dialog(parent ? parent : wxGetApp().GetTopWindow(), + _L("Open G-code file:"), + from_u8(wxGetApp().app_config->get_last_dir()), wxString(), + file_wildcards(FT_GCODE), wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if (dialog.ShowModal() == wxID_OK) { + wxString path = dialog.GetPath(); + start_new_gcodeviewer(&path); + } +} + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/Utils/Process.hpp b/src/slic3r/Utils/Process.hpp new file mode 100644 index 000000000..65f90222d --- /dev/null +++ b/src/slic3r/Utils/Process.hpp @@ -0,0 +1,20 @@ +#ifndef GUI_PROCESS_HPP +#define GUI_PROCESS_HPP + +class wxWindow; +class wxString; + +namespace Slic3r { +namespace GUI { + +// Start a new slicer instance, optionally with a file to open. +void start_new_slicer(const wxString *path_to_open = nullptr); +// Start a new G-code viewer instance, optionally with a file to open. +void start_new_gcodeviewer(const wxString *path_to_open = nullptr); +// Open a file dialog, ask the user to select a new G-code to open, start a new G-code viewer. +void start_new_gcodeviewer_open_file(wxWindow *parent = nullptr); + +} // namespace GUI +} // namespace Slic3r + +#endif // GUI_PROCESS_HPP diff --git a/src/slic3r/Utils/Serial.cpp b/src/slic3r/Utils/Serial.cpp index 737e76c0b..0c1ad1de5 100644 --- a/src/slic3r/Utils/Serial.cpp +++ b/src/slic3r/Utils/Serial.cpp @@ -1,5 +1,7 @@ #include "Serial.hpp" +#include "libslic3r/Exception.hpp" + #include #include #include @@ -298,7 +300,7 @@ void Serial::set_baud_rate(unsigned baud_rate) auto handle_errno = [](int retval) { if (retval != 0) { - throw std::runtime_error( + throw Slic3r::RuntimeError( (boost::format("Could not set baud rate: %1%") % strerror(errno)).str() ); } @@ -346,7 +348,7 @@ void Serial::set_baud_rate(unsigned baud_rate) handle_errno(::cfsetspeed(&ios, baud_rate)); handle_errno(::tcsetattr(handle, TCSAFLUSH, &ios)); #else - throw std::runtime_error("Custom baud rates are not currently supported on this OS"); + throw Slic3r::RuntimeError("Custom baud rates are not currently supported on this OS"); #endif } } @@ -358,7 +360,7 @@ void Serial::set_DTR(bool on) auto handle = native_handle(); #if defined(_WIN32) && !defined(__SYMBIAN32__) if (! EscapeCommFunction(handle, on ? SETDTR : CLRDTR)) { - throw std::runtime_error("Could not set serial port DTR"); + throw Slic3r::RuntimeError("Could not set serial port DTR"); } #else int status; @@ -369,7 +371,7 @@ void Serial::set_DTR(bool on) } } - throw std::runtime_error( + throw Slic3r::RuntimeError( (boost::format("Could not set serial port DTR: %1%") % strerror(errno)).str() ); #endif diff --git a/src/slic3r/Utils/Thread.hpp b/src/slic3r/Utils/Thread.hpp index e9c76d2ab..194971c9e 100644 --- a/src/slic3r/Utils/Thread.hpp +++ b/src/slic3r/Utils/Thread.hpp @@ -1,5 +1,5 @@ -#ifndef THREAD_HPP -#define THREAD_HPP +#ifndef GUI_THREAD_HPP +#define GUI_THREAD_HPP #include #include @@ -25,4 +25,4 @@ template inline boost::thread create_thread(Fn &&fn) } -#endif // THREAD_HPP +#endif // GUI_THREAD_HPP diff --git a/src/slic3r/Utils/UndoRedo.cpp b/src/slic3r/Utils/UndoRedo.cpp index 9c8d7a8c6..10b8062f7 100644 --- a/src/slic3r/Utils/UndoRedo.cpp +++ b/src/slic3r/Utils/UndoRedo.cpp @@ -847,7 +847,7 @@ void StackImpl::load_snapshot(size_t timestamp, Slic3r::Model& model, Slic3r::GU // Find the snapshot by time. It must exist. const auto it_snapshot = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(timestamp)); if (it_snapshot == m_snapshots.end() || it_snapshot->timestamp != timestamp) - throw std::runtime_error((boost::format("Snapshot with timestamp %1% does not exist") % timestamp).str()); + throw Slic3r::RuntimeError((boost::format("Snapshot with timestamp %1% does not exist") % timestamp).str()); m_active_snapshot_time = timestamp; model.clear_objects(); diff --git a/tests/fff_print/test_data.cpp b/tests/fff_print/test_data.cpp index 09ca730ec..8e5f6bafd 100644 --- a/tests/fff_print/test_data.cpp +++ b/tests/fff_print/test_data.cpp @@ -137,7 +137,7 @@ TriangleMesh mesh(TestMesh m) { {0,1,2}, {2,1,3}, {4,0,5}, {4,1,0}, {6,4,7}, {7,4,5}, {4,8,1}, {0,2,5}, {5,2,9}, {2,10,9}, {10,3,11}, {2,3,10}, {9,10,12}, {13,9,12}, {3,1,8}, {11,3,8}, {10,11,8}, {4,10,8}, {6,12,10}, {4,6,10}, {7,13,12}, {6,7,12}, {7,5,9}, {13,7,9} }); break; default: - throw std::invalid_argument("Slic3r::Test::mesh(): called with invalid mesh ID"); + throw Slic3r::InvalidArgument("Slic3r::Test::mesh(): called with invalid mesh ID"); break; } diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index 30b93eafc..501af0c6f 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -17,6 +17,7 @@ add_executable(${_TEST_NAME}_tests test_marchingsquares.cpp test_timeutils.cpp test_voronoi.cpp + test_optimizers.cpp test_png_io.cpp test_timeutils.cpp ) diff --git a/tests/libslic3r/test_optimizers.cpp b/tests/libslic3r/test_optimizers.cpp new file mode 100644 index 000000000..6e84f6a69 --- /dev/null +++ b/tests/libslic3r/test_optimizers.cpp @@ -0,0 +1,59 @@ +#include +#include + +#include + +#include + +void check_opt_result(double score, double ref, double abs_err, double rel_err) +{ + double abs_diff = std::abs(score - ref); + double rel_diff = std::abs(abs_diff / std::abs(ref)); + + bool abs_reached = abs_diff < abs_err; + bool rel_reached = rel_diff < rel_err; + bool precision_reached = abs_reached || rel_reached; + REQUIRE(precision_reached); +} + +template void test_sin(Opt &&opt) +{ + using namespace Slic3r::opt; + + auto optfunc = [](const auto &in) { + auto [phi] = in; + + return std::sin(phi); + }; + + auto init = initvals({PI}); + auto optbounds = bounds({ {0., 2 * PI}}); + + Result result_min = opt.to_min().optimize(optfunc, init, optbounds); + Result result_max = opt.to_max().optimize(optfunc, init, optbounds); + + check_opt_result(result_min.score, -1., 1e-2, 1e-4); + check_opt_result(result_max.score, 1., 1e-2, 1e-4); +} + +template void test_sphere_func(Opt &&opt) +{ + using namespace Slic3r::opt; + + Result result = opt.to_min().optimize([](const auto &in) { + auto [x, y] = in; + + return x * x + y * y + 1.; + }, initvals({.6, -0.2}), bounds({{-1., 1.}, {-1., 1.}})); + + check_opt_result(result.score, 1., 1e-2, 1e-4); +} + +TEST_CASE("Test brute force optimzer for basic 1D and 2D functions", "[Opt]") { + using namespace Slic3r::opt; + + Optimizer opt; + + test_sin(opt); + test_sphere_func(opt); +} diff --git a/tests/sla_print/CMakeLists.txt b/tests/sla_print/CMakeLists.txt index d071b4973..dc583f1a1 100644 --- a/tests/sla_print/CMakeLists.txt +++ b/tests/sla_print/CMakeLists.txt @@ -1,7 +1,7 @@ get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests_main.cpp sla_print_tests.cpp - sla_test_utils.hpp sla_test_utils.cpp sla_treebuilder_tests.cpp + sla_test_utils.hpp sla_test_utils.cpp sla_supptgen_tests.cpp sla_raycast_tests.cpp) target_link_libraries(${_TEST_NAME}_tests test_common libslic3r) diff --git a/tests/sla_print/sla_print_tests.cpp b/tests/sla_print/sla_print_tests.cpp index dad2b9097..1575ee0e6 100644 --- a/tests/sla_print/sla_print_tests.cpp +++ b/tests/sla_print/sla_print_tests.cpp @@ -5,6 +5,7 @@ #include "sla_test_utils.hpp" #include +#include namespace { @@ -239,3 +240,14 @@ TEST_CASE("halfcone test", "[halfcone]") { m.require_shared_vertices(); m.WriteOBJFile("Halfcone.obj"); } + +TEST_CASE("Test concurrency") +{ + std::vector vals = grid(0., 100., 10.); + + double ref = std::accumulate(vals.begin(), vals.end(), 0.); + + double s = sla::ccr_par::reduce(vals.begin(), vals.end(), 0., std::plus{}); + + REQUIRE(s == Approx(ref)); +} diff --git a/tests/sla_print/sla_supptgen_tests.cpp b/tests/sla_print/sla_supptgen_tests.cpp index 1d7a3ebee..ee9013a44 100644 --- a/tests/sla_print/sla_supptgen_tests.cpp +++ b/tests/sla_print/sla_supptgen_tests.cpp @@ -89,8 +89,6 @@ TEST_CASE("Overhanging edge should be supported", "[SupGen]") { sla::SupportPointGenerator::Config cfg; sla::SupportPoints pts = calc_support_pts(mesh, cfg); - REQUIRE(min_point_distance(pts) >= cfg.minimal_distance); - Linef3 overh{ {0.f, -depth / 2.f, 0.f}, {0.f, depth / 2.f, 0.f}}; // Get all the points closer that 1 mm to the overhanging edge: @@ -102,29 +100,29 @@ TEST_CASE("Overhanging edge should be supported", "[SupGen]") { }); REQUIRE(overh_pts.size() * cfg.support_force() > overh.length() * cfg.tear_pressure()); - REQUIRE(min_point_distance(pts) >= cfg.minimal_distance); + double ddiff = min_point_distance(pts) - cfg.minimal_distance; + REQUIRE(ddiff > - 0.1 * cfg.minimal_distance); } -// FIXME: Not working yet -//TEST_CASE("Hollowed cube should be supported from the inside", "[SupGen][Hollowed]") { -// TriangleMesh mesh = make_cube(20., 20., 20.); +TEST_CASE("Hollowed cube should be supported from the inside", "[SupGen][Hollowed]") { + TriangleMesh mesh = make_cube(20., 20., 20.); -// hollow_mesh(mesh, HollowingConfig{}); + hollow_mesh(mesh, HollowingConfig{}); -// mesh.WriteOBJFile("cube_hollowed.obj"); + mesh.WriteOBJFile("cube_hollowed.obj"); -// auto bb = mesh.bounding_box(); -// auto h = float(bb.max.z() - bb.min.z()); -// Vec3f mv = bb.center().cast() - Vec3f{0.f, 0.f, 0.5f * h}; -// mesh.translate(-mv); -// mesh.require_shared_vertices(); + auto bb = mesh.bounding_box(); + auto h = float(bb.max.z() - bb.min.z()); + Vec3f mv = bb.center().cast() - Vec3f{0.f, 0.f, 0.5f * h}; + mesh.translate(-mv); + mesh.require_shared_vertices(); -// sla::SupportPointGenerator::Config cfg; -// sla::SupportPoints pts = calc_support_pts(mesh, cfg); -// sla::remove_bottom_points(pts, mesh.bounding_box().min.z() + EPSILON); + sla::SupportPointGenerator::Config cfg; + sla::SupportPoints pts = calc_support_pts(mesh, cfg); + sla::remove_bottom_points(pts, mesh.bounding_box().min.z() + EPSILON); -// REQUIRE(!pts.empty()); -//} + REQUIRE(!pts.empty()); +} TEST_CASE("Two parallel plates should be supported", "[SupGen][Hollowed]") { diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index f6b548fa0..a6a0f4304 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -38,7 +38,10 @@ void test_support_model_collision(const std::string &obj_filename, Polygons intersections = intersection(sup_slice, mod_slice); - notouch = notouch && intersections.empty(); + double pinhead_r = scaled(input_supportcfg.head_front_radius_mm); + + // TODO:: make it strict without a threshold of PI * pihead_radius ^ 2 + notouch = notouch && area(intersections) < PI * pinhead_r * pinhead_r; } /*if (!notouch) */export_failed_case(support_slices, byproducts); diff --git a/tests/sla_print/sla_treebuilder_tests.cpp b/tests/sla_print/sla_treebuilder_tests.cpp deleted file mode 100644 index 91c2ea6f8..000000000 --- a/tests/sla_print/sla_treebuilder_tests.cpp +++ /dev/null @@ -1,99 +0,0 @@ -//#include -//#include - -//#include "libslic3r/TriangleMesh.hpp" -//#include "libslic3r/SLA/SupportTreeBuildsteps.hpp" -//#include "libslic3r/SLA/SupportTreeMesher.hpp" - -//TEST_CASE("Test bridge_mesh_intersect on a cube's wall", "[SLABridgeMeshInters]") { -// using namespace Slic3r; - -// TriangleMesh cube = make_cube(10., 10., 10.); - -// sla::SupportConfig cfg = {}; // use default config -// sla::SupportPoints pts = {{10.f, 5.f, 5.f, float(cfg.head_front_radius_mm), false}}; -// sla::SupportableMesh sm{cube, pts, cfg}; - -// size_t steps = 45; -// SECTION("Bridge is straight horizontal and pointing away from the cube") { - -// sla::Bridge bridge(pts[0].pos.cast(), Vec3d{15., 5., 5.}, -// pts[0].head_front_radius); - -// auto hit = sla::query_hit(sm, bridge); - -// REQUIRE(std::isinf(hit.distance())); - -// cube.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); -// cube.require_shared_vertices(); -// cube.WriteOBJFile("cube1.obj"); -// } - -// SECTION("Bridge is tilted down in 45 degrees, pointing away from the cube") { -// sla::Bridge bridge(pts[0].pos.cast(), Vec3d{15., 5., 0.}, -// pts[0].head_front_radius); - -// auto hit = sla::query_hit(sm, bridge); - -// REQUIRE(std::isinf(hit.distance())); - -// cube.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); -// cube.require_shared_vertices(); -// cube.WriteOBJFile("cube2.obj"); -// } -//} - - -//TEST_CASE("Test bridge_mesh_intersect on a sphere", "[SLABridgeMeshInters]") { -// using namespace Slic3r; - -// TriangleMesh sphere = make_sphere(1.); - -// sla::SupportConfig cfg = {}; // use default config -// cfg.head_back_radius_mm = cfg.head_front_radius_mm; -// sla::SupportPoints pts = {{1.f, 0.f, 0.f, float(cfg.head_front_radius_mm), false}}; -// sla::SupportableMesh sm{sphere, pts, cfg}; - -// size_t steps = 45; -// SECTION("Bridge is straight horizontal and pointing away from the sphere") { - -// sla::Bridge bridge(pts[0].pos.cast(), Vec3d{2., 0., 0.}, -// pts[0].head_front_radius); - -// auto hit = sla::query_hit(sm, bridge); - -// sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); -// sphere.require_shared_vertices(); -// sphere.WriteOBJFile("sphere1.obj"); - -// REQUIRE(std::isinf(hit.distance())); -// } - -// SECTION("Bridge is tilted down 45 deg and pointing away from the sphere") { - -// sla::Bridge bridge(pts[0].pos.cast(), Vec3d{2., 0., -2.}, -// pts[0].head_front_radius); - -// auto hit = sla::query_hit(sm, bridge); - -// sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); -// sphere.require_shared_vertices(); -// sphere.WriteOBJFile("sphere2.obj"); - -// REQUIRE(std::isinf(hit.distance())); -// } - -// SECTION("Bridge is tilted down 90 deg and pointing away from the sphere") { - -// sla::Bridge bridge(pts[0].pos.cast(), Vec3d{1., 0., -2.}, -// pts[0].head_front_radius); - -// auto hit = sla::query_hit(sm, bridge); - -// sphere.merge(sla::to_triangle_mesh(get_mesh(bridge, steps))); -// sphere.require_shared_vertices(); -// sphere.WriteOBJFile("sphere3.obj"); - -// REQUIRE(std::isinf(hit.distance())); -// } -//} diff --git a/version.inc b/version.inc index 30b373bdf..e5985fcd0 100644 --- a/version.inc +++ b/version.inc @@ -7,3 +7,6 @@ set(SLIC3R_VERSION "2.3.0-alpha0") set(SLIC3R_BUILD_ID "PrusaSlicer-${SLIC3R_VERSION}+UNKNOWN") set(SLIC3R_RC_VERSION "2,3,0,0") set(SLIC3R_RC_VERSION_DOTS "2.3.0.0") + +set(GCODEVIEWER_APP_NAME "Prusa GCode Viewer") +set(GCODEVIEWER_BUILD_ID "Prusa GCode Viewer-${SLIC3R_VERSION}+UNKNOWN")