From 611a2434479fe0a9b20426eeb323ce15d94f506c Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 22 Apr 2020 17:14:09 +0200 Subject: [PATCH 01/11] Add question box on PrusaSlicer start to accept detected CA store.. Fix compile --- src/slic3r/GUI/GUI_App.cpp | 32 ++++++++++++++++--- src/slic3r/Utils/Http.cpp | 65 ++++++++++++++++++++++++++++++-------- src/slic3r/Utils/Http.hpp | 4 +++ 3 files changed, 83 insertions(+), 18 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 794226520..aee71f16e 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -321,20 +322,41 @@ bool GUI_App::on_init_inner() set_data_dir(wxStandardPaths::Get().GetUserDataDir().ToUTF8().data()); app_config = new AppConfig(); - 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(); // load settings app_conf_exists = app_config->exists(); if (app_conf_exists) { app_config->load(); } + + std::string msg = Http::tls_global_init(); + wxRichMessageDialog + dlg(nullptr, + wxString::Format(_(L("%s\nDo you want to continue?")), _(msg)), + "PrusaSlicer", wxICON_QUESTION | wxYES_NO); + + bool ssl_accept = app_config->get("tls_cert_store_accepted") == "yes"; + std::string ssl_cert_store = app_config->get("tls_accepted_cert_store_location"); + ssl_accept = ssl_accept && ssl_cert_store == Http::tls_system_cert_store(); + + dlg.ShowCheckBox(_(L("Remember my choice"))); + if (!msg.empty() && !ssl_accept) { + if (dlg.ShowModal() != wxID_YES) return false; + app_config->set("tls_cert_store_accepted", + dlg.IsCheckBoxChecked() ? "yes" : "no"); + app_config->set("tls_accepted_cert_store_location", + dlg.IsCheckBoxChecked() ? Http::tls_system_cert_store() : ""); + } + app_config->set("version", SLIC3R_VERSION); app_config->save(); + + 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(); #ifdef __WXMSW__ associate_3mf_files(); diff --git a/src/slic3r/Utils/Http.cpp b/src/slic3r/Utils/Http.cpp index 30e25abe2..101654c56 100644 --- a/src/slic3r/Utils/Http.cpp +++ b/src/slic3r/Utils/Http.cpp @@ -18,6 +18,8 @@ #include #endif +#define L(s) s + #include "libslic3r/libslic3r.h" #include "libslic3r/Utils.hpp" @@ -32,7 +34,8 @@ namespace Slic3r { struct CurlGlobalInit { static std::unique_ptr instance; - + std::string message; + CurlGlobalInit() { #ifdef OPENSSL_CERT_OVERRIDE // defined if SLIC3R_STATIC=ON @@ -57,21 +60,39 @@ struct CurlGlobalInit ssl_cafile = X509_get_default_cert_file(); int replace = true; - - if (!ssl_cafile || !fs::exists(fs::path(ssl_cafile))) - for (const char * bundle : CA_BUNDLES) { - if (fs::exists(fs::path(bundle))) { - ::setenv(SSL_CA_FILE, bundle, replace); + if (!ssl_cafile || !fs::exists(fs::path(ssl_cafile))) { + const char * bundle = nullptr; + for (const char * b : CA_BUNDLES) { + if (fs::exists(fs::path(b))) { + ::setenv(SSL_CA_FILE, bundle = b, replace); break; } } - BOOST_LOG_TRIVIAL(info) - << "Detected OpenSSL root CA store: " << ::getenv(SSL_CA_FILE); + if (!bundle) + message = L("Could not detect system SSL certificate store. " + "PrusaSlicer will be unable to establish secure " + "network connections."); + else + message = string_printf( + L("PrusaSlicer detected system SSL certificate store in: %s"), + bundle); -#endif + message += string_printf( + L("\nTo specify the system certificate store manually, please " + "set the %s environment variable to the correct CA bundle " + "and restart the application."), + SSL_CA_FILE); + } + +#endif // OPENSSL_CERT_OVERRIDE - ::curl_global_init(CURL_GLOBAL_DEFAULT); + if (CURLcode ec = ::curl_global_init(CURL_GLOBAL_DEFAULT)) { + message = L("CURL init has failed. PrusaSlicer will be unable to establish " + "network connections. See logs for additional details."); + + BOOST_LOG_TRIVIAL(error) << ::curl_easy_strerror(ec); + } } ~CurlGlobalInit() { ::curl_global_cleanup(); } @@ -132,8 +153,7 @@ Http::priv::priv(const std::string &url) , limit(0) , cancel(false) { - if (!CurlGlobalInit::instance) - CurlGlobalInit::instance = std::make_unique(); + Http::tls_global_init(); if (curl == nullptr) { throw std::runtime_error(std::string("Could not construct Curl object")); @@ -494,7 +514,26 @@ bool Http::ca_file_supported() ::CURL *curl = ::curl_easy_init(); bool res = priv::ca_file_supported(curl); if (curl != nullptr) { ::curl_easy_cleanup(curl); } - return res; + return res; +} + +std::string Http::tls_global_init() +{ + if (!CurlGlobalInit::instance) + CurlGlobalInit::instance = std::make_unique(); + + return CurlGlobalInit::instance->message; +} + +std::string Http::tls_system_cert_store() +{ + std::string ret; + +#ifdef OPENSSL_CERT_OVERRIDE + ret = ::getenv(X509_get_default_cert_file_env()); +#endif + + return ret; } std::string Http::url_encode(const std::string &str) diff --git a/src/slic3r/Utils/Http.hpp b/src/slic3r/Utils/Http.hpp index 076fa4a0c..f16236279 100644 --- a/src/slic3r/Utils/Http.hpp +++ b/src/slic3r/Utils/Http.hpp @@ -100,6 +100,10 @@ public: // Tells whether current backend supports seting up a CA file using ca_file() static bool ca_file_supported(); + + // Return empty string on success or error message on fail. + static std::string tls_global_init(); + static std::string tls_system_cert_store(); // converts the given string to an url_encoded_string static std::string url_encode(const std::string &str); From 89d376dc35cebbbdf0f2bdefcb6828f48f18d9dc Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 23 Apr 2020 18:17:37 +0200 Subject: [PATCH 02/11] Add min_object_distance method as free function taking ConfigBase argument --- src/PrusaSlicer.cpp | 11 ++++--- src/libslic3r/PrintConfig.cpp | 52 +++++++++++++++++++++++----------- src/libslic3r/PrintConfig.hpp | 5 ++-- src/slic3r/GUI/Plater.cpp | 9 ++---- tests/fff_print/test_data.cpp | 2 +- tests/fff_print/test_model.cpp | 2 +- xs/xsp/Config.xsp | 4 +-- 7 files changed, 52 insertions(+), 33 deletions(-) diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 9270d3887..2857b428e 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -174,6 +174,7 @@ int CLI::run(int argc, char **argv) m_print_config.apply(fff_print_config, true); } else if (printer_technology == ptSLA) { // The default value has to be different from the one in fff mode. + sla_print_config.printer_technology.value = ptSLA; sla_print_config.output_filename_format.value = "[input_filename_base].sl1"; // The default bed shape should reflect the default display parameters @@ -186,6 +187,8 @@ int CLI::run(int argc, char **argv) m_print_config.apply(sla_print_config, true); } + double min_obj_dist = min_object_distance(m_print_config); + // Loop through transform options. bool user_center_specified = false; for (auto const &opt_key : m_transforms) { @@ -199,7 +202,7 @@ int CLI::run(int argc, char **argv) m.add_default_instances(); const BoundingBoxf &bb = fff_print_config.bed_shape.values; m.arrange_objects( - fff_print_config.min_object_distance(), + min_obj_dist, // If we are going to use the merged model for printing, honor // the configured print bed for arranging, otherwise do it freely. this->has_print_action() ? &bb : nullptr @@ -216,10 +219,10 @@ int CLI::run(int argc, char **argv) ); if (all_objects_have_instances) { // if all input objects have defined position(s) apply duplication to the whole model - model.duplicate(m_config.opt_int("duplicate"), fff_print_config.min_object_distance(), &bb); + model.duplicate(m_config.opt_int("duplicate"), min_obj_dist, &bb); } else { model.add_default_instances(); - model.duplicate_objects(m_config.opt_int("duplicate"), fff_print_config.min_object_distance(), &bb); + model.duplicate_objects(m_config.opt_int("duplicate"), min_obj_dist, &bb); } } } else if (opt_key == "duplicate_grid") { @@ -424,7 +427,7 @@ int CLI::run(int argc, char **argv) PrintBase *print = (printer_technology == ptFFF) ? static_cast(&fff_print) : static_cast(&sla_print); if (! m_config.opt_bool("dont_arrange")) { //FIXME make the min_object_distance configurable. - model.arrange_objects(fff_print.config().min_object_distance()); + model.arrange_objects(min_obj_dist); model.center_instances_around_point((! user_center_specified && m_print_config.has("bed_shape")) ? BoundingBoxf(m_print_config.opt("bed_shape")->values).center() : m_config.option("center")->value); diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index c7a7a9c8e..cba027d66 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3060,6 +3060,42 @@ DynamicPrintConfig* DynamicPrintConfig::new_from_defaults_keys(const std::vector return out; } +double min_object_distance(const ConfigBase &cfg) +{ + double ret = 0.; + + if (printer_technology(cfg) == ptSLA) ret = 6.; + else { + auto ecr_opt = cfg.option("extruder_clearance_radius"); + auto dd_opt = cfg.option("duplicate_distance"); + auto co_opt = cfg.option("complete_objects"); + + if (!ecr_opt || !dd_opt || !co_opt) ret = 0.; + else { + // min object distance is max(duplicate_distance, clearance_radius) + ret = (co_opt->value && ecr_opt->value > dd_opt->value) ? + ecr_opt->value : dd_opt->value; + } + } + + return ret; +} + +PrinterTechnology printer_technology(const ConfigBase &cfg) +{ + const ConfigOptionEnum *opt = cfg.option>("printer_technology"); + + if (opt) return opt->value; + + const ConfigOptionBool *export_opt = cfg.option("export_sla"); + if (export_opt && export_opt->getBool()) return ptSLA; + + export_opt = cfg.option("export_gcode"); + if (export_opt && export_opt->getBool()) return ptFFF; + + return ptUnknown; +} + void DynamicPrintConfig::normalize() { if (this->has("extruder")) { @@ -3130,22 +3166,6 @@ std::string DynamicPrintConfig::validate() } } -double PrintConfig::min_object_distance() const -{ - return PrintConfig::min_object_distance(static_cast(this)); -} - -double PrintConfig::min_object_distance(const ConfigBase *config) -{ - double extruder_clearance_radius = config->option("extruder_clearance_radius")->getFloat(); - double duplicate_distance = config->option("duplicate_distance")->getFloat(); - - // min object distance is max(duplicate_distance, clearance_radius) - return (config->option("complete_objects")->getBool() && extruder_clearance_radius > duplicate_distance) - ? extruder_clearance_radius - : duplicate_distance; -} - //FIXME localize this function. std::string FullPrintConfig::validate() { diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index ca509e37a..695629416 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -194,6 +194,9 @@ extern const PrintConfigDef print_config_def; class StaticPrintConfig; +PrinterTechnology printer_technology(const ConfigBase &cfg); +double min_object_distance(const ConfigBase &cfg); + // Slic3r dynamic configuration, used to override the configuration // per object, per modification volume or per printing material. // The dynamic configuration is also used to store user modifications of the print global parameters, @@ -749,8 +752,6 @@ class PrintConfig : public MachineEnvelopeConfig, public GCodeConfig STATIC_PRINT_CONFIG_CACHE_DERIVED(PrintConfig) PrintConfig() : MachineEnvelopeConfig(0), GCodeConfig(0) { initialize_cache(); *this = s_cache_PrintConfig.defaults(); } public: - double min_object_distance() const; - static double min_object_distance(const ConfigBase *config); ConfigOptionBool avoid_crossing_perimeters; ConfigOptionPoints bed_shape; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index c7c5798ce..55772f456 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2868,13 +2868,8 @@ void Plater::priv::find_new_position(const ModelInstancePtrs &instances, void Plater::priv::ArrangeJob::process() { static const auto arrangestr = _L("Arranging"); - // FIXME: I don't know how to obtain the minimum distance, it depends - // on printer technology. I guess the following should work but it crashes. - double dist = 6; // PrintConfig::min_object_distance(config); - if (plater().printer_technology == ptFFF) { - dist = PrintConfig::min_object_distance(plater().config); - } - + double dist = min_object_distance(*plater().config); + coord_t min_d = scaled(dist); auto count = unsigned(m_selected.size() + m_unprintable.size()); arrangement::BedShapeHint bedshape = plater().get_bed_shape_hint(); diff --git a/tests/fff_print/test_data.cpp b/tests/fff_print/test_data.cpp index c2c6a5f33..77c82d77b 100644 --- a/tests/fff_print/test_data.cpp +++ b/tests/fff_print/test_data.cpp @@ -167,7 +167,7 @@ void init_print(std::vector &&meshes, Slic3r::Print &print, Slic3r object->add_volume(std::move(t)); object->add_instance(); } - model.arrange_objects(PrintConfig::min_object_distance(&config)); + model.arrange_objects(min_object_distance(config)); model.center_instances_around_point(Slic3r::Vec2d(100, 100)); for (ModelObject *mo : model.objects) { mo->ensure_on_bed(); diff --git a/tests/fff_print/test_model.cpp b/tests/fff_print/test_model.cpp index 3378a8363..addb2cd7a 100644 --- a/tests/fff_print/test_model.cpp +++ b/tests/fff_print/test_model.cpp @@ -41,7 +41,7 @@ SCENARIO("Model construction", "[Model]") { } } model_object->add_instance(); - model.arrange_objects(PrintConfig::min_object_distance(&config)); + model.arrange_objects(min_object_distance(config)); model.center_instances_around_point(Slic3r::Vec2d(100, 100)); model_object->ensure_on_bed(); print.auto_assign_extruders(model_object); diff --git a/xs/xsp/Config.xsp b/xs/xsp/Config.xsp index 435dda5a2..63dc5b312 100644 --- a/xs/xsp/Config.xsp +++ b/xs/xsp/Config.xsp @@ -50,7 +50,7 @@ void erase(t_config_option_key opt_key); void normalize(); %name{setenv} void setenv_(); - double min_object_distance() %code{% PrintConfig cfg; cfg.apply(*THIS, true); RETVAL = cfg.min_object_distance(); %}; + double min_object_distance() %code{% RETVAL = Slic3r::min_object_distance(*THIS); %}; static DynamicPrintConfig* load(char *path) %code%{ auto config = new DynamicPrintConfig(); @@ -114,7 +114,7 @@ } %}; %name{setenv} void setenv_(); - double min_object_distance() %code{% RETVAL = PrintConfig::min_object_distance(THIS); %}; + double min_object_distance() %code{% RETVAL = Slic3r::min_object_distance(*THIS); %}; static StaticPrintConfig* load(char *path) %code%{ auto config = new FullPrintConfig(); From 8c0453651423eabaa8536620abb4599b365ad318 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 27 Mar 2020 09:05:26 +0100 Subject: [PATCH 03/11] Integrate scaling and unscaling into Point.hpp --- src/libslic3r/BoundingBox.hpp | 5 + src/libslic3r/MTUtils.hpp | 229 +--------------------------------- src/libslic3r/Point.hpp | 66 ++++++++++ src/libslic3r/libslic3r.h | 32 +++++ 4 files changed, 104 insertions(+), 228 deletions(-) diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp index 4fbe72163..2216d7888 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -186,6 +186,11 @@ inline bool empty(const BoundingBox3Base &bb) return ! bb.defined || bb.min(0) >= bb.max(0) || bb.min(1) >= bb.max(1) || bb.min(2) >= bb.max(2); } +inline BoundingBox scaled(const BoundingBoxf &bb) { return {scaled(bb.min), scaled(bb.max)}; } +inline BoundingBox3 scaled(const BoundingBoxf3 &bb) { return {scaled(bb.min), scaled(bb.max)}; } +inline BoundingBoxf unscaled(const BoundingBox &bb) { return {unscaled(bb.min), unscaled(bb.max)}; } +inline BoundingBoxf3 unscaled(const BoundingBox3 &bb) { return {unscaled(bb.min), unscaled(bb.max)}; } + } // namespace Slic3r // Serialization through the Cereal library diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp index 3402d2c85..170a6599c 100644 --- a/src/libslic3r/MTUtils.hpp +++ b/src/libslic3r/MTUtils.hpp @@ -11,6 +11,7 @@ #include "libslic3r.h" #include "Point.hpp" +#include "BoundingBox.hpp" namespace Slic3r { @@ -75,143 +76,6 @@ public: } }; -/// An std compatible random access iterator which uses indices to the -/// source vector thus resistant to invalidation caused by relocations. It -/// also "knows" its container. No comparison is neccesary to the container -/// "end()" iterator. The template can be instantiated with a different -/// value type than that of the container's but the types must be -/// compatible. E.g. a base class of the contained objects is compatible. -/// -/// For a constant iterator, one can instantiate this template with a value -/// type preceded with 'const'. -template -class IndexBasedIterator -{ - static const size_t NONE = size_t(-1); - - std::reference_wrapper m_index_ref; - size_t m_idx = NONE; - -public: - using value_type = Value; - using pointer = Value *; - using reference = Value &; - using difference_type = long; - using iterator_category = std::random_access_iterator_tag; - - inline explicit IndexBasedIterator(Vector &index, size_t idx) - : m_index_ref(index), m_idx(idx) - {} - - // Post increment - inline IndexBasedIterator operator++(int) - { - IndexBasedIterator cpy(*this); - ++m_idx; - return cpy; - } - - inline IndexBasedIterator operator--(int) - { - IndexBasedIterator cpy(*this); - --m_idx; - return cpy; - } - - inline IndexBasedIterator &operator++() - { - ++m_idx; - return *this; - } - - inline IndexBasedIterator &operator--() - { - --m_idx; - return *this; - } - - inline IndexBasedIterator &operator+=(difference_type l) - { - m_idx += size_t(l); - return *this; - } - - inline IndexBasedIterator operator+(difference_type l) - { - auto cpy = *this; - cpy += l; - return cpy; - } - - inline IndexBasedIterator &operator-=(difference_type l) - { - m_idx -= size_t(l); - return *this; - } - - inline IndexBasedIterator operator-(difference_type l) - { - auto cpy = *this; - cpy -= l; - return cpy; - } - - operator difference_type() { return difference_type(m_idx); } - - /// Tesing the end of the container... this is not possible with std - /// iterators. - inline bool is_end() const - { - return m_idx >= m_index_ref.get().size(); - } - - inline Value &operator*() const - { - assert(m_idx < m_index_ref.get().size()); - return m_index_ref.get().operator[](m_idx); - } - - inline Value *operator->() const - { - assert(m_idx < m_index_ref.get().size()); - return &m_index_ref.get().operator[](m_idx); - } - - /// If both iterators point past the container, they are equal... - inline bool operator==(const IndexBasedIterator &other) - { - size_t e = m_index_ref.get().size(); - return m_idx == other.m_idx || (m_idx >= e && other.m_idx >= e); - } - - inline bool operator!=(const IndexBasedIterator &other) - { - return !(*this == other); - } - - inline bool operator<=(const IndexBasedIterator &other) - { - return (m_idx < other.m_idx) || (*this == other); - } - - inline bool operator<(const IndexBasedIterator &other) - { - return m_idx < other.m_idx && (*this != other); - } - - inline bool operator>=(const IndexBasedIterator &other) - { - return m_idx > other.m_idx || *this == other; - } - - inline bool operator>(const IndexBasedIterator &other) - { - return m_idx > other.m_idx && *this != other; - } -}; - /// A very simple range concept implementation with iterator-like objects. template class Range { @@ -252,97 +116,6 @@ template struct remove_cvref template using remove_cvref_t = typename remove_cvref::type; -// A shorter C++14 style form of the enable_if metafunction -template -using enable_if_t = typename std::enable_if::type; - -// ///////////////////////////////////////////////////////////////////////////// -// Type safe conversions to and from scaled and unscaled coordinates -// ///////////////////////////////////////////////////////////////////////////// - -// A meta-predicate which is true for integers wider than or equal to coord_t -template struct is_scaled_coord -{ - static const SLIC3R_CONSTEXPR bool value = - std::is_integral::value && - std::numeric_limits::digits >= - std::numeric_limits::digits; -}; - -// Meta predicates for floating, 'scaled coord' and generic arithmetic types -template -using FloatingOnly = enable_if_t::value, O>; - -template -using ScaledCoordOnly = enable_if_t::value, O>; - -template -using IntegerOnly = enable_if_t::value, O>; - -template -using ArithmeticOnly = enable_if_t::value, O>; - -// Semantics are the following: -// Upscaling (scaled()): only from floating point types (or Vec) to either -// floating point or integer 'scaled coord' coordinates. -// Downscaling (unscaled()): from arithmetic (or Vec) to floating point only - -// Conversion definition from unscaled to floating point scaled -template> -inline constexpr FloatingOnly scaled(const Tin &v) noexcept -{ - return Tout(v / Tin(SCALING_FACTOR)); -} - -// Conversion definition from unscaled to integer 'scaled coord'. -// TODO: is the rounding necessary? Here it is commented out to show that -// it can be different for integers but it does not have to be. Using -// std::round means loosing noexcept and constexpr modifiers -template> -inline constexpr ScaledCoordOnly scaled(const Tin &v) noexcept -{ - //return static_cast(std::round(v / SCALING_FACTOR)); - return Tout(v / Tin(SCALING_FACTOR)); -} - -// Conversion for Eigen vectors (N dimensional points) -template, - int...EigenArgs> -inline Eigen::Matrix, N, EigenArgs...> -scaled(const Eigen::Matrix &v) -{ - return (v / SCALING_FACTOR).template cast(); -} - -// Conversion from arithmetic scaled type to floating point unscaled -template, - class = FloatingOnly> -inline constexpr Tout unscaled(const Tin &v) noexcept -{ - return Tout(v * Tout(SCALING_FACTOR)); -} - -// Unscaling for Eigen vectors. Input base type can be arithmetic, output base -// type can only be floating point. -template, - class = FloatingOnly, - int...EigenArgs> -inline constexpr Eigen::Matrix -unscaled(const Eigen::Matrix &v) noexcept -{ - return v.template cast() * SCALING_FACTOR; -} - template // Arbitrary allocator can be used inline IntegerOnly> reserve_vector(I capacity) { diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index e095f1c75..7a6b31395 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -288,6 +288,72 @@ private: std::ostream& operator<<(std::ostream &stm, const Vec2d &pointf); + +// ///////////////////////////////////////////////////////////////////////////// +// Type safe conversions to and from scaled and unscaled coordinates +// ///////////////////////////////////////////////////////////////////////////// + +// Semantics are the following: +// Upscaling (scaled()): only from floating point types (or Vec) to either +// floating point or integer 'scaled coord' coordinates. +// Downscaling (unscaled()): from arithmetic (or Vec) to floating point only + +// Conversion definition from unscaled to floating point scaled +template> +inline constexpr FloatingOnly scaled(const Tin &v) noexcept +{ + return Tout(v / Tin(SCALING_FACTOR)); +} + +// Conversion definition from unscaled to integer 'scaled coord'. +// TODO: is the rounding necessary? Here it is commented out to show that +// it can be different for integers but it does not have to be. Using +// std::round means loosing noexcept and constexpr modifiers +template> +inline constexpr ScaledCoordOnly scaled(const Tin &v) noexcept +{ + //return static_cast(std::round(v / SCALING_FACTOR)); + return Tout(v / Tin(SCALING_FACTOR)); +} + +// Conversion for Eigen vectors (N dimensional points) +template, + int...EigenArgs> +inline Eigen::Matrix, N, EigenArgs...> +scaled(const Eigen::Matrix &v) +{ + return (v / SCALING_FACTOR).template cast(); +} + +// Conversion from arithmetic scaled type to floating point unscaled +template, + class = FloatingOnly> +inline constexpr Tout unscaled(const Tin &v) noexcept +{ + return Tout(v * Tout(SCALING_FACTOR)); +} + +// Unscaling for Eigen vectors. Input base type can be arithmetic, output base +// type can only be floating point. +template, + class = FloatingOnly, + int...EigenArgs> +inline constexpr Eigen::Matrix +unscaled(const Eigen::Matrix &v) noexcept +{ + return v.template cast() * SCALING_FACTOR; +} + } // namespace Slic3r // start Boost diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index 1cf946f8b..5012762a9 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -17,6 +17,7 @@ #include #include #include +#include #include "Technologies.hpp" #include "Semver.hpp" @@ -247,6 +248,37 @@ static inline bool is_approx(Number value, Number test_value) return std::fabs(double(value) - double(test_value)) < double(EPSILON); } +// A meta-predicate which is true for integers wider than or equal to coord_t +template struct is_scaled_coord +{ + static const constexpr bool value = + std::is_integral::value && + std::numeric_limits::digits >= + std::numeric_limits::digits; +}; + +// Meta predicates for floating, 'scaled coord' and generic arithmetic types +// Can be used to restrict templates to work for only the specified set of types. +// parameter T is the type we want to restrict +// parameter O (Optional defaults to T) is the type that the whole expression +// will be evaluated to. +// e.g. template FloatingOnly is_nan(T val); +// The whole template will be defined only for floating point types and the +// return type will be bool. +// For more info how to use, see docs for std::enable_if +// +template +using FloatingOnly = std::enable_if_t::value, O>; + +template +using ScaledCoordOnly = std::enable_if_t::value, O>; + +template +using IntegerOnly = std::enable_if_t::value, O>; + +template +using ArithmeticOnly = std::enable_if_t::value, O>; + } // namespace Slic3r #endif From 69c02a407bb3ce4463efa207053a18634871db02 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 27 Mar 2020 09:20:06 +0100 Subject: [PATCH 04/11] Add libnest tests for various basic object functions --- .../include/libnest2d/geometry_traits.hpp | 3 + src/libnest2d/include/libnest2d/libnest2d.hpp | 1 + src/libnest2d/tools/svgtools.hpp | 25 ++-- tests/libnest2d/libnest2d_tests_main.cpp | 136 +++++++++++++++--- 4 files changed, 133 insertions(+), 32 deletions(-) diff --git a/src/libnest2d/include/libnest2d/geometry_traits.hpp b/src/libnest2d/include/libnest2d/geometry_traits.hpp index 72e239a70..c447cfcd9 100644 --- a/src/libnest2d/include/libnest2d/geometry_traits.hpp +++ b/src/libnest2d/include/libnest2d/geometry_traits.hpp @@ -982,6 +982,9 @@ template inline double area(const S& poly, const PolygonTag& ) }); } +template +inline double area(const RawShapes& shapes, const MultiPolygonTag&); + template // Dispatching function inline double area(const S& sh) { diff --git a/src/libnest2d/include/libnest2d/libnest2d.hpp b/src/libnest2d/include/libnest2d/libnest2d.hpp index b6d7fcdcf..5eef28c40 100644 --- a/src/libnest2d/include/libnest2d/libnest2d.hpp +++ b/src/libnest2d/include/libnest2d/libnest2d.hpp @@ -27,6 +27,7 @@ using Coord = TCoord; using Box = _Box; using Segment = _Segment; using Circle = _Circle; +using MultiPolygon = TMultiShape; using Item = _Item; using Rectangle = _Rectangle; diff --git a/src/libnest2d/tools/svgtools.hpp b/src/libnest2d/tools/svgtools.hpp index e1ed1ad05..2a05b551d 100644 --- a/src/libnest2d/tools/svgtools.hpp +++ b/src/libnest2d/tools/svgtools.hpp @@ -5,7 +5,7 @@ #include #include -#include +#include namespace libnest2d { namespace svg { @@ -48,23 +48,28 @@ public: conf_.width = static_cast(box.width()) / conf_.mm_in_coord_units; } - - void writeItem(const Item& item) { + + void writeShape(RawShape tsh) { if(svg_layers_.empty()) addLayer(); - auto tsh = item.transformedShape(); if(conf_.origo_location == BOTTOMLEFT) { auto d = static_cast( - std::round(conf_.height*conf_.mm_in_coord_units) ); - + std::round(conf_.height*conf_.mm_in_coord_units) ); + auto& contour = shapelike::contour(tsh); for(auto& v : contour) setY(v, -getY(v) + d); - + auto& holes = shapelike::holes(tsh); for(auto& h : holes) for(auto& v : h) setY(v, -getY(v) + d); - + } - currentLayer() += shapelike::serialize(tsh, - 1.0/conf_.mm_in_coord_units) + "\n"; + currentLayer() += + shapelike::serialize(tsh, + 1.0 / conf_.mm_in_coord_units) + + "\n"; + } + + void writeItem(const Item& item) { + writeShape(item.transformedShape()); } void writePackGroup(const PackGroup& result) { diff --git a/tests/libnest2d/libnest2d_tests_main.cpp b/tests/libnest2d/libnest2d_tests_main.cpp index c7259ae53..e0692f4e7 100644 --- a/tests/libnest2d/libnest2d_tests_main.cpp +++ b/tests/libnest2d/libnest2d_tests_main.cpp @@ -472,32 +472,30 @@ TEST_CASE("ArrangeRectanglesLoose", "[Nesting]") namespace { using namespace libnest2d; -template -void exportSVG(std::vector>& result, const Bin& bin, int idx = 0) { - std::string loc = "out"; +template +void exportSVG(const char *loc, It from, It to) { - static std::string svg_header = - R"raw( + static const char* svg_header = +R"raw( )raw"; - int i = idx; - auto r = result; // for(auto r : result) { - std::fstream out(loc + std::to_string(i) + ".svg", std::fstream::out); + std::fstream out(loc, std::fstream::out); if(out.is_open()) { out << svg_header; - Item rbin( RectangleItem(bin.width(), bin.height()) ); - for(unsigned j = 0; j < rbin.vertexCount(); j++) { - auto v = rbin.vertex(j); - setY(v, -getY(v)/SCALE + 500 ); - setX(v, getX(v)/SCALE); - rbin.setVertex(j, v); - } - out << shapelike::serialize(rbin.rawShape()) << std::endl; - for(Item& sh : r) { - Item tsh(sh.transformedShape()); +// Item rbin( RectangleItem(bin.width(), bin.height()) ); +// for(unsigned j = 0; j < rbin.vertexCount(); j++) { +// auto v = rbin.vertex(j); +// setY(v, -getY(v)/SCALE + 500 ); +// setX(v, getX(v)/SCALE); +// rbin.setVertex(j, v); +// } +// out << shapelike::serialize(rbin.rawShape()) << std::endl; + for(auto it = from; it != to; ++it) { + const Item &itm = *it; + Item tsh(itm.transformedShape()); for(unsigned j = 0; j < tsh.vertexCount(); j++) { auto v = tsh.vertex(j); setY(v, -getY(v)/SCALE + 500); @@ -509,10 +507,16 @@ void exportSVG(std::vector>& result, const Bin& bin out << "\n" << std::endl; } out.close(); - + // i++; // } } + +template +void exportSVG(std::vector>& result, int idx = 0) { + exportSVG((std::string("out") + std::to_string(idx) + ".svg").c_str(), + result.begin(), result.end()); +} } TEST_CASE("BottomLeftStressTest", "[Geometry]") { @@ -541,7 +545,7 @@ TEST_CASE("BottomLeftStressTest", "[Geometry]") { valid = (valid && !r1.isInside(r2) && !r2.isInside(r1)); if(!valid) { std::cout << "error index: " << i << std::endl; - exportSVG(result, bin, i); + exportSVG(result, i); } REQUIRE(valid); } else { @@ -894,7 +898,7 @@ void testNfp(const std::vector& testdata) { int TEST_CASEcase = 0; - auto& exportfun = exportSVG; + auto& exportfun = exportSVG; auto onetest = [&](Item& orbiter, Item& stationary, unsigned /*testidx*/){ TEST_CASEcase++; @@ -941,7 +945,7 @@ void testNfp(const std::vector& testdata) { std::ref(stationary), std::ref(tmp), std::ref(infp) }; - exportfun(inp, bin, TEST_CASEcase*i++); + exportfun(inp, TEST_CASEcase*i++); } REQUIRE(touching); @@ -1096,3 +1100,91 @@ TEST_CASE("MinAreaBBWithRotatingCalipers", "[Geometry]") { REQUIRE(succ); } } + +template MultiPolygon merged_pile(It from, It to, int bin_id) +{ + MultiPolygon pile; + pile.reserve(size_t(to - from)); + + for (auto it = from; it != to; ++it) { + if (it->binId() == bin_id) pile.emplace_back(it->transformedShape()); + } + + return nfp::merge(pile); +} + +TEST_CASE("Test for bed center distance optimization", "[Nesting], [NestKernels]") +{ + static const constexpr ClipperLib::cInt W = 10000000; + + // Get the input items and define the bin. + std::vector input(9, {W, W}); + + auto bin = Box::infinite(); + + NfpPlacer::Config pconfig; + + pconfig.object_function = [](const Item &item) -> double { + return pl::magnsq(item.boundingBox().center()); + }; + + size_t bins = nest(input, bin, 0, NestConfig{pconfig}); + + REQUIRE(bins == 1); + + // Gather the items into piles of arranged polygons... + MultiPolygon pile; + pile.reserve(input.size()); + + for (auto &itm : input) { + REQUIRE(itm.binId() == 0); + pile.emplace_back(itm.transformedShape()); + } + + MultiPolygon m = merged_pile(input.begin(), input.end(), 0); + + REQUIRE(m.size() == 1); + + REQUIRE(sl::area(m) == Approx(9. * W * W)); +} + +TEST_CASE("Test for biggest bounding box area", "[Nesting], [NestKernels]") +{ + static const constexpr ClipperLib::cInt W = 10000000; + static const constexpr size_t N = 100; + + // Get the input items and define the bin. + std::vector input(N, {W, W}); + + auto bin = Box::infinite(); + + NfpPlacer::Config pconfig; + pconfig.rotations = {0.}; + Box pile_box; + pconfig.before_packing = + [&pile_box](const MultiPolygon &pile, + const _ItemGroup &/*packed_items*/, + const _ItemGroup &/*remaining_items*/) { + pile_box = sl::boundingBox(pile); + }; + + pconfig.object_function = [&pile_box](const Item &item) -> double { + Box b = sl::boundingBox(item.boundingBox(), pile_box); + double area = b.area() / (W * W); + return -area; + }; + + size_t bins = nest(input, bin, 0, NestConfig{pconfig}); + + // To debug: + exportSVG<1000000>("out", input.begin(), input.end()); + + REQUIRE(bins == 1); + + MultiPolygon pile = merged_pile(input.begin(), input.end(), 0); + Box bb = sl::boundingBox(pile); + + // Here the result shall be a stairway of boxes + REQUIRE(pile.size() == N); + REQUIRE(bb.area() == N * N * W * W); +} From 44ca0a6c3d8857428d0b59c5c35859442724928d Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Fri, 27 Mar 2020 09:58:08 +0100 Subject: [PATCH 05/11] Add universal method to get bed shape from Config objects --- src/libslic3r/PrintConfig.cpp | 31 +++++++++++++++++++++++++++++++ src/libslic3r/PrintConfig.hpp | 4 ++++ 2 files changed, 35 insertions(+) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index cba027d66..229e37527 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3575,8 +3575,39 @@ void DynamicPrintAndCLIConfig::handle_legacy(t_config_option_key &opt_key, std:: } } +static Points to_points(const std::vector &dpts) +{ + Points pts; pts.reserve(dpts.size()); + for (auto &v : dpts) + pts.emplace_back( coord_t(scale_(v.x())), coord_t(scale_(v.y())) ); + return pts; } +Points get_bed_shape(const DynamicPrintConfig &config) +{ + const auto *bed_shape_opt = config.opt("bed_shape"); + if (!bed_shape_opt) { + + // Here, it is certain that the bed shape is missing, so an infinite one + // has to be used, but still, the center of bed can be queried + if (auto center_opt = config.opt("center")) + return { scaled(center_opt->value) }; + + return {}; + } + + return to_points(bed_shape_opt->values); +} + +Points get_bed_shape(const PrintConfig &cfg) +{ + return to_points(cfg.bed_shape.values); +} + +Points get_bed_shape(const SLAPrinterConfig &cfg) { return to_points(cfg.bed_shape.values); } + +} // namespace Slic3r + #include CEREAL_REGISTER_TYPE(Slic3r::DynamicPrintConfig) CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::DynamicConfig, Slic3r::DynamicPrintConfig) diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 695629416..c2ecc461e 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1306,6 +1306,10 @@ private: static PrintAndCLIConfigDef s_def; }; +Points get_bed_shape(const DynamicPrintConfig &cfg); +Points get_bed_shape(const PrintConfig &cfg); +Points get_bed_shape(const SLAPrinterConfig &cfg); + } // namespace Slic3r // Serialization through the Cereal library From 1bffc2b99bedb0d0d76baeec52523dc1fef737e1 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 23 Apr 2020 18:19:03 +0200 Subject: [PATCH 06/11] Add ModelArrange.hpp as extension to Model.hpp, use it for duplicating Refactored Arrange interface: remove the union based BedShapeHint, replace it with proper function overloads WARN: this commit is only intermediate, it does not compile. --- src/PrusaSlicer.cpp | 79 ++++---- src/libslic3r/Arrange.cpp | 322 ++++++++++++--------------------- src/libslic3r/Arrange.hpp | 167 ++++++----------- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/Model.cpp | 112 +----------- src/libslic3r/Model.hpp | 9 +- src/libslic3r/ModelArrange.cpp | 83 +++++++++ src/libslic3r/ModelArrange.hpp | 68 +++++++ src/libslic3r/Polygon.cpp | 9 +- src/libslic3r/Polygon.hpp | 4 +- tests/fff_print/test_data.cpp | 4 +- tests/fff_print/test_model.cpp | 4 +- xs/xsp/Model.xsp | 7 +- 13 files changed, 389 insertions(+), 481 deletions(-) create mode 100644 src/libslic3r/ModelArrange.cpp create mode 100644 src/libslic3r/ModelArrange.hpp diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 2857b428e..0f9df1e41 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -34,6 +34,7 @@ #include "libslic3r/Config.hpp" #include "libslic3r/Geometry.hpp" #include "libslic3r/Model.hpp" +#include "libslic3r/ModelArrange.hpp" #include "libslic3r/Print.hpp" #include "libslic3r/SLAPrint.hpp" #include "libslic3r/TriangleMesh.hpp" @@ -53,12 +54,6 @@ using namespace Slic3r; -PrinterTechnology get_printer_technology(const DynamicConfig &config) -{ - const ConfigOptionEnum *opt = config.option>("printer_technology"); - return (opt == nullptr) ? ptUnknown : opt->value; -} - int CLI::run(int argc, char **argv) { // Switch boost::filesystem to utf8. @@ -86,13 +81,15 @@ int CLI::run(int argc, char **argv) m_extra_config.apply(m_config, true); m_extra_config.normalize(); + + PrinterTechnology printer_technology = Slic3r::printer_technology(m_config); bool start_gui = m_actions.empty() && // cutting transformations are setting an "export" action. 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(); - PrinterTechnology printer_technology = get_printer_technology(m_extra_config); + const std::vector &load_configs = m_config.option("load", true)->values; // load config files supplied via --load @@ -113,7 +110,7 @@ int CLI::run(int argc, char **argv) return 1; } config.normalize(); - PrinterTechnology other_printer_technology = get_printer_technology(config); + 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) { @@ -134,7 +131,7 @@ int CLI::run(int argc, char **argv) // 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 = get_printer_technology(config); + 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) { @@ -161,9 +158,6 @@ int CLI::run(int argc, char **argv) // Normalizing after importing the 3MFs / AMFs m_print_config.normalize(); - if (printer_technology == ptUnknown) - printer_technology = std::find(m_actions.begin(), m_actions.end(), "export_sla") == m_actions.end() ? ptFFF : ptSLA; - // Initialize full print configs for both the FFF and SLA technologies. FullPrintConfig fff_print_config; SLAFullPrintConfig sla_print_config; @@ -187,10 +181,18 @@ int CLI::run(int argc, char **argv) m_print_config.apply(sla_print_config, true); } - double min_obj_dist = min_object_distance(m_print_config); - + std::string validity = m_print_config.validate(); + if (!validity.empty()) { + boost::nowide::cerr << "error: " << validity << std::endl; + return 1; + } + // Loop through transform options. bool user_center_specified = false; + Points bed = get_bed_shape(m_print_config); + ArrangeParams arrange_cfg; + arrange_cfg.min_obj_distance = scaled(min_object_distance(m_print_config)); + for (auto const &opt_key : m_transforms) { if (opt_key == "merge") { Model m; @@ -200,29 +202,33 @@ int CLI::run(int argc, char **argv) // Rearrange instances unless --dont-arrange is supplied if (! m_config.opt_bool("dont_arrange")) { m.add_default_instances(); - const BoundingBoxf &bb = fff_print_config.bed_shape.values; - m.arrange_objects( - min_obj_dist, - // If we are going to use the merged model for printing, honor - // the configured print bed for arranging, otherwise do it freely. - this->has_print_action() ? &bb : nullptr - ); + if (this->has_print_action()) + arrange_objects(m, bed, arrange_cfg); + else + arrange_objects(m, InfiniteBed{}, arrange_cfg); } m_models.clear(); m_models.emplace_back(std::move(m)); } else if (opt_key == "duplicate") { - const BoundingBoxf &bb = fff_print_config.bed_shape.values; for (auto &model : m_models) { const bool all_objects_have_instances = std::none_of( model.objects.begin(), model.objects.end(), [](ModelObject* o){ return o->instances.empty(); } ); - if (all_objects_have_instances) { - // if all input objects have defined position(s) apply duplication to the whole model - model.duplicate(m_config.opt_int("duplicate"), min_obj_dist, &bb); - } else { - model.add_default_instances(); - model.duplicate_objects(m_config.opt_int("duplicate"), min_obj_dist, &bb); + + int dups = m_config.opt_int("duplicate"); + if (!all_objects_have_instances) model.add_default_instances(); + + try { + if (dups > 1) { + // if all input objects have defined position(s) apply duplication to the whole model + duplicate(model, size_t(dups), bed, arrange_cfg); + } else { + arrange_objects(model, bed, arrange_cfg); + } + } catch (std::exception &ex) { + boost::nowide::cerr << "error: " << ex.what() << std::endl; + return 1; } } } else if (opt_key == "duplicate_grid") { @@ -426,11 +432,11 @@ int CLI::run(int argc, char **argv) PrintBase *print = (printer_technology == ptFFF) ? static_cast(&fff_print) : static_cast(&sla_print); if (! m_config.opt_bool("dont_arrange")) { - //FIXME make the min_object_distance configurable. - model.arrange_objects(min_obj_dist); - model.center_instances_around_point((! user_center_specified && m_print_config.has("bed_shape")) ? - BoundingBoxf(m_print_config.opt("bed_shape")->values).center() : - m_config.option("center")->value); + if (user_center_specified) { + Vec2d c = m_config.option("center")->value; + arrange_objects(model, InfiniteBed{scaled(c)}, arrange_cfg); + } else + arrange_objects(model, bed, arrange_cfg); } if (printer_technology == ptFFF) { for (auto* mo : model.objects) @@ -612,6 +618,8 @@ bool CLI::setup(int argc, char **argv) if (opt_loglevel != 0) set_logging_level(opt_loglevel->value); } + + std::string validity = m_config.validate(); // Initialize with defaults. for (const t_optiondef_map *options : { &cli_actions_config_def.options, &cli_transform_config_def.options, &cli_misc_config_def.options }) @@ -619,6 +627,11 @@ bool CLI::setup(int argc, char **argv) m_config.option(optdef.first, true); set_data_dir(m_config.opt_string("datadir")); + + if (!validity.empty()) { + boost::nowide::cerr << "error: " << validity << std::endl; + return false; + } return true; } diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index b8ef0bcdc..5b048b0ff 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -1,7 +1,6 @@ #include "Arrange.hpp" -#include "Geometry.hpp" +//#include "Geometry.hpp" #include "SVG.hpp" -#include "MTUtils.hpp" #include #include @@ -83,7 +82,7 @@ const double BIG_ITEM_TRESHOLD = 0.02; // Fill in the placer algorithm configuration with values carefully chosen for // Slic3r. template -void fillConfig(PConf& pcfg) { +void fill_config(PConf& pcfg) { // Align the arranged pile into the center of the bin pcfg.alignment = PConf::Alignment::CENTER; @@ -105,7 +104,7 @@ void fillConfig(PConf& pcfg) { // Apply penalty to object function result. This is used only when alignment // after arrange is explicitly disabled (PConfig::Alignment::DONT_ALIGN) -double fixed_overfit(const std::tuple& result, const Box &binbb) +static double fixed_overfit(const std::tuple& result, const Box &binbb) { double score = std::get<0>(result); Box pilebb = std::get<1>(result); @@ -312,7 +311,7 @@ public: , m_bin_area(sl::area(bin)) , m_norm(std::sqrt(m_bin_area)) { - fillConfig(m_pconf); + fill_config(m_pconf); // Set up a callback that is called just before arranging starts // This functionality is provided by the Nester class (m_pack). @@ -363,6 +362,9 @@ public: m_item_count = 0; } + PConfig& config() { return m_pconf; } + const PConfig& config() const { return m_pconf; } + inline void preload(std::vector& fixeditems) { m_pconf.alignment = PConfig::Alignment::DONT_ALIGN; auto bb = sl::boundingBox(m_bin); @@ -438,127 +440,6 @@ std::function AutoArranger::get_objfn() }; } -inline Circle to_lnCircle(const CircleBed& circ) { - return Circle({circ.center()(0), circ.center()(1)}, circ.radius()); -} - -// Get the type of bed geometry from a simple vector of points. -void BedShapeHint::reset(BedShapes type) -{ - if (m_type != type) { - if (m_type == bsIrregular) - m_bed.polygon.Slic3r::Polyline::~Polyline(); - else if (type == bsIrregular) - ::new (&m_bed.polygon) Polyline(); - } - - m_type = type; -} - -BedShapeHint::BedShapeHint(const Polyline &bed) { - auto x = [](const Point& p) { return p(X); }; - auto y = [](const Point& p) { return p(Y); }; - - auto width = [x](const BoundingBox& box) { - return x(box.max) - x(box.min); - }; - - auto height = [y](const BoundingBox& box) { - return y(box.max) - y(box.min); - }; - - auto area = [&width, &height](const BoundingBox& box) { - double w = width(box); - double h = height(box); - return w * h; - }; - - auto poly_area = [](Polyline p) { - Polygon pp; pp.points.reserve(p.points.size() + 1); - pp.points = std::move(p.points); - pp.points.emplace_back(pp.points.front()); - return std::abs(pp.area()); - }; - - auto distance_to = [x, y](const Point& p1, const Point& p2) { - double dx = x(p2) - x(p1); - double dy = y(p2) - y(p1); - return std::sqrt(dx*dx + dy*dy); - }; - - auto bb = bed.bounding_box(); - - auto isCircle = [bb, distance_to](const Polyline& polygon) { - auto center = bb.center(); - std::vector vertex_distances; - double avg_dist = 0; - for (auto pt: polygon.points) - { - double distance = distance_to(center, pt); - vertex_distances.push_back(distance); - avg_dist += distance; - } - - avg_dist /= vertex_distances.size(); - - CircleBed ret(center, avg_dist); - for(auto el : vertex_distances) - { - if (std::abs(el - avg_dist) > 10 * SCALED_EPSILON) { - ret = CircleBed(); - break; - } - } - - return ret; - }; - - auto parea = poly_area(bed); - - if( (1.0 - parea/area(bb)) < 1e-3 ) { - m_type = BedShapes::bsBox; - m_bed.box = bb; - } - else if(auto c = isCircle(bed)) { - m_type = BedShapes::bsCircle; - m_bed.circ = c; - } else { - assert(m_type != BedShapes::bsIrregular); - m_type = BedShapes::bsIrregular; - ::new (&m_bed.polygon) Polyline(bed); - } -} - -BedShapeHint &BedShapeHint::operator=(BedShapeHint &&cpy) -{ - reset(cpy.m_type); - - switch(m_type) { - case bsBox: m_bed.box = std::move(cpy.m_bed.box); break; - case bsCircle: m_bed.circ = std::move(cpy.m_bed.circ); break; - case bsIrregular: m_bed.polygon = std::move(cpy.m_bed.polygon); break; - case bsInfinite: m_bed.infbed = std::move(cpy.m_bed.infbed); break; - case bsUnknown: break; - } - - return *this; -} - -BedShapeHint &BedShapeHint::operator=(const BedShapeHint &cpy) -{ - reset(cpy.m_type); - - switch(m_type) { - case bsBox: m_bed.box = cpy.m_bed.box; break; - case bsCircle: m_bed.circ = cpy.m_bed.circ; break; - case bsIrregular: m_bed.polygon = cpy.m_bed.polygon; break; - case bsInfinite: m_bed.infbed = cpy.m_bed.infbed; break; - case bsUnknown: break; - } - - return *this; -} - template void remove_large_items(std::vector &items, Bin &&bin) { auto it = items.begin(); @@ -572,12 +453,12 @@ void _arrange( std::vector & shapes, std::vector & excludes, const BinT & bin, - coord_t minobjd, + const ArrangeParams & params, std::function progressfn, std::function stopfn) { // Integer ceiling the min distance from the bed perimeters - coord_t md = minobjd; + coord_t md = params.min_obj_distance; md = (md % 2) ? md / 2 + 1 : md / 2; auto corrected_bin = bin; @@ -585,7 +466,10 @@ void _arrange( AutoArranger arranger{corrected_bin, progressfn, stopfn}; - auto infl = coord_t(std::ceil(minobjd / 2.0)); + arranger.config().accuracy = params.accuracy; + arranger.config().parallel = params.parallel; + + auto infl = coord_t(std::ceil(params.min_obj_distance / 2.0)); for (Item& itm : shapes) itm.inflate(infl); for (Item& itm : excludes) itm.inflate(infl); @@ -603,44 +487,106 @@ void _arrange( for (Item &itm : inp) itm.inflate(-infl); } -// The final client function for arrangement. A progress indicator and -// a stop predicate can be also be passed to control the process. -void arrange(ArrangePolygons & arrangables, - const ArrangePolygons & excludes, - coord_t min_obj_dist, - const BedShapeHint & bedhint, - std::function progressind, - std::function stopcondition) +inline Box to_nestbin(const BoundingBox &bb) { return Box{{bb.min(X), bb.min(Y)}, {bb.max(X), bb.max(Y)}};} +inline Circle to_nestbin(const CircleBed &c) { return Circle({c.center()(0), c.center()(1)}, c.radius()); } +inline clppr::Polygon to_nestbin(const Polygon &p) { return sl::create(Slic3rMultiPoint_to_ClipperPath(p)); } +inline Box to_nestbin(const InfiniteBed &bed) { return Box::infinite({bed.center.x(), bed.center.y()}); } + +inline coord_t width(const BoundingBox& box) { return box.max.x() - box.min.x(); } +inline coord_t height(const BoundingBox& box) { return box.max.y() - box.min.y(); } +inline double area(const BoundingBox& box) { return double(width(box)) * height(box); } +inline double poly_area(const Points &pts) { return std::abs(Polygon::area(pts)); } +inline double distance_to(const Point& p1, const Point& p2) +{ + double dx = p2.x() - p1.x(); + double dy = p2.y() - p1.y(); + return std::sqrt(dx*dx + dy*dy); +} + +static CircleBed to_circle(const Point ¢er, const Points& points) { + std::vector vertex_distances; + double avg_dist = 0; + + for (auto pt : points) + { + double distance = distance_to(center, pt); + vertex_distances.push_back(distance); + avg_dist += distance; + } + + avg_dist /= vertex_distances.size(); + + CircleBed ret(center, avg_dist); + for(auto el : vertex_distances) + { + if (std::abs(el - avg_dist) > 10 * SCALED_EPSILON) { + ret = {}; + break; + } + } + + return ret; +} + +// Create Item from Arrangeable +static void process_arrangeable(const ArrangePolygon &arrpoly, + std::vector & outp) +{ + Polygon p = arrpoly.poly.contour; + const Vec2crd &offs = arrpoly.translation; + double rotation = arrpoly.rotation; + + if (p.is_counter_clockwise()) p.reverse(); + + clppr::Polygon clpath(Slic3rMultiPoint_to_ClipperPath(p)); + + if (!clpath.Contour.empty()) { + auto firstp = clpath.Contour.front(); + clpath.Contour.emplace_back(firstp); + } + + outp.emplace_back(std::move(clpath)); + outp.back().rotation(rotation); + outp.back().translation({offs.x(), offs.y()}); + outp.back().binId(arrpoly.bed_idx); + outp.back().priority(arrpoly.priority); +} + +template<> +void arrange(ArrangePolygons & items, + const ArrangePolygons &excludes, + const Points & bed, + const ArrangeParams & params) +{ + if (bed.empty()) + arrange(items, excludes, InfiniteBed{}, params); + else if (bed.size() == 1) + arrange(items, excludes, InfiniteBed{bed.front()}, params); + else { + auto bb = BoundingBox(bed); + CircleBed circ = to_circle(bb.center(), bed); + auto parea = poly_area(bed); + + if ((1.0 - parea / area(bb)) < 1e-3) + arrange(items, excludes, bb, params); + else if (!std::isnan(circ.radius())) + arrange(items, excludes, circ, params); + else + arrange(items, excludes, Polygon(bed), params); + } +} + +template +void arrange(ArrangePolygons & arrangables, + const ArrangePolygons &excludes, + const BedT & bed, + const ArrangeParams & params) { namespace clppr = ClipperLib; std::vector items, fixeditems; items.reserve(arrangables.size()); - // Create Item from Arrangeable - auto process_arrangeable = [](const ArrangePolygon &arrpoly, - std::vector & outp) - { - Polygon p = arrpoly.poly.contour; - const Vec2crd &offs = arrpoly.translation; - double rotation = arrpoly.rotation; - - if (p.is_counter_clockwise()) p.reverse(); - - clppr::Polygon clpath(Slic3rMultiPoint_to_ClipperPath(p)); - - if (!clpath.Contour.empty()) { - auto firstp = clpath.Contour.front(); - clpath.Contour.emplace_back(firstp); - } - - outp.emplace_back(std::move(clpath)); - outp.back().rotation(rotation); - outp.back().translation({offs.x(), offs.y()}); - outp.back().binId(arrpoly.bed_idx); - outp.back().priority(arrpoly.priority); - }; - for (ArrangePolygon &arrangeable : arrangables) process_arrangeable(arrangeable, items); @@ -649,45 +595,10 @@ void arrange(ArrangePolygons & arrangables, for (Item &itm : fixeditems) itm.inflate(scaled(-2. * EPSILON)); - auto &cfn = stopcondition; - auto &pri = progressind; + auto &cfn = params.stopcondition; + auto &pri = params.progressind; - switch (bedhint.get_type()) { - case bsBox: { - // Create the arranger for the box shaped bed - BoundingBox bbb = bedhint.get_box(); - Box binbb{{bbb.min(X), bbb.min(Y)}, {bbb.max(X), bbb.max(Y)}}; - - _arrange(items, fixeditems, binbb, min_obj_dist, pri, cfn); - break; - } - case bsCircle: { - auto cc = to_lnCircle(bedhint.get_circle()); - - _arrange(items, fixeditems, cc, min_obj_dist, pri, cfn); - break; - } - case bsIrregular: { - auto ctour = Slic3rMultiPoint_to_ClipperPath(bedhint.get_irregular()); - auto irrbed = sl::create(std::move(ctour)); - BoundingBox polybb(bedhint.get_irregular()); - - _arrange(items, fixeditems, irrbed, min_obj_dist, pri, cfn); - break; - } - case bsInfinite: { - const InfiniteBed& nobin = bedhint.get_infinite(); - auto infbb = Box::infinite({nobin.center.x(), nobin.center.y()}); - - _arrange(items, fixeditems, infbb, min_obj_dist, pri, cfn); - break; - } - case bsUnknown: { - // We know nothing about the bed, let it be infinite and zero centered - _arrange(items, fixeditems, Box::infinite(), min_obj_dist, pri, cfn); - break; - } - } + _arrange(items, fixeditems, to_nestbin(bed), params, pri, cfn); for(size_t i = 0; i < items.size(); ++i) { clppr::IntPoint tr = items[i].translation(); @@ -697,15 +608,10 @@ void arrange(ArrangePolygons & arrangables, } } -// Arrange, without the fixed items (excludes) -void arrange(ArrangePolygons & inp, - coord_t min_d, - const BedShapeHint & bedhint, - std::function prfn, - std::function stopfn) -{ - arrange(inp, {}, min_d, bedhint, prfn, stopfn); -} +template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const BoundingBox &bed, const ArrangeParams ¶ms); +template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const CircleBed &bed, const ArrangeParams ¶ms); +template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Polygon &bed, const ArrangeParams ¶ms); +template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const InfiniteBed &bed, const ArrangeParams ¶ms); } // namespace arr } // namespace Slic3r diff --git a/src/libslic3r/Arrange.hpp b/src/libslic3r/Arrange.hpp index 1cfe1c907..352e9e1cf 100644 --- a/src/libslic3r/Arrange.hpp +++ b/src/libslic3r/Arrange.hpp @@ -1,12 +1,10 @@ -#ifndef MODELARRANGE_HPP -#define MODELARRANGE_HPP +#ifndef ARRANGE_HPP +#define ARRANGE_HPP #include "ExPolygon.hpp" #include "BoundingBox.hpp" -namespace Slic3r { - -namespace arrangement { +namespace Slic3r { namespace arrangement { /// A geometry abstraction for a circular print bed. Similarly to BoundingBox. class CircleBed { @@ -15,96 +13,16 @@ class CircleBed { public: inline CircleBed(): center_(0, 0), radius_(std::nan("")) {} - inline CircleBed(const Point& c, double r): center_(c), radius_(r) {} + explicit inline CircleBed(const Point& c, double r): center_(c), radius_(r) {} inline double radius() const { return radius_; } inline const Point& center() const { return center_; } - inline operator bool() { return !std::isnan(radius_); } }; /// Representing an unbounded bed. -struct InfiniteBed { Point center; }; - -/// Types of print bed shapes. -enum BedShapes { - bsBox, - bsCircle, - bsIrregular, - bsInfinite, - bsUnknown -}; - -/// Info about the print bed for the arrange() function. This is a variant -/// holding one of the four shapes a bed can be. -class BedShapeHint { - BedShapes m_type = BedShapes::bsInfinite; - - // The union neither calls constructors nor destructors of its members. - // The only member with non-trivial constructor / destructor is the polygon, - // a placement new / delete needs to be called over it. - union BedShape_u { // TODO: use variant from cpp17? - CircleBed circ; - BoundingBox box; - Polyline polygon; - InfiniteBed infbed{}; - ~BedShape_u() {} - BedShape_u() {} - } m_bed; - - // Reset the type, allocate m_bed properly - void reset(BedShapes type); - -public: - - BedShapeHint(){} - - /// Get a bed shape hint for arrange() from a naked Polyline. - explicit BedShapeHint(const Polyline &polyl); - explicit BedShapeHint(const BoundingBox &bb) - { - m_type = bsBox; m_bed.box = bb; - } - - explicit BedShapeHint(const CircleBed &c) - { - m_type = bsCircle; m_bed.circ = c; - } - - explicit BedShapeHint(const InfiniteBed &ibed) - { - m_type = bsInfinite; m_bed.infbed = ibed; - } - - ~BedShapeHint() - { - if (m_type == BedShapes::bsIrregular) - m_bed.polygon.Slic3r::Polyline::~Polyline(); - } - - BedShapeHint(const BedShapeHint &cpy) { *this = cpy; } - BedShapeHint(BedShapeHint &&cpy) { *this = std::move(cpy); } - - BedShapeHint &operator=(const BedShapeHint &cpy); - BedShapeHint& operator=(BedShapeHint &&cpy); - - BedShapes get_type() const { return m_type; } - - const BoundingBox &get_box() const - { - assert(m_type == bsBox); return m_bed.box; - } - const CircleBed &get_circle() const - { - assert(m_type == bsCircle); return m_bed.circ; - } - const Polyline &get_irregular() const - { - assert(m_type == bsIrregular); return m_bed.polygon; - } - const InfiniteBed &get_infinite() const - { - assert(m_type == bsInfinite); return m_bed.infbed; - } +struct InfiniteBed { + Point center; + explicit InfiniteBed(const Point &p = {0, 0}): center{p} {} }; /// A logical bed representing an object not being arranged. Either the arrange @@ -125,9 +43,14 @@ struct ArrangePolygon { ExPolygon poly; /// The 2D silhouette to be arranged Vec2crd translation{0, 0}; /// The translation of the poly double rotation{0.0}; /// The rotation of the poly in radians + coord_t inflation = 0; /// Arrange with inflated polygon int bed_idx{UNARRANGED}; /// To which logical bed does poly belong... int priority{0}; + // If empty, any rotation is allowed (currently unsupported) + // If only a zero is there, no rotation is allowed + std::vector allowed_rotations = {0.}; + /// Optional setter function which can store arbitrary data in its closure std::function setter = nullptr; @@ -140,6 +63,30 @@ struct ArrangePolygon { using ArrangePolygons = std::vector; +struct ArrangeParams { + + /// The minimum distance which is allowed for any + /// pair of items on the print bed in any direction. + coord_t min_obj_distance = 0.; + + /// The accuracy of optimization. + /// Goes from 0.0 to 1.0 and scales performance as well + float accuracy = 0.65f; + + /// Allow parallel execution. + bool parallel = true; + + /// Progress indicator callback called when an object gets packed. + /// The unsigned argument is the number of items remaining to pack. + std::function progressind; + + /// A predicate returning true if abort is needed. + std::function stopcondition; + + ArrangeParams() = default; + explicit ArrangeParams(coord_t md) : min_obj_distance(md) {} +}; + /** * \brief Arranges the input polygons. * @@ -150,33 +97,23 @@ using ArrangePolygons = std::vector; * \param items Input vector of ArrangePolygons. The transformation, rotation * and bin_idx fields will be changed after the call finished and can be used * to apply the result on the input polygon. - * - * \param min_obj_distance The minimum distance which is allowed for any - * pair of items on the print bed in any direction. - * - * \param bedhint Info about the shape and type of the bed. - * - * \param progressind Progress indicator callback called when - * an object gets packed. The unsigned argument is the number of items - * remaining to pack. - * - * \param stopcondition A predicate returning true if abort is needed. */ -void arrange(ArrangePolygons & items, - coord_t min_obj_distance, - const BedShapeHint & bedhint, - std::function progressind = nullptr, - std::function stopcondition = nullptr); +template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const TBed &bed, const ArrangeParams ¶ms = {}); -/// Same as the previous, only that it takes unmovable items as an -/// additional argument. Those will be considered as already arranged objects. -void arrange(ArrangePolygons & items, - const ArrangePolygons & excludes, - coord_t min_obj_distance, - const BedShapeHint & bedhint, - std::function progressind = nullptr, - std::function stopcondition = nullptr); +// A dispatch function that determines the bed shape from a set of points. +template<> void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Points &bed, const ArrangeParams ¶ms); + +extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const BoundingBox &bed, const ArrangeParams ¶ms); +extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const CircleBed &bed, const ArrangeParams ¶ms); +extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const Polygon &bed, const ArrangeParams ¶ms); +extern template void arrange(ArrangePolygons &items, const ArrangePolygons &excludes, const InfiniteBed &bed, const ArrangeParams ¶ms); + +inline void arrange(ArrangePolygons &items, const Points &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); } +inline void arrange(ArrangePolygons &items, const BoundingBox &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); } +inline void arrange(ArrangePolygons &items, const CircleBed &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); } +inline void arrange(ArrangePolygons &items, const Polygon &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); } +inline void arrange(ArrangePolygons &items, const InfiniteBed &bed, const ArrangeParams ¶ms = {}) { arrange(items, {}, bed, params); } + +}} // namespace Slic3r::arrangement -} // arr -} // Slic3r #endif // MODELARRANGE_HPP diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index b37e09ad0..2dc18728c 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -120,6 +120,8 @@ add_library(libslic3r STATIC Line.hpp Model.cpp Model.hpp + ModelArrange.hpp + ModelArrange.cpp CustomGCode.cpp CustomGCode.hpp Arrange.hpp diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 59df8eb61..619d80c43 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1,4 +1,5 @@ #include "Model.hpp" +#include "ModelArrange.hpp" #include "Geometry.hpp" #include "MTUtils.hpp" @@ -355,116 +356,6 @@ TriangleMesh Model::mesh() const return mesh; } -static bool _arrange(const Pointfs &sizes, coordf_t dist, const BoundingBoxf* bb, Pointfs &out) -{ - if (sizes.empty()) - // return if the list is empty or the following call to BoundingBoxf constructor will lead to a crash - return true; - - // we supply unscaled data to arrange() - bool result = Slic3r::Geometry::arrange( - sizes.size(), // number of parts - BoundingBoxf(sizes).max, // width and height of a single cell - dist, // distance between cells - bb, // bounding box of the area to fill - out // output positions - ); - - if (!result && bb != nullptr) { - // Try to arrange again ignoring bb - result = Slic3r::Geometry::arrange( - sizes.size(), // number of parts - BoundingBoxf(sizes).max, // width and height of a single cell - dist, // distance between cells - nullptr, // bounding box of the area to fill - out // output positions - ); - } - - return result; -} - -/* arrange objects preserving their instance count - but altering their instance positions */ -bool Model::arrange_objects(coordf_t dist, const BoundingBoxf* bb) -{ - size_t count = 0; - for (auto obj : objects) count += obj->instances.size(); - - arrangement::ArrangePolygons input; - ModelInstancePtrs instances; - input.reserve(count); - instances.reserve(count); - for (ModelObject *mo : objects) - for (ModelInstance *minst : mo->instances) { - input.emplace_back(minst->get_arrange_polygon()); - instances.emplace_back(minst); - } - - arrangement::BedShapeHint bedhint; - coord_t bedwidth = 0; - - if (bb) { - bedwidth = scaled(bb->size().x()); - bedhint = arrangement::BedShapeHint( - BoundingBox(scaled(bb->min), scaled(bb->max))); - } - - arrangement::arrange(input, scaled(dist), bedhint); - - bool ret = true; - coord_t stride = bedwidth + bedwidth / 5; - - for(size_t i = 0; i < input.size(); ++i) { - if (input[i].bed_idx != 0) ret = false; - if (input[i].bed_idx >= 0) { - input[i].translation += Vec2crd{input[i].bed_idx * stride, 0}; - instances[i]->apply_arrange_result(input[i].translation.cast(), - input[i].rotation); - } - } - - return ret; -} - -// Duplicate the entire model preserving instance relative positions. -void Model::duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb) -{ - Pointfs model_sizes(copies_num-1, to_2d(this->bounding_box().size())); - Pointfs positions; - if (! _arrange(model_sizes, dist, bb, positions)) - throw std::invalid_argument("Cannot duplicate part as the resulting objects would not fit on the print bed.\n"); - - // note that this will leave the object count unaltered - - for (ModelObject *o : this->objects) { - // make a copy of the pointers in order to avoid recursion when appending their copies - ModelInstancePtrs instances = o->instances; - for (const ModelInstance *i : instances) { - for (const Vec2d &pos : positions) { - ModelInstance *instance = o->add_instance(*i); - instance->set_offset(instance->get_offset() + Vec3d(pos(0), pos(1), 0.0)); - } - } - o->invalidate_bounding_box(); - } -} - -/* this will append more instances to each object - and then automatically rearrange everything */ -void Model::duplicate_objects(size_t copies_num, coordf_t dist, const BoundingBoxf* bb) -{ - for (ModelObject *o : this->objects) { - // make a copy of the pointers in order to avoid recursion when appending their copies - ModelInstancePtrs instances = o->instances; - for (const ModelInstance *i : instances) - for (size_t k = 2; k <= copies_num; ++ k) - o->add_instance(*i); - } - - this->arrange_objects(dist, bb); -} - void Model::duplicate_objects_grid(size_t x, size_t y, coordf_t dist) { if (this->objects.size() > 1) throw "Grid duplication is not supported with multiple objects"; @@ -1991,6 +1882,7 @@ void check_model_ids_equal(const Model &model1, const Model &model2) } } } + #endif /* NDEBUG */ } diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 2ddad9e59..a0c5f4e8a 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -802,11 +802,9 @@ public: bool center_instances_around_point(const Vec2d &point); void translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelObject *o : this->objects) o->translate(x, y, z); } TriangleMesh mesh() const; - bool arrange_objects(coordf_t dist, const BoundingBoxf* bb = NULL); + // Croaks if the duplicated objects do not fit the print bed. - void duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL); - void duplicate_objects(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL); - void duplicate_objects_grid(size_t x, size_t y, coordf_t dist); + void duplicate_objects_grid(size_t x, size_t y, coordf_t dist); bool looks_like_multipart_object() const; void convert_multipart_object(unsigned int max_extruders); @@ -822,6 +820,7 @@ public: std::string propose_export_file_name_and_path(const std::string &new_extension) const; private: + explicit Model(int) : ObjectBase(-1) { assert(this->id().invalid()); }; void assign_new_unique_ids_recursive(); void update_links_bottom_up_recursive(); @@ -831,7 +830,7 @@ private: template void serialize(Archive &ar) { Internal::StaticSerializationWrapper wipe_tower_wrapper(wipe_tower); ar(materials, objects, wipe_tower_wrapper); - } + } }; #undef OBJECTBASE_DERIVED_COPY_MOVE_CLONE diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp new file mode 100644 index 000000000..6ddbc5361 --- /dev/null +++ b/src/libslic3r/ModelArrange.cpp @@ -0,0 +1,83 @@ +#include "ModelArrange.hpp" +#include "MTUtils.hpp" + +namespace Slic3r { + +arrangement::ArrangePolygons get_arrange_polys(const Model &model, ModelInstancePtrs &instances) +{ + size_t count = 0; + for (auto obj : model.objects) count += obj->instances.size(); + + ArrangePolygons input; + input.reserve(count); + instances.clear(); instances.reserve(count); + for (ModelObject *mo : model.objects) + for (ModelInstance *minst : mo->instances) { + input.emplace_back(minst->get_arrange_polygon()); + instances.emplace_back(minst); + } + + return input; +} + +bool apply_arrange_polys(ArrangePolygons &input, ModelInstancePtrs &instances, VirtualBedFn vfn) +{ + bool ret = true; + + for(size_t i = 0; i < input.size(); ++i) { + if (input[i].bed_idx != 0) { ret = false; if (vfn) vfn(input[i]); } + if (input[i].bed_idx >= 0) + instances[i]->apply_arrange_result(input[i].translation, + input[i].rotation); + } + + return ret; +} + +Slic3r::arrangement::ArrangePolygon get_arrange_poly(const Model &model) +{ + ArrangePolygon ap; + Points &apts = ap.poly.contour.points; + for (const ModelObject *mo : model.objects) + for (const ModelInstance *minst : mo->instances) { + ArrangePolygon obj_ap = minst->get_arrange_polygon(); + ap.poly.contour.rotate(obj_ap.rotation); + ap.poly.contour.translate(obj_ap.translation.x(), obj_ap.translation.y()); + const Points &pts = obj_ap.poly.contour.points; + std::copy(pts.begin(), pts.end(), std::back_inserter(apts)); + } + + apts = Geometry::convex_hull(apts); + return ap; +} + +void duplicate(Model &model, Slic3r::arrangement::ArrangePolygons &copies, VirtualBedFn vfn) +{ + for (ModelObject *o : model.objects) { + // make a copy of the pointers in order to avoid recursion when appending their copies + ModelInstancePtrs instances = o->instances; + o->instances.clear(); + for (const ModelInstance *i : instances) { + for (arrangement::ArrangePolygon &ap : copies) { + if (ap.bed_idx != 0) vfn(ap); + ModelInstance *instance = o->add_instance(*i); + Vec2d pos = unscale(ap.translation); + instance->set_offset(instance->get_offset() + to_3d(pos, 0.)); + } + } + o->invalidate_bounding_box(); + } +} + +void duplicate_objects(Model &model, size_t copies_num) +{ + for (ModelObject *o : model.objects) { + // make a copy of the pointers in order to avoid recursion when appending their copies + ModelInstancePtrs instances = o->instances; + for (const ModelInstance *i : instances) + for (size_t k = 2; k <= copies_num; ++ k) + o->add_instance(*i); + } +} + +} // namespace Slic3r diff --git a/src/libslic3r/ModelArrange.hpp b/src/libslic3r/ModelArrange.hpp new file mode 100644 index 000000000..d65b0fd6d --- /dev/null +++ b/src/libslic3r/ModelArrange.hpp @@ -0,0 +1,68 @@ +#ifndef MODELARRANGE_HPP +#define MODELARRANGE_HPP + +#include +#include + +namespace Slic3r { + +using arrangement::ArrangePolygon; +using arrangement::ArrangePolygons; +using arrangement::ArrangeParams; +using arrangement::InfiniteBed; +using arrangement::CircleBed; + +// Do something with ArrangePolygons in virtual beds +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"); +} + +ArrangePolygons get_arrange_polys(const Model &model, ModelInstancePtrs &instances); +ArrangePolygon get_arrange_poly(const Model &model); +bool apply_arrange_polys(ArrangePolygons &polys, ModelInstancePtrs &instances, VirtualBedFn); + +void duplicate(Model &model, ArrangePolygons &copies, VirtualBedFn); +void duplicate_objects(Model &model, size_t copies_num); + +template +bool arrange_objects(Model & model, + const TBed & bed, + const ArrangeParams ¶ms, + VirtualBedFn vfn = throw_if_out_of_bed) +{ + ModelInstancePtrs instances; + auto&& input = get_arrange_polys(model, instances); + arrangement::arrange(input, bed, params); + + return apply_arrange_polys(input, instances, vfn); +} + +template +void duplicate(Model & model, + size_t copies_num, + const TBed & bed, + const ArrangeParams ¶ms, + VirtualBedFn vfn = throw_if_out_of_bed) +{ + ArrangePolygons copies(copies_num, get_arrange_poly(model)); + arrangement::arrange(copies, bed, params); + duplicate(model, copies, vfn); +} + +template +void duplicate_objects(Model & model, + size_t copies_num, + const TBed & bed, + const ArrangeParams ¶ms, + VirtualBedFn vfn = throw_if_out_of_bed) +{ + duplicate_objects(model, copies_num); + arrange_objects(model, bed, params, vfn); +} + +} + +#endif // MODELARRANGE_HPP diff --git a/src/libslic3r/Polygon.cpp b/src/libslic3r/Polygon.cpp index e1e299144..48e63dab3 100644 --- a/src/libslic3r/Polygon.cpp +++ b/src/libslic3r/Polygon.cpp @@ -48,12 +48,12 @@ int64_t Polygon::area2x() const } */ -double Polygon::area() const +double Polygon::area(const Points &points) { size_t n = points.size(); if (n < 3) return 0.; - + double a = 0.; for (size_t i = 0, j = n - 1; i < n; ++i) { a += ((double)points[j](0) + (double)points[i](0)) * ((double)points[i](1) - (double)points[j](1)); @@ -62,6 +62,11 @@ double Polygon::area() const return 0.5 * a; } +double Polygon::area() const +{ + return Polygon::area(points); +} + bool Polygon::is_counter_clockwise() const { return ClipperLib::Orientation(Slic3rMultiPoint_to_ClipperPath(*this)); diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index 0f8457ebd..c6678e2d8 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -22,6 +22,7 @@ public: const Point& operator[](Points::size_type idx) const { return this->points[idx]; } Polygon() {} + virtual ~Polygon() = default; explicit Polygon(const Points &points) : MultiPoint(points) {} Polygon(std::initializer_list points) : MultiPoint(points) {} Polygon(const Polygon &other) : MultiPoint(other.points) {} @@ -46,7 +47,8 @@ public: // Split a closed polygon into an open polyline, with the split point duplicated at both ends. Polyline split_at_first_point() const { return this->split_at_index(0); } Points equally_spaced_points(double distance) const { return this->split_at_first_point().equally_spaced_points(distance); } - + + static double area(const Points &pts); double area() const; bool is_counter_clockwise() const; bool is_clockwise() const; diff --git a/tests/fff_print/test_data.cpp b/tests/fff_print/test_data.cpp index 77c82d77b..70f82f4a5 100644 --- a/tests/fff_print/test_data.cpp +++ b/tests/fff_print/test_data.cpp @@ -12,6 +12,7 @@ #include #include +#include using namespace std; @@ -167,8 +168,7 @@ void init_print(std::vector &&meshes, Slic3r::Print &print, Slic3r object->add_volume(std::move(t)); object->add_instance(); } - model.arrange_objects(min_object_distance(config)); - model.center_instances_around_point(Slic3r::Vec2d(100, 100)); + arrange_objects(model, InfiniteBed{}, ArrangeParams{ scaled(min_object_distance(config))}); for (ModelObject *mo : model.objects) { mo->ensure_on_bed(); print.auto_assign_extruders(mo); diff --git a/tests/fff_print/test_model.cpp b/tests/fff_print/test_model.cpp index addb2cd7a..6cb926621 100644 --- a/tests/fff_print/test_model.cpp +++ b/tests/fff_print/test_model.cpp @@ -2,6 +2,7 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/Model.hpp" +#include "libslic3r/ModelArrange.hpp" #include #include @@ -41,8 +42,7 @@ SCENARIO("Model construction", "[Model]") { } } model_object->add_instance(); - model.arrange_objects(min_object_distance(config)); - model.center_instances_around_point(Slic3r::Vec2d(100, 100)); + arrange_objects(model, InfiniteBed{scaled(Vec2d(100, 100))}, ArrangeParams{scaled(min_object_distance(config))}); model_object->ensure_on_bed(); print.auto_assign_extruders(model_object); THEN("Print works?") { diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp index 35b1c01ce..4fb35578d 100644 --- a/xs/xsp/Model.xsp +++ b/xs/xsp/Model.xsp @@ -3,6 +3,7 @@ %{ #include #include "libslic3r/Model.hpp" +#include "libslic3r/ModelArrange.hpp" #include "libslic3r/Print.hpp" #include "libslic3r/PrintConfig.hpp" #include "libslic3r/Slicing.hpp" @@ -80,9 +81,9 @@ ModelObjectPtrs* objects() %code%{ RETVAL = &THIS->objects; %}; - bool arrange_objects(double dist, BoundingBoxf* bb = NULL); - void duplicate(unsigned int copies_num, double dist, BoundingBoxf* bb = NULL); - void duplicate_objects(unsigned int copies_num, double dist, BoundingBoxf* bb = NULL); + bool arrange_objects(double dist, BoundingBoxf* bb = NULL) %code%{ ArrangeParams ap{scaled(dist)}; if (bb) arrange_objects(*THIS, scaled(*bb), ap); else arrange_objects(*THIS, InfiniteBed{}, ap); %}; + void duplicate(unsigned int copies_num, double dist, BoundingBoxf* bb = NULL) %code%{ ArrangeParams ap{scaled(dist)}; if (bb) duplicate(*THIS, copies_num, scaled(*bb), ap); else duplicate(*THIS, copies_num, InfiniteBed{}, ap); %}; + void duplicate_objects(unsigned int copies_num, double dist, BoundingBoxf* bb = NULL) %code%{ ArrangeParams ap{scaled(dist)}; if (bb) duplicate_objects(*THIS, copies_num, scaled(*bb), ap); else duplicate_objects(*THIS, copies_num, InfiniteBed{}, ap); %}; void duplicate_objects_grid(unsigned int x, unsigned int y, double dist); bool looks_like_multipart_object() const; From 728d90cb334cd78f82c28bc78651cfa545db16a8 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 23 Apr 2020 18:47:51 +0200 Subject: [PATCH 07/11] Separate jobs from Plater, re-add big bed workaround --- src/libslic3r/ModelArrange.cpp | 2 +- src/slic3r/CMakeLists.txt | 5 + src/slic3r/GUI/ArrangeJob.cpp | 223 +++++++++++++ src/slic3r/GUI/ArrangeJob.hpp | 77 +++++ src/slic3r/GUI/Job.cpp | 121 +++++++ src/slic3r/GUI/Job.hpp | 130 +++----- src/slic3r/GUI/Plater.cpp | 506 ++++-------------------------- src/slic3r/GUI/Plater.hpp | 12 +- src/slic3r/GUI/RotoptimizeJob.cpp | 68 ++++ src/slic3r/GUI/RotoptimizeJob.hpp | 24 ++ 10 files changed, 635 insertions(+), 533 deletions(-) create mode 100644 src/slic3r/GUI/ArrangeJob.cpp create mode 100644 src/slic3r/GUI/ArrangeJob.hpp create mode 100644 src/slic3r/GUI/Job.cpp create mode 100644 src/slic3r/GUI/RotoptimizeJob.cpp create mode 100644 src/slic3r/GUI/RotoptimizeJob.hpp diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp index 6ddbc5361..85aa25a5f 100644 --- a/src/libslic3r/ModelArrange.cpp +++ b/src/libslic3r/ModelArrange.cpp @@ -27,7 +27,7 @@ bool apply_arrange_polys(ArrangePolygons &input, ModelInstancePtrs &instances, V for(size_t i = 0; i < input.size(); ++i) { if (input[i].bed_idx != 0) { ret = false; if (vfn) vfn(input[i]); } if (input[i].bed_idx >= 0) - instances[i]->apply_arrange_result(input[i].translation, + instances[i]->apply_arrange_result(input[i].translation.cast(), input[i].rotation); } diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 3047a0b7f..c448fe3ba 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -148,6 +148,11 @@ set(SLIC3R_GUI_SOURCES GUI/PrintHostDialogs.cpp GUI/PrintHostDialogs.hpp GUI/Job.hpp + GUI/Job.cpp + GUI/ArrangeJob.hpp + GUI/ArrangeJob.cpp + GUI/RotoptimizeJob.hpp + GUI/RotoptimizeJob.cpp GUI/Mouse3DController.cpp GUI/Mouse3DController.hpp GUI/DoubleSlider.cpp diff --git a/src/slic3r/GUI/ArrangeJob.cpp b/src/slic3r/GUI/ArrangeJob.cpp new file mode 100644 index 000000000..e43631f7e --- /dev/null +++ b/src/slic3r/GUI/ArrangeJob.cpp @@ -0,0 +1,223 @@ +#include "ArrangeJob.hpp" + +#include "libslic3r/MTUtils.hpp" + +#include "Plater.hpp" +#include "GLCanvas3D.hpp" +#include "GUI.hpp" + +namespace Slic3r { namespace GUI { + +// Cache the wti info +class WipeTower: public GLCanvas3D::WipeTowerInfo { + using ArrangePolygon = arrangement::ArrangePolygon; +public: + explicit WipeTower(const GLCanvas3D::WipeTowerInfo &wti) + : GLCanvas3D::WipeTowerInfo(wti) + {} + + explicit WipeTower(GLCanvas3D::WipeTowerInfo &&wti) + : GLCanvas3D::WipeTowerInfo(std::move(wti)) + {} + + void apply_arrange_result(const Vec2d& tr, double rotation) + { + m_pos = unscaled(tr); m_rotation = rotation; + apply_wipe_tower(); + } + + ArrangePolygon get_arrange_polygon() const + { + Polygon ap({ + {coord_t(0), coord_t(0)}, + {scaled(m_bb_size(X)), coord_t(0)}, + {scaled(m_bb_size)}, + {coord_t(0), scaled(m_bb_size(Y))}, + {coord_t(0), coord_t(0)}, + }); + + ArrangePolygon ret; + ret.poly.contour = std::move(ap); + ret.translation = scaled(m_pos); + ret.rotation = m_rotation; + ret.priority++; + return ret; + } +}; + +static WipeTower get_wipe_tower(Plater &plater) +{ + return WipeTower{plater.canvas3D()->get_wipe_tower_info()}; +} + +void ArrangeJob::clear_input() +{ + const Model &model = m_plater->model(); + + size_t count = 0, cunprint = 0; // To know how much space to reserve + for (auto obj : model.objects) + for (auto mi : obj->instances) + mi->printable ? count++ : cunprint++; + + m_selected.clear(); + m_unselected.clear(); + m_unprintable.clear(); + m_selected.reserve(count + 1 /* for optional wti */); + m_unselected.reserve(count + 1 /* for optional wti */); + m_unprintable.reserve(cunprint /* for optional wti */); +} + +double ArrangeJob::bed_stride() const { + double bedwidth = m_plater->bed_shape_bb().size().x(); + return scaled((1. + LOGICAL_BED_GAP) * bedwidth); +} + +void ArrangeJob::prepare_all() { + clear_input(); + + for (ModelObject *obj: m_plater->model().objects) + for (ModelInstance *mi : obj->instances) { + ArrangePolygons & cont = mi->printable ? m_selected : m_unprintable; + cont.emplace_back(get_arrange_poly(mi)); + } + + if (auto wti = get_wipe_tower(*m_plater)) + m_selected.emplace_back(wti.get_arrange_polygon()); +} + +void ArrangeJob::prepare_selected() { + clear_input(); + + Model &model = m_plater->model(); + double stride = bed_stride(); + + std::vector + obj_sel(model.objects.size(), nullptr); + + for (auto &s : m_plater->get_selection().get_content()) + if (s.first < int(obj_sel.size())) + obj_sel[size_t(s.first)] = &s.second; + + // Go through the objects and check if inside the selection + for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) { + const Selection::InstanceIdxsList * instlist = obj_sel[oidx]; + ModelObject *mo = model.objects[oidx]; + + std::vector inst_sel(mo->instances.size(), false); + + if (instlist) + for (auto inst_id : *instlist) + inst_sel[size_t(inst_id)] = true; + + for (size_t i = 0; i < inst_sel.size(); ++i) { + ArrangePolygon &&ap = get_arrange_poly(mo->instances[i]); + + ArrangePolygons &cont = mo->instances[i]->printable ? + (inst_sel[i] ? m_selected : + m_unselected) : + m_unprintable; + + cont.emplace_back(std::move(ap)); + } + } + + if (auto wti = get_wipe_tower(*m_plater)) { + ArrangePolygon &&ap = get_arrange_poly(&wti); + + m_plater->get_selection().is_wipe_tower() ? + m_selected.emplace_back(std::move(ap)) : + m_unselected.emplace_back(std::move(ap)); + } + + // If the selection was empty arrange everything + if (m_selected.empty()) m_selected.swap(m_unselected); + + // The strides have to be removed from the fixed items. For the + // arrangeable (selected) items bed_idx is ignored and the + // translation is irrelevant. + for (auto &p : m_unselected) p.translation(X) -= p.bed_idx * stride; +} + +void ArrangeJob::prepare() +{ + wxGetKeyState(WXK_SHIFT) ? prepare_selected() : prepare_all(); +} + +void ArrangeJob::process() +{ + static const auto arrangestr = _(L("Arranging")); + + double dist = min_object_distance(*m_plater->config()); + + arrangement::ArrangeParams params; + params.min_obj_distance = scaled(dist); + + auto count = unsigned(m_selected.size() + m_unprintable.size()); + Points bedpts = get_bed_shape(*m_plater->config()); + + params.stopcondition = [this]() { return was_canceled(); }; + + try { + params.progressind = [this, count](unsigned st) { + st += m_unprintable.size(); + if (st > 0) update_status(int(count - st), arrangestr); + }; + + arrangement::arrange(m_selected, m_unselected, bedpts, params); + + params.progressind = [this, count](unsigned st) { + if (st > 0) update_status(int(count - st), arrangestr); + }; + + arrangement::arrange(m_unprintable, {}, bedpts, params); + } catch (std::exception & /*e*/) { + GUI::show_error(m_plater, + _(L("Could not arrange model objects! " + "Some geometries may be invalid."))); + } + + // finalize just here. + update_status(int(count), + was_canceled() ? _(L("Arranging canceled.")) + : _(L("Arranging done."))); +} + +void ArrangeJob::finalize() { + // Ignore the arrange result if aborted. + if (was_canceled()) return; + + // Unprintable items go to the last virtual bed + int beds = 0; + + // Apply the arrange result to all selected objects + for (ArrangePolygon &ap : m_selected) { + beds = std::max(ap.bed_idx, beds); + ap.apply(); + } + + // Get the virtual beds from the unselected items + for (ArrangePolygon &ap : m_unselected) + beds = std::max(ap.bed_idx, beds); + + // Move the unprintable items to the last virtual bed. + for (ArrangePolygon &ap : m_unprintable) { + ap.bed_idx += beds + 1; + ap.apply(); + } + + m_plater->update(); + + Job::finalize(); +} + +arrangement::ArrangePolygon get_wipe_tower_arrangepoly(Plater &plater) +{ + return WipeTower{plater.canvas3D()->get_wipe_tower_info()}.get_arrange_polygon(); +} + +void apply_wipe_tower_arrangepoly(Plater &plater, const arrangement::ArrangePolygon &ap) +{ + WipeTower{plater.canvas3D()->get_wipe_tower_info()}.apply_arrange_result(ap.translation.cast(), ap.rotation); +} + +}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/ArrangeJob.hpp b/src/slic3r/GUI/ArrangeJob.hpp new file mode 100644 index 000000000..bd097af6b --- /dev/null +++ b/src/slic3r/GUI/ArrangeJob.hpp @@ -0,0 +1,77 @@ +#ifndef ARRANGEJOB_HPP +#define ARRANGEJOB_HPP + +#include "Job.hpp" +#include "libslic3r/Arrange.hpp" + +namespace Slic3r { namespace GUI { + +class Plater; + +class ArrangeJob : public Job +{ + Plater *m_plater; + + using ArrangePolygon = arrangement::ArrangePolygon; + using ArrangePolygons = arrangement::ArrangePolygons; + + // The gap between logical beds in the x axis expressed in ratio of + // the current bed width. + static const constexpr double LOGICAL_BED_GAP = 1. / 5.; + + ArrangePolygons m_selected, m_unselected, m_unprintable; + + // clear m_selected and m_unselected, reserve space for next usage + void clear_input(); + + // Stride between logical beds + double bed_stride() const; + + // Set up arrange polygon for a ModelInstance and Wipe tower + template ArrangePolygon get_arrange_poly(T *obj) const + { + ArrangePolygon ap = obj->get_arrange_polygon(); + ap.priority = 0; + ap.bed_idx = ap.translation.x() / bed_stride(); + ap.setter = [obj, this](const ArrangePolygon &p) { + if (p.is_arranged()) { + Vec2d t = p.translation.cast(); + t.x() += p.bed_idx * bed_stride(); + obj->apply_arrange_result(t, p.rotation); + } + }; + return ap; + } + + // Prepare all objects on the bed regardless of the selection + void prepare_all(); + + // Prepare the selected and unselected items separately. If nothing is + // selected, behaves as if everything would be selected. + void prepare_selected(); + +protected: + + void prepare() override; + +public: + ArrangeJob(std::shared_ptr pri, Plater *plater) + : Job{std::move(pri)}, m_plater{plater} + {} + + int status_range() const override + { + return int(m_selected.size() + m_unprintable.size()); + } + + void process() override; + + void finalize() override; +}; + +arrangement::ArrangePolygon get_wipe_tower_arrangepoly(Plater &); +void apply_wipe_tower_arrangepoly(Plater &plater, const arrangement::ArrangePolygon &ap); + +}} // namespace Slic3r::GUI + +#endif // ARRANGEJOB_HPP diff --git a/src/slic3r/GUI/Job.cpp b/src/slic3r/GUI/Job.cpp new file mode 100644 index 000000000..cc2cb75f1 --- /dev/null +++ b/src/slic3r/GUI/Job.cpp @@ -0,0 +1,121 @@ +#include + +#include "Job.hpp" +#include + +namespace Slic3r { + +void GUI::Job::run() +{ + m_running.store(true); + process(); + m_running.store(false); + + // ensure to call the last status to finalize the job + update_status(status_range(), ""); +} + +void GUI::Job::update_status(int st, const wxString &msg) +{ + auto evt = new wxThreadEvent(); + evt->SetInt(st); + evt->SetString(msg); + wxQueueEvent(this, evt); +} + +GUI::Job::Job(std::shared_ptr pri) + : m_progress(std::move(pri)) +{ + Bind(wxEVT_THREAD, [this](const wxThreadEvent &evt) { + auto msg = evt.GetString(); + if (!msg.empty()) + m_progress->set_status_text(msg.ToUTF8().data()); + + if (m_finalized) return; + + m_progress->set_progress(evt.GetInt()); + if (evt.GetInt() == status_range()) { + // set back the original range and cancel callback + m_progress->set_range(m_range); + m_progress->set_cancel_callback(); + wxEndBusyCursor(); + + finalize(); + + // dont do finalization again for the same process + m_finalized = true; + } + }); +} + +void GUI::Job::start() +{ // Start the job. No effect if the job is already running + if (!m_running.load()) { + prepare(); + + // Save the current status indicatior range and push the new one + m_range = m_progress->get_range(); + m_progress->set_range(status_range()); + + // init cancellation flag and set the cancel callback + m_canceled.store(false); + m_progress->set_cancel_callback( + [this]() { m_canceled.store(true); }); + + m_finalized = false; + + // Changing cursor to busy + wxBeginBusyCursor(); + + try { // Execute the job + m_thread = create_thread([this] { this->run(); }); + } catch (std::exception &) { + update_status(status_range(), + _(L("ERROR: not enough resources to " + "execute a new job."))); + } + + // The state changes will be undone when the process hits the + // last status value, in the status update handler (see ctor) + } +} + +bool GUI::Job::join(int timeout_ms) +{ + if (!m_thread.joinable()) return true; + + if (timeout_ms <= 0) + m_thread.join(); + else if (!m_thread.try_join_for(boost::chrono::milliseconds(timeout_ms))) + return false; + + return true; +} + +void GUI::ExclusiveJobGroup::start(size_t jid) { + assert(jid < m_jobs.size()); + stop_all(); + m_jobs[jid]->start(); +} + +void GUI::ExclusiveJobGroup::join_all(int wait_ms) +{ + std::vector aborted(m_jobs.size(), false); + + for (size_t jid = 0; jid < m_jobs.size(); ++jid) + aborted[jid] = m_jobs[jid]->join(wait_ms); + + if (!std::all_of(aborted.begin(), aborted.end(), [](bool t) { return t; })) + BOOST_LOG_TRIVIAL(error) << "Could not abort a job!"; +} + +bool GUI::ExclusiveJobGroup::is_any_running() const +{ + return std::any_of(m_jobs.begin(), m_jobs.end(), + [](const std::unique_ptr &j) { + return j->is_running(); + }); +} + +} + diff --git a/src/slic3r/GUI/Job.hpp b/src/slic3r/GUI/Job.hpp index ac31b9bdb..b1a652b5e 100644 --- a/src/slic3r/GUI/Job.hpp +++ b/src/slic3r/GUI/Job.hpp @@ -31,62 +31,25 @@ class Job : public wxEvtHandler bool m_finalized = false; std::shared_ptr m_progress; - void run() - { - m_running.store(true); - process(); - m_running.store(false); - - // ensure to call the last status to finalize the job - update_status(status_range(), ""); - } + void run(); protected: // status range for a particular job virtual int status_range() const { return 100; } // status update, to be used from the work thread (process() method) - void update_status(int st, const wxString &msg = "") - { - auto evt = new wxThreadEvent(); - evt->SetInt(st); - evt->SetString(msg); - wxQueueEvent(this, evt); - } - - bool was_canceled() const { return m_canceled.load(); } - + void update_status(int st, const wxString &msg = ""); + + bool was_canceled() const { return m_canceled.load(); } + // Launched just before start(), a job can use it to prepare internals virtual void prepare() {} // Launched when the job is finished. It refreshes the 3Dscene by def. virtual void finalize() { m_finalized = true; } - - + public: - Job(std::shared_ptr pri) : m_progress(pri) - { - Bind(wxEVT_THREAD, [this](const wxThreadEvent &evt) { - auto msg = evt.GetString(); - if (!msg.empty()) - m_progress->set_status_text(msg.ToUTF8().data()); - - if (m_finalized) return; - - m_progress->set_progress(evt.GetInt()); - if (evt.GetInt() == status_range()) { - // set back the original range and cancel callback - m_progress->set_range(m_range); - m_progress->set_cancel_callback(); - wxEndBusyCursor(); - - finalize(); - - // dont do finalization again for the same process - m_finalized = true; - } - }); - } + Job(std::shared_ptr pri); bool is_finalized() const { return m_finalized; } @@ -97,59 +60,50 @@ public: virtual void process() = 0; - void start() - { // Start the job. No effect if the job is already running - if (!m_running.load()) { - prepare(); - - // Save the current status indicatior range and push the new one - m_range = m_progress->get_range(); - m_progress->set_range(status_range()); - - // init cancellation flag and set the cancel callback - m_canceled.store(false); - m_progress->set_cancel_callback( - [this]() { m_canceled.store(true); }); - - m_finalized = false; - - // Changing cursor to busy - wxBeginBusyCursor(); - - try { // Execute the job - m_thread = create_thread([this] { this->run(); }); - } catch (std::exception &) { - update_status(status_range(), - _(L("ERROR: not enough resources to " - "execute a new job."))); - } - - // The state changes will be undone when the process hits the - // last status value, in the status update handler (see ctor) - } - } + void start(); // To wait for the running job and join the threads. False is // returned if the timeout has been reached and the job is still // running. Call cancel() before this fn if you want to explicitly // end the job. - bool join(int timeout_ms = 0) - { - if (!m_thread.joinable()) return true; - - if (timeout_ms <= 0) - m_thread.join(); - else if (!m_thread.try_join_for(boost::chrono::milliseconds(timeout_ms))) - return false; - - return true; - } + bool join(int timeout_ms = 0); bool is_running() const { return m_running.load(); } void cancel() { m_canceled.store(true); } }; -} -} +// Jobs defined inside the group class will be managed so that only one can +// run at a time. Also, the background process will be stopped if a job is +// started. +class ExclusiveJobGroup +{ + static const int ABORT_WAIT_MAX_MS = 10000; + + std::vector> m_jobs; + +protected: + virtual void before_start() {} + +public: + virtual ~ExclusiveJobGroup() = default; + + size_t add_job(std::unique_ptr &&job) + { + m_jobs.emplace_back(std::move(job)); + return m_jobs.size() - 1; + } + + void start(size_t jid); + + void cancel_all() { for (auto& j : m_jobs) j->cancel(); } + + void join_all(int wait_ms = 0); + + void stop_all() { cancel_all(); join_all(ABORT_WAIT_MAX_MS); } + + bool is_any_running() const; +}; + +}} // namespace Slic3r::GUI #endif // JOB_HPP diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 55772f456..2500ee6b8 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -36,7 +36,6 @@ #include "libslic3r/GCode/ThumbnailData.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/SLA/Hollowing.hpp" -#include "libslic3r/SLA/Rotfinder.hpp" #include "libslic3r/SLA/SupportPoint.hpp" #include "libslic3r/Polygon.hpp" #include "libslic3r/Print.hpp" @@ -44,13 +43,6 @@ #include "libslic3r/SLAPrint.hpp" #include "libslic3r/Utils.hpp" -//#include "libslic3r/ClipperUtils.hpp" - -// #include "libnest2d/optimizers/nlopt/genetic.hpp" -// #include "libnest2d/backends/clipper/geometries.hpp" -// #include "libnest2d/utils/rotcalipers.hpp" -#include "libslic3r/MinAreaBoundingBox.hpp" - #include "GUI.hpp" #include "GUI_App.hpp" #include "GUI_ObjectList.hpp" @@ -69,7 +61,8 @@ #include "Camera.hpp" #include "Mouse3DController.hpp" #include "Tab.hpp" -#include "Job.hpp" +#include "ArrangeJob.hpp" +#include "RotoptimizeJob.hpp" #include "PresetBundle.hpp" #include "BackgroundSlicingProcess.hpp" #include "ProgressStatusBar.hpp" @@ -1485,311 +1478,37 @@ struct Plater::priv BackgroundSlicingProcess background_process; bool suppressed_backround_processing_update { false }; - // Cache the wti info - class WipeTower: public GLCanvas3D::WipeTowerInfo { - using ArrangePolygon = arrangement::ArrangePolygon; - friend priv; - public: - - void apply_arrange_result(const Vec2d& tr, double rotation) - { - m_pos = unscaled(tr); m_rotation = rotation; - apply_wipe_tower(); - } - - ArrangePolygon get_arrange_polygon() const - { - Polygon p({ - {coord_t(0), coord_t(0)}, - {scaled(m_bb_size(X)), coord_t(0)}, - {scaled(m_bb_size)}, - {coord_t(0), scaled(m_bb_size(Y))}, - {coord_t(0), coord_t(0)}, - }); - - ArrangePolygon ret; - ret.poly.contour = std::move(p); - ret.translation = scaled(m_pos); - ret.rotation = m_rotation; - ret.priority++; - return ret; - } - } wipetower; - - WipeTower& updated_wipe_tower() { - auto wti = view3D->get_canvas3d()->get_wipe_tower_info(); - wipetower.m_pos = wti.pos(); - wipetower.m_rotation = wti.rotation(); - wipetower.m_bb_size = wti.bb_size(); - return wipetower; - } - - // A class to handle UI jobs like arranging and optimizing rotation. - // These are not instant jobs, the user has to be informed about their - // state in the status progress indicator. On the other hand they are - // separated from the background slicing process. Ideally, these jobs should - // run when the background process is not running. - // - // TODO: A mechanism would be useful for blocking the plater interactions: - // objects would be frozen for the user. In case of arrange, an animation - // could be shown, or with the optimize orientations, partial results - // could be displayed. - class PlaterJob: public Job - { - priv *m_plater; - protected: - - priv & plater() { return *m_plater; } - const priv &plater() const { return *m_plater; } - - // Launched when the job is finished. It refreshes the 3Dscene by def. - void finalize() override - { - // Do a full refresh of scene tree, including regenerating - // all the GLVolumes. FIXME The update function shall just - // reload the modified matrices. - if (!Job::was_canceled()) - plater().update(unsigned(UpdateParams::FORCE_FULL_SCREEN_REFRESH)); - - Job::finalize(); - } - - public: - PlaterJob(priv *_plater) - : Job(_plater->statusbar()), m_plater(_plater) - {} - }; - - enum class Jobs : size_t { - Arrange, - Rotoptimize - }; - - class ArrangeJob : public PlaterJob - { - using ArrangePolygon = arrangement::ArrangePolygon; - using ArrangePolygons = arrangement::ArrangePolygons; - - // The gap between logical beds in the x axis expressed in ratio of - // the current bed width. - static const constexpr double LOGICAL_BED_GAP = 1. / 5.; - - ArrangePolygons m_selected, m_unselected, m_unprintable; - - // clear m_selected and m_unselected, reserve space for next usage - void clear_input() { - const Model &model = plater().model; - - size_t count = 0, cunprint = 0; // To know how much space to reserve - for (auto obj : model.objects) - for (auto mi : obj->instances) - mi->printable ? count++ : cunprint++; - - m_selected.clear(); - m_unselected.clear(); - m_unprintable.clear(); - m_selected.reserve(count + 1 /* for optional wti */); - m_unselected.reserve(count + 1 /* for optional wti */); - m_unprintable.reserve(cunprint /* for optional wti */); - } - - // Stride between logical beds - double bed_stride() const { - double bedwidth = plater().bed_shape_bb().size().x(); - return scaled((1. + LOGICAL_BED_GAP) * bedwidth); - } - - // Set up arrange polygon for a ModelInstance and Wipe tower - template ArrangePolygon get_arrange_poly(T *obj) const { - ArrangePolygon ap = obj->get_arrange_polygon(); - ap.priority = 0; - ap.bed_idx = ap.translation.x() / bed_stride(); - ap.setter = [obj, this](const ArrangePolygon &p) { - if (p.is_arranged()) { - Vec2d t = p.translation.cast(); - t.x() += p.bed_idx * bed_stride(); - obj->apply_arrange_result(t, p.rotation); - } - }; - return ap; - } - - // Prepare all objects on the bed regardless of the selection - void prepare_all() { - clear_input(); - - for (ModelObject *obj: plater().model.objects) - for (ModelInstance *mi : obj->instances) { - ArrangePolygons & cont = mi->printable ? m_selected : m_unprintable; - cont.emplace_back(get_arrange_poly(mi)); - } - - auto& wti = plater().updated_wipe_tower(); - if (wti) m_selected.emplace_back(get_arrange_poly(&wti)); - } - - // Prepare the selected and unselected items separately. If nothing is - // selected, behaves as if everything would be selected. - void prepare_selected() { - clear_input(); - - Model &model = plater().model; - coord_t stride = bed_stride(); - - std::vector - obj_sel(model.objects.size(), nullptr); - - for (auto &s : plater().get_selection().get_content()) - if (s.first < int(obj_sel.size())) - obj_sel[size_t(s.first)] = &s.second; - - // Go through the objects and check if inside the selection - for (size_t oidx = 0; oidx < model.objects.size(); ++oidx) { - const Selection::InstanceIdxsList * instlist = obj_sel[oidx]; - ModelObject *mo = model.objects[oidx]; - - std::vector inst_sel(mo->instances.size(), false); - - if (instlist) - for (auto inst_id : *instlist) - inst_sel[size_t(inst_id)] = true; - - for (size_t i = 0; i < inst_sel.size(); ++i) { - ArrangePolygon &&ap = get_arrange_poly(mo->instances[i]); - - ArrangePolygons &cont = mo->instances[i]->printable ? - (inst_sel[i] ? m_selected : - m_unselected) : - m_unprintable; - - cont.emplace_back(std::move(ap)); - } - } - - auto& wti = plater().updated_wipe_tower(); - if (wti) { - ArrangePolygon &&ap = get_arrange_poly(&wti); - - plater().get_selection().is_wipe_tower() ? - m_selected.emplace_back(std::move(ap)) : - m_unselected.emplace_back(std::move(ap)); - } - - // If the selection was empty arrange everything - if (m_selected.empty()) m_selected.swap(m_unselected); - - // The strides have to be removed from the fixed items. For the - // arrangeable (selected) items bed_idx is ignored and the - // translation is irrelevant. - for (auto &p : m_unselected) p.translation(X) -= p.bed_idx * stride; - } - - protected: - - void prepare() override - { - wxGetKeyState(WXK_SHIFT) ? prepare_selected() : prepare_all(); - } - - public: - using PlaterJob::PlaterJob; - - int status_range() const override - { - return int(m_selected.size() + m_unprintable.size()); - } - - void process() override; - - void finalize() override { - // Ignore the arrange result if aborted. - if (was_canceled()) return; - - // Unprintable items go to the last virtual bed - int beds = 0; - - // Apply the arrange result to all selected objects - for (ArrangePolygon &ap : m_selected) { - beds = std::max(ap.bed_idx, beds); - ap.apply(); - } - - // Get the virtual beds from the unselected items - for (ArrangePolygon &ap : m_unselected) - beds = std::max(ap.bed_idx, beds); - - // Move the unprintable items to the last virtual bed. - for (ArrangePolygon &ap : m_unprintable) { - ap.bed_idx += beds + 1; - ap.apply(); - } - - plater().update(); - } - }; - - class RotoptimizeJob : public PlaterJob - { - public: - using PlaterJob::PlaterJob; - void process() override; - }; - - // Jobs defined inside the group class will be managed so that only one can // run at a time. Also, the background process will be stopped if a job is - // started. - class ExclusiveJobGroup { - - static const int ABORT_WAIT_MAX_MS = 10000; - - priv * m_plater; - - ArrangeJob arrange_job{m_plater}; - RotoptimizeJob rotoptimize_job{m_plater}; - - // To create a new job, just define a new subclass of Job, implement - // the process and the optional prepare() and finalize() methods - // Register the instance of the class in the m_jobs container - // if it cannot run concurrently with other jobs in this group - - std::vector> m_jobs{arrange_job, - rotoptimize_job}; - + // started. It is up the the plater to ensure that the background slicing + // can't be restarted while a ui job is still running. + class Jobs: public ExclusiveJobGroup + { + priv *m; + size_t m_arrange_id, m_rotoptimize_id; + + void before_start() override { m->background_process.stop(); } + public: - ExclusiveJobGroup(priv *_plater) : m_plater(_plater) {} - - void start(Jobs jid) { - m_plater->background_process.stop(); - stop_all(); - m_jobs[size_t(jid)].get().start(); - } - - void cancel_all() { for (Job& j : m_jobs) j.cancel(); } - - void join_all(int wait_ms = 0) + Jobs(priv *_m) : m(_m) { - std::vector aborted(m_jobs.size(), false); - - for (size_t jid = 0; jid < m_jobs.size(); ++jid) - aborted[jid] = m_jobs[jid].get().join(wait_ms); - - if (!all_of(aborted)) - BOOST_LOG_TRIVIAL(error) << "Could not abort a job!"; + m_arrange_id = add_job(std::make_unique(m->statusbar(), m->q)); + m_rotoptimize_id = add_job(std::make_unique(m->statusbar(), m->q)); } - - void stop_all() { cancel_all(); join_all(ABORT_WAIT_MAX_MS); } - - const Job& get(Jobs jobid) const { return m_jobs[size_t(jobid)]; } - - bool is_any_running() const + + void arrange() { - return std::any_of(m_jobs.begin(), - m_jobs.end(), - [](const Job &j) { return j.is_running(); }); + m->take_snapshot(_(L("Arrange"))); + start(m_arrange_id); } - - } m_ui_jobs{this}; + + void optimize_rotation() + { + m->take_snapshot(_(L("Optimize Rotation"))); + start(m_rotoptimize_id); + } + + } m_ui_jobs; bool delayed_scene_refresh; std::string delayed_error_message; @@ -1808,10 +1527,10 @@ struct Plater::priv priv(Plater *q, MainFrame *main_frame); ~priv(); - enum class UpdateParams { - FORCE_FULL_SCREEN_REFRESH = 1, - FORCE_BACKGROUND_PROCESSING_UPDATE = 2, - POSTPONE_VALIDATION_ERROR_MESSAGE = 4, + enum class UpdateParams { + FORCE_FULL_SCREEN_REFRESH = 1, + FORCE_BACKGROUND_PROCESSING_UPDATE = 2, + POSTPONE_VALIDATION_ERROR_MESSAGE = 4, }; void update(unsigned int flags = 0); void select_view(const std::string& direction); @@ -1847,9 +1566,7 @@ struct Plater::priv std::string get_config(const std::string &key) const; BoundingBoxf bed_shape_bb() const; BoundingBox scaled_bed_shape_bb() const; - arrangement::BedShapeHint get_bed_shape_hint() const; - void find_new_position(const ModelInstancePtrs &instances, coord_t min_d); std::vector load_files(const std::vector& input_files, bool load_model, bool load_config); std::vector load_model_objects(const ModelObjectPtrs &model_objects); wxString get_export_file(GUI::FileType file_type); @@ -1867,8 +1584,6 @@ struct Plater::priv void delete_object_from_model(size_t obj_idx); void reset(); void mirror(Axis axis); - void arrange(); - void sla_optimize_rotation(); void split_object(); void split_volume(); void scale_selection_to_fit_print_volume(); @@ -2035,6 +1750,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) "support_material", "support_material_extruder", "support_material_interface_extruder", "support_material_contact_distance", "raft_layers" })) , sidebar(new Sidebar(q)) + , m_ui_jobs(this) , delayed_scene_refresh(false) , view_toolbar(GLToolbar::Radio, "View") , m_project_filename(wxEmptyString) @@ -2110,14 +1826,15 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) sidebar->Bind(EVT_SCHEDULE_BACKGROUND_PROCESS, [this](SimpleEvent&) { this->schedule_background_process(); }); wxGLCanvas* view3D_canvas = view3D->get_wxglcanvas(); + // 3DScene events: view3D_canvas->Bind(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, [this](SimpleEvent&) { this->schedule_background_process(); }); view3D_canvas->Bind(EVT_GLCANVAS_OBJECT_SELECT, &priv::on_object_select, this); view3D_canvas->Bind(EVT_GLCANVAS_RIGHT_CLICK, &priv::on_right_click, this); view3D_canvas->Bind(EVT_GLCANVAS_REMOVE_OBJECT, [q](SimpleEvent&) { q->remove_selected(); }); - view3D_canvas->Bind(EVT_GLCANVAS_ARRANGE, [this](SimpleEvent&) { arrange(); }); + view3D_canvas->Bind(EVT_GLCANVAS_ARRANGE, [this](SimpleEvent&) { this->q->arrange(); }); view3D_canvas->Bind(EVT_GLCANVAS_SELECT_ALL, [this](SimpleEvent&) { this->q->select_all(); }); - view3D_canvas->Bind(EVT_GLCANVAS_QUESTION_MARK, [this](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); }); + view3D_canvas->Bind(EVT_GLCANVAS_QUESTION_MARK, [](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); }); view3D_canvas->Bind(EVT_GLCANVAS_INCREASE_INSTANCES, [this](Event &evt) { if (evt.data == 1) this->q->increase_instances(); else if (this->can_decrease_instances()) this->q->decrease_instances(); }); view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_MOVED, [this](SimpleEvent&) { update(); }); @@ -2142,7 +1859,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) view3D_canvas->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this); view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE, [q](SimpleEvent&) { q->remove_selected(); }); view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE_ALL, [q](SimpleEvent&) { q->reset_with_confirm(); }); - view3D_canvas->Bind(EVT_GLTOOLBAR_ARRANGE, [this](SimpleEvent&) { arrange(); }); + view3D_canvas->Bind(EVT_GLTOOLBAR_ARRANGE, [this](SimpleEvent&) { this->q->arrange(); }); view3D_canvas->Bind(EVT_GLTOOLBAR_COPY, [q](SimpleEvent&) { q->copy_selection_to_clipboard(); }); view3D_canvas->Bind(EVT_GLTOOLBAR_PASTE, [q](SimpleEvent&) { q->paste_from_clipboard(); }); view3D_canvas->Bind(EVT_GLTOOLBAR_MORE, [q](SimpleEvent&) { q->increase_instances(); }); @@ -2810,40 +2527,12 @@ void Plater::priv::mirror(Axis axis) view3D->mirror_selection(axis); } -void Plater::priv::arrange() -{ - this->take_snapshot(_L("Arrange")); - m_ui_jobs.start(Jobs::Arrange); -} - - -// This method will find an optimal orientation for the currently selected item -// Very similar in nature to the arrange method above... -void Plater::priv::sla_optimize_rotation() { - this->take_snapshot(_L("Optimize Rotation")); - m_ui_jobs.start(Jobs::Rotoptimize); -} - -arrangement::BedShapeHint Plater::priv::get_bed_shape_hint() const { - - const auto *bed_shape_opt = config->opt("bed_shape"); - assert(bed_shape_opt); - - if (!bed_shape_opt) return {}; - - auto &bedpoints = bed_shape_opt->values; - Polyline bedpoly; bedpoly.points.reserve(bedpoints.size()); - for (auto &v : bedpoints) bedpoly.append(scaled(v)); - - return arrangement::BedShapeHint(bedpoly); -} - -void Plater::priv::find_new_position(const ModelInstancePtrs &instances, +void Plater::find_new_position(const ModelInstancePtrs &instances, coord_t min_d) { arrangement::ArrangePolygons movable, fixed; - - for (const ModelObject *mo : model.objects) + + for (const ModelObject *mo : p->model.objects) for (const ModelInstance *inst : mo->instances) { auto it = std::find(instances.begin(), instances.end(), inst); auto arrpoly = inst->get_arrange_polygon(); @@ -2853,11 +2542,12 @@ void Plater::priv::find_new_position(const ModelInstancePtrs &instances, else movable.emplace_back(std::move(arrpoly)); } - - if (updated_wipe_tower()) - fixed.emplace_back(wipetower.get_arrange_polygon()); - - arrangement::arrange(movable, fixed, min_d, get_bed_shape_hint()); + + if (p->view3D->get_canvas3d()->get_wipe_tower_info()) + fixed.emplace_back(get_wipe_tower_arrangepoly(*this)); + + arrangement::arrange(movable, fixed, get_bed_shape(*config()), + arrangement::ArrangeParams{min_d}); for (size_t i = 0; i < instances.size(); ++i) if (movable[i].bed_idx == 0) @@ -2865,90 +2555,6 @@ void Plater::priv::find_new_position(const ModelInstancePtrs &instances, movable[i].rotation); } -void Plater::priv::ArrangeJob::process() { - static const auto arrangestr = _L("Arranging"); - - double dist = min_object_distance(*plater().config); - - coord_t min_d = scaled(dist); - auto count = unsigned(m_selected.size() + m_unprintable.size()); - arrangement::BedShapeHint bedshape = plater().get_bed_shape_hint(); - - auto stopfn = [this]() { return was_canceled(); }; - - try { - arrangement::arrange(m_selected, m_unselected, min_d, bedshape, - [this, count](unsigned st) { - st += m_unprintable.size(); - if (st > 0) update_status(int(count - st), arrangestr); - }, stopfn); - arrangement::arrange(m_unprintable, {}, min_d, bedshape, - [this, count](unsigned st) { - if (st > 0) update_status(int(count - st), arrangestr); - }, stopfn); - } catch (std::exception & /*e*/) { - GUI::show_error(plater().q, - _L("Could not arrange model objects! " - "Some geometries may be invalid.")); - } - - // finalize just here. - update_status(int(count), - was_canceled() ? _L("Arranging canceled.") - : _L("Arranging done.")); -} - -void Plater::priv::RotoptimizeJob::process() -{ - int obj_idx = plater().get_selected_object_idx(); - if (obj_idx < 0) { return; } - - ModelObject *o = plater().model.objects[size_t(obj_idx)]; - - auto r = sla::find_best_rotation( - *o, - .005f, - [this](unsigned s) { - if (s < 100) - update_status(int(s), - _L("Searching for optimal orientation")); - }, - [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]}); - - auto trmatrix = oi->get_transformation().get_matrix(); - Polygon trchull = o->convex_hull_2d(trmatrix); - - MinAreaBoundigBox rotbb(trchull, MinAreaBoundigBox::pcConvex); - double r = rotbb.angle_to_X(); - - // The box should be landscape - if(rotbb.width() < rotbb.height()) r += PI / 2; - - Vec3d rt = oi->get_rotation(); rt(Z) += r; - - oi->set_rotation(rt); - } - - plater().find_new_position(o->instances, scaled(mindist)); - - // Correct the z offset of the object which was corrupted be - // the rotation - o->ensure_on_bed(); - } - - update_status(100, - was_canceled() ? _L("Orientation search canceled.") - : _L("Orientation found.")); -} - - void Plater::priv::split_object() { int obj_idx = get_selected_object_idx(); @@ -3589,7 +3195,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) } // update plater with new config - wxGetApp().plater()->on_config_change(wxGetApp().preset_bundle->full_config()); + q->on_config_change(wxGetApp().preset_bundle->full_config()); /* Settings list can be changed after printer preset changing, so * update all settings items for all item had it. * Furthermore, Layers editing is implemented only for FFF printers @@ -4036,8 +3642,12 @@ bool Plater::priv::complit_init_sla_object_menu() sla_object_menu.AppendSeparator(); // Add the automatic rotation sub-menu - append_menu_item(&sla_object_menu, wxID_ANY, _L("Optimize orientation"), _L("Optimize the rotation of the object for better print results."), - [this](wxCommandEvent&) { sla_optimize_rotation(); }); + append_menu_item( + &sla_object_menu, wxID_ANY, _(L("Optimize orientation")), + _(L("Optimize the rotation of the object for better print results.")), + [this](wxCommandEvent &) { + m_ui_jobs.optimize_rotation(); + }); return true; } @@ -4733,7 +4343,7 @@ void Plater::increase_instances(size_t num) sidebar().obj_list()->increase_object_instances(obj_idx, was_one_instance ? num + 1 : num); if (p->get_config("autocenter") == "1") - p->arrange(); + arrange(); p->update(); @@ -5467,6 +5077,11 @@ bool Plater::is_export_gcode_scheduled() const return p->background_process.is_export_scheduled(); } +const Selection &Plater::get_selection() const +{ + return p->get_selection(); +} + int Plater::get_selected_object_idx() { return p->get_selected_object_idx(); @@ -5492,6 +5107,11 @@ BoundingBoxf Plater::bed_shape_bb() const return p->bed_shape_bb(); } +void Plater::arrange() +{ + p->m_ui_jobs.arrange(); +} + void Plater::set_current_canvas_as_dirty() { p->set_current_canvas_as_dirty(); @@ -5514,6 +5134,8 @@ PrinterTechnology Plater::printer_technology() const return p->printer_technology; } +const DynamicPrintConfig * Plater::config() const { return p->config; } + void Plater::set_printer_technology(PrinterTechnology printer_technology) { p->printer_technology = printer_technology; diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index efdaa75cc..121689148 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -9,8 +9,10 @@ #include #include "Preset.hpp" +#include "Selection.hpp" #include "libslic3r/BoundingBox.hpp" +#include "Job.hpp" #include "wxExtensions.hpp" class wxButton; @@ -252,12 +254,16 @@ public: void set_project_filename(const wxString& filename); bool is_export_gcode_scheduled() const; - + + const Selection& get_selection() const; int get_selected_object_idx(); bool is_single_full_object_selection() const; GLCanvas3D* canvas3D(); GLCanvas3D* get_current_canvas3D(); BoundingBoxf bed_shape_bb() const; + + void arrange(); + void find_new_position(const ModelInstancePtrs &instances, coord_t min_d); void set_current_canvas_as_dirty(); #if ENABLE_NON_STATIC_CANVAS_MANAGER @@ -266,6 +272,7 @@ public: #endif // ENABLE_NON_STATIC_CANVAS_MANAGER PrinterTechnology printer_technology() const; + const DynamicPrintConfig * config() const; void set_printer_technology(PrinterTechnology printer_technology); void copy_selection_to_clipboard(); @@ -371,6 +378,7 @@ private: bool m_was_scheduled; }; -}} +} // namespace GUI +} // namespace Slic3r #endif diff --git a/src/slic3r/GUI/RotoptimizeJob.cpp b/src/slic3r/GUI/RotoptimizeJob.cpp new file mode 100644 index 000000000..10f95201b --- /dev/null +++ b/src/slic3r/GUI/RotoptimizeJob.cpp @@ -0,0 +1,68 @@ +#include "RotoptimizeJob.hpp" + +#include "libslic3r/MTUtils.hpp" +#include "libslic3r/SLA/Rotfinder.hpp" +#include "libslic3r/MinAreaBoundingBox.hpp" + +#include "Plater.hpp" + +namespace Slic3r { namespace GUI { + +void RotoptimizeJob::process() +{ + int obj_idx = m_plater->get_selected_object_idx(); + if (obj_idx < 0) { return; } + + ModelObject *o = m_plater->model().objects[size_t(obj_idx)]; + + auto r = sla::find_best_rotation( + *o, + .005f, + [this](unsigned s) { + if (s < 100) + update_status(int(s), + _(L("Searching for optimal orientation"))); + }, + [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]}); + + auto trmatrix = oi->get_transformation().get_matrix(); + Polygon trchull = o->convex_hull_2d(trmatrix); + + MinAreaBoundigBox rotbb(trchull, MinAreaBoundigBox::pcConvex); + double phi = rotbb.angle_to_X(); + + // The box should be landscape + if(rotbb.width() < rotbb.height()) phi += PI / 2; + + Vec3d rt = oi->get_rotation(); rt(Z) += phi; + + oi->set_rotation(rt); + } + + m_plater->find_new_position(o->instances, scaled(mindist)); + + // Correct the z offset of the object which was corrupted be + // the rotation + o->ensure_on_bed(); + } + + update_status(100, was_canceled() ? _(L("Orientation search canceled.")) : + _(L("Orientation found."))); +} + +void RotoptimizeJob::finalize() +{ + if (!was_canceled()) + m_plater->update(); + + Job::finalize(); +} + +}} diff --git a/src/slic3r/GUI/RotoptimizeJob.hpp b/src/slic3r/GUI/RotoptimizeJob.hpp new file mode 100644 index 000000000..983c43c68 --- /dev/null +++ b/src/slic3r/GUI/RotoptimizeJob.hpp @@ -0,0 +1,24 @@ +#ifndef ROTOPTIMIZEJOB_HPP +#define ROTOPTIMIZEJOB_HPP + +#include "Job.hpp" + +namespace Slic3r { namespace GUI { + +class Plater; + +class RotoptimizeJob : public Job +{ + Plater *m_plater; +public: + RotoptimizeJob(std::shared_ptr pri, Plater *plater) + : Job{std::move(pri)}, m_plater{plater} + {} + + void process() override; + void finalize() override; +}; + +}} // namespace Slic3r::GUI + +#endif // ROTOPTIMIZEJOB_HPP From 6eb51a1cca4d99385f8b1795d5fc1063424c1635 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 23 Apr 2020 18:56:09 +0200 Subject: [PATCH 08/11] Move ui jobs into separate folder --- src/slic3r/CMakeLists.txt | 18 +++++++++--------- src/slic3r/GUI/{ => Jobs}/ArrangeJob.cpp | 6 +++--- src/slic3r/GUI/{ => Jobs}/ArrangeJob.hpp | 0 src/slic3r/GUI/{ => Jobs}/Job.cpp | 0 src/slic3r/GUI/{ => Jobs}/Job.hpp | 3 ++- .../GUI/{ => Jobs}/ProgressIndicator.hpp | 0 src/slic3r/GUI/{ => Jobs}/RotoptimizeJob.cpp | 2 +- src/slic3r/GUI/{ => Jobs}/RotoptimizeJob.hpp | 0 src/slic3r/GUI/Plater.cpp | 4 ++-- src/slic3r/GUI/Plater.hpp | 2 +- src/slic3r/GUI/ProgressStatusBar.hpp | 2 +- 11 files changed, 19 insertions(+), 18 deletions(-) rename src/slic3r/GUI/{ => Jobs}/ArrangeJob.cpp (98%) rename src/slic3r/GUI/{ => Jobs}/ArrangeJob.hpp (100%) rename src/slic3r/GUI/{ => Jobs}/Job.cpp (100%) rename src/slic3r/GUI/{ => Jobs}/Job.hpp (98%) rename src/slic3r/GUI/{ => Jobs}/ProgressIndicator.hpp (100%) rename src/slic3r/GUI/{ => Jobs}/RotoptimizeJob.cpp (98%) rename src/slic3r/GUI/{ => Jobs}/RotoptimizeJob.hpp (100%) diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index c448fe3ba..1e9f3b862 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -142,17 +142,17 @@ set(SLIC3R_GUI_SOURCES GUI/UpdateDialogs.hpp GUI/FirmwareDialog.cpp GUI/FirmwareDialog.hpp - GUI/ProgressIndicator.hpp - GUI/ProgressStatusBar.hpp - GUI/ProgressStatusBar.cpp GUI/PrintHostDialogs.cpp GUI/PrintHostDialogs.hpp - GUI/Job.hpp - GUI/Job.cpp - GUI/ArrangeJob.hpp - GUI/ArrangeJob.cpp - GUI/RotoptimizeJob.hpp - GUI/RotoptimizeJob.cpp + GUI/Jobs/Job.hpp + GUI/Jobs/Job.cpp + GUI/Jobs/ArrangeJob.hpp + GUI/Jobs/ArrangeJob.cpp + GUI/Jobs/RotoptimizeJob.hpp + GUI/Jobs/RotoptimizeJob.cpp + GUI/Jobs/ProgressIndicator.hpp + GUI/ProgressStatusBar.hpp + GUI/ProgressStatusBar.cpp GUI/Mouse3DController.cpp GUI/Mouse3DController.hpp GUI/DoubleSlider.cpp diff --git a/src/slic3r/GUI/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp similarity index 98% rename from src/slic3r/GUI/ArrangeJob.cpp rename to src/slic3r/GUI/Jobs/ArrangeJob.cpp index e43631f7e..00b9fb654 100644 --- a/src/slic3r/GUI/ArrangeJob.cpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp @@ -2,9 +2,9 @@ #include "libslic3r/MTUtils.hpp" -#include "Plater.hpp" -#include "GLCanvas3D.hpp" -#include "GUI.hpp" +#include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/GUI.hpp" namespace Slic3r { namespace GUI { diff --git a/src/slic3r/GUI/ArrangeJob.hpp b/src/slic3r/GUI/Jobs/ArrangeJob.hpp similarity index 100% rename from src/slic3r/GUI/ArrangeJob.hpp rename to src/slic3r/GUI/Jobs/ArrangeJob.hpp diff --git a/src/slic3r/GUI/Job.cpp b/src/slic3r/GUI/Jobs/Job.cpp similarity index 100% rename from src/slic3r/GUI/Job.cpp rename to src/slic3r/GUI/Jobs/Job.cpp diff --git a/src/slic3r/GUI/Job.hpp b/src/slic3r/GUI/Jobs/Job.hpp similarity index 98% rename from src/slic3r/GUI/Job.hpp rename to src/slic3r/GUI/Jobs/Job.hpp index b1a652b5e..130ca2ed9 100644 --- a/src/slic3r/GUI/Job.hpp +++ b/src/slic3r/GUI/Jobs/Job.hpp @@ -5,7 +5,8 @@ #include #include -#include + +#include "ProgressIndicator.hpp" #include diff --git a/src/slic3r/GUI/ProgressIndicator.hpp b/src/slic3r/GUI/Jobs/ProgressIndicator.hpp similarity index 100% rename from src/slic3r/GUI/ProgressIndicator.hpp rename to src/slic3r/GUI/Jobs/ProgressIndicator.hpp diff --git a/src/slic3r/GUI/RotoptimizeJob.cpp b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp similarity index 98% rename from src/slic3r/GUI/RotoptimizeJob.cpp rename to src/slic3r/GUI/Jobs/RotoptimizeJob.cpp index 10f95201b..4cc34e71c 100644 --- a/src/slic3r/GUI/RotoptimizeJob.cpp +++ b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp @@ -4,7 +4,7 @@ #include "libslic3r/SLA/Rotfinder.hpp" #include "libslic3r/MinAreaBoundingBox.hpp" -#include "Plater.hpp" +#include "slic3r/GUI/Plater.hpp" namespace Slic3r { namespace GUI { diff --git a/src/slic3r/GUI/RotoptimizeJob.hpp b/src/slic3r/GUI/Jobs/RotoptimizeJob.hpp similarity index 100% rename from src/slic3r/GUI/RotoptimizeJob.hpp rename to src/slic3r/GUI/Jobs/RotoptimizeJob.hpp diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 2500ee6b8..7f323c554 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -61,8 +61,8 @@ #include "Camera.hpp" #include "Mouse3DController.hpp" #include "Tab.hpp" -#include "ArrangeJob.hpp" -#include "RotoptimizeJob.hpp" +#include "Jobs/ArrangeJob.hpp" +#include "Jobs/RotoptimizeJob.hpp" #include "PresetBundle.hpp" #include "BackgroundSlicingProcess.hpp" #include "ProgressStatusBar.hpp" diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 121689148..e71eb3db4 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -12,7 +12,7 @@ #include "Selection.hpp" #include "libslic3r/BoundingBox.hpp" -#include "Job.hpp" +#include "Jobs/Job.hpp" #include "wxExtensions.hpp" class wxButton; diff --git a/src/slic3r/GUI/ProgressStatusBar.hpp b/src/slic3r/GUI/ProgressStatusBar.hpp index faeb7a34e..15b10deeb 100644 --- a/src/slic3r/GUI/ProgressStatusBar.hpp +++ b/src/slic3r/GUI/ProgressStatusBar.hpp @@ -6,7 +6,7 @@ #include #include -#include "ProgressIndicator.hpp" +#include "Jobs/ProgressIndicator.hpp" class wxTimer; class wxGauge; From 247fca6d55318fbaa5c7424bf7605db7fb02e342 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Mon, 13 Apr 2020 12:31:37 +0200 Subject: [PATCH 09/11] Initial version of sl1 import with sla::Raster refactor. --- src/PrusaSlicer.cpp | 6 +- src/libslic3r/CMakeLists.txt | 16 +- src/libslic3r/Format/SL1.cpp | 171 ++++++++ src/libslic3r/Format/SL1.hpp | 44 ++ src/libslic3r/MTUtils.hpp | 4 +- src/libslic3r/MarchingSquares.hpp | 432 ++++++++++++++++++++ src/libslic3r/SLA/AGGRaster.hpp | 222 ++++++++++ src/libslic3r/SLA/Pad.cpp | 200 +-------- src/libslic3r/SLA/Raster.cpp | 320 --------------- src/libslic3r/SLA/Raster.hpp | 157 ------- src/libslic3r/SLA/RasterBase.cpp | 89 ++++ src/libslic3r/SLA/RasterBase.hpp | 124 ++++++ src/libslic3r/SLA/RasterToPolygons.cpp | 88 ++++ src/libslic3r/SLA/RasterToPolygons.hpp | 15 + src/libslic3r/SLA/RasterWriter.cpp | 151 ------- src/libslic3r/SLA/RasterWriter.hpp | 130 ------ src/libslic3r/SLAPrint.cpp | 45 +- src/libslic3r/SLAPrint.hpp | 70 ++-- src/libslic3r/SLAPrintSteps.cpp | 47 +-- src/libslic3r/SLAPrintSteps.hpp | 2 +- src/libslic3r/SlicesToTriangleMesh.cpp | 83 ++++ src/libslic3r/SlicesToTriangleMesh.hpp | 24 ++ src/libslic3r/TriangulateWall.cpp | 133 ++++++ src/libslic3r/TriangulateWall.hpp | 17 + src/libslic3r/Zipper.cpp | 2 +- src/libslic3r/Zipper.hpp | 2 +- src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/BackgroundSlicingProcess.cpp | 7 +- src/slic3r/GUI/BackgroundSlicingProcess.hpp | 5 +- src/slic3r/GUI/MainFrame.cpp | 5 + src/slic3r/GUI/Plater.cpp | 23 ++ src/slic3r/GUI/Plater.hpp | 1 + src/slic3r/Utils/SLAZipFileImport.cpp | 144 +++++++ src/slic3r/Utils/SLAZipFileImport.hpp | 14 + tests/libslic3r/CMakeLists.txt | 1 + tests/libslic3r/test_marchingsquares.cpp | 342 ++++++++++++++++ tests/sla_print/sla_print_tests.cpp | 40 +- tests/sla_print/sla_test_utils.cpp | 39 +- tests/sla_print/sla_test_utils.hpp | 13 +- 39 files changed, 2136 insertions(+), 1094 deletions(-) create mode 100644 src/libslic3r/Format/SL1.cpp create mode 100644 src/libslic3r/Format/SL1.hpp create mode 100644 src/libslic3r/MarchingSquares.hpp create mode 100644 src/libslic3r/SLA/AGGRaster.hpp delete mode 100644 src/libslic3r/SLA/Raster.cpp delete mode 100644 src/libslic3r/SLA/Raster.hpp create mode 100644 src/libslic3r/SLA/RasterBase.cpp create mode 100644 src/libslic3r/SLA/RasterBase.hpp create mode 100644 src/libslic3r/SLA/RasterToPolygons.cpp create mode 100644 src/libslic3r/SLA/RasterToPolygons.hpp delete mode 100644 src/libslic3r/SLA/RasterWriter.cpp delete mode 100644 src/libslic3r/SLA/RasterWriter.hpp create mode 100644 src/libslic3r/SlicesToTriangleMesh.cpp create mode 100644 src/libslic3r/SlicesToTriangleMesh.hpp create mode 100644 src/libslic3r/TriangulateWall.cpp create mode 100644 src/libslic3r/TriangulateWall.hpp create mode 100644 src/slic3r/Utils/SLAZipFileImport.cpp create mode 100644 src/slic3r/Utils/SLAZipFileImport.hpp create mode 100644 tests/libslic3r/test_marchingsquares.cpp diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 0f9df1e41..751df6fbf 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -42,6 +42,7 @@ #include "libslic3r/Format/3mf.hpp" #include "libslic3r/Format/STL.hpp" #include "libslic3r/Format/OBJ.hpp" +#include "libslic3r/Format/SL1.hpp" #include "libslic3r/Utils.hpp" #include "PrusaSlicer.hpp" @@ -422,7 +423,8 @@ int CLI::run(int argc, char **argv) std::string outfile = m_config.opt_string("output"); Print fff_print; SLAPrint sla_print; - + SL1Archive sla_archive(sla_print.printer_config()); + sla_print.set_printer(&sla_archive); sla_print.set_status_callback( [](const PrintBase::SlicingStatus& s) { @@ -462,7 +464,7 @@ int CLI::run(int argc, char **argv) outfile = sla_print.output_filepath(outfile); // We need to finalize the filename beforehand because the export function sets the filename inside the zip metadata outfile_final = sla_print.print_statistics().finalize_output_path(outfile); - sla_print.export_raster(outfile_final); + sla_archive.export_print(outfile_final, sla_print); } if (outfile != outfile_final && Slic3r::rename_file(outfile, outfile_final)) { boost::nowide::cerr << "Renaming file " << outfile << " to " << outfile_final << " failed" << std::endl; diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 2dc18728c..eb0f87e50 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -77,6 +77,8 @@ add_library(libslic3r STATIC Format/PRUS.hpp Format/STL.cpp Format/STL.hpp + Format/SL1.hpp + Format/SL1.cpp GCode/Analyzer.cpp GCode/Analyzer.hpp GCode/ThumbnailData.cpp @@ -162,6 +164,8 @@ add_library(libslic3r STATIC SLAPrint.hpp Slicing.cpp Slicing.hpp + SlicesToTriangleMesh.hpp + SlicesToTriangleMesh.cpp SlicingAdaptive.cpp SlicingAdaptive.hpp SupportMaterial.cpp @@ -177,6 +181,8 @@ add_library(libslic3r STATIC Tesselate.hpp TriangleMesh.cpp TriangleMesh.hpp + TriangulateWall.hpp + TriangulateWall.cpp utils.cpp Utils.hpp Time.cpp @@ -191,6 +197,7 @@ add_library(libslic3r STATIC SimplifyMesh.hpp SimplifyMeshImpl.hpp SimplifyMesh.cpp + MarchingSquares.hpp ${OpenVDBUtils_SOURCES} SLA/Common.hpp SLA/Common.cpp @@ -208,10 +215,11 @@ add_library(libslic3r STATIC SLA/Rotfinder.cpp SLA/BoostAdapter.hpp SLA/SpatIndex.hpp - SLA/Raster.hpp - SLA/Raster.cpp - SLA/RasterWriter.hpp - SLA/RasterWriter.cpp + SLA/RasterBase.hpp + SLA/RasterBase.cpp + SLA/AGGRaster.hpp + SLA/RasterToPolygons.hpp + SLA/RasterToPolygons.cpp SLA/ConcaveHull.hpp SLA/ConcaveHull.cpp SLA/Hollowing.hpp diff --git a/src/libslic3r/Format/SL1.cpp b/src/libslic3r/Format/SL1.cpp new file mode 100644 index 000000000..ba5e89330 --- /dev/null +++ b/src/libslic3r/Format/SL1.cpp @@ -0,0 +1,171 @@ +#include "SL1.hpp" +#include "GCode/ThumbnailData.hpp" +#include "libslic3r/Time.hpp" + +#include +#include + +#include "libslic3r/Zipper.hpp" +#include "libslic3r/SLAPrint.hpp" + +namespace Slic3r { + +using ConfMap = std::map; + +namespace { + +std::string to_ini(const ConfMap &m) +{ + std::string ret; + for (auto ¶m : m) ret += param.first + " = " + param.second + "\n"; + + return ret; +} + +std::string get_cfg_value(const DynamicPrintConfig &cfg, const std::string &key) +{ + std::string ret; + + if (cfg.has(key)) { + auto opt = cfg.option(key); + if (opt) ret = opt->serialize(); + } + + return ret; +} + +void fill_iniconf(ConfMap &m, const SLAPrint &print) +{ + auto &cfg = print.full_print_config(); + m["layerHeight"] = get_cfg_value(cfg, "layer_height"); + m["expTime"] = get_cfg_value(cfg, "exposure_time"); + m["expTimeFirst"] = get_cfg_value(cfg, "initial_exposure_time"); + m["materialName"] = get_cfg_value(cfg, "sla_material_settings_id"); + m["printerModel"] = get_cfg_value(cfg, "printer_model"); + m["printerVariant"] = get_cfg_value(cfg, "printer_variant"); + m["printerProfile"] = get_cfg_value(cfg, "printer_settings_id"); + m["printProfile"] = get_cfg_value(cfg, "sla_print_settings_id"); + m["fileCreationTimestamp"] = Utils::utc_timestamp(); + m["prusaSlicerVersion"] = SLIC3R_BUILD_ID; + + SLAPrintStatistics stats = print.print_statistics(); + // Set statistics values to the printer + + double used_material = (stats.objects_used_material + + stats.support_used_material) / 1000; + + int num_fade = print.default_object_config().faded_layers.getInt(); + num_fade = num_fade >= 0 ? num_fade : 0; + + m["usedMaterial"] = std::to_string(used_material); + m["numFade"] = std::to_string(num_fade); + m["numSlow"] = std::to_string(stats.slow_layers_count); + m["numFast"] = std::to_string(stats.fast_layers_count); + m["printTime"] = std::to_string(stats.estimated_print_time); + + m["action"] = "print"; +} + +void fill_slicerconf(ConfMap &m, const SLAPrint &print) +{ + using namespace std::literals::string_view_literals; + + // Sorted list of config keys, which shall not be stored into the ini. + static constexpr auto banned_keys = { + "compatible_printers"sv, + "compatible_prints"sv, + "print_host"sv, + "printhost_apikey"sv, + "printhost_cafile"sv + }; + + assert(std::is_sorted(banned_keys.begin(), banned_keys.end())); + auto is_banned = [](const std::string &key) { + return std::binary_search(banned_keys.begin(), banned_keys.end(), key); + }; + + auto &cfg = print.full_print_config(); + for (const std::string &key : cfg.keys()) + if (! is_banned(key) && ! cfg.option(key)->is_nil()) + m[key] = cfg.opt_serialize(key); + +} + +} // namespace + +uqptr SL1Archive::create_raster() const +{ + sla::RasterBase::Resolution res; + sla::RasterBase::PixelDim pxdim; + std::array mirror; + + double w = m_cfg.display_width.getFloat(); + double h = m_cfg.display_height.getFloat(); + auto pw = size_t(m_cfg.display_pixels_x.getInt()); + auto ph = size_t(m_cfg.display_pixels_y.getInt()); + + mirror[X] = m_cfg.display_mirror_x.getBool(); + mirror[Y] = m_cfg.display_mirror_y.getBool(); + + auto ro = m_cfg.display_orientation.getInt(); + sla::RasterBase::Orientation orientation = + ro == sla::RasterBase::roPortrait ? sla::RasterBase::roPortrait : + sla::RasterBase::roLandscape; + + if (orientation == sla::RasterBase::roPortrait) { + std::swap(w, h); + std::swap(pw, ph); + } + + res = sla::RasterBase::Resolution{pw, ph}; + pxdim = sla::RasterBase::PixelDim{w / pw, h / ph}; + sla::RasterBase::Trafo tr{orientation, mirror}; + + double gamma = m_cfg.gamma_correction.getFloat(); + + return sla::create_raster_grayscale_aa(res, pxdim, gamma, tr); +} + +sla::EncodedRaster SL1Archive::encode_raster(const sla::RasterBase &rst) const +{ + return rst.encode(sla::PNGRasterEncoder()); +} + +void SL1Archive::export_print(Zipper& zipper, + const SLAPrint &print, + const std::string &prjname) +{ + std::string project = + prjname.empty() ? + boost::filesystem::path(zipper.get_filename()).stem().string() : + prjname; + + ConfMap iniconf, slicerconf; + fill_iniconf(iniconf, print); + + iniconf["jobDir"] = project; + + fill_slicerconf(slicerconf, print); + + try { + zipper.add_entry("config.ini"); + zipper << to_ini(iniconf); + zipper.add_entry("prusaslicer.ini"); + zipper << to_ini(slicerconf); + + size_t i = 0; + for (const sla::EncodedRaster &rst : m_layers) { + + std::string imgname = project + string_printf("%.5d", i++) + "." + + rst.extension(); + + zipper.add_entry(imgname.c_str(), rst.data(), rst.size()); + } + } catch(std::exception& e) { + BOOST_LOG_TRIVIAL(error) << e.what(); + // Rethrow the exception + throw; + } +} + +} // namespace Slic3r diff --git a/src/libslic3r/Format/SL1.hpp b/src/libslic3r/Format/SL1.hpp new file mode 100644 index 000000000..1b9e95392 --- /dev/null +++ b/src/libslic3r/Format/SL1.hpp @@ -0,0 +1,44 @@ +#ifndef ARCHIVETRAITS_HPP +#define ARCHIVETRAITS_HPP + +#include + +#include "libslic3r/Zipper.hpp" +#include "libslic3r/SLAPrint.hpp" + +namespace Slic3r { + +class SL1Archive: public SLAPrinter { + SLAPrinterConfig m_cfg; + +protected: + uqptr create_raster() const override; + sla::EncodedRaster encode_raster(const sla::RasterBase &rst) const override; + +public: + + SL1Archive() = default; + explicit SL1Archive(const SLAPrinterConfig &cfg): m_cfg(cfg) {} + explicit SL1Archive(SLAPrinterConfig &&cfg): m_cfg(std::move(cfg)) {} + + void export_print(Zipper &zipper, const SLAPrint &print, const std::string &projectname = ""); + void export_print(const std::string &fname, const SLAPrint &print, const std::string &projectname = "") + { + Zipper zipper(fname); + export_print(zipper, print, projectname); + } + + void apply(const SLAPrinterConfig &cfg) override + { + auto diff = m_cfg.diff(cfg); + if (!diff.empty()) { + m_cfg.apply_only(cfg, diff); + m_layers = {}; + } + } +}; + + +} // namespace Slic3r::sla + +#endif // ARCHIVETRAITS_HPP diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp index 170a6599c..294f9ae6f 100644 --- a/src/libslic3r/MTUtils.hpp +++ b/src/libslic3r/MTUtils.hpp @@ -126,10 +126,10 @@ inline IntegerOnly> reserve_vector(I capacity) } /// Exactly like Matlab https://www.mathworks.com/help/matlab/ref/linspace.html -template +template> inline std::vector linspace_vector(const ArithmeticOnly &start, const T &stop, - const IntegerOnly &n) + const I &n) { std::vector vals(n, T()); diff --git a/src/libslic3r/MarchingSquares.hpp b/src/libslic3r/MarchingSquares.hpp new file mode 100644 index 000000000..a5aeb0953 --- /dev/null +++ b/src/libslic3r/MarchingSquares.hpp @@ -0,0 +1,432 @@ +#ifndef MARCHINGSQUARES_HPP +#define MARCHINGSQUARES_HPP + +#include +#include +#include +#include +#include + +namespace marchsq { + +// Marks a square in the grid +struct Coord { + size_t r = 0, c = 0; + + Coord() = default; + explicit Coord(size_t s) : r(s), c(s) {} + Coord(size_t _r, size_t _c): r(_r), c(_c) {} + + size_t seq(const Coord &res) const { return r * res.c + c; } + Coord& operator+=(const Coord& b) { r += b.r; c += b.c; return *this; } + Coord operator+(const Coord& b) const { Coord a = *this; a += b; return a; } +}; + +// Closed ring of cell coordinates +using Ring = std::vector; + +// Specialize this struct to register a raster type for the Marching squares alg +template struct _RasterTraits { + + // The type of pixel cell in the raster + using ValueType = typename T::ValueType; + + // Value at a given position + static ValueType get(const T &raster, size_t row, size_t col); + + // Number of rows and cols of the raster + static size_t rows(const T &raster); + static size_t cols(const T &raster); +}; + +// Specialize this to use parellel loops within the algorithm +template struct _Loop { + template static void for_each(It from, It to, Fn &&fn) + { + for (auto it = from; it < to; ++it) fn(*it, size_t(it - from)); + } +}; + +namespace __impl { + +template using RasterTraits = _RasterTraits>; +template using TRasterValue = typename RasterTraits::ValueType; + +template TRasterValue isoval(const T &raster, const Coord &crd) +{ + return RasterTraits::get(raster, crd.r, crd.c); +} + +template size_t rows(const T &raster) +{ + return RasterTraits::rows(raster); +} + +template size_t cols(const T &raster) +{ + return RasterTraits::cols(raster); +} + +template +void for_each(ExecutionPolicy&& policy, It from, It to, Fn &&fn) +{ + _Loop::for_each(from, to, fn); +} + +// Type of squares (tiles) depending on which vertices are inside an ROI +// The vertices would be marked a, b, c, d in counter clockwise order from the +// bottom left vertex of a square. +// d --- c +// | | +// | | +// a --- b +enum class SquareTag : uint8_t { +// 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 + none, a, b, ab, c, ac, bc, abc, d, ad, bd, abd, cd, acd, bcd, full +}; + +template constexpr std::underlying_type_t _t(E e) noexcept +{ + return static_cast>(e); +} + +enum class Dir: uint8_t { left, down, right, up, none}; + +static const constexpr Dir NEXT_CCW[] = { + /* 00 */ Dir::none, // SquareTag::none (empty square, nowhere to go) + /* 01 */ Dir::left, // SquareTag::a + /* 02 */ Dir::down, // SquareTag::b + /* 03 */ Dir::left, // SquareTag::ab + /* 04 */ Dir::right, // SquareTag::c + /* 05 */ Dir::none, // SquareTag::ac (ambiguous case) + /* 06 */ Dir::down, // SquareTag::bc + /* 07 */ Dir::left, // SquareTag::abc + /* 08 */ Dir::up, // SquareTag::d + /* 09 */ Dir::up, // SquareTag::ad + /* 10 */ Dir::none, // SquareTag::bd (ambiguous case) + /* 11 */ Dir::up, // SquareTag::abd + /* 12 */ Dir::right, // SquareTag::cd + /* 13 */ Dir::right, // SquareTag::acd + /* 14 */ Dir::down, // SquareTag::bcd + /* 15 */ Dir::none // SquareTag::full (full covered, nowhere to go) +}; + +static const constexpr uint8_t PREV_CCW[] = { + /* 00 */ 1 << _t(Dir::none), + /* 01 */ 1 << _t(Dir::up), + /* 02 */ 1 << _t(Dir::left), + /* 03 */ 1 << _t(Dir::left), + /* 04 */ 1 << _t(Dir::down), + /* 05 */ 1 << _t(Dir::up) | 1 << _t(Dir::down), + /* 06 */ 1 << _t(Dir::down), + /* 07 */ 1 << _t(Dir::down), + /* 08 */ 1 << _t(Dir::right), + /* 09 */ 1 << _t(Dir::up), + /* 10 */ 1 << _t(Dir::left) | 1 << _t(Dir::right), + /* 11 */ 1 << _t(Dir::left), + /* 12 */ 1 << _t(Dir::right), + /* 13 */ 1 << _t(Dir::up), + /* 14 */ 1 << _t(Dir::right), + /* 15 */ 1 << _t(Dir::none) +}; + +const constexpr uint8_t DIRMASKS[] = { + /*left: */ 0x01, /*down*/ 0x12, /*right */0x21, /*up*/ 0x10, /*none*/ 0x00 +}; + +inline Coord step(const Coord &crd, Dir d) +{ + uint8_t dd = DIRMASKS[uint8_t(d)]; + return {crd.r - 1 + (dd & 0x0f), crd.c - 1 + (dd >> 4)}; +} + +template class Grid { + const Rst * m_rst = nullptr; + Coord m_cellsize, m_res_1, m_window, m_gridsize; + std::vector m_tags; // Assign tags to each square + + Coord rastercoord(const Coord &crd) const + { + return {crd.r * m_window.r, crd.c * m_window.c}; + } + + Coord bl(const Coord &crd) const { return tl(crd) + Coord{m_res_1.r, 0}; } + Coord br(const Coord &crd) const { return tl(crd) + Coord{m_res_1.r, m_res_1.c}; } + Coord tr(const Coord &crd) const { return tl(crd) + Coord{0, m_res_1.c}; } + Coord tl(const Coord &crd) const { return rastercoord(crd); } + + TRasterValue bottomleft(const Coord &cell) const + { + return isoval(*m_rst, bl(cell)); + } + + TRasterValue bottomright(const Coord &cell) const + { + return isoval(*m_rst, br(cell)); + } + + TRasterValue topright(const Coord &cell) const + { + return isoval(*m_rst, tr(cell)); + } + + TRasterValue topleft(const Coord &cell) const + { + return isoval(*m_rst, tl(cell)); + } + + // Calculate the tag for a cell (or square). The cell coordinates mark the + // top left vertex of a square in the raster. v is the isovalue + uint8_t get_tag_for_cell(const Coord &cell, TRasterValue v) + { + uint8_t t = (bottomleft(cell) >= v) + + ((bottomright(cell) >= v) << 1) + + ((topright(cell) >= v) << 2) + + ((topleft(cell) >= v) << 3); + + assert(t < 16); + return t; + } + + // Get a cell coordinate from a sequential index + Coord coord(size_t i) const { return {i / m_gridsize.c, i % m_gridsize.c}; } + + size_t seq(const Coord &crd) const { return crd.seq(m_gridsize); } + + bool is_visited(size_t idx, Dir d = Dir::none) const + { + SquareTag t = get_tag(idx); + uint8_t ref = d == Dir::none ? PREV_CCW[_t(t)] : uint8_t(1 << _t(d)); + return t == SquareTag::full || t == SquareTag::none || + ((m_tags[idx] & 0xf0) >> 4) == ref; + } + + void set_visited(size_t idx, Dir d = Dir::none) + { + m_tags[idx] |= (1 << (_t(d)) << 4); + } + + bool is_ambiguous(size_t idx) const + { + SquareTag t = get_tag(idx); + return t == SquareTag::ac || t == SquareTag::bd; + } + + // Search for a new starting square + size_t search_start_cell(size_t i = 0) const + { + // Skip ambiguous tags as starting tags due to unknown previous + // direction. + while ((i < m_tags.size() && is_visited(i)) || is_ambiguous(i)) ++i; + + return i; + } + + SquareTag get_tag(size_t idx) const { return SquareTag(m_tags[idx] & 0x0f); } + + Dir next_dir(Dir prev, SquareTag tag) const + { + // Treat ambiguous cases as two separate regions in one square. + switch (tag) { + case SquareTag::ac: + switch (prev) { + case Dir::down: return Dir::right; + case Dir::up: return Dir::left; + default: assert(false); return Dir::none; + } + case SquareTag::bd: + switch (prev) { + case Dir::right: return Dir::up; + case Dir::left: return Dir::down; + default: assert(false); return Dir::none; + } + default: + return NEXT_CCW[uint8_t(tag)]; + } + + return Dir::none; + } + + struct CellIt { + Coord crd; Dir dir= Dir::none; const Rst *rst = nullptr; + TRasterValue operator*() const { return isoval(*rst, crd); } + CellIt& operator++() { crd = step(crd, dir); return *this; } + CellIt operator++(int) { CellIt it = *this; ++(*this); return it; } + bool operator!=(const CellIt &it) { return crd.r != it.crd.r || crd.c != it.crd.c; } + + using value_type = TRasterValue; + using pointer = TRasterValue *; + using reference = TRasterValue &; + using difference_type = long; + using iterator_category = std::forward_iterator_tag; + }; + + // Two cell iterators representing an edge of a square. This is then + // used for binary search for the first active pixel on the edge. + struct Edge { CellIt from, to; }; + + Edge edge(const Coord &ringvertex) + { + size_t idx = ringvertex.r; + Coord cell = coord(idx); + uint8_t tg = m_tags[ringvertex.r]; + SquareTag t = SquareTag(tg & 0x0f); + + switch (t) { + case SquareTag::a: + case SquareTag::ab: + case SquareTag::abc: + return {{tl(cell), Dir::down, m_rst}, {bl(cell)}}; + case SquareTag::b: + case SquareTag::bc: + case SquareTag::bcd: + return {{bl(cell), Dir::right, m_rst}, {br(cell)}}; + case SquareTag::c: + return {{br(cell), Dir::up, m_rst}, {tr(cell)}}; + case SquareTag::ac: + switch (Dir(ringvertex.c)) { + case Dir::left: return {{tl(cell), Dir::down, m_rst}, {bl(cell)}}; + case Dir::right: return {{br(cell), Dir::up, m_rst}, {tr(cell)}}; + default: assert(false); + } + case SquareTag::d: + case SquareTag::ad: + case SquareTag::abd: + return {{tr(cell), Dir::left, m_rst}, {tl(cell)}}; + case SquareTag::bd: + switch (Dir(ringvertex.c)) { + case Dir::down: return {{bl(cell), Dir::right, m_rst}, {br(cell)}}; + case Dir::up: return {{tr(cell), Dir::left, m_rst}, {tl(cell)}}; + default: assert(false); + } + case SquareTag::cd: + case SquareTag::acd: + return {{br(cell), Dir::up, m_rst}, {tr(cell)}}; + case SquareTag::full: + case SquareTag::none: { + Coord crd{tl(cell) + Coord{m_cellsize.r / 2, m_cellsize.c / 2}}; + return {{crd, Dir::none, m_rst}, crd}; + } + } + + return {}; + } + +public: + explicit Grid(const Rst &rst, const Coord &cellsz, const Coord &overlap) + : m_rst{&rst} + , m_cellsize{cellsz} + , m_res_1{m_cellsize.r - 1, m_cellsize.c - 1} + , m_window{overlap.r < cellsz.r ? cellsz.r - overlap.r : cellsz.r, + overlap.c < cellsz.c ? cellsz.c - overlap.c : cellsz.c} + , m_gridsize{(rows(rst) - overlap.r) / m_window.r, + (cols(rst) - overlap.c) / m_window.c} + , m_tags(m_gridsize.r * m_gridsize.c, 0) + {} + + // Go through the cells and mark them with the appropriate tag. + template + void tag_grid(ExecutionPolicy &&policy, TRasterValue isoval) + { + // parallel for r + for_each (std::forward(policy), + m_tags.begin(), m_tags.end(), + [this, isoval](uint8_t& tag, size_t idx) { + tag = get_tag_for_cell(coord(idx), isoval); + }); + } + + // Scan for the rings on the tagged grid. Each ring vertex stores the + // sequential index of the cell and the next direction (Dir). + // This info can be used later to calculate the exact raster coordinate. + std::vector scan_rings() + { + std::vector rings; + size_t startidx = 0; + while ((startidx = search_start_cell(startidx)) < m_tags.size()) { + Ring ring; + + size_t idx = startidx; + Dir prev = Dir::none, next = next_dir(prev, get_tag(idx)); + + while (next != Dir::none && !is_visited(idx, prev)) { + Coord ringvertex{idx, size_t(next)}; + ring.emplace_back(ringvertex); + set_visited(idx, prev); + + idx = seq(step(coord(idx), next)); + prev = next; + next = next_dir(next, get_tag(idx)); + } + + // To prevent infinite loops in case of degenerate input + if (next == Dir::none) m_tags[startidx] = _t(SquareTag::none); + + if (ring.size() > 1) { + ring.pop_back(); + rings.emplace_back(ring); + } + } + + return rings; + } + + // Calculate the exact raster position from the cells which store the + // sequantial index of the square and the next direction + template + void interpolate_rings(ExecutionPolicy && policy, + std::vector &rings, + TRasterValue isov) + { + for_each(std::forward(policy), + rings.begin(), rings.end(), [this, isov] (Ring &ring, size_t) { + for (Coord &ringvertex : ring) { + Edge e = edge(ringvertex); + CellIt found = std::lower_bound(e.from, e.to, isov); + ringvertex = found.crd; + } + }); + } +}; + +template +std::vector execute_with_policy(ExecutionPolicy && policy, + const Raster & raster, + TRasterValue isoval, + Coord windowsize = {}) +{ + if (!rows(raster) || !cols(raster)) return {}; + + size_t ratio = cols(raster) / rows(raster); + + if (!windowsize.r) windowsize.r = 2; + if (!windowsize.c) + windowsize.c = std::max(size_t(2), windowsize.r * ratio); + + Coord overlap{1}; + + Grid grid{raster, windowsize, overlap}; + + grid.tag_grid(std::forward(policy), isoval); + std::vector rings = grid.scan_rings(); + grid.interpolate_rings(std::forward(policy), rings, isoval); + + return rings; +} + +template +std::vector execute(const Raster &raster, + TRasterValue isoval, + Coord windowsize = {}) +{ + return execute_with_policy(nullptr, raster, isoval, windowsize); +} + +} // namespace __impl + +using __impl::execute_with_policy; +using __impl::execute; + +} // namespace marchsq + +#endif // MARCHINGSQUARES_HPP diff --git a/src/libslic3r/SLA/AGGRaster.hpp b/src/libslic3r/SLA/AGGRaster.hpp new file mode 100644 index 000000000..37baed9e8 --- /dev/null +++ b/src/libslic3r/SLA/AGGRaster.hpp @@ -0,0 +1,222 @@ +#ifndef AGGRASTER_HPP +#define AGGRASTER_HPP + +#include +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/MTUtils.hpp" +#include + +// For rasterizing +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace Slic3r { + +inline const Polygon& contour(const ExPolygon& p) { return p.contour; } +inline const ClipperLib::Path& contour(const ClipperLib::Polygon& p) { return p.Contour; } + +inline const Polygons& holes(const ExPolygon& p) { return p.holes; } +inline const ClipperLib::Paths& holes(const ClipperLib::Polygon& p) { return p.Holes; } + +namespace sla { + +template struct Colors { + static const Color White; + static const Color Black; +}; + +template const Color Colors::White = Color{255}; +template const Color Colors::Black = Color{0}; + +template*/> class Renderer, + class Rasterizer = agg::rasterizer_scanline_aa<>, + class Scanline = agg::scanline_p8> +class AGGRaster: public RasterBase { +public: + using TColor = typename PixelRenderer::color_type; + using TValue = typename TColor::value_type; + using TPixel = typename PixelRenderer::pixel_type; + using TRawBuffer = agg::rendering_buffer; + +protected: + + Resolution m_resolution; + PixelDim m_pxdim_scaled; // used for scaled coordinate polygons + + std::vector m_buf; + agg::rendering_buffer m_rbuf; + + PixelRenderer m_pixrenderer; + + agg::renderer_base m_raw_renderer; + Renderer> m_renderer; + + Trafo m_trafo; + Scanline m_scanlines; + Rasterizer m_rasterizer; + + void flipy(agg::path_storage &path) const + { + path.flip_y(0, double(m_resolution.height_px)); + } + + void flipx(agg::path_storage &path) const + { + path.flip_x(0, double(m_resolution.width_px)); + } + + double getPx(const Point &p) { return p(0) * m_pxdim_scaled.w_mm; } + double getPy(const Point &p) { return p(1) * m_pxdim_scaled.h_mm; } + agg::path_storage to_path(const Polygon &poly) { return to_path(poly.points); } + double getPx(const ClipperLib::IntPoint &p) { return p.X * m_pxdim_scaled.w_mm; } + double getPy(const ClipperLib::IntPoint& p) { return p.Y * m_pxdim_scaled.h_mm; } + + template agg::path_storage _to_path(const PointVec& v) + { + agg::path_storage path; + + auto it = v.begin(); + path.move_to(getPx(*it), getPy(*it)); + while(++it != v.end()) path.line_to(getPx(*it), getPy(*it)); + path.line_to(getPx(v.front()), getPy(v.front())); + + return path; + } + + template agg::path_storage _to_path_flpxy(const PointVec& v) + { + agg::path_storage path; + + auto it = v.begin(); + path.move_to(getPy(*it), getPx(*it)); + while(++it != v.end()) path.line_to(getPy(*it), getPx(*it)); + path.line_to(getPy(v.front()), getPx(v.front())); + + return path; + } + + template agg::path_storage to_path(const PointVec &v) + { + auto path = m_trafo.flipXY ? _to_path_flpxy(v) : _to_path(v); + + path.translate_all_paths(m_trafo.center_x * m_pxdim_scaled.w_mm, + m_trafo.center_y * m_pxdim_scaled.h_mm); + + if(m_trafo.mirror_x) flipx(path); + if(m_trafo.mirror_y) flipy(path); + + return path; + } + + template void _draw(const P &poly) + { + m_rasterizer.reset(); + + m_rasterizer.add_path(to_path(contour(poly))); + for(auto& h : holes(poly)) m_rasterizer.add_path(to_path(h)); + + agg::render_scanlines(m_rasterizer, m_scanlines, m_renderer); + } + +public: + template AGGRaster(const Resolution &res, + const PixelDim & pd, + const Trafo & trafo, + const TColor & foreground, + const TColor & background, + GammaFn && gammafn) + : m_resolution(res) + , m_pxdim_scaled(SCALING_FACTOR / pd.w_mm, SCALING_FACTOR / pd.h_mm) + , m_buf(res.pixels()) + , m_rbuf(reinterpret_cast(m_buf.data()), + unsigned(res.width_px), + unsigned(res.height_px), + int(res.width_px *PixelRenderer::num_components)) + , m_pixrenderer(m_rbuf) + , m_raw_renderer(m_pixrenderer) + , m_renderer(m_raw_renderer) + , m_trafo(trafo) + { + m_renderer.color(foreground); + clear(background); + + m_rasterizer.gamma(gammafn); + } + + Trafo trafo() const override { return m_trafo; } + Resolution resolution() const override { return m_resolution; } + PixelDim pixel_dimensions() const override + { + return {SCALING_FACTOR / m_pxdim_scaled.w_mm, + SCALING_FACTOR / m_pxdim_scaled.h_mm}; + } + + void draw(const ExPolygon &poly) override { _draw(poly); } + void draw(const ClipperLib::Polygon &poly) override { _draw(poly); } + + EncodedRaster encode(RasterEncoder encoder) const override + { + return encoder(m_buf.data(), m_resolution.width_px, m_resolution.height_px, 1); + } + + void clear(const TColor color) { m_raw_renderer.clear(color); } +}; + +/* + * Captures an anti-aliased monochrome canvas where vectorial + * polygons can be rasterized. Fill color is always white and the background is + * black. Contours are anti-aliased. + * + * A gamma function can be specified at compile time to make it more flexible. + */ +using _RasterGrayscaleAA = + AGGRaster; + +class RasterGrayscaleAA : public _RasterGrayscaleAA { + using Base = _RasterGrayscaleAA; + using typename Base::TColor; + using typename Base::TValue; +public: + template + RasterGrayscaleAA(const RasterBase::Resolution &res, + const RasterBase::PixelDim & pd, + const RasterBase::Trafo & trafo, + GammaFn && fn) + : Base(res, pd, trafo, Colors::White, Colors::Black, + std::forward(fn)) + {} + + uint8_t read_pixel(size_t col, size_t row) const + { + static_assert(std::is_same::value, "Not grayscale pix"); + + uint8_t px; + Base::m_buf[row * Base::resolution().width_px + col].get(px); + return px; + } + + void clear() { Base::clear(Colors::Black); } +}; + +class RasterGrayscaleAAGammaPower: public RasterGrayscaleAA { +public: + RasterGrayscaleAAGammaPower(const RasterBase::Resolution &res, + const RasterBase::PixelDim & pd, + const RasterBase::Trafo & trafo, + double gamma = 1.) + : RasterGrayscaleAA(res, pd, trafo, agg::gamma_power(gamma)) + {} +}; + +}} // namespace Slic3r::sla + +#endif // AGGRASTER_HPP diff --git a/src/libslic3r/SLA/Pad.cpp b/src/libslic3r/SLA/Pad.cpp index cf1786758..d933ef5ed 100644 --- a/src/libslic3r/SLA/Pad.cpp +++ b/src/libslic3r/SLA/Pad.cpp @@ -11,6 +11,8 @@ #include "Tesselate.hpp" #include "MTUtils.hpp" +#include "TriangulateWall.hpp" + // For debugging: // #include // #include @@ -27,186 +29,27 @@ namespace Slic3r { namespace sla { namespace { -/// This function will return a triangulation of a sheet connecting an upper -/// and a lower plate given as input polygons. It will not triangulate the -/// plates themselves only the sheet. The caller has to specify the lower and -/// upper z levels in world coordinates as well as the offset difference -/// between the sheets. If the lower_z_mm is higher than upper_z_mm or the -/// offset difference is negative, the resulting triangle orientation will be -/// reversed. -/// -/// IMPORTANT: This is not a universal triangulation algorithm. It assumes -/// that the lower and upper polygons are offsetted versions of the same -/// original polygon. In general, it assumes that one of the polygons is -/// completely inside the other. The offset difference is the reference -/// distance from the inner polygon's perimeter to the outer polygon's -/// perimeter. The real distance will be variable as the clipper offset has -/// different strategies (rounding, etc...). This algorithm should have -/// O(2n + 3m) complexity where n is the number of upper vertices and m is the -/// number of lower vertices. Contour3D walls( const Polygon &lower, const Polygon &upper, double lower_z_mm, - double upper_z_mm, - double offset_difference_mm, - ThrowOnCancel thr = [] {}) + double upper_z_mm) { + Wall w = triangulate_wall(lower, upper, lower_z_mm, upper_z_mm); + Contour3D ret; - - if(upper.points.size() < 3 || lower.size() < 3) return ret; - - // The concept of the algorithm is relatively simple. It will try to find - // the closest vertices from the upper and the lower polygon and use those - // as starting points. Then it will create the triangles sequentially using - // an edge from the upper polygon and a vertex from the lower or vice versa, - // depending on the resulting triangle's quality. - // The quality is measured by a scalar value. So far it looks like it is - // enough to derive it from the slope of the triangle's two edges connecting - // the upper and the lower part. A reference slope is calculated from the - // height and the offset difference. - - // Offset in the index array for the ceiling - const auto offs = upper.points.size(); - - // Shorthand for the vertex arrays - auto& upts = upper.points, &lpts = lower.points; - auto& rpts = ret.points; auto& ind = ret.faces3; - - // If the Z levels are flipped, or the offset difference is negative, we - // will interpret that as the triangles normals should be inverted. - bool inverted = upper_z_mm < lower_z_mm || offset_difference_mm < 0; - - // Copy the points into the mesh, convert them from 2D to 3D - rpts.reserve(upts.size() + lpts.size()); - ind.reserve(2 * upts.size() + 2 * lpts.size()); - for (auto &p : upts) - rpts.emplace_back(unscaled(p.x()), unscaled(p.y()), upper_z_mm); - for (auto &p : lpts) - rpts.emplace_back(unscaled(p.x()), unscaled(p.y()), lower_z_mm); - - // Create pointing indices into vertex arrays. u-upper, l-lower - size_t uidx = 0, lidx = offs, unextidx = 1, lnextidx = offs + 1; - - // Simple squared distance calculation. - auto distfn = [](const Vec3d& p1, const Vec3d& p2) { - auto p = p1 - p2; return p.transpose() * p; - }; - - // We need to find the closest point on lower polygon to the first point on - // the upper polygon. These will be our starting points. - double distmin = std::numeric_limits::max(); - for(size_t l = lidx; l < rpts.size(); ++l) { - thr(); - double d = distfn(rpts[l], rpts[uidx]); - if(d < distmin) { lidx = l; distmin = d; } - } - - // Set up lnextidx to be ahead of lidx in cyclic mode - lnextidx = lidx + 1; - if(lnextidx == rpts.size()) lnextidx = offs; - - // This will be the flip switch to toggle between upper and lower triangle - // creation mode - enum class Proceed { - UPPER, // A segment from the upper polygon and one vertex from the lower - LOWER // A segment from the lower polygon and one vertex from the upper - } proceed = Proceed::UPPER; - - // Flags to help evaluating loop termination. - bool ustarted = false, lstarted = false; - - // The variables for the fitness values, one for the actual and one for the - // previous. - double current_fit = 0, prev_fit = 0; - - // Every triangle of the wall has two edges connecting the upper plate with - // the lower plate. From the length of these two edges and the zdiff we - // can calculate the momentary squared offset distance at a particular - // position on the wall. The average of the differences from the reference - // (squared) offset distance will give us the driving fitness value. - const double offsdiff2 = std::pow(offset_difference_mm, 2); - const double zdiff2 = std::pow(upper_z_mm - lower_z_mm, 2); - - // Mark the current vertex iterator positions. If the iterators return to - // the same position, the loop can be terminated. - size_t uendidx = uidx, lendidx = lidx; - - do { thr(); // check throw if canceled - - prev_fit = current_fit; - - switch(proceed) { // proceed depending on the current state - case Proceed::UPPER: - if(!ustarted || uidx != uendidx) { // there are vertices remaining - // Get the 3D vertices in order - const Vec3d& p_up1 = rpts[uidx]; - const Vec3d& p_low = rpts[lidx]; - const Vec3d& p_up2 = rpts[unextidx]; - - // Calculate fitness: the average of the two connecting edges - double a = offsdiff2 - (distfn(p_up1, p_low) - zdiff2); - double b = offsdiff2 - (distfn(p_up2, p_low) - zdiff2); - current_fit = (std::abs(a) + std::abs(b)) / 2; - - if(current_fit > prev_fit) { // fit is worse than previously - proceed = Proceed::LOWER; - } else { // good to go, create the triangle - inverted - ? ind.emplace_back(int(unextidx), int(lidx), int(uidx)) - : ind.emplace_back(int(uidx), int(lidx), int(unextidx)); - - // Increment the iterators, rotate if necessary - ++uidx; ++unextidx; - if(unextidx == offs) unextidx = 0; - if(uidx == offs) uidx = 0; - - ustarted = true; // mark the movement of the iterators - // so that the comparison to uendidx can be made correctly - } - } else proceed = Proceed::LOWER; - - break; - case Proceed::LOWER: - // Mode with lower segment, upper vertex. Same structure: - if(!lstarted || lidx != lendidx) { - const Vec3d& p_low1 = rpts[lidx]; - const Vec3d& p_low2 = rpts[lnextidx]; - const Vec3d& p_up = rpts[uidx]; - - double a = offsdiff2 - (distfn(p_up, p_low1) - zdiff2); - double b = offsdiff2 - (distfn(p_up, p_low2) - zdiff2); - current_fit = (std::abs(a) + std::abs(b)) / 2; - - if(current_fit > prev_fit) { - proceed = Proceed::UPPER; - } else { - inverted - ? ind.emplace_back(int(uidx), int(lnextidx), int(lidx)) - : ind.emplace_back(int(lidx), int(lnextidx), int(uidx)); - - ++lidx; ++lnextidx; - if(lnextidx == rpts.size()) lnextidx = offs; - if(lidx == rpts.size()) lidx = offs; - - lstarted = true; - } - } else proceed = Proceed::UPPER; - - break; - } // end of switch - } while(!ustarted || !lstarted || uidx != uendidx || lidx != lendidx); - + ret.points = std::move(w.first); + ret.faces3 = std::move(w.second); + return ret; } // Same as walls() but with identical higher and lower polygons. Contour3D inline straight_walls(const Polygon &plate, double lo_z, - double hi_z, - ThrowOnCancel thr) + double hi_z) { - return walls(plate, plate, lo_z, hi_z, .0 /*offset_diff*/, thr); + return walls(plate, plate, lo_z, hi_z); } // Function to cut tiny connector cavities for a given polygon. The input poly @@ -534,10 +377,8 @@ bool add_cavity(Contour3D &pad, ExPolygon &top_poly, const PadConfig3D &cfg, top_poly = pdiff.front(); double z_min = -cfg.wing_height, z_max = 0; - double offset_difference = -wing_distance; - pad.merge(walls(inner_base.contour, middle_base.contour, z_min, z_max, - offset_difference, thr)); - + pad.merge(walls(inner_base.contour, middle_base.contour, z_min, z_max)); + thr(); pad.merge(triangulate_expolygon_3d(inner_base, z_min, NORMALS_UP)); return true; @@ -555,17 +396,17 @@ Contour3D create_outer_pad_geometry(const ExPolygons & skeleton, offset_contour_only(pad_part, -scaled(cfg.bottom_offset())); if (bottom_poly.empty()) continue; - + thr(); + double z_min = -cfg.height, z_max = 0; - ret.merge(walls(top_poly.contour, bottom_poly.contour, z_max, z_min, - cfg.bottom_offset(), thr)); + ret.merge(walls(top_poly.contour, bottom_poly.contour, z_max, z_min)); if (cfg.wing_height > 0. && add_cavity(ret, top_poly, cfg, thr)) z_max = -cfg.wing_height; for (auto &h : bottom_poly.holes) - ret.merge(straight_walls(h, z_max, z_min, thr)); - + ret.merge(straight_walls(h, z_max, z_min)); + ret.merge(triangulate_expolygon_3d(bottom_poly, z_min, NORMALS_DOWN)); ret.merge(triangulate_expolygon_3d(top_poly, NORMALS_UP)); } @@ -581,11 +422,12 @@ Contour3D create_inner_pad_geometry(const ExPolygons & skeleton, double z_max = 0., z_min = -cfg.height; for (const ExPolygon &pad_part : skeleton) { - ret.merge(straight_walls(pad_part.contour, z_max, z_min,thr)); + thr(); + ret.merge(straight_walls(pad_part.contour, z_max, z_min)); for (auto &h : pad_part.holes) - ret.merge(straight_walls(h, z_max, z_min, thr)); - + ret.merge(straight_walls(h, z_max, z_min)); + ret.merge(triangulate_expolygon_3d(pad_part, z_min, NORMALS_DOWN)); ret.merge(triangulate_expolygon_3d(pad_part, z_max, NORMALS_UP)); } diff --git a/src/libslic3r/SLA/Raster.cpp b/src/libslic3r/SLA/Raster.cpp deleted file mode 100644 index 9b7f1db7d..000000000 --- a/src/libslic3r/SLA/Raster.cpp +++ /dev/null @@ -1,320 +0,0 @@ -#ifndef SLARASTER_CPP -#define SLARASTER_CPP - -#include - -#include -#include "libslic3r/ExPolygon.hpp" -#include "libslic3r/MTUtils.hpp" -#include - -// For rasterizing -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -// Experimental minz image write: -#include - -namespace Slic3r { - -inline const Polygon& contour(const ExPolygon& p) { return p.contour; } -inline const ClipperLib::Path& contour(const ClipperLib::Polygon& p) { return p.Contour; } - -inline const Polygons& holes(const ExPolygon& p) { return p.holes; } -inline const ClipperLib::Paths& holes(const ClipperLib::Polygon& p) { return p.Holes; } - -namespace sla { - -const Raster::TMirroring Raster::NoMirror = {false, false}; -const Raster::TMirroring Raster::MirrorX = {true, false}; -const Raster::TMirroring Raster::MirrorY = {false, true}; -const Raster::TMirroring Raster::MirrorXY = {true, true}; - - -using TPixelRenderer = agg::pixfmt_gray8; // agg::pixfmt_rgb24; -using TRawRenderer = agg::renderer_base; -using TPixel = TPixelRenderer::color_type; -using TRawBuffer = agg::rendering_buffer; -using TBuffer = std::vector; - -using TRendererAA = agg::renderer_scanline_aa_solid; - -class Raster::Impl { -public: - - static const TPixel ColorWhite; - static const TPixel ColorBlack; - - using Format = Raster::RawData; - -private: - Raster::Resolution m_resolution; - Raster::PixelDim m_pxdim_scaled; // used for scaled coordinate polygons - TBuffer m_buf; - TRawBuffer m_rbuf; - TPixelRenderer m_pixfmt; - TRawRenderer m_raw_renderer; - TRendererAA m_renderer; - - std::function m_gammafn; - Trafo m_trafo; - - inline void flipy(agg::path_storage& path) const { - path.flip_y(0, double(m_resolution.height_px)); - } - - inline void flipx(agg::path_storage& path) const { - path.flip_x(0, double(m_resolution.width_px)); - } - -public: - inline Impl(const Raster::Resolution & res, - const Raster::PixelDim & pd, - const Trafo &trafo) - : m_resolution(res) - , m_pxdim_scaled(SCALING_FACTOR / pd.w_mm, SCALING_FACTOR / pd.h_mm) - , m_buf(res.pixels()) - , m_rbuf(reinterpret_cast(m_buf.data()), - unsigned(res.width_px), - unsigned(res.height_px), - int(res.width_px * TPixelRenderer::num_components)) - , m_pixfmt(m_rbuf) - , m_raw_renderer(m_pixfmt) - , m_renderer(m_raw_renderer) - , m_trafo(trafo) - { - m_renderer.color(ColorWhite); - - if (trafo.gamma > 0) m_gammafn = agg::gamma_power(trafo.gamma); - else m_gammafn = agg::gamma_threshold(0.5); - - clear(); - } - - template void draw(const P &poly) { - agg::rasterizer_scanline_aa<> ras; - agg::scanline_p8 scanlines; - - ras.gamma(m_gammafn); - - ras.add_path(to_path(contour(poly))); - for(auto& h : holes(poly)) ras.add_path(to_path(h)); - - agg::render_scanlines(ras, scanlines, m_renderer); - } - - inline void clear() { - m_raw_renderer.clear(ColorBlack); - } - - inline TBuffer& buffer() { return m_buf; } - inline const TBuffer& buffer() const { return m_buf; } - - - inline const Raster::Resolution resolution() { return m_resolution; } - inline const Raster::PixelDim pixdim() - { - return {SCALING_FACTOR / m_pxdim_scaled.w_mm, - SCALING_FACTOR / m_pxdim_scaled.h_mm}; - } - -private: - inline double getPx(const Point& p) { - return p(0) * m_pxdim_scaled.w_mm; - } - - inline double getPy(const Point& p) { - return p(1) * m_pxdim_scaled.h_mm; - } - - inline agg::path_storage to_path(const Polygon& poly) - { - return to_path(poly.points); - } - - inline double getPx(const ClipperLib::IntPoint& p) { - return p.X * m_pxdim_scaled.w_mm; - } - - inline double getPy(const ClipperLib::IntPoint& p) { - return p.Y * m_pxdim_scaled.h_mm; - } - - template agg::path_storage _to_path(const PointVec& v) - { - agg::path_storage path; - - auto it = v.begin(); - path.move_to(getPx(*it), getPy(*it)); - while(++it != v.end()) path.line_to(getPx(*it), getPy(*it)); - path.line_to(getPx(v.front()), getPy(v.front())); - - return path; - } - - template agg::path_storage _to_path_flpxy(const PointVec& v) - { - agg::path_storage path; - - auto it = v.begin(); - path.move_to(getPy(*it), getPx(*it)); - while(++it != v.end()) path.line_to(getPy(*it), getPx(*it)); - path.line_to(getPy(v.front()), getPx(v.front())); - - return path; - } - - template agg::path_storage to_path(const PointVec &v) - { - auto path = m_trafo.flipXY ? _to_path_flpxy(v) : _to_path(v); - - path.translate_all_paths(m_trafo.origin_x * m_pxdim_scaled.w_mm, - m_trafo.origin_y * m_pxdim_scaled.h_mm); - - if(m_trafo.mirror_x) flipx(path); - if(m_trafo.mirror_y) flipy(path); - - return path; - } - -}; - -const TPixel Raster::Impl::ColorWhite = TPixel(255); -const TPixel Raster::Impl::ColorBlack = TPixel(0); - -Raster::Raster() { reset(); } - -Raster::Raster(const Raster::Resolution &r, - const Raster::PixelDim & pd, - const Raster::Trafo & tr) -{ - reset(r, pd, tr); -} - -Raster::~Raster() = default; - -Raster::Raster(Raster &&m) = default; -Raster &Raster::operator=(Raster &&) = default; - -void Raster::reset(const Raster::Resolution &r, const Raster::PixelDim &pd, - const Trafo &trafo) -{ - m_impl.reset(); - m_impl.reset(new Impl(r, pd, trafo)); -} - -void Raster::reset() -{ - m_impl.reset(); -} - -Raster::Resolution Raster::resolution() const -{ - if (m_impl) return m_impl->resolution(); - - return Resolution{0, 0}; -} - -Raster::PixelDim Raster::pixel_dimensions() const -{ - if (m_impl) return m_impl->pixdim(); - - return PixelDim{0., 0.}; -} - -void Raster::clear() -{ - assert(m_impl); - m_impl->clear(); -} - -void Raster::draw(const ExPolygon &expoly) -{ - assert(m_impl); - m_impl->draw(expoly); -} - -void Raster::draw(const ClipperLib::Polygon &poly) -{ - assert(m_impl); - m_impl->draw(poly); -} - -uint8_t Raster::read_pixel(size_t x, size_t y) const -{ - assert (m_impl); - TPixel::value_type px; - m_impl->buffer()[y * resolution().width_px + x].get(px); - return px; -} - -PNGImage & PNGImage::serialize(const Raster &raster) -{ - size_t s = 0; - m_buffer.clear(); - - void *rawdata = tdefl_write_image_to_png_file_in_memory( - get_internals(raster).buffer().data(), - int(raster.resolution().width_px), - int(raster.resolution().height_px), 1, &s); - - // On error, data() will return an empty vector. No other info can be - // retrieved from miniz anyway... - if (rawdata == nullptr) return *this; - - auto ptr = static_cast(rawdata); - - m_buffer.reserve(s); - std::copy(ptr, ptr + s, std::back_inserter(m_buffer)); - - MZ_FREE(rawdata); - return *this; -} - -std::ostream &operator<<(std::ostream &stream, const Raster::RawData &bytes) -{ - stream.write(reinterpret_cast(bytes.data()), - std::streamsize(bytes.size())); - - return stream; -} - -Raster::RawData::~RawData() = default; - -PPMImage & PPMImage::serialize(const Raster &raster) -{ - auto header = std::string("P5 ") + - std::to_string(raster.resolution().width_px) + " " + - std::to_string(raster.resolution().height_px) + " " + "255 "; - - const auto &impl = get_internals(raster); - auto sz = impl.buffer().size() * sizeof(TBuffer::value_type); - size_t s = sz + header.size(); - - m_buffer.clear(); - m_buffer.reserve(s); - - auto buff = reinterpret_cast(impl.buffer().data()); - std::copy(header.begin(), header.end(), std::back_inserter(m_buffer)); - std::copy(buff, buff+sz, std::back_inserter(m_buffer)); - - return *this; -} - -const Raster::Impl &Raster::RawData::get_internals(const Raster &raster) -{ - return *raster.m_impl; -} - -} // namespace sla -} // namespace Slic3r - -#endif // SLARASTER_CPP diff --git a/src/libslic3r/SLA/Raster.hpp b/src/libslic3r/SLA/Raster.hpp deleted file mode 100644 index 4d76b3290..000000000 --- a/src/libslic3r/SLA/Raster.hpp +++ /dev/null @@ -1,157 +0,0 @@ -#ifndef SLA_RASTER_HPP -#define SLA_RASTER_HPP - -#include -#include -#include -#include -#include -#include - -#include - -namespace ClipperLib { struct Polygon; } - -namespace Slic3r { -namespace sla { - -/** - * @brief Raster captures an anti-aliased monochrome canvas where vectorial - * polygons can be rasterized. Fill color is always white and the background is - * black. Contours are anti-aliased. - * - * It also supports saving the raster data into a standard output stream in raw - * or PNG format. - */ -class Raster { - class Impl; - std::unique_ptr m_impl; -public: - - // Raw byte buffer paired with its size. Suitable for compressed image data. - class RawData - { - protected: - std::vector m_buffer; - const Impl& get_internals(const Raster& raster); - public: - RawData() = default; - RawData(std::vector&& data): m_buffer(std::move(data)) {} - virtual ~RawData(); - - RawData(const RawData &) = delete; - RawData &operator=(const RawData &) = delete; - - RawData(RawData &&) = default; - RawData &operator=(RawData &&) = default; - - size_t size() const { return m_buffer.size(); } - const uint8_t * data() const { return m_buffer.data(); } - - virtual RawData& serialize(const Raster &/*raster*/) { return *this; } - virtual std::string get_file_extension() const = 0; - }; - - /// Type that represents a resolution in pixels. - struct Resolution { - size_t width_px; - size_t height_px; - - inline Resolution(size_t w = 0, size_t h = 0) - : width_px(w), height_px(h) - {} - - inline size_t pixels() const { return width_px * height_px; } - }; - - /// Types that represents the dimension of a pixel in millimeters. - struct PixelDim { - double w_mm; - double h_mm; - inline PixelDim(double px_width_mm = 0.0, double px_height_mm = 0.0): - w_mm(px_width_mm), h_mm(px_height_mm) {} - }; - - enum Orientation { roLandscape, roPortrait }; - - using TMirroring = std::array; - static const TMirroring NoMirror; - static const TMirroring MirrorX; - static const TMirroring MirrorY; - static const TMirroring MirrorXY; - - struct Trafo { - bool mirror_x = false, mirror_y = false, flipXY = false; - coord_t origin_x = 0, origin_y = 0; - - // If gamma is zero, thresholding will be performed which disables AA. - double gamma = 1.; - - // Portrait orientation will make sure the drawed polygons are rotated - // by 90 degrees. - Trafo(Orientation o = roLandscape, const TMirroring &mirror = NoMirror) - // XY flipping implicitly does an X mirror - : mirror_x(o == roPortrait ? !mirror[0] : mirror[0]) - , mirror_y(!mirror[1]) // Makes raster origin to be top left corner - , flipXY(o == roPortrait) - {} - }; - - Raster(); - Raster(const Resolution &r, - const PixelDim & pd, - const Trafo & tr = {}); - - Raster(const Raster& cpy) = delete; - Raster& operator=(const Raster& cpy) = delete; - Raster(Raster&& m); - Raster& operator=(Raster&&); - ~Raster(); - - /// Reallocated everything for the given resolution and pixel dimension. - void reset(const Resolution& r, - const PixelDim& pd, - const Trafo &tr = {}); - - /** - * Release the allocated resources. Drawing in this state ends in - * unspecified behavior. - */ - void reset(); - - /// Get the resolution of the raster. - Resolution resolution() const; - PixelDim pixel_dimensions() const; - - /// Clear the raster with black color. - void clear(); - - /// Draw a polygon with holes. - void draw(const ExPolygon& poly); - void draw(const ClipperLib::Polygon& poly); - - uint8_t read_pixel(size_t w, size_t h) const; - - inline bool empty() const { return ! bool(m_impl); } - -}; - -class PNGImage: public Raster::RawData { -public: - PNGImage& serialize(const Raster &raster) override; - std::string get_file_extension() const override { return "png"; } -}; - -class PPMImage: public Raster::RawData { -public: - PPMImage& serialize(const Raster &raster) override; - std::string get_file_extension() const override { return "ppm"; } -}; - -std::ostream& operator<<(std::ostream &stream, const Raster::RawData &bytes); - -} // sla -} // Slic3r - - -#endif // SLARASTER_HPP diff --git a/src/libslic3r/SLA/RasterBase.cpp b/src/libslic3r/SLA/RasterBase.cpp new file mode 100644 index 000000000..581e84880 --- /dev/null +++ b/src/libslic3r/SLA/RasterBase.cpp @@ -0,0 +1,89 @@ +#ifndef SLARASTER_CPP +#define SLARASTER_CPP + +#include + +#include +#include + +// minz image write: +#include + +namespace Slic3r { namespace sla { + +const RasterBase::TMirroring RasterBase::NoMirror = {false, false}; +const RasterBase::TMirroring RasterBase::MirrorX = {true, false}; +const RasterBase::TMirroring RasterBase::MirrorY = {false, true}; +const RasterBase::TMirroring RasterBase::MirrorXY = {true, true}; + +EncodedRaster PNGRasterEncoder::operator()(const void *ptr, size_t w, size_t h, + size_t num_components) +{ + std::vector buf; + size_t s = 0; + + void *rawdata = tdefl_write_image_to_png_file_in_memory( + ptr, int(w), int(h), int(num_components), &s); + + // On error, data() will return an empty vector. No other info can be + // retrieved from miniz anyway... + if (rawdata == nullptr) return EncodedRaster({}, "png"); + + auto pptr = static_cast(rawdata); + + buf.reserve(s); + std::copy(pptr, pptr + s, std::back_inserter(buf)); + + MZ_FREE(rawdata); + return EncodedRaster(std::move(buf), "png"); +} + +std::ostream &operator<<(std::ostream &stream, const EncodedRaster &bytes) +{ + stream.write(reinterpret_cast(bytes.data()), + std::streamsize(bytes.size())); + + return stream; +} + +EncodedRaster PPMRasterEncoder::operator()(const void *ptr, size_t w, size_t h, + size_t num_components) +{ + std::vector buf; + + auto header = std::string("P5 ") + + std::to_string(w) + " " + + std::to_string(h) + " " + "255 "; + + auto sz = w * h * num_components; + size_t s = sz + header.size(); + + buf.reserve(s); + + auto buff = reinterpret_cast(ptr); + std::copy(header.begin(), header.end(), std::back_inserter(buf)); + std::copy(buff, buff+sz, std::back_inserter(buf)); + + return EncodedRaster(std::move(buf), "ppm"); +} + +std::unique_ptr create_raster_grayscale_aa( + const RasterBase::Resolution &res, + const RasterBase::PixelDim & pxdim, + double gamma, + const RasterBase::Trafo & tr) +{ + std::unique_ptr rst; + + if (gamma > 0) + rst = std::make_unique(res, pxdim, tr, gamma); + else + rst = std::make_unique(res, pxdim, tr, agg::gamma_threshold(.5)); + + return rst; +} + +} // namespace sla +} // namespace Slic3r + +#endif // SLARASTER_CPP diff --git a/src/libslic3r/SLA/RasterBase.hpp b/src/libslic3r/SLA/RasterBase.hpp new file mode 100644 index 000000000..431c731a6 --- /dev/null +++ b/src/libslic3r/SLA/RasterBase.hpp @@ -0,0 +1,124 @@ +#ifndef SLA_RASTERBASE_HPP +#define SLA_RASTERBASE_HPP + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace ClipperLib { struct Polygon; } + +namespace Slic3r { + +template using uqptr = std::unique_ptr; +template using shptr = std::shared_ptr; +template using wkptr = std::weak_ptr; + +namespace sla { + +// Raw byte buffer paired with its size. Suitable for compressed image data. +class EncodedRaster { +protected: + std::vector m_buffer; + std::string m_ext; +public: + EncodedRaster() = default; + explicit EncodedRaster(std::vector &&buf, std::string ext) + : m_buffer(std::move(buf)), m_ext(std::move(ext)) + {} + + size_t size() const { return m_buffer.size(); } + const void * data() const { return m_buffer.data(); } + const char * extension() const { return m_ext.c_str(); } +}; + +using RasterEncoder = + std::function; + +class RasterBase { +public: + + enum Orientation { roLandscape, roPortrait }; + + using TMirroring = std::array; + static const TMirroring NoMirror; + static const TMirroring MirrorX; + static const TMirroring MirrorY; + static const TMirroring MirrorXY; + + struct Trafo { + bool mirror_x = false, mirror_y = false, flipXY = false; + coord_t center_x = 0, center_y = 0; + + // Portrait orientation will make sure the drawed polygons are rotated + // by 90 degrees. + Trafo(Orientation o = roLandscape, const TMirroring &mirror = NoMirror) + // XY flipping implicitly does an X mirror + : mirror_x(o == roPortrait ? !mirror[0] : mirror[0]) + , mirror_y(!mirror[1]) // Makes raster origin to be top left corner + , flipXY(o == roPortrait) + {} + + TMirroring get_mirror() const { return { (roPortrait ? !mirror_x : mirror_x), mirror_y}; } + Orientation get_orientation() const { return flipXY ? roPortrait : roLandscape; } + Point get_center() const { return {center_x, center_y}; } + }; + + /// Type that represents a resolution in pixels. + struct Resolution { + size_t width_px = 0; + size_t height_px = 0; + + Resolution(size_t w = 0, size_t h = 0) : width_px(w), height_px(h) {} + size_t pixels() const { return width_px * height_px; } + }; + + /// Types that represents the dimension of a pixel in millimeters. + struct PixelDim { + double w_mm = 0.; + double h_mm = 0.; + + PixelDim(double px_width_mm = 0.0, double px_height_mm = 0.0) + : w_mm(px_width_mm), h_mm(px_height_mm) + {} + }; + + virtual ~RasterBase() = default; + + /// Draw a polygon with holes. + virtual void draw(const ExPolygon& poly) = 0; + virtual void draw(const ClipperLib::Polygon& poly) = 0; + + /// Get the resolution of the raster. + virtual Resolution resolution() const = 0; + virtual PixelDim pixel_dimensions() const = 0; + virtual Trafo trafo() const = 0; + + virtual EncodedRaster encode(RasterEncoder encoder) const = 0; +}; + +struct PNGRasterEncoder { + EncodedRaster operator()(const void *ptr, size_t w, size_t h, size_t num_components); +}; + +struct PPMRasterEncoder { + EncodedRaster operator()(const void *ptr, size_t w, size_t h, size_t num_components); +}; + +std::ostream& operator<<(std::ostream &stream, const EncodedRaster &bytes); + +// If gamma is zero, thresholding will be performed which disables AA. +uqptr create_raster_grayscale_aa( + const RasterBase::Resolution &res, + const RasterBase::PixelDim & pxdim, + double gamma = 1.0, + const RasterBase::Trafo & tr = {}); + +}} // namespace Slic3r::sla + +#endif // SLARASTERBASE_HPP diff --git a/src/libslic3r/SLA/RasterToPolygons.cpp b/src/libslic3r/SLA/RasterToPolygons.cpp new file mode 100644 index 000000000..6b1d8992c --- /dev/null +++ b/src/libslic3r/SLA/RasterToPolygons.cpp @@ -0,0 +1,88 @@ +#include "RasterToPolygons.hpp" + +#include "AGGRaster.hpp" +#include "libslic3r/MarchingSquares.hpp" +#include "MTUtils.hpp" +#include "ClipperUtils.hpp" + +namespace marchsq { + +// Specialize this struct to register a raster type for the Marching squares alg +template<> struct _RasterTraits { + using Rst = Slic3r::sla::RasterGrayscaleAA; + + // The type of pixel cell in the raster + using ValueType = uint8_t; + + // Value at a given position + static uint8_t get(const Rst &rst, size_t row, size_t col) { return rst.read_pixel(col, row); } + + // Number of rows and cols of the raster + static size_t rows(const Rst &rst) { return rst.resolution().height_px; } + static size_t cols(const Rst &rst) { return rst.resolution().width_px; } +}; + +} // namespace Slic3r::marchsq + +namespace Slic3r { namespace sla { + +template void foreach_vertex(ExPolygon &poly, Fn &&fn) +{ + for (auto &p : poly.contour.points) fn(p); + for (auto &h : poly.holes) + for (auto &p : h.points) fn(p); +} + +ExPolygons raster_to_polygons(const RasterGrayscaleAA &rst, float accuracy) +{ + size_t rows = rst.resolution().height_px, cols = rst.resolution().width_px; + + if (rows < 2 || cols < 2) return {}; + + Polygons polys; + size_t w_rows = (2 + rows / 8) - size_t(accuracy * rows / 8); + size_t w_cols = std::max(size_t(2), w_rows * cols / rows); + + std::vector rings = + marchsq::execute(rst, 128, {w_rows, w_cols}); + + polys.reserve(rings.size()); + + auto pxd = rst.pixel_dimensions(); + for (const marchsq::Ring &ring : rings) { + Polygon poly; Points &pts = poly.points; + pts.reserve(ring.size()); + + for (const marchsq::Coord &crd : ring) + pts.emplace_back(scaled(crd.c * pxd.w_mm), scaled(crd.r * pxd.h_mm)); + + polys.emplace_back(poly); + } + + // reverse the raster transformations + ExPolygons unioned = union_ex(polys); + coord_t width = scaled(cols * pxd.h_mm), height = scaled(rows * pxd.w_mm); + + auto tr = rst.trafo(); + for (ExPolygon &expoly : unioned) { + if (tr.mirror_y) + foreach_vertex(expoly, [height](Point &p) {p.y() = height - p.y(); }); + + if (tr.mirror_x) + foreach_vertex(expoly, [width](Point &p) {p.x() = width - p.x(); }); + + expoly.translate(-tr.center_x, -tr.center_y); + + if (tr.flipXY) + foreach_vertex(expoly, [](Point &p) { std::swap(p.x(), p.y()); }); + + if ((tr.mirror_x + tr.mirror_y + tr.flipXY) % 2) { + expoly.contour.reverse(); + for (auto &h : expoly.holes) h.reverse(); + } + } + + return unioned; +} + +}} // namespace Slic3r diff --git a/src/libslic3r/SLA/RasterToPolygons.hpp b/src/libslic3r/SLA/RasterToPolygons.hpp new file mode 100644 index 000000000..131fe518e --- /dev/null +++ b/src/libslic3r/SLA/RasterToPolygons.hpp @@ -0,0 +1,15 @@ +#ifndef RASTERTOPOLYGONS_HPP +#define RASTERTOPOLYGONS_HPP + +#include "libslic3r/ExPolygon.hpp" + +namespace Slic3r { +namespace sla { + +class RasterGrayscaleAA; + +ExPolygons raster_to_polygons(const RasterGrayscaleAA &rst, float accuracy = 1.f); + +}} + +#endif // RASTERTOPOLYGONS_HPP diff --git a/src/libslic3r/SLA/RasterWriter.cpp b/src/libslic3r/SLA/RasterWriter.cpp deleted file mode 100644 index 13aef7d8a..000000000 --- a/src/libslic3r/SLA/RasterWriter.cpp +++ /dev/null @@ -1,151 +0,0 @@ -#include - -#include - -#include "libslic3r/PrintConfig.hpp" -#include -#include - -#include "ExPolygon.hpp" -#include - -#include -#include - -namespace Slic3r { namespace sla { - -void RasterWriter::write_ini(const std::map &m, std::string &ini) -{ - for (auto ¶m : m) ini += param.first + " = " + param.second + "\n"; -} - -std::string RasterWriter::create_ini_content(const std::string& projectname) const -{ - std::string out("action = print\njobDir = "); - out += projectname + "\n"; - write_ini(m_config, out); - return out; -} - -RasterWriter::RasterWriter(const Raster::Resolution &res, - const Raster::PixelDim & pixdim, - const Raster::Trafo & trafo, - double gamma) - : m_res(res), m_pxdim(pixdim), m_trafo(trafo), m_gamma(gamma) -{} - -void RasterWriter::save(const std::string &fpath, const std::string &prjname) -{ - try { - Zipper zipper(fpath); // zipper with no compression - save(zipper, prjname); - zipper.finalize(); - } catch(std::exception& e) { - BOOST_LOG_TRIVIAL(error) << e.what(); - // Rethrow the exception - throw; - } -} - -void RasterWriter::save(Zipper &zipper, const std::string &prjname) -{ - try { - std::string project = - prjname.empty() ? - boost::filesystem::path(zipper.get_filename()).stem().string() : - prjname; - - zipper.add_entry("config.ini"); - - zipper << create_ini_content(project); - - zipper.add_entry("prusaslicer.ini"); - std::string prusaslicer_ini; - write_ini(m_slicer_config, prusaslicer_ini); - zipper << prusaslicer_ini; - - for(unsigned i = 0; i < m_layers_rst.size(); i++) - { - if(m_layers_rst[i].rawbytes.size() > 0) { - char lyrnum[6]; - std::sprintf(lyrnum, "%.5d", i); - auto zfilename = project + lyrnum + ".png"; - - // Add binary entry to the zipper - zipper.add_entry(zfilename, - m_layers_rst[i].rawbytes.data(), - m_layers_rst[i].rawbytes.size()); - } - } - } catch(std::exception& e) { - BOOST_LOG_TRIVIAL(error) << e.what(); - // Rethrow the exception - throw; - } -} - -namespace { - -std::string get_cfg_value(const DynamicPrintConfig &cfg, const std::string &key) -{ - std::string ret; - - if (cfg.has(key)) { - auto opt = cfg.option(key); - if (opt) ret = opt->serialize(); - } - - return ret; -} - -void append_full_config(const DynamicPrintConfig &cfg, std::map &keys) -{ - using namespace std::literals::string_view_literals; - - // Sorted list of config keys, which shall not be stored into the ini. - static constexpr auto banned_keys = { - "compatible_printers"sv, - "compatible_prints"sv, - "print_host"sv, - "printhost_apikey"sv, - "printhost_cafile"sv - }; - - assert(std::is_sorted(banned_keys.begin(), banned_keys.end())); - auto is_banned = [](const std::string &key) { - return std::binary_search(banned_keys.begin(), banned_keys.end(), key); - }; - - for (const std::string &key : cfg.keys()) - if (! is_banned(key) && ! cfg.option(key)->is_nil()) - keys[key] = cfg.opt_serialize(key); -} - -} // namespace - -void RasterWriter::set_config(const DynamicPrintConfig &cfg) -{ - m_config["layerHeight"] = get_cfg_value(cfg, "layer_height"); - m_config["expTime"] = get_cfg_value(cfg, "exposure_time"); - m_config["expTimeFirst"] = get_cfg_value(cfg, "initial_exposure_time"); - m_config["materialName"] = get_cfg_value(cfg, "sla_material_settings_id"); - m_config["printerModel"] = get_cfg_value(cfg, "printer_model"); - m_config["printerVariant"] = get_cfg_value(cfg, "printer_variant"); - m_config["printerProfile"] = get_cfg_value(cfg, "printer_settings_id"); - m_config["printProfile"] = get_cfg_value(cfg, "sla_print_settings_id"); - m_config["fileCreationTimestamp"] = Utils::utc_timestamp(); - m_config["prusaSlicerVersion"] = SLIC3R_BUILD_ID; - append_full_config(cfg, m_slicer_config); -} - -void RasterWriter::set_statistics(const PrintStatistics &stats) -{ - m_config["usedMaterial"] = std::to_string(stats.used_material); - m_config["numFade"] = std::to_string(stats.num_fade); - m_config["numSlow"] = std::to_string(stats.num_slow); - m_config["numFast"] = std::to_string(stats.num_fast); - m_config["printTime"] = std::to_string(stats.estimated_print_time_s); -} - -} // namespace sla -} // namespace Slic3r diff --git a/src/libslic3r/SLA/RasterWriter.hpp b/src/libslic3r/SLA/RasterWriter.hpp deleted file mode 100644 index 75162893d..000000000 --- a/src/libslic3r/SLA/RasterWriter.hpp +++ /dev/null @@ -1,130 +0,0 @@ -#ifndef SLA_RASTERWRITER_HPP -#define SLA_RASTERWRITER_HPP - -// For png export of the sliced model -#include -#include -#include -#include -#include -#include - -#include -#include - -namespace Slic3r { - -class DynamicPrintConfig; - -namespace sla { - -// API to write the zipped sla output layers and metadata. -// Implementation uses PNG raster output. -// Be aware that if a large number of layers are allocated, it can very well -// exhaust the available memory especially on 32 bit platform. -// This class is designed to be used in parallel mode. Layers have an ID and -// each layer can be written and compressed independently (in parallel). -// At the end when all layers where written, the save method can be used to -// write out the result into a zipped archive. -class RasterWriter -{ -public: - - // Used for addressing parameters of set_statistics() - struct PrintStatistics - { - double used_material = 0.; - double estimated_print_time_s = 0.; - size_t num_fade = 0; - size_t num_slow = 0; - size_t num_fast = 0; - }; - -private: - - // A struct to bind the raster image data and its compressed bytes together. - struct Layer { - Raster raster; - PNGImage rawbytes; - - Layer() = default; - - // The image is big, do not copy by accident - Layer(const Layer&) = delete; - Layer& operator=(const Layer&) = delete; - - Layer(Layer &&m) = default; - Layer &operator=(Layer &&) = default; - }; - - // We will save the compressed PNG data into RawBytes type buffers in - // parallel. Later we can write every layer to the disk sequentially. - std::vector m_layers_rst; - Raster::Resolution m_res; - Raster::PixelDim m_pxdim; - Raster::Trafo m_trafo; - double m_gamma; - - std::map m_config; - std::map m_slicer_config; - - static void write_ini(const std::map &m, std::string &ini); - std::string create_ini_content(const std::string& projectname) const; - -public: - - // SLARasterWriter is using Raster in custom mirroring mode - RasterWriter(const Raster::Resolution &res, - const Raster::PixelDim & pixdim, - const Raster::Trafo & trafo, - double gamma = 1.); - - RasterWriter(const RasterWriter& ) = delete; - RasterWriter& operator=(const RasterWriter&) = delete; - RasterWriter(RasterWriter&& m) = default; - RasterWriter& operator=(RasterWriter&&) = default; - - inline void layers(unsigned cnt) { if(cnt > 0) m_layers_rst.resize(cnt); } - inline unsigned layers() const { return unsigned(m_layers_rst.size()); } - - template void draw_polygon(const Poly& p, unsigned lyr) - { - assert(lyr < m_layers_rst.size()); - m_layers_rst[lyr].raster.draw(p); - } - - inline void begin_layer(unsigned lyr) { - if(m_layers_rst.size() <= lyr) m_layers_rst.resize(lyr+1); - m_layers_rst[lyr].raster.reset(m_res, m_pxdim, m_trafo); - } - - inline void begin_layer() { - m_layers_rst.emplace_back(); - m_layers_rst.front().raster.reset(m_res, m_pxdim, m_trafo); - } - - inline void finish_layer(unsigned lyr_id) { - assert(lyr_id < m_layers_rst.size()); - m_layers_rst[lyr_id].rawbytes.serialize(m_layers_rst[lyr_id].raster); - m_layers_rst[lyr_id].raster.reset(); - } - - inline void finish_layer() { - if(!m_layers_rst.empty()) { - m_layers_rst.back().rawbytes.serialize(m_layers_rst.back().raster); - m_layers_rst.back().raster.reset(); - } - } - - void save(const std::string &fpath, const std::string &prjname = ""); - void save(Zipper &zipper, const std::string &prjname = ""); - - void set_statistics(const PrintStatistics &statistics); - - void set_config(const DynamicPrintConfig &cfg); -}; - -} // namespace sla -} // namespace Slic3r - -#endif // SLARASTERWRITER_HPP diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 4ec5aae29..2402207a8 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -227,6 +227,8 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con m_material_config.apply_only(config, material_diff, true); // Handle changes to object config defaults m_default_object_config.apply_only(config, object_diff, true); + + if (m_printer) m_printer->apply(m_printer_config); struct ModelObjectStatus { enum Status { @@ -482,7 +484,6 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig con } if(m_objects.empty()) { - m_printer.reset(); m_printer_input = {}; m_print_statistics = {}; } @@ -657,6 +658,12 @@ std::string SLAPrint::validate() const return ""; } +void SLAPrint::set_printer(SLAPrinter *arch) +{ + invalidate_step(slapsRasterize); + m_printer = arch; +} + bool SLAPrint::invalidate_step(SLAPrintStep step) { bool invalidated = Inherited::invalidate_step(step); @@ -676,7 +683,7 @@ void SLAPrint::process() // Assumption: at this point the print objects should be populated only with // the model objects we have to process and the instances are also filtered - Steps printsteps{this}; + Steps printsteps(this); // We want to first process all objects... std::vector level1_obj_steps = { @@ -729,7 +736,7 @@ void SLAPrint::process() throw_if_canceled(); po->set_done(step); } - + incr = printsteps.progressrange(step); } } @@ -754,7 +761,7 @@ void SLAPrint::process() throw_if_canceled(); set_done(currentstep); } - + st += printsteps.progressrange(currentstep); } @@ -855,36 +862,6 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector mirror; - - double w = m_printer_config.display_width.getFloat(); - double h = m_printer_config.display_height.getFloat(); - auto pw = size_t(m_printer_config.display_pixels_x.getInt()); - auto ph = size_t(m_printer_config.display_pixels_y.getInt()); - - mirror[X] = m_printer_config.display_mirror_x.getBool(); - mirror[Y] = m_printer_config.display_mirror_y.getBool(); - - auto orientation = get_printer_orientation(); - if (orientation == sla::Raster::roPortrait) { - std::swap(w, h); - std::swap(pw, ph); - } - - res = sla::Raster::Resolution{pw, ph}; - pxdim = sla::Raster::PixelDim{w / pw, h / ph}; - sla::Raster::Trafo tr{orientation, mirror}; - tr.gamma = m_printer_config.gamma_correction.getFloat(); - - m_printer.reset(new sla::RasterWriter(res, pxdim, tr)); - m_printer->set_config(m_full_print_config); - return *m_printer; -} - // Returns true if an object step is done on all objects and there's at least one object. bool SLAPrint::is_step_done(SLAPrintObjectStep step) const { diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 70f773f6b..a27207565 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -3,7 +3,7 @@ #include #include "PrintBase.hpp" -#include "SLA/RasterWriter.hpp" +#include "SLA/RasterBase.hpp" #include "SLA/SupportTree.hpp" #include "Point.hpp" #include "MTUtils.hpp" @@ -369,6 +369,31 @@ struct SLAPrintStatistics } }; +class SLAPrinter { +protected: + std::vector m_layers; + + virtual uqptr create_raster() const = 0; + virtual sla::EncodedRaster encode_raster(const sla::RasterBase &rst) const = 0; + +public: + virtual ~SLAPrinter() = default; + + virtual void apply(const SLAPrinterConfig &cfg) = 0; + + // Fn have to be thread safe: void(sla::RasterBase& raster, size_t lyrid); + template void draw_layers(size_t layer_num, Fn &&drawfn) + { + m_layers.resize(layer_num); + sla::ccr::enumerate(m_layers.begin(), m_layers.end(), + [this, &drawfn](sla::EncodedRaster& enc, size_t idx) { + auto rst = create_raster(); + drawfn(*rst, idx); + enc = encode_raster(*rst); + }); + } +}; + /** * @brief This class is the high level FSM for the SLA printing process. * @@ -403,18 +428,6 @@ public: // Returns true if the last step was finished with success. bool finished() const override { return this->is_step_done(slaposSliceSupports) && this->Inherited::is_step_done(slapsRasterize); } - inline void export_raster(const std::string& fpath, - const std::string& projectname = "") - { - if(m_printer) m_printer->save(fpath, projectname); - } - - inline void export_raster(Zipper &zipper, - const std::string& projectname = "") - { - if(m_printer) m_printer->save(zipper, projectname); - } - const PrintObjects& objects() const { return m_objects; } const SLAPrintConfig& print_config() const { return m_print_config; } @@ -445,14 +458,15 @@ public: std::vector m_transformed_slices; - template void transformed_slices(Container&& c) { + template void transformed_slices(Container&& c) + { m_transformed_slices = std::forward(c); } friend class SLAPrint::Steps; public: - + explicit PrintLayer(coord_t lvl) : m_level(lvl) {} // for being sorted in their container (see m_printer_input) @@ -474,8 +488,11 @@ public: // The aggregated and leveled print records from various objects. // TODO: use this structure for the preview in the future. const std::vector& print_layers() const { return m_printer_input; } - + + void set_printer(SLAPrinter *archiver); + private: + // Implement same logic as in SLAPrintObject bool invalidate_step(SLAPrintStep st); @@ -491,13 +508,13 @@ private: std::vector m_stepmask; // Ready-made data for rasterization. - std::vector m_printer_input; - - // The printer itself - std::unique_ptr m_printer; - + std::vector m_printer_input; + + // The archive object which collects the raster images after slicing + SLAPrinter *m_printer = nullptr; + // Estimated print time, material consumed. - SLAPrintStatistics m_print_statistics; + SLAPrintStatistics m_print_statistics; class StatusReporter { @@ -512,15 +529,6 @@ private: double status() const { return m_st; } } m_report_status; - - sla::RasterWriter &init_printer(); - - inline sla::Raster::Orientation get_printer_orientation() const - { - auto ro = m_printer_config.display_orientation.getInt(); - return ro == sla::Raster::roPortrait ? sla::Raster::roPortrait : - sla::Raster::roLandscape; - } friend SLAPrintObject; }; diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 01220a633..e421e9c1d 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -816,16 +816,7 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() { // Rasterizing the model objects, and their supports void SLAPrint::Steps::rasterize() { - if(canceled()) return; - - auto &print_statistics = m_print->m_print_statistics; - auto &printer_input = m_print->m_printer_input; - - // Set up the printer, allocate space for all the layers - sla::RasterWriter &printer = m_print->init_printer(); - - auto lvlcnt = unsigned(printer_input.size()); - printer.layers(lvlcnt); + if(canceled() || !m_print->m_printer) return; // coefficient to map the rasterization state (0-99) to the allocated // portion (slot) of the process state @@ -837,7 +828,7 @@ void SLAPrint::Steps::rasterize() // pst: previous state double pst = current_status(); - double increment = (slot * sd) / printer_input.size(); + double increment = (slot * sd) / m_print->m_printer_input.size(); double dstatus = current_status(); sla::ccr::SpinningMutex slck; @@ -845,20 +836,14 @@ void SLAPrint::Steps::rasterize() // procedure to process one height level. This will run in parallel auto lvlfn = - [this, &slck, &printer, increment, &dstatus, &pst] - (PrintLayer& printlayer, size_t idx) + [this, &slck, increment, &dstatus, &pst] + (sla::RasterBase& raster, size_t idx) { + PrintLayer& printlayer = m_print->m_printer_input[idx]; if(canceled()) return; - auto level_id = unsigned(idx); - // Switch to the appropriate layer in the printer - printer.begin_layer(level_id); - - for(const ClipperLib::Polygon& poly : printlayer.transformed_slices()) - printer.draw_polygon(poly, level_id); - - // Finish the layer for later saving it. - printer.finish_layer(level_id); + for (const ClipperLib::Polygon& poly : printlayer.transformed_slices()) + raster.draw(poly); // Status indication guarded with the spinlock { @@ -875,24 +860,8 @@ void SLAPrint::Steps::rasterize() // last minute escape if(canceled()) return; - // Sequential version (for testing) - // for(unsigned l = 0; l < lvlcnt; ++l) lvlfn(l); - // Print all the layers in parallel - sla::ccr::enumerate(printer_input.begin(), printer_input.end(), lvlfn); - - // Set statistics values to the printer - sla::RasterWriter::PrintStatistics stats; - stats.used_material = (print_statistics.objects_used_material + - print_statistics.support_used_material) / 1000; - - int num_fade = m_print->m_default_object_config.faded_layers.getInt(); - stats.num_fade = num_fade >= 0 ? size_t(num_fade) : size_t(0); - stats.num_fast = print_statistics.fast_layers_count; - stats.num_slow = print_statistics.slow_layers_count; - stats.estimated_print_time_s = print_statistics.estimated_print_time; - - printer.set_statistics(stats); + m_print->m_printer->draw_layers(m_print->m_printer_input.size(), lvlfn); } std::string SLAPrint::Steps::label(SLAPrintObjectStep step) diff --git a/src/libslic3r/SLAPrintSteps.hpp b/src/libslic3r/SLAPrintSteps.hpp index d3341bc14..19b64d4a9 100644 --- a/src/libslic3r/SLAPrintSteps.hpp +++ b/src/libslic3r/SLAPrintSteps.hpp @@ -46,7 +46,7 @@ private: void apply_printer_corrections(SLAPrintObject &po, SliceOrigin o); public: - Steps(SLAPrint *print); + explicit Steps(SLAPrint *print); void hollow_model(SLAPrintObject &po); void drill_holes (SLAPrintObject &po); diff --git a/src/libslic3r/SlicesToTriangleMesh.cpp b/src/libslic3r/SlicesToTriangleMesh.cpp new file mode 100644 index 000000000..bd0961d04 --- /dev/null +++ b/src/libslic3r/SlicesToTriangleMesh.cpp @@ -0,0 +1,83 @@ + +#include "SlicesToTriangleMesh.hpp" + +#include "libslic3r/TriangulateWall.hpp" +#include "libslic3r/SLA/Contour3D.hpp" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/Tesselate.hpp" + +namespace Slic3r { + +inline sla::Contour3D walls(const Polygon &lower, + const Polygon &upper, + double lower_z_mm, + double upper_z_mm) +{ + Wall w = triangulate_wall(lower, upper, lower_z_mm, upper_z_mm); + + sla::Contour3D ret; + ret.points = std::move(w.first); + ret.faces3 = std::move(w.second); + + return ret; +} + +// Same as walls() but with identical higher and lower polygons. +sla::Contour3D inline straight_walls(const Polygon &plate, + double lo_z, + double hi_z) +{ + return walls(plate, plate, lo_z, hi_z); +} + +sla::Contour3D inline straight_walls(const ExPolygon &plate, + double lo_z, + double hi_z) +{ + sla::Contour3D ret; + ret.merge(straight_walls(plate.contour, lo_z, hi_z)); + for (auto &h : plate.holes) ret.merge(straight_walls(h, lo_z, hi_z)); + return ret; +} + +sla::Contour3D inline straight_walls(const ExPolygons &slice, + double lo_z, + double hi_z) +{ + sla::Contour3D ret; + for (const ExPolygon &poly : slice) + ret.merge(straight_walls(poly, lo_z, hi_z)); + + return ret; +} + +void slices_to_triangle_mesh(TriangleMesh & mesh, + const std::vector &slices, + double zmin, + double lh, + double ilh) +{ + sla::Contour3D cntr3d; + double h = zmin; + + auto it = slices.begin(), xt = std::next(it); + cntr3d.merge(triangulate_expolygons_3d(*it, h, NORMALS_DOWN)); + cntr3d.merge(straight_walls(*it, h, h + ilh)); + h += ilh; + while (xt != slices.end()) { + ExPolygons dff1 = diff_ex(*it, *xt); + ExPolygons dff2 = diff_ex(*xt, *it); + cntr3d.merge(triangulate_expolygons_3d(dff1, h, NORMALS_UP)); + cntr3d.merge(triangulate_expolygons_3d(dff2, h, NORMALS_UP)); + cntr3d.merge(straight_walls(*xt, h, h + lh)); + h += lh; + ++it; ++xt; + } + + cntr3d.merge(triangulate_expolygons_3d(*it, h, NORMALS_UP)); + + mesh.merge(sla::to_triangle_mesh(cntr3d)); + mesh.require_shared_vertices(); +} + +} // namespace Slic3r diff --git a/src/libslic3r/SlicesToTriangleMesh.hpp b/src/libslic3r/SlicesToTriangleMesh.hpp new file mode 100644 index 000000000..133312d56 --- /dev/null +++ b/src/libslic3r/SlicesToTriangleMesh.hpp @@ -0,0 +1,24 @@ +#ifndef SLICESTOTRIANGLEMESH_HPP +#define SLICESTOTRIANGLEMESH_HPP + +#include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/ExPolygon.hpp" + +namespace Slic3r { + +void slices_to_triangle_mesh(TriangleMesh & mesh, + const std::vector &slices, + double zmin, + double lh, + double ilh); + +inline TriangleMesh slices_to_triangle_mesh( + const std::vector &slices, double zmin, double lh, double ilh) +{ + TriangleMesh out; slices_to_triangle_mesh(out, slices, zmin, lh, ilh); + return out; +} + +} // namespace Slic3r + +#endif // SLICESTOTRIANGLEMESH_HPP diff --git a/src/libslic3r/TriangulateWall.cpp b/src/libslic3r/TriangulateWall.cpp new file mode 100644 index 000000000..ec2945b10 --- /dev/null +++ b/src/libslic3r/TriangulateWall.cpp @@ -0,0 +1,133 @@ +#include "TriangulateWall.hpp" +#include "MTUtils.hpp" + +namespace Slic3r { + +class Ring { + size_t idx = 0, nextidx = 1, startidx = 0, begin = 0, end = 0; + +public: + explicit Ring(size_t from, size_t to) : begin(from), end(to) { init(begin); } + + size_t size() const { return end - begin; } + std::pair pos() const { return {idx, nextidx}; } + bool is_lower() const { return idx < size(); } + + void inc() + { + if (nextidx != startidx) nextidx++; + if (nextidx == end) nextidx = begin; + idx ++; + if (idx == end) idx = begin; + } + + void init(size_t pos) + { + startidx = begin + (pos - begin) % size(); + idx = startidx; + nextidx = begin + (idx + 1 - begin) % size(); + } + + bool is_finished() const { return nextidx == idx; } +}; + +static double sq_dst(const Vec3d &v1, const Vec3d& v2) +{ + Vec3d v = v1 - v2; + return v.x() * v.x() + v.y() * v.y() /*+ v.z() * v.z()*/; +} + +static double score(const Ring& onring, const Ring &offring, + const std::vector &pts) +{ + double a = sq_dst(pts[onring.pos().first], pts[offring.pos().first]); + double b = sq_dst(pts[onring.pos().second], pts[offring.pos().first]); + return (std::abs(a) + std::abs(b)) / 2.; +} + +class Triangulator { + const std::vector *pts; + Ring *onring, *offring; + + double calc_score() const + { + return Slic3r::score(*onring, *offring, *pts); + } + + void synchronize_rings() + { + Ring lring = *offring; + auto minsc = Slic3r::score(*onring, lring, *pts); + size_t imin = lring.pos().first; + + lring.inc(); + + while(!lring.is_finished()) { + double score = Slic3r::score(*onring, lring, *pts); + if (score < minsc) { minsc = score; imin = lring.pos().first; } + lring.inc(); + } + + offring->init(imin); + } + + void emplace_indices(std::vector &indices) + { + Vec3i tr{int(onring->pos().first), int(onring->pos().second), + int(offring->pos().first)}; + if (onring->is_lower()) std::swap(tr(0), tr(1)); + indices.emplace_back(tr); + } + +public: + void run(std::vector &indices) + { + synchronize_rings(); + + double score = 0, prev_score = 0; + while (!onring->is_finished() || !offring->is_finished()) { + prev_score = score; + if (onring->is_finished() || (score = calc_score()) > prev_score) { + std::swap(onring, offring); + } else { + emplace_indices(indices); + onring->inc(); + } + } + } + + explicit Triangulator(const std::vector *points, + Ring & lower, + Ring & upper) + : pts{points}, onring{&upper}, offring{&lower} + {} +}; + +Wall triangulate_wall( + const Polygon & lower, + const Polygon & upper, + double lower_z_mm, + double upper_z_mm) +{ + if (upper.points.size() < 3 || lower.points.size() < 3) return {}; + + Wall wall; + auto &pts = wall.first; + auto &ind = wall.second; + + pts.reserve(lower.points.size() + upper.points.size()); + for (auto &p : lower.points) + wall.first.emplace_back(unscaled(p.x()), unscaled(p.y()), lower_z_mm); + for (auto &p : upper.points) + wall.first.emplace_back(unscaled(p.x()), unscaled(p.y()), upper_z_mm); + + ind.reserve(2 * (lower.size() + upper.size())); + + Ring lring{0, lower.points.size()}, uring{lower.points.size(), pts.size()}; + Triangulator t{&pts, lring, uring}; + t.run(ind); + + return wall; +} + +} // namespace Slic3r diff --git a/src/libslic3r/TriangulateWall.hpp b/src/libslic3r/TriangulateWall.hpp new file mode 100644 index 000000000..68bf4b0ac --- /dev/null +++ b/src/libslic3r/TriangulateWall.hpp @@ -0,0 +1,17 @@ +#ifndef TRIANGULATEWALL_HPP +#define TRIANGULATEWALL_HPP + +#include "libslic3r/Polygon.hpp" + +namespace Slic3r { + +using Wall = std::pair, std::vector>; + +Wall triangulate_wall( + const Polygon & lower, + const Polygon & upper, + double lower_z_mm, + double upper_z_mm); +} + +#endif // TRIANGULATEWALL_HPP diff --git a/src/libslic3r/Zipper.cpp b/src/libslic3r/Zipper.cpp index a5b53584d..ec9d3aa16 100644 --- a/src/libslic3r/Zipper.cpp +++ b/src/libslic3r/Zipper.cpp @@ -167,7 +167,7 @@ void Zipper::add_entry(const std::string &name) m_entry = name; } -void Zipper::add_entry(const std::string &name, const uint8_t *data, size_t l) +void Zipper::add_entry(const std::string &name, const void *data, size_t l) { if(!m_impl->is_alive()) return; diff --git a/src/libslic3r/Zipper.hpp b/src/libslic3r/Zipper.hpp index be1e69b5c..d203ea7b2 100644 --- a/src/libslic3r/Zipper.hpp +++ b/src/libslic3r/Zipper.hpp @@ -49,7 +49,7 @@ public: /// Add a new binary file entry with an instantly given byte buffer. /// This method throws exactly like finish_entry() does. - void add_entry(const std::string& name, const std::uint8_t* data, size_t l); + void add_entry(const std::string& name, const void* data, size_t bytes); // Writing data to the archive works like with standard streams. The target // within the zip file is the entry created with the add_entry method. diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 1e9f3b862..c0853fb65 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -182,6 +182,8 @@ set(SLIC3R_GUI_SOURCES Utils/HexFile.cpp Utils/HexFile.hpp Utils/Thread.hpp + Utils/SLAZipFileImport.hpp + Utils/SLAZipFileImport.cpp ) if (APPLE) diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 7ce942855..cfd38354b 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -19,6 +19,7 @@ #include "libslic3r/Utils.hpp" #include "libslic3r/GCode/PostProcessor.hpp" #include "libslic3r/GCode/PreviewData.hpp" +#include "libslic3r/Format/SL1.hpp" #include "libslic3r/libslic3r.h" #include @@ -149,7 +150,7 @@ void BackgroundSlicingProcess::process_sla() const std::string export_path = m_sla_print->print_statistics().finalize_output_path(m_export_path); Zipper zipper(export_path); - m_sla_print->export_raster(zipper); + m_sla_archive.export_print(zipper, *m_sla_print); if (m_thumbnail_cb != nullptr) { @@ -473,9 +474,9 @@ void BackgroundSlicingProcess::prepare_upload() m_upload_job.upload_data.upload_path = m_fff_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string()); } else { m_upload_job.upload_data.upload_path = m_sla_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string()); - + Zipper zipper{source_path.string()}; - m_sla_print->export_raster(zipper, m_upload_job.upload_data.upload_path.string()); + m_sla_archive.export_print(zipper, *m_sla_print, m_upload_job.upload_data.upload_path.string()); if (m_thumbnail_cb != nullptr) { ThumbnailsList thumbnails; diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp index b868a233f..efaea1d11 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp @@ -10,6 +10,7 @@ #include #include "libslic3r/Print.hpp" +#include "libslic3r/Format/SL1.hpp" #include "slic3r/Utils/PrintHost.hpp" @@ -19,6 +20,7 @@ class DynamicPrintConfig; class GCodePreviewData; class Model; class SLAPrint; +class SL1Archive; class SlicingStatusEvent : public wxEvent { @@ -47,7 +49,7 @@ public: ~BackgroundSlicingProcess(); void set_fff_print(Print *print) { m_fff_print = print; } - void set_sla_print(SLAPrint *print) { m_sla_print = print; } + void set_sla_print(SLAPrint *print) { m_sla_print = print; m_sla_print->set_printer(&m_sla_archive); } void set_gcode_preview_data(GCodePreviewData *gpd) { m_gcode_preview_data = gpd; } void set_thumbnail_cb(ThumbnailsGeneratorCallback cb) { m_thumbnail_cb = cb; } @@ -155,6 +157,7 @@ private: GCodePreviewData *m_gcode_preview_data = nullptr; // Callback function, used to write thumbnails into gcode. ThumbnailsGeneratorCallback m_thumbnail_cb = nullptr; + SL1Archive m_sla_archive; // Temporary G-code, there is one defined for the BackgroundSlicingProcess, differentiated from the other processes by a process ID. std::string m_temp_output_path; // Output path provided by the user. The output path may be set even if the slicing is running, diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 6a1964506..f7d7a6cac 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -589,6 +589,11 @@ void MainFrame::init_menubar() append_menu_item(import_menu, wxID_ANY, _(L("Import STL/OBJ/AM&F/3MF")) + dots + "\tCtrl+I", _(L("Load a model")), [this](wxCommandEvent&) { if (m_plater) m_plater->add_model(); }, "import_plater", nullptr, [this](){return m_plater != nullptr; }, this); + + append_menu_item(import_menu, wxID_ANY, _(L("Import SL1 archive")) + dots, _(L("Load an SL1 output archive")), + [this](wxCommandEvent&) { if (m_plater) m_plater->import_sl1_archive(); }, "import_plater", nullptr, + [this](){return m_plater != nullptr; }, this); + import_menu->AppendSeparator(); append_menu_item(import_menu, wxID_ANY, _(L("Import &Config")) + dots + "\tCtrl+L", _(L("Load exported configuration file")), [this](wxCommandEvent&) { load_config_file(); }, "import_config", nullptr, diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 7f323c554..75ef32762 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include "libslic3r/libslic3r.h" #include "libslic3r/Format/STL.hpp" @@ -72,6 +73,7 @@ #include "../Utils/PrintHost.hpp" #include "../Utils/FixModelByWin10.hpp" #include "../Utils/UndoRedo.hpp" +#include "../Utils/SLAZipFileImport.hpp" #include "RemovableDriveManager.hpp" #if ENABLE_NON_STATIC_CANVAS_MANAGER #ifdef __APPLE__ @@ -4251,6 +4253,27 @@ void Plater::add_model() load_files(paths, true, false); } +void Plater::import_sl1_archive() +{ + wxFileDialog dlg(this, _(L("Choose SL1 archive:")), + from_u8(wxGetApp().app_config->get_last_dir()), "", + "SL1 archive files (*.sl1)|*.sl1;*.SL1;*.zip;*.ZIP", + wxFD_OPEN | wxFD_FILE_MUST_EXIST); + + if (dlg.ShowModal() == wxID_OK) { + try { + TriangleMesh mesh = import_model_from_sla_zip(dlg.GetPath()); + ModelObject * obj = p->model.add_object(wxFileName(dlg.GetPath()).GetName(), "", mesh); + if (obj) { + obj->add_instance(); + update(); + } + } catch (std::exception &ex) { + show_error(this, ex.what()); + } + } +} + void Plater::extract_config_from_project() { wxString input_file; diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index e71eb3db4..2ac4f23c1 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -159,6 +159,7 @@ public: void load_project(); void load_project(const wxString& filename); void add_model(); + void import_sl1_archive(); void extract_config_from_project(); std::vector load_files(const std::vector& input_files, bool load_model = true, bool load_config = true); diff --git a/src/slic3r/Utils/SLAZipFileImport.cpp b/src/slic3r/Utils/SLAZipFileImport.cpp new file mode 100644 index 000000000..6543e8674 --- /dev/null +++ b/src/slic3r/Utils/SLAZipFileImport.cpp @@ -0,0 +1,144 @@ +#include "SLAZipFileImport.hpp" + +#include "libslic3r/SlicesToTriangleMesh.hpp" +#include "libslic3r/MarchingSquares.hpp" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/MTUtils.hpp" +#include "libslic3r/PrintConfig.hpp" + +#include +#include +#include +#include +#include + +#include + +#include + +namespace marchsq { + +// Specialize this struct to register a raster type for the Marching squares alg +template<> struct _RasterTraits { + using Rst = wxImage; + + // The type of pixel cell in the raster + using ValueType = uint8_t; + + // Value at a given position + static uint8_t get(const Rst &rst, size_t row, size_t col) { return rst.GetRed(col, row); } + + // Number of rows and cols of the raster + static size_t rows(const Rst &rst) { return rst.GetHeight(); } + static size_t cols(const Rst &rst) { return rst.GetWidth(); } +}; + +} // namespace marchsq + +namespace Slic3r { + +ExPolygons rings_to_expolygons(const std::vector &rings, + double px_w, double px_h) +{ + ExPolygons polys; polys.reserve(rings.size()); + + for (const marchsq::Ring &ring : rings) { + Polygon poly; Points &pts = poly.points; + pts.reserve(ring.size()); + + for (const marchsq::Coord &crd : ring) + pts.emplace_back(scaled(crd.c * px_w), scaled(crd.r * px_h)); + + polys.emplace_back(poly); + } + + // reverse the raster transformations + return union_ex(polys); +} + +TriangleMesh import_model_from_sla_zip(const wxString &zipfname) +{ + wxFileInputStream in(zipfname); + wxZipInputStream zip(in, wxConvUTF8); + + std::map files; + + while (auto entry = std::unique_ptr(zip.GetNextEntry())) { + auto fname = wxFileName(entry->GetName()); + wxString name_lo = fname.GetFullName().Lower(); + + if (fname.IsDir() || name_lo.Contains("thumbnail")) continue; + + if (!zip.OpenEntry(*entry)) + throw std::runtime_error("Cannot read archive"); + + wxMemoryOutputStream &stream = files[name_lo.ToStdString()]; + zip.Read(stream); + std::cout << name_lo << " read bytes: " << zip.LastRead() << std::endl; + if (!zip.LastRead()) std::cout << zip.GetLastError() << std::endl; + } + + using boost::property_tree::ptree; + + auto load_ini = [&files](const std::string &key, ptree &tree) { + auto it = files.find(key); + if (it != files.end()) { + wxString str; + wxStringOutputStream oss{&str}; + wxMemoryInputStream inp{it->second}; + oss.Write(inp); + std::stringstream iss(str.ToStdString()); + boost::property_tree::read_ini(iss, tree); + files.erase(it); + } else { + throw std::runtime_error(key + " is missing"); + } + }; + + ptree profile_tree, config; + load_ini("prusaslicer.ini", profile_tree); + load_ini("config.ini", config); + + DynamicPrintConfig profile; + profile.load(profile_tree); + + size_t disp_cols = profile.opt_int("display_pixels_x"); + size_t disp_rows = profile.opt_int("display_pixels_y"); + double disp_w = profile.opt_float("display_width"); + double disp_h = profile.opt_float("display_height"); + double px_w = disp_w / disp_cols; + double px_h = disp_h / disp_rows; + + auto jobdir = config.get("jobDir"); + for (auto &c : jobdir) c = std::tolower(c); + + for (auto it = files.begin(); it != files.end();) + if (it->first.find(jobdir) == std::string::npos || + wxFileName(it->first).GetExt().Lower() != "png") + it = files.erase(it); + else ++it; + + std::vector slices(files.size()); + size_t i = 0; + for (auto &item : files) { + wxMemoryOutputStream &imagedata = item.second; + wxMemoryInputStream stream{imagedata}; + wxImage img{stream, "image/png"}; + + std::cout << img.GetWidth() << " " << img.GetHeight() << std::endl; + + auto rings = marchsq::execute(img, 128); + slices[i++] = rings_to_expolygons(rings, px_w, px_h); + } + + TriangleMesh out; + if (!slices.empty()) { + double lh = profile.opt_float("layer_height"); + double ilh = profile.opt_float("initial_layer_height"); + out = slices_to_triangle_mesh(slices, 0, lh, ilh); + } + + return out; +} + +} // namespace Slic3r diff --git a/src/slic3r/Utils/SLAZipFileImport.hpp b/src/slic3r/Utils/SLAZipFileImport.hpp new file mode 100644 index 000000000..4e36f86f4 --- /dev/null +++ b/src/slic3r/Utils/SLAZipFileImport.hpp @@ -0,0 +1,14 @@ +#ifndef SLAZIPFILEIMPORT_HPP +#define SLAZIPFILEIMPORT_HPP + +#include "libslic3r/TriangleMesh.hpp" + +#include + +namespace Slic3r { + +TriangleMesh import_model_from_sla_zip(const wxString &zipfname); + +} + +#endif // SLAZIPFILEIMPORT_HPP diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index 2353414f9..b41dbf8ba 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -13,6 +13,7 @@ add_executable(${_TEST_NAME}_tests test_stl.cpp test_meshsimplify.cpp test_meshboolean.cpp + test_marchingsquares.cpp test_timeutils.cpp ) diff --git a/tests/libslic3r/test_marchingsquares.cpp b/tests/libslic3r/test_marchingsquares.cpp new file mode 100644 index 000000000..ce1c0e3ff --- /dev/null +++ b/tests/libslic3r/test_marchingsquares.cpp @@ -0,0 +1,342 @@ +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +using namespace Slic3r; + +static Slic3r::sla::RasterGrayscaleAA create_raster( + const sla::RasterBase::Resolution &res, + double disp_w = 100., + double disp_h = 100.) +{ + sla::RasterBase::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px}; + + auto bb = BoundingBox({0, 0}, {scaled(disp_w), scaled(disp_h)}); + sla::RasterBase::Trafo trafo; +// trafo.center_x = bb.center().x(); +// trafo.center_y = bb.center().y(); +// trafo.center_x = scaled(pixdim.w_mm); +// trafo.center_y = scaled(pixdim.h_mm); + + return sla::RasterGrayscaleAA{res, pixdim, trafo, agg::gamma_threshold(.5)}; +} + +static ExPolygon square(double a, Point center = {0, 0}) +{ + ExPolygon poly; + coord_t V = scaled(a / 2.); + + poly.contour.points = {{-V, -V}, {V, -V}, {V, V}, {-V, V}}; + poly.translate(center.x(), center.y()); + + return poly; +} + +static ExPolygon square_with_hole(double a, Point center = {0, 0}) +{ + ExPolygon poly = square(a); + + poly.holes.emplace_back(); + coord_t V = scaled(a / 4.); + poly.holes.front().points = {{-V, V}, {V, V}, {V, -V}, {-V, -V}}; + + poly.translate(center.x(), center.y()); + + return poly; +} + +static ExPolygons circle_with_hole(double r, Point center = {0, 0}) { + + ExPolygon poly; + + std::vector pis = linspace_vector(0., 2 * PI, 100); + + coord_t rs = scaled(r); + for (double phi : pis) { + poly.contour.points.emplace_back(rs * std::cos(phi), rs * std::sin(phi)); + } + + poly.holes.emplace_back(poly.contour); + poly.holes.front().reverse(); + for (auto &p : poly.holes.front().points) p /= 2; + + poly.translate(center.x(), center.y()); + + return {poly}; +} + +template +static void test_expolys(Rst && rst, + const ExPolygons & ref, + float accuracy, + const std::string &name = "test") +{ + for (const ExPolygon &expoly : ref) rst.draw(expoly); + + std::fstream out(name + ".png", std::ios::out); + out << rst.encode(sla::PNGRasterEncoder{}); + out.close(); + + ExPolygons extracted = sla::raster_to_polygons(rst, accuracy); + + SVG svg(name + ".svg"); + svg.draw(extracted); + svg.Close(); + + REQUIRE(extracted.size() == ref.size()); + for (size_t i = 0; i < ref.size(); ++i) { + REQUIRE(extracted[i].contour.is_counter_clockwise()); + REQUIRE(extracted[i].holes.size() == ref[i].holes.size()); + + for (auto &h : extracted[i].holes) REQUIRE(h.is_clockwise()); + + double refa = ref[i].area(); + REQUIRE(std::abs(extracted[i].area() - refa) < 0.1 * refa); + } +} + +TEST_CASE("Empty raster should result in empty polygons", "[MarchingSquares]") { + sla::RasterGrayscaleAAGammaPower rst{{}, {}, {}}; + ExPolygons extracted = sla::raster_to_polygons(rst); + REQUIRE(extracted.size() == 0); +} + +TEST_CASE("Marching squares directions", "[MarchingSquares]") { + marchsq::Coord crd{1, 1}; + + REQUIRE(step(crd, marchsq::__impl::Dir::left).r == 1); + REQUIRE(step(crd, marchsq::__impl::Dir::left).c == 0); + + REQUIRE(step(crd, marchsq::__impl::Dir::down).r == 2); + REQUIRE(step(crd, marchsq::__impl::Dir::down).c == 1); + + REQUIRE(step(crd, marchsq::__impl::Dir::right).r == 1); + REQUIRE(step(crd, marchsq::__impl::Dir::right).c == 2); + + REQUIRE(step(crd, marchsq::__impl::Dir::up).r == 0); + REQUIRE(step(crd, marchsq::__impl::Dir::up).c == 1); +} + +TEST_CASE("4x4 raster with one ring", "[MarchingSquares]") { + + sla::RasterBase::PixelDim pixdim{1, 1}; + + // We need one additional row and column to detect edges + sla::RasterGrayscaleAA rst{{5, 5}, pixdim, {}, agg::gamma_threshold(.5)}; + + // Draw a triangle from individual pixels + rst.draw(square(1., {1500000, 1500000})); + rst.draw(square(1., {2500000, 1500000})); + rst.draw(square(1., {3500000, 1500000})); + + rst.draw(square(1., {2500000, 2500000})); + rst.draw(square(1., {3500000, 2500000})); + + rst.draw(square(1., {3500000, 3500000})); + + std::fstream out("4x4.png", std::ios::out); + out << rst.encode(sla::PNGRasterEncoder{}); + out.close(); + + ExPolygons extracted = sla::raster_to_polygons(rst); + + SVG svg("4x4.svg"); + svg.draw(extracted); + svg.Close(); + + REQUIRE(extracted.size() == 1); +} + +TEST_CASE("4x4 raster with two rings", "[MarchingSquares]") { + + sla::RasterBase::PixelDim pixdim{1, 1}; + + // We need one additional row and column to detect edges + sla::RasterGrayscaleAA rst{{5, 5}, pixdim, {}, agg::gamma_threshold(.5)}; + + SECTION("Ambiguous case with 'ac' square") { + + // Draw a triangle from individual pixels + rst.draw(square(1., {3500000, 2500000})); + rst.draw(square(1., {3500000, 3500000})); + rst.draw(square(1., {2500000, 3500000})); + + rst.draw(square(1., {2500000, 1500000})); + rst.draw(square(1., {1500000, 1500000})); + rst.draw(square(1., {1500000, 2500000})); + + std::fstream out("4x4_ac.png", std::ios::out); + out << rst.encode(sla::PNGRasterEncoder{}); + out.close(); + + ExPolygons extracted = sla::raster_to_polygons(rst); + + SVG svg("4x4_ac.svg"); + svg.draw(extracted); + svg.Close(); + + REQUIRE(extracted.size() == 2); + } + + SECTION("Ambiguous case with 'bd' square") { + + // Draw a triangle from individual pixels + rst.draw(square(1., {3500000, 1500000})); + rst.draw(square(1., {3500000, 2500000})); + rst.draw(square(1., {2500000, 1500000})); + + rst.draw(square(1., {1500000, 2500000})); + rst.draw(square(1., {1500000, 3500000})); + rst.draw(square(1., {2500000, 3500000})); + + std::fstream out("4x4_bd.png", std::ios::out); + out << rst.encode(sla::PNGRasterEncoder{}); + out.close(); + + ExPolygons extracted = sla::raster_to_polygons(rst); + + SVG svg("4x4_bd.svg"); + svg.draw(extracted); + svg.Close(); + + REQUIRE(extracted.size() == 2); + } +} + +TEST_CASE("Square with hole in the middle", "[MarchingSquares]") { + using namespace Slic3r; + + ExPolygons inp = {square_with_hole(50.)}; + + SECTION("Proportional raster, 1x1 mm pixel size, full accuracy") { + test_expolys(create_raster({100, 100}, 100., 100.), inp, 1.f, "square_with_hole_proportional_1x1_mm_px_full"); + } + + SECTION("Proportional raster, 1x1 mm pixel size, half accuracy") { + test_expolys(create_raster({100, 100}, 100., 100.), inp, .5f, "square_with_hole_proportional_1x1_mm_px_half"); + } + + SECTION("Landscape raster, 1x1 mm pixel size, full accuracy") { + test_expolys(create_raster({150, 100}, 150., 100.), inp, 1.f, "square_with_hole_landsc_1x1_mm_px_full"); + } + + SECTION("Landscape raster, 1x1 mm pixel size, half accuracy") { + test_expolys(create_raster({150, 100}, 150., 100.), inp, .5f, "square_with_hole_landsc_1x1_mm_px_half"); + } + + SECTION("Portrait raster, 1x1 mm pixel size, full accuracy") { + test_expolys(create_raster({100, 150}, 100., 150.), inp, 1.f, "square_with_hole_portrait_1x1_mm_px_full"); + } + + SECTION("Portrait raster, 1x1 mm pixel size, half accuracy") { + test_expolys(create_raster({100, 150}, 100., 150.), inp, .5f, "square_with_hole_portrait_1x1_mm_px_half"); + } + + SECTION("Proportional raster, 2x2 mm pixel size, full accuracy") { + test_expolys(create_raster({200, 200}, 100., 100.), inp, 1.f, "square_with_hole_proportional_2x2_mm_px_full"); + } + + SECTION("Proportional raster, 2x2 mm pixel size, half accuracy") { + test_expolys(create_raster({200, 200}, 100., 100.), inp, .5f, "square_with_hole_proportional_2x2_mm_px_half"); + } + + SECTION("Proportional raster, 0.5x0.5 mm pixel size, full accuracy") { + test_expolys(create_raster({50, 50}, 100., 100.), inp, 1.f, "square_with_hole_proportional_0.5x0.5_mm_px_full"); + } + + SECTION("Proportional raster, 0.5x0.5 mm pixel size, half accuracy") { + test_expolys(create_raster({50, 50}, 100., 100.), inp, .5f, "square_with_hole_proportional_0.5x0.5_mm_px_half"); + } +} + +TEST_CASE("Circle with hole in the middle", "[MarchingSquares]") { + using namespace Slic3r; + + test_expolys(create_raster({100, 100}), circle_with_hole(25.), 1.f, "circle_with_hole"); +} + +static void recreate_object_from_slices(const std::string &objname, float lh) { + TriangleMesh mesh = load_model(objname); + mesh.require_shared_vertices(); + + auto bb = mesh.bounding_box(); + std::vector layers; + slice_mesh(mesh, grid(float(bb.min.z()), float(bb.max.z()), lh), layers, 0.f, []{}); + + TriangleMesh out = slices_to_triangle_mesh(layers, bb.min.z(), double(lh), double(lh)); + + out.require_shared_vertices(); + out.WriteOBJFile("out_from_slices.obj"); +} + +static void recreate_object_from_rasters(const std::string &objname, float lh) { + TriangleMesh mesh = load_model(objname); + + auto bb = mesh.bounding_box(); +// Vec3f tr = -bb.center().cast(); +// mesh.translate(tr.x(), tr.y(), tr.z()); +// bb = mesh.bounding_box(); + + std::vector layers; + slice_mesh(mesh, grid(float(bb.min.z()) + lh, float(bb.max.z()), lh), layers, 0.f, []{}); + + sla::RasterBase::Resolution res{2560, 1440}; + double disp_w = 120.96; + double disp_h = 68.04; + + size_t cntr = 0; + for (ExPolygons &layer : layers) { + auto rst = create_raster(res, disp_w, disp_h); + + for (ExPolygon &island : layer) { + rst.draw(island); + } + + std::fstream out(objname + std::to_string(cntr) + ".png", std::ios::out); + out << rst.encode(sla::PNGRasterEncoder{}); + out.close(); + + ExPolygons layer_ = sla::raster_to_polygons(rst); +// float delta = scaled(std::min(rst.pixel_dimensions().h_mm, +// rst.pixel_dimensions().w_mm)); + +// layer_ = expolygons_simplify(layer_, delta); + + SVG svg(objname + std::to_string(cntr) + ".svg", BoundingBox(Point{0, 0}, Point{scaled(disp_w), scaled(disp_h)})); + svg.draw(layer_); + svg.draw(layer, "green"); + svg.Close(); + + double layera = 0., layera_ = 0.; + for (auto &p : layer) layera += p.area(); + for (auto &p : layer_) layera_ += p.area(); + + std::cout << cntr++ << std::endl; + double diff = std::abs(layera_ - layera); + REQUIRE((diff <= 0.1 * layera || diff < scaled(1.) * scaled(1.))); + + layer = std::move(layer_); + } + + TriangleMesh out = slices_to_triangle_mesh(layers, bb.min.z(), double(lh), double(lh)); + + out.require_shared_vertices(); + out.WriteOBJFile("out_from_rasters.obj"); +} + +TEST_CASE("Recreate object from rasters", "[SL1Import]") { + recreate_object_from_rasters("triang.obj", 0.05f); +} diff --git a/tests/sla_print/sla_print_tests.cpp b/tests/sla_print/sla_print_tests.cpp index 10f5742d3..82df2c1a6 100644 --- a/tests/sla_print/sla_print_tests.cpp +++ b/tests/sla_print/sla_print_tests.cpp @@ -154,19 +154,12 @@ TEST_CASE("FloorSupportsDoNotPierceModel", "[SLASupportGeneration]") { test_support_model_collision(fname, supportcfg); } -TEST_CASE("DefaultRasterShouldBeEmpty", "[SLARasterOutput]") { - sla::Raster raster; - REQUIRE(raster.empty()); -} - TEST_CASE("InitializedRasterShouldBeNONEmpty", "[SLARasterOutput]") { // Default Prusa SL1 display parameters - sla::Raster::Resolution res{2560, 1440}; - sla::Raster::PixelDim pixdim{120. / res.width_px, 68. / res.height_px}; + sla::RasterBase::Resolution res{2560, 1440}; + sla::RasterBase::PixelDim pixdim{120. / res.width_px, 68. / res.height_px}; - sla::Raster raster; - raster.reset(res, pixdim); - REQUIRE_FALSE(raster.empty()); + sla::RasterGrayscaleAAGammaPower raster(res, pixdim, {}, 1.); REQUIRE(raster.resolution().width_px == res.width_px); REQUIRE(raster.resolution().height_px == res.height_px); REQUIRE(raster.pixel_dimensions().w_mm == Approx(pixdim.w_mm)); @@ -174,13 +167,14 @@ TEST_CASE("InitializedRasterShouldBeNONEmpty", "[SLARasterOutput]") { } TEST_CASE("MirroringShouldBeCorrect", "[SLARasterOutput]") { - sla::Raster::TMirroring mirrorings[] = {sla::Raster::NoMirror, - sla::Raster::MirrorX, - sla::Raster::MirrorY, - sla::Raster::MirrorXY}; + sla::RasterBase::TMirroring mirrorings[] = {sla::RasterBase::NoMirror, + sla::RasterBase::MirrorX, + sla::RasterBase::MirrorY, + sla::RasterBase::MirrorXY}; + + sla::RasterBase::Orientation orientations[] = + {sla::RasterBase::roLandscape, sla::RasterBase::roPortrait}; - sla::Raster::Orientation orientations[] = {sla::Raster::roLandscape, - sla::Raster::roPortrait}; for (auto orientation : orientations) for (auto &mirror : mirrorings) check_raster_transformations(orientation, mirror); @@ -189,10 +183,11 @@ TEST_CASE("MirroringShouldBeCorrect", "[SLARasterOutput]") { TEST_CASE("RasterizedPolygonAreaShouldMatch", "[SLARasterOutput]") { double disp_w = 120., disp_h = 68.; - sla::Raster::Resolution res{2560, 1440}; - sla::Raster::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px}; + sla::RasterBase::Resolution res{2560, 1440}; + sla::RasterBase::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px}; - sla::Raster raster{res, pixdim}; + double gamma = 1.; + sla::RasterGrayscaleAAGammaPower raster(res, pixdim, {}, gamma); auto bb = BoundingBox({0, 0}, {scaled(disp_w), scaled(disp_h)}); ExPolygon poly = square_with_hole(10.); @@ -215,6 +210,13 @@ TEST_CASE("RasterizedPolygonAreaShouldMatch", "[SLARasterOutput]") { diff = std::abs(a - ra); REQUIRE(diff <= predict_error(poly, pixdim)); + + sla::RasterGrayscaleAA raster0(res, pixdim, {}, [](double) { return 0.; }); + REQUIRE(raster_pxsum(raster0) == 0); + + raster0.draw(poly); + ra = raster_white_area(raster); + REQUIRE(raster_pxsum(raster0) == 0); } TEST_CASE("Triangle mesh conversions should be correct", "[SLAConversions]") diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index a844b2eae..883e4268a 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -1,4 +1,5 @@ #include "sla_test_utils.hpp" +#include "libslic3r/SLA/AGGRaster.hpp" void test_support_model_collision(const std::string &obj_filename, const sla::SupportConfig &input_supportcfg, @@ -293,18 +294,19 @@ void check_validity(const TriangleMesh &input_mesh, int flags) } } -void check_raster_transformations(sla::Raster::Orientation o, sla::Raster::TMirroring mirroring) +void check_raster_transformations(sla::RasterBase::Orientation o, sla::RasterBase::TMirroring mirroring) { double disp_w = 120., disp_h = 68.; - sla::Raster::Resolution res{2560, 1440}; - sla::Raster::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px}; + sla::RasterBase::Resolution res{2560, 1440}; + sla::RasterBase::PixelDim pixdim{disp_w / res.width_px, disp_h / res.height_px}; auto bb = BoundingBox({0, 0}, {scaled(disp_w), scaled(disp_h)}); - sla::Raster::Trafo trafo{o, mirroring}; - trafo.origin_x = bb.center().x(); - trafo.origin_y = bb.center().y(); + sla::RasterBase::Trafo trafo{o, mirroring}; + trafo.center_x = bb.center().x(); + trafo.center_y = bb.center().y(); + double gamma = 1.; - sla::Raster raster{res, pixdim, trafo}; + sla::RasterGrayscaleAAGammaPower raster{res, pixdim, trafo, gamma}; // create box of size 32x32 pixels (not 1x1 to avoid antialiasing errors) coord_t pw = 32 * coord_t(std::ceil(scaled(pixdim.w_mm))); @@ -319,7 +321,7 @@ void check_raster_transformations(sla::Raster::Orientation o, sla::Raster::TMirr // Now calculate the position of the translated box according to output // trafo. - if (o == sla::Raster::Orientation::roPortrait) expected_box.rotate(PI / 2.); + if (o == sla::RasterBase::Orientation::roPortrait) expected_box.rotate(PI / 2.); if (mirroring[X]) for (auto &p : expected_box.contour.points) p.x() = -p.x(); @@ -340,10 +342,9 @@ void check_raster_transformations(sla::Raster::Orientation o, sla::Raster::TMirr auto px = raster.read_pixel(w, h); if (px != FullWhite) { - sla::PNGImage img; std::fstream outf("out.png", std::ios::out); - outf << img.serialize(raster); + outf << raster.encode(sla::PNGRasterEncoder()); } REQUIRE(px == FullWhite); @@ -361,9 +362,21 @@ ExPolygon square_with_hole(double v) return poly; } -double raster_white_area(const sla::Raster &raster) +long raster_pxsum(const sla::RasterGrayscaleAA &raster) { - if (raster.empty()) return std::nan(""); + auto res = raster.resolution(); + long a = 0; + + for (size_t x = 0; x < res.width_px; ++x) + for (size_t y = 0; y < res.height_px; ++y) + a += raster.read_pixel(x, y); + + return a; +} + +double raster_white_area(const sla::RasterGrayscaleAA &raster) +{ + if (raster.resolution().pixels() == 0) return std::nan(""); auto res = raster.resolution(); double a = 0; @@ -377,7 +390,7 @@ double raster_white_area(const sla::Raster &raster) return a; } -double predict_error(const ExPolygon &p, const sla::Raster::PixelDim &pd) +double predict_error(const ExPolygon &p, const sla::RasterBase::PixelDim &pd) { auto lines = p.lines(); double pix_err = pixel_area(FullWhite, pd) / 2.; diff --git a/tests/sla_print/sla_test_utils.hpp b/tests/sla_print/sla_test_utils.hpp index f3727bd39..3652b1f81 100644 --- a/tests/sla_print/sla_test_utils.hpp +++ b/tests/sla_print/sla_test_utils.hpp @@ -16,7 +16,7 @@ #include "libslic3r/SLA/SupportTreeBuilder.hpp" #include "libslic3r/SLA/SupportTreeBuildsteps.hpp" #include "libslic3r/SLA/SupportPointGenerator.hpp" -#include "libslic3r/SLA/Raster.hpp" +#include "libslic3r/SLA/AGGRaster.hpp" #include "libslic3r/SLA/ConcaveHull.hpp" #include "libslic3r/MTUtils.hpp" @@ -170,18 +170,19 @@ static constexpr const TPixel FullBlack = 0; template constexpr int arraysize(const A (&)[N]) { return N; } -void check_raster_transformations(sla::Raster::Orientation o, - sla::Raster::TMirroring mirroring); +void check_raster_transformations(sla::RasterBase::Orientation o, + sla::RasterBase::TMirroring mirroring); ExPolygon square_with_hole(double v); -inline double pixel_area(TPixel px, const sla::Raster::PixelDim &pxdim) +inline double pixel_area(TPixel px, const sla::RasterBase::PixelDim &pxdim) { return (pxdim.h_mm * pxdim.w_mm) * px * 1. / (FullWhite - FullBlack); } -double raster_white_area(const sla::Raster &raster); +double raster_white_area(const sla::RasterGrayscaleAA &raster); +long raster_pxsum(const sla::RasterGrayscaleAA &raster); -double predict_error(const ExPolygon &p, const sla::Raster::PixelDim &pd); +double predict_error(const ExPolygon &p, const sla::RasterBase::PixelDim &pd); #endif // SLA_TEST_UTILS_HPP From 217477a9ff62e084f6b616943705d84578417170 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 23 Apr 2020 19:12:07 +0200 Subject: [PATCH 10/11] SLA archive import with miniz, marching square bugfixes Fix compilation on Windows Fix array subscript out of range error in MarchingSquares Fix normals of mesh constructed from slices Improve performance of mesh construction from slices --- src/libslic3r/MarchingSquares.hpp | 104 ++++---- src/libslic3r/SLA/RasterToPolygons.cpp | 9 +- src/libslic3r/SLA/RasterToPolygons.hpp | 4 +- src/libslic3r/SlicesToTriangleMesh.cpp | 103 +++++--- src/libslic3r/Zipper.cpp | 80 +----- src/libslic3r/Zipper.hpp | 2 +- src/libslic3r/miniz_extension.cpp | 88 +++++++ src/libslic3r/miniz_extension.hpp | 21 +- src/slic3r/CMakeLists.txt | 4 +- src/slic3r/GUI/GUI_ObjectList.cpp | 17 +- src/slic3r/GUI/GUI_ObjectList.hpp | 2 + src/slic3r/GUI/Plater.cpp | 8 +- src/slic3r/Utils/SLAImport.cpp | 314 +++++++++++++++++++++++ src/slic3r/Utils/SLAImport.hpp | 36 +++ src/slic3r/Utils/SLAZipFileImport.cpp | 144 ----------- src/slic3r/Utils/SLAZipFileImport.hpp | 14 - tests/libslic3r/test_marchingsquares.cpp | 113 +++++--- 17 files changed, 690 insertions(+), 373 deletions(-) create mode 100644 src/slic3r/Utils/SLAImport.cpp create mode 100644 src/slic3r/Utils/SLAImport.hpp delete mode 100644 src/slic3r/Utils/SLAZipFileImport.cpp delete mode 100644 src/slic3r/Utils/SLAZipFileImport.hpp diff --git a/src/libslic3r/MarchingSquares.hpp b/src/libslic3r/MarchingSquares.hpp index a5aeb0953..d5f07fbde 100644 --- a/src/libslic3r/MarchingSquares.hpp +++ b/src/libslic3r/MarchingSquares.hpp @@ -11,11 +11,11 @@ namespace marchsq { // Marks a square in the grid struct Coord { - size_t r = 0, c = 0; + long r = 0, c = 0; Coord() = default; - explicit Coord(size_t s) : r(s), c(s) {} - Coord(size_t _r, size_t _c): r(_r), c(_c) {} + explicit Coord(long s) : r(s), c(s) {} + Coord(long _r, long _c): r(_r), c(_c) {} size_t seq(const Coord &res) const { return r * res.c + c; } Coord& operator+=(const Coord& b) { r += b.r; c += b.c; return *this; } @@ -52,11 +52,6 @@ namespace __impl { template using RasterTraits = _RasterTraits>; template using TRasterValue = typename RasterTraits::ValueType; -template TRasterValue isoval(const T &raster, const Coord &crd) -{ - return RasterTraits::get(raster, crd.r, crd.c); -} - template size_t rows(const T &raster) { return RasterTraits::rows(raster); @@ -67,6 +62,11 @@ template size_t cols(const T &raster) return RasterTraits::cols(raster); } +template TRasterValue isoval(const T &rst, const Coord &crd) +{ + return RasterTraits::get(rst, crd.r, crd.c); +} + template void for_each(ExecutionPolicy&& policy, It from, It to, Fn &&fn) { @@ -142,55 +142,46 @@ inline Coord step(const Coord &crd, Dir d) template class Grid { const Rst * m_rst = nullptr; - Coord m_cellsize, m_res_1, m_window, m_gridsize; + Coord m_cellsize, m_res_1, m_window, m_gridsize, m_grid_1; std::vector m_tags; // Assign tags to each square Coord rastercoord(const Coord &crd) const { - return {crd.r * m_window.r, crd.c * m_window.c}; + return {(crd.r - 1) * m_window.r, (crd.c - 1) * m_window.c}; } Coord bl(const Coord &crd) const { return tl(crd) + Coord{m_res_1.r, 0}; } Coord br(const Coord &crd) const { return tl(crd) + Coord{m_res_1.r, m_res_1.c}; } Coord tr(const Coord &crd) const { return tl(crd) + Coord{0, m_res_1.c}; } Coord tl(const Coord &crd) const { return rastercoord(crd); } - - TRasterValue bottomleft(const Coord &cell) const - { - return isoval(*m_rst, bl(cell)); - } - TRasterValue bottomright(const Coord &cell) const + bool is_within(const Coord &crd) { - return isoval(*m_rst, br(cell)); - } - - TRasterValue topright(const Coord &cell) const - { - return isoval(*m_rst, tr(cell)); - } - - TRasterValue topleft(const Coord &cell) const - { - return isoval(*m_rst, tl(cell)); - } + long R = rows(*m_rst), C = cols(*m_rst); + return crd.r >= 0 && crd.r < R && crd.c >= 0 && crd.c < C; + }; // Calculate the tag for a cell (or square). The cell coordinates mark the // top left vertex of a square in the raster. v is the isovalue uint8_t get_tag_for_cell(const Coord &cell, TRasterValue v) - { - uint8_t t = (bottomleft(cell) >= v) + - ((bottomright(cell) >= v) << 1) + - ((topright(cell) >= v) << 2) + - ((topleft(cell) >= v) << 3); + { + Coord sqr[] = {bl(cell), br(cell), tr(cell), tl(cell)}; + + uint8_t t = ((is_within(sqr[0]) && isoval(*m_rst, sqr[0]) >= v)) + + ((is_within(sqr[1]) && isoval(*m_rst, sqr[1]) >= v) << 1) + + ((is_within(sqr[2]) && isoval(*m_rst, sqr[2]) >= v) << 2) + + ((is_within(sqr[3]) && isoval(*m_rst, sqr[3]) >= v) << 3); assert(t < 16); return t; } // Get a cell coordinate from a sequential index - Coord coord(size_t i) const { return {i / m_gridsize.c, i % m_gridsize.c}; } - + Coord coord(size_t i) const + { + return {long(i) / m_gridsize.c, long(i) % m_gridsize.c}; + } + size_t seq(const Coord &crd) const { return crd.seq(m_gridsize); } bool is_visited(size_t idx, Dir d = Dir::none) const @@ -217,7 +208,7 @@ template class Grid { { // Skip ambiguous tags as starting tags due to unknown previous // direction. - while ((i < m_tags.size() && is_visited(i)) || is_ambiguous(i)) ++i; + while ((i < m_tags.size()) && (is_visited(i) || is_ambiguous(i))) ++i; return i; } @@ -248,8 +239,9 @@ template class Grid { } struct CellIt { - Coord crd; Dir dir= Dir::none; const Rst *rst = nullptr; - TRasterValue operator*() const { return isoval(*rst, crd); } + Coord crd; Dir dir= Dir::none; const Rst *grid = nullptr; + + TRasterValue operator*() const { return isoval(*grid, crd); } CellIt& operator++() { crd = step(crd, dir); return *this; } CellIt operator++(int) { CellIt it = *this; ++(*this); return it; } bool operator!=(const CellIt &it) { return crd.r != it.crd.r || crd.c != it.crd.c; } @@ -265,7 +257,7 @@ template class Grid { // used for binary search for the first active pixel on the edge. struct Edge { CellIt from, to; }; - Edge edge(const Coord &ringvertex) + Edge _edge(const Coord &ringvertex) const { size_t idx = ringvertex.r; Coord cell = coord(idx); @@ -312,6 +304,28 @@ template class Grid { return {}; } + Edge edge(const Coord &ringvertex) const + { + const long R = rows(*m_rst), C = cols(*m_rst); + const long R_1 = R - 1, C_1 = C - 1; + + Edge e = _edge(ringvertex); + e.to.dir = e.from.dir; + ++e.to; + + e.from.crd.r = std::min(e.from.crd.r, R_1); + e.from.crd.r = std::max(e.from.crd.r, 0l); + e.from.crd.c = std::min(e.from.crd.c, C_1); + e.from.crd.c = std::max(e.from.crd.c, 0l); + + e.to.crd.r = std::min(e.to.crd.r, R); + e.to.crd.r = std::max(e.to.crd.r, 0l); + e.to.crd.c = std::min(e.to.crd.c, C); + e.to.crd.c = std::max(e.to.crd.c, 0l); + + return e; + } + public: explicit Grid(const Rst &rst, const Coord &cellsz, const Coord &overlap) : m_rst{&rst} @@ -319,8 +333,8 @@ public: , m_res_1{m_cellsize.r - 1, m_cellsize.c - 1} , m_window{overlap.r < cellsz.r ? cellsz.r - overlap.r : cellsz.r, overlap.c < cellsz.c ? cellsz.c - overlap.c : cellsz.c} - , m_gridsize{(rows(rst) - overlap.r) / m_window.r, - (cols(rst) - overlap.c) / m_window.c} + , m_gridsize{2 + (long(rows(rst)) - overlap.r) / m_window.r, + 2 + (long(cols(rst)) - overlap.c) / m_window.c} , m_tags(m_gridsize.r * m_gridsize.c, 0) {} @@ -350,7 +364,7 @@ public: Dir prev = Dir::none, next = next_dir(prev, get_tag(idx)); while (next != Dir::none && !is_visited(idx, prev)) { - Coord ringvertex{idx, size_t(next)}; + Coord ringvertex{long(idx), long(next)}; ring.emplace_back(ringvertex); set_visited(idx, prev); @@ -379,9 +393,11 @@ public: TRasterValue isov) { for_each(std::forward(policy), - rings.begin(), rings.end(), [this, isov] (Ring &ring, size_t) { + rings.begin(), rings.end(), [this, isov] (Ring &ring, size_t) + { for (Coord &ringvertex : ring) { Edge e = edge(ringvertex); + CellIt found = std::lower_bound(e.from, e.to, isov); ringvertex = found.crd; } @@ -401,7 +417,7 @@ std::vector execute_with_policy(ExecutionPolicy && policy, if (!windowsize.r) windowsize.r = 2; if (!windowsize.c) - windowsize.c = std::max(size_t(2), windowsize.r * ratio); + windowsize.c = std::max(2l, long(windowsize.r * ratio)); Coord overlap{1}; diff --git a/src/libslic3r/SLA/RasterToPolygons.cpp b/src/libslic3r/SLA/RasterToPolygons.cpp index 6b1d8992c..cd84a3cb4 100644 --- a/src/libslic3r/SLA/RasterToPolygons.cpp +++ b/src/libslic3r/SLA/RasterToPolygons.cpp @@ -33,15 +33,15 @@ template void foreach_vertex(ExPolygon &poly, Fn &&fn) for (auto &p : h.points) fn(p); } -ExPolygons raster_to_polygons(const RasterGrayscaleAA &rst, float accuracy) +ExPolygons raster_to_polygons(const RasterGrayscaleAA &rst, Vec2i windowsize) { size_t rows = rst.resolution().height_px, cols = rst.resolution().width_px; if (rows < 2 || cols < 2) return {}; Polygons polys; - size_t w_rows = (2 + rows / 8) - size_t(accuracy * rows / 8); - size_t w_cols = std::max(size_t(2), w_rows * cols / rows); + long w_rows = std::max(2l, long(windowsize.y())); + long w_cols = std::max(2l, long(windowsize.x())); std::vector rings = marchsq::execute(rst, 128, {w_rows, w_cols}); @@ -49,6 +49,9 @@ ExPolygons raster_to_polygons(const RasterGrayscaleAA &rst, float accuracy) polys.reserve(rings.size()); auto pxd = rst.pixel_dimensions(); + pxd.w_mm = (rst.resolution().width_px * pxd.w_mm) / (rst.resolution().width_px - 1); + pxd.h_mm = (rst.resolution().height_px * pxd.h_mm) / (rst.resolution().height_px - 1); + for (const marchsq::Ring &ring : rings) { Polygon poly; Points &pts = poly.points; pts.reserve(ring.size()); diff --git a/src/libslic3r/SLA/RasterToPolygons.hpp b/src/libslic3r/SLA/RasterToPolygons.hpp index 131fe518e..c0e1f4114 100644 --- a/src/libslic3r/SLA/RasterToPolygons.hpp +++ b/src/libslic3r/SLA/RasterToPolygons.hpp @@ -8,8 +8,8 @@ namespace sla { class RasterGrayscaleAA; -ExPolygons raster_to_polygons(const RasterGrayscaleAA &rst, float accuracy = 1.f); +ExPolygons raster_to_polygons(const RasterGrayscaleAA &rst, Vec2i windowsize = {2, 2}); -}} +}} // namespace Slic3r::sla #endif // RASTERTOPOLYGONS_HPP diff --git a/src/libslic3r/SlicesToTriangleMesh.cpp b/src/libslic3r/SlicesToTriangleMesh.cpp index bd0961d04..d6a546961 100644 --- a/src/libslic3r/SlicesToTriangleMesh.cpp +++ b/src/libslic3r/SlicesToTriangleMesh.cpp @@ -1,23 +1,40 @@ #include "SlicesToTriangleMesh.hpp" -#include "libslic3r/TriangulateWall.hpp" +#include "libslic3r/MTUtils.hpp" #include "libslic3r/SLA/Contour3D.hpp" #include "libslic3r/ClipperUtils.hpp" #include "libslic3r/Tesselate.hpp" +#include +#include + namespace Slic3r { -inline sla::Contour3D walls(const Polygon &lower, - const Polygon &upper, - double lower_z_mm, - double upper_z_mm) +inline sla::Contour3D wall_strip(const Polygon &poly, + double lower_z_mm, + double upper_z_mm) { - Wall w = triangulate_wall(lower, upper, lower_z_mm, upper_z_mm); - sla::Contour3D ret; - ret.points = std::move(w.first); - ret.faces3 = std::move(w.second); + + size_t startidx = ret.points.size(); + size_t offs = poly.points.size(); + + ret.points.reserve(ret.points.size() + 2 *offs); + + for (const Point &p : poly.points) + ret.points.emplace_back(to_3d(unscaled(p), lower_z_mm)); + + for (const Point &p : poly.points) + ret.points.emplace_back(to_3d(unscaled(p), upper_z_mm)); + + for (size_t i = startidx + 1; i < startidx + offs; ++i) { + ret.faces3.emplace_back(i - 1, i, i + offs - 1); + ret.faces3.emplace_back(i, i + offs, i + offs - 1); + } + + ret.faces3.emplace_back(startidx + offs - 1, startidx, startidx + 2 * offs - 1); + ret.faces3.emplace_back(startidx, startidx + offs, startidx + 2 * offs - 1); return ret; } @@ -27,7 +44,7 @@ sla::Contour3D inline straight_walls(const Polygon &plate, double lo_z, double hi_z) { - return walls(plate, plate, lo_z, hi_z); + return wall_strip(plate, lo_z, hi_z); } sla::Contour3D inline straight_walls(const ExPolygon &plate, @@ -43,7 +60,7 @@ sla::Contour3D inline straight_walls(const ExPolygon &plate, sla::Contour3D inline straight_walls(const ExPolygons &slice, double lo_z, double hi_z) -{ +{ sla::Contour3D ret; for (const ExPolygon &poly : slice) ret.merge(straight_walls(poly, lo_z, hi_z)); @@ -51,32 +68,60 @@ sla::Contour3D inline straight_walls(const ExPolygons &slice, return ret; } +sla::Contour3D slices_to_triangle_mesh(const std::vector &slices, + double zmin, + const std::vector & grid) +{ + assert(slices.size() == grid.size()); + + using Layers = std::vector; + std::vector layers(slices.size()); + size_t len = slices.size() - 1; + + tbb::parallel_for(size_t(0), len, [&slices, &layers, &grid](size_t i) { + const ExPolygons &upper = slices[i + 1]; + const ExPolygons &lower = slices[i]; + + ExPolygons dff1 = diff_ex(lower, upper); + ExPolygons dff2 = diff_ex(upper, lower); + layers[i].merge(triangulate_expolygons_3d(dff1, grid[i], NORMALS_UP)); + layers[i].merge(triangulate_expolygons_3d(dff2, grid[i], NORMALS_DOWN)); + layers[i].merge(straight_walls(upper, grid[i], grid[i + 1])); + + }); + + sla::Contour3D ret = tbb::parallel_reduce( + tbb::blocked_range(layers.begin(), layers.end()), + sla::Contour3D{}, + [](const tbb::blocked_range& r, sla::Contour3D init) { + for(auto it = r.begin(); it != r.end(); ++it ) init.merge(*it); + return init; + }, + []( const sla::Contour3D &a, const sla::Contour3D &b ) { + sla::Contour3D res{a}; res.merge(b); return res; + }); + + ret.merge(triangulate_expolygons_3d(slices.front(), zmin, NORMALS_DOWN)); + ret.merge(straight_walls(slices.front(), zmin, grid.front())); + ret.merge(triangulate_expolygons_3d(slices.back(), grid.back(), NORMALS_UP)); + + return ret; +} + void slices_to_triangle_mesh(TriangleMesh & mesh, const std::vector &slices, double zmin, double lh, double ilh) { - sla::Contour3D cntr3d; - double h = zmin; + std::vector wall_meshes(slices.size()); + std::vector grid(slices.size(), zmin + ilh); - auto it = slices.begin(), xt = std::next(it); - cntr3d.merge(triangulate_expolygons_3d(*it, h, NORMALS_DOWN)); - cntr3d.merge(straight_walls(*it, h, h + ilh)); - h += ilh; - while (xt != slices.end()) { - ExPolygons dff1 = diff_ex(*it, *xt); - ExPolygons dff2 = diff_ex(*xt, *it); - cntr3d.merge(triangulate_expolygons_3d(dff1, h, NORMALS_UP)); - cntr3d.merge(triangulate_expolygons_3d(dff2, h, NORMALS_UP)); - cntr3d.merge(straight_walls(*xt, h, h + lh)); - h += lh; - ++it; ++xt; - } + for (size_t i = 1; i < grid.size(); ++i) grid[i] = grid[i - 1] + lh; - cntr3d.merge(triangulate_expolygons_3d(*it, h, NORMALS_UP)); - - mesh.merge(sla::to_triangle_mesh(cntr3d)); + sla::Contour3D cntr = slices_to_triangle_mesh(slices, zmin, grid); + mesh.merge(sla::to_triangle_mesh(cntr)); + mesh.repaired = true; mesh.require_shared_vertices(); } diff --git a/src/libslic3r/Zipper.cpp b/src/libslic3r/Zipper.cpp index ec9d3aa16..02f022083 100644 --- a/src/libslic3r/Zipper.cpp +++ b/src/libslic3r/Zipper.cpp @@ -17,90 +17,14 @@ namespace Slic3r { -class Zipper::Impl { +class Zipper::Impl: public MZ_Archive { public: - mz_zip_archive arch; std::string m_zipname; - static std::string get_errorstr(mz_zip_error mz_err) - { - switch (mz_err) - { - case MZ_ZIP_NO_ERROR: - return "no error"; - case MZ_ZIP_UNDEFINED_ERROR: - return L("undefined error"); - case MZ_ZIP_TOO_MANY_FILES: - return L("too many files"); - case MZ_ZIP_FILE_TOO_LARGE: - return L("file too large"); - case MZ_ZIP_UNSUPPORTED_METHOD: - return L("unsupported method"); - case MZ_ZIP_UNSUPPORTED_ENCRYPTION: - return L("unsupported encryption"); - case MZ_ZIP_UNSUPPORTED_FEATURE: - return L("unsupported feature"); - case MZ_ZIP_FAILED_FINDING_CENTRAL_DIR: - return L("failed finding central directory"); - case MZ_ZIP_NOT_AN_ARCHIVE: - return L("not a ZIP archive"); - case MZ_ZIP_INVALID_HEADER_OR_CORRUPTED: - return L("invalid header or archive is corrupted"); - case MZ_ZIP_UNSUPPORTED_MULTIDISK: - return L("unsupported multidisk archive"); - case MZ_ZIP_DECOMPRESSION_FAILED: - return L("decompression failed or archive is corrupted"); - case MZ_ZIP_COMPRESSION_FAILED: - return L("compression failed"); - case MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE: - return L("unexpected decompressed size"); - case MZ_ZIP_CRC_CHECK_FAILED: - return L("CRC-32 check failed"); - case MZ_ZIP_UNSUPPORTED_CDIR_SIZE: - return L("unsupported central directory size"); - case MZ_ZIP_ALLOC_FAILED: - return L("allocation failed"); - case MZ_ZIP_FILE_OPEN_FAILED: - return L("file open failed"); - case MZ_ZIP_FILE_CREATE_FAILED: - return L("file create failed"); - case MZ_ZIP_FILE_WRITE_FAILED: - return L("file write failed"); - case MZ_ZIP_FILE_READ_FAILED: - return L("file read failed"); - case MZ_ZIP_FILE_CLOSE_FAILED: - return L("file close failed"); - case MZ_ZIP_FILE_SEEK_FAILED: - return L("file seek failed"); - case MZ_ZIP_FILE_STAT_FAILED: - return L("file stat failed"); - case MZ_ZIP_INVALID_PARAMETER: - return L("invalid parameter"); - case MZ_ZIP_INVALID_FILENAME: - return L("invalid filename"); - case MZ_ZIP_BUF_TOO_SMALL: - return L("buffer too small"); - case MZ_ZIP_INTERNAL_ERROR: - return L("internal error"); - case MZ_ZIP_FILE_NOT_FOUND: - return L("file not found"); - case MZ_ZIP_ARCHIVE_TOO_LARGE: - return L("archive is too large"); - case MZ_ZIP_VALIDATION_FAILED: - return L("validation failed"); - case MZ_ZIP_WRITE_CALLBACK_FAILED: - return L("write calledback failed"); - default: - break; - } - - return "unknown error"; - } - std::string formatted_errorstr() const { return L("Error with zip archive") + " " + m_zipname + ": " + - get_errorstr(arch.m_last_error) + "!"; + get_errorstr() + "!"; } SLIC3R_NORETURN void blow_up() const diff --git a/src/libslic3r/Zipper.hpp b/src/libslic3r/Zipper.hpp index d203ea7b2..bbaf2f05e 100644 --- a/src/libslic3r/Zipper.hpp +++ b/src/libslic3r/Zipper.hpp @@ -28,7 +28,7 @@ public: // Will blow up in a runtime exception if the file cannot be created. explicit Zipper(const std::string& zipfname, - e_compression level = NO_COMPRESSION); + e_compression level = FAST_COMPRESSION); ~Zipper(); // No copies allwed, this is a file resource... diff --git a/src/libslic3r/miniz_extension.cpp b/src/libslic3r/miniz_extension.cpp index 17cc136fc..76b4cb4e5 100644 --- a/src/libslic3r/miniz_extension.cpp +++ b/src/libslic3r/miniz_extension.cpp @@ -1,9 +1,17 @@ +#include + #include "miniz_extension.hpp" #if defined(_MSC_VER) || defined(__MINGW64__) #include "boost/nowide/cstdio.hpp" #endif +#include "I18N.hpp" + +//! macro used to mark string used at localization, +//! return same string +#define L(s) Slic3r::I18N::translate(s) + namespace Slic3r { namespace { @@ -68,4 +76,84 @@ bool open_zip_writer(mz_zip_archive *zip, const std::string &fname) bool close_zip_reader(mz_zip_archive *zip) { return close_zip(zip, true); } bool close_zip_writer(mz_zip_archive *zip) { return close_zip(zip, false); } +MZ_Archive::MZ_Archive() +{ + mz_zip_zero_struct(&arch); } + +std::string MZ_Archive::get_errorstr(mz_zip_error mz_err) +{ + switch (mz_err) + { + case MZ_ZIP_NO_ERROR: + return "no error"; + case MZ_ZIP_UNDEFINED_ERROR: + return L("undefined error"); + case MZ_ZIP_TOO_MANY_FILES: + return L("too many files"); + case MZ_ZIP_FILE_TOO_LARGE: + return L("file too large"); + case MZ_ZIP_UNSUPPORTED_METHOD: + return L("unsupported method"); + case MZ_ZIP_UNSUPPORTED_ENCRYPTION: + return L("unsupported encryption"); + case MZ_ZIP_UNSUPPORTED_FEATURE: + return L("unsupported feature"); + case MZ_ZIP_FAILED_FINDING_CENTRAL_DIR: + return L("failed finding central directory"); + case MZ_ZIP_NOT_AN_ARCHIVE: + return L("not a ZIP archive"); + case MZ_ZIP_INVALID_HEADER_OR_CORRUPTED: + return L("invalid header or archive is corrupted"); + case MZ_ZIP_UNSUPPORTED_MULTIDISK: + return L("unsupported multidisk archive"); + case MZ_ZIP_DECOMPRESSION_FAILED: + return L("decompression failed or archive is corrupted"); + case MZ_ZIP_COMPRESSION_FAILED: + return L("compression failed"); + case MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE: + return L("unexpected decompressed size"); + case MZ_ZIP_CRC_CHECK_FAILED: + return L("CRC-32 check failed"); + case MZ_ZIP_UNSUPPORTED_CDIR_SIZE: + return L("unsupported central directory size"); + case MZ_ZIP_ALLOC_FAILED: + return L("allocation failed"); + case MZ_ZIP_FILE_OPEN_FAILED: + return L("file open failed"); + case MZ_ZIP_FILE_CREATE_FAILED: + return L("file create failed"); + case MZ_ZIP_FILE_WRITE_FAILED: + return L("file write failed"); + case MZ_ZIP_FILE_READ_FAILED: + return L("file read failed"); + case MZ_ZIP_FILE_CLOSE_FAILED: + return L("file close failed"); + case MZ_ZIP_FILE_SEEK_FAILED: + return L("file seek failed"); + case MZ_ZIP_FILE_STAT_FAILED: + return L("file stat failed"); + case MZ_ZIP_INVALID_PARAMETER: + return L("invalid parameter"); + case MZ_ZIP_INVALID_FILENAME: + return L("invalid filename"); + case MZ_ZIP_BUF_TOO_SMALL: + return L("buffer too small"); + case MZ_ZIP_INTERNAL_ERROR: + return L("internal error"); + case MZ_ZIP_FILE_NOT_FOUND: + return L("file not found"); + case MZ_ZIP_ARCHIVE_TOO_LARGE: + return L("archive is too large"); + case MZ_ZIP_VALIDATION_FAILED: + return L("validation failed"); + case MZ_ZIP_WRITE_CALLBACK_FAILED: + return L("write calledback failed"); + default: + break; + } + + return "unknown error"; +} + +} // namespace Slic3r diff --git a/src/libslic3r/miniz_extension.hpp b/src/libslic3r/miniz_extension.hpp index 8d0967cbc..006226bf2 100644 --- a/src/libslic3r/miniz_extension.hpp +++ b/src/libslic3r/miniz_extension.hpp @@ -11,6 +11,25 @@ bool open_zip_writer(mz_zip_archive *zip, const std::string &fname_utf8); bool close_zip_reader(mz_zip_archive *zip); bool close_zip_writer(mz_zip_archive *zip); -} +class MZ_Archive { +public: + mz_zip_archive arch; + + MZ_Archive(); + + static std::string get_errorstr(mz_zip_error mz_err); + + std::string get_errorstr() const + { + return get_errorstr(arch.m_last_error) + "!"; + } + + bool is_alive() const + { + return arch.m_zip_mode != MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED; + } +}; + +} // namespace Slic3r #endif // MINIZ_EXTENSION_HPP diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index c0853fb65..1f1284a9e 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -182,8 +182,8 @@ set(SLIC3R_GUI_SOURCES Utils/HexFile.cpp Utils/HexFile.hpp Utils/Thread.hpp - Utils/SLAZipFileImport.hpp - Utils/SLAZipFileImport.cpp + Utils/SLAImport.hpp + Utils/SLAImport.cpp ) if (APPLE) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 6297cace3..1f2ce0221 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2071,37 +2071,40 @@ void ObjectList::load_shape_object(const std::string& type_name) // Create mesh BoundingBoxf3 bb; TriangleMesh mesh = create_mesh(type_name, bb); + load_mesh_object(mesh, _(L("Shape")) + "-" + _(type_name)); +} +void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name) +{ // Add mesh to model as a new object Model& model = wxGetApp().plater()->model(); - const wxString name = _(L("Shape")) + "-" + _(type_name); #ifdef _DEBUG check_model_ids_validity(model); #endif /* _DEBUG */ - + std::vector object_idxs; ModelObject* new_object = model.add_object(); new_object->name = into_u8(name); new_object->add_instance(); // each object should have at list one instance - + ModelVolume* new_volume = new_object->add_volume(mesh); new_volume->name = into_u8(name); // set a default extruder value, since user can't add it manually new_volume->config.set_key_value("extruder", new ConfigOptionInt(0)); new_object->invalidate_bounding_box(); - + new_object->center_around_origin(); new_object->ensure_on_bed(); - + const BoundingBoxf bed_shape = wxGetApp().plater()->bed_shape_bb(); new_object->instances[0]->set_offset(Slic3r::to_3d(bed_shape.center().cast(), -new_object->origin_translation(2))); - + object_idxs.push_back(model.objects.size() - 1); #ifdef _DEBUG check_model_ids_validity(model); #endif /* _DEBUG */ - + paste_objects_into_list(object_idxs); #ifdef _DEBUG diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 609411cd5..72e130737 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -24,6 +24,7 @@ class ConfigOptionsGroup; class DynamicPrintConfig; class ModelObject; class ModelVolume; +class TriangleMesh; enum class ModelVolumeType : int; // FIXME: broken build on mac os because of this is missing: @@ -265,6 +266,7 @@ public: void load_part(ModelObject* model_object, std::vector> &volumes_info, ModelVolumeType type); void load_generic_subobject(const std::string& type_name, const ModelVolumeType type); void load_shape_object(const std::string &type_name); + void load_mesh_object(const TriangleMesh &mesh, const wxString &name); void del_object(const int obj_idx); void del_subobject_item(wxDataViewItem& item); void del_settings_from_config(const wxDataViewItem& parent_item); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 75ef32762..f9fe06a85 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -73,7 +73,7 @@ #include "../Utils/PrintHost.hpp" #include "../Utils/FixModelByWin10.hpp" #include "../Utils/UndoRedo.hpp" -#include "../Utils/SLAZipFileImport.hpp" +#include "../Utils/SLAImport.hpp" #include "RemovableDriveManager.hpp" #if ENABLE_NON_STATIC_CANVAS_MANAGER #ifdef __APPLE__ @@ -4263,11 +4263,7 @@ void Plater::import_sl1_archive() if (dlg.ShowModal() == wxID_OK) { try { TriangleMesh mesh = import_model_from_sla_zip(dlg.GetPath()); - ModelObject * obj = p->model.add_object(wxFileName(dlg.GetPath()).GetName(), "", mesh); - if (obj) { - obj->add_instance(); - update(); - } + p->sidebar->obj_list()->load_mesh_object(mesh, wxFileName(dlg.GetPath()).GetName()); } catch (std::exception &ex) { show_error(this, ex.what()); } diff --git a/src/slic3r/Utils/SLAImport.cpp b/src/slic3r/Utils/SLAImport.cpp new file mode 100644 index 000000000..442025a77 --- /dev/null +++ b/src/slic3r/Utils/SLAImport.cpp @@ -0,0 +1,314 @@ +#include "SLAImport.hpp" + +#include + +#include "libslic3r/SlicesToTriangleMesh.hpp" +#include "libslic3r/MarchingSquares.hpp" +#include "libslic3r/ClipperUtils.hpp" +#include "libslic3r/MTUtils.hpp" +#include "libslic3r/PrintConfig.hpp" +#include "libslic3r/SLA/RasterBase.hpp" +#include "libslic3r/miniz_extension.hpp" + +#include +#include +#include + +#include +#include + +namespace marchsq { + +// Specialize this struct to register a raster type for the Marching squares alg +template<> struct _RasterTraits { + using Rst = wxImage; + + // The type of pixel cell in the raster + using ValueType = uint8_t; + + // Value at a given position + static uint8_t get(const Rst &rst, size_t row, size_t col) + { + return rst.GetRed(col, row); + } + + // Number of rows and cols of the raster + static size_t rows(const Rst &rst) { return rst.GetHeight(); } + static size_t cols(const Rst &rst) { return rst.GetWidth(); } +}; + +} // namespace marchsq + +namespace Slic3r { + +namespace { + +struct ArchiveData { + boost::property_tree::ptree profile, config; + std::vector images; +}; + +static const constexpr char *CONFIG_FNAME = "config.ini"; +static const constexpr char *PROFILE_FNAME = "prusaslicer.ini"; + +boost::property_tree::ptree read_ini(const mz_zip_archive_file_stat &entry, + MZ_Archive & zip) +{ + std::string buf(size_t(entry.m_uncomp_size), '\0'); + + 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()); + + boost::property_tree::ptree tree; + std::stringstream ss(buf); + boost::property_tree::read_ini(ss, tree); + return tree; +} + +sla::EncodedRaster read_png(const mz_zip_archive_file_stat &entry, + MZ_Archive & zip, + const std::string & name) +{ + std::vector buf(entry.m_uncomp_size); + + 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()); + + return sla::EncodedRaster(std::move(buf), + name.empty() ? entry.m_filename : name); +} + +ArchiveData extract_sla_archive(const std::string &zipfname, + const std::string &exclude) +{ + ArchiveData arch; + + // Little RAII + struct Arch: public MZ_Archive { + Arch(const std::string &fname) { + if (!open_zip_reader(&arch, fname)) + throw std::runtime_error(get_errorstr()); + } + + ~Arch() { close_zip_reader(&arch); } + } zip (zipfname); + + mz_uint num_entries = mz_zip_reader_get_num_files(&zip.arch); + + for (mz_uint i = 0; i < num_entries; ++i) + { + mz_zip_archive_file_stat entry; + + if (mz_zip_reader_file_stat(&zip.arch, i, &entry)) + { + std::string name = entry.m_filename; + boost::algorithm::to_lower(name); + + if (boost::algorithm::contains(name, exclude)) continue; + + if (name == CONFIG_FNAME) arch.config = read_ini(entry, zip); + if (name == PROFILE_FNAME) arch.profile = read_ini(entry, zip); + + if (boost::filesystem::path(name).extension().string() == ".png") { + auto it = std::lower_bound( + arch.images.begin(), arch.images.end(), sla::EncodedRaster({}, name), + [](const sla::EncodedRaster &r1, const sla::EncodedRaster &r2) { + return std::less()(r1.extension(), r2.extension()); + }); + + arch.images.insert(it, read_png(entry, zip, name)); + } + } + } + + return arch; +} + +ExPolygons rings_to_expolygons(const std::vector &rings, + double px_w, double px_h) +{ + ExPolygons polys; polys.reserve(rings.size()); + + for (const marchsq::Ring &ring : rings) { + Polygon poly; Points &pts = poly.points; + pts.reserve(ring.size()); + + for (const marchsq::Coord &crd : ring) + pts.emplace_back(scaled(crd.c * px_w), scaled(crd.r * px_h)); + + polys.emplace_back(poly); + } + + // reverse the raster transformations + return union_ex(polys); +} + +template void foreach_vertex(ExPolygon &poly, Fn &&fn) +{ + for (auto &p : poly.contour.points) fn(p); + for (auto &h : poly.holes) + for (auto &p : h.points) fn(p); +} + +void invert_raster_trafo(ExPolygons & expolys, + const sla::RasterBase::Trafo &trafo, + coord_t width, + coord_t height) +{ + for (auto &expoly : expolys) { + if (trafo.mirror_y) + foreach_vertex(expoly, [height](Point &p) {p.y() = height - p.y(); }); + + if (trafo.mirror_x) + foreach_vertex(expoly, [width](Point &p) {p.x() = width - p.x(); }); + + expoly.translate(-trafo.center_x, -trafo.center_y); + + if (trafo.flipXY) + foreach_vertex(expoly, [](Point &p) { std::swap(p.x(), p.y()); }); + + if ((trafo.mirror_x + trafo.mirror_y + trafo.flipXY) % 2) { + expoly.contour.reverse(); + for (auto &h : expoly.holes) h.reverse(); + } + } +} + +struct RasterParams { + sla::RasterBase::Trafo trafo; // Raster transformations + coord_t width, height; // scaled raster dimensions (not resolution) + double px_h, px_w; // pixel dimesions + marchsq::Coord win; // marching squares window size +}; + +RasterParams get_raster_params(const DynamicPrintConfig &cfg) +{ + auto *opt_disp_cols = cfg.option("display_pixels_x"); + auto *opt_disp_rows = cfg.option("display_pixels_y"); + auto *opt_disp_w = cfg.option("display_width"); + auto *opt_disp_h = cfg.option("display_height"); + auto *opt_mirror_x = cfg.option("display_mirror_x"); + auto *opt_mirror_y = cfg.option("display_mirror_y"); + auto *opt_orient = cfg.option>("display_orientation"); + + 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"); + + RasterParams rstp; + + rstp.px_w = opt_disp_w->value / (opt_disp_cols->value - 1); + rstp.px_h = opt_disp_h->value / (opt_disp_rows->value - 1); + + sla::RasterBase::Trafo trafo{opt_orient->value == sladoLandscape ? + sla::RasterBase::roLandscape : + sla::RasterBase::roPortrait, + {opt_mirror_x->value, opt_mirror_y->value}}; + + rstp.height = scaled(opt_disp_h->value); + rstp.width = scaled(opt_disp_w->value); + + return rstp; +} + +struct SliceParams { double layerh = 0., initial_layerh = 0.; }; + +SliceParams get_slice_params(const DynamicPrintConfig &cfg) +{ + auto *opt_layerh = cfg.option("layer_height"); + auto *opt_init_layerh = cfg.option("initial_layer_height"); + + if (!opt_layerh || !opt_init_layerh) + throw std::runtime_error("Invalid SL1 file"); + + return SliceParams{opt_layerh->getFloat(), opt_init_layerh->getFloat()}; +} + +std::vector extract_slices_from_sla_archive( + ArchiveData & arch, + const RasterParams & rstp, + std::function progr) +{ + auto jobdir = arch.config.get("jobDir"); + for (auto &c : jobdir) c = std::tolower(c); + + std::vector slices(arch.images.size()); + + struct Status + { + double incr, val, prev; + bool stop = false; + tbb::spin_mutex mutex; + } st {100. / slices.size(), 0., 0.}; + + tbb::parallel_for(size_t(0), arch.images.size(), + [&arch, &slices, &st, &rstp, progr](size_t i) { + // Status indication guarded with the spinlock + { + std::lock_guard lck(st.mutex); + if (st.stop) return; + + st.val += st.incr; + double curr = std::round(st.val); + if (curr > st.prev) { + st.prev = curr; + st.stop = !progr(int(curr)); + } + } + + auto &buf = arch.images[i]; + wxMemoryInputStream stream{buf.data(), buf.size()}; + wxImage img{stream}; + + auto rings = marchsq::execute(img, 128, rstp.win); + ExPolygons expolys = rings_to_expolygons(rings, rstp.px_w, rstp.px_h); + + // Invert the raster transformations indicated in + // the profile metadata + invert_raster_trafo(expolys, rstp.trafo, rstp.width, rstp.height); + + slices[i] = std::move(expolys); + }); + + if (st.stop) slices = {}; + + return slices; +} + +} // namespace + +void import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out) +{ + ArchiveData arch = extract_sla_archive(zipfname, "png"); + out.load(arch.profile); +} + +void import_sla_archive( + const std::string & zipfname, + Vec2i windowsize, + TriangleMesh & out, + DynamicPrintConfig & profile, + std::function progr) +{ + // Ensure minimum window size for marching squares + windowsize.x() = std::max(2, windowsize.x()); + windowsize.y() = std::max(2, windowsize.y()); + + ArchiveData arch = extract_sla_archive(zipfname, "thumbnail"); + profile.load(arch.profile); + + RasterParams rstp = get_raster_params(profile); + rstp.win = {windowsize.y(), windowsize.x()}; + + SliceParams slicp = get_slice_params(profile); + + std::vector slices = + extract_slices_from_sla_archive(arch, rstp, progr); + + if (!slices.empty()) + out = slices_to_triangle_mesh(slices, 0, slicp.layerh, slicp.initial_layerh); +} + +} // namespace Slic3r diff --git a/src/slic3r/Utils/SLAImport.hpp b/src/slic3r/Utils/SLAImport.hpp new file mode 100644 index 000000000..a819bd7e7 --- /dev/null +++ b/src/slic3r/Utils/SLAImport.hpp @@ -0,0 +1,36 @@ +#ifndef SLAIMPORT_HPP +#define SLAIMPORT_HPP + +#include + +#include +#include +#include + +namespace Slic3r { + +class TriangleMesh; +class DynamicPrintConfig; + +void import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out); + +void import_sla_archive( + const std::string & zipfname, + Vec2i windowsize, + TriangleMesh & out, + DynamicPrintConfig & profile, + std::function progr = [](int) { return true; }); + +inline void import_sla_archive( + const std::string & zipfname, + Vec2i windowsize, + TriangleMesh & out, + std::function progr = [](int) { return true; }) +{ + DynamicPrintConfig profile; + import_sla_archive(zipfname, windowsize, out, profile, progr); +} + +} + +#endif // SLAIMPORT_HPP diff --git a/src/slic3r/Utils/SLAZipFileImport.cpp b/src/slic3r/Utils/SLAZipFileImport.cpp deleted file mode 100644 index 6543e8674..000000000 --- a/src/slic3r/Utils/SLAZipFileImport.cpp +++ /dev/null @@ -1,144 +0,0 @@ -#include "SLAZipFileImport.hpp" - -#include "libslic3r/SlicesToTriangleMesh.hpp" -#include "libslic3r/MarchingSquares.hpp" -#include "libslic3r/ClipperUtils.hpp" -#include "libslic3r/MTUtils.hpp" -#include "libslic3r/PrintConfig.hpp" - -#include -#include -#include -#include -#include - -#include - -#include - -namespace marchsq { - -// Specialize this struct to register a raster type for the Marching squares alg -template<> struct _RasterTraits { - using Rst = wxImage; - - // The type of pixel cell in the raster - using ValueType = uint8_t; - - // Value at a given position - static uint8_t get(const Rst &rst, size_t row, size_t col) { return rst.GetRed(col, row); } - - // Number of rows and cols of the raster - static size_t rows(const Rst &rst) { return rst.GetHeight(); } - static size_t cols(const Rst &rst) { return rst.GetWidth(); } -}; - -} // namespace marchsq - -namespace Slic3r { - -ExPolygons rings_to_expolygons(const std::vector &rings, - double px_w, double px_h) -{ - ExPolygons polys; polys.reserve(rings.size()); - - for (const marchsq::Ring &ring : rings) { - Polygon poly; Points &pts = poly.points; - pts.reserve(ring.size()); - - for (const marchsq::Coord &crd : ring) - pts.emplace_back(scaled(crd.c * px_w), scaled(crd.r * px_h)); - - polys.emplace_back(poly); - } - - // reverse the raster transformations - return union_ex(polys); -} - -TriangleMesh import_model_from_sla_zip(const wxString &zipfname) -{ - wxFileInputStream in(zipfname); - wxZipInputStream zip(in, wxConvUTF8); - - std::map files; - - while (auto entry = std::unique_ptr(zip.GetNextEntry())) { - auto fname = wxFileName(entry->GetName()); - wxString name_lo = fname.GetFullName().Lower(); - - if (fname.IsDir() || name_lo.Contains("thumbnail")) continue; - - if (!zip.OpenEntry(*entry)) - throw std::runtime_error("Cannot read archive"); - - wxMemoryOutputStream &stream = files[name_lo.ToStdString()]; - zip.Read(stream); - std::cout << name_lo << " read bytes: " << zip.LastRead() << std::endl; - if (!zip.LastRead()) std::cout << zip.GetLastError() << std::endl; - } - - using boost::property_tree::ptree; - - auto load_ini = [&files](const std::string &key, ptree &tree) { - auto it = files.find(key); - if (it != files.end()) { - wxString str; - wxStringOutputStream oss{&str}; - wxMemoryInputStream inp{it->second}; - oss.Write(inp); - std::stringstream iss(str.ToStdString()); - boost::property_tree::read_ini(iss, tree); - files.erase(it); - } else { - throw std::runtime_error(key + " is missing"); - } - }; - - ptree profile_tree, config; - load_ini("prusaslicer.ini", profile_tree); - load_ini("config.ini", config); - - DynamicPrintConfig profile; - profile.load(profile_tree); - - size_t disp_cols = profile.opt_int("display_pixels_x"); - size_t disp_rows = profile.opt_int("display_pixels_y"); - double disp_w = profile.opt_float("display_width"); - double disp_h = profile.opt_float("display_height"); - double px_w = disp_w / disp_cols; - double px_h = disp_h / disp_rows; - - auto jobdir = config.get("jobDir"); - for (auto &c : jobdir) c = std::tolower(c); - - for (auto it = files.begin(); it != files.end();) - if (it->first.find(jobdir) == std::string::npos || - wxFileName(it->first).GetExt().Lower() != "png") - it = files.erase(it); - else ++it; - - std::vector slices(files.size()); - size_t i = 0; - for (auto &item : files) { - wxMemoryOutputStream &imagedata = item.second; - wxMemoryInputStream stream{imagedata}; - wxImage img{stream, "image/png"}; - - std::cout << img.GetWidth() << " " << img.GetHeight() << std::endl; - - auto rings = marchsq::execute(img, 128); - slices[i++] = rings_to_expolygons(rings, px_w, px_h); - } - - TriangleMesh out; - if (!slices.empty()) { - double lh = profile.opt_float("layer_height"); - double ilh = profile.opt_float("initial_layer_height"); - out = slices_to_triangle_mesh(slices, 0, lh, ilh); - } - - return out; -} - -} // namespace Slic3r diff --git a/src/slic3r/Utils/SLAZipFileImport.hpp b/src/slic3r/Utils/SLAZipFileImport.hpp deleted file mode 100644 index 4e36f86f4..000000000 --- a/src/slic3r/Utils/SLAZipFileImport.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef SLAZIPFILEIMPORT_HPP -#define SLAZIPFILEIMPORT_HPP - -#include "libslic3r/TriangleMesh.hpp" - -#include - -namespace Slic3r { - -TriangleMesh import_model_from_sla_zip(const wxString &zipfname); - -} - -#endif // SLAZIPFILEIMPORT_HPP diff --git a/tests/libslic3r/test_marchingsquares.cpp b/tests/libslic3r/test_marchingsquares.cpp index ce1c0e3ff..9912ff2ca 100644 --- a/tests/libslic3r/test_marchingsquares.cpp +++ b/tests/libslic3r/test_marchingsquares.cpp @@ -1,3 +1,5 @@ +#define NOMINMAX + #include #include @@ -5,6 +7,7 @@ #include #include + #include #include #include @@ -17,6 +20,11 @@ using namespace Slic3r; +static double area(const sla::RasterBase::PixelDim &pxd) +{ + return pxd.w_mm * pxd.h_mm; +} + static Slic3r::sla::RasterGrayscaleAA create_raster( const sla::RasterBase::Resolution &res, double disp_w = 100., @@ -26,10 +34,8 @@ static Slic3r::sla::RasterGrayscaleAA create_raster( auto bb = BoundingBox({0, 0}, {scaled(disp_w), scaled(disp_h)}); sla::RasterBase::Trafo trafo; -// trafo.center_x = bb.center().x(); -// trafo.center_y = bb.center().y(); -// trafo.center_x = scaled(pixdim.w_mm); -// trafo.center_y = scaled(pixdim.h_mm); + trafo.center_x = bb.center().x(); + trafo.center_y = bb.center().y(); return sla::RasterGrayscaleAA{res, pixdim, trafo, agg::gamma_threshold(.5)}; } @@ -78,10 +84,13 @@ static ExPolygons circle_with_hole(double r, Point center = {0, 0}) { return {poly}; } +static const Vec2i W4x4 = {4, 4}; +static const Vec2i W2x2 = {2, 2}; + template static void test_expolys(Rst && rst, const ExPolygons & ref, - float accuracy, + Vec2i window, const std::string &name = "test") { for (const ExPolygon &expoly : ref) rst.draw(expoly); @@ -90,12 +99,23 @@ static void test_expolys(Rst && rst, out << rst.encode(sla::PNGRasterEncoder{}); out.close(); - ExPolygons extracted = sla::raster_to_polygons(rst, accuracy); + ExPolygons extracted = sla::raster_to_polygons(rst, window); SVG svg(name + ".svg"); svg.draw(extracted); + svg.draw(ref, "green"); svg.Close(); + double max_rel_err = 0.1; + sla::RasterBase::PixelDim pxd = rst.pixel_dimensions(); + double max_abs_err = area(pxd) * scaled(1.) * scaled(1.); + + BoundingBox ref_bb; + for (auto &expoly : ref) ref_bb.merge(expoly.contour.bounding_box()); + + double max_displacement = 4. * (std::pow(pxd.h_mm, 2) + std::pow(pxd.w_mm, 2)); + max_displacement *= scaled(1.) * scaled(1.); + REQUIRE(extracted.size() == ref.size()); for (size_t i = 0; i < ref.size(); ++i) { REQUIRE(extracted[i].contour.is_counter_clockwise()); @@ -104,7 +124,16 @@ static void test_expolys(Rst && rst, for (auto &h : extracted[i].holes) REQUIRE(h.is_clockwise()); double refa = ref[i].area(); - REQUIRE(std::abs(extracted[i].area() - refa) < 0.1 * refa); + double abs_err = std::abs(extracted[i].area() - refa); + double rel_err = abs_err / refa; + + REQUIRE((rel_err <= max_rel_err || abs_err <= max_abs_err)); + + BoundingBox bb; + for (auto &expoly : extracted) bb.merge(expoly.contour.bounding_box()); + + Point d = bb.center() - ref_bb.center(); + REQUIRE(double(d.transpose() * d) <= max_displacement); } } @@ -130,22 +159,36 @@ TEST_CASE("Marching squares directions", "[MarchingSquares]") { REQUIRE(step(crd, marchsq::__impl::Dir::up).c == 1); } +TEST_CASE("Fully covered raster should result in a rectangle", "[MarchingSquares]") { + auto rst = create_raster({4, 4}, 4., 4.); + + ExPolygon rect = square(4); + + SECTION("Full accuracy") { + test_expolys(rst, {rect}, W2x2, "fully_covered_full_acc"); + } + + SECTION("Half accuracy") { + test_expolys(rst, {rect}, W4x4, "fully_covered_half_acc"); + } +} + TEST_CASE("4x4 raster with one ring", "[MarchingSquares]") { sla::RasterBase::PixelDim pixdim{1, 1}; // We need one additional row and column to detect edges - sla::RasterGrayscaleAA rst{{5, 5}, pixdim, {}, agg::gamma_threshold(.5)}; + sla::RasterGrayscaleAA rst{{4, 4}, pixdim, {}, agg::gamma_threshold(.5)}; // Draw a triangle from individual pixels + rst.draw(square(1., {0500000, 0500000})); + rst.draw(square(1., {1500000, 0500000})); + rst.draw(square(1., {2500000, 0500000})); + rst.draw(square(1., {1500000, 1500000})); rst.draw(square(1., {2500000, 1500000})); - rst.draw(square(1., {3500000, 1500000})); rst.draw(square(1., {2500000, 2500000})); - rst.draw(square(1., {3500000, 2500000})); - - rst.draw(square(1., {3500000, 3500000})); std::fstream out("4x4.png", std::ios::out); out << rst.encode(sla::PNGRasterEncoder{}); @@ -222,73 +265,59 @@ TEST_CASE("Square with hole in the middle", "[MarchingSquares]") { ExPolygons inp = {square_with_hole(50.)}; SECTION("Proportional raster, 1x1 mm pixel size, full accuracy") { - test_expolys(create_raster({100, 100}, 100., 100.), inp, 1.f, "square_with_hole_proportional_1x1_mm_px_full"); + test_expolys(create_raster({100, 100}, 100., 100.), inp, W2x2, "square_with_hole_proportional_1x1_mm_px_full"); } SECTION("Proportional raster, 1x1 mm pixel size, half accuracy") { - test_expolys(create_raster({100, 100}, 100., 100.), inp, .5f, "square_with_hole_proportional_1x1_mm_px_half"); + test_expolys(create_raster({100, 100}, 100., 100.), inp, W4x4, "square_with_hole_proportional_1x1_mm_px_half"); } SECTION("Landscape raster, 1x1 mm pixel size, full accuracy") { - test_expolys(create_raster({150, 100}, 150., 100.), inp, 1.f, "square_with_hole_landsc_1x1_mm_px_full"); + test_expolys(create_raster({150, 100}, 150., 100.), inp, W2x2, "square_with_hole_landsc_1x1_mm_px_full"); } SECTION("Landscape raster, 1x1 mm pixel size, half accuracy") { - test_expolys(create_raster({150, 100}, 150., 100.), inp, .5f, "square_with_hole_landsc_1x1_mm_px_half"); + test_expolys(create_raster({150, 100}, 150., 100.), inp, W4x4, "square_with_hole_landsc_1x1_mm_px_half"); } SECTION("Portrait raster, 1x1 mm pixel size, full accuracy") { - test_expolys(create_raster({100, 150}, 100., 150.), inp, 1.f, "square_with_hole_portrait_1x1_mm_px_full"); + test_expolys(create_raster({100, 150}, 100., 150.), inp, W2x2, "square_with_hole_portrait_1x1_mm_px_full"); } SECTION("Portrait raster, 1x1 mm pixel size, half accuracy") { - test_expolys(create_raster({100, 150}, 100., 150.), inp, .5f, "square_with_hole_portrait_1x1_mm_px_half"); + test_expolys(create_raster({100, 150}, 100., 150.), inp, W4x4, "square_with_hole_portrait_1x1_mm_px_half"); } SECTION("Proportional raster, 2x2 mm pixel size, full accuracy") { - test_expolys(create_raster({200, 200}, 100., 100.), inp, 1.f, "square_with_hole_proportional_2x2_mm_px_full"); + test_expolys(create_raster({200, 200}, 100., 100.), inp, W2x2, "square_with_hole_proportional_2x2_mm_px_full"); } SECTION("Proportional raster, 2x2 mm pixel size, half accuracy") { - test_expolys(create_raster({200, 200}, 100., 100.), inp, .5f, "square_with_hole_proportional_2x2_mm_px_half"); + test_expolys(create_raster({200, 200}, 100., 100.), inp, W4x4, "square_with_hole_proportional_2x2_mm_px_half"); } SECTION("Proportional raster, 0.5x0.5 mm pixel size, full accuracy") { - test_expolys(create_raster({50, 50}, 100., 100.), inp, 1.f, "square_with_hole_proportional_0.5x0.5_mm_px_full"); + test_expolys(create_raster({50, 50}, 100., 100.), inp, W2x2, "square_with_hole_proportional_0.5x0.5_mm_px_full"); } SECTION("Proportional raster, 0.5x0.5 mm pixel size, half accuracy") { - test_expolys(create_raster({50, 50}, 100., 100.), inp, .5f, "square_with_hole_proportional_0.5x0.5_mm_px_half"); + test_expolys(create_raster({50, 50}, 100., 100.), inp, W4x4, "square_with_hole_proportional_0.5x0.5_mm_px_half"); } } TEST_CASE("Circle with hole in the middle", "[MarchingSquares]") { using namespace Slic3r; - test_expolys(create_raster({100, 100}), circle_with_hole(25.), 1.f, "circle_with_hole"); -} - -static void recreate_object_from_slices(const std::string &objname, float lh) { - TriangleMesh mesh = load_model(objname); - mesh.require_shared_vertices(); - - auto bb = mesh.bounding_box(); - std::vector layers; - slice_mesh(mesh, grid(float(bb.min.z()), float(bb.max.z()), lh), layers, 0.f, []{}); - - TriangleMesh out = slices_to_triangle_mesh(layers, bb.min.z(), double(lh), double(lh)); - - out.require_shared_vertices(); - out.WriteOBJFile("out_from_slices.obj"); + test_expolys(create_raster({1000, 1000}), circle_with_hole(25.), W2x2, "circle_with_hole"); } static void recreate_object_from_rasters(const std::string &objname, float lh) { TriangleMesh mesh = load_model(objname); auto bb = mesh.bounding_box(); -// Vec3f tr = -bb.center().cast(); -// mesh.translate(tr.x(), tr.y(), tr.z()); -// bb = mesh.bounding_box(); + Vec3f tr = -bb.center().cast(); + mesh.translate(tr.x(), tr.y(), tr.z()); + bb = mesh.bounding_box(); std::vector layers; slice_mesh(mesh, grid(float(bb.min.z()) + lh, float(bb.max.z()), lh), layers, 0.f, []{}); @@ -311,7 +340,7 @@ static void recreate_object_from_rasters(const std::string &objname, float lh) { ExPolygons layer_ = sla::raster_to_polygons(rst); // float delta = scaled(std::min(rst.pixel_dimensions().h_mm, -// rst.pixel_dimensions().w_mm)); +// rst.pixel_dimensions().w_mm)) / 2; // layer_ = expolygons_simplify(layer_, delta); @@ -338,5 +367,5 @@ static void recreate_object_from_rasters(const std::string &objname, float lh) { } TEST_CASE("Recreate object from rasters", "[SL1Import]") { - recreate_object_from_rasters("triang.obj", 0.05f); + recreate_object_from_rasters("frog_legs.obj", 0.05f); } From 83929c29845f37271b68e81cb1f700e38808216f Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Thu, 23 Apr 2020 19:45:55 +0200 Subject: [PATCH 11/11] Add ui job for SLA import --- src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/Jobs/SLAImportJob.cpp | 226 +++++++++++++++++++++++++++ src/slic3r/GUI/Jobs/SLAImportJob.hpp | 31 ++++ src/slic3r/GUI/Plater.cpp | 26 ++- 4 files changed, 269 insertions(+), 16 deletions(-) create mode 100644 src/slic3r/GUI/Jobs/SLAImportJob.cpp create mode 100644 src/slic3r/GUI/Jobs/SLAImportJob.hpp diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 1f1284a9e..2ac8a1ff0 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -150,6 +150,8 @@ set(SLIC3R_GUI_SOURCES GUI/Jobs/ArrangeJob.cpp GUI/Jobs/RotoptimizeJob.hpp GUI/Jobs/RotoptimizeJob.cpp + GUI/Jobs/SLAImportJob.hpp + GUI/Jobs/SLAImportJob.cpp GUI/Jobs/ProgressIndicator.hpp GUI/ProgressStatusBar.hpp GUI/ProgressStatusBar.cpp diff --git a/src/slic3r/GUI/Jobs/SLAImportJob.cpp b/src/slic3r/GUI/Jobs/SLAImportJob.cpp new file mode 100644 index 000000000..e791bf94e --- /dev/null +++ b/src/slic3r/GUI/Jobs/SLAImportJob.cpp @@ -0,0 +1,226 @@ +#include "SLAImportJob.hpp" + +#include "slic3r/GUI/GUI.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/AppConfig.hpp" +#include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/PresetBundle.hpp" +#include "slic3r/GUI/GUI_ObjectList.hpp" +#include "slic3r/Utils/SLAImport.hpp" + +#include +#include +#include +#include +#include + +namespace Slic3r { namespace GUI { + +enum class Sel { modelAndProfile, profileOnly, modelOnly}; + +class ImportDlg: public wxDialog { + wxFilePickerCtrl *m_filepicker; + wxComboBox *m_import_dropdown, *m_quality_dropdown; + +public: + ImportDlg(Plater *plater) + : wxDialog{plater, wxID_ANY, "Import SLA archive"} + { + auto szvert = new wxBoxSizer{wxVERTICAL}; + auto szfilepck = new wxBoxSizer{wxHORIZONTAL}; + + m_filepicker = new wxFilePickerCtrl(this, wxID_ANY, + from_u8(wxGetApp().app_config->get_last_dir()), _(L("Choose SLA archive:")), + "SL1 archive files (*.sl1, *.zip)|*.sl1;*.SL1;*.zip;*.ZIP", + wxDefaultPosition, wxDefaultSize, wxFLP_DEFAULT_STYLE | wxFD_OPEN | wxFD_FILE_MUST_EXIST); + + szfilepck->Add(new wxStaticText(this, wxID_ANY, _(L("Import file: "))), 0, wxALIGN_CENTER); + szfilepck->Add(m_filepicker, 1); + szvert->Add(szfilepck, 0, wxALL | wxEXPAND, 5); + + auto szchoices = new wxBoxSizer{wxHORIZONTAL}; + + static const std::vector inp_choices = { + _(L("Import model and profile")), + _(L("Import profile only")), + _(L("Import model only")) + }; + + m_import_dropdown = new wxComboBox( + this, wxID_ANY, inp_choices[0], wxDefaultPosition, wxDefaultSize, + inp_choices.size(), inp_choices.data(), wxCB_READONLY | wxCB_DROPDOWN); + + szchoices->Add(m_import_dropdown); + szchoices->Add(new wxStaticText(this, wxID_ANY, _(L("Quality: "))), 0, wxALIGN_CENTER | wxALL, 5); + + static const std::vector qual_choices = { + _(L("Accurate")), + _(L("Balanced")), + _(L("Quick")) + }; + + m_quality_dropdown = new wxComboBox( + this, wxID_ANY, qual_choices[0], wxDefaultPosition, wxDefaultSize, + qual_choices.size(), qual_choices.data(), wxCB_READONLY | wxCB_DROPDOWN); + szchoices->Add(m_quality_dropdown); + + m_import_dropdown->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) { + if (get_selection() == Sel::profileOnly) + m_quality_dropdown->Disable(); + else m_quality_dropdown->Enable(); + }); + + szvert->Add(szchoices, 0, wxALL, 5); + szvert->AddStretchSpacer(1); + auto szbtn = new wxBoxSizer(wxHORIZONTAL); + szbtn->Add(new wxButton{this, wxID_CANCEL}); + szbtn->Add(new wxButton{this, wxID_OK}); + szvert->Add(szbtn, 0, wxALIGN_RIGHT | wxALL, 5); + + SetSizerAndFit(szvert); + } + + Sel get_selection() const + { + int sel = m_import_dropdown->GetSelection(); + return Sel(std::min(int(Sel::modelOnly), std::max(0, sel))); + } + + Vec2i get_marchsq_windowsize() const + { + enum { Accurate, Balanced, Fast}; + + switch(m_quality_dropdown->GetSelection()) + { + case Fast: return {8, 8}; + case Balanced: return {4, 4}; + default: + case Accurate: + return {2, 2}; + } + } + + wxString get_path() const + { + return m_filepicker->GetPath(); + } +}; + +class SLAImportJob::priv { +public: + Plater *plater; + + Sel sel = Sel::modelAndProfile; + + TriangleMesh mesh; + DynamicPrintConfig profile; + wxString path; + Vec2i win = {2, 2}; + std::string err; + + priv(Plater *plt): plater{plt} {} +}; + +SLAImportJob::SLAImportJob(std::shared_ptr pri, Plater *plater) + : Job{std::move(pri)}, p{std::make_unique(plater)} +{} + +SLAImportJob::~SLAImportJob() = default; + +void SLAImportJob::process() +{ + auto progr = [this](int s) { + if (s < 100) update_status(int(s), _(L("Importing SLA archive"))); + return !was_canceled(); + }; + + if (p->path.empty()) return; + + std::string path = p->path.ToUTF8().data(); + try { + switch (p->sel) { + case Sel::modelAndProfile: + import_sla_archive(path, p->win, p->mesh, p->profile, progr); + break; + case Sel::modelOnly: + import_sla_archive(path, p->win, p->mesh, progr); + break; + case Sel::profileOnly: + import_sla_archive(path, p->profile); + break; + } + + } catch (std::exception &ex) { + p->err = ex.what(); + } + + update_status(100, was_canceled() ? _(L("Importing canceled.")) : + _(L("Importing done."))); +} + +void SLAImportJob::reset() +{ + p->sel = Sel::modelAndProfile; + p->mesh = {}; + p->profile = {}; + p->win = {2, 2}; + p->path.Clear(); +} + +void SLAImportJob::prepare() +{ + reset(); + + ImportDlg dlg{p->plater}; + + if (dlg.ShowModal() == wxID_OK) { + auto path = dlg.get_path(); + auto nm = wxFileName(path); + p->path = !nm.Exists(wxFILE_EXISTS_REGULAR) ? "" : path.ToUTF8(); + p->sel = dlg.get_selection(); + p->win = dlg.get_marchsq_windowsize(); + } else { + p->path = ""; + } +} + +void SLAImportJob::finalize() +{ + // Ignore the arrange result if aborted. + if (was_canceled()) return; + + if (!p->err.empty()) { + show_error(p->plater, p->err); + p->err = ""; + return; + } + + std::string name = wxFileName(p->path).GetName().ToUTF8().data(); + + if (!p->profile.empty()) { + const ModelObjectPtrs& objects = p->plater->model().objects; + for (auto object : objects) + if (object->volumes.size() > 1) + { + Slic3r::GUI::show_info(nullptr, + _(L("You cannot load SLA project with a multi-part object on the bed")) + "\n\n" + + _(L("Please check your object list before preset changing.")), + _(L("Attention!")) ); + return; + } + + DynamicPrintConfig config = {}; + config.apply(SLAFullPrintConfig::defaults()); + config += std::move(p->profile); + + wxGetApp().preset_bundle->load_config_model(name, std::move(config)); + wxGetApp().load_current_presets(); + } + + if (!p->mesh.empty()) + p->plater->sidebar().obj_list()->load_mesh_object(p->mesh, name); + + reset(); +} + +}} diff --git a/src/slic3r/GUI/Jobs/SLAImportJob.hpp b/src/slic3r/GUI/Jobs/SLAImportJob.hpp new file mode 100644 index 000000000..cff6cc899 --- /dev/null +++ b/src/slic3r/GUI/Jobs/SLAImportJob.hpp @@ -0,0 +1,31 @@ +#ifndef SLAIMPORTJOB_HPP +#define SLAIMPORTJOB_HPP + +#include "Job.hpp" + +namespace Slic3r { namespace GUI { + +class Plater; + +class SLAImportJob : public Job { + class priv; + + std::unique_ptr p; + +public: + SLAImportJob(std::shared_ptr pri, Plater *plater); + ~SLAImportJob(); + + void process() override; + + void reset(); + +protected: + void prepare() override; + + void finalize() override; +}; + +}} // namespace Slic3r::GUI + +#endif // SLAIMPORTJOB_HPP diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index f9fe06a85..db9e7e59a 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -27,7 +27,6 @@ #include #include #include -#include #include "libslic3r/libslic3r.h" #include "libslic3r/Format/STL.hpp" @@ -64,6 +63,7 @@ #include "Tab.hpp" #include "Jobs/ArrangeJob.hpp" #include "Jobs/RotoptimizeJob.hpp" +#include "Jobs/SLAImportJob.hpp" #include "PresetBundle.hpp" #include "BackgroundSlicingProcess.hpp" #include "ProgressStatusBar.hpp" @@ -73,7 +73,6 @@ #include "../Utils/PrintHost.hpp" #include "../Utils/FixModelByWin10.hpp" #include "../Utils/UndoRedo.hpp" -#include "../Utils/SLAImport.hpp" #include "RemovableDriveManager.hpp" #if ENABLE_NON_STATIC_CANVAS_MANAGER #ifdef __APPLE__ @@ -1487,7 +1486,7 @@ struct Plater::priv class Jobs: public ExclusiveJobGroup { priv *m; - size_t m_arrange_id, m_rotoptimize_id; + size_t m_arrange_id, m_rotoptimize_id, m_sla_import_id; void before_start() override { m->background_process.stop(); } @@ -1496,6 +1495,7 @@ struct Plater::priv { m_arrange_id = add_job(std::make_unique(m->statusbar(), m->q)); m_rotoptimize_id = add_job(std::make_unique(m->statusbar(), m->q)); + m_sla_import_id = add_job(std::make_unique(m->statusbar(), m->q)); } void arrange() @@ -1510,6 +1510,12 @@ struct Plater::priv start(m_rotoptimize_id); } + void import_sla_arch() + { + m->take_snapshot(_(L("Import SLA archive"))); + start(m_sla_import_id); + } + } m_ui_jobs; bool delayed_scene_refresh; @@ -4255,19 +4261,7 @@ void Plater::add_model() void Plater::import_sl1_archive() { - wxFileDialog dlg(this, _(L("Choose SL1 archive:")), - from_u8(wxGetApp().app_config->get_last_dir()), "", - "SL1 archive files (*.sl1)|*.sl1;*.SL1;*.zip;*.ZIP", - wxFD_OPEN | wxFD_FILE_MUST_EXIST); - - if (dlg.ShowModal() == wxID_OK) { - try { - TriangleMesh mesh = import_model_from_sla_zip(dlg.GetPath()); - p->sidebar->obj_list()->load_mesh_object(mesh, wxFileName(dlg.GetPath()).GetName()); - } catch (std::exception &ex) { - show_error(this, ex.what()); - } - } + p->m_ui_jobs.import_sla_arch(); } void Plater::extract_config_from_project()