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/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/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} 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? diff --git a/scripts/flatpak/com.orcaslicer.OrcaSlicer.yml b/scripts/flatpak/com.orcaslicer.OrcaSlicer.yml index 07b1e6f813..5435b9dfff 100644 --- a/scripts/flatpak/com.orcaslicer.OrcaSlicer.yml +++ b/scripts/flatpak/com.orcaslicer.OrcaSlicer.yml @@ -385,6 +385,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 @@ -421,6 +432,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 @@ -434,6 +449,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/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 4aa45a60ed..12a3a73e7c 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) { @@ -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; 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/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); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7539369c64..aa340a87d1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -88,40 +88,6 @@ function(orcaslicer_discover_tests TARGET) catch_discover_tests(${TARGET} ADD_TAGS_AS_LABELS) endfunction() -# Stages a private Python runtime beside TARGET's output and (on macOS) links -# in the matching rpath, so an embedded pybind11 interpreter in TARGET can find -# libpython/the stdlib at runtime without depending on the build machine's -# Python install. Apply this to every test target that embeds an interpreter - -# duplicating the WIN32/APPLE blocks by hand lets them drift out of sync. -function(orcaslicer_stage_test_python_runtime TARGET) - if (WIN32) - add_custom_command(TARGET ${TARGET} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E make_directory "$/python" - COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_PREFIX_PATH}/libpython" "$/python" - COMMAND ${CMAKE_COMMAND} -E copy_if_different - "${CMAKE_PREFIX_PATH}/libpython/python${_bundled_python_abi}.dll" - "${CMAKE_PREFIX_PATH}/libpython/vcruntime140.dll" - "${CMAKE_PREFIX_PATH}/libpython/vcruntime140_1.dll" - "$" - COMMENT "Copying Python runtime for ${TARGET}" - VERBATIM - ) - elseif (APPLE) - target_link_options(${TARGET} PRIVATE - "LINKER:-rpath,@executable_path/python/lib") - - add_custom_command(TARGET ${TARGET} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E rm -rf - "$/python" - COMMAND ${CMAKE_COMMAND} -E copy_directory - "${CMAKE_PREFIX_PATH}/libpython" - "$/python" - COMMENT "Copying Python runtime for ${TARGET}" - VERBATIM - ) - endif() -endfunction() - add_subdirectory(libnest2d) add_subdirectory(libslic3r) add_subdirectory(slic3rutils) 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 diff --git a/tests/slic3rutils/CMakeLists.txt b/tests/slic3rutils/CMakeLists.txt index c7b6d6ff6e..50f7a475e9 100644 --- a/tests/slic3rutils/CMakeLists.txt +++ b/tests/slic3rutils/CMakeLists.txt @@ -33,6 +33,43 @@ set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests") orcaslicer_copy_test_dlls(${_TEST_NAME}_tests) -orcaslicer_stage_test_python_runtime(${_TEST_NAME}_tests) +if (WIN32) + add_custom_command(TARGET ${_TEST_NAME}_tests POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory "$/python" + COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_PREFIX_PATH}/libpython" "$/python" + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${CMAKE_PREFIX_PATH}/libpython/python${_bundled_python_abi}.dll" + "${CMAKE_PREFIX_PATH}/libpython/vcruntime140.dll" + "${CMAKE_PREFIX_PATH}/libpython/vcruntime140_1.dll" + "$" + COMMENT "Copying Python runtime for slic3rutils plugin host API tests" + VERBATIM + ) +elseif (APPLE) + target_link_options(${_TEST_NAME}_tests PRIVATE + "LINKER:-rpath,@executable_path/python/lib") + + add_custom_command(TARGET ${_TEST_NAME}_tests POST_BUILD + COMMAND ${CMAKE_COMMAND} -E rm -rf + "$/python" + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_PREFIX_PATH}/libpython" + "$/python" + 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)