From 9c0cb3b47cf84210c03b2c1e4e58c82112a01484 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 4 Jan 2024 21:18:04 +0100 Subject: [PATCH 1/5] printrequest implementation: printrequest reader WIP Co-authored-by: David Kocik transformation matrix loading Loading filaments from printRequest Conversion of string to double Reading PrintRequest when material not installed empty tree check + allow printrequest for mac PrintRequest: Set print profile if current doesnt have compatible sla material followup fix of c24477cd10d92de00874504be434872d49a20ec9 Fix compile error on gcc --- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/Format/PrintRequest.cpp | 161 ++++++++++++++++++++++++++ src/libslic3r/Format/PrintRequest.hpp | 12 ++ src/libslic3r/Model.cpp | 10 +- src/libslic3r/Preset.cpp | 18 ++- src/libslic3r/Preset.hpp | 4 +- src/libslic3r/PresetBundle.cpp | 14 +++ src/libslic3r/PresetBundle.hpp | 1 + src/slic3r/GUI/Plater.cpp | 59 +++++++++- 9 files changed, 273 insertions(+), 8 deletions(-) create mode 100644 src/libslic3r/Format/PrintRequest.cpp create mode 100644 src/libslic3r/Format/PrintRequest.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 1ec0e1c8a0..6bf7035ec3 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -149,6 +149,8 @@ set(SLIC3R_SOURCES Format/SVG.cpp Format/SLAArchiveFormatRegistry.hpp Format/SLAArchiveFormatRegistry.cpp + Format/PrintRequest.cpp + Format/PrintRequest.cpp GCode/ThumbnailData.cpp GCode/ThumbnailData.hpp GCode/Thumbnails.cpp diff --git a/src/libslic3r/Format/PrintRequest.cpp b/src/libslic3r/Format/PrintRequest.cpp new file mode 100644 index 0000000000..c7a0a53fb1 --- /dev/null +++ b/src/libslic3r/Format/PrintRequest.cpp @@ -0,0 +1,161 @@ +#include "PrintRequest.hpp" + +#include +#include +#include +#include +#include +#include + +#include "libslic3r/Exception.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/Format/STL.hpp" + +//#include "slic3r/GUI/format.hpp" + +namespace Slic3r { +namespace pt = boost::property_tree; +namespace { +void read_file(const char* input_file, pt::ptree& tree) +{ + boost::filesystem::path path(input_file); + + boost::nowide::ifstream ifs(path.string()); + try { + pt::read_xml(ifs, tree); + } + catch (const boost::property_tree::xml_parser::xml_parser_error& err) { + throw Slic3r::RuntimeError("Failed reading PrintRequest file. File format is corrupt."); + } +} + +void read_tree(const boost::property_tree::ptree::value_type& section, boost::filesystem::path& model_path, std::string& material, std::string& material_color, std::vector& transformation_matrix) +{ + for (const auto& data : section.second) { + if (data.first == "Path") { + model_path = boost::filesystem::path(data.second.data()); + } + else if (data.first == "Material") { + material = data.second.data(); + } + else if (data.first == "MaterialColor") { + material_color = data.second.data(); + } + else if (data.first == "TransformationMatrix") { + transformation_matrix.reserve(16); + for (const auto& element : data.second) { + transformation_matrix.emplace_back(element.second.data()); + } + } + } +} +bool fill_model(Model* model, const boost::filesystem::path& model_path, const std::string& material, const std::vector& transformation_matrix) +{ + if (!boost::filesystem::exists(model_path)) + throw Slic3r::RuntimeError("Failed reading PrintRequest file. Path doesn't exists. " + model_path.string()); + if (!boost::algorithm::iends_with(model_path.string(), ".stl")) + throw Slic3r::RuntimeError("Failed reading PrintRequest file. Path is not stl file. " + model_path.string()); + bool result = load_stl(model_path.string().c_str(), model); + if (!material.empty()) { + model->objects.back()->volumes.front()->set_material_id(material); + } + return result; +} +void add_instance(Model* model, const boost::filesystem::path& model_path, const std::vector& transformation_matrix) +{ + if (transformation_matrix.size() >= 16) { + + auto string_to_double = [model_path](const std::string& from) -> double { + double ret_val; + auto answer = fast_float::from_chars(from.data(), from.data() + from.size(), ret_val); + if (answer.ec != std::errc()) + throw Slic3r::RuntimeError("Failed reading PrintRequest file. Couldn't parse transformation matrix. " + model_path.string()); + return ret_val; + }; + + Vec3d offset_vector; + Slic3r::Transform3d matrix; + try + { + offset_vector = Slic3r::Vec3d(string_to_double(transformation_matrix[3]), string_to_double(transformation_matrix[7]), string_to_double(transformation_matrix[11])); + // PrintRequest is row-major 4x4, Slic3r::Transform3d (Eigen) is column-major by default 3x3 + matrix(0, 0) = string_to_double(transformation_matrix[0]); + matrix(1, 0) = string_to_double(transformation_matrix[1]); + matrix(2, 0) = string_to_double(transformation_matrix[2]); + matrix(0, 1) = string_to_double(transformation_matrix[4]); + matrix(1, 1) = string_to_double(transformation_matrix[5]); + matrix(2, 1) = string_to_double(transformation_matrix[6]); + matrix(0, 2) = string_to_double(transformation_matrix[8]); + matrix(1, 2) = string_to_double(transformation_matrix[9]); + matrix(2, 2) = string_to_double(transformation_matrix[10]); + } + catch (const Slic3r::RuntimeError& e) { + throw e; + } + + + ModelObject* object = model->objects.back(); + Slic3r::Geometry::Transformation transformation(matrix); + transformation.set_offset(offset_vector); + object->add_instance(transformation); + } +} + +} + +bool load_printRequest(const char* input_file, Model* model) +{ + pt::ptree tree; + try + { + read_file(input_file, tree); + } + catch (const std::exception& e) + { + throw e; + } + + bool result = true; + + for (const auto& section0 : tree) { + if (section0.first != "PrintRequest") + continue; + if (section0.second.empty()) + continue; + for (const auto& section1 : section0.second) { + if (section1.first != "Files") + continue; + if (section1.second.empty()) + continue; + for (const auto& section2 : section1.second) { + if (section2.first != "File") + continue; + if (section2.second.empty()) + continue; + boost::filesystem::path model_path; + std::string material; + std::string material_color; + std::vector transformation_matrix; + + try + { + read_tree(section2, model_path, material, material_color, transformation_matrix); + result = result && fill_model(model, model_path, material, transformation_matrix); + if (!result) + return false; + add_instance(model, model_path, transformation_matrix); + } + catch (const std::exception& e) + { + throw e; + } + + + } + } + } + + return true; +} + +} // namespace Slic3r diff --git a/src/libslic3r/Format/PrintRequest.hpp b/src/libslic3r/Format/PrintRequest.hpp new file mode 100644 index 0000000000..bf92d20482 --- /dev/null +++ b/src/libslic3r/Format/PrintRequest.hpp @@ -0,0 +1,12 @@ +#ifndef slic3r_Format_PrintRequest_hpp_ +#define slic3r_Format_PrintRequest_hpp_ + + + +namespace Slic3r { +class Model; +bool load_printRequest(const char* input_file, Model* model); + +} //namespace Slic3r + +#endif \ No newline at end of file diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 2de86a5283..ba3e1075b9 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -28,6 +28,7 @@ #include "Format/3mf.hpp" #include "Format/STEP.hpp" #include "Format/SVG.hpp" +#include "Format/PrintRequest.hpp" #include @@ -140,6 +141,8 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c result = load_3mf(input_file.c_str(), *config, *config_substitutions, &model, false); else if (boost::algorithm::iends_with(input_file, ".svg")) result = load_svg(input_file, model); + else if (boost::ends_with(input_file, ".printRequest")) + result = load_printRequest(input_file.c_str(), &model); else throw Slic3r::RuntimeError("Unknown file format. Input file must have .stl, .obj, .step/.stp, .svg, .amf(.xml) or extension .3mf(.zip)."); @@ -148,9 +151,10 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c if (model.objects.empty()) throw Slic3r::RuntimeError("The supplied file couldn't be read because it's empty"); - - for (ModelObject *o : model.objects) - o->input_file = input_file; + + if (!boost::ends_with(input_file, ".printRequest")) + for (ModelObject *o : model.objects) + o->input_file = input_file; if (options & LoadAttribute::AddDefaultInstances) model.add_default_instances(); diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index ea1024101b..5c88a7a903 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1213,6 +1213,20 @@ const std::string& PresetCollection::get_preset_name_by_alias(const std::string& return alias; } +const std::string& PresetCollection::get_preset_name_by_alias_invisible(const std::string& alias) const +{ + for ( + // Find the 1st profile name with the alias. + auto it = Slic3r::lower_bound_by_predicate(m_map_alias_to_profile_name.begin(), m_map_alias_to_profile_name.end(), [&alias](auto& l) { return l.first < alias; }); + // Continue over all profile names with the same alias. + it != m_map_alias_to_profile_name.end() && it->first == alias; ++it) + if (auto it_preset = this->find_preset_internal(it->second); + it_preset != m_presets.end() && it_preset->name == it->second && + it_preset->is_compatible) + return it_preset->name; + return alias; +} + const std::string* PresetCollection::get_preset_name_renamed(const std::string &old_name) const { auto it_renamed = m_map_system_profile_renamed.find(old_name); @@ -1478,13 +1492,13 @@ Preset& PresetCollection::select_preset(size_t idx) return m_presets[idx]; } -bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, bool force) +bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, bool force, bool force_invisible /*= false*/) { std::string name = Preset::remove_suffix_modified(name_w_suffix); // 1) Try to find the preset by its name. auto it = this->find_preset_internal(name); size_t idx = 0; - if (it != m_presets.end() && it->name == name && it->is_visible) + if (it != m_presets.end() && it->name == name && (force_invisible || it->is_visible)) // Preset found by its name and it is visible. idx = it - m_presets.begin(); else { diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index e3df0c24ba..51133eeb7b 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -410,6 +410,7 @@ public: PresetWithVendorProfile get_edited_preset_with_vendor_profile() const { return this->get_preset_with_vendor_profile(this->get_edited_preset()); } const std::string& get_preset_name_by_alias(const std::string& alias) const; + const std::string& get_preset_name_by_alias_invisible(const std::string& alias) const; const std::string* get_preset_name_renamed(const std::string &old_name) const; // used to update preset_choice from Tab @@ -528,7 +529,8 @@ public: // Select a profile by its name. Return true if the selection changed. // Without force, the selection is only updated if the index changes. // With force, the changes are reverted if the new index is the same as the old index. - bool select_preset_by_name(const std::string &name, bool force); + // With force_invisible, force preset selection even it's invisible. + bool select_preset_by_name(const std::string &name, bool force, bool force_invisible = false); // Generate a file path from a profile name. Add the ".ini" suffix if it is missing. std::string path_from_name(const std::string &new_name) const; diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index f6399dc63f..5198c31c80 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -496,6 +496,20 @@ const std::string& PresetBundle::get_preset_name_by_alias( const Preset::Type& p return presets.get_preset_name_by_alias(alias); } +const std::string& PresetBundle::get_preset_name_by_alias_invisible(const Preset::Type& preset_type, const std::string& alias) const +{ + // there are not aliases for Printers profiles + if (preset_type == Preset::TYPE_PRINTER || preset_type == Preset::TYPE_INVALID) + return alias; + + const PresetCollection& presets = preset_type == Preset::TYPE_PRINT ? prints : + preset_type == Preset::TYPE_SLA_PRINT ? sla_prints : + preset_type == Preset::TYPE_FILAMENT ? filaments : + sla_materials; + + return presets.get_preset_name_by_alias_invisible(alias); +} + void PresetBundle::save_changes_for_preset(const std::string& new_name, Preset::Type type, const std::vector& unselected_options) { diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index d7d56e8256..4bdc10ea4d 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -182,6 +182,7 @@ public: void load_installed_printers(const AppConfig &config); const std::string& get_preset_name_by_alias(const Preset::Type& preset_type, const std::string& alias, int extruder_id = -1); + const std::string& get_preset_name_by_alias_invisible(const Preset::Type& preset_type, const std::string& alias) const; // Save current preset of a provided type under a new name. If the name is different from the old one, // Unselected option would be reverted to the beginning values diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 14f535e2c9..44a72c8d63 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -300,6 +300,7 @@ struct Plater::priv static const std::regex pattern_any_amf; static const std::regex pattern_prusa; static const std::regex pattern_zip; + static const std::regex pattern_printRequest; priv(Plater *q, MainFrame *main_frame); ~priv(); @@ -597,6 +598,7 @@ const std::regex Plater::priv::pattern_zip_amf(".*[.]zip[.]amf", std::regex::ica const std::regex Plater::priv::pattern_any_amf(".*[.](amf|amf[.]xml|zip[.]amf)", std::regex::icase); const std::regex Plater::priv::pattern_prusa(".*prusa", std::regex::icase); const std::regex Plater::priv::pattern_zip(".*zip", std::regex::icase); +const std::regex Plater::priv::pattern_printRequest(".*printRequest", std::regex::icase); Plater::priv::priv(Plater *q, MainFrame *main_frame) : q(q) @@ -1198,6 +1200,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ const bool type_zip_amf = !type_3mf && std::regex_match(path.string(), pattern_zip_amf); const bool type_any_amf = !type_3mf && std::regex_match(path.string(), pattern_any_amf); const bool type_prusa = std::regex_match(path.string(), pattern_prusa); + const bool type_printRequest = std::regex_match(path.string(), pattern_printRequest); Slic3r::Model model; bool is_project_file = type_prusa; @@ -1370,7 +1373,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ convert_model_if(model, answer_convert_from_imperial_units == wxID_YES); } - if (model.looks_like_multipart_object()) { + if (!type_printRequest && model.looks_like_multipart_object()) { if (answer_consider_as_multi_part_objects == wxOK_DEFAULT) { RichMessageDialog dlg(q, _L( "This file contains several objects positioned at multiple heights.\n" @@ -1409,6 +1412,58 @@ std::vector Plater::priv::load_files(const std::vector& input_ } if (!model_object->instances.empty()) model_object->ensure_on_bed(is_project_file); + if (type_printRequest) { + for (ModelInstance* obj_instance : model_object->instances) { + obj_instance->set_offset(obj_instance->get_offset() + Slic3r::to_3d(this->bed.build_volume().bed_center(), -model_object->origin_translation(2))); + } + } + } + if (type_printRequest) { + assert(model.materials.size()); + + for (const auto& material : model.materials) { + std::string preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias_invisible(Preset::Type::TYPE_SLA_MATERIAL, + Preset::remove_suffix_modified(material.first)); + Preset* prst = wxGetApp().preset_bundle->sla_materials.find_preset(preset_name, false); + if (!prst) { //did not find compatible profile + // try find alias of material comaptible with another print profile - if exists, use the print profile + auto& prints = wxGetApp().preset_bundle->sla_prints; + std::string edited_print_name = prints.get_edited_preset().name; + bool found = false; + for (auto it = prints.begin(); it != prints.end(); ++it) + { + if (it->name != edited_print_name) { + BOOST_LOG_TRIVIAL(error) << it->name; + wxGetApp().get_tab(Preset::Type::TYPE_SLA_PRINT)->select_preset(it->name, false); + preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias_invisible(Preset::Type::TYPE_SLA_MATERIAL, + Preset::remove_suffix_modified(material.first)); + prst = wxGetApp().preset_bundle->sla_materials.find_preset(preset_name, false); + if (prst) { + found = true; + break; + } + } + } + if (!found) { + // return to original print profile + wxGetApp().get_tab(Preset::Type::TYPE_SLA_PRINT)->select_preset(edited_print_name, false); + std::string notif_text = into_u8(_L("Material preset was not loaded:")); + notif_text += "\n - " + preset_name; + q->get_notification_manager()->push_notification(NotificationType::CustomNotification, + NotificationManager::NotificationLevel::PrintInfoNotificationLevel, notif_text); + break; + } + } + + PresetBundle* preset_bundle = wxGetApp().preset_bundle; + if (preset_bundle->sla_materials.get_selected_preset_name() != preset_name) { + preset_bundle->sla_materials.select_preset_by_name(preset_name, false, true); + preset_bundle->tmp_installed_presets = { preset_name }; + q->notify_about_installed_presets(); + wxGetApp().load_current_presets(false);// For this case we shouldn't check printer_presets + } + break; + } } if (one_by_one) { @@ -4814,7 +4869,7 @@ void ProjectDropDialog::on_dpi_changed(const wxRect& suggested_rect) bool Plater::load_files(const wxArrayString& filenames, bool delete_after_load/*=false*/) { - const std::regex pattern_drop(".*[.](stl|obj|amf|3mf|prusa|step|stp|zip)", std::regex::icase); + const std::regex pattern_drop(".*[.](stl|obj|amf|3mf|prusa|step|stp|zip|printRequest)", std::regex::icase); const std::regex pattern_gcode_drop(".*[.](gcode|g|bgcode|bgc)", std::regex::icase); std::vector paths; From ac2580caf0b6c25abba008109529a6748730520c Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 4 Oct 2023 16:52:30 +0200 Subject: [PATCH 2/5] SLA z correction: first SLA Z correction prototype Change Z correction controlling parameter to integer layer count Add z correction mm conversion text as description label under Z correction Fixed a crash when switching pages in material tab --- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/Preset.cpp | 1 + src/libslic3r/PrintConfig.cpp | 8 ++ src/libslic3r/PrintConfig.hpp | 2 + src/libslic3r/SLA/ZCorrection.cpp | 130 ++++++++++++++++++++++ src/libslic3r/SLA/ZCorrection.hpp | 84 ++++++++++++++ src/libslic3r/SLAPrint.cpp | 1 + src/libslic3r/SLAPrintSteps.cpp | 6 + src/slic3r/GUI/Tab.cpp | 25 ++++- src/slic3r/GUI/Tab.hpp | 3 + tests/sla_print/CMakeLists.txt | 3 +- tests/sla_print/sla_zcorrection_tests.cpp | 123 ++++++++++++++++++++ 12 files changed, 386 insertions(+), 2 deletions(-) create mode 100644 src/libslic3r/SLA/ZCorrection.cpp create mode 100644 src/libslic3r/SLA/ZCorrection.hpp create mode 100644 tests/sla_print/sla_zcorrection_tests.cpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 6bf7035ec3..f76f07f70c 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -483,6 +483,8 @@ set(SLIC3R_SOURCES SLA/DefaultSupportTree.cpp SLA/BranchingTreeSLA.hpp SLA/BranchingTreeSLA.cpp + SLA/ZCorrection.hpp + SLA/ZCorrection.cpp BranchingTree/BranchingTree.cpp BranchingTree/BranchingTree.hpp BranchingTree/PointCloud.cpp diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 5c88a7a903..32a334dbb6 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -609,6 +609,7 @@ static std::vector s_Preset_sla_material_options { "material_print_speed", "area_fill", "default_sla_material_profile", + "zcorrection_layers", "compatible_prints", "compatible_prints_condition", "compatible_printers", "compatible_printers_condition", "inherits", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 943dce6b18..264454adac 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -4042,6 +4042,14 @@ void PrintConfigDef::init_sla_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0.2)); + def = this->add("zcorrection_layers", coInt); + def->label = L("Z compensation"); + def->category = L("Advanced"); + def->tooltip = L("Number of layers to Z correct to avoid cross layer bleed"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionInt(0)); + def = this->add("gamma_correction", coFloat); def->label = L("Printer gamma correction"); def->full_label = L("Printer gamma correction"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 9cf6ae8038..7cd88fd6ff 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1174,6 +1174,8 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, material_correction_y)) ((ConfigOptionFloat, material_correction_z)) ((ConfigOptionEnum, material_print_speed)) + ((ConfigOptionInt, zcorrection_layers)) + ((ConfigOptionFloatNullable, material_ow_support_pillar_diameter)) ((ConfigOptionFloatNullable, material_ow_branchingsupport_pillar_diameter)) ((ConfigOptionFloatNullable, material_ow_support_head_front_diameter)) diff --git a/src/libslic3r/SLA/ZCorrection.cpp b/src/libslic3r/SLA/ZCorrection.cpp new file mode 100644 index 0000000000..5843d8858b --- /dev/null +++ b/src/libslic3r/SLA/ZCorrection.cpp @@ -0,0 +1,130 @@ +#include "ZCorrection.hpp" + +#include "Execution/ExecutionTBB.hpp" + +#include "libslic3r/ClipperUtils.hpp" + +namespace Slic3r { namespace sla { + +std::vector apply_zcorrection( + const std::vector &slices, size_t layers) +{ + return zcorr_detail::apply_zcorrection(ex_tbb, slices, layers); +} + +std::vector apply_zcorrection(const std::vector &slices, + const std::vector &grid, + float depth) +{ + return zcorr_detail::apply_zcorrection(ex_tbb, slices, grid, depth); +} + +namespace zcorr_detail { + +DepthMap create_depthmap(const std::vector &slices, + const std::vector &grid, + size_t max_depth) +{ + struct DepthPoly { + size_t depth = 0; + ExPolygons contour; + }; + + DepthMap ret; + + if (slices.empty() || slices.size() != grid.size()) + return ret; + + size_t depth_limit = max_depth > 0 ? max_depth : slices.size(); + ret.resize(slices.size()); + + ret.front() = DepthMapLayer{ {size_t{0}, slices.front()} }; + + for (size_t i = 0; i < slices.size() - 1; ++i) { + DepthMapLayer &depths_current = ret[i]; + DepthMapLayer &depths_nxt = ret[i + 1]; + + for (const auto &[depth, cntrs] : depths_current) { + DepthPoly common; + + common.contour = intersection_ex(slices[i + 1], cntrs); + common.depth = std::min(depth_limit, depth + 1); + + DepthPoly overhangs; + overhangs.contour = diff_ex(slices[i + 1], cntrs); + + if (!common.contour.empty()) { + std::copy(common.contour.begin(), common.contour.end(), + std::back_inserter(depths_nxt[common.depth])); + } + + if (!overhangs.contour.empty()) { + std::copy(overhangs.contour.begin(), overhangs.contour.end(), + std::back_inserter(depths_nxt[overhangs.depth])); + } + } + + for(auto &[i, cntrs] : depths_nxt) { + depths_nxt[i] = union_ex(cntrs); + } + } + + return ret; +} + +void apply_zcorrection(DepthMap &dmap, size_t layers) +{ + for (size_t lyr = 0; lyr < dmap.size(); ++lyr) { + size_t threshold = std::min(lyr, layers); + + auto &dlayer = dmap[lyr]; + + for (auto it = dlayer.begin(); it != dlayer.end();) + if (it->first < threshold) + it = dlayer.erase(it); + else + ++it; + } +} + +ExPolygons merged_layer(const DepthMapLayer &dlayer) +{ + using namespace Slic3r; + + ExPolygons out; + for (auto &[i, cntrs] : dlayer) { + std::copy(cntrs.begin(), cntrs.end(), std::back_inserter(out)); + } + + out = union_ex(out); + + return out; +} + +std::vector depthmap_to_slices(const DepthMap &dm) +{ + auto out = reserve_vector(dm.size()); + for (const auto &dlayer : dm) { + out.emplace_back(merged_layer(dlayer)); + } + + return out; +} + +ExPolygons intersect_layers(const std::vector &slices, + size_t layer_from, size_t layers_down) +{ + size_t drill_to = std::min(layer_from, layers_down); + auto drill_to_layer = static_cast(layer_from - drill_to); + + ExPolygons merged_lyr = slices[layer_from]; + for (int i = layer_from; i >= drill_to_layer; --i) + merged_lyr = intersection_ex(merged_lyr, slices[i]); + + return merged_lyr; +} + +} // namespace zcorr_detail + +} // namespace sla +} // namespace Slic3r diff --git a/src/libslic3r/SLA/ZCorrection.hpp b/src/libslic3r/SLA/ZCorrection.hpp new file mode 100644 index 0000000000..45c3e0cd76 --- /dev/null +++ b/src/libslic3r/SLA/ZCorrection.hpp @@ -0,0 +1,84 @@ +#ifndef ZCORRECTION_HPP +#define ZCORRECTION_HPP + +#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Execution/Execution.hpp" + +namespace Slic3r { +namespace sla { + +std::vector apply_zcorrection(const std::vector &slices, + size_t layers); + +std::vector apply_zcorrection(const std::vector &slices, + const std::vector &grid, + float depth); + +namespace zcorr_detail { + +ExPolygons intersect_layers(const std::vector &slices, + size_t layer_from, + size_t layers_down); + +template +std::vector apply_zcorrection(Ex ep, + const std::vector &slices, + size_t layers) +{ + std::vector output(slices.size()); + + execution::for_each(ep, size_t{0}, slices.size(), + [&output, &slices, layers] (size_t lyr) { + output[lyr] = intersect_layers(slices, lyr, layers); + }, execution::max_concurrency(ep)); + + return output; +} + +inline size_t depth_to_layers(const std::vector &grid, + size_t from_layer, + float depth) +{ + size_t depth_layers = 0; + while (from_layer > depth_layers && + grid[from_layer - depth_layers] > grid[from_layer] - depth) + depth_layers++; + + return depth_layers; +} + +template +std::vector apply_zcorrection(Ex ep, + const std::vector &slices, + const std::vector &grid, + float depth) +{ + std::vector output(slices.size()); + + execution::for_each(ep, size_t{0}, slices.size(), + [&output, &slices, &grid, depth] (size_t lyr) { + output[lyr] = intersect_layers(slices, lyr, + depth_to_layers(grid, lyr, depth)); + }, execution::max_concurrency(ep)); + + return output; +} + +using DepthMapLayer = std::map; +using DepthMap = std::vector; + +DepthMap create_depthmap(const std::vector &slices, + const std::vector &grid, size_t max_depth = 0); + +void apply_zcorrection(DepthMap &dmap, size_t layers); + +ExPolygons merged_layer(const DepthMapLayer &dlayer); + +std::vector depthmap_to_slices(const DepthMap &dm); + +} // namespace zcorr_detail + +} // namespace sla +} // namespace Slic3r + +#endif // ZCORRECTION_HPP diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 8116cae2ce..4dbc71b954 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -842,6 +842,7 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector #include #include +#include #include #include @@ -129,6 +130,11 @@ void SLAPrint::Steps::apply_printer_corrections(SLAPrintObject &po, SliceOrigin if (idx < slices.size()) slices[idx] = elephant_foot_compensation(slices[idx], min_w, efc(i)); } + + if (o == soModel) { // Z correction applies only to the model slices + slices = sla::apply_zcorrection(slices, + m_print->m_material_config.zcorrection_layers.getInt()); + } } indexed_triangle_set SLAPrint::Steps::generate_preview_vdb( diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 657123fd3c..fb084e14b7 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -5341,7 +5341,16 @@ void TabSLAMaterial::build() opt.opt.label = axis; line.append_option(opt); } + optgroup->append_line(line); + optgroup->append_single_option_line("zcorrection_layers"); + + line = Line{ "", "" }; + line.full_width = 1; + // line.label_path = category_path + "recommended-thin-wall-thickness"; + line.widget = [this](wxWindow* parent) { + return description_line_widget(parent, &m_z_correction_to_mm_description); + }; optgroup->append_line(line); add_material_overrides_page(); @@ -5491,7 +5500,19 @@ void TabSLAMaterial::update() // m_update_cnt--; // // if (m_update_cnt == 0) - wxGetApp().mainframe->on_config_changed(m_config); + wxGetApp().mainframe->on_config_changed(m_config); +} + +void TabSLAMaterial::update_description_lines() +{ + if (m_active_page && m_active_page->title() == "Material" && m_z_correction_to_mm_description) { + auto cfg = m_preset_bundle->full_config(); + double lh = cfg.opt_float("layer_height"); + int zlayers = cfg.opt_int("zcorrection_layers"); + m_z_correction_to_mm_description->SetText(GUI::format_wxstr(_L("Current Z correction depth is: %1% mm"), zlayers * lh)); + } + + Tab::update_description_lines(); } void TabSLAMaterial::update_sla_prusa_specific_visibility() @@ -5519,6 +5540,8 @@ void TabSLAMaterial::clear_pages() for (auto& over_opt : m_overrides_options) over_opt.second = nullptr; + + m_z_correction_to_mm_description = nullptr; } void TabSLAMaterial::msw_rescale() diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 277c72cb85..d6f536a8f1 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -571,6 +571,8 @@ class TabSLAMaterial : public Tab void update_material_overrides_page(); std::map m_overrides_options; + ogStaticText* m_z_correction_to_mm_description = nullptr; + public: TabSLAMaterial(wxBookCtrlBase* parent) : Tab(parent, _L("Materials"), Slic3r::Preset::TYPE_SLA_MATERIAL) {} @@ -586,6 +588,7 @@ public: void sys_color_changed() override; bool supports_printer_technology(const PrinterTechnology tech) const override { return tech == ptSLA; } void update_sla_prusa_specific_visibility() override; + void update_description_lines() override; }; class TabSLAPrint : public Tab diff --git a/tests/sla_print/CMakeLists.txt b/tests/sla_print/CMakeLists.txt index 3a5d96c7ac..b244e76ac8 100644 --- a/tests/sla_print/CMakeLists.txt +++ b/tests/sla_print/CMakeLists.txt @@ -5,7 +5,8 @@ add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests_main.cpp sla_supptgen_tests.cpp sla_raycast_tests.cpp sla_supptreeutils_tests.cpp - sla_archive_readwrite_tests.cpp) + sla_archive_readwrite_tests.cpp + sla_zcorrection_tests.cpp) # mold linker for successful linking needs also to link TBB library and link it before libslic3r. target_link_libraries(${_TEST_NAME}_tests test_common TBB::tbb TBB::tbbmalloc libslic3r) diff --git a/tests/sla_print/sla_zcorrection_tests.cpp b/tests/sla_print/sla_zcorrection_tests.cpp new file mode 100644 index 0000000000..69734c253b --- /dev/null +++ b/tests/sla_print/sla_zcorrection_tests.cpp @@ -0,0 +1,123 @@ +#include +#include + +#include + +#include "libslic3r/TriangleMeshSlicer.hpp" +#include "libslic3r/SLA/ZCorrection.hpp" +#include "libslic3r/MTUtils.hpp" +#include "libslic3r/SVG.hpp" + +void print_depthmap(std::string_view prefix, + const Slic3r::BoundingBox &bb, + const Slic3r::sla::zcorr_detail::DepthMap &dm) +{ + using namespace Slic3r; + + size_t cnt = 0; + for (const sla::zcorr_detail::DepthMapLayer &layer : dm) { + SVG svg(std::string(prefix) + std::to_string(cnt++) + ".svg", bb); + for (const auto &[depth, dpolys] : layer) { + svg.draw_outline(dpolys); + svg.draw(dpolys, "green", 1. + depth / 10.f); + } + } +} + +TEST_CASE("Number of layers should be equal after z correction", "[ZCorr]") +{ + using namespace Slic3r; + + const size_t Layers = random_value(size_t{1}, size_t{100}); + INFO("Layers = " << Layers); + + float zcorr_depth = GENERATE(0.f, random_value(0.01f, 10.f)); + + std::vector slices(Layers); + std::vector hgrid = grid(0.f, Layers * 1.f, 1.f); + + std::vector output = sla::apply_zcorrection(slices, hgrid, zcorr_depth); + + REQUIRE(slices.size() == output.size()); +} + +TEST_CASE("Testing DepthMap for a cube", "[ZCorr]") +{ + using namespace Slic3r; + + TriangleMesh mesh = load_model("20mm_cube.obj"); + auto bb = bounding_box(mesh); + bb.offset(-0.1); + + std::vector hgrid = grid(bb.min.z(), bb.max.z(), 1.f); + + std::vector slices = slice_mesh_ex(mesh.its, hgrid, {}); + + sla::zcorr_detail::DepthMap dmap = sla::zcorr_detail::create_depthmap(slices, hgrid); + + REQUIRE(dmap.size() == slices.size()); + + for (size_t i = 0; i < slices.size(); ++i) { + const auto &dlayer = dmap[i]; + const ExPolygons &slayer = slices[i]; + REQUIRE(dlayer.size() == 1); + REQUIRE(dlayer.begin()->first == i); + double ad = area(dlayer.begin()->second); + double as = area(slayer); + REQUIRE(ad == Approx(as).margin(EPSILON)); + } +} + +TEST_CASE("Testing DepthMap for arbitrary shapes", "[ZCorr]") +{ + using namespace Slic3r; + + auto modelname = GENERATE("V_standing.obj", "A_upsidedown.obj"); + + TriangleMesh mesh = load_model(modelname); + auto bb = bounding_box(mesh); + bb.offset(-0.1); + + std::vector hgrid = grid(bb.min.z(), bb.max.z(), 0.5f); + + std::vector slices = slice_mesh_ex(mesh.its, hgrid, {}); + + size_t zcorr_layers = GENERATE(size_t{0}, random_value(size_t{1}, size_t{10})); + + sla::zcorr_detail::DepthMap dmap = + sla::zcorr_detail::create_depthmap(slices, hgrid, zcorr_layers); + +#ifndef NDEBUG + print_depthmap("debug_dmap", scaled(to_2d(bb)), dmap); +#endif + + REQUIRE(dmap.size() == slices.size()); + + auto corrslices_fast = sla::apply_zcorrection(slices, zcorr_layers); + sla::zcorr_detail::apply_zcorrection(dmap, zcorr_layers); + + for (size_t i = 0; i < corrslices_fast.size(); ++i) { + ExPolygons dlayer = sla::zcorr_detail::merged_layer(dmap[i]); + const ExPolygons &slayer = corrslices_fast[i]; + double ad = area(dlayer); + double as = area(slayer); + REQUIRE(ad == Approx(as).margin(EPSILON)); + } +} + +TEST_CASE("Test depth to layers calculation", "[ZCorr]") { + using namespace Slic3r; + + float layer_h = 0.5f; + std::vector hgrid = grid(0.f, 100.f, layer_h); + + float depth = GENERATE(0.f, + random_value(0.01f, 0.499f), + 0.5f, + random_value(0.501f, 10.f)); + + for (size_t i = 0; i < hgrid.size(); ++i) { + auto expected_lyrs = std::min(i, static_cast(std::ceil(depth/layer_h))); + REQUIRE(sla::zcorr_detail::depth_to_layers(hgrid, i, depth) == expected_lyrs); + } +} From c31647f3ac07bc4a9a0594c03c3ca2b44d0706d6 Mon Sep 17 00:00:00 2001 From: David Kocik Date: Fri, 8 Dec 2023 12:54:49 +0100 Subject: [PATCH 3/5] Sla material info text in wizard with link --- src/slic3r/GUI/ConfigWizard.cpp | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 3d7109799d..a5e992c09f 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -766,6 +766,10 @@ PageMaterials::PageMaterials(ConfigWizard *parent, Materials *materials, wxStrin html_window = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxSize(60 * em, 20 * em), wxHW_SCROLLBAR_AUTO); + html_window->Bind(wxEVT_HTML_LINK_CLICKED, [](wxHtmlLinkEvent& event) { + wxGetApp().open_browser_with_warning_dialog(event.GetLinkInfo().GetHref()); + event.Skip(false); + }); append(html_window, 0, wxEXPAND); list_printer->Bind(wxEVT_LISTBOX, [this](wxCommandEvent& evt) { @@ -855,9 +859,19 @@ void PageMaterials::set_compatible_printers_html_window(const std::vector(\"Template\") printer are universal profiles available for all printers. These might not be compatible with your printer."), materials->technology == T_FFF ? _L("Filaments") : _L("SLA materials")); } else { + bool has_medical = false; + for (const Preset *printer : materials->printers) { + if (printer->vendor && printer->vendor->id == "PrusaProMedical") { + has_medical = true; + } + } + // TRN PrusaSlicer-Medical ConfigWizard: Materials" + wxString zero_line = _L("The list of validated workflows for Medical One can be found in this article. Profiles for other materials are not verified by the material manufacturer and therefore may not correspond to the current version of the material."); + if (!has_medical) { + zero_line.Clear(); + } // TRN ConfigWizard: Materials : "%1%" = "Filaments"/"SLA materials" wxString first_line = format_wxstr(_L("%1% marked with * are not compatible with some installed printers."), materials->technology == T_FFF ? _L("Filaments") : _L("SLA materials")); - if (all_printers) { // TRN ConfigWizard: Materials : "%1%" = "filament"/"SLA material" wxString second_line = format_wxstr(_L("All installed printers are compatible with the selected %1%."), materials->technology == T_FFF ? _L("filament") : _L("SLA material")); @@ -868,12 +882,14 @@ void PageMaterials::set_compatible_printers_html_window(const std::vector" "" "" - "%s

%s" + "" + "%s

%s

%s" "
" "" "" , bgr_clr_str , text_clr_str + , zero_line , first_line , second_line ); @@ -891,13 +907,16 @@ void PageMaterials::set_compatible_printers_html_window(const std::vector" "" "" - "%s

%s" + "" + "%s

%s

%s" "" "" , bgr_clr_str , text_clr_str + , zero_line , first_line - , second_line); + , second_line + ); for (size_t i = 0; i < printer_names.size(); ++i) { text += wxString::Format("", boost::nowide::widen(printer_names[i])); From b6f1a8e8db194959b293f3f013209bb4a361b341 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 12 Jun 2024 19:42:51 +0200 Subject: [PATCH 4/5] Only allow loading of printRequest in SLA mode (to prevent a crash) --- src/slic3r/GUI/Plater.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 44a72c8d63..accd89f36a 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1202,6 +1202,13 @@ std::vector Plater::priv::load_files(const std::vector& input_ const bool type_prusa = std::regex_match(path.string(), pattern_prusa); const bool type_printRequest = std::regex_match(path.string(), pattern_printRequest); + if (type_printRequest && printer_technology != ptSLA) { + Slic3r::GUI::show_info(nullptr, + _L("PrintRequest can only be loaded if an SLA printer is selected."), + _L("Error!")); + continue; + } + Slic3r::Model model; bool is_project_file = type_prusa; try { From dcde3148a5092d109815df7ba0142384cade7a0f Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 14 Jun 2024 10:09:49 +0200 Subject: [PATCH 5/5] Fixed loading of SLA archives when path contains non-ASCII characters --- src/slic3r/GUI/Jobs/SLAImportJob.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/slic3r/GUI/Jobs/SLAImportJob.cpp b/src/slic3r/GUI/Jobs/SLAImportJob.cpp index 77c9714b94..ea689905c0 100644 --- a/src/slic3r/GUI/Jobs/SLAImportJob.cpp +++ b/src/slic3r/GUI/Jobs/SLAImportJob.cpp @@ -105,8 +105,8 @@ void SLAImportJob::prepare() { reset(); - auto path = p->import_dlg->get_path(); - auto nm = wxFileName(path); + const std::string path = p->import_dlg->get_path(); + auto nm = wxFileName(from_u8(path)); p->path = !nm.Exists(wxFILE_EXISTS_REGULAR) ? "" : nm.GetFullPath(); if (p->path.empty()) { p->err = _u8L("The file does not exist.");
%s