From 67a16dabca4af8b0de871d75bea08a7ce2cfc9f8 Mon Sep 17 00:00:00 2001 From: Ian Chua Date: Thu, 8 Oct 2026 23:49:33 +0800 Subject: [PATCH 01/10] fix: prompt for permission when plugin tries to create a thread (#16248) * fix: prompt for permission when plugin tries to create a thread * fix: request permission on main thread --- src/slic3r/plugin/PluginAuditManager.cpp | 82 ++++++++++++++++++++--- src/slic3r/plugin/PluginFsUtils.cpp | 2 + src/slic3r/plugin/PluginFsUtils.hpp | 1 + tests/slic3rutils/test_plugin_install.cpp | 2 + 4 files changed, 78 insertions(+), 9 deletions(-) diff --git a/src/slic3r/plugin/PluginAuditManager.cpp b/src/slic3r/plugin/PluginAuditManager.cpp index f3bd6c1636..efb730da8c 100644 --- a/src/slic3r/plugin/PluginAuditManager.cpp +++ b/src/slic3r/plugin/PluginAuditManager.cpp @@ -116,6 +116,8 @@ static const std::unordered_map audit_event_cat {"_winapi.CreateProcess", AuditEventCategory::ProcessCreate}, {"_posixsubprocess.fork_exec", AuditEventCategory::ProcessCreate}, + // threading + {"_thread.start_new_thread", AuditEventCategory::Threading}, // processreplace: exec* replaces the current process image rather than spawning a child {"os.exec", AuditEventCategory::ProcessReplace}, }; @@ -739,6 +741,12 @@ std::vector audit_targets(const std::string& event_name, AuditEvent } return targets; } + case AuditEventCategory::Threading: + // Thread creation exposes no user-supplied target. Use a fixed sentinel so the grant + // persists per plugin: the permission list matches targets by exact string, and the + // started function's repr embeds an address that changes every run. + targets.emplace_back("thread"); + return targets; default: break; } @@ -765,6 +773,7 @@ std::vector* permission_list_for(AuditEventCategory category, Plugi case AuditEventCategory::Http: return &permissions.network_http; case AuditEventCategory::Socket: return &permissions.network_socket; case AuditEventCategory::ProcessCreate: return &permissions.process; + case AuditEventCategory::Threading: return &permissions.threading; case AuditEventCategory::ProcessReplace: return &permissions.process; default: return nullptr; } @@ -837,6 +846,8 @@ wxString audit_message(AuditEventCategory category, const wxString& plugin_name, return wxString::Format(_L("Plugin \"%s\" is requesting to open a network connection to:\n%s"), plugin_name, target_list); case AuditEventCategory::ProcessCreate: return wxString::Format(_L("Plugin \"%s\" is requesting to run the following command(s):\n%s"), plugin_name, target_list); + case AuditEventCategory::Threading: + return wxString::Format(_L("Plugin \"%s\" is requesting permission to create a thread."), plugin_name); case AuditEventCategory::ProcessReplace: return wxString::Format(_L("Plugin \"%s\" is requesting to replace the running application with:\n%s"), plugin_name, target_list); default: @@ -844,6 +855,67 @@ wxString audit_message(AuditEventCategory category, const wxString& plugin_name, } } +// Builds and shows the modal permission prompt. Must run on the GUI thread. +bool prompt_for_targets(AuditEventCategory category, const std::string& plugin_name, const std::string& event_name, + const std::vector& unresolved) +{ + wxString target_list; + for (const auto& target : unresolved) + target_list += wxString::FromUTF8(target.c_str()) + "\n"; + + wxMessageDialog dialog(nullptr, + audit_message(category, wxString::FromUTF8(plugin_name.c_str()), + wxString::FromUTF8(event_name.c_str()), target_list), + _L("Plugin permission request"), wxYES_NO | wxICON_WARNING); + return dialog.ShowModal() == wxID_YES; +} + +// Records a grant in the plugin's sidecar so it is not asked again. Reads the install state +// freshly because the async prompt outlives the caller's stack copy of it. +void persist_grant(const std::string& plugin_key, AuditEventCategory category, const std::vector& targets) +{ + PluginInstallState state; + if (!PluginManager::instance().get_install_state(plugin_key, state)) + return; + + std::vector* permission_list = permission_list_for(category, state.permissions); + if (!permission_list) + return; + + for (const auto& target : targets) + persist_permission(plugin_key, state, *permission_list, target); +} + +// Requests permission for an audited event, returning true when it is already granted or the user +// approves an inline prompt. +// +// An audited event can fire on a thread the UI thread may itself be blocked waiting on: the +// SlicingPipeline hook runs on the slicing worker thread (see PluginHooks.cpp), and +// BackgroundSlicingProcess::stop()/stop_internal() park the UI thread until that worker stops. +// Blocking the worker on a marshaled modal -- which is safe for the plugin-load worker that +// request_filesystem_read_permissions runs on -- would therefore deadlock the application (the +// invariant PluginHostUi.cpp documents for slicing-hook UI calls). Off the main thread the prompt +// is therefore posted asynchronously and the current event denied (fail closed, like an unanswered +// prompt); the grant is persisted once the user accepts, so a later attempt succeeds without +// re-prompting. +bool request_permission(AuditEventCategory category, const std::string& plugin_key, const std::string& plugin_name, + const std::string& event_name, const std::vector& unresolved) +{ + if (wxTheApp == nullptr || GUI::wxGetApp().is_closing()) + return false; + + if (wxIsMainThread()) + return prompt_for_targets(category, plugin_name, event_name, unresolved); + + GUI::wxGetApp().CallAfter([category, plugin_key, plugin_name, event_name, unresolved]() { + if (wxTheApp == nullptr || GUI::wxGetApp().is_closing()) + return; + if (prompt_for_targets(category, plugin_name, event_name, unresolved)) + persist_grant(plugin_key, category, unresolved); + }); + return false; +} + int decide_audited_event(PluginAuditManager& mgr, PluginInstallState& state, const std::string& plugin_key, @@ -864,15 +936,7 @@ int decide_audited_event(PluginAuditManager& mgr, return 0; } - wxString target_list; - for (const auto& target : unresolved) - target_list += wxString::FromUTF8(target.c_str()) + "\n"; - - wxMessageDialog dialog(nullptr, - audit_message(category, wxString::FromUTF8(plugin_name.c_str()), - wxString::FromUTF8(event_name.c_str()), target_list), - _L("Plugin permission request"), wxYES_NO | wxICON_WARNING); - if (dialog.ShowModal() != wxID_YES) + if (!request_permission(category, plugin_key, plugin_name, event_name, unresolved)) return report_denied(mgr, event_name, {false, "audit permission required"}); if (permission_list) diff --git a/src/slic3r/plugin/PluginFsUtils.cpp b/src/slic3r/plugin/PluginFsUtils.cpp index e3b1bc3bee..7956d01bb0 100644 --- a/src/slic3r/plugin/PluginFsUtils.cpp +++ b/src/slic3r/plugin/PluginFsUtils.cpp @@ -807,6 +807,7 @@ bool read_install_state(const boost::filesystem::path& plugin_dir, PluginInstall read_string_list("network_http", parsed.permissions.network_http); read_string_list("network_socket", parsed.permissions.network_socket); read_string_list("process", parsed.permissions.process); + read_string_list("threading", parsed.permissions.threading); } if (state.contains("enabled") && state["enabled"].is_boolean()) @@ -850,6 +851,7 @@ bool write_install_state(const boost::filesystem::path& plugin_dir, const Plugin {"network_http", state.permissions.network_http}, {"network_socket", state.permissions.network_socket}, {"process", state.permissions.process}, + {"threading", state.permissions.threading}, }; nlohmann::json capabilities = nlohmann::json::array(); diff --git a/src/slic3r/plugin/PluginFsUtils.hpp b/src/slic3r/plugin/PluginFsUtils.hpp index 7e552b7896..bd335373d0 100644 --- a/src/slic3r/plugin/PluginFsUtils.hpp +++ b/src/slic3r/plugin/PluginFsUtils.hpp @@ -92,6 +92,7 @@ struct PluginPermissions std::vector network_http; std::vector network_socket; std::vector process; + std::vector threading; }; struct PluginInstallState { diff --git a/tests/slic3rutils/test_plugin_install.cpp b/tests/slic3rutils/test_plugin_install.cpp index d79519c236..b860486979 100644 --- a/tests/slic3rutils/test_plugin_install.cpp +++ b/tests/slic3rutils/test_plugin_install.cpp @@ -122,6 +122,7 @@ TEST_CASE("install-state sidecar is the source of truth for a cloud plugin's ins state.permissions.network_http = {"https://api.example.com"}; state.permissions.network_socket = {"192.168.45.6:443"}; state.permissions.process = {"/usr/bin/curl"}; + state.permissions.threading = {"thread"}; REQUIRE(write_install_state(plugin_dir, state)); // Permission data is persisted in the same sidecar as the installation metadata. @@ -132,6 +133,7 @@ TEST_CASE("install-state sidecar is the source of truth for a cloud plugin's ins CHECK(persisted.permissions.network_http == state.permissions.network_http); CHECK(persisted.permissions.network_socket == state.permissions.network_socket); CHECK(persisted.permissions.process == state.permissions.process); + CHECK(persisted.permissions.threading == state.permissions.threading); // Reading the sidecar back onto a freshly-scanned descriptor (whose header version is still // 1.0.0) must surface the cloud-installed 1.2.0. This is what lets update_cloud_metadata compare From 9d32c1c5450186aafa0bbadcdd38a1c11769ef7c Mon Sep 17 00:00:00 2001 From: Error404JoyNotFound Date: Fri, 9 Oct 2026 00:14:03 +0530 Subject: [PATCH 02/10] docs: correct layer_num index tooltip to 0-based (fixes #10340) (#15984) --- src/libslic3r/PrintConfig.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index f76b8c35f9..c8e364cc69 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -12999,7 +12999,7 @@ CustomGcodeSpecificConfigDef::CustomGcodeSpecificConfigDef() // Common Defs def = this->add("layer_num", coInt); def->label = L("Layer number"); - def->tooltip = L("Index of the current layer. One-based (i.e. first layer is number 1)."); + def->tooltip = L("Index of the current layer. Zero-based (i.e. first layer is number 0), except in extrusion role change G-code, where it is one-based."); def = this->add("layer_z", coFloat); def->label = L("Layer Z"); From ee2c40ea854870e54c70dd6ab84c4573cf8aee76 Mon Sep 17 00:00:00 2001 From: Rodrigo Faselli <162915171+RF47@users.noreply.github.com> Date: Thu, 8 Oct 2026 16:00:28 -0300 Subject: [PATCH 03/10] Fix Mesh Boolean negative (cut) text object (#16275) * Fix Mesh Boolean negative (cut) text object * Update test_meshboolean.cpp * missing headers * Update test_meshboolean.cpp * Refactor mcut difference test for source splits * Apply suggestion from @raistlin7447 --- src/libslic3r/MeshBoolean.cpp | 23 +++++++++++++++- tests/libslic3r/test_meshboolean.cpp | 40 +++++++++++++++++++++++++++- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/MeshBoolean.cpp b/src/libslic3r/MeshBoolean.cpp index 44d54be99d..37e3c84b3f 100644 --- a/src/libslic3r/MeshBoolean.cpp +++ b/src/libslic3r/MeshBoolean.cpp @@ -1045,7 +1045,28 @@ void do_boolean(McutMesh& srcMesh, const McutMesh& cutMesh, const std::string& b // But we can force it to work by spliting the src mesh into disconnected components, // and do booleans seperately, then merge all the results. indexed_triangle_set all_its; - if (boolean_opts == "UNION" || boolean_opts == "A_NOT_B") { + if (boolean_opts == "A_NOT_B") { + // Each cut can leave the source with several disconnected components, which mcut rejects + // in the next dispatch, so re-split after every cut part (e.g. each letter of a text). + std::vector parts = std::move(src_parts); + for (size_t j = 0; j < cut_parts.size(); j++) { + auto cut_part = triangle_mesh_to_mcut(cut_parts[j]); + std::vector next_parts; + for (indexed_triangle_set &part : parts) { + auto src_part = triangle_mesh_to_mcut(part); + if (do_boolean_single(*src_part, *cut_part, boolean_opts)) { + TriangleMesh tri_part = mcut_to_triangle_mesh(*src_part); + std::vector pieces = its_split(tri_part.its); + std::move(pieces.begin(), pieces.end(), std::back_inserter(next_parts)); + } else + next_parts.emplace_back(std::move(part)); + } + parts = std::move(next_parts); + } + for (const indexed_triangle_set &part : parts) + its_merge(all_its, part); + } + else if (boolean_opts == "UNION") { for (size_t i = 0; i < src_parts.size(); i++) { auto src_part = triangle_mesh_to_mcut(src_parts[i]); for (size_t j = 0; j < cut_parts.size(); j++) { diff --git a/tests/libslic3r/test_meshboolean.cpp b/tests/libslic3r/test_meshboolean.cpp index 6aebd56481..fc04ba27fc 100644 --- a/tests/libslic3r/test_meshboolean.cpp +++ b/tests/libslic3r/test_meshboolean.cpp @@ -1,9 +1,11 @@ #include #include #include - +#include +#include #include #include +#include using namespace Slic3r; @@ -24,3 +26,39 @@ TEST_CASE("CGAL and TriangleMesh conversions", "[MeshBoolean]") { REQUIRE(! MeshBoolean::cgal::does_self_intersect(M)); } + +TEST_CASE("mcut difference handles source splits between cuts", "[MeshBoolean]") { + TriangleMesh body = make_cube(30., 10., 10.); + + TriangleMesh tool; + + // First cut splits the source into two disconnected components. + TriangleMesh slab = make_cube(2., 12., 20.); + slab.translate(Vec3f(14.f, -1.f, -5.f)); + its_merge(tool.its, slab.its); + + // These cuts must still be applied after the source has been split. + TriangleMesh left_hole = make_cube(4., 4., 20.); + left_hole.translate(Vec3f(3.f, 3.f, -5.f)); + its_merge(tool.its, left_hole.its); + + TriangleMesh right_hole = make_cube(4., 4., 20.); + right_hole.translate(Vec3f(21.f, 3.f, -5.f)); + its_merge(tool.its, right_hole.its); + + std::vector result; + MeshBoolean::mcut::make_boolean(body, tool, result, "A_NOT_B"); + + REQUIRE(result.size() == 1); + + const std::vector components = + its_split(result.front().its); + + REQUIRE(components.size() == 2); + + // 3000 - 200 - 160 - 160 = 2480. + REQUIRE_THAT( + result.front().volume(), + Catch::Matchers::WithinRel(2480., 1e-3) + ); +} From e6be22dd4c0c65363d1f2363d88980061290089b Mon Sep 17 00:00:00 2001 From: Fernando Marino` Date: Thu, 8 Oct 2026 22:22:57 +0200 Subject: [PATCH 04/10] Assign lib_name from $1 in has_host_runtime_library (#15912) --- src/dev-utils/platform/unix/build_linux_image.sh.in | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/dev-utils/platform/unix/build_linux_image.sh.in b/src/dev-utils/platform/unix/build_linux_image.sh.in index 8466597d34..03dc483b7d 100755 --- a/src/dev-utils/platform/unix/build_linux_image.sh.in +++ b/src/dev-utils/platform/unix/build_linux_image.sh.in @@ -303,10 +303,11 @@ else fi has_host_runtime_library() { - local lib_name path + local lib_name="\$1" + local path if command -v ldconfig >/dev/null 2>&1; then - if ldconfig -p 2>/dev/null | grep -Fq " \$lib_name"; then + if ldconfig -p 2>/dev/null | grep -Fq "\$lib_name ("; then return 0 fi fi From 1ec9b315f53fae2a91b9999f6634be43ebe6a3c5 Mon Sep 17 00:00:00 2001 From: mosfet80 <10235105+mosfet80@users.noreply.github.com> Date: Thu, 8 Oct 2026 22:43:38 +0200 Subject: [PATCH 05/10] Update CMake minimum version to 3.10 (#16097) --- build_release_macos.sh | 4 ++-- deps/CMakeLists.txt | 8 ++++---- scripts/Dockerfile.deps-assimp | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/build_release_macos.sh b/build_release_macos.sh index db03cb4ecd..728cb8e757 100755 --- a/build_release_macos.sh +++ b/build_release_macos.sh @@ -104,8 +104,8 @@ fi CMAKE_VERSION=$(cmake --version | head -1 | sed 's/[^0-9]*\([0-9]*\).*/\1/') if [ "$CMAKE_VERSION" -ge 4 ] 2>/dev/null; then - export CMAKE_POLICY_VERSION_MINIMUM=3.5 - export CMAKE_POLICY_COMPAT="-DCMAKE_POLICY_VERSION_MINIMUM=3.5" + export CMAKE_POLICY_VERSION_MINIMUM=3.10 + export CMAKE_POLICY_COMPAT="-DCMAKE_POLICY_VERSION_MINIMUM=3.10" echo "Detected CMake 4.x, adding compatibility flag (env + cmake arg)" else export CMAKE_POLICY_COMPAT="" diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index aa64ad4dc9..eda0c67e94 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -1,5 +1,5 @@ if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "4.0") - set(CMAKE_POLICY_VERSION_MINIMUM 3.5 CACHE STRING "" FORCE) + set(CMAKE_POLICY_VERSION_MINIMUM 3.10 CACHE STRING "" FORCE) endif() # @@ -24,7 +24,7 @@ endif() # therefore, unfortunately, the installation cannot be copied/moved elsewhere without re-installing wxWidgets. # -cmake_minimum_required(VERSION 3.2) +cmake_minimum_required(VERSION 3.10) if (APPLE) # if CMAKE_OSX_DEPLOYMENT_TARGET is not set, set it to 12.0 (the lowest Xcode 27 accepts) if (NOT CMAKE_OSX_DEPLOYMENT_TARGET) @@ -224,7 +224,7 @@ if (NOT IS_CROSS_COMPILE OR NOT APPLE) ${_source_dir_arg} ${_gen} CMAKE_ARGS - -DCMAKE_POLICY_VERSION_MINIMUM=3.5 + -DCMAKE_POLICY_VERSION_MINIMUM=3.10 -DCMAKE_INSTALL_PREFIX:STRING=${DESTDIR} -DCMAKE_MODULE_PATH:STRING=${PROJECT_SOURCE_DIR}/../cmake/modules -DCMAKE_PREFIX_PATH:STRING=${DESTDIR} @@ -279,7 +279,7 @@ else() ${_source_dir_arg} ${_gen} CMAKE_ARGS - -DCMAKE_POLICY_VERSION_MINIMUM=3.5 + -DCMAKE_POLICY_VERSION_MINIMUM=3.10 -DCMAKE_INSTALL_PREFIX:STRING=${DESTDIR} -DCMAKE_PREFIX_PATH:STRING=${DESTDIR} -DCMAKE_IGNORE_PREFIX_PATH:STRING=${CMAKE_IGNORE_PREFIX_PATH} diff --git a/scripts/Dockerfile.deps-assimp b/scripts/Dockerfile.deps-assimp index 40566885c0..505eb3e4f5 100644 --- a/scripts/Dockerfile.deps-assimp +++ b/scripts/Dockerfile.deps-assimp @@ -46,7 +46,7 @@ RUN set -eux; \ tar -xzf /tmp/assimp.tar.gz -C /tmp/assimp-src --strip-components=1; \ DESTDIR=/OrcaSlicer/deps/build/destdir/usr/local; \ cmake -S /tmp/assimp-src -B /tmp/assimp-build -G Ninja \ - -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ + -DCMAKE_POLICY_VERSION_MINIMUM=3.10 \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX="$DESTDIR" \ -DCMAKE_PREFIX_PATH="$DESTDIR" \ From 8790b07773a126c17b8dd115baae07ee1985d6f5 Mon Sep 17 00:00:00 2001 From: Kris Austin Date: Thu, 8 Oct 2026 16:10:37 -0500 Subject: [PATCH 06/10] fix: missing chamfers on STEP import since the OCCT 8.0.1 update (#16290) --- ...epMesh-seam-pcurve-at-edge-parameter.patch | 28 +++ deps/OCCT/OCCT.cmake | 7 + tests/data/cone_tilted_seam_pcurve.step | 166 ++++++++++++++++++ tests/libslic3r/test_step.cpp | 18 ++ 4 files changed, 219 insertions(+) create mode 100644 deps/OCCT/0001-BRepMesh-seam-pcurve-at-edge-parameter.patch create mode 100644 tests/data/cone_tilted_seam_pcurve.step diff --git a/deps/OCCT/0001-BRepMesh-seam-pcurve-at-edge-parameter.patch b/deps/OCCT/0001-BRepMesh-seam-pcurve-at-edge-parameter.patch new file mode 100644 index 0000000000..84c599a5f3 --- /dev/null +++ b/deps/OCCT/0001-BRepMesh-seam-pcurve-at-edge-parameter.patch @@ -0,0 +1,28 @@ +diff --git a/src/ModelingAlgorithms/TKMesh/BRepMesh/BRepMesh_ModelPreProcessor.cxx b/src/ModelingAlgorithms/TKMesh/BRepMesh/BRepMesh_ModelPreProcessor.cxx +index 6f63781..9b1c08e 100644 +--- a/src/ModelingAlgorithms/TKMesh/BRepMesh/BRepMesh_ModelPreProcessor.cxx ++++ b/src/ModelingAlgorithms/TKMesh/BRepMesh/BRepMesh_ModelPreProcessor.cxx +@@ -210,10 +210,10 @@ private: + + // Define two pcurves of the seam-edge. + occ::handle aPC1, aPC2; +- double af, al; ++ double af, al, af1, al1; + + aE.Orientation(TopAbs_FORWARD); +- aPC1 = BRep_Tool::CurveOnSurface(aE, aF, af, al); ++ aPC1 = BRep_Tool::CurveOnSurface(aE, aF, af1, al1); + + aE.Orientation(TopAbs_REVERSED); + aPC2 = BRep_Tool::CurveOnSurface(aE, aF, af, al); +@@ -224,7 +224,9 @@ private: + } + + // Select the correct pcurve of the seam-edge. +- const gp_Pnt2d& aFPntOfPC1 = aPC1->Value(aPC1->FirstParameter()); ++ // Use the edge's first parameter. A Geom2d_Line's FirstParameter() is -Precision::Infinite(), ++ // where a direction of (2e-16, -1) from rounding error gives an X far outside the U range. ++ const gp_Pnt2d aFPntOfPC1 = aPC1->Value(af1); + + if (std::abs(aLPntOfIPC1.X() - aFPntOfPC1.X()) > Precision::Confusion()) + { diff --git a/deps/OCCT/OCCT.cmake b/deps/OCCT/OCCT.cmake index 4cab60ec6a..0466d8e868 100644 --- a/deps/OCCT/OCCT.cmake +++ b/deps/OCCT/OCCT.cmake @@ -25,9 +25,16 @@ endif() # shipped bytes. Windows ships only the DLLs libslic3r links, so the tab adds the TKFillet, # TKOffset and TKBool DLLs. See docs/HLSD/design-tab.md. +if (IN_GIT_REPO) + set(OCCT_DIRECTORY_FLAG --directory ${BINARY_DIR_REL}/dep_OCCT-prefix/src/dep_OCCT) +endif () + orcaslicer_add_cmake_project(OCCT URL https://github.com/Open-Cascade-SAS/OCCT/archive/refs/tags/V8_0_1.zip URL_HASH SHA256=7c033d917ee8f040c0512d289dcc5f02c148889d5bac17c3e25639accb44f0da + # Makes BRepMesh triangulate cone faces whose seam pcurve is slightly tilted + # (Open-Cascade-SAS/OCCT#572); remove the patch once an OCCT release includes the fix. + PATCH_COMMAND git apply ${OCCT_DIRECTORY_FLAG} --verbose --ignore-space-change --whitespace=fix ${CMAKE_CURRENT_LIST_DIR}/0001-BRepMesh-seam-pcurve-at-edge-parameter.patch #DEPENDS dep_Boost DEPENDS ${FREETYPE_PKG} CMAKE_ARGS diff --git a/tests/data/cone_tilted_seam_pcurve.step b/tests/data/cone_tilted_seam_pcurve.step new file mode 100644 index 0000000000..172012114b --- /dev/null +++ b/tests/data/cone_tilted_seam_pcurve.step @@ -0,0 +1,166 @@ +ISO-10303-21; +HEADER; +FILE_DESCRIPTION(('Open CASCADE Model'),'2;1'); +FILE_NAME('Open CASCADE Shape Model','2026-10-08T09:57:33',('Author'),( + 'Open CASCADE'),'Open CASCADE STEP processor 8.0','Open CASCADE 8.0' + ,'Unknown'); +FILE_SCHEMA(('AUTOMOTIVE_DESIGN { 1 0 10303 214 1 1 1 1 }')); +ENDSEC; +DATA; +#1 = APPLICATION_PROTOCOL_DEFINITION('international standard', + 'automotive_design',2000,#2); +#2 = APPLICATION_CONTEXT( + 'core data for automotive mechanical design processes'); +#3 = SHAPE_DEFINITION_REPRESENTATION(#4,#10); +#4 = PRODUCT_DEFINITION_SHAPE('','',#5); +#5 = PRODUCT_DEFINITION('design','',#6,#9); +#6 = PRODUCT_DEFINITION_FORMATION('','',#7); +#7 = PRODUCT('Open CASCADE STEP translator 8.0 1', + 'Open CASCADE STEP translator 8.0 1','',(#8)); +#8 = PRODUCT_CONTEXT('',#2,'mechanical'); +#9 = PRODUCT_DEFINITION_CONTEXT('part definition',#2,'design'); +#10 = ADVANCED_BREP_SHAPE_REPRESENTATION('',(#11,#15),#121); +#11 = AXIS2_PLACEMENT_3D('',#12,#13,#14); +#12 = CARTESIAN_POINT('',(0.,0.,0.)); +#13 = DIRECTION('',(0.,0.,1.)); +#14 = DIRECTION('',(1.,0.,-0.)); +#15 = MANIFOLD_SOLID_BREP('',#16); +#16 = CLOSED_SHELL('',(#17,#113,#117)); +#17 = ADVANCED_FACE('',(#18),#31,.T.); +#18 = FACE_BOUND('',#19,.T.); +#19 = EDGE_LOOP('',(#20,#58,#81,#112)); +#20 = ORIENTED_EDGE('',*,*,#21,.F.); +#21 = EDGE_CURVE('',#22,#22,#24,.T.); +#22 = VERTEX_POINT('',#23); +#23 = CARTESIAN_POINT('',(-36.4,0.,25.)); +#24 = SURFACE_CURVE('',#25,(#30,#42),.PCURVE_S1.); +#25 = CIRCLE('',#26,36.4); +#26 = AXIS2_PLACEMENT_3D('',#27,#28,#29); +#27 = CARTESIAN_POINT('',(0.,0.,25.)); +#28 = DIRECTION('',(0.,0.,1.)); +#29 = DIRECTION('',(1.,0.,-0.)); +#30 = PCURVE('',#31,#36); +#31 = CONICAL_SURFACE('',#32,36.4,0.785398163397); +#32 = AXIS2_PLACEMENT_3D('',#33,#34,#35); +#33 = CARTESIAN_POINT('',(0.,0.,25.)); +#34 = DIRECTION('',(0.,0.,1.)); +#35 = DIRECTION('',(1.,0.,-0.)); +#36 = DEFINITIONAL_REPRESENTATION('',(#37),#41); +#37 = LINE('',#38,#39); +#38 = CARTESIAN_POINT('',(-6.28318530718,3.552713678801E-15)); +#39 = VECTOR('',#40,1.); +#40 = DIRECTION('',(1.,0.)); +#41 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2) +PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE','' + ) ); +#42 = PCURVE('',#43,#48); +#43 = PLANE('',#44); +#44 = AXIS2_PLACEMENT_3D('',#45,#46,#47); +#45 = CARTESIAN_POINT('',(0.,0.,25.)); +#46 = DIRECTION('',(-0.,-0.,-1.)); +#47 = DIRECTION('',(-1.,0.,0.)); +#48 = DEFINITIONAL_REPRESENTATION('',(#49),#57); +#49 = ( BOUNDED_CURVE() B_SPLINE_CURVE(2,(#50,#51,#52,#53,#54,#55,#56), +.UNSPECIFIED.,.T.,.F.) B_SPLINE_CURVE_WITH_KNOTS((1,2,2,2,2,1),( + -2.094395102393,0.,2.094395102393,4.188790204786,6.28318530718, +8.377580409573),.UNSPECIFIED.) CURVE() GEOMETRIC_REPRESENTATION_ITEM() +RATIONAL_B_SPLINE_CURVE((1.,0.5,1.,0.5,1.,0.5,1.)) REPRESENTATION_ITEM( + '') ); +#50 = CARTESIAN_POINT('',(-36.4,0.)); +#51 = CARTESIAN_POINT('',(-36.4,63.046649395507)); +#52 = CARTESIAN_POINT('',(18.2,31.523324697754)); +#53 = CARTESIAN_POINT('',(72.8,8.915428697793E-15)); +#54 = CARTESIAN_POINT('',(18.2,-31.52332469775)); +#55 = CARTESIAN_POINT('',(-36.4,-63.0466493955)); +#56 = CARTESIAN_POINT('',(-36.4,0.)); +#57 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2) +PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE','' + ) ); +#58 = ORIENTED_EDGE('',*,*,#59,.T.); +#59 = EDGE_CURVE('',#22,#60,#62,.T.); +#60 = VERTEX_POINT('',#61); +#61 = CARTESIAN_POINT('',(-11.4,0.,3.552713678801E-15)); +#62 = SEAM_CURVE('',#63,(#67,#74),.PCURVE_S1.); +#63 = LINE('',#64,#65); +#64 = CARTESIAN_POINT('',(-36.4,0.,25.)); +#65 = VECTOR('',#66,1.); +#66 = DIRECTION('',(0.707106781187,8.659560562349E-17,-0.707106781187)); +#67 = PCURVE('',#31,#68); +#68 = DEFINITIONAL_REPRESENTATION('',(#69),#73); +#69 = LINE('',#70,#71); +#70 = CARTESIAN_POINT('',(-3.14159265359,3.552713678801E-15)); +#71 = VECTOR('',#72,1.); +#72 = DIRECTION('',(2.13162820728E-16,-1.)); +#73 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2) +PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE','' + ) ); +#74 = PCURVE('',#31,#75); +#75 = DEFINITIONAL_REPRESENTATION('',(#76),#80); +#76 = LINE('',#77,#78); +#77 = CARTESIAN_POINT('',(3.14159265359,3.552713678801E-15)); +#78 = VECTOR('',#79,1.); +#79 = DIRECTION('',(2.13162820728E-16,-1.)); +#80 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2) +PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE','' + ) ); +#81 = ORIENTED_EDGE('',*,*,#82,.F.); +#82 = EDGE_CURVE('',#60,#60,#83,.T.); +#83 = SURFACE_CURVE('',#84,(#89,#96),.PCURVE_S1.); +#84 = CIRCLE('',#85,11.4); +#85 = AXIS2_PLACEMENT_3D('',#86,#87,#88); +#86 = CARTESIAN_POINT('',(0.,0.,3.552713678801E-15)); +#87 = DIRECTION('',(0.,0.,-1.)); +#88 = DIRECTION('',(1.,0.,0.)); +#89 = PCURVE('',#31,#90); +#90 = DEFINITIONAL_REPRESENTATION('',(#91),#95); +#91 = LINE('',#92,#93); +#92 = CARTESIAN_POINT('',(6.28318530718,-25.)); +#93 = VECTOR('',#94,1.); +#94 = DIRECTION('',(-1.,0.)); +#95 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2) +PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE','' + ) ); +#96 = PCURVE('',#97,#102); +#97 = PLANE('',#98); +#98 = AXIS2_PLACEMENT_3D('',#99,#100,#101); +#99 = CARTESIAN_POINT('',(0.,0.,3.552713678801E-15)); +#100 = DIRECTION('',(0.,0.,1.)); +#101 = DIRECTION('',(1.,0.,-0.)); +#102 = DEFINITIONAL_REPRESENTATION('',(#103),#111); +#103 = ( BOUNDED_CURVE() B_SPLINE_CURVE(2,(#104,#105,#106,#107,#108,#109 +,#110),.UNSPECIFIED.,.T.,.F.) B_SPLINE_CURVE_WITH_KNOTS((1,2,2,2,2,1),( + -2.094395102393,0.,2.094395102393,4.188790204786,6.28318530718, +8.377580409573),.UNSPECIFIED.) CURVE() GEOMETRIC_REPRESENTATION_ITEM() +RATIONAL_B_SPLINE_CURVE((1.,0.5,1.,0.5,1.,0.5,1.)) REPRESENTATION_ITEM( + '') ); +#104 = CARTESIAN_POINT('',(11.4,0.)); +#105 = CARTESIAN_POINT('',(11.4,-19.74537920628)); +#106 = CARTESIAN_POINT('',(-5.7,-9.872689603143)); +#107 = CARTESIAN_POINT('',(-22.8,-2.792194702056E-15)); +#108 = CARTESIAN_POINT('',(-5.7,9.872689603143)); +#109 = CARTESIAN_POINT('',(11.4,19.745379206285)); +#110 = CARTESIAN_POINT('',(11.4,0.)); +#111 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2) +PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE','' + ) ); +#112 = ORIENTED_EDGE('',*,*,#59,.F.); +#113 = ADVANCED_FACE('',(#114),#43,.F.); +#114 = FACE_BOUND('',#115,.F.); +#115 = EDGE_LOOP('',(#116)); +#116 = ORIENTED_EDGE('',*,*,#21,.F.); +#117 = ADVANCED_FACE('',(#118),#97,.F.); +#118 = FACE_BOUND('',#119,.F.); +#119 = EDGE_LOOP('',(#120)); +#120 = ORIENTED_EDGE('',*,*,#82,.F.); +#121 = ( GEOMETRIC_REPRESENTATION_CONTEXT(3) +GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#125)) GLOBAL_UNIT_ASSIGNED_CONTEXT +((#122,#123,#124)) REPRESENTATION_CONTEXT('Context #1', + '3D Context with UNIT and UNCERTAINTY') ); +#122 = ( LENGTH_UNIT() NAMED_UNIT(*) SI_UNIT(.MILLI.,.METRE.) ); +#123 = ( NAMED_UNIT(*) PLANE_ANGLE_UNIT() SI_UNIT($,.RADIAN.) ); +#124 = ( NAMED_UNIT(*) SI_UNIT($,.STERADIAN.) SOLID_ANGLE_UNIT() ); +#125 = UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(1.E-07),#122, + 'distance_accuracy_value','confusion accuracy'); +#126 = PRODUCT_RELATED_PRODUCT_CATEGORY('part',$,(#7)); +ENDSEC; +END-ISO-10303-21; diff --git a/tests/libslic3r/test_step.cpp b/tests/libslic3r/test_step.cpp index 0bb83f5809..5d843499fa 100644 --- a/tests/libslic3r/test_step.cpp +++ b/tests/libslic3r/test_step.cpp @@ -8,6 +8,7 @@ #include #include "libslic3r/Model.hpp" #include "libslic3r/Format/STEP.hpp" +#include "libslic3r/TriangleMesh.hpp" #include "test_utils.hpp" using namespace Slic3r; @@ -73,6 +74,23 @@ TEST_CASE("A security classification assignment does not crash import", "[Step]" CHECK(model.objects.front()->volumes.front()->mesh().facets_count() == 4); // a tetrahedron } +// The fixture is a truncated cone whose seam pcurves have the direction (2.1e-16, -1). +TEST_CASE("A cone with a slightly tilted seam imports as a closed mesh", "[Step]") +{ + const std::string path = std::string(TEST_DATA_DIR) + PATH_SEPARATOR "cone_tilted_seam_pcurve.step"; + + Model model; + bool cancel = false; + Step step(path); + + REQUIRE(step.load() == Step::Step_Status::LOAD_SUCCESS); + REQUIRE(step.mesh(&model, cancel, false) == Step::Step_Status::MESH_SUCCESS); + + REQUIRE(model.objects.size() == 1); + REQUIRE(model.objects.front()->volumes.size() == 1); + CHECK(its_num_open_edges(model.objects.front()->volumes.front()->mesh().its) == 0); +} + TEST_CASE("isUtf8 recognises two, three and four byte sequences", "[Step]") { CHECK(StepPreProcessor::isUtf8("\xC3\xA9")); // U+00E9 From 785a1946a6a8f9d5f48fd4c8b3287194908c010a Mon Sep 17 00:00:00 2001 From: Ian Bassi Date: Thu, 8 Oct 2026 18:28:56 -0300 Subject: [PATCH 07/10] Fix Adaptive and Support Cubic infill density with modifiers (#16295) --- docs/HLSD/separated-infills.md | 7 +++ src/libslic3r/Fill/Fill.cpp | 8 +-- src/libslic3r/Fill/FillAdaptive.cpp | 91 +++++------------------------ src/libslic3r/Fill/FillAdaptive.hpp | 21 +++++-- src/libslic3r/Layer.hpp | 9 ++- src/libslic3r/Print.hpp | 4 +- src/libslic3r/PrintObject.cpp | 88 +++++++++++++++++----------- tests/fff_print/test_fill.cpp | 39 ++++++++++++- 8 files changed, 136 insertions(+), 131 deletions(-) diff --git a/docs/HLSD/separated-infills.md b/docs/HLSD/separated-infills.md index b7f12f4199..10742eb042 100644 --- a/docs/HLSD/separated-infills.md +++ b/docs/HLSD/separated-infills.md @@ -79,6 +79,13 @@ the same `infill_body()`. The octree of the whole object is built only for an object of a single body, or when some body received no triangles, which then uses it. +The line spacing of an octree comes from the density, line width and multiline +count of a region, so a modifier or a part with its own density needs octrees of +its own. `adaptive_fill_line_spacing()` gives the spacing of each region, and +`FillAdaptive::RegionOctrees` holds one set of octrees per distinct spacing, +shared by the regions that have it. A set is built only for the bodies its +regions fill. The fill takes the set of its region, then the octree of its body. + ## Patterns left out Lightning grows its trees over the whole object, so moving a reference point diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 3dadb20253..2f23917be6 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -1320,7 +1320,7 @@ void export_group_fills_to_svg(const char *path, const std::vector #endif // friend to Layer -void Layer::make_fills(const FillAdaptive::Octrees* adaptive_fill_octrees, const FillAdaptive::Octrees* support_fill_octrees, FillLightning::Generator* lightning_generator) +void Layer::make_fills(const FillAdaptive::RegionOctrees* fill_octrees, FillLightning::Generator* lightning_generator) { for (LayerRegion *layerm : m_regions) layerm->fills.clear(); @@ -1353,7 +1353,7 @@ void Layer::make_fills(const FillAdaptive::Octrees* adaptive_fill_octrees, const f->z = this->print_z; f->angle = surface_fill.params.angle; f->fixed_angle = surface_fill.params.fixed_angle; - const FillAdaptive::Octrees *octrees = surface_fill.params.pattern == ipSupportCubic ? support_fill_octrees : adaptive_fill_octrees; + const FillAdaptive::Octrees *octrees = fill_octrees ? fill_octrees->region(surface_fill.region_id) : nullptr; f->print_config = &this->object()->print()->config(); f->print_object_config = &this->object()->config(); if (surface_fill.params.pattern == ipConcentricInternal) { @@ -1516,7 +1516,7 @@ void Layer::make_fills(const FillAdaptive::Octrees* adaptive_fill_octrees, const * - For lightning/adaptive patterns, the respective generators are wired so their * polylines match the final infill layout. */ -Polylines Layer::generate_sparse_infill_polylines_for_anchoring(const FillAdaptive::Octrees* adaptive_fill_octrees, const FillAdaptive::Octrees* support_fill_octrees, FillLightning::Generator* lightning_generator) const +Polylines Layer::generate_sparse_infill_polylines_for_anchoring(const FillAdaptive::RegionOctrees* fill_octrees, FillLightning::Generator* lightning_generator) const { LockRegionParam skin_inner_param; std::vector surface_fills = group_fills(*this, skin_inner_param); @@ -1574,7 +1574,7 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(const FillAdapti f->z = this->print_z; f->angle = surface_fill.params.angle; f->fixed_angle = surface_fill.params.fixed_angle; - const FillAdaptive::Octrees *octrees = surface_fill.params.pattern == ipSupportCubic ? support_fill_octrees : adaptive_fill_octrees; + const FillAdaptive::Octrees *octrees = fill_octrees ? fill_octrees->region(surface_fill.region_id) : nullptr; f->print_config = &this->object()->print()->config(); f->print_object_config = &this->object()->config(); diff --git a/src/libslic3r/Fill/FillAdaptive.cpp b/src/libslic3r/Fill/FillAdaptive.cpp index 210394ea51..b693ae7cfa 100644 --- a/src/libslic3r/Fill/FillAdaptive.cpp +++ b/src/libslic3r/Fill/FillAdaptive.cpp @@ -301,88 +301,25 @@ void OctreeDeleter::operator()(Octree *p) { delete p; } -std::pair adaptive_fill_line_spacing(const PrintObject &print_object) +std::vector adaptive_fill_line_spacing(const PrintObject &print_object) { - // Output, spacing for icAdaptiveCubic and icSupportCubic - double adaptive_line_spacing = 0.; - double support_line_spacing = 0.; - - enum class Tristate { - Yes, - No, - Maybe - }; - struct RegionFillData { - Tristate has_adaptive_infill; - Tristate has_support_infill; - double density; - double extrusion_width; - }; - std::vector region_fill_data; - region_fill_data.reserve(print_object.num_printing_regions()); - bool build_octree = false; + std::vector line_spacing(print_object.num_printing_regions(), 0.); const std::vector &nozzle_diameters = print_object.print()->config().nozzle_diameter.values; double max_nozzle_diameter = *std::max_element(nozzle_diameters.begin(), nozzle_diameters.end()); double default_infill_extrusion_width = Flow::auto_extrusion_width(FlowRole::frInfill, float(max_nozzle_diameter)); - for (size_t region_id = 0; region_id < print_object.num_printing_regions(); ++ region_id) { - const PrintRegionConfig &config = print_object.printing_region(region_id).config(); - bool nonempty = config.sparse_infill_density > 0; - bool has_adaptive_infill = nonempty && config.sparse_infill_pattern == ipAdaptiveCubic; - bool has_support_infill = nonempty && config.sparse_infill_pattern == ipSupportCubic; - double sparse_infill_line_width = config.sparse_infill_line_width.get_abs_value(max_nozzle_diameter); - region_fill_data.push_back(RegionFillData({ - has_adaptive_infill ? Tristate::Maybe : Tristate::No, - has_support_infill ? Tristate::Maybe : Tristate::No, - config.sparse_infill_density, - sparse_infill_line_width != 0. ? sparse_infill_line_width : default_infill_extrusion_width - })); - build_octree |= has_adaptive_infill || has_support_infill; + for (size_t region_id = 0; region_id < line_spacing.size(); ++ region_id) { + const PrintRegionConfig &config = print_object.printing_region(region_id).config(); + if (config.sparse_infill_density <= 0 || ! is_octree_infill_pattern(config.sparse_infill_pattern) || + std::none_of(print_object.layers().begin(), print_object.layers().end(), [region_id](const Layer *layer) { + return region_id < layer->regions().size() && ! layer->regions()[region_id]->fill_surfaces.empty(); + })) + continue; + double extrusion_width = config.sparse_infill_line_width.get_abs_value(max_nozzle_diameter); + if (extrusion_width == 0.) + extrusion_width = default_infill_extrusion_width; + line_spacing[region_id] = extrusion_width / ((config.sparse_infill_density / 100.0f) * 0.333333333f) * config.fill_multiline.value; } - - if (build_octree) { - // Compute the average of above parameters over all layers - for (const Layer *layer : print_object.layers()) - for (size_t region_id = 0; region_id < layer->regions().size(); ++ region_id) { - RegionFillData &rd = region_fill_data[region_id]; - if (rd.has_adaptive_infill == Tristate::Maybe && ! layer->regions()[region_id]->fill_surfaces.empty()) - rd.has_adaptive_infill = Tristate::Yes; - if (rd.has_support_infill == Tristate::Maybe && ! layer->regions()[region_id]->fill_surfaces.empty()) - rd.has_support_infill = Tristate::Yes; - } - - double adaptive_fill_density = 0.; - double adaptive_infill_extrusion_width = 0.; - int adaptive_cnt = 0; - double support_fill_density = 0.; - double support_infill_extrusion_width = 0.; - int support_cnt = 0; - - for (const RegionFillData &rd : region_fill_data) { - if (rd.has_adaptive_infill == Tristate::Yes) { - adaptive_fill_density += rd.density; - adaptive_infill_extrusion_width += rd.extrusion_width; - ++ adaptive_cnt; - } else if (rd.has_support_infill == Tristate::Yes) { - support_fill_density += rd.density; - support_infill_extrusion_width += rd.extrusion_width; - ++ support_cnt; - } - } - - auto to_line_spacing = [](int cnt, double density, double extrusion_width) { - if (cnt) { - density /= double(cnt); - extrusion_width /= double(cnt); - return extrusion_width / ((density / 100.0f) * 0.333333333f); - } else - return 0.; - }; - const int n_multiline = print_object.printing_region(0).config().fill_multiline.value; - adaptive_line_spacing = to_line_spacing(adaptive_cnt, adaptive_fill_density, adaptive_infill_extrusion_width) * n_multiline; - support_line_spacing = to_line_spacing(support_cnt, support_fill_density, support_infill_extrusion_width) * n_multiline; - } - - return std::make_pair(adaptive_line_spacing, support_line_spacing); + return line_spacing; } // Context used by generate_infill_lines() when recursively traversing an octree in a DDA fashion diff --git a/src/libslic3r/Fill/FillAdaptive.hpp b/src/libslic3r/Fill/FillAdaptive.hpp index 57cbe713f1..47b6f62e28 100644 --- a/src/libslic3r/Fill/FillAdaptive.hpp +++ b/src/libslic3r/Fill/FillAdaptive.hpp @@ -59,11 +59,22 @@ struct Octrees } }; -// Calculate line spacing for -// 1) adaptive cubic infill -// 2) adaptive internal support cubic infill -// Returns zero for a particular infill type if no such infill is to be generated. -std::pair adaptive_fill_line_spacing(const PrintObject &print_object); +// Orca: The octrees of each line spacing the regions of an object fill with. +struct RegionOctrees +{ + std::vector sets; + // Index into sets for each region, -1 for a region without adaptive or support cubic infill. + std::vector region_set; + + const Octrees *region(size_t region_id) const + { + return region_id < region_set.size() && region_set[region_id] >= 0 ? &sets[region_set[region_id]] : nullptr; + } +}; + +// Line spacing of the adaptive or support cubic infill of each region of the object, +// zero for a region that generates no such infill. +std::vector adaptive_fill_line_spacing(const PrintObject &print_object); // Rotation of the octree to stand on one of its corners. Eigen::Quaterniond transform_to_world(); diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 74df3e370c..d691a1da9d 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -33,7 +33,7 @@ class PrintObject; class Print; namespace FillAdaptive { - struct Octrees; + struct RegionOctrees; }; namespace FillLightning { @@ -207,10 +207,9 @@ public: static bool is_perimeter_compatible(const Print& print, const PrintRegion& a, const PrintRegion& b); void make_perimeters(); // Phony version of make_fills() without parameters for Perl integration only. - void make_fills() { this->make_fills(nullptr, nullptr); } - void make_fills(const FillAdaptive::Octrees* adaptive_fill_octrees, const FillAdaptive::Octrees* support_fill_octrees, FillLightning::Generator* lightning_generator = nullptr); - Polylines generate_sparse_infill_polylines_for_anchoring(const FillAdaptive::Octrees *adaptive_fill_octrees, - const FillAdaptive::Octrees *support_fill_octrees, + void make_fills() { this->make_fills(nullptr); } + void make_fills(const FillAdaptive::RegionOctrees* fill_octrees, FillLightning::Generator* lightning_generator = nullptr); + Polylines generate_sparse_infill_polylines_for_anchoring(const FillAdaptive::RegionOctrees *fill_octrees, FillLightning::Generator* lightning_generator) const; void make_ironing(); // Returns the filament id (1-based) the region is ironed with, or -1 when the diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 25d08316b6..5e85bb040c 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -583,7 +583,7 @@ private: void discover_horizontal_shells(); void combine_infill(); void _generate_support_material(); - std::pair prepare_adaptive_infill_data( + FillAdaptive::RegionOctrees prepare_adaptive_infill_data( const std::vector>& surfaces_w_layer) const; FillLightning::GeneratorPtr prepare_lightning_infill_data(); @@ -616,7 +616,7 @@ private: // so that next call to make_perimeters() performs a union() before computing loops bool m_typed_slices = false; - std::pair m_adaptive_fill_octrees; + FillAdaptive::RegionOctrees m_adaptive_fill_octrees; std::vector m_separated_body_bboxes; FillLightning::GeneratorPtr m_lightning_generator; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index e65314a68a..4f4d823951 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -848,7 +848,7 @@ void PrintObject::infill() [this](const tbb::blocked_range& range) { for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { m_print->throw_if_canceled(); - m_layers[layer_idx]->make_fills(&m_adaptive_fill_octrees.first, &m_adaptive_fill_octrees.second, this->m_lightning_generator.get()); + m_layers[layer_idx]->make_fills(&m_adaptive_fill_octrees, this->m_lightning_generator.get()); } } ); @@ -1168,14 +1168,27 @@ static std::vector split_mesh_by_body(const PrintObject &o return bodies; } -std::pair PrintObject::prepare_adaptive_infill_data( +FillAdaptive::RegionOctrees PrintObject::prepare_adaptive_infill_data( const std::vector> &surfaces_w_layer) const { using namespace FillAdaptive; - auto [adaptive_line_spacing, support_line_spacing] = adaptive_fill_line_spacing(*this); - if ((adaptive_line_spacing == 0. && support_line_spacing == 0.) || this->layers().empty()) + // Orca: Each region fills with the octrees of its own line spacing, shared by the regions of equal spacing. + const std::vector line_spacing = adaptive_fill_line_spacing(*this); + std::vector> spacings; // Line spacing, support cubic. + RegionOctrees octrees; + octrees.region_set.assign(line_spacing.size(), -1); + for (size_t region_id = 0; region_id < line_spacing.size(); ++ region_id) + if (line_spacing[region_id] > 0.) { + const std::pair spacing(line_spacing[region_id], this->printing_region(region_id).config().sparse_infill_pattern == ipSupportCubic); + const auto it = std::find(spacings.begin(), spacings.end(), spacing); + octrees.region_set[region_id] = int(it - spacings.begin()); + if (it == spacings.end()) + spacings.push_back(spacing); + } + if (spacings.empty() || this->layers().empty()) return {}; + octrees.sets.resize(spacings.size()); indexed_triangle_set mesh = this->model_object()->raw_indexed_triangle_set(); // Rotate mesh and build octree on it with axis-aligned (standart base) cubes. @@ -1198,44 +1211,50 @@ std::pair PrintObject::prepare_ada } }); - // Orca: Each body gets the octree it has when sliced on its own, from its own triangles. - std::pair octrees; - const size_t num_bodies = m_separated_body_bboxes.size(); - bool need_object = num_bodies <= 1; + // Orca: Each body gets the octree it has when sliced on its own, from its own triangles, for each line spacing + // its regions fill with. Body num_bodies stands for the whole object, which serves an object of a single body + // and the surfaces of bodies that have no octree of their own. + const size_t num_bodies = m_separated_body_bboxes.size(); + std::vector> to_build; // Set, body. + std::vector body_meshes; + std::vector> body_overhangs(num_bodies); if (num_bodies > 1) { - const std::vector body_meshes = split_mesh_by_body(*this, mesh, num_bodies); - need_object = std::any_of(body_meshes.begin(), body_meshes.end(), [](const indexed_triangle_set &its) { return its.indices.empty(); }); - std::vector> body_overhangs(num_bodies); + body_meshes = split_mesh_by_body(*this, mesh, num_bodies); for (size_t i = 0; i < surfaces_w_layer.size(); ++ i) if (const int body = separated_body_at(*surfaces_w_layer[i].second, surfaces_w_layer[i].first->expolygon.contour.points.front()); body >= 0) append(body_overhangs[body], overhangs[i]); - if (adaptive_line_spacing) - octrees.first.bodies.resize(num_bodies); - if (support_line_spacing) - octrees.second.bodies.resize(num_bodies); - tbb::parallel_for(tbb::blocked_range(0, num_bodies), [&, adaptive_spacing = adaptive_line_spacing, support_spacing = support_line_spacing]( - const tbb::blocked_range &range) { - for (size_t body = range.begin(); body < range.end(); ++ body) { - m_print->throw_if_canceled(); - if (body_meshes[body].indices.empty()) - continue; - if (adaptive_spacing) - octrees.first.bodies[body] = build_octree(body_meshes[body], body_overhangs[body], adaptive_spacing, false); - if (support_spacing) - octrees.second.bodies[body] = build_octree(body_meshes[body], body_overhangs[body], support_spacing, true); - } - }); - } + std::vector> fills(spacings.size(), std::vector(num_bodies + 1, false)); + for (const Layer *layer : m_layers) + for (size_t region_id = 0; region_id < layer->regions().size() && region_id < octrees.region_set.size(); ++ region_id) + if (const int set = octrees.region_set[region_id]; set >= 0) + for (const Surface &surface : layer->regions()[region_id]->fill_surfaces) { + const int body = separated_body_at(*layer, surface.expolygon.contour.points.front()); + fills[set][body >= 0 && ! body_meshes[body].indices.empty() ? size_t(body) : num_bodies] = true; + } + for (size_t set = 0; set < spacings.size(); ++ set) { + octrees.sets[set].bodies.resize(num_bodies); + for (size_t body = 0; body <= num_bodies; ++ body) + if (fills[set][body]) + to_build.emplace_back(set, body); + } + } else + for (size_t set = 0; set < spacings.size(); ++ set) + to_build.emplace_back(set, num_bodies); // and gather them. for (size_t i = 1; i < overhangs.size(); ++ i) append(overhangs.front(), std::move(overhangs[i])); - // Orca: The object's octree only serves bodies that have none of their own. - if (need_object && adaptive_line_spacing) - octrees.first.object = build_octree(mesh, overhangs.front(), adaptive_line_spacing, false); - if (need_object && support_line_spacing) - octrees.second.object = build_octree(mesh, overhangs.front(), support_line_spacing, true); + tbb::parallel_for(tbb::blocked_range(0, to_build.size()), [&](const tbb::blocked_range &range) { + for (size_t i = range.begin(); i < range.end(); ++ i) { + m_print->throw_if_canceled(); + const auto [set, body] = to_build[i]; + const bool object = body == num_bodies; + (object ? octrees.sets[set].object : octrees.sets[set].bodies[body]) = + build_octree(object ? mesh : body_meshes[body], object ? overhangs.front() : body_overhangs[body], spacings[set].first, + spacings[set].second); + } + }); return octrees; } @@ -3077,8 +3096,7 @@ void PrintObject::bridge_over_infill() for (size_t job_idx = r.begin(); job_idx < r.end(); job_idx++) { size_t lidx = layers_to_generate_infill[job_idx]; infill_lines.at( - lidx) = po->get_layer(lidx)->generate_sparse_infill_polylines_for_anchoring(&po->m_adaptive_fill_octrees.first, - &po->m_adaptive_fill_octrees.second, + lidx) = po->get_layer(lidx)->generate_sparse_infill_polylines_for_anchoring(&po->m_adaptive_fill_octrees, po->m_lightning_generator.get()); } }); diff --git a/tests/fff_print/test_fill.cpp b/tests/fff_print/test_fill.cpp index 19ad1711b8..e1d93c5e15 100644 --- a/tests/fff_print/test_fill.cpp +++ b/tests/fff_print/test_fill.cpp @@ -1780,7 +1780,7 @@ TEST_CASE("Sparse plane-path anchors match the printed infill", "[Fill][Internal const AABBTreeLines::LinesDistancer printed_tree(to_lines(printed)); // Orca: Exclude perimeter connections: anchoring and extrusion can trim those differently. - const Polylines anchors = intersection_pl(layer.generate_sparse_infill_polylines_for_anchoring(nullptr, nullptr, nullptr), + const Polylines anchors = intersection_pl(layer.generate_sparse_infill_polylines_for_anchoring(nullptr, nullptr), shrink(to_polygons(layer.lslices), scale_(3.))); REQUIRE_FALSE(anchors.empty()); double max_distance = 0.; @@ -1792,8 +1792,9 @@ TEST_CASE("Sparse plane-path anchors match the printed infill", "[Fill][Internal CHECK(unscale(max_distance) <= config.opt_float("resolution")); } -// Orca: Slices the meshes as the parts of one object, where they are. -static Print &slice_parts(Print &print, DynamicPrintConfig config, const std::vector &parts) +// Orca: Slices the meshes as the parts of one object, where they are, with modifiers of their own config. +static Print &slice_parts(Print &print, DynamicPrintConfig config, const std::vector &parts, + const std::vector> &modifiers = {}) { config.set_deserialize_strict({{"layer_height", 0.2}, {"initial_layer_print_height", 0.2}, @@ -1804,6 +1805,8 @@ static Print &slice_parts(Print &print, DynamicPrintConfig config, const std::ve Slic3r::Test::init_print({parts.front()}, print, model, config, nullptr, false); for (size_t i = 1; i < parts.size(); ++ i) model.objects.front()->add_volume(TriangleMesh(parts[i]), ModelVolumeType::MODEL_PART, false); + for (const auto &[mesh, modifier_config] : modifiers) + model.objects.front()->add_volume(TriangleMesh(mesh), ModelVolumeType::PARAMETER_MODIFIER, false)->config.apply(modifier_config); print.apply(model, config); print.process(); return print; @@ -1988,3 +1991,33 @@ TEST_CASE("Adaptive infill fills each body like the body sliced alone", "[Fill][ CHECK(unmatched.first < 0.02); CHECK(unmatched.second < 0.02); } + +TEST_CASE("Adaptive infill of a modifier leaves the density of the other regions", "[Fill][Regression]") +{ + const std::string pattern = GENERATE("adaptivecubic", "supportcubic"); + CAPTURE(pattern); + auto config = DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({{"sparse_infill_pattern", pattern}, + {"sparse_infill_density", "15%"}, + {"top_shell_layers", 0}, + {"bottom_shell_layers", 0}}); + TriangleMesh bodies = make_cube(30, 30, 6), second = make_cube(30, 30, 6); + second.translate(40, 0, 0); + bodies.merge(second); + // Orca: A denser modifier over the right half of the second body. + TriangleMesh modifier = make_cube(20, 40, 10); + modifier.translate(55, -5, -2); + DynamicPrintConfig dense = config; + dense.set_deserialize_strict({{"sparse_infill_density", "60%"}}); + Print print, print_sparse, print_dense; + slice_parts(print, config, {bodies}, {{modifier, dense}}); + slice_parts(print_sparse, config, {bodies}); + slice_parts(print_dense, dense, {bodies}); + + // Orca: Bed regions 3 mm inside the walls and the modifier, away from the links along them. + auto rect = [](double x0, double y0, double x1, double y1) { + return Polygon({Point::new_scale(x0, y0), Point::new_scale(x1, y0), Point::new_scale(x1, y1), Point::new_scale(x0, y1)}); + }; + CHECK(unmatched_between_prints(print, print_sparse, erInternalInfill, {rect(3, 3, 27, 27), rect(43, 3, 52, 27)}) < 0.02); + CHECK(unmatched_between_prints(print, print_dense, erInternalInfill, {rect(58, 3, 67, 27)}) < 0.02); +} From 16380e856048f74205d79d4053ffb3717eaadcbf Mon Sep 17 00:00:00 2001 From: lodriguez Date: Fri, 9 Oct 2026 01:35:47 +0200 Subject: [PATCH 08/10] add fallback to link spnav dynamiclly (#14223) --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2bfb01a657..a208c5e7a2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1084,10 +1084,10 @@ endif () find_path(SPNAV_INCLUDE_DIR spnav.h) if (SPNAV_INCLUDE_DIR) - find_library(SPNAV_LIB NAMES libspnav.a) # Force linking libspnav statically + find_library(SPNAV_LIB NAMES libspnav.a spnav) if (SPNAV_LIB) add_definitions(-DHAVE_SPNAV) - message(STATUS "SPNAV library found") + message(STATUS "SPNAV library found: ${SPNAV_LIB}") else() message(STATUS "SPNAV library NOT found, Spacenavd not supported") endif() From c7e7fa2a0bb95d1b1020d75db55f4393409f71ad Mon Sep 17 00:00:00 2001 From: TheLegendTubaGuy <95944177+thelegendtubaguy@users.noreply.github.com> Date: Thu, 8 Oct 2026 18:40:52 -0500 Subject: [PATCH 09/10] Multi Object Vase Settings from Plate Settings Change (#16091) --- src/slic3r/GUI/PartPlate.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/slic3r/GUI/PartPlate.cpp b/src/slic3r/GUI/PartPlate.cpp index 9798d2df23..78ee62c680 100644 --- a/src/slic3r/GUI/PartPlate.cpp +++ b/src/slic3r/GUI/PartPlate.cpp @@ -3128,7 +3128,7 @@ void PartPlate::set_vase_mode_related_object_config(int obj_id) { new_conf.set_key_value("detect_thin_wall", new ConfigOptionBool(false)); new_conf.set_key_value("timelapse_type", new ConfigOptionEnum(tlTraditional)); new_conf.set_key_value("overhang_reverse", new ConfigOptionBool(false)); - auto applying_keys = global_config->diff(new_conf); + const auto applying_keys = global_config->diff(new_conf); for (ModelObject* object : obj_ptrs) { ModelConfigObject& config = object->config; @@ -3137,8 +3137,8 @@ void PartPlate::set_vase_mode_related_object_config(int obj_id) { config.set_key_value(opt_key, new_conf.option(opt_key)->clone()); } - applying_keys = config.get().diff(new_conf); - for (auto opt_key : applying_keys) { + const auto object_keys = config.get().diff(new_conf); + for (auto opt_key : object_keys) { config.set_key_value(opt_key, new_conf.option(opt_key)->clone()); } } From b3490a1cdce0ac5e36d94ff9659700dc906d0212 Mon Sep 17 00:00:00 2001 From: Harm Berntsen Date: Fri, 9 Oct 2026 03:39:26 +0200 Subject: [PATCH 10/10] Prevent segfault when X509_get_default_cert_file_env() environment variable is not set (#16128) --- src/slic3r/Utils/Http.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/slic3r/Utils/Http.cpp b/src/slic3r/Utils/Http.cpp index 552c0b6a39..c268ef251c 100644 --- a/src/slic3r/Utils/Http.cpp +++ b/src/slic3r/Utils/Http.cpp @@ -947,13 +947,12 @@ std::string Http::tls_global_init() std::string Http::tls_system_cert_store() { - std::string ret; - #ifdef OPENSSL_CERT_OVERRIDE - ret = ::getenv(X509_get_default_cert_file_env()); + if (const char *cert_file = ::getenv(X509_get_default_cert_file_env())) + return cert_file; #endif - return ret; + return {}; } std::string Http::url_encode(const std::string &str)