From 2b6eb425e428df118326eba55f5b6f41fe84f2e5 Mon Sep 17 00:00:00 2001 From: Lam Wei Lun Date: Tue, 15 Sep 2026 18:04:06 +0800 Subject: [PATCH 1/6] Update YouTube URL for publish 3MF guide --- src/slic3r/GUI/PublishSettingsDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/PublishSettingsDialog.cpp b/src/slic3r/GUI/PublishSettingsDialog.cpp index e63cec7615..82de9fca68 100644 --- a/src/slic3r/GUI/PublishSettingsDialog.cpp +++ b/src/slic3r/GUI/PublishSettingsDialog.cpp @@ -665,7 +665,7 @@ PublishSettingsDialog::PublishSettingsDialog(wxWindow* parent, }; wxBoxSizer* links_sizer = new wxBoxSizer(wxVERTICAL); links_sizer->Add(make_link(_L("Publish 3MF Wiki"), "https://www.orcaslicer.com/wiki/publishing_3mf/publish_3mf.html"), 0, wxALIGN_LEFT); - links_sizer->Add(make_link(_L("Publish 3MF Video Guide"), "https://www.youtube.com/@OfficialOrcaSlicer/videos"), 0, + links_sizer->Add(make_link(_L("Publish 3MF Video Guide"), "https://www.youtube.com/watch?v=-xt1N29UIOg"), 0, wxTOP | wxALIGN_LEFT, FromDIP(4)); wxBoxSizer* footer = new wxBoxSizer(wxHORIZONTAL); From bd1304443cb417d39c7be7d4182d8d8c1f737908 Mon Sep 17 00:00:00 2001 From: Kris Austin Date: Tue, 15 Sep 2026 07:03:20 -0500 Subject: [PATCH 2/6] fix: guard per-filament array reads against short config arrays (#14789) * fix: guard H2C per-filament array reads against short config arrays The H2C tool-ordering, wipe-tower, and g-code export paths index per-filament config arrays by filament/tool id. A config with fewer entries than the filament count (partial or legacy projects, minimal test configs) makes these reads run past the end of the vector: silent under a normal STL, but UB that aborts under the flatpak build's bounds-checked STL (_GLIBCXX_ASSERTIONS). Route the reads through the existing clamping accessors (get_at, get_filament_category, is_in_same_extruder) and add a small clamp helper for filament_change_length. The guards are no-ops when the arrays are sized to the filament count, so correctly specified configs are unaffected. * fix: size the grouping context's filament_info to the filament count build_filament_group_context built model_info.filament_info by walking filament_type, so a config whose filament_type is shorter than the filament count produced a short vector. FilamentGroup indexes filament_info by filament id, so clamping the individual reads only moved the out-of-bounds access downstream. Loop to filament_nums and read all three fields through get_at, and drop filament_ids entries past the filament count, since the grouping code pairs filament_ids and filament_info by position. Adds a regression test with four filaments and one-entry filament_type / filament_is_support. Without the fix it throws bad_alloc from copying a garbage std::string read past the end. * fix: guard the carousel nozzle-change length reads too The carousel branch added in b90ac13d86/b0dddb4648 reads m_filaments_change_length by tool id without a bounds check, the same pattern this branch already routed through filament_change_length_at a few lines above in both plan_toolchange and plan_tower_new. * fix: guard WipeTower per-filament array reads against short config arrays The BambuStudio WipeTower sync reintroduced raw per-filament array indexing that reads out of bounds when a config leaves an array shorter than the filament count: m_physical_extruder_map in format_line_M104/M109 (indexed even when empty), and m_filament_categories in get_wall_skip_points and get_wall_filament_for_all_layer. Silent on a normal STL, a hard abort under the bounds-checked STL the Flatpak build uses. Bounds-check the physical extruder map before indexing (omitting the T token, as the existing -1 path already does), and route the two raw m_filament_categories reads through the clamping get_filament_category() accessor the surrounding code already uses. No change for correctly-sized configs. --- src/libslic3r/GCode.cpp | 4 +- src/libslic3r/GCode/ToolOrdering.cpp | 19 ++++---- src/libslic3r/GCode/WipeTower.cpp | 8 ++-- .../test_toolordering_nozzle_group.cpp | 44 +++++++++++++++++++ 4 files changed, 61 insertions(+), 14 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 4aa45a60ed..902786bf7d 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -3555,7 +3555,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato auto used_filaments = print.get_slice_used_filaments(false); this->placeholder_parser().set("is_all_bbl_filament", std::all_of(used_filaments.begin(), used_filaments.end(), [&](auto idx) { - return m_config.filament_vendor.values[idx] == "Bambu Lab"; + return m_config.filament_vendor.get_at(idx) == "Bambu Lab"; })); //add during_print_exhaust_fan_speed @@ -3572,7 +3572,7 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato this->placeholder_parser().set("outer_wall_volumetric_speed", new ConfigOptionFloat(outer_wall_volumetric_speed)); auto first_layer_filaments = print.get_slice_used_filaments(true); - bool has_tpu_in_first_layer = std::any_of(first_layer_filaments.begin(), first_layer_filaments.end(), [&](unsigned int idx) { return m_config.filament_type.values[idx] == "TPU"; }); + bool has_tpu_in_first_layer = std::any_of(first_layer_filaments.begin(), first_layer_filaments.end(), [&](unsigned int idx) { return m_config.filament_type.get_at(idx) == "TPU"; }); this->placeholder_parser().set("has_tpu_in_first_layer", new ConfigOptionBool(has_tpu_in_first_layer)); if (print.calib_params().mode == CalibMode::Calib_PA_Line) { diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index c6517c6e65..e9be0171e4 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -1488,10 +1488,10 @@ static FilamentGroupContext build_filament_group_context( auto machine_filament_info = build_machine_filaments(print->get_extruder_filament_info(), extruder_ams_counts, ignore_ext_filament); - std::vector filament_types = print_config.filament_type.values; - std::vector filament_colours = print_config.filament_colour.values; - std::vector filament_is_support = print_config.filament_is_support.values; - std::vector filament_ids = print_config.filament_ids.values; + // The grouping code walks filament_ids and indexes filament_info by the same position. + std::vector filament_ids = print_config.filament_ids.values; + if (filament_ids.size() > filament_nums) + filament_ids.resize(filament_nums); FGMode fg_mode = mode == FilamentMapMode::fmmAutoForMatch ? FGMode::MatchMode : FGMode::FlushMode; context.model_info.flush_matrix = std::move(nozzle_flush_mtx); @@ -1500,11 +1500,14 @@ static FilamentGroupContext build_filament_group_context( context.model_info.filament_ids = filament_ids; context.model_info.unprintable_volumes = unprintable_volumes; - for (size_t idx = 0; idx < filament_types.size(); ++idx) { + // Consumers index filament_info by filament id, so it must span the filament count: a partial + // or legacy config can leave any of these arrays short, and get_at clamps. + context.model_info.filament_info.reserve(filament_nums); + for (size_t idx = 0; idx < filament_nums; ++idx) { FilamentGroupUtils::FilamentInfo info; - info.color = filament_colours[idx]; - info.type = filament_types[idx]; - info.is_support = filament_is_support[idx]; + info.color = print_config.filament_colour.get_at(idx); + info.type = print_config.filament_type.get_at(idx); + info.is_support = print_config.filament_is_support.get_at(idx); context.model_info.filament_info.emplace_back(std::move(info)); } diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 589ac14bad..e80433f4ae 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -1349,7 +1349,7 @@ public: // flavor it reaches understands, not the zero dwell the other flavors flush with. buffer += "M400\n"; buffer += "M104"; - if (target_extruder != -1) + if (target_extruder != -1 && target_extruder < int(m_physical_extruder_map.size())) buffer += (" T" + std::to_string(m_physical_extruder_map[target_extruder])); buffer += " S" + std::to_string(target_temp) + " N0"; // N0 means the gcode is generated by slicer if (!comment.empty()) buffer += " ;" + comment; @@ -1361,7 +1361,7 @@ public: WipeTowerWriter &format_line_M109(int target_temp, int target_extruder, const std::string &comment = std::string()) { std::string buffer = "M109"; - if (target_extruder != -1) + if (target_extruder != -1 && target_extruder < int(m_physical_extruder_map.size())) buffer += (" T" + std::to_string(m_physical_extruder_map[target_extruder])); buffer += " S" + std::to_string(target_temp) + " N0"; // N0 means the gcode is generated by slicer if (!comment.empty()) buffer += " ;" + comment; @@ -3309,7 +3309,7 @@ void WipeTower::get_wall_skip_points(const WipeTowerInfo &layer, int layer_id) if (!cur_block_depth.count(m_filpar[new_filament].category)) cur_block_depth[m_filpar[new_filament].category] = block->start_depth; process_depth = cur_block_depth[m_filpar[new_filament].category]; if (is_need_ramming(new_filament, old_filament, layer_id)) { - if (m_filament_categories[new_filament] == m_filament_categories[old_filament]) + if (get_filament_category(new_filament) == get_filament_category(old_filament)) process_depth += nozzle_change_depth; else { if (!cur_block_depth.count(m_filpar[old_filament].category)) { @@ -4783,7 +4783,7 @@ int WipeTower::get_wall_filament_for_all_layer() int filament_id = -1; int filament_count = 0; for (auto iter = filament_counts.begin(); iter != filament_counts.end(); ++iter) { - if (m_filament_categories[iter->first] == selected_category && iter->second > filament_count) { + if (get_filament_category(iter->first) == selected_category && iter->second > filament_count) { filament_id = iter->first; filament_count = iter->second; } diff --git a/tests/libslic3r/test_toolordering_nozzle_group.cpp b/tests/libslic3r/test_toolordering_nozzle_group.cpp index 26e36c0dbf..d01ccf5856 100644 --- a/tests/libslic3r/test_toolordering_nozzle_group.cpp +++ b/tests/libslic3r/test_toolordering_nozzle_group.cpp @@ -163,6 +163,50 @@ TEST_CASE("H2C multi-nozzle: filaments get distinct nozzles on the 6-nozzle extr } } +TEST_CASE("Grouping context spans the filament count with mis-sized config arrays", "[ToolOrdering][H2C]") +{ + // FilamentGroup indexes the grouping context's filament_info by filament id, so a short + // per-filament array must not shorten it: the reads run off the end. + DynamicPrintConfig config = DynamicPrintConfig::full_print_config(); + // Single 6-nozzle extruder: opens the grouping engine without needing a BBL multi-extruder. + config.option("nozzle_diameter", true)->values = {0.4}; + config.option("extruder_max_nozzle_count", true)->values = {6}; + config.option("extruder_nozzle_stats", true)->values = {"Standard#6"}; + + // Four filaments, with filament_type / filament_is_support left short on purpose. + config.option("filament_colour", true)->values = {"#FF0000", "#00FF00", "#0000FF", "#FFFF00"}; + config.option("filament_type", true)->values = {"PLA"}; + config.option("filament_is_support", true)->values = {0}; + config.option("filament_diameter", true)->values = {1.75, 1.75, 1.75, 1.75}; + config.option("filament_map", true)->values = {1, 1, 1, 1}; + config.option("flush_volumes_matrix", true)->values = std::vector(16, 140.); + config.option("flush_multiplier", true)->values = {1.}; + + Model model; + model.add_object("cube", "", make_cube(20, 20, 20))->add_instance(); + + Print print; + print.apply(model, config); + // apply() does not pad the per-filament arrays, so the mis-sizing survives into the engine. + REQUIRE(print.config().filament_type.values.size() < print.config().filament_colour.values.size()); + + std::vector> layer_filaments = {{0, 1}, {1, 2}, {2, 3}}; + + SECTION("short per-filament arrays still yield one entry per filament") { + auto result = ToolOrdering::get_recommended_filament_maps(layer_filaments, &print, FilamentMapMode::fmmAutoForFlush, {}, {}); + REQUIRE(result.get_extruder_map(false).size() == 4); + for (int f = 0; f < 4; ++f) + REQUIRE(result.get_extruder_id(f) == 0); + } + + SECTION("filament_ids longer than the filament count is truncated, not paired past the end") { + config.option("filament_ids", true)->values = {"a", "b", "c", "d", "e", "f"}; + print.apply(model, config); + auto result = ToolOrdering::get_recommended_filament_maps(layer_filaments, &print, FilamentMapMode::fmmAutoForFlush, {}, {}); + REQUIRE(result.get_extruder_map(false).size() == 4); + } +} + TEST_CASE("H2C dynamic selector: per-layer nozzle ids reach the g-code surface", "[ToolOrdering][H2C][Dynamic]") { // The per-layer regroup engine From ac3997c0d1920dc37ebb0a093e7e4ba423a4e7ea Mon Sep 17 00:00:00 2001 From: Kris Austin Date: Tue, 15 Sep 2026 07:46:31 -0500 Subject: [PATCH 3/6] fix: bounds-check the toolchange flush-volume and HRC per-filament lookups (#15289) * fix: bounds-check the toolchange flush-volume and HRC per-filament lookups GCode::set_extruder's toolchange flush-volume lookup and GCodeProcessor::update_slice_warnings's HRC check index per-filament and per-extruder arrays (flush_volumes_matrix, the filament map, the nozzle list) by filament/extruder id. When a config leaves one of those arrays shorter than the filament count (partial or legacy multi-extruder projects, minimal configs), the reads run off the end: silent on a normal STL, a hard abort under _GLIBCXX_ASSERTIONS. Route both reads through bounds checks: the flush lookup falls back to no flush, matching the existing unknown-old-filament branch beside it, and the HRC check skips an unmapped filament, mirroring the required_nozzle_HRC guard on the line above. When the arrays are sized to the filament count the values are unchanged, so correctly-specified configs are unaffected. * ci: retrigger checks --- src/libslic3r/GCode.cpp | 6 ++++-- src/libslic3r/GCode/GCodeProcessor.cpp | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 902786bf7d..12a3a73e7c 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -9474,12 +9474,14 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo if (old_filament_id_in_new_extruder == -1) wipe_volume = 0; else { - wipe_volume = flush_matrix[old_filament_id_in_new_extruder * number_of_extruders + new_filament_id]; + size_t flush_idx = size_t(old_filament_id_in_new_extruder) * number_of_extruders + new_filament_id; + wipe_volume = flush_idx < flush_matrix.size() ? flush_matrix[flush_idx] : 0.f; wipe_volume *= m_config.flush_multiplier.get_at(new_extruder_id); } } else { - wipe_volume = flush_matrix[old_filament_id * number_of_extruders + new_filament_id]; + size_t flush_idx = size_t(old_filament_id) * number_of_extruders + new_filament_id; + wipe_volume = flush_idx < flush_matrix.size() ? flush_matrix[flush_idx] : 0.f; wipe_volume *= m_config.flush_multiplier.get_at(new_extruder_id); // if is multi_extruder only use the fist extruder matrix } wipe_volume = std::max(0.f, wipe_volume-grab_purge_volume); diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index e4da19cd73..6fc7717386 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -7596,8 +7596,8 @@ void GCodeProcessor::update_slice_warnings() if (used_filaments[idx] < m_result.required_nozzle_HRC.size()) filament_hrc = m_result.required_nozzle_HRC[used_filaments[idx]]; - int filament_extruder_id = m_filament_maps[used_filaments[idx]]; - int extruder_hrc = nozzle_hrc_lists[filament_extruder_id]; + int filament_extruder_id = used_filaments[idx] < m_filament_maps.size() ? m_filament_maps[used_filaments[idx]] : -1; + int extruder_hrc = (filament_extruder_id >= 0 && (size_t) filament_extruder_id < nozzle_hrc_lists.size()) ? nozzle_hrc_lists[filament_extruder_id] : 0; BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(": Check HRC: filament:%1%, hrc=%2%, extruder:%3%, hrc:%4%") % used_filaments[idx] % filament_hrc % filament_extruder_id % extruder_hrc; From 7e545651bb6256e008517a26ca93e66450b97624 Mon Sep 17 00:00:00 2001 From: Kris Austin Date: Tue, 15 Sep 2026 09:55:11 -0500 Subject: [PATCH 4/6] deps: compile unicodectype.c unoptimised in the Windows arm64 Python (#15719) VS 2026's ARM64 code generator needs about 27 GB for _PyUnicode_ToNumeric, a switch with 1951 cases in Objects/unicodetype_db.h; the same file takes under 1 GB on x64. The 16 GB CI runner has an 18.9 GB commit limit and only gets through when Windows grows the pagefile on the temp disk in time, so cold arm64 dependency builds fail at random with C1002 "compiler is out of heap space". build_release_vs.bat returns 0 on failure, so the job still reports success and the incomplete dependencies are cached. A property sheet compiles that one file with optimisation off on arm64; the rest stays whole-program optimised and x64 is unchanged. MSBuild reads it from PCbuild/msbuild.rsp, which is now written at configure time and copied in, so a checkout path with spaces works too. --- deps/python3/arm64-unicodectype.props | 12 ++++++++++++ deps/python3/python3.cmake | 12 +++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 deps/python3/arm64-unicodectype.props diff --git a/deps/python3/arm64-unicodectype.props b/deps/python3/arm64-unicodectype.props new file mode 100644 index 0000000000..5f5727c842 --- /dev/null +++ b/deps/python3/arm64-unicodectype.props @@ -0,0 +1,12 @@ + + + + + + Disabled + false + + + diff --git a/deps/python3/python3.cmake b/deps/python3/python3.cmake index 2eae315d0b..3e063fac4e 100644 --- a/deps/python3/python3.cmake +++ b/deps/python3/python3.cmake @@ -88,8 +88,18 @@ if(WIN32) list(APPEND _python_env_args "PreferredToolArchitecture=${_python_tool_arch}") endif() + # MSBuild reads extra switches from PCbuild/msbuild.rsp. + set(_python_rsp "/p:PlatformToolset=${_python_platform_toolset}\n") + # VS 2026's ARM64 code generator needs about 27 GB for one function in + # Objects/unicodectype.c (python/cpython#153668); the property sheet compiles + # that file without optimisation. + if(_python_pcbuild_platform STREQUAL "ARM64") + file(TO_NATIVE_PATH "${CMAKE_CURRENT_LIST_DIR}/arm64-unicodectype.props" _python_arm64_props) + string(APPEND _python_rsp "/p:ForceImportAfterCppTargets=\"${_python_arm64_props}\"\n") + endif() + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/python3-msbuild.rsp" "${_python_rsp}") set(_conf_cmd - cmd /c "echo /p:PlatformToolset=${_python_platform_toolset}>PCbuild\\msbuild.rsp" + ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_BINARY_DIR}/python3-msbuild.rsp" /PCbuild/msbuild.rsp ) set(_build_cmd ${CMAKE_COMMAND} -E env ${_python_env_args} From 9409598c2a9c68ec571720799187bf7f02367487 Mon Sep 17 00:00:00 2001 From: Kris Austin Date: Tue, 15 Sep 2026 10:41:46 -0500 Subject: [PATCH 5/6] ci: run the unit-test suite under the flatpak build's bounds-checked STL (#14709) * ci(flatpak): run the unit suite in a separate job, mirroring the other arches Alternative to the in-job step: split build and test like the Linux/Windows/ macOS legs. The flatpak build now builds the test binaries in-sandbox (the action's run-tests fires the module's build-only test-commands), prunes the kept build tree to the test binaries + CTest metadata + data, and uploads it with /app as a test asset (size reported to the run summary). A new unit_tests_flatpak matrix job downloads that asset on a native runner, restores the module-build symlink, and runs the suite via flatpak-builder --run (which bind-mounts /run/build so TEST_DATA_DIR resolves) against the GNOME SDK's bounds-checked STL. Results feed publish_test_results. Costs a per-arch asset upload/download + a runtime install on the test runner; the trade-off vs the in-job step is a genuine separate graph box. * ci(flatpak): run tests via `flatpak build` to avoid rofiles-fuse `flatpak-builder --run` sets up a rofiles-fuse overlay that this CI container rejects (Failure spawning rofiles-fuse, exit_status: 256), even in a fresh job with a machine-id and the runtime installed, and --disable-rofiles-fuse is not accepted in --run mode. `flatpak build` enters the sandbox via bwrap directly, so it sidesteps rofiles-fuse; bind-mounting the build tree at /run/build gives the same path the compiled-in TEST_DATA_DIR expects. * ci(flatpak): slim the test asset (strip binaries, drop source tree) The first cut shipped ~1 GB: the test exes carried debug info (the SDK builds with -g and only the app gets stripped) and the packaged module dir included the whole copied source tree the tests never read at runtime. Strip the test binaries and keep only build_flatpak/tests, tests/ (TEST_DATA_DIR) and scripts/. The irreducible remainder is /app, which the exes link against. * ci(flatpak): extract the test run into a reusable unit_tests_flatpak workflow Move the flatpak test job out of build_all.yml into a reusable unit_tests_flatpak.yml, called once per arch (Flatpak x86_64 / aarch64) the same way the other arches call unit_tests.yml. build_all.yml keeps only the build + asset packaging; the reusable workflow downloads the asset, runs the suite via `flatpak build`, and uploads results as test-results- for publish_test_results. Drops the now-unused manifest checkout (flatpak build does not need it). * ci(flatpak): trim comments to the non-obvious No behavior change. * ci(flatpak): drop redundant caller comment * ci(flatpak): drop redundant trim comment * ci(flatpak): drop size-report scaffolding and redundant if-guards * ci(flatpak): force the app module to rebuild so the test asset always exists flatpak-builder caches modules by content hash and skips a hit, producing no build tree and no test asset, so a re-run of the same commit would leave the separate test job with nothing to download. Inject a per-run cache-buster into the OrcaSlicer module's build-options (part of its cache key) so it always rebuilds, mirroring how the other arches cache only deps and always rebuild the app and tests. The deps modules stay cached. * ci(flatpak): trim cache-buster comment, fix stale step name * fix: guard H2C per-filament array reads against short config arrays The H2C tool-ordering, wipe-tower, and g-code export paths index per-filament config arrays by filament/tool id. A config with fewer entries than the filament count (partial or legacy projects, minimal test configs) makes these reads run past the end of the vector: silent under a normal STL, but UB that aborts under the flatpak build's bounds-checked STL (_GLIBCXX_ASSERTIONS). Route the reads through the existing clamping accessors (get_at, get_filament_category, is_in_same_extruder) and add a small clamp helper for filament_change_length. The guards are no-ops when the arrays are sized to the filament count, so correctly specified configs are unaffected. * ci(flatpak): build filament_group_tests too The suite landed on main after this branch was cut and arrived via a later merge, so it was missing from the target list and ctest failed the leg with filament_group_tests_NOT_BUILT. Not tests/all, which build_linux.sh uses: that is a Ninja subdirectory target and this build configures with the default Makefile generator, where it does not exist. * ci(flatpak): give the embedded-interpreter tests a valid Python home python_test_support.hpp sets PyConfig.home to /python when that path resolves. WIN32/APPLE populate it with a copied bundled runtime; the flatpak leg had no such branch, so home resolved to a directory with no stdlib and all 21 embedded plugin tests failed at "failed to get the Python codec of the filesystem encoding". Symlink /python to the bundled /app/libpython that already ships in the flatpak (the test exe links libpython3.12.so from there via rpath), so the interpreter initializes without duplicating the runtime. * ci(flatpak): sync the ToolOrdering guard mirror with #14789 Match #14709's build_filament_group_context guard to the version on #14789 (size filament_info to filament_nums, truncate filament_ids) so the folded guard is a byte-identical mirror that drops cleanly when #14789 merges, instead of leaving a stale hunk that conflicts on rebase. * fix: guard WipeTower per-filament array reads against short config arrays The BambuStudio WipeTower sync reintroduced raw per-filament array indexing that reads out of bounds when a config leaves an array shorter than the filament count: m_physical_extruder_map in format_line_M104/M109 (indexed even when empty), and m_filament_categories in get_wall_skip_points and get_wall_filament_for_all_layer. Silent on a normal STL, a hard abort under the bounds-checked STL the Flatpak build uses. Bounds-check the physical extruder map before indexing (omitting the T token, as the existing -1 path already does), and route the two raw m_filament_categories reads through the clamping get_filament_category() accessor the surrounding code already uses. No change for correctly-sized configs. * fix: default-initialize WallToolPathsParams fields min_length_factor and is_top_or_bottom_layer had no default initializers, and the FillConcentric/FillConcentricInternal callers never set them, so WallToolPaths::removeSmallLines() thresholded on stack garbage. Which short extrusion lines it dropped then depended on memory layout, so concentric solid-infill output was nondeterministic between runs and across machines. Give every member a default, matching the adjacent FillParams. The perimeter path was already fine because it builds the struct via make_paths_params(). * fix: bounds-check the toolchange flush-volume and HRC per-filament lookups GCode::set_extruder's toolchange flush-volume lookup and GCodeProcessor::update_slice_warnings's HRC check index per-filament and per-extruder arrays (flush_volumes_matrix, the filament map, the nozzle list) by filament/extruder id. When a config leaves one of those arrays shorter than the filament count (partial or legacy multi-extruder projects, minimal configs), the reads run off the end: silent on a normal STL, a hard abort under _GLIBCXX_ASSERTIONS. Route both reads through bounds checks: the flush lookup falls back to no flush, matching the existing unknown-old-filament branch beside it, and the HRC check skips an unmapped filament, mirroring the required_nozzle_HRC guard on the line above. When the arrays are sized to the filament count the values are unchanged, so correctly-specified configs are unaffected. * ci: retrigger checks * ci: name the flatpak rebuild token after the cache it defeats Since #15650 the Flatpak job also has a compiler cache, so a bare "cache-buster" no longer says which cache is meant. Call it flatpak_builder_cache_buster, and name the build-dir trim step after the flatpak-builder cache save it keeps lean. * ci: ship resources/profiles and resources/printers in the flatpak test asset Two slic3rutils tests added in 4aa0e1d60b read resources/printers/bambu_filament_ids.json through PROFILES_DIR/.., and the asset dropped resources/ entirely, so both failed parsing an empty stream on each Flatpak leg. Keep the two subtrees the tests reach; test_gcodewriter's shipped-profile case stops skipping on this leg too. * ci: restore the CRLF line endings of build_all.yml The last merge from upstream/main rewrote the file with LF endings, which turns the 60-line change into a whole-file diff on GitHub. Upstream has had this file as CRLF since it was created, so put it back. * ci: trigger Build all on changes to the unit-test workflows The path filters only matched build_*.yml, so an edit to unit_tests.yml or unit_tests_flatpak.yml could merge without ever running. * ci: put a timeout on the flatpak unit-test step Matches the 20 minutes of the regular unit-test workflow; without it a hung test holds the runner for the six-hour job default. --- .github/workflows/build_all.yml | 62 ++++++++++++++++- .github/workflows/unit_tests_flatpak.yml | 67 +++++++++++++++++++ scripts/flatpak/com.orcaslicer.OrcaSlicer.yml | 18 +++++ tests/slic3rutils/CMakeLists.txt | 12 ++++ 4 files changed, 156 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/unit_tests_flatpak.yml diff --git a/.github/workflows/build_all.yml b/.github/workflows/build_all.yml index 10edec5aaa..f8d6bb8235 100644 --- a/.github/workflows/build_all.yml +++ b/.github/workflows/build_all.yml @@ -14,6 +14,7 @@ on: - 'localization/**' - 'resources/**' - ".github/workflows/build_*.yml" + - ".github/workflows/unit_tests*.yml" - 'scripts/build_preset_cache.*' - 'scripts/flatpak/**' - 'scripts/msix/**' @@ -30,6 +31,7 @@ on: - '**/CMakeLists.txt' - 'version.inc' - ".github/workflows/build_*.yml" + - ".github/workflows/unit_tests*.yml" - 'build_linux.sh' - 'build_release_vs.bat' - 'build_release_vs2022.bat' @@ -207,7 +209,7 @@ jobs: ./validator-bin/OrcaSlicer_profile_validator -p "${{ github.workspace }}/resources/profiles" -s -l 2 publish_test_results: name: Publish Test Results - needs: [unit_tests_linux_x86_64, unit_tests_linux_aarch64, unit_tests_windows_x64, unit_tests_windows_arm64, unit_tests_macos_arm64] + needs: [unit_tests_linux_x86_64, unit_tests_linux_aarch64, unit_tests_windows_x64, unit_tests_windows_arm64, unit_tests_macos_arm64, unit_tests_flatpak_x86_64, unit_tests_flatpak_aarch64] if: ${{ !cancelled() }} runs-on: ubuntu-latest steps: @@ -324,9 +326,16 @@ jobs: sed -i '/^build-options:/a\ no-debuginfo: true\n strip: true' \ scripts/flatpak/com.orcaslicer.OrcaSlicer.yml shell: bash - - name: Inject git commit hash into Flatpak manifest + # flatpak-builder reuses a module from its cache when the definition and + # sources are unchanged, so a re-run of the same commit would skip the + # OrcaSlicer module and ship no test asset. A per-run value in that module's + # env keeps it rebuilding; orca_deps stays cached, and the compiler cache + # still serves the rebuild. + - name: Inject commit hash and flatpak-builder cache buster into Flatpak manifest + env: + flatpak_builder_cache_buster: ${{ github.run_id }}-${{ github.run_attempt }} run: | - sed -i "/name: OrcaSlicer/{n;s|buildsystem: simple|buildsystem: simple\n build-options:\n env:\n git_commit_hash: \"$git_commit_hash\"|}" \ + sed -i "/name: OrcaSlicer/{n;s|buildsystem: simple|buildsystem: simple\n build-options:\n env:\n flatpak_builder_cache_buster: \"$flatpak_builder_cache_buster\"\n git_commit_hash: \"$git_commit_hash\"|}" \ scripts/flatpak/com.orcaslicer.OrcaSlicer.yml shell: bash # flatpak-builder's --ccache only wraps cc and gcc, and the manifest builds @@ -372,6 +381,10 @@ jobs: save-cache: false arch: ${{ matrix.variant.arch }} upload-artifact: false + # run-tests fires the module's build-only test-commands; keep-build-dirs + # retains the binaries for the packaging step below. + run-tests: true + keep-build-dirs: true # The build has just touched everything it can use, so an object untouched # for a week is dead, usually orphaned by a flag change. - name: Compiler cache statistics @@ -425,3 +438,46 @@ jobs: asset_name: OrcaSlicer-Linux-flatpak_nightly${{ env.nightly_suffix }}_${{ matrix.variant.arch }}.flatpak asset_content_type: application/octet-stream max_releases: 1 # optional, if there are more releases than this matching the asset_name, the oldest ones are going to be deleted + # The asset is /app (the exes link it at runtime) plus the build tree + # slimmed to what ctest needs. + - name: Package flatpak test asset + shell: bash + run: | + d=$(ls -d .flatpak-builder/build/OrcaSlicer-* | tail -1) + find "$d/build_flatpak" -mindepth 1 -maxdepth 1 ! -name tests -exec rm -rf {} + + # Strip debug info (the SDK builds with -g, only the app gets stripped); + # the bounds checks are compiled in, so a stripped exe still catches them. + find "$d/build_flatpak/tests" -type f -perm -u+x -exec strip --strip-unneeded {} + 2>/dev/null || true + # At runtime the tests read tests/ (TEST_DATA_DIR), scripts/, and under + # resources/ the shipped profiles (PROFILES_DIR) and the printers/ maps. + find "$d" -mindepth 1 -maxdepth 1 -type d \ + ! -name tests ! -name build_flatpak ! -name scripts ! -name resources -exec rm -rf {} + + find "$d/resources" -mindepth 1 -maxdepth 1 ! -name profiles ! -name printers -exec rm -rf {} + + tar -cf flatpak-test-asset.tar flatpak_app "$d" + - name: Upload flatpak test asset + uses: actions/upload-artifact@v7 + with: + name: ${{ github.sha }}-flatpak-tests-${{ matrix.variant.arch }} + path: flatpak-test-asset.tar + retention-days: 1 + # keep-build-dirs would otherwise land in the flatpak-builder cache saved post-job. + - name: Drop the kept build dirs before the flatpak-builder cache saves + if: always() + shell: bash + run: rm -rf .flatpak-builder/build + unit_tests_flatpak_x86_64: + name: Flatpak x86_64 + needs: flatpak + if: ${{ !cancelled() && success() }} + uses: ./.github/workflows/unit_tests_flatpak.yml + with: + os: ubuntu-24.04 + artifact: ${{ github.sha }}-flatpak-tests-x86_64 + unit_tests_flatpak_aarch64: + name: Flatpak aarch64 + needs: flatpak + if: ${{ !cancelled() && success() }} + uses: ./.github/workflows/unit_tests_flatpak.yml + with: + os: ubuntu-24.04-arm + artifact: ${{ github.sha }}-flatpak-tests-aarch64 diff --git a/.github/workflows/unit_tests_flatpak.yml b/.github/workflows/unit_tests_flatpak.yml new file mode 100644 index 0000000000..ce261c210c --- /dev/null +++ b/.github/workflows/unit_tests_flatpak.yml @@ -0,0 +1,67 @@ +name: Flatpak Unit Tests + +# Run the flatpak build's test asset inside the sandbox, once per arch. The +# GNOME SDK's _GLIBCXX_ASSERTIONS gives a bounds-checked STL that catches +# out-of-bounds reads no other test leg does. +on: + workflow_call: + inputs: + os: + required: true + type: string + artifact: + description: Test asset uploaded by the flatpak build leg + required: true + type: string + +jobs: + unit_tests_flatpak: + name: Flatpak Unit Tests + runs-on: ${{ inputs.os }} + container: + image: ghcr.io/flathub-infra/flatpak-github-actions:gnome-50 + options: --privileged + steps: + - name: Restore test asset + uses: actions/download-artifact@v8 + with: + name: ${{ inputs.artifact }} + - name: Run unit tests (bounds-checked sandbox) + timeout-minutes: 20 + shell: bash + run: | + tar -xf flatpak-test-asset.tar + # Recreate the stable module symlink so /run/build/OrcaSlicer resolves. + d=$(ls -d .flatpak-builder/build/OrcaSlicer-* | tail -1) + ln -sfn "$(basename "$d")" .flatpak-builder/build/OrcaSlicer + # The runtime + SDK + the llvm extension the app metadata references, + # which `flatpak build` mounts; best-effort, the image may have them. + flatpak remote-add --if-not-exists --user flathub https://flathub.org/repo/flathub.flatpakrepo + flatpak install --user -y --noninteractive flathub \ + org.gnome.Platform//50 org.gnome.Sdk//50 org.freedesktop.Sdk.Extension.llvm21//25.08 || true + # `flatpak build` uses bwrap (no rofiles-fuse, which this container + # rejects); bind-mount the build tree so the baked TEST_DATA_DIR resolves. + flatpak build --die-with-parent --share=network \ + --bind-mount=/run/build="$PWD/.flatpak-builder/build" \ + flatpak_app \ + bash -c 'cd /run/build/OrcaSlicer && scripts/run_unit_tests.sh build_flatpak/tests' + - name: Collect test results + if: always() + shell: bash + run: | + d=$(ls -d .flatpak-builder/build/OrcaSlicer-* 2>/dev/null | tail -1 || true) + [ -n "$d" ] && [ -f "$d/ctest_results.xml" ] && cp "$d/ctest_results.xml" ctest_results.xml || true + - name: Upload Test Results + if: always() + uses: actions/upload-artifact@v7 + with: + name: test-results-${{ inputs.artifact }} + path: ctest_results.xml + retention-days: 5 + if-no-files-found: warn + - name: Delete Test Asset + if: success() + uses: geekyeggo/delete-artifact@v6 + with: + name: ${{ inputs.artifact }} + failOnError: false diff --git a/scripts/flatpak/com.orcaslicer.OrcaSlicer.yml b/scripts/flatpak/com.orcaslicer.OrcaSlicer.yml index 00aa430f84..668f51334b 100644 --- a/scripts/flatpak/com.orcaslicer.OrcaSlicer.yml +++ b/scripts/flatpak/com.orcaslicer.OrcaSlicer.yml @@ -378,6 +378,17 @@ modules: - cmake --build build_flatpak --target generate_system_cache -j$FLATPAK_BUILDER_N_JOBS - ./scripts/build_preset_cache.sh -n -b build_flatpak /app/share/OrcaSlicer/profiles + # Built (not run) here via the action's run-tests, then shipped to a separate + # test job. Only the test sources compile; nothing installs to /app. + test-commands: + - cmake . -B build_flatpak -DBUILD_TESTS=ON + # A suite missing from this list fails the leg loudly, since ctest registers a + # _NOT_BUILT test for it. (tests/all is a Ninja subdirectory target and + # this build uses the default Makefile generator, so it is not available here.) + - cmake --build build_flatpak -j"${FLATPAK_BUILDER_N_JOBS:-$(nproc)}" --target + libslic3r_tests fff_print_tests sla_print_tests libnest2d_tests slic3rutils_tests + filament_group_tests + cleanup: - /include @@ -414,6 +425,10 @@ modules: - type: dir path: ../../localization dest: localization + # For the post-build unit-test step (BUILD_TESTS=ON); not built by the app. + - type: dir + path: ../../tests + dest: tests - type: file path: ../../CMakeLists.txt @@ -427,6 +442,9 @@ modules: - type: file path: ../build_preset_cache.sh dest: scripts + - type: file + path: ../run_unit_tests.sh + dest: scripts # AppData metainfo for GNOME Software & Co. - type: file diff --git a/tests/slic3rutils/CMakeLists.txt b/tests/slic3rutils/CMakeLists.txt index 2ab78f56de..3ddacc5a1b 100644 --- a/tests/slic3rutils/CMakeLists.txt +++ b/tests/slic3rutils/CMakeLists.txt @@ -55,6 +55,18 @@ elseif (APPLE) COMMENT "Copying Python runtime for macOS plugin host API tests" VERBATIM ) +elseif (FLATPAK) + # Same /python home as WIN32/APPLE; symlink since /app/libpython + # already ships in the flatpak (the test exe links libpython3.12.so from it). + add_custom_command(TARGET ${_TEST_NAME}_tests POST_BUILD + COMMAND ${CMAKE_COMMAND} -E rm -rf + "$/python" + COMMAND ${CMAKE_COMMAND} -E create_symlink + "${CMAKE_PREFIX_PATH}/libpython" + "$/python" + COMMENT "Linking Python runtime for flatpak plugin host API tests" + VERBATIM + ) endif() orcaslicer_discover_tests(${_TEST_NAME}_tests) From a0ada1aa882b1aa0ceb77d0a5e0684dcfd174100 Mon Sep 17 00:00:00 2001 From: SoftFever Date: Wed, 16 Sep 2026 00:43:50 +0800 Subject: [PATCH 6/6] fix wiki links --- AGENTS.md | 6 +++--- resources/data/hints.ini | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 4be195b40d..01402af3eb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -64,16 +64,16 @@ ctest --test-dir ./tests/fff_print - Keep code concise and clear. Manually simplify AI generated bloated codes before review. - Include targeted tests or documented verification for behavior changes, especially in slicing logic, profiles, formats, and GUI defaults. - For profile changes (`resources/profiles//**`), check that `version` in the sibling `resources/profiles/.json` was bumped. -- For translation changes (`localization/i18n/**/*.po`), check that recurring terms match the [Localization glossary](https://github.com/OrcaSlicer/OrcaSlicer_WIKI/blob/main/guides/localization_glossary.md) for that language. +- For translation changes (`localization/i18n/**/*.po`), check that recurring terms match the [Localization glossary](https://github.com/OrcaSlicer/OrcaSlicer_WIKI/blob/main/developer_reference/localization_glossary.md) for that language. ## Localization & translations Catalogs live in `localization/i18n//OrcaSlicer_.po`; the template is `OrcaSlicer.pot`. -See the [Localization guide](https://github.com/OrcaSlicer/OrcaSlicer_WIKI/blob/main/guides/localization_guide.md) for the human-facing version of these principles. +See the [Localization guide](https://github.com/OrcaSlicer/OrcaSlicer_WIKI/blob/main/developer_reference/localization_guide.md) for the human-facing version of these principles. ### Terminology -- Use the [Localization glossary](https://github.com/OrcaSlicer/OrcaSlicer_WIKI/blob/main/guides/localization_glossary.md) as the source of truth for recurring terms, so the same English term is always rendered the same way within a language, and terms that must stay in English (brand/product names, acronyms, materials, file formats, G-code tokens, macros/variables/identifiers) are not translated. +- Use the [Localization glossary](https://github.com/OrcaSlicer/OrcaSlicer_WIKI/blob/main/developer_reference/localization_glossary.md) as the source of truth for recurring terms, so the same English term is always rendered the same way within a language, and terms that must stay in English (brand/product names, acronyms, materials, file formats, G-code tokens, macros/variables/identifiers) are not translated. - If a term's established translation changes, update both the affected `.po` files and the glossary (`localization_glossary.tsv`, then regenerate) so they stay in sync. - Translate the *meaning*, not the words. Check what the string actually controls before translating it — English reuses one word for different things. `Flow ratio` (multiplier), `Flow Rate` (throughput) and `Flow Dynamics` (pressure compensation) are three different terms; `extruder` may mean the toolhead, the feeder motor, or the nozzle depending on the string. - Reuse one template per recurring message shape (`Failed to connect to …`, `Are you sure you want to …?`), even where the English wording varies. diff --git a/resources/data/hints.ini b/resources/data/hints.ini index 15d2758551..a71fb868f9 100644 --- a/resources/data/hints.ini +++ b/resources/data/hints.ini @@ -75,7 +75,7 @@ documentation_link = https://www.orcaslicer.com/wiki/material_temperatures#print [hint:Calibration] text = Calibration\nDid you know that calibrating your printer can do wonders? Check out our beloved calibration solution in OrcaSlicer. -documentation_link = https://www.orcaslicer.com/wiki/calibration +documentation_link = https://www.orcaslicer.com/wiki/calibration_guide [hint:Auxiliary fan] text = Auxiliary fan\nDid you know that OrcaSlicer supports Auxiliary part cooling fan?