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); + } +}