From b29c3b36ece50495f4ab1687e29beb346c23ab84 Mon Sep 17 00:00:00 2001 From: Ioannis Giannakas <59056762+igiannakas@users.noreply.github.com> Date: Sun, 4 Oct 2026 20:08:42 +0100 Subject: [PATCH 1/6] Fix a small memory leak when creating default enum list options (#16133) Fix memory leak in ConfigOptionDef::create_default_option for enum lists --- src/libslic3r/Config.cpp | 18 +++++++----------- tests/libslic3r/test_config.cpp | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index cac46d7108..7428c10fe7 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -326,17 +326,13 @@ ConfigOption* ConfigOptionDef::create_default_option() const return new ConfigOptionEnumGeneric(this->enum_keys_map, this->default_value->getInt()); if (type == coEnums) { - auto dft = this->default_value->clone(); - if (dft->nullable()) { - ConfigOptionEnumsGenericNullable *opt = dynamic_cast(this->default_value->clone()); - opt->keys_map = this->enum_keys_map; - return opt; - } else { - ConfigOptionEnumsGeneric *opt = dynamic_cast(this->default_value->clone()); - opt->keys_map = this->enum_keys_map; - return opt; - } - delete dft; + // Enum list defaults are built without a keys map, which the copy needs to deserialize and serialize names. + ConfigOption *opt = this->default_value->clone(); + if (auto *nullable_enums = dynamic_cast(opt)) + nullable_enums->keys_map = this->enum_keys_map; + else if (auto *enums = dynamic_cast(opt)) + enums->keys_map = this->enum_keys_map; + return opt; } return this->default_value->clone(); diff --git a/tests/libslic3r/test_config.cpp b/tests/libslic3r/test_config.cpp index 1b48c9551e..da8c7195fb 100644 --- a/tests/libslic3r/test_config.cpp +++ b/tests/libslic3r/test_config.cpp @@ -1477,3 +1477,21 @@ TEST_CASE("A static config applied onto a config of another type falls back to a dynamic.apply(region); CHECK(dynamic.opt_serialize("sparse_infill_pattern") == "gyroid"); } + +TEST_CASE("Default options of enum lists get their definition's keys map", "[Config]") +{ + size_t enum_lists = 0; + for (const auto &[key, def] : print_config_def.options) { + if (def.type != coEnums || !def.default_value) + continue; + INFO(key); + const std::unique_ptr opt(def.create_default_option()); + CHECK(*opt == *def.default_value); + const auto *nullable_enums = dynamic_cast(opt.get()); + const auto *enums = dynamic_cast(opt.get()); + REQUIRE((nullable_enums != nullptr) != (enums != nullptr)); + CHECK((nullable_enums != nullptr ? nullable_enums->keys_map : enums->keys_map) == def.enum_keys_map); + ++enum_lists; + } + CHECK(enum_lists > 0); +} From 7fd6e5fd723ef9133530a8a8fbac7cbb2e12f408 Mon Sep 17 00:00:00 2001 From: Ioannis Giannakas <59056762+igiannakas@users.noreply.github.com> Date: Sun, 4 Oct 2026 21:54:47 +0100 Subject: [PATCH 2/6] Fix crash on macOS when OrcaSlicer is quit from the Dock, a logout or a restart (#16136) Fix crash when quitting from the Dock, logout or restart on macOS --- src/slic3r/GUI/GUI_App.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 94ef20e9fd..73a58c5b51 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -3087,6 +3087,18 @@ bool GUI_App::on_init_inner() d->EndModal(wxID_ABORT); }); +#ifdef __APPLE__ + // A quit request from the Dock, a logout or a restart ends with AppKit calling exit() right after this event, so + // OnExit() and ~GUI_App() never run. Shut the plugins and Python down here as ~GUI_App() does. Left to + // PluginManager's static destructor, the shutdown locks hook state that has already been destroyed and aborts. + wxGetApp().Bind(wxEVT_END_SESSION, [](wxCloseEvent &e) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "received wxEVT_END_SESSION"; + Slic3r::PluginManager::instance().shutdown(); + Slic3r::PythonInterpreter::instance().shutdown(); + e.Skip(); + }); +#endif + // Verify resources path const wxString resources_dir = from_u8(Slic3r::resources_dir()); wxCHECK_MSG(wxDirExists(resources_dir), false, From 93fca831223c76e5bdfbc82f665401df8b781d4d Mon Sep 17 00:00:00 2001 From: TheLegendTubaGuy <95944177+thelegendtubaguy@users.noreply.github.com> Date: Sun, 4 Oct 2026 16:13:17 -0500 Subject: [PATCH 3/6] Fix memory leak of gap fill paths in solid infill (#16137) --- src/libslic3r/Fill/FillBase.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index 91a3bb42af..aabd2b3209 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -264,10 +264,7 @@ void Fill::_create_gap_fill(const Surface* surface, const FillParams& params, Ex return p.length() < scale_(params.config->filter_out_gap_fill.value); }), polylines.end()); - ExtrusionEntityCollection gap_fill; - variable_width(polylines, erGapFill, params.flow, gap_fill.entities); - auto gap = std::move(gap_fill.entities); - out->append(gap); + variable_width(polylines, erGapFill, params.flow, out->entities); } } } From fb529f8315f55009bd737fce8b51790a1a14b041 Mon Sep 17 00:00:00 2001 From: Damir Galeev <49319928+DImagine@users.noreply.github.com> Date: Mon, 5 Oct 2026 00:22:35 +0300 Subject: [PATCH 4/6] Fix startup freeze from synchronous scripts in the camera view (#16104) --- src/slic3r/GUI/StatusPanel.cpp | 31 ------------------------------- src/slic3r/GUI/StatusPanel.hpp | 2 -- 2 files changed, 33 deletions(-) diff --git a/src/slic3r/GUI/StatusPanel.cpp b/src/slic3r/GUI/StatusPanel.cpp index a1f2b72c1a..6cea75fb71 100644 --- a/src/slic3r/GUI/StatusPanel.cpp +++ b/src/slic3r/GUI/StatusPanel.cpp @@ -1618,19 +1618,11 @@ wxBoxSizer *StatusBasePanel::create_monitoring_page() m_custom_camera_view = WebView::CreateWebView(this, wxEmptyString); m_custom_camera_view->EnableContextMenu(false); - Bind(wxEVT_WEBVIEW_NAVIGATING, &StatusBasePanel::on_webview_navigating, this, m_custom_camera_view->GetId()); m_web_media_controller = std::make_unique(m_custom_camera_view); m_media_play_ctrl = new MediaPlayCtrl(this, m_media_ctrl, wxDefaultPosition, wxSize(-1, FromDIP(40))); m_media_play_ctrl->SetWebMediaController(m_web_media_controller.get()); m_custom_camera_view->Hide(); - // m_custom_camera_view->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, [this](wxWebViewEvent& evt) { - // if (evt.GetString() == "leavepictureinpicture") { - // // When leaving PiP, video gets paused in some cases and toggling play - // // programmatically does not work. - // m_custom_camera_view->Reload(); - // } - // }); sizer->Add(m_media_ctrl, 1, wxEXPAND | wxALL, 0); sizer->Add(m_custom_camera_view, 1, wxEXPAND | wxALL, 0); @@ -1643,12 +1635,6 @@ wxBoxSizer *StatusBasePanel::create_monitoring_page() return sizer; } -void StatusBasePanel::on_webview_navigating(wxWebViewEvent& evt) { - wxGetApp().CallAfter([this] { - remove_controls(); - }); -} - wxBoxSizer *StatusBasePanel::create_machine_control_page(wxWindow *parent) { wxBoxSizer *bSizer_right = new wxBoxSizer(wxVERTICAL); @@ -5121,23 +5107,6 @@ void StatusPanel::on_camera_enter(wxMouseEvent& event) } } -void StatusBasePanel::remove_controls() -{ - const std::string js_cleanup_video_element = R"( - document.body.style.overflow='hidden'; - const video = document.querySelector('video'); - video.setAttribute('style', 'width: 100% !important;'); - video.removeAttribute('controls'); - video.addEventListener('leavepictureinpicture', () => { - window.wx.postMessage('leavepictureinpicture'); - }); - video.addEventListener('enterpictureinpicture', () => { - window.wx.postMessage('enterpictureinpicture'); - }); - )"; - m_custom_camera_view->RunScript(js_cleanup_video_element); -} - void StatusPanel::on_camera_leave(wxMouseEvent& event) { if (obj && m_camera_popup) { diff --git a/src/slic3r/GUI/StatusPanel.hpp b/src/slic3r/GUI/StatusPanel.hpp index 9f7547ecf1..4a17aff571 100644 --- a/src/slic3r/GUI/StatusPanel.hpp +++ b/src/slic3r/GUI/StatusPanel.hpp @@ -606,8 +606,6 @@ protected: virtual void on_axis_ctrl_e_up_10(wxCommandEvent &event) { event.Skip(); } virtual void on_axis_ctrl_e_down_10(wxCommandEvent &event) { event.Skip(); } virtual void on_nozzle_selected(wxCommandEvent &event) { event.Skip(); } - void remove_controls(); - void on_webview_navigating(wxWebViewEvent& evt); public: StatusBasePanel(wxWindow * parent, From 4b4a261787ee815fbde96adfe04f3871d88acc16 Mon Sep 17 00:00:00 2001 From: TheLegendTubaGuy <95944177+thelegendtubaguy@users.noreply.github.com> Date: Sun, 4 Oct 2026 16:23:40 -0500 Subject: [PATCH 5/6] Fix small binary STLs failing to load as ASCII (#16130) --- deps_src/admesh/stlinit.cpp | 13 +++++++++++++ tests/libslic3r/test_stl.cpp | 25 +++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/deps_src/admesh/stlinit.cpp b/deps_src/admesh/stlinit.cpp index 9d44cdf266..3b8f59866a 100644 --- a/deps_src/admesh/stlinit.cpp +++ b/deps_src/admesh/stlinit.cpp @@ -75,6 +75,19 @@ static FILE *stl_open_count_facets(stl_file *stl, const char *file, unsigned int break; } } + // Zero normals and coordinates like 10 or 15 have no byte above 127, so the test above can miss a binary file. + // Its size still matches its facet count; text read as that count would need a file of gigabytes. + if (stl->stats.type == ascii) { + uint32_t header_num_facets; + fseek(fp, custom_header_length, SEEK_SET); + if (fread(&header_num_facets, sizeof(uint32_t), 1, fp) == 1) { +#if BOOST_ENDIAN_BIG_BYTE + stl_internal_reverse_quads((char*)&header_num_facets, 4); +#endif /* BOOST_ENDIAN_BIG_BYTE */ + if (header_size + uint64_t(header_num_facets) * SIZEOF_STL_FACET == file_size) + stl->stats.type = binary; + } + } rewind(fp); uint32_t num_facets = 0; diff --git a/tests/libslic3r/test_stl.cpp b/tests/libslic3r/test_stl.cpp index 0223392c3f..b121bfc122 100644 --- a/tests/libslic3r/test_stl.cpp +++ b/tests/libslic3r/test_stl.cpp @@ -1,10 +1,13 @@ #include +#include #include #include "libslic3r/Point.hpp" #include #include "libslic3r/Model.hpp" #include "libslic3r/Format/STL.hpp" +#include "test_utils.hpp" +#include using namespace Slic3r; @@ -58,3 +61,25 @@ SCENARIO("Reading an STL file", "[stl]") { } } } + +TEST_CASE("A binary STL whose facet bytes never exceed 127 is read as binary", "[stl]") +{ + const indexed_triangle_set cube = its_make_cube(10., 10., 10.); + std::string stl(80, '\0'); + const auto append = [&stl](const auto &value) { stl.append(reinterpret_cast(&value), sizeof(value)); }; + append(uint32_t(cube.indices.size())); + const stl_normal zero_normal = stl_normal::Zero(); + for (const stl_triangle_vertex_indices &facet : cube.indices) { + append(zero_normal); + for (int i = 0; i < 3; ++i) + append(cube.vertices[facet[i]]); + append(uint16_t(0)); + } + REQUIRE(std::none_of(stl.begin() + 84, stl.begin() + 84 + 128, [](unsigned char c) { return c > 127; })); + + ScopedTemporaryFile file(".stl"); + boost::nowide::ofstream(file.string(), std::ios::binary) << stl; + Model model; + REQUIRE(load_stl(file.string().c_str(), &model)); + REQUIRE(is_approx(model.objects.front()->volumes.front()->mesh().size(), Vec3d(10, 10, 10))); +} From f3d0b8a553958a10fe94df7d6ca2513d5690e8e3 Mon Sep 17 00:00:00 2001 From: Kris Austin Date: Sun, 4 Oct 2026 19:12:32 -0500 Subject: [PATCH 6/6] ci: move macOS jobs off the retiring macos-14 runner (#16088) --- .github/workflows/build_all.yml | 6 +++--- .github/workflows/build_check_cache.yml | 2 +- .github/workflows/build_orca.yml | 8 +++++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build_all.yml b/.github/workflows/build_all.yml index 0fcb6ba869..ab4cd57f5f 100644 --- a/.github/workflows/build_all.yml +++ b/.github/workflows/build_all.yml @@ -127,7 +127,7 @@ jobs: if: ${{ !cancelled() && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }} uses: ./.github/workflows/build_check_cache.yml with: - os: ${{ vars.SELF_HOSTED && 'orca-macos-arm64' || 'macos-14' }} + os: ${{ vars.SELF_HOSTED && 'orca-macos-arm64' || 'macos-15' }} arch: ${{ matrix.arch }} build-deps-only: ${{ inputs.build-deps-only || false }} force-build: ${{ github.event_name == 'schedule' }} @@ -138,7 +138,7 @@ jobs: if: ${{ !cancelled() && needs.build_macos_arch.result == 'success' && !inputs.build-deps-only && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }} uses: ./.github/workflows/build_orca.yml with: - os: ${{ vars.SELF_HOSTED && 'orca-macos-arm64' || 'macos-14' }} + os: ${{ vars.SELF_HOSTED && 'orca-macos-arm64' || 'macos-15' }} arch: universal macos-combine-only: true secrets: inherit @@ -182,7 +182,7 @@ jobs: if: ${{ !cancelled() && success() }} uses: ./.github/workflows/unit_tests.yml with: - os: ${{ vars.SELF_HOSTED && 'orca-macos-arm64' || 'macos-14' }} + os: ${{ vars.SELF_HOSTED && 'orca-macos-arm64' || 'macos-15' }} artifact: ${{ github.sha }}-tests-macos-arm64 test-dir: build/arm64/tests # Slice a two-colour cube through every shipped printer, and through every diff --git a/.github/workflows/build_check_cache.yml b/.github/workflows/build_check_cache.yml index 2da05e1d69..31fdd6cb28 100644 --- a/.github/workflows/build_check_cache.yml +++ b/.github/workflows/build_check_cache.yml @@ -40,7 +40,7 @@ jobs: # Anything that changes how the tree is built belongs in the key, or a job # restores one it cannot use. Linux amd64 passes no arch deliberately, so # 'linux-clang' keeps the cache it already has. - cache-os: ${{ runner.os == 'macOS' && format('macos-{0}', inputs.arch) || (runner.os == 'Windows' && format('windows-{0}-{1}', inputs.arch, inputs.compiler) || format('linux-clang{0}', inputs.arch && format('-{0}', inputs.arch) || '')) }} + cache-os: ${{ runner.os == 'macOS' && format('{0}-{1}', inputs.os, inputs.arch) || (runner.os == 'Windows' && format('windows-{0}-{1}', inputs.arch, inputs.compiler) || format('linux-clang{0}', inputs.arch && format('-{0}', inputs.arch) || '')) }} # The Windows ARM64 deps build in build-arm64, all others under build; # build_deps.yml and build_orca.yml pass the Windows directory to build_win.bat. dep-folder-name: ${{ runner.os == 'macOS' && format('/{0}', inputs.arch) || (runner.os == 'Windows' && inputs.arch == 'arm64') && '-arm64/OrcaSlicer_dep' || '/OrcaSlicer_dep' }} diff --git a/.github/workflows/build_orca.yml b/.github/workflows/build_orca.yml index 98f0d66032..7b001a8c48 100644 --- a/.github/workflows/build_orca.yml +++ b/.github/workflows/build_orca.yml @@ -183,10 +183,12 @@ jobs: - name: Free disk space if: runner.os == 'macOS' && !inputs.macos-combine-only && !vars.SELF_HOSTED run: | - df -hI /dev/disk3s1s1 - sudo find /Applications -maxdepth 1 -type d -name "Xcode_*.app" ! -name "Xcode_15.4.app" -exec rm -rf {} + + df -hI / + # Keep only the selected Xcode + xcode=$(basename "$(cd "$(xcode-select -p)/../.." && pwd -P)") + sudo find /Applications -maxdepth 1 -type d -name "Xcode_*.app" ! -name "$xcode" -exec rm -rf {} + sudo rm -rf ~/Library/Developer/CoreSimulator/Caches/* - df -hI /dev/disk3s1s1 + df -hI / - name: Build slicer mac if: runner.os == 'macOS' && !inputs.macos-combine-only