mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-16 13:32:44 +00:00
Merge branch 'main' into feature/filament_id
This commit is contained in:
@@ -5,3 +5,8 @@
|
|||||||
# with CRLF line endings: it fails on the first line. Windows checkouts default
|
# with CRLF line endings: it fails on the first line. Windows checkouts default
|
||||||
# to core.autocrlf=true, so keep these LF whatever the platform.
|
# to core.autocrlf=true, so keep these LF whatever the platform.
|
||||||
*.sh text eol=lf
|
*.sh text eol=lf
|
||||||
|
|
||||||
|
# Batch files are read by cmd.exe, which tracks a byte offset into the file to
|
||||||
|
# resume after `call :label`. With LF endings that offset can land wrong and the
|
||||||
|
# label lookup fails, so keep these CRLF whatever the platform.
|
||||||
|
*.bat text eol=crlf
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ on:
|
|||||||
- 'build_linux.sh'
|
- 'build_linux.sh'
|
||||||
- 'build_release_vs.bat'
|
- 'build_release_vs.bat'
|
||||||
- 'build_release_vs2022.bat'
|
- 'build_release_vs2022.bat'
|
||||||
|
- 'build_win.bat'
|
||||||
|
- 'scripts/test_build_win.ps1'
|
||||||
- 'build_release_macos.sh'
|
- 'build_release_macos.sh'
|
||||||
- 'scripts/build_preset_cache.*'
|
- 'scripts/build_preset_cache.*'
|
||||||
- 'scripts/flatpak/**'
|
- 'scripts/flatpak/**'
|
||||||
@@ -56,6 +58,22 @@ concurrency:
|
|||||||
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
# build_win.bat ships a test suite. Run it before the Windows builds.
|
||||||
|
check_build_script:
|
||||||
|
name: Windows build script tests
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
lfs: 'false'
|
||||||
|
|
||||||
|
# Windows PowerShell rather than pwsh: the suite drives build_win.bat
|
||||||
|
# through cmd, and the two differ in how they quote native arguments.
|
||||||
|
- name: Run the build script test suite
|
||||||
|
shell: powershell
|
||||||
|
run: .\scripts\test_build_win.ps1
|
||||||
|
|
||||||
build_linux:
|
build_linux:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@@ -81,14 +99,16 @@ jobs:
|
|||||||
# SELF_HOSTED skips arm64 (the self-hosted Windows server is x64-only).
|
# SELF_HOSTED skips arm64 (the self-hosted Windows server is x64-only).
|
||||||
matrix:
|
matrix:
|
||||||
include: ${{ fromJSON(vars.SELF_HOSTED
|
include: ${{ fromJSON(vars.SELF_HOSTED
|
||||||
&& '[{"arch":"x64","os":"orca-win-server"}]'
|
&& '[{"arch":"x64","os":"orca-win-server","compiler":"clang"}]'
|
||||||
|| '[{"arch":"x64","os":"windows-latest"},{"arch":"arm64","os":"windows-11-arm"}]') }}
|
|| '[{"arch":"x64","os":"windows-latest","compiler":"clang"},{"arch":"arm64","os":"windows-11-arm","compiler":"clang"}]') }}
|
||||||
|
needs: check_build_script
|
||||||
# Don't run scheduled builds on forks:
|
# Don't run scheduled builds on forks:
|
||||||
if: ${{ !cancelled() && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }}
|
if: ${{ !cancelled() && needs.check_build_script.result == 'success' && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }}
|
||||||
uses: ./.github/workflows/build_check_cache.yml
|
uses: ./.github/workflows/build_check_cache.yml
|
||||||
with:
|
with:
|
||||||
os: ${{ matrix.os }}
|
os: ${{ matrix.os }}
|
||||||
arch: ${{ matrix.arch }}
|
arch: ${{ matrix.arch }}
|
||||||
|
compiler: ${{ matrix.compiler }}
|
||||||
build-deps-only: ${{ inputs.build-deps-only || false }}
|
build-deps-only: ${{ inputs.build-deps-only || false }}
|
||||||
force-build: ${{ github.event_name == 'schedule' }}
|
force-build: ${{ github.event_name == 'schedule' }}
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ on:
|
|||||||
arch:
|
arch:
|
||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
|
compiler:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: msvc
|
||||||
build-deps-only:
|
build-deps-only:
|
||||||
required: false
|
required: false
|
||||||
type: boolean
|
type: boolean
|
||||||
@@ -33,10 +37,10 @@ jobs:
|
|||||||
- name: set outputs
|
- name: set outputs
|
||||||
id: set_outputs
|
id: set_outputs
|
||||||
env:
|
env:
|
||||||
# Keep macOS/Windows cache keys architecture-specific. amd64 Linux passes
|
# Anything that changes how the tree is built belongs in the key, or a job
|
||||||
# no arch (key stays 'linux-clang', preserving the existing cache);
|
# restores one it cannot use. Linux amd64 passes no arch deliberately, so
|
||||||
# aarch64 gets its own 'linux-clang-aarch64' key.
|
# 'linux-clang' keeps the cache it already has.
|
||||||
cache-os: ${{ runner.os == 'macOS' && format('macos-{0}', inputs.arch) || (runner.os == 'Windows' && format('windows-{0}', inputs.arch) || format('linux-clang{0}', inputs.arch && format('-{0}', inputs.arch) || '')) }}
|
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) || '')) }}
|
||||||
# ARM64 builds use the build-arm64 tree (see build_release_vs.bat); x64/other use build.
|
# ARM64 builds use the build-arm64 tree (see build_release_vs.bat); x64/other use build.
|
||||||
dep-folder-name: ${{ runner.os == 'macOS' && format('/{0}', inputs.arch) || (runner.os == 'Windows' && inputs.arch == 'arm64') && '-arm64/OrcaSlicer_dep' || '/OrcaSlicer_dep' }}
|
dep-folder-name: ${{ runner.os == 'macOS' && format('/{0}', inputs.arch) || (runner.os == 'Windows' && inputs.arch == 'arm64') && '-arm64/OrcaSlicer_dep' || '/OrcaSlicer_dep' }}
|
||||||
output-cmd: ${{ runner.os == 'Windows' && '$env:GITHUB_OUTPUT' || '"$GITHUB_OUTPUT"'}}
|
output-cmd: ${{ runner.os == 'Windows' && '$env:GITHUB_OUTPUT' || '"$GITHUB_OUTPUT"'}}
|
||||||
@@ -62,6 +66,7 @@ jobs:
|
|||||||
valid-cache: ${{ needs.check_cache.outputs.valid-cache == 'true' }}
|
valid-cache: ${{ needs.check_cache.outputs.valid-cache == 'true' }}
|
||||||
os: ${{ inputs.os }}
|
os: ${{ inputs.os }}
|
||||||
arch: ${{ inputs.arch }}
|
arch: ${{ inputs.arch }}
|
||||||
|
compiler: ${{ inputs.compiler }}
|
||||||
build-deps-only: ${{ inputs.build-deps-only }}
|
build-deps-only: ${{ inputs.build-deps-only }}
|
||||||
force-build: ${{ inputs.force-build }}
|
force-build: ${{ inputs.force-build }}
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ on:
|
|||||||
arch:
|
arch:
|
||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
|
compiler:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: msvc
|
||||||
build-deps-only:
|
build-deps-only:
|
||||||
required: false
|
required: false
|
||||||
type: boolean
|
type: boolean
|
||||||
@@ -135,11 +139,22 @@ jobs:
|
|||||||
choco install strawberryperl
|
choco install strawberryperl
|
||||||
}
|
}
|
||||||
$arch = "${{ inputs.arch }}"
|
$arch = "${{ inputs.arch }}"
|
||||||
|
# -l selects clang-cl and -x Ninja; together they build the deps with clang.
|
||||||
|
$clang = "${{ inputs.compiler }}" -eq "clang"
|
||||||
|
$flags = if ($clang) { "-l", "-x" } else { @() }
|
||||||
|
if ($clang) {
|
||||||
|
# OpenSSL builds with nmake, which needs a VC environment.
|
||||||
|
$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
|
||||||
|
$vs = & $vswhere -latest -property installationPath
|
||||||
|
$devArch = if ($arch -eq "arm64") { "arm64" } else { "amd64" }
|
||||||
|
Import-Module "$vs\Common7\Tools\Microsoft.VisualStudio.DevShell.dll"
|
||||||
|
Enter-VsDevShell -VsInstallPath $vs -SkipAutomaticLocation -DevCmdArguments "-arch=$devArch"
|
||||||
|
}
|
||||||
if ($arch -eq "arm64") {
|
if ($arch -eq "arm64") {
|
||||||
.\build_release_vs.bat deps arm64
|
.\build_release_vs.bat deps arm64 @flags
|
||||||
.\build_release_vs.bat pack arm64
|
.\build_release_vs.bat pack arm64
|
||||||
} else {
|
} else {
|
||||||
.\build_release_vs.bat deps
|
.\build_release_vs.bat deps @flags
|
||||||
.\build_release_vs.bat pack
|
.\build_release_vs.bat pack
|
||||||
}
|
}
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
@@ -204,4 +219,5 @@ jobs:
|
|||||||
cache-path: ${{ inputs.cache-path }}
|
cache-path: ${{ inputs.cache-path }}
|
||||||
os: ${{ inputs.os }}
|
os: ${{ inputs.os }}
|
||||||
arch: ${{ inputs.arch }}
|
arch: ${{ inputs.arch }}
|
||||||
|
compiler: ${{ inputs.compiler }}
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ on:
|
|||||||
arch:
|
arch:
|
||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
|
compiler:
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: msvc
|
||||||
macos-combine-only:
|
macos-combine-only:
|
||||||
required: false
|
required: false
|
||||||
type: boolean
|
type: boolean
|
||||||
@@ -395,7 +399,18 @@ jobs:
|
|||||||
# "tests" builds the unit tests too; the unit_tests_windows_* jobs run them.
|
# "tests" builds the unit tests too; the unit_tests_windows_* jobs run them.
|
||||||
run: |
|
run: |
|
||||||
$arch = "${{ inputs.arch }}"
|
$arch = "${{ inputs.arch }}"
|
||||||
if ($arch -eq "arm64") { .\build_release_vs.bat slicer arm64 tests } else { .\build_release_vs.bat slicer tests }
|
# -l selects clang-cl and -x Ninja; together they build the slicer with clang.
|
||||||
|
$clang = "${{ inputs.compiler }}" -eq "clang"
|
||||||
|
$flags = if ($clang) { "-l", "-x" } else { @() }
|
||||||
|
if ($clang) {
|
||||||
|
# Build against the same VC toolchain and SDK as the dependencies.
|
||||||
|
$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
|
||||||
|
$vs = & $vswhere -latest -property installationPath
|
||||||
|
$devArch = if ($arch -eq "arm64") { "arm64" } else { "amd64" }
|
||||||
|
Import-Module "$vs\Common7\Tools\Microsoft.VisualStudio.DevShell.dll"
|
||||||
|
Enter-VsDevShell -VsInstallPath $vs -SkipAutomaticLocation -DevCmdArguments "-arch=$devArch"
|
||||||
|
}
|
||||||
|
if ($arch -eq "arm64") { .\build_release_vs.bat slicer arm64 @flags tests } else { .\build_release_vs.bat slicer @flags tests }
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
|
|
||||||
- name: Build system preset cache (Windows)
|
- name: Build system preset cache (Windows)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
Build
|
Build
|
||||||
Build.bat
|
Build.bat
|
||||||
/build*/
|
/build*/
|
||||||
|
/out/
|
||||||
CMakeLists.txt.user
|
CMakeLists.txt.user
|
||||||
CMakeUserPresets.json
|
CMakeUserPresets.json
|
||||||
**/CMakeLists.txt.autosave
|
**/CMakeLists.txt.autosave
|
||||||
@@ -51,3 +52,4 @@ internal_docs/
|
|||||||
__pycache__/
|
__pycache__/
|
||||||
*.pyc
|
*.pyc
|
||||||
*.opc
|
*.opc
|
||||||
|
/.test/
|
||||||
|
|||||||
+1268
File diff suppressed because it is too large
Load Diff
Vendored
+8
@@ -1,3 +1,10 @@
|
|||||||
|
# clang-cl cannot emit IGESAppli_GeneralModule.cxx on ARM64
|
||||||
|
# (llvm/llvm-project#62081). cl and clang-cl share an ABI.
|
||||||
|
set(_occt_compiler_args "")
|
||||||
|
if ("${DEPS_ARCH}" STREQUAL "arm64" AND CMAKE_CXX_COMPILER_ID STREQUAL Clang)
|
||||||
|
set(_occt_compiler_args -DCMAKE_C_COMPILER:STRING=cl -DCMAKE_CXX_COMPILER:STRING=cl)
|
||||||
|
endif ()
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
set(library_build_type "Shared")
|
set(library_build_type "Shared")
|
||||||
else()
|
else()
|
||||||
@@ -31,6 +38,7 @@ orcaslicer_add_cmake_project(OCCT
|
|||||||
-DBUILD_MODULE_ModelingAlgorithms=OFF
|
-DBUILD_MODULE_ModelingAlgorithms=OFF
|
||||||
-DBUILD_MODULE_ModelingData=OFF
|
-DBUILD_MODULE_ModelingData=OFF
|
||||||
-DBUILD_MODULE_Visualization=OFF
|
-DBUILD_MODULE_Visualization=OFF
|
||||||
|
${_occt_compiler_args}
|
||||||
)
|
)
|
||||||
|
|
||||||
# add_dependencies(dep_OCCT ${FREETYPE_PKG})
|
# add_dependencies(dep_OCCT ${FREETYPE_PKG})
|
||||||
|
|||||||
Vendored
+8
@@ -10,6 +10,13 @@ else ()
|
|||||||
set(_options "")
|
set(_options "")
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
|
# carotene is OpenCV's ARM NEON HAL. It uses M_PI without _USE_MATH_DEFINES
|
||||||
|
# and does not compile with clang-cl.
|
||||||
|
set(_disable_carotene "")
|
||||||
|
if ("${DEPS_ARCH}" STREQUAL "arm64" AND CMAKE_CXX_COMPILER_ID STREQUAL Clang)
|
||||||
|
set(_disable_carotene "-DWITH_CAROTENE=OFF")
|
||||||
|
endif ()
|
||||||
|
|
||||||
if (IN_GIT_REPO)
|
if (IN_GIT_REPO)
|
||||||
set(OpenCV_DIRECTORY_FLAG --directory ${BINARY_DIR_REL}/dep_OpenCV-prefix/src/dep_OpenCV)
|
set(OpenCV_DIRECTORY_FLAG --directory ${BINARY_DIR_REL}/dep_OpenCV-prefix/src/dep_OpenCV)
|
||||||
endif ()
|
endif ()
|
||||||
@@ -83,5 +90,6 @@ orcaslicer_add_cmake_project(OpenCV
|
|||||||
-DWITH_PROTOBUF=OFF
|
-DWITH_PROTOBUF=OFF
|
||||||
-DWITH_WIN32UI=OFF
|
-DWITH_WIN32UI=OFF
|
||||||
-DHAVE_WIN32UI=FALSE
|
-DHAVE_WIN32UI=FALSE
|
||||||
|
${_disable_carotene}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Vendored
+1
-1
@@ -6,7 +6,7 @@ if(DEFINED OPENSSL_ARCH)
|
|||||||
set(_cross_arch ${OPENSSL_ARCH})
|
set(_cross_arch ${OPENSSL_ARCH})
|
||||||
else()
|
else()
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
if("${CMAKE_GENERATOR_PLATFORM}" STREQUAL "ARM64")
|
if("${DEPS_ARCH}" STREQUAL "arm64")
|
||||||
set(_cross_arch "VC-WIN64-ARM")
|
set(_cross_arch "VC-WIN64-ARM")
|
||||||
else()
|
else()
|
||||||
set(_cross_arch "VC-WIN64A")
|
set(_cross_arch "VC-WIN64A")
|
||||||
|
|||||||
Vendored
+4
@@ -1,6 +1,10 @@
|
|||||||
if (APPLE)
|
if (APPLE)
|
||||||
# Only disable NEON extension for Apple ARM builds, leave it enabled for Raspberry PI.
|
# Only disable NEON extension for Apple ARM builds, leave it enabled for Raspberry PI.
|
||||||
set(_disable_neon_extension "-DPNG_ARM_NEON=off")
|
set(_disable_neon_extension "-DPNG_ARM_NEON=off")
|
||||||
|
elseif ("${DEPS_ARCH}" STREQUAL "arm64" AND CMAKE_CXX_COMPILER_ID STREQUAL Clang)
|
||||||
|
# libpng's CMake ignores PNG_ARM_NEON on Windows ARM64 and skips the NEON
|
||||||
|
# sources, but pngpriv.h enables NEON anyway.
|
||||||
|
set(_disable_neon_extension "-DCMAKE_C_FLAGS=/DWIN32 /D_WINDOWS /DPNG_ARM_NEON_OPT=0")
|
||||||
else ()
|
else ()
|
||||||
set(_disable_neon_extension "")
|
set(_disable_neon_extension "")
|
||||||
endif ()
|
endif ()
|
||||||
|
|||||||
@@ -4497,7 +4497,7 @@ msgid ""
|
|||||||
"The value will be reset to 0."
|
"The value will be reset to 0."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Alternate extra wall does't work well when ensure vertical shell thickness is set to All."
|
msgid "Alternate extra wall doesn't work well when ensure vertical shell thickness is set to All."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -4528,13 +4528,13 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"seam_slope_start_height need to be smaller than layer_height.\n"
|
"seam_slope_start_height needs to be smaller than layer_height.\n"
|
||||||
"Reset to 0."
|
"Reset to 0."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, no-c-format, no-boost-format
|
#, no-c-format, no-boost-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Lock depth should smaller than skin depth.\n"
|
"Lock depth should be smaller than skin depth.\n"
|
||||||
"Reset to 50% of skin depth."
|
"Reset to 50% of skin depth."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -4893,7 +4893,7 @@ msgstr ""
|
|||||||
"\n"
|
"\n"
|
||||||
"El valor es restablirà a 0."
|
"El valor es restablirà a 0."
|
||||||
|
|
||||||
msgid "Alternate extra wall does't work well when ensure vertical shell thickness is set to All."
|
msgid "Alternate extra wall doesn't work well when ensure vertical shell thickness is set to All."
|
||||||
msgstr "La paret addicional alternativa no funciona bé quan assegurar el gruix de la closca vertical està establert a Tot."
|
msgstr "La paret addicional alternativa no funciona bé quan assegurar el gruix de la closca vertical està establert a Tot."
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -4939,7 +4939,7 @@ msgstr ""
|
|||||||
"NO - Mantenir l'alçada de la capa de suport independent"
|
"NO - Mantenir l'alçada de la capa de suport independent"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"seam_slope_start_height need to be smaller than layer_height.\n"
|
"seam_slope_start_height needs to be smaller than layer_height.\n"
|
||||||
"Reset to 0."
|
"Reset to 0."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"seam_slope_start_height ha de ser més petit que layer_height.\n"
|
"seam_slope_start_height ha de ser més petit que layer_height.\n"
|
||||||
@@ -4947,7 +4947,7 @@ msgstr ""
|
|||||||
|
|
||||||
#, no-c-format, no-boost-format
|
#, no-c-format, no-boost-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Lock depth should smaller than skin depth.\n"
|
"Lock depth should be smaller than skin depth.\n"
|
||||||
"Reset to 50% of skin depth."
|
"Reset to 50% of skin depth."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"La profunditat de bloqueig ha de ser menor que la profunditat de la pell.\n"
|
"La profunditat de bloqueig ha de ser menor que la profunditat de la pell.\n"
|
||||||
|
|||||||
@@ -4851,7 +4851,7 @@ msgstr ""
|
|||||||
"\n"
|
"\n"
|
||||||
"Hodnota bude nastavena na 0."
|
"Hodnota bude nastavena na 0."
|
||||||
|
|
||||||
msgid "Alternate extra wall does't work well when ensure vertical shell thickness is set to All."
|
msgid "Alternate extra wall doesn't work well when ensure vertical shell thickness is set to All."
|
||||||
msgstr "Střídavá přídavná stěna nefunguje správně, pokud je zajištění tloušťky svislé stěny nastaveno na Vše."
|
msgstr "Střídavá přídavná stěna nefunguje správně, pokud je zajištění tloušťky svislé stěny nastaveno na Vše."
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -4897,7 +4897,7 @@ msgstr ""
|
|||||||
"NE – Ponechat nezávislou výšku podpůrné vrstvy"
|
"NE – Ponechat nezávislou výšku podpůrné vrstvy"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"seam_slope_start_height need to be smaller than layer_height.\n"
|
"seam_slope_start_height needs to be smaller than layer_height.\n"
|
||||||
"Reset to 0."
|
"Reset to 0."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"seam_slope_start_height musí být menší než layer_height.\n"
|
"seam_slope_start_height musí být menší než layer_height.\n"
|
||||||
@@ -4905,7 +4905,7 @@ msgstr ""
|
|||||||
|
|
||||||
#, no-c-format, no-boost-format
|
#, no-c-format, no-boost-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Lock depth should smaller than skin depth.\n"
|
"Lock depth should be smaller than skin depth.\n"
|
||||||
"Reset to 50% of skin depth."
|
"Reset to 50% of skin depth."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Hloubka zamčení musí být menší než hloubka krycí vrstvy.\n"
|
"Hloubka zamčení musí být menší než hloubka krycí vrstvy.\n"
|
||||||
|
|||||||
@@ -4757,7 +4757,7 @@ msgstr ""
|
|||||||
"\n"
|
"\n"
|
||||||
"Der Wert wird auf 0 zurückgesetzt."
|
"Der Wert wird auf 0 zurückgesetzt."
|
||||||
|
|
||||||
msgid "Alternate extra wall does't work well when ensure vertical shell thickness is set to All."
|
msgid "Alternate extra wall doesn't work well when ensure vertical shell thickness is set to All."
|
||||||
msgstr "Der alternative zusätzliche Wandmodus funktioniert nicht gut, wenn die vertikale Wanddicke auf Alle eingestellt ist."
|
msgstr "Der alternative zusätzliche Wandmodus funktioniert nicht gut, wenn die vertikale Wanddicke auf Alle eingestellt ist."
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -4803,7 +4803,7 @@ msgstr ""
|
|||||||
"NEIN - unabhängige Stütz-Schichthöhen beibehalten"
|
"NEIN - unabhängige Stütz-Schichthöhen beibehalten"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"seam_slope_start_height need to be smaller than layer_height.\n"
|
"seam_slope_start_height needs to be smaller than layer_height.\n"
|
||||||
"Reset to 0."
|
"Reset to 0."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"seam_slope_start_height muss kleiner als layer_height sein.\n"
|
"seam_slope_start_height muss kleiner als layer_height sein.\n"
|
||||||
@@ -4811,7 +4811,7 @@ msgstr ""
|
|||||||
|
|
||||||
#, no-c-format, no-boost-format
|
#, no-c-format, no-boost-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Lock depth should smaller than skin depth.\n"
|
"Lock depth should be smaller than skin depth.\n"
|
||||||
"Reset to 50% of skin depth."
|
"Reset to 50% of skin depth."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Die Verriegelungstiefe sollte kleiner als die Hauttiefe sein.\n"
|
"Die Verriegelungstiefe sollte kleiner als die Hauttiefe sein.\n"
|
||||||
|
|||||||
@@ -4493,7 +4493,7 @@ msgid ""
|
|||||||
"The value will be reset to 0."
|
"The value will be reset to 0."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Alternate extra wall does't work well when ensure vertical shell thickness is set to All."
|
msgid "Alternate extra wall doesn't work well when ensure vertical shell thickness is set to All."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -4524,13 +4524,13 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"seam_slope_start_height need to be smaller than layer_height.\n"
|
"seam_slope_start_height needs to be smaller than layer_height.\n"
|
||||||
"Reset to 0."
|
"Reset to 0."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, no-c-format, no-boost-format
|
#, no-c-format, no-boost-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Lock depth should smaller than skin depth.\n"
|
"Lock depth should be smaller than skin depth.\n"
|
||||||
"Reset to 50% of skin depth."
|
"Reset to 50% of skin depth."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -4629,7 +4629,7 @@ msgstr ""
|
|||||||
"\n"
|
"\n"
|
||||||
"El valor se restablecerá a 0."
|
"El valor se restablecerá a 0."
|
||||||
|
|
||||||
msgid "Alternate extra wall does't work well when ensure vertical shell thickness is set to All."
|
msgid "Alternate extra wall doesn't work well when ensure vertical shell thickness is set to All."
|
||||||
msgstr "Perímetro adicional alternado no funciona bien cuando \"Garantizar el grosor vertical de las cubiertas\" se establece en Todos."
|
msgstr "Perímetro adicional alternado no funciona bien cuando \"Garantizar el grosor vertical de las cubiertas\" se establece en Todos."
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -4675,7 +4675,7 @@ msgstr ""
|
|||||||
"NO - Mantener la altura de capa de soportes independiente"
|
"NO - Mantener la altura de capa de soportes independiente"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"seam_slope_start_height need to be smaller than layer_height.\n"
|
"seam_slope_start_height needs to be smaller than layer_height.\n"
|
||||||
"Reset to 0."
|
"Reset to 0."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"seam_slope_start_height debe ser menor que layer_height.\n"
|
"seam_slope_start_height debe ser menor que layer_height.\n"
|
||||||
@@ -4683,7 +4683,7 @@ msgstr ""
|
|||||||
|
|
||||||
#, no-c-format, no-boost-format
|
#, no-c-format, no-boost-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Lock depth should smaller than skin depth.\n"
|
"Lock depth should be smaller than skin depth.\n"
|
||||||
"Reset to 50% of skin depth."
|
"Reset to 50% of skin depth."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"La profundidad de bloqueo debe ser menor que la profundidad de piel.\n"
|
"La profundidad de bloqueo debe ser menor que la profundidad de piel.\n"
|
||||||
|
|||||||
@@ -4670,7 +4670,7 @@ msgstr ""
|
|||||||
"\n"
|
"\n"
|
||||||
"Balioa 0ra berrezarriko da."
|
"Balioa 0ra berrezarriko da."
|
||||||
|
|
||||||
msgid "Alternate extra wall does't work well when ensure vertical shell thickness is set to All."
|
msgid "Alternate extra wall doesn't work well when ensure vertical shell thickness is set to All."
|
||||||
msgstr "Horma gehigarri txandakatuak ez du ondo funtzionatzen \"Bermatu oskolaren lodiera bertikala\" Guztiak gisa ezarrita dagoenean."
|
msgstr "Horma gehigarri txandakatuak ez du ondo funtzionatzen \"Bermatu oskolaren lodiera bertikala\" Guztiak gisa ezarrita dagoenean."
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -4716,7 +4716,7 @@ msgstr ""
|
|||||||
"NO - 'Euskarrien Geruza-Altuera Independentea' mantendu"
|
"NO - 'Euskarrien Geruza-Altuera Independentea' mantendu"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"seam_slope_start_height need to be smaller than layer_height.\n"
|
"seam_slope_start_height needs to be smaller than layer_height.\n"
|
||||||
"Reset to 0."
|
"Reset to 0."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"seam_slope_start_height txigiagoa izan behar da layer_height baino.\n"
|
"seam_slope_start_height txigiagoa izan behar da layer_height baino.\n"
|
||||||
@@ -4724,7 +4724,7 @@ msgstr ""
|
|||||||
|
|
||||||
#, no-c-format, no-boost-format
|
#, no-c-format, no-boost-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Lock depth should smaller than skin depth.\n"
|
"Lock depth should be smaller than skin depth.\n"
|
||||||
"Reset to 50% of skin depth."
|
"Reset to 50% of skin depth."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Blokeo-sakonera gainazalaren sakonera baino txikiagoa izan behar da.\n"
|
"Blokeo-sakonera gainazalaren sakonera baino txikiagoa izan behar da.\n"
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ msgstr ""
|
|||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n==0 || n==1) ? 0 : 1;\n"
|
"Plural-Forms: nplurals=2; plural=(n==0 || n==1) ? 0 : 1;\n"
|
||||||
"X-Generator: Poedit 3.6\n"
|
"X-Generator: Poedit 3.9\n"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Main Extruder"
|
msgid "Main Extruder"
|
||||||
@@ -4707,7 +4707,7 @@ msgstr ""
|
|||||||
"\n"
|
"\n"
|
||||||
"La valeur sera remise à 0."
|
"La valeur sera remise à 0."
|
||||||
|
|
||||||
msgid "Alternate extra wall does't work well when ensure vertical shell thickness is set to All."
|
msgid "Alternate extra wall doesn't work well when ensure vertical shell thickness is set to All."
|
||||||
msgstr "La paroi supplémentaire alternée ne fonctionne pas bien lorsque le paramètre Assurer l’épaisseur de la coque verticale est réglée sur Tous."
|
msgstr "La paroi supplémentaire alternée ne fonctionne pas bien lorsque le paramètre Assurer l’épaisseur de la coque verticale est réglée sur Tous."
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -4753,7 +4753,7 @@ msgstr ""
|
|||||||
"NON - Conserver la hauteur de la couche de support indépendante"
|
"NON - Conserver la hauteur de la couche de support indépendante"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"seam_slope_start_height need to be smaller than layer_height.\n"
|
"seam_slope_start_height needs to be smaller than layer_height.\n"
|
||||||
"Reset to 0."
|
"Reset to 0."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"seam_slope_start_height doit être inférieur à la hauteur de couche.\n"
|
"seam_slope_start_height doit être inférieur à la hauteur de couche.\n"
|
||||||
@@ -4761,7 +4761,7 @@ msgstr ""
|
|||||||
|
|
||||||
#, no-c-format, no-boost-format
|
#, no-c-format, no-boost-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Lock depth should smaller than skin depth.\n"
|
"Lock depth should be smaller than skin depth.\n"
|
||||||
"Reset to 50% of skin depth."
|
"Reset to 50% of skin depth."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"La profondeur de verrouillage doit être inférieure à la profondeur de la peau.\n"
|
"La profondeur de verrouillage doit être inférieure à la profondeur de la peau.\n"
|
||||||
@@ -10707,7 +10707,7 @@ msgid "Acceleration limitation"
|
|||||||
msgstr "Limitation d'accélération"
|
msgstr "Limitation d'accélération"
|
||||||
|
|
||||||
msgid "Jerk limitation"
|
msgid "Jerk limitation"
|
||||||
msgstr "Limitation des secousses"
|
msgstr "Limitation du jerk"
|
||||||
|
|
||||||
msgid "Single extruder multi-material setup"
|
msgid "Single extruder multi-material setup"
|
||||||
msgstr "Configuration multi-matériaux pour extrudeur unique"
|
msgstr "Configuration multi-matériaux pour extrudeur unique"
|
||||||
@@ -12226,7 +12226,7 @@ msgid "Plate %d: %s does not support filament %s"
|
|||||||
msgstr "Plaque %d : %s ne prend pas en charge le filament %s"
|
msgstr "Plaque %d : %s ne prend pas en charge le filament %s"
|
||||||
|
|
||||||
msgid "Setting the jerk speed too low could lead to artifacts on curved surfaces"
|
msgid "Setting the jerk speed too low could lead to artifacts on curved surfaces"
|
||||||
msgstr "Un réglage trop bas de la vitesse de saccade peut entraîner des artefacts sur les surfaces courbes"
|
msgstr "Un réglage trop bas de la vitesse de jerk peut entraîner des artefacts sur les surfaces courbes"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"The jerk setting exceeds the printer's maximum jerk (machine_max_jerk_x/machine_max_jerk_y).\n"
|
"The jerk setting exceeds the printer's maximum jerk (machine_max_jerk_x/machine_max_jerk_y).\n"
|
||||||
@@ -12234,7 +12234,7 @@ msgid ""
|
|||||||
"You can adjust the maximum jerk setting in your printer's configuration to get higher speeds."
|
"You can adjust the maximum jerk setting in your printer's configuration to get higher speeds."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Le réglage du jerk dépasse le jerk maximum de l’imprimante (machine_max_jerk_x/machine_max_jerk_y).\n"
|
"Le réglage du jerk dépasse le jerk maximum de l’imprimante (machine_max_jerk_x/machine_max_jerk_y).\n"
|
||||||
"Orca plafonne automatiquement la vitesse de l’impulsion pour s’assurer qu’elle ne dépasse pas les capacités de l’imprimante.\n"
|
"Orca plafonne automatiquement la vitesse du jerk pour s’assurer qu’elle ne dépasse pas les capacités de l’imprimante.\n"
|
||||||
"Vous pouvez ajuster le réglage du jerk maximum dans la configuration de votre imprimante pour obtenir des vitesses plus élevées."
|
"Vous pouvez ajuster le réglage du jerk maximum dans la configuration de votre imprimante pour obtenir des vitesses plus élevées."
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -21188,8 +21188,7 @@ msgstr ""
|
|||||||
"Divisez vos impressions en plateaux\n"
|
"Divisez vos impressions en plateaux\n"
|
||||||
"Saviez-vous que vous pouvez diviser un modèle comportant de nombreuses pièces en plateaux individuels prêts à être imprimés ? Cela simplifie le processus de suivi de toutes les pièces."
|
"Saviez-vous que vous pouvez diviser un modèle comportant de nombreuses pièces en plateaux individuels prêts à être imprimés ? Cela simplifie le processus de suivi de toutes les pièces."
|
||||||
|
|
||||||
#: resources/data/hints.ini: [hint:Speed up your print with Adaptive Layer
|
#: resources/data/hints.ini: [hint:Speed up your print with Adaptive Layer Height]
|
||||||
#: Height]
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Speed up your print with Adaptive Layer Height\n"
|
"Speed up your print with Adaptive Layer Height\n"
|
||||||
"Did you know that you can print a model even faster by using the Adaptive Layer Height option? Check it out!"
|
"Did you know that you can print a model even faster by using the Adaptive Layer Height option? Check it out!"
|
||||||
@@ -21261,8 +21260,7 @@ msgstr ""
|
|||||||
"Améliorer la solidité\n"
|
"Améliorer la solidité\n"
|
||||||
"Saviez-vous que vous pouvez définir un plus grand nombre de périmètre et une densité de remplissage plus élevée pour améliorer la résistance du modèle ?"
|
"Saviez-vous que vous pouvez définir un plus grand nombre de périmètre et une densité de remplissage plus élevée pour améliorer la résistance du modèle ?"
|
||||||
|
|
||||||
#: resources/data/hints.ini: [hint:When do you need to print with the printer
|
#: resources/data/hints.ini: [hint:When do you need to print with the printer door opened]
|
||||||
#: door opened]
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"When do you need to print with the printer door opened?\n"
|
"When do you need to print with the printer door opened?\n"
|
||||||
"Did you know that opening the printer door can reduce the probability of extruder/hotend clogging when printing lower temperature filament with a higher enclosure temperature? There is more info about this in the Wiki."
|
"Did you know that opening the printer door can reduce the probability of extruder/hotend clogging when printing lower temperature filament with a higher enclosure temperature? There is more info about this in the Wiki."
|
||||||
|
|||||||
@@ -4803,7 +4803,7 @@ msgstr ""
|
|||||||
"\n"
|
"\n"
|
||||||
"Az érték 0-ra áll vissza."
|
"Az érték 0-ra áll vissza."
|
||||||
|
|
||||||
msgid "Alternate extra wall does't work well when ensure vertical shell thickness is set to All."
|
msgid "Alternate extra wall doesn't work well when ensure vertical shell thickness is set to All."
|
||||||
msgstr "A váltakozó extra fal nem működik jól, ha a függőleges héjvastagság biztosítása \"Mind\" értékre van állítva."
|
msgstr "A váltakozó extra fal nem működik jól, ha a függőleges héjvastagság biztosítása \"Mind\" értékre van állítva."
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -4849,7 +4849,7 @@ msgstr ""
|
|||||||
"NEM - Független támasz rétegmagasság megtartása"
|
"NEM - Független támasz rétegmagasság megtartása"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"seam_slope_start_height need to be smaller than layer_height.\n"
|
"seam_slope_start_height needs to be smaller than layer_height.\n"
|
||||||
"Reset to 0."
|
"Reset to 0."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"A seam_slope_start_height értékének kisebbnek kell lennie, mint a layer_height.\n"
|
"A seam_slope_start_height értékének kisebbnek kell lennie, mint a layer_height.\n"
|
||||||
@@ -4857,7 +4857,7 @@ msgstr ""
|
|||||||
|
|
||||||
#, no-c-format, no-boost-format
|
#, no-c-format, no-boost-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Lock depth should smaller than skin depth.\n"
|
"Lock depth should be smaller than skin depth.\n"
|
||||||
"Reset to 50% of skin depth."
|
"Reset to 50% of skin depth."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"A rögzítési mélységnek kisebbnek kell lennie, mint a felületi réteg mélysége.\n"
|
"A rögzítési mélységnek kisebbnek kell lennie, mint a felületi réteg mélysége.\n"
|
||||||
|
|||||||
@@ -4805,7 +4805,7 @@ msgstr ""
|
|||||||
"\n"
|
"\n"
|
||||||
"Il valore verrà reimpostato a 0."
|
"Il valore verrà reimpostato a 0."
|
||||||
|
|
||||||
msgid "Alternate extra wall does't work well when ensure vertical shell thickness is set to All."
|
msgid "Alternate extra wall doesn't work well when ensure vertical shell thickness is set to All."
|
||||||
msgstr "Parete aggiuntiva alternativa non funziona bene quando \"Garantisci spessore verticale del guscio\" è impostato su Tutto."
|
msgstr "Parete aggiuntiva alternativa non funziona bene quando \"Garantisci spessore verticale del guscio\" è impostato su Tutto."
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -4851,7 +4851,7 @@ msgstr ""
|
|||||||
"NO - Mantieni Altezza strato di supporto indipendente"
|
"NO - Mantieni Altezza strato di supporto indipendente"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"seam_slope_start_height need to be smaller than layer_height.\n"
|
"seam_slope_start_height needs to be smaller than layer_height.\n"
|
||||||
"Reset to 0."
|
"Reset to 0."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"seam_slope_start_height deve essere inferiore a layer_height.\n"
|
"seam_slope_start_height deve essere inferiore a layer_height.\n"
|
||||||
@@ -4859,7 +4859,7 @@ msgstr ""
|
|||||||
|
|
||||||
#, no-c-format, no-boost-format
|
#, no-c-format, no-boost-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Lock depth should smaller than skin depth.\n"
|
"Lock depth should be smaller than skin depth.\n"
|
||||||
"Reset to 50% of skin depth."
|
"Reset to 50% of skin depth."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"La profondità di intersezione deve essere inferiore alla profondità della pelle.\n"
|
"La profondità di intersezione deve essere inferiore alla profondità della pelle.\n"
|
||||||
|
|||||||
@@ -4818,7 +4818,7 @@ msgstr ""
|
|||||||
"\n"
|
"\n"
|
||||||
"値は0にリセットされます。"
|
"値は0にリセットされます。"
|
||||||
|
|
||||||
msgid "Alternate extra wall does't work well when ensure vertical shell thickness is set to All."
|
msgid "Alternate extra wall doesn't work well when ensure vertical shell thickness is set to All."
|
||||||
msgstr "垂直シェル厚さを「すべて」に設定すると、交互追加壁が適切に機能しません。"
|
msgstr "垂直シェル厚さを「すべて」に設定すると、交互追加壁が適切に機能しません。"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -4864,7 +4864,7 @@ msgstr ""
|
|||||||
"いいえ - 「独立サポート積層ピッチ」を有効にする"
|
"いいえ - 「独立サポート積層ピッチ」を有効にする"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"seam_slope_start_height need to be smaller than layer_height.\n"
|
"seam_slope_start_height needs to be smaller than layer_height.\n"
|
||||||
"Reset to 0."
|
"Reset to 0."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"seam_slope_start_heightはlayer_heightより小さくする必要があります。\n"
|
"seam_slope_start_heightはlayer_heightより小さくする必要があります。\n"
|
||||||
@@ -4872,7 +4872,7 @@ msgstr ""
|
|||||||
|
|
||||||
#, no-c-format, no-boost-format
|
#, no-c-format, no-boost-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Lock depth should smaller than skin depth.\n"
|
"Lock depth should be smaller than skin depth.\n"
|
||||||
"Reset to 50% of skin depth."
|
"Reset to 50% of skin depth."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"ロック深さはスキン深さより小さくする必要があります。\n"
|
"ロック深さはスキン深さより小さくする必要があります。\n"
|
||||||
@@ -10617,7 +10617,7 @@ msgid "Junction Deviation"
|
|||||||
msgstr "接合偏差"
|
msgstr "接合偏差"
|
||||||
|
|
||||||
msgid "Jerk(XY)"
|
msgid "Jerk(XY)"
|
||||||
msgstr "ジャーク(XY)"
|
msgstr "Jerk(XY)"
|
||||||
|
|
||||||
msgid "Raft"
|
msgid "Raft"
|
||||||
msgstr "ラフト"
|
msgstr "ラフト"
|
||||||
@@ -10914,7 +10914,7 @@ msgid "Acceleration limitation"
|
|||||||
msgstr "加速制限"
|
msgstr "加速制限"
|
||||||
|
|
||||||
msgid "Jerk limitation"
|
msgid "Jerk limitation"
|
||||||
msgstr "振動特性"
|
msgstr "Jerk制限"
|
||||||
|
|
||||||
msgid "Single extruder multi-material setup"
|
msgid "Single extruder multi-material setup"
|
||||||
msgstr "シングルエクストルーダー マルチマテリアル設定"
|
msgstr "シングルエクストルーダー マルチマテリアル設定"
|
||||||
@@ -12485,7 +12485,7 @@ msgid "Plate %d: %s does not support filament %s"
|
|||||||
msgstr "プレート %d: %s がフィラメント %s を使用できません"
|
msgstr "プレート %d: %s がフィラメント %s を使用できません"
|
||||||
|
|
||||||
msgid "Setting the jerk speed too low could lead to artifacts on curved surfaces"
|
msgid "Setting the jerk speed too low could lead to artifacts on curved surfaces"
|
||||||
msgstr "ジャーク速度を低く設定しすぎると曲面にアーティファクトが発生する可能性があります"
|
msgstr "Jerk速度を低く設定しすぎると曲面にアーティファクトが発生する可能性があります"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -14750,7 +14750,7 @@ msgid "Klipper's max_accel_to_decel will be adjusted to this %% of acceleration.
|
|||||||
msgstr "Klipperのmax_accel_to_decelが、加速度のこの%%に調整されます。"
|
msgstr "Klipperのmax_accel_to_decelが、加速度のこの%%に調整されます。"
|
||||||
|
|
||||||
msgid "Default jerk."
|
msgid "Default jerk."
|
||||||
msgstr "デフォルトジャーク。"
|
msgstr "デフォルトのJerkです。"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Marlin Firmware Junction Deviation (replaces the traditional XY Jerk setting)."
|
msgid "Marlin Firmware Junction Deviation (replaces the traditional XY Jerk setting)."
|
||||||
@@ -15841,28 +15841,28 @@ msgid "Maximum acceleration of the E axis"
|
|||||||
msgstr "E軸最大加速度"
|
msgstr "E軸最大加速度"
|
||||||
|
|
||||||
msgid "Maximum jerk X"
|
msgid "Maximum jerk X"
|
||||||
msgstr "最大振動 X"
|
msgstr "最大Jerk X"
|
||||||
|
|
||||||
msgid "Maximum jerk Y"
|
msgid "Maximum jerk Y"
|
||||||
msgstr "最大振動 Y"
|
msgstr "最大Jerk Y"
|
||||||
|
|
||||||
msgid "Maximum jerk Z"
|
msgid "Maximum jerk Z"
|
||||||
msgstr "最大振動 Z"
|
msgstr "最大Jerk Z"
|
||||||
|
|
||||||
msgid "Maximum jerk E"
|
msgid "Maximum jerk E"
|
||||||
msgstr "最大振動 E"
|
msgstr "最大Jerk E"
|
||||||
|
|
||||||
msgid "Maximum jerk of the X axis"
|
msgid "Maximum jerk of the X axis"
|
||||||
msgstr "最大振動 X"
|
msgstr "X軸最大Jerk"
|
||||||
|
|
||||||
msgid "Maximum jerk of the Y axis"
|
msgid "Maximum jerk of the Y axis"
|
||||||
msgstr "最大振動 Y"
|
msgstr "Y軸最大Jerk"
|
||||||
|
|
||||||
msgid "Maximum jerk of the Z axis"
|
msgid "Maximum jerk of the Z axis"
|
||||||
msgstr "最大振動 Z"
|
msgstr "Z軸最大Jerk"
|
||||||
|
|
||||||
msgid "Maximum jerk of the E axis"
|
msgid "Maximum jerk of the E axis"
|
||||||
msgstr "最大振動 E"
|
msgstr "E軸最大Jerk"
|
||||||
|
|
||||||
msgid "Maximum Junction Deviation"
|
msgid "Maximum Junction Deviation"
|
||||||
msgstr "最大接合偏差"
|
msgstr "最大接合偏差"
|
||||||
|
|||||||
@@ -4829,7 +4829,7 @@ msgstr ""
|
|||||||
"\n"
|
"\n"
|
||||||
"값이 0으로 재설정됩니다."
|
"값이 0으로 재설정됩니다."
|
||||||
|
|
||||||
msgid "Alternate extra wall does't work well when ensure vertical shell thickness is set to All."
|
msgid "Alternate extra wall doesn't work well when ensure vertical shell thickness is set to All."
|
||||||
msgstr "세로 쉘 두께가 모두로 설정된 경우 대체 여분의 벽이 제대로 작동하지 않습니다."
|
msgstr "세로 쉘 두께가 모두로 설정된 경우 대체 여분의 벽이 제대로 작동하지 않습니다."
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -4875,7 +4875,7 @@ msgstr ""
|
|||||||
"아니요 - 독립적 서포트 레이어 높이 유지"
|
"아니요 - 독립적 서포트 레이어 높이 유지"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"seam_slope_start_height need to be smaller than layer_height.\n"
|
"seam_slope_start_height needs to be smaller than layer_height.\n"
|
||||||
"Reset to 0."
|
"Reset to 0."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"심_경사_시작_높이는 레이어_높이보다 작아야 합니다.\n"
|
"심_경사_시작_높이는 레이어_높이보다 작아야 합니다.\n"
|
||||||
@@ -4883,7 +4883,7 @@ msgstr ""
|
|||||||
|
|
||||||
#, no-c-format, no-boost-format
|
#, no-c-format, no-boost-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Lock depth should smaller than skin depth.\n"
|
"Lock depth should be smaller than skin depth.\n"
|
||||||
"Reset to 50% of skin depth."
|
"Reset to 50% of skin depth."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"잠금 깊이는 스킨 깊이보다 작아야 합니다.\n"
|
"잠금 깊이는 스킨 깊이보다 작아야 합니다.\n"
|
||||||
|
|||||||
@@ -4792,7 +4792,7 @@ msgstr ""
|
|||||||
"\n"
|
"\n"
|
||||||
"Reikšmė bus atstatyta į 0."
|
"Reikšmė bus atstatyta į 0."
|
||||||
|
|
||||||
msgid "Alternate extra wall does't work well when ensure vertical shell thickness is set to All."
|
msgid "Alternate extra wall doesn't work well when ensure vertical shell thickness is set to All."
|
||||||
msgstr "Alternatyvi papildoma sienelė veikia prastai, kai vertikalaus apvalkalo storio užtikrinimas nustatytas į „Visi“."
|
msgstr "Alternatyvi papildoma sienelė veikia prastai, kai vertikalaus apvalkalo storio užtikrinimas nustatytas į „Visi“."
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -4838,7 +4838,7 @@ msgstr ""
|
|||||||
"NE – palikti nepriklausomą atramų sluoksnio aukštį"
|
"NE – palikti nepriklausomą atramų sluoksnio aukštį"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"seam_slope_start_height need to be smaller than layer_height.\n"
|
"seam_slope_start_height needs to be smaller than layer_height.\n"
|
||||||
"Reset to 0."
|
"Reset to 0."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"„seam_slope_start_height“ turi būti mažesnis už „layer_height“\n"
|
"„seam_slope_start_height“ turi būti mažesnis už „layer_height“\n"
|
||||||
@@ -4846,7 +4846,7 @@ msgstr ""
|
|||||||
|
|
||||||
#, no-c-format, no-boost-format
|
#, no-c-format, no-boost-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Lock depth should smaller than skin depth.\n"
|
"Lock depth should be smaller than skin depth.\n"
|
||||||
"Reset to 50% of skin depth."
|
"Reset to 50% of skin depth."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Fiksavimo gylis turėtų būti mažesnis už išorinio sluoksnio gylį.\n"
|
"Fiksavimo gylis turėtų būti mažesnis už išorinio sluoksnio gylį.\n"
|
||||||
|
|||||||
@@ -5217,7 +5217,7 @@ msgstr ""
|
|||||||
"De waarde wordt teruggezet naar 0."
|
"De waarde wordt teruggezet naar 0."
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Alternate extra wall does't work well when ensure vertical shell thickness is set to All."
|
msgid "Alternate extra wall doesn't work well when ensure vertical shell thickness is set to All."
|
||||||
msgstr "Afwisselende extra wand werkt niet goed wanneer 'verticale wanddikte waarborgen' op Alles is ingesteld."
|
msgstr "Afwisselende extra wand werkt niet goed wanneer 'verticale wanddikte waarborgen' op Alles is ingesteld."
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
@@ -5265,7 +5265,7 @@ msgstr ""
|
|||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid ""
|
msgid ""
|
||||||
"seam_slope_start_height need to be smaller than layer_height.\n"
|
"seam_slope_start_height needs to be smaller than layer_height.\n"
|
||||||
"Reset to 0."
|
"Reset to 0."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"seam_slope_start_height moet kleiner zijn dan layer_height.\n"
|
"seam_slope_start_height moet kleiner zijn dan layer_height.\n"
|
||||||
@@ -5274,7 +5274,7 @@ msgstr ""
|
|||||||
# AI Translated
|
# AI Translated
|
||||||
#, no-c-format, no-boost-format
|
#, no-c-format, no-boost-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Lock depth should smaller than skin depth.\n"
|
"Lock depth should be smaller than skin depth.\n"
|
||||||
"Reset to 50% of skin depth."
|
"Reset to 50% of skin depth."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"De vergrendeldiepte moet kleiner zijn dan de huiddiepte.\n"
|
"De vergrendeldiepte moet kleiner zijn dan de huiddiepte.\n"
|
||||||
|
|||||||
@@ -4907,7 +4907,7 @@ msgstr ""
|
|||||||
"\n"
|
"\n"
|
||||||
"Wartość zostanie zresetowana do 0."
|
"Wartość zostanie zresetowana do 0."
|
||||||
|
|
||||||
msgid "Alternate extra wall does't work well when ensure vertical shell thickness is set to All."
|
msgid "Alternate extra wall doesn't work well when ensure vertical shell thickness is set to All."
|
||||||
msgstr "Alternatywna dodatkowa ściana działa tylko wtedy, gdy jest wyłączona opcja „zapewnij stałą grubość pionowej powłoki”."
|
msgstr "Alternatywna dodatkowa ściana działa tylko wtedy, gdy jest wyłączona opcja „zapewnij stałą grubość pionowej powłoki”."
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -4953,7 +4953,7 @@ msgstr ""
|
|||||||
"NIE - Zachowaj Niezależną wysokość warstwy podpory"
|
"NIE - Zachowaj Niezależną wysokość warstwy podpory"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"seam_slope_start_height need to be smaller than layer_height.\n"
|
"seam_slope_start_height needs to be smaller than layer_height.\n"
|
||||||
"Reset to 0."
|
"Reset to 0."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"seam_slope_start_height musi być mniejsza niż wysokość warstwy.\n"
|
"seam_slope_start_height musi być mniejsza niż wysokość warstwy.\n"
|
||||||
@@ -4962,7 +4962,7 @@ msgstr ""
|
|||||||
# AI Translated
|
# AI Translated
|
||||||
#, no-c-format, no-boost-format
|
#, no-c-format, no-boost-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Lock depth should smaller than skin depth.\n"
|
"Lock depth should be smaller than skin depth.\n"
|
||||||
"Reset to 50% of skin depth."
|
"Reset to 50% of skin depth."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Głębokość blokady powinna być mniejsza niż głębokość skóry.\n"
|
"Głębokość blokady powinna być mniejsza niż głębokość skóry.\n"
|
||||||
|
|||||||
@@ -4641,7 +4641,7 @@ msgstr ""
|
|||||||
"\n"
|
"\n"
|
||||||
"O valor será redefinido para 0."
|
"O valor será redefinido para 0."
|
||||||
|
|
||||||
msgid "Alternate extra wall does't work well when ensure vertical shell thickness is set to All."
|
msgid "Alternate extra wall doesn't work well when ensure vertical shell thickness is set to All."
|
||||||
msgstr "A parede extra alternada não funciona bem quando a espessura vertical da casca está definida para Todos."
|
msgstr "A parede extra alternada não funciona bem quando a espessura vertical da casca está definida para Todos."
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -4687,7 +4687,7 @@ msgstr ""
|
|||||||
"NÃO — Manter a Altura da Camada de Suporte Independente"
|
"NÃO — Manter a Altura da Camada de Suporte Independente"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"seam_slope_start_height need to be smaller than layer_height.\n"
|
"seam_slope_start_height needs to be smaller than layer_height.\n"
|
||||||
"Reset to 0."
|
"Reset to 0."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"seam_slope_start_height precisa ser menor que layer_height.\n"
|
"seam_slope_start_height precisa ser menor que layer_height.\n"
|
||||||
@@ -4695,7 +4695,7 @@ msgstr ""
|
|||||||
|
|
||||||
#, no-c-format, no-boost-format
|
#, no-c-format, no-boost-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Lock depth should smaller than skin depth.\n"
|
"Lock depth should be smaller than skin depth.\n"
|
||||||
"Reset to 50% of skin depth."
|
"Reset to 50% of skin depth."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"A profundidade do travamento deve ser menor que a profundidade da textura.\n"
|
"A profundidade do travamento deve ser menor que a profundidade da textura.\n"
|
||||||
|
|||||||
@@ -4780,7 +4780,7 @@ msgstr ""
|
|||||||
"\n"
|
"\n"
|
||||||
"Значение будет сброшено до 0."
|
"Значение будет сброшено до 0."
|
||||||
|
|
||||||
msgid "Alternate extra wall does't work well when ensure vertical shell thickness is set to All."
|
msgid "Alternate extra wall doesn't work well when ensure vertical shell thickness is set to All."
|
||||||
msgstr "Чередующаяся дополнительная стенка не работает, если для «Сохранение толщины вертикальной оболочки» установлено значение «Все»."
|
msgstr "Чередующаяся дополнительная стенка не работает, если для «Сохранение толщины вертикальной оболочки» установлено значение «Все»."
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -4833,7 +4833,7 @@ msgstr ""
|
|||||||
"Нет – отключить черновую башню"
|
"Нет – отключить черновую башню"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"seam_slope_start_height need to be smaller than layer_height.\n"
|
"seam_slope_start_height needs to be smaller than layer_height.\n"
|
||||||
"Reset to 0."
|
"Reset to 0."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Начальная высота не должна превышать высоту слоя.\n"
|
"Начальная высота не должна превышать высоту слоя.\n"
|
||||||
@@ -4841,7 +4841,7 @@ msgstr ""
|
|||||||
|
|
||||||
#, no-c-format, no-boost-format
|
#, no-c-format, no-boost-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Lock depth should smaller than skin depth.\n"
|
"Lock depth should be smaller than skin depth.\n"
|
||||||
"Reset to 50% of skin depth."
|
"Reset to 50% of skin depth."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Перекрытие должно быть меньше внутренней оболочки.\n"
|
"Перекрытие должно быть меньше внутренней оболочки.\n"
|
||||||
|
|||||||
@@ -5279,7 +5279,7 @@ msgstr ""
|
|||||||
"Värdet kommer att återställas till 0."
|
"Värdet kommer att återställas till 0."
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Alternate extra wall does't work well when ensure vertical shell thickness is set to All."
|
msgid "Alternate extra wall doesn't work well when ensure vertical shell thickness is set to All."
|
||||||
msgstr "Alternerande extra vägg fungerar inte bra när säkerställ vertikal skaltjocklek är inställt på Alla."
|
msgstr "Alternerande extra vägg fungerar inte bra när säkerställ vertikal skaltjocklek är inställt på Alla."
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
@@ -5327,7 +5327,7 @@ msgstr ""
|
|||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid ""
|
msgid ""
|
||||||
"seam_slope_start_height need to be smaller than layer_height.\n"
|
"seam_slope_start_height needs to be smaller than layer_height.\n"
|
||||||
"Reset to 0."
|
"Reset to 0."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"seam_slope_start_height måste vara mindre än layer_height.\n"
|
"seam_slope_start_height måste vara mindre än layer_height.\n"
|
||||||
@@ -5336,7 +5336,7 @@ msgstr ""
|
|||||||
# AI Translated
|
# AI Translated
|
||||||
#, no-c-format, no-boost-format
|
#, no-c-format, no-boost-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Lock depth should smaller than skin depth.\n"
|
"Lock depth should be smaller than skin depth.\n"
|
||||||
"Reset to 50% of skin depth."
|
"Reset to 50% of skin depth."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Låsdjupet bör vara mindre än ytskiktets djup.\n"
|
"Låsdjupet bör vara mindre än ytskiktets djup.\n"
|
||||||
|
|||||||
@@ -4785,7 +4785,7 @@ msgstr ""
|
|||||||
"\n"
|
"\n"
|
||||||
"ค่าจะถูกรีเซ็ตเป็น 0"
|
"ค่าจะถูกรีเซ็ตเป็น 0"
|
||||||
|
|
||||||
msgid "Alternate extra wall does't work well when ensure vertical shell thickness is set to All."
|
msgid "Alternate extra wall doesn't work well when ensure vertical shell thickness is set to All."
|
||||||
msgstr "ผนังเสริมสำรองทำงานได้ไม่ดีเมื่อตั้งค่าความหนาของเปลือกแนวตั้งเป็นทั้งหมด"
|
msgstr "ผนังเสริมสำรองทำงานได้ไม่ดีเมื่อตั้งค่าความหนาของเปลือกแนวตั้งเป็นทั้งหมด"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -4831,7 +4831,7 @@ msgstr ""
|
|||||||
"ไม่ - รักษาความสูงของชั้นรองรับที่เป็นอิสระ"
|
"ไม่ - รักษาความสูงของชั้นรองรับที่เป็นอิสระ"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"seam_slope_start_height need to be smaller than layer_height.\n"
|
"seam_slope_start_height needs to be smaller than layer_height.\n"
|
||||||
"Reset to 0."
|
"Reset to 0."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"seam_slope_start_height ต้องเล็กกว่า layer_height\n"
|
"seam_slope_start_height ต้องเล็กกว่า layer_height\n"
|
||||||
@@ -4839,7 +4839,7 @@ msgstr ""
|
|||||||
|
|
||||||
#, no-c-format, no-boost-format
|
#, no-c-format, no-boost-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Lock depth should smaller than skin depth.\n"
|
"Lock depth should be smaller than skin depth.\n"
|
||||||
"Reset to 50% of skin depth."
|
"Reset to 50% of skin depth."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"ความลึกของล็อคควรน้อยกว่าความลึกของผิวหนัง\n"
|
"ความลึกของล็อคควรน้อยกว่าความลึกของผิวหนัง\n"
|
||||||
|
|||||||
@@ -4847,7 +4847,7 @@ msgstr ""
|
|||||||
"\n"
|
"\n"
|
||||||
"Değer 0'a sıfırlanacaktır."
|
"Değer 0'a sıfırlanacaktır."
|
||||||
|
|
||||||
msgid "Alternate extra wall does't work well when ensure vertical shell thickness is set to All."
|
msgid "Alternate extra wall doesn't work well when ensure vertical shell thickness is set to All."
|
||||||
msgstr "Alternatif ekstra duvar, dikey kabuk kalınlığının Tümü olarak ayarlandığından emin olunduğunda iyi çalışmaz."
|
msgstr "Alternatif ekstra duvar, dikey kabuk kalınlığının Tümü olarak ayarlandığından emin olunduğunda iyi çalışmaz."
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -4893,7 +4893,7 @@ msgstr ""
|
|||||||
"HAYIR - Bağımsız Destek Katmanı Yüksekliğini Koruyun"
|
"HAYIR - Bağımsız Destek Katmanı Yüksekliğini Koruyun"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"seam_slope_start_height need to be smaller than layer_height.\n"
|
"seam_slope_start_height needs to be smaller than layer_height.\n"
|
||||||
"Reset to 0."
|
"Reset to 0."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"seam_slope_start_height, layer_height değerinden küçük olmalıdır.\n"
|
"seam_slope_start_height, layer_height değerinden küçük olmalıdır.\n"
|
||||||
@@ -4901,7 +4901,7 @@ msgstr ""
|
|||||||
|
|
||||||
#, no-c-format, no-boost-format
|
#, no-c-format, no-boost-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Lock depth should smaller than skin depth.\n"
|
"Lock depth should be smaller than skin depth.\n"
|
||||||
"Reset to 50% of skin depth."
|
"Reset to 50% of skin depth."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Kilit derinliği cilt derinliğinden daha küçük olmalıdır.\n"
|
"Kilit derinliği cilt derinliğinden daha küçük olmalıdır.\n"
|
||||||
|
|||||||
@@ -4781,7 +4781,7 @@ msgstr ""
|
|||||||
"\n"
|
"\n"
|
||||||
"Значення буде скинуто на 0."
|
"Значення буде скинуто на 0."
|
||||||
|
|
||||||
msgid "Alternate extra wall does't work well when ensure vertical shell thickness is set to All."
|
msgid "Alternate extra wall doesn't work well when ensure vertical shell thickness is set to All."
|
||||||
msgstr "Чергування додаткової стінки не працює добре, якщо для параметра \"Забезпечити товщину вертикальної оболонки\" встановлено значення \"Всі\"."
|
msgstr "Чергування додаткової стінки не працює добре, якщо для параметра \"Забезпечити товщину вертикальної оболонки\" встановлено значення \"Всі\"."
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -4827,7 +4827,7 @@ msgstr ""
|
|||||||
"НІ - Залишити незалежну висоту шарів підтримки"
|
"НІ - Залишити незалежну висоту шарів підтримки"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"seam_slope_start_height need to be smaller than layer_height.\n"
|
"seam_slope_start_height needs to be smaller than layer_height.\n"
|
||||||
"Reset to 0."
|
"Reset to 0."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"seam_slope_start_height має бути менше висоти шару.\n"
|
"seam_slope_start_height має бути менше висоти шару.\n"
|
||||||
@@ -4836,7 +4836,7 @@ msgstr ""
|
|||||||
# AI Translated
|
# AI Translated
|
||||||
#, no-c-format, no-boost-format
|
#, no-c-format, no-boost-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Lock depth should smaller than skin depth.\n"
|
"Lock depth should be smaller than skin depth.\n"
|
||||||
"Reset to 50% of skin depth."
|
"Reset to 50% of skin depth."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Глибина зчеплення має бути меншою за глибину оболонки.\n"
|
"Глибина зчеплення має бути меншою за глибину оболонки.\n"
|
||||||
|
|||||||
@@ -5040,7 +5040,7 @@ msgstr ""
|
|||||||
"\n"
|
"\n"
|
||||||
"Giá trị sẽ được đặt lại về 0."
|
"Giá trị sẽ được đặt lại về 0."
|
||||||
|
|
||||||
msgid "Alternate extra wall does't work well when ensure vertical shell thickness is set to All."
|
msgid "Alternate extra wall doesn't work well when ensure vertical shell thickness is set to All."
|
||||||
msgstr "Luân phiên wall phụ không hoạt động tốt khi đảm bảo độ dày shell dọc được đặt thành Tất cả."
|
msgstr "Luân phiên wall phụ không hoạt động tốt khi đảm bảo độ dày shell dọc được đặt thành Tất cả."
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -5086,7 +5086,7 @@ msgstr ""
|
|||||||
"NO - Giữ chiều cao lớp support độc lập"
|
"NO - Giữ chiều cao lớp support độc lập"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"seam_slope_start_height need to be smaller than layer_height.\n"
|
"seam_slope_start_height needs to be smaller than layer_height.\n"
|
||||||
"Reset to 0."
|
"Reset to 0."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"seam_slope_start_height cần nhỏ hơn layer_height.\n"
|
"seam_slope_start_height cần nhỏ hơn layer_height.\n"
|
||||||
@@ -5094,7 +5094,7 @@ msgstr ""
|
|||||||
|
|
||||||
#, no-c-format, no-boost-format
|
#, no-c-format, no-boost-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Lock depth should smaller than skin depth.\n"
|
"Lock depth should be smaller than skin depth.\n"
|
||||||
"Reset to 50% of skin depth."
|
"Reset to 50% of skin depth."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Độ sâu khóa phải nhỏ hơn độ sâu skin.\n"
|
"Độ sâu khóa phải nhỏ hơn độ sâu skin.\n"
|
||||||
|
|||||||
@@ -4641,7 +4641,7 @@ msgstr ""
|
|||||||
"\n"
|
"\n"
|
||||||
"这个数值将被重置为0。"
|
"这个数值将被重置为0。"
|
||||||
|
|
||||||
msgid "Alternate extra wall does't work well when ensure vertical shell thickness is set to All."
|
msgid "Alternate extra wall doesn't work well when ensure vertical shell thickness is set to All."
|
||||||
msgstr "“交替添加额外”与“确保垂直外壳厚度”的”全部“选项不兼容。"
|
msgstr "“交替添加额外”与“确保垂直外壳厚度”的”全部“选项不兼容。"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -4687,7 +4687,7 @@ msgstr ""
|
|||||||
"否 - 选择保留支撑独立层高"
|
"否 - 选择保留支撑独立层高"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"seam_slope_start_height need to be smaller than layer_height.\n"
|
"seam_slope_start_height needs to be smaller than layer_height.\n"
|
||||||
"Reset to 0."
|
"Reset to 0."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"seam_slope_start_height需要小于layer_height。\n"
|
"seam_slope_start_height需要小于layer_height。\n"
|
||||||
@@ -4695,7 +4695,7 @@ msgstr ""
|
|||||||
|
|
||||||
#, no-c-format, no-boost-format
|
#, no-c-format, no-boost-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Lock depth should smaller than skin depth.\n"
|
"Lock depth should be smaller than skin depth.\n"
|
||||||
"Reset to 50% of skin depth."
|
"Reset to 50% of skin depth."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"锁定深度应小于表皮深度。\n"
|
"锁定深度应小于表皮深度。\n"
|
||||||
|
|||||||
@@ -4758,7 +4758,7 @@ msgstr ""
|
|||||||
"\n"
|
"\n"
|
||||||
"該值將會重設為 0。"
|
"該值將會重設為 0。"
|
||||||
|
|
||||||
msgid "Alternate extra wall does't work well when ensure vertical shell thickness is set to All."
|
msgid "Alternate extra wall doesn't work well when ensure vertical shell thickness is set to All."
|
||||||
msgstr "當確保垂直外殼厚度設為『全部』時,交錯額外牆壁效果不佳。"
|
msgstr "當確保垂直外殼厚度設為『全部』時,交錯額外牆壁效果不佳。"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -4815,7 +4815,7 @@ msgstr ""
|
|||||||
"否 - 選擇保留獨立支撐層高"
|
"否 - 選擇保留獨立支撐層高"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"seam_slope_start_height need to be smaller than layer_height.\n"
|
"seam_slope_start_height needs to be smaller than layer_height.\n"
|
||||||
"Reset to 0."
|
"Reset to 0."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"seam_slope_start_height 必須小於 layer_height。\n"
|
"seam_slope_start_height 必須小於 layer_height。\n"
|
||||||
@@ -4824,7 +4824,7 @@ msgstr ""
|
|||||||
|
|
||||||
#, no-c-format, no-boost-format
|
#, no-c-format, no-boost-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"Lock depth should smaller than skin depth.\n"
|
"Lock depth should be smaller than skin depth.\n"
|
||||||
"Reset to 50% of skin depth."
|
"Reset to 50% of skin depth."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"鎖定深度應小於表皮深度。\n"
|
"鎖定深度應小於表皮深度。\n"
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
@echo off
|
||||||
|
REM Runs check_profile.ps1, the Windows twin of check_profile.sh, from cmd.
|
||||||
|
REM -ExecutionPolicy Bypass is needed because a Windows client defaults to Restricted,
|
||||||
|
REM which refuses to run a checked-out .ps1 at all.
|
||||||
|
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0check_profile.ps1" %*
|
||||||
|
exit /b %ERRORLEVEL%
|
||||||
@@ -0,0 +1,564 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Local twin of the "Check profiles" CI job (.github/workflows/check_profiles.yml), for Windows.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
The Windows counterpart of scripts/check_profile.sh, and kept deliberately close to it.
|
||||||
|
|
||||||
|
Runs the same five checks, in the same order, with the same validator flags, and with the
|
||||||
|
same semantics: every check runs even after an earlier one fails (the workflow's
|
||||||
|
continue-on-error), then the script exits non-zero once at the end.
|
||||||
|
|
||||||
|
extra_json_check scripts/orca_extra_profile_check.py
|
||||||
|
validate_system validator -p <profiles> -l <level>
|
||||||
|
validate_slice validator -p <profiles> -s -l <level>
|
||||||
|
validate_filament_subtypes validator -p <profiles> -l <level> -f
|
||||||
|
validate_custom validator against every released custom-preset fixture
|
||||||
|
|
||||||
|
Everything that has to be downloaded - the profile validator and the custom-preset fixture
|
||||||
|
archives - lands under <repo>\.test\check_profiles and is reused on the next run. That
|
||||||
|
directory also holds one log per check plus a copy of the comment CI would post on the PR.
|
||||||
|
|
||||||
|
resources\profiles\user, which the validator creates as its data dir but a CI checkout never
|
||||||
|
has, is moved aside for the duration of the run and restored on exit. Only one run per work
|
||||||
|
dir at a time.
|
||||||
|
|
||||||
|
x64 and ARM64 hosts are both supported. A locally built validator is chosen by the machine
|
||||||
|
type in its PE header rather than by the name of its build tree, so build\ (x64) and
|
||||||
|
build-arm64\ side by side resolve correctly; the published nightly is x64 only and runs
|
||||||
|
under emulation on ARM64.
|
||||||
|
|
||||||
|
.PARAMETER ProfilesDir
|
||||||
|
Profile tree to validate (default: resources\profiles). extra_json_check always looks at the
|
||||||
|
tree next to the script, so this only redirects the validator checks.
|
||||||
|
|
||||||
|
.PARAMETER Validator
|
||||||
|
OrcaSlicer_profile_validator.exe to use; also $env:ORCA_PROFILE_VALIDATOR. Default: the local
|
||||||
|
build*\ Release build (then RelWithDebInfo, MinSizeRel, Debug) for this architecture, else
|
||||||
|
the nightly release build is downloaded.
|
||||||
|
|
||||||
|
.PARAMETER Download
|
||||||
|
Ignore local builds and use the downloaded nightly validator.
|
||||||
|
|
||||||
|
.PARAMETER Refresh
|
||||||
|
Re-download the validator and fixtures instead of using the cache.
|
||||||
|
|
||||||
|
.PARAMETER WorkDir
|
||||||
|
Downloads, logs and fixture trees (default: .test\check_profiles). Point it somewhere short,
|
||||||
|
such as D:\t, if a fixture tree trips Windows' 260-character path limit.
|
||||||
|
|
||||||
|
.PARAMETER LogLevel
|
||||||
|
Validator log level (default: 2, as in CI).
|
||||||
|
|
||||||
|
.PARAMETER Checks
|
||||||
|
Checks to run, by name (default: all of them, in the order listed above).
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
scripts\check_profile.bat
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
powershell -ExecutionPolicy Bypass -File scripts\check_profile.ps1 validate_system validate_slice
|
||||||
|
#>
|
||||||
|
|
||||||
|
# PositionalBinding is off so that the check names are the only positional arguments; left on,
|
||||||
|
# a bare "validate_system" would bind to whichever named parameter came next in this block.
|
||||||
|
[CmdletBinding(PositionalBinding = $false)]
|
||||||
|
param(
|
||||||
|
[Alias('p')] [string] $ProfilesDir,
|
||||||
|
[string] $Validator,
|
||||||
|
[string] $WorkDir,
|
||||||
|
[Alias('l')] [int] $LogLevel = 2,
|
||||||
|
[switch] $Download,
|
||||||
|
[switch] $Refresh,
|
||||||
|
[Alias('h')] [switch] $Help,
|
||||||
|
[Parameter(Position = 0, ValueFromRemainingArguments = $true)] [string[]] $Checks
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
# Windows PowerShell 5.1 still negotiates TLS 1.0/1.1, which github.com refuses.
|
||||||
|
[Net.ServicePointManager]::SecurityProtocol =
|
||||||
|
[Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
|
||||||
|
|
||||||
|
$ValidatorReleaseUrl = 'https://github.com/OrcaSlicer/OrcaSlicer/releases/download/nightly-builds'
|
||||||
|
$FixtureReleaseUrl = 'https://github.com/OrcaSlicer/OrcaSlicer-profile-validator/releases/download/fixture-archive'
|
||||||
|
|
||||||
|
$RepoRoot = Split-Path -Parent $PSScriptRoot
|
||||||
|
|
||||||
|
# PROCESSOR_ARCHITECTURE reports the architecture of the *shell*, so a 32-bit PowerShell on a
|
||||||
|
# 64-bit OS says x86; ARCHITEW6432 is the machine's own in that case.
|
||||||
|
$HostArch = if ($env:PROCESSOR_ARCHITEW6432) { $env:PROCESSOR_ARCHITEW6432 } else { $env:PROCESSOR_ARCHITECTURE }
|
||||||
|
$HostArch = switch ($HostArch) {
|
||||||
|
'ARM64' { 'arm64' }
|
||||||
|
'AMD64' { 'x64' }
|
||||||
|
default { 'x86' }
|
||||||
|
}
|
||||||
|
|
||||||
|
$AllChecks = @('extra_json_check', 'validate_system', 'validate_slice', 'validate_filament_subtypes', 'validate_custom')
|
||||||
|
|
||||||
|
$script:LogWriter = $null
|
||||||
|
$script:Python = ''
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------- helpers
|
||||||
|
|
||||||
|
function Die([string] $Message) {
|
||||||
|
Write-Host "check_profile.ps1: $Message" -ForegroundColor Red
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
# UTF-8 without a BOM, so a log reads the same here as the artifact CI uploads.
|
||||||
|
function New-LogWriter([string] $Path) {
|
||||||
|
New-Object IO.StreamWriter($Path, $false, (New-Object Text.UTF8Encoding($false)))
|
||||||
|
}
|
||||||
|
|
||||||
|
# Output of the check being run: shown, and kept in that check's log.
|
||||||
|
function Write-CheckLog([string] $Text) {
|
||||||
|
Write-Host $Text
|
||||||
|
if ($script:LogWriter) { $script:LogWriter.WriteLine($Text) }
|
||||||
|
}
|
||||||
|
|
||||||
|
# Runs a program and returns its exit code, streaming stdout and stderr into the current check's
|
||||||
|
# log. -OutFile sends that output to a file of its own instead, silently.
|
||||||
|
function Invoke-Tool {
|
||||||
|
param([string] $Exe, [string[]] $Arguments, [string] $OutFile)
|
||||||
|
|
||||||
|
# Under 'Stop', 2>&1 turns every stderr line of a native command into a terminating error.
|
||||||
|
# Assigning the preference here scopes it to this function, so it undoes itself on return.
|
||||||
|
$ErrorActionPreference = 'Continue'
|
||||||
|
|
||||||
|
$writer = if ($OutFile) { New-LogWriter $OutFile } else { $null }
|
||||||
|
try {
|
||||||
|
& $Exe @Arguments 2>&1 | ForEach-Object {
|
||||||
|
if ($writer) { $writer.WriteLine("$_") } else { Write-CheckLog "$_" }
|
||||||
|
}
|
||||||
|
return $LASTEXITCODE
|
||||||
|
} catch {
|
||||||
|
if ($writer) { $writer.WriteLine("$_") } else { Write-CheckLog "$_" }
|
||||||
|
return 1
|
||||||
|
} finally {
|
||||||
|
if ($writer) { $writer.Dispose() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# The first $Limit characters of a log, as CI truncates them for the PR comment.
|
||||||
|
function Get-LogHead([string] $Path, [int] $Limit) {
|
||||||
|
$text = if (Test-Path -LiteralPath $Path) { [IO.File]::ReadAllText($Path) } else { '' }
|
||||||
|
if (-not $text) { return 'No output captured' }
|
||||||
|
if ($text.Length -gt $Limit) { return $text.Substring(0, $Limit) }
|
||||||
|
return $text
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------- arguments
|
||||||
|
|
||||||
|
if ($Help) { Get-Help $PSCommandPath -Detailed; exit 0 }
|
||||||
|
|
||||||
|
foreach ($name in $Checks) {
|
||||||
|
if ($name -like '-*') { Die "unknown option '$name' (try -Help)" }
|
||||||
|
if ($AllChecks -notcontains $name) { Die "unknown check '$name' (try -Help)" }
|
||||||
|
}
|
||||||
|
if (-not $Checks) { $Checks = $AllChecks }
|
||||||
|
|
||||||
|
if (-not $ProfilesDir) { $ProfilesDir = Join-Path $RepoRoot 'resources\profiles' }
|
||||||
|
if (-not (Test-Path -LiteralPath $ProfilesDir -PathType Container)) { Die "profile directory not found: $ProfilesDir" }
|
||||||
|
$ProfilesDir = (Resolve-Path -LiteralPath $ProfilesDir).Path
|
||||||
|
|
||||||
|
if (-not $WorkDir) { $WorkDir = Join-Path $RepoRoot '.test\check_profiles' }
|
||||||
|
$LogDir = Join-Path $WorkDir 'logs'
|
||||||
|
try { New-Item -ItemType Directory -Force -Path $LogDir | Out-Null } catch { Die "cannot create ${LogDir}: $_" }
|
||||||
|
$WorkDir = (Resolve-Path -LiteralPath $WorkDir).Path
|
||||||
|
$LogDir = Join-Path $WorkDir 'logs'
|
||||||
|
|
||||||
|
if (-not $Validator) { $Validator = $env:ORCA_PROFILE_VALIDATOR }
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------- clean profile tree
|
||||||
|
|
||||||
|
# The validator points its data dir at the profile tree, so it creates - and, with -g, fills -
|
||||||
|
# <profiles>\user. A CI checkout never has that directory, and anything left in it from an
|
||||||
|
# earlier local run would be loaded as user presets and validated too. Move it aside for the
|
||||||
|
# duration of the run so what gets checked is what CI checks.
|
||||||
|
$script:StashedUserDir = ''
|
||||||
|
|
||||||
|
function Push-UserPresets {
|
||||||
|
$user = Join-Path $ProfilesDir 'user'
|
||||||
|
if (-not (Test-Path -LiteralPath $user -PathType Container)) { return }
|
||||||
|
$script:StashedUserDir = Join-Path $WorkDir "user-presets-$PID"
|
||||||
|
Remove-Item -LiteralPath $script:StashedUserDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
try { Move-Item -LiteralPath $user -Destination $script:StashedUserDir }
|
||||||
|
catch { $script:StashedUserDir = ''; Die "cannot move $user aside: $_" }
|
||||||
|
Write-Host "moved $user aside for the run (restored on exit)"
|
||||||
|
}
|
||||||
|
|
||||||
|
function Pop-UserPresets {
|
||||||
|
# The validator leaves an empty user\default\{filament,machine,process} skeleton behind.
|
||||||
|
# Prune it directory by directory, never wholesale: one that holds a real file survives and
|
||||||
|
# is reported instead of being deleted. Runs even when nothing was stashed, so a tree that
|
||||||
|
# had no user\ before the run does not gain one.
|
||||||
|
$user = Join-Path $ProfilesDir 'user'
|
||||||
|
Remove-EmptyDirs $user
|
||||||
|
if (-not $script:StashedUserDir) { return }
|
||||||
|
if (Test-Path -LiteralPath $user) {
|
||||||
|
Write-Host "$user is not empty; your presets stay in $($script:StashedUserDir)"
|
||||||
|
} else {
|
||||||
|
Move-Item -LiteralPath $script:StashedUserDir -Destination $user
|
||||||
|
}
|
||||||
|
$script:StashedUserDir = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function Remove-EmptyDirs([string] $Path) {
|
||||||
|
if (-not (Test-Path -LiteralPath $Path -PathType Container)) { return }
|
||||||
|
Get-ChildItem -LiteralPath $Path -Recurse -Directory -Force |
|
||||||
|
Sort-Object { $_.FullName.Length } -Descending |
|
||||||
|
ForEach-Object {
|
||||||
|
if (-not (Get-ChildItem -LiteralPath $_.FullName -Force)) {
|
||||||
|
Remove-Item -LiteralPath $_.FullName -Force
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (-not (Get-ChildItem -LiteralPath $Path -Force)) { Remove-Item -LiteralPath $Path -Force }
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------- downloads
|
||||||
|
|
||||||
|
# Cached; -Refresh, or -Force, pulls a new copy. Release assets never change, so caching them is
|
||||||
|
# safe; the fixture manifest is the index that grows with every release, and CI re-reads it on
|
||||||
|
# every run.
|
||||||
|
function Save-Asset {
|
||||||
|
param([string] $Url, [string] $Dest, [switch] $Force)
|
||||||
|
|
||||||
|
if (-not $Refresh -and -not $Force -and (Test-Path -LiteralPath $Dest -PathType Leaf) -and
|
||||||
|
(Get-Item -LiteralPath $Dest).Length -gt 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $Dest) | Out-Null
|
||||||
|
Write-Host "downloading $(Split-Path -Leaf $Dest) ..."
|
||||||
|
|
||||||
|
# Invoke-WebRequest spends most of a large download repainting its progress bar.
|
||||||
|
$ProgressPreference = 'SilentlyContinue'
|
||||||
|
$part = "$Dest.part"
|
||||||
|
for ($attempt = 1; ; $attempt++) {
|
||||||
|
try {
|
||||||
|
Invoke-WebRequest -Uri $Url -OutFile $part -UseBasicParsing
|
||||||
|
break
|
||||||
|
} catch {
|
||||||
|
if ($attempt -ge 3) {
|
||||||
|
Remove-Item -LiteralPath $part -Force -ErrorAction SilentlyContinue
|
||||||
|
throw "download failed: $Url"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Move-Item -LiteralPath $part -Destination $Dest -Force
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-Sha256([string] $Path) {
|
||||||
|
return (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
# Directories a built validator can sit in, best first: build_win.bat names its trees build,
|
||||||
|
# build-dbginfo, build-minsize and build-dbg, each optionally -clang and -arm64 suffixed, and
|
||||||
|
# CMake puts the binary in src\<config>. Each pattern is matched both directly under a build root
|
||||||
|
# and one level down (build\x64), for trees laid out the way the macOS ones are.
|
||||||
|
function Get-ValidatorSearchDirs {
|
||||||
|
foreach ($config in 'Release', 'RelWithDebInfo', 'MinSizeRel', 'Debug') {
|
||||||
|
Join-Path $RepoRoot "build*\src\$config"
|
||||||
|
Join-Path $RepoRoot "build*\*\src\$config"
|
||||||
|
}
|
||||||
|
Join-Path $RepoRoot 'build*\src'
|
||||||
|
Join-Path $RepoRoot 'build*\*\src'
|
||||||
|
}
|
||||||
|
|
||||||
|
# The machine type from the PE header, which is what actually decides whether an .exe can run
|
||||||
|
# here - the name of the build tree only says what it was meant to be.
|
||||||
|
function Get-ExeArch([string] $Path) {
|
||||||
|
try {
|
||||||
|
$stream = [IO.File]::OpenRead($Path)
|
||||||
|
try {
|
||||||
|
$reader = New-Object IO.BinaryReader($stream)
|
||||||
|
$stream.Position = 0x3C # e_lfanew: offset of the PE header
|
||||||
|
$stream.Position = $reader.ReadInt32()
|
||||||
|
if ($reader.ReadUInt32() -ne 0x00004550) { return '' } # "PE\0\0"
|
||||||
|
switch ($reader.ReadUInt16()) {
|
||||||
|
0x8664 { 'x64' }
|
||||||
|
0xAA64 { 'arm64' }
|
||||||
|
0x014C { 'x86' }
|
||||||
|
default { '' }
|
||||||
|
}
|
||||||
|
} finally { $stream.Dispose() }
|
||||||
|
} catch { '' }
|
||||||
|
}
|
||||||
|
|
||||||
|
# First locally built OrcaSlicer_profile_validator.exe, in the order above. An x86 build, and on
|
||||||
|
# ARM64 an x64 one, is kept only as a fallback: it runs, but emulated. An ARM64 build on an x64
|
||||||
|
# host does not run at all and is never offered.
|
||||||
|
function Find-LocalValidator {
|
||||||
|
$emulated = ''
|
||||||
|
foreach ($pattern in (Get-ValidatorSearchDirs)) {
|
||||||
|
foreach ($dir in @(Resolve-Path -Path $pattern -ErrorAction SilentlyContinue)) {
|
||||||
|
$candidate = Join-Path $dir.Path 'OrcaSlicer_profile_validator.exe'
|
||||||
|
if (-not (Test-Path -LiteralPath $candidate -PathType Leaf)) { continue }
|
||||||
|
$arch = Get-ExeArch $candidate
|
||||||
|
if (-not $arch -or $arch -eq $HostArch) { return $candidate }
|
||||||
|
if (-not $emulated -and ($arch -eq 'x86' -or $HostArch -eq 'arm64')) { $emulated = $candidate }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($emulated) { Write-Host "no $HostArch build found, falling back to $emulated (emulated)" }
|
||||||
|
return $emulated
|
||||||
|
}
|
||||||
|
|
||||||
|
# The nightly release build, same one CI uses. Windows ships the bare .exe, x64 only.
|
||||||
|
function Save-NightlyValidator {
|
||||||
|
if ($HostArch -ne 'x64') {
|
||||||
|
Write-Host "the nightly Windows validator is x64; it runs here under emulation"
|
||||||
|
}
|
||||||
|
$exe = Join-Path $WorkDir 'validator\OrcaSlicer_profile_validator.exe'
|
||||||
|
Save-Asset -Url "$ValidatorReleaseUrl/OrcaSlicer_profile_validator_Windows_nightly.exe" -Dest $exe
|
||||||
|
return $exe
|
||||||
|
}
|
||||||
|
|
||||||
|
function Resolve-Validator {
|
||||||
|
if ($Validator) {
|
||||||
|
if (-not (Test-Path -LiteralPath $Validator -PathType Leaf)) { Die "validator not found: $Validator" }
|
||||||
|
return (Resolve-Path -LiteralPath $Validator).Path
|
||||||
|
}
|
||||||
|
if (-not $Download) {
|
||||||
|
$local = Find-LocalValidator
|
||||||
|
if ($local) {
|
||||||
|
Write-Host "using locally built validator: $local"
|
||||||
|
return $local
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try { $downloaded = Save-NightlyValidator } catch { Die "could not obtain a profile validator: $_" }
|
||||||
|
Write-Host "using downloaded validator: $downloaded"
|
||||||
|
return $downloaded
|
||||||
|
}
|
||||||
|
|
||||||
|
# python3 is rarely on PATH on Windows: the py launcher is the reliable way in, and a bare
|
||||||
|
# `python` may be the Store stub, which prints an advert and exits non-zero. Probe each for the
|
||||||
|
# interpreter it actually resolves to, and use that.
|
||||||
|
function Resolve-Python {
|
||||||
|
$ErrorActionPreference = 'Continue'
|
||||||
|
if ($script:Python) { return $script:Python }
|
||||||
|
foreach ($candidate in 'py -3', 'python', 'python3') {
|
||||||
|
$words = $candidate -split ' '
|
||||||
|
$exe = Get-Command $words[0] -CommandType Application -ErrorAction SilentlyContinue | Select-Object -First 1
|
||||||
|
if (-not $exe) { continue }
|
||||||
|
$leading = @($words | Select-Object -Skip 1)
|
||||||
|
$found = & $exe.Source @leading -c 'import sys; print(sys.executable)' 2>$null
|
||||||
|
if ($LASTEXITCODE -eq 0 -and $found) {
|
||||||
|
$script:Python = "$found"
|
||||||
|
return $script:Python
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Die 'no Python 3 found; install it (or the py launcher) and re-run'
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------- checks
|
||||||
|
|
||||||
|
$CheckBodies = @{
|
||||||
|
|
||||||
|
extra_json_check = {
|
||||||
|
Invoke-Tool -Exe (Resolve-Python) -Arguments @((Join-Path $RepoRoot 'scripts\orca_extra_profile_check.py'))
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_system = {
|
||||||
|
Invoke-Tool -Exe $Validator -Arguments @('-p', $ProfilesDir, '-l', "$LogLevel")
|
||||||
|
}
|
||||||
|
|
||||||
|
# Slices a two-colour cube through every printer so all custom g-code (incl.
|
||||||
|
# change_filament_gcode) is expanded - catches undefined-placeholder / invalid-flow bugs the
|
||||||
|
# static checks cannot see.
|
||||||
|
validate_slice = {
|
||||||
|
Invoke-Tool -Exe $Validator -Arguments @('-p', $ProfilesDir, '-s', '-l', "$LogLevel")
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_filament_subtypes = {
|
||||||
|
Invoke-Tool -Exe $Validator -Arguments @('-p', $ProfilesDir, '-l', "$LogLevel", '-f')
|
||||||
|
}
|
||||||
|
|
||||||
|
# Every released fixture is a snapshot of user presets saved by that OrcaSlicer version; each
|
||||||
|
# is unpacked over the current system profiles and validated, so a profile change that would
|
||||||
|
# break an existing user's presets fails here.
|
||||||
|
validate_custom = {
|
||||||
|
$fixturesDir = Join-Path $WorkDir 'profile-fixtures'
|
||||||
|
$outputDir = Join-Path $WorkDir 'custom-preset-validation'
|
||||||
|
New-Item -ItemType Directory -Force -Path $fixturesDir, $outputDir | Out-Null
|
||||||
|
|
||||||
|
$manifestPath = Join-Path $fixturesDir 'manifest.json'
|
||||||
|
Save-Asset -Url "$FixtureReleaseUrl/manifest.json" -Dest $manifestPath -Force
|
||||||
|
|
||||||
|
$manifest = Get-Content -LiteralPath $manifestPath -Raw | ConvertFrom-Json
|
||||||
|
$entries = if ($manifest -is [array]) { $manifest } else { $manifest.fixtures }
|
||||||
|
$fixtures = @($entries | Where-Object { $_.version -and $_.asset })
|
||||||
|
if (-not $fixtures) {
|
||||||
|
Write-CheckLog "No custom preset fixtures found in $FixtureReleaseUrl/manifest.json"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
$status = 0
|
||||||
|
$failedLogs = @()
|
||||||
|
$summary = @('## Custom Preset Fixture Validation', '', '| Version | Status | Log |', '| --- | --- | --- |')
|
||||||
|
|
||||||
|
foreach ($fixture in $fixtures) {
|
||||||
|
$version = $fixture.version
|
||||||
|
$asset = $fixture.asset
|
||||||
|
$fixtureZip = Join-Path $fixturesDir $asset
|
||||||
|
$profileTree = Join-Path $outputDir "profiles-$version"
|
||||||
|
$logPath = Join-Path $outputDir "$version.log"
|
||||||
|
$assetUrl = "$FixtureReleaseUrl/$([uri]::EscapeDataString($asset))"
|
||||||
|
|
||||||
|
Save-Asset -Url $assetUrl -Dest $fixtureZip
|
||||||
|
|
||||||
|
$expected = $fixture.asset_sha256
|
||||||
|
if ($expected -and $expected -ne '<sha256>' -and (Get-Sha256 $fixtureZip) -ne $expected.ToUpperInvariant()) {
|
||||||
|
# A cached zip can be stale or truncated; the release asset itself is immutable,
|
||||||
|
# so deleting it forces Save-Asset to pull a fresh copy.
|
||||||
|
Write-Host "checksum mismatch for $asset, re-downloading"
|
||||||
|
Remove-Item -LiteralPath $fixtureZip -Force
|
||||||
|
Save-Asset -Url $assetUrl -Dest $fixtureZip
|
||||||
|
$actual = Get-Sha256 $fixtureZip
|
||||||
|
if ($actual -ne $expected.ToUpperInvariant()) {
|
||||||
|
Write-CheckLog "${asset}: expected $expected, got $actual"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "validating custom presets from $version ..."
|
||||||
|
Remove-Item -LiteralPath $profileTree -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
New-Item -ItemType Directory -Force -Path $profileTree | Out-Null
|
||||||
|
# Piped rather than copied through <profiles>\*, so a vendor directory whose name
|
||||||
|
# holds a wildcard character is still copied by its literal path.
|
||||||
|
Get-ChildItem -LiteralPath $ProfilesDir -Force | Copy-Item -Destination $profileTree -Recurse -Force
|
||||||
|
Remove-Item -LiteralPath (Join-Path $profileTree 'user') -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
Expand-Archive -LiteralPath $fixtureZip -DestinationPath $profileTree -Force
|
||||||
|
|
||||||
|
$result = Invoke-Tool -Exe $Validator -Arguments @('-p', $profileTree, '-l', "$LogLevel") -OutFile $logPath
|
||||||
|
if ($result -eq 0) {
|
||||||
|
$summary += "| $version | PASS | $version.log |"
|
||||||
|
# Only failures are worth keeping; each tree is a full copy of resources\profiles.
|
||||||
|
Remove-Item -LiteralPath $profileTree -Recurse -Force
|
||||||
|
} else {
|
||||||
|
$summary += "| $version | FAIL | $version.log |"
|
||||||
|
$failedLogs += $logPath
|
||||||
|
$status = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[IO.File]::WriteAllLines((Join-Path $outputDir 'summary.md'), [string[]] $summary)
|
||||||
|
$summary | ForEach-Object { Write-CheckLog $_ }
|
||||||
|
|
||||||
|
if ($failedLogs) {
|
||||||
|
Write-CheckLog ''
|
||||||
|
Write-CheckLog '## Failed Fixture Logs'
|
||||||
|
foreach ($logPath in $failedLogs) {
|
||||||
|
Write-CheckLog ''
|
||||||
|
Write-CheckLog "### $([IO.Path]::GetFileNameWithoutExtension($logPath))"
|
||||||
|
Write-CheckLog '```'
|
||||||
|
Write-CheckLog (Get-LogHead $logPath 12000)
|
||||||
|
Write-CheckLog '```'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Heading CI puts above this check's log in the PR comment.
|
||||||
|
$CommentHeadings = @{
|
||||||
|
extra_json_check = '### Extra JSON Check Failed'
|
||||||
|
validate_system = '### System Profile Validation Failed'
|
||||||
|
validate_slice = '### Slice Validation Failed (custom g-code expansion)'
|
||||||
|
validate_filament_subtypes = '### Filament Subtype Validation Failed'
|
||||||
|
validate_custom = '### Custom Preset Validation Failed'
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------- run
|
||||||
|
|
||||||
|
function Invoke-Check([string] $Name) {
|
||||||
|
$log = Join-Path $LogDir "$Name.log"
|
||||||
|
Write-Host ''
|
||||||
|
Write-Host "==> $Name" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
$script:LogWriter = New-LogWriter $log
|
||||||
|
try {
|
||||||
|
$result = & $CheckBodies[$Name] | Select-Object -Last 1
|
||||||
|
} catch {
|
||||||
|
Write-CheckLog "$_"
|
||||||
|
$result = 1
|
||||||
|
} finally {
|
||||||
|
$script:LogWriter.Dispose()
|
||||||
|
$script:LogWriter = $null
|
||||||
|
}
|
||||||
|
|
||||||
|
if ([int] $result -eq 0) {
|
||||||
|
Write-Host " $Name passed" -ForegroundColor Green
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
Write-Host " $Name failed (exit $result)" -ForegroundColor Red
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
|
||||||
|
# The fixture trees under the work dir are shared scratch space keyed by fixture version, so a
|
||||||
|
# second run would delete a tree the first one is validating.
|
||||||
|
$LockDir = Join-Path $WorkDir '.lock'
|
||||||
|
try { New-Item -ItemType Directory -Path $LockDir -ErrorAction Stop | Out-Null }
|
||||||
|
catch { Die "another run is using $WorkDir (pass -WorkDir, or remove $LockDir if no run is active)" }
|
||||||
|
|
||||||
|
# The validator writes UTF-8, but PowerShell decodes a child process's output using the console
|
||||||
|
# code page, which mangles the accented and CJK preset names in its messages.
|
||||||
|
$PreviousOutputEncoding = [Console]::OutputEncoding
|
||||||
|
|
||||||
|
try {
|
||||||
|
[Console]::OutputEncoding = New-Object Text.UTF8Encoding($false)
|
||||||
|
Push-UserPresets
|
||||||
|
|
||||||
|
if ($Checks | Where-Object { $_ -ne 'extra_json_check' }) { $Validator = Resolve-Validator }
|
||||||
|
|
||||||
|
$results = [ordered] @{}
|
||||||
|
foreach ($name in $AllChecks) {
|
||||||
|
if ($Checks -contains $name) { $results[$name] = Invoke-Check $name }
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host ''
|
||||||
|
Write-Host '==> summary' -ForegroundColor Cyan
|
||||||
|
foreach ($name in $results.Keys) {
|
||||||
|
if ($results[$name]) {
|
||||||
|
Write-Host " PASS $name" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host " FAIL $name ($(Join-Path $LogDir "$name.log"))" -ForegroundColor Red
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$failed = @($results.Keys | Where-Object { -not $results[$_] })
|
||||||
|
if (-not $failed) {
|
||||||
|
Remove-Item -LiteralPath (Join-Path $WorkDir 'pr_comment.md') -Force -ErrorAction SilentlyContinue
|
||||||
|
Write-Host ''
|
||||||
|
Write-Host "All checks passed. Logs: $LogDir" -ForegroundColor Green
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# The comment check_profiles_comment.yml would post when something fails.
|
||||||
|
$comment = @(
|
||||||
|
# Marker matched by check_profiles_comment.yml to delete prior comments.
|
||||||
|
'<!-- profile-validation-comment -->'
|
||||||
|
'## :x: Profile Validation Errors'
|
||||||
|
''
|
||||||
|
foreach ($name in $failed) {
|
||||||
|
$CommentHeadings[$name]
|
||||||
|
''
|
||||||
|
'```'
|
||||||
|
Get-LogHead (Join-Path $LogDir "$name.log") 30000
|
||||||
|
'```'
|
||||||
|
''
|
||||||
|
}
|
||||||
|
'---'
|
||||||
|
'*Please fix the above errors and push a new commit.*'
|
||||||
|
)
|
||||||
|
$commentPath = Join-Path $WorkDir 'pr_comment.md'
|
||||||
|
[IO.File]::WriteAllLines($commentPath, [string[]] $comment)
|
||||||
|
|
||||||
|
Write-Host ''
|
||||||
|
Write-Host "One or more profile checks failed. Logs: $LogDir" -ForegroundColor Red
|
||||||
|
Write-Host "The comment CI would post: $commentPath"
|
||||||
|
exit 1
|
||||||
|
} finally {
|
||||||
|
Pop-UserPresets
|
||||||
|
Remove-Item -LiteralPath $LockDir -Force -ErrorAction SilentlyContinue
|
||||||
|
[Console]::OutputEncoding = $PreviousOutputEncoding
|
||||||
|
}
|
||||||
Executable
+527
@@ -0,0 +1,527 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Local twin of the "Check profiles" CI job (.github/workflows/check_profiles.yml).
|
||||||
|
#
|
||||||
|
# Runs the same five checks, in the same order, with the same validator flags, and with the
|
||||||
|
# same semantics: every check runs even after an earlier one fails (the workflow's
|
||||||
|
# continue-on-error), then the script exits non-zero once at the end.
|
||||||
|
#
|
||||||
|
# Everything that has to be downloaded - the profile validator and the custom-preset fixture
|
||||||
|
# archives - lands under <repo>/.test/check_profiles/ and is reused on the next run. That
|
||||||
|
# directory also holds one log per check plus a copy of the comment CI would post on the PR.
|
||||||
|
#
|
||||||
|
# resources/profiles/user, which the validator creates as its data dir but a CI checkout never
|
||||||
|
# has, is moved aside for the duration of the run and restored on exit. Only one run per work
|
||||||
|
# dir at a time.
|
||||||
|
#
|
||||||
|
# Usage: scripts/check_profile.sh [OPTION]... [CHECK]...
|
||||||
|
|
||||||
|
# The check_* functions run through run_check, which dispatches on the check name, so
|
||||||
|
# ShellCheck cannot see that they (and what they call) are used.
|
||||||
|
# shellcheck disable=SC2329
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
VALIDATOR_RELEASE_URL="https://github.com/OrcaSlicer/OrcaSlicer/releases/download/nightly-builds"
|
||||||
|
FIXTURE_RELEASE_URL="https://github.com/OrcaSlicer/OrcaSlicer-profile-validator/releases/download/fixture-archive"
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
REPO_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)"
|
||||||
|
|
||||||
|
HOST_ARCH="$(uname -m)"
|
||||||
|
|
||||||
|
PROFILES_DIR="${REPO_ROOT}/resources/profiles"
|
||||||
|
WORK_DIR="${REPO_ROOT}/.test/check_profiles"
|
||||||
|
VALIDATOR="${ORCA_PROFILE_VALIDATOR:-}"
|
||||||
|
LOG_LEVEL=2
|
||||||
|
PREFER_DOWNLOAD=0
|
||||||
|
REFRESH=0
|
||||||
|
|
||||||
|
ALL_CHECKS=(extra_json_check validate_system validate_slice validate_filament_subtypes validate_custom)
|
||||||
|
CHECKS=()
|
||||||
|
# "<check><TAB>pass|fail" per check that ran; a string rather than an array because bash 3.2
|
||||||
|
# (still the /bin/bash on macOS) cannot expand an empty array under `set -u`.
|
||||||
|
RESULTS=""
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
Run the profile checks from .github/workflows/check_profiles.yml locally.
|
||||||
|
|
||||||
|
Usage: scripts/check_profile.sh [OPTION]... [CHECK]...
|
||||||
|
|
||||||
|
Checks (default: all, in this order):
|
||||||
|
extra_json_check scripts/orca_extra_profile_check.py
|
||||||
|
validate_system validator -p <profiles> -l <level>
|
||||||
|
validate_slice validator -p <profiles> -s -l <level>
|
||||||
|
validate_filament_subtypes validator -p <profiles> -l <level> -f
|
||||||
|
validate_custom validator against every released custom-preset fixture
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-p, --profiles DIR profile tree to validate (default: resources/profiles)
|
||||||
|
--validator BIN OrcaSlicer_profile_validator to use; also \$ORCA_PROFILE_VALIDATOR.
|
||||||
|
Default: the local build*/ Release build (then RelWithDebInfo, then
|
||||||
|
Debug) for this architecture, else the nightly release build is
|
||||||
|
downloaded for this platform
|
||||||
|
--download ignore local builds and use the downloaded nightly validator
|
||||||
|
--refresh re-download the validator and fixtures instead of using the cache
|
||||||
|
--work-dir DIR downloads, logs and fixture trees (default: .test/check_profiles)
|
||||||
|
-l, --log-level N validator log level (default: ${LOG_LEVEL}, as in CI)
|
||||||
|
-h, --help show this help
|
||||||
|
|
||||||
|
Note: extra_json_check always looks at the tree next to the script
|
||||||
|
(<repo>/resources/profiles); --profiles only redirects the validator checks.
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
msg() { printf '%s\n' "$*" >&2; }
|
||||||
|
die() { printf 'check_profile.sh: %s\n' "$*" >&2; exit 2; }
|
||||||
|
|
||||||
|
if [ -t 1 ]; then
|
||||||
|
C_RED=$'\033[91m'; C_GREEN=$'\033[92m'; C_BOLD=$'\033[1m'; C_RESET=$'\033[0m'
|
||||||
|
else
|
||||||
|
C_RED=''; C_GREEN=''; C_BOLD=''; C_RESET=''
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------- arguments
|
||||||
|
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
-h|--help) usage; exit 0 ;;
|
||||||
|
-p|--profiles) [ $# -ge 2 ] || die "$1 needs a directory"; PROFILES_DIR="$2"; shift 2 ;;
|
||||||
|
--validator) [ $# -ge 2 ] || die "$1 needs a path"; VALIDATOR="$2"; shift 2 ;;
|
||||||
|
--work-dir) [ $# -ge 2 ] || die "$1 needs a directory"; WORK_DIR="$2"; shift 2 ;;
|
||||||
|
-l|--log-level) [ $# -ge 2 ] || die "$1 needs a number"; LOG_LEVEL="$2"; shift 2 ;;
|
||||||
|
--download) PREFER_DOWNLOAD=1; shift ;;
|
||||||
|
--refresh) REFRESH=1; shift ;;
|
||||||
|
-*) die "unknown option '$1' (try --help)" ;;
|
||||||
|
*)
|
||||||
|
known=0
|
||||||
|
for check in "${ALL_CHECKS[@]}"; do
|
||||||
|
[ "$1" = "${check}" ] && known=1
|
||||||
|
done
|
||||||
|
[ "${known}" -eq 1 ] || die "unknown check '$1' (try --help)"
|
||||||
|
CHECKS[${#CHECKS[@]}]="$1"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[ "${#CHECKS[@]}" -gt 0 ] || CHECKS=("${ALL_CHECKS[@]}")
|
||||||
|
|
||||||
|
[ -d "${PROFILES_DIR}" ] || die "profile directory not found: ${PROFILES_DIR}"
|
||||||
|
PROFILES_DIR="$(cd -- "${PROFILES_DIR}" && pwd)"
|
||||||
|
|
||||||
|
LOG_DIR="${WORK_DIR}/logs"
|
||||||
|
mkdir -p "${LOG_DIR}" || die "cannot create ${LOG_DIR}"
|
||||||
|
WORK_DIR="$(cd -- "${WORK_DIR}" && pwd)"
|
||||||
|
LOG_DIR="${WORK_DIR}/logs"
|
||||||
|
|
||||||
|
wants() {
|
||||||
|
local check
|
||||||
|
for check in "${CHECKS[@]}"; do
|
||||||
|
[ "${check}" = "$1" ] && return 0
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------- clean profile tree
|
||||||
|
|
||||||
|
# The validator points its data dir at the profile tree, so it creates - and, with -g, fills -
|
||||||
|
# <profiles>/user. A CI checkout never has that directory, and anything left in it from an
|
||||||
|
# earlier local run would be loaded as user presets and validated too. Move it aside for the
|
||||||
|
# duration of the run so what gets checked is what CI checks.
|
||||||
|
STASHED_USER_DIR=""
|
||||||
|
|
||||||
|
stash_user_presets() {
|
||||||
|
[ -d "${PROFILES_DIR}/user" ] || return 0
|
||||||
|
STASHED_USER_DIR="${WORK_DIR}/user-presets-$$"
|
||||||
|
rm -rf "${STASHED_USER_DIR}"
|
||||||
|
mv "${PROFILES_DIR}/user" "${STASHED_USER_DIR}" || { STASHED_USER_DIR=""; die "cannot move ${PROFILES_DIR}/user aside"; }
|
||||||
|
msg "moved ${PROFILES_DIR}/user aside for the run (restored on exit)"
|
||||||
|
}
|
||||||
|
|
||||||
|
restore_user_presets() {
|
||||||
|
# The validator leaves an empty user/default/{filament,machine,process} skeleton behind.
|
||||||
|
# Prune it with rmdir, never rm -rf: a directory that holds a real file survives and is
|
||||||
|
# reported instead of being deleted. Runs even when nothing was stashed, so a tree that had
|
||||||
|
# no user/ before the run does not gain one.
|
||||||
|
find "${PROFILES_DIR}/user" -depth -type d -exec rmdir {} + 2>/dev/null
|
||||||
|
[ -n "${STASHED_USER_DIR}" ] || return 0
|
||||||
|
if [ -d "${PROFILES_DIR}/user" ]; then
|
||||||
|
msg "${PROFILES_DIR}/user is not empty; your presets stay in ${STASHED_USER_DIR}"
|
||||||
|
STASHED_USER_DIR=""
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
mv "${STASHED_USER_DIR}" "${PROFILES_DIR}/user"
|
||||||
|
STASHED_USER_DIR=""
|
||||||
|
}
|
||||||
|
|
||||||
|
# The fixture trees under the work dir are shared scratch space keyed by fixture version, so a
|
||||||
|
# second run would delete a tree the first one is validating.
|
||||||
|
LOCK_DIR="${WORK_DIR}/.lock"
|
||||||
|
mkdir "${LOCK_DIR}" 2>/dev/null ||
|
||||||
|
die "another run is using ${WORK_DIR} (pass --work-dir, or remove ${LOCK_DIR} if no run is active)"
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
restore_user_presets
|
||||||
|
rmdir "${LOCK_DIR}" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Installed only once the lock is ours, so a refused start never releases someone else's.
|
||||||
|
trap cleanup EXIT
|
||||||
|
trap 'cleanup; exit 130' INT TERM
|
||||||
|
|
||||||
|
stash_user_presets
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------- downloads
|
||||||
|
|
||||||
|
# fetch URL DEST [force] - cached; --refresh, or a non-empty third argument, forces a new
|
||||||
|
# download. Release assets never change, so caching them is safe; the fixture manifest is the
|
||||||
|
# index that grows with every release, and CI re-reads it on every run.
|
||||||
|
fetch() {
|
||||||
|
local url="$1" dest="$2" force="${3:-}"
|
||||||
|
if [ "${REFRESH}" -eq 0 ] && [ -z "${force}" ] && [ -s "${dest}" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
mkdir -p "$(dirname -- "${dest}")" || return 1
|
||||||
|
msg "downloading $(basename -- "${dest}") ..."
|
||||||
|
if ! curl -fsSL --retry 3 -o "${dest}.part" "${url}"; then
|
||||||
|
rm -f "${dest}.part"
|
||||||
|
msg "download failed: ${url}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
mv -f "${dest}.part" "${dest}"
|
||||||
|
}
|
||||||
|
|
||||||
|
sha256_of() {
|
||||||
|
if command -v sha256sum >/dev/null 2>&1; then
|
||||||
|
sha256sum "$1" | cut -d' ' -f1
|
||||||
|
elif command -v shasum >/dev/null 2>&1; then
|
||||||
|
shasum -a 256 "$1" | cut -d' ' -f1
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Directories a built validator can sit in, best first: every Ninja Multi-Config Release tree,
|
||||||
|
# then RelWithDebInfo, then Debug, then a single-config generator's plain src/. Each pattern is
|
||||||
|
# matched both directly under a build root (build/) and one level down (build/arm64, build/x64),
|
||||||
|
# and unmatched globs are dropped by the caller's -d test.
|
||||||
|
validator_search_dirs() {
|
||||||
|
local config
|
||||||
|
for config in Release RelWithDebInfo Debug; do
|
||||||
|
printf '%s\n' "${REPO_ROOT}"/build*/src/"${config}" "${REPO_ROOT}"/build*/*/src/"${config}"
|
||||||
|
done
|
||||||
|
printf '%s\n' "${REPO_ROOT}"/build*/src "${REPO_ROOT}"/build*/*/src
|
||||||
|
}
|
||||||
|
|
||||||
|
# A build tree named for the other architecture (build/x86_64 on an arm64 host, build/arm64 on
|
||||||
|
# an x64 one) is a last resort: on Linux it will not run at all, on macOS it goes via Rosetta.
|
||||||
|
# Takes a repo-relative path - an absolute one would also match an arch name in the checkout path.
|
||||||
|
is_foreign_arch_dir() {
|
||||||
|
case "${HOST_ARCH}" in
|
||||||
|
arm64|aarch64) case "$1" in *x86_64*|*x86-64*|*x64*|*amd64*) return 0 ;; esac ;;
|
||||||
|
x86_64|amd64) case "$1" in *arm64*|*aarch64*) return 0 ;; esac ;;
|
||||||
|
esac
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# First locally built OrcaSlicer_profile_validator, in the order above. macOS puts it in an .app
|
||||||
|
# bundle, Linux and Windows next to the other binaries.
|
||||||
|
find_local_validator() {
|
||||||
|
local dir candidate foreign=""
|
||||||
|
while IFS= read -r dir; do
|
||||||
|
[ -d "${dir}" ] || continue
|
||||||
|
for candidate in \
|
||||||
|
"${dir}/OrcaSlicer_profile_validator" \
|
||||||
|
"${dir}/OrcaSlicer_profile_validator.exe" \
|
||||||
|
"${dir}/OrcaSlicer_profile_validator.app/Contents/MacOS/OrcaSlicer_profile_validator"; do
|
||||||
|
[ -f "${candidate}" ] && [ -x "${candidate}" ] || continue
|
||||||
|
if ! is_foreign_arch_dir "${dir#"${REPO_ROOT}"/}"; then
|
||||||
|
printf '%s\n' "${candidate}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
[ -n "${foreign}" ] || foreign="${candidate}"
|
||||||
|
done
|
||||||
|
done <<EOF
|
||||||
|
$(validator_search_dirs)
|
||||||
|
EOF
|
||||||
|
[ -n "${foreign}" ] || return 1
|
||||||
|
msg "no ${HOST_ARCH} build found, falling back to ${foreign}"
|
||||||
|
printf '%s\n' "${foreign}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# The nightly release build, same one CI uses. Linux ships the bare binary, macOS a .dmg
|
||||||
|
# holding the signed .app, Windows an .exe.
|
||||||
|
download_validator() {
|
||||||
|
local dest="${WORK_DIR}/validator" binary dmg app mounted app_src
|
||||||
|
case "$(uname -s)" in
|
||||||
|
Linux*)
|
||||||
|
case "${HOST_ARCH}" in
|
||||||
|
arm64|aarch64) msg "the nightly Linux validator is x86_64; build it locally for ${HOST_ARCH}" ;;
|
||||||
|
esac
|
||||||
|
binary="${dest}/OrcaSlicer_profile_validator"
|
||||||
|
fetch "${VALIDATOR_RELEASE_URL}/OrcaSlicer_profile_validator_Linux_Ubuntu2404_nightly" "${binary}" || return 1
|
||||||
|
chmod +x "${binary}" || return 1
|
||||||
|
;;
|
||||||
|
Darwin*)
|
||||||
|
dmg="${dest}/OrcaSlicer_profile_validator.dmg"
|
||||||
|
app="${dest}/OrcaSlicer_profile_validator.app"
|
||||||
|
binary="${app}/Contents/MacOS/OrcaSlicer_profile_validator"
|
||||||
|
fetch "${VALIDATOR_RELEASE_URL}/OrcaSlicer_profile_validator_Mac_universal_nightly.dmg" "${dmg}" || return 1
|
||||||
|
if [ ! -x "${binary}" ] || [ "${REFRESH}" -eq 1 ]; then
|
||||||
|
mounted="${dest}/mnt"
|
||||||
|
rm -rf "${mounted}" "${app}"
|
||||||
|
mkdir -p "${mounted}" || return 1
|
||||||
|
hdiutil attach -nobrowse -readonly -mountpoint "${mounted}" "${dmg}" >/dev/null || return 1
|
||||||
|
app_src="$(find "${mounted}" -maxdepth 1 -name '*.app' -print 2>/dev/null | head -n 1)"
|
||||||
|
if [ -n "${app_src}" ]; then
|
||||||
|
cp -R "${app_src}" "${app}"
|
||||||
|
fi
|
||||||
|
hdiutil detach "${mounted}" >/dev/null 2>&1
|
||||||
|
rmdir "${mounted}" 2>/dev/null
|
||||||
|
[ -x "${binary}" ] || { msg "no validator app inside ${dmg}"; return 1; }
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
MINGW*|MSYS*|CYGWIN*)
|
||||||
|
binary="${dest}/OrcaSlicer_profile_validator.exe"
|
||||||
|
fetch "${VALIDATOR_RELEASE_URL}/OrcaSlicer_profile_validator_Windows_nightly.exe" "${binary}" || return 1
|
||||||
|
chmod +x "${binary}" || return 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
msg "no nightly validator published for $(uname -s); build it (-DORCA_TOOLS=ON) and pass --validator"
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
printf '%s\n' "${binary}"
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve_validator() {
|
||||||
|
if [ -n "${VALIDATOR}" ]; then
|
||||||
|
[ -x "${VALIDATOR}" ] || die "validator not executable: ${VALIDATOR}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [ "${PREFER_DOWNLOAD}" -eq 0 ]; then
|
||||||
|
VALIDATOR="$(find_local_validator)"
|
||||||
|
if [ -n "${VALIDATOR}" ]; then
|
||||||
|
msg "using locally built validator: ${VALIDATOR}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
VALIDATOR="$(download_validator)" || die "could not obtain a profile validator"
|
||||||
|
msg "using downloaded validator: ${VALIDATOR}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------- checks
|
||||||
|
|
||||||
|
check_extra_json_check() {
|
||||||
|
python3 "${REPO_ROOT}/scripts/orca_extra_profile_check.py"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_validate_system() {
|
||||||
|
"${VALIDATOR}" -p "${PROFILES_DIR}" -l "${LOG_LEVEL}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Slices a two-colour cube through every printer so all custom g-code (incl. change_filament_gcode)
|
||||||
|
# is expanded - catches undefined-placeholder / invalid-flow bugs the static checks cannot see.
|
||||||
|
check_validate_slice() {
|
||||||
|
"${VALIDATOR}" -p "${PROFILES_DIR}" -s -l "${LOG_LEVEL}"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_validate_filament_subtypes() {
|
||||||
|
"${VALIDATOR}" -p "${PROFILES_DIR}" -l "${LOG_LEVEL}" -f
|
||||||
|
}
|
||||||
|
|
||||||
|
# Every released fixture is a snapshot of user presets saved by that OrcaSlicer version; each is
|
||||||
|
# unpacked over the current system profiles and validated, so a profile change that would break
|
||||||
|
# an existing user's presets fails here.
|
||||||
|
check_validate_custom() {
|
||||||
|
local fixtures_dir="${WORK_DIR}/profile-fixtures"
|
||||||
|
local output_dir="${WORK_DIR}/custom-preset-validation"
|
||||||
|
local summary="${output_dir}/summary.md"
|
||||||
|
local status=0 failed_logs=""
|
||||||
|
local version asset expected_sha256 asset_url fixture_zip profile_tree log_path actual_sha256 result
|
||||||
|
|
||||||
|
command -v unzip >/dev/null 2>&1 || { msg "unzip is required for validate_custom"; return 1; }
|
||||||
|
mkdir -p "${fixtures_dir}" "${output_dir}" || return 1
|
||||||
|
|
||||||
|
fetch "${FIXTURE_RELEASE_URL}/manifest.json" "${fixtures_dir}/manifest.json" force || return 1
|
||||||
|
|
||||||
|
MANIFEST_PATH="${fixtures_dir}/manifest.json" python3 - > "${fixtures_dir}/fixtures.tsv" <<'PY'
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
with open(os.environ["MANIFEST_PATH"], encoding="utf-8") as fh:
|
||||||
|
manifest = json.load(fh)
|
||||||
|
|
||||||
|
if isinstance(manifest, dict):
|
||||||
|
entries = manifest.get("fixtures", [])
|
||||||
|
else:
|
||||||
|
entries = manifest
|
||||||
|
|
||||||
|
for entry in entries:
|
||||||
|
version = entry.get("version", "")
|
||||||
|
asset = entry.get("asset", "")
|
||||||
|
sha256 = entry.get("asset_sha256", "")
|
||||||
|
if not version or not asset:
|
||||||
|
continue
|
||||||
|
print(f"{version}\t{asset}\t{sha256}")
|
||||||
|
PY
|
||||||
|
|
||||||
|
if [ ! -s "${fixtures_dir}/fixtures.tsv" ]; then
|
||||||
|
echo "No custom preset fixtures found in ${FIXTURE_RELEASE_URL}/manifest.json"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "## Custom Preset Fixture Validation"
|
||||||
|
echo ""
|
||||||
|
echo "| Version | Status | Log |"
|
||||||
|
echo "| --- | --- | --- |"
|
||||||
|
} > "${summary}"
|
||||||
|
|
||||||
|
while IFS=$'\t' read -r version asset expected_sha256; do
|
||||||
|
[ -n "${version}" ] || continue
|
||||||
|
fixture_zip="${fixtures_dir}/${asset}"
|
||||||
|
profile_tree="${output_dir}/profiles-${version}"
|
||||||
|
log_path="${output_dir}/${version}.log"
|
||||||
|
|
||||||
|
asset_url="${FIXTURE_RELEASE_URL}/$(python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=""))' "${asset}")"
|
||||||
|
fetch "${asset_url}" "${fixture_zip}" || return 1
|
||||||
|
|
||||||
|
if [ -n "${expected_sha256}" ] && [ "${expected_sha256}" != "<sha256>" ]; then
|
||||||
|
actual_sha256="$(sha256_of "${fixture_zip}")"
|
||||||
|
if [ -z "${actual_sha256}" ]; then
|
||||||
|
msg "no sha256 tool available, skipping checksum of ${asset}"
|
||||||
|
elif [ "${actual_sha256}" != "${expected_sha256}" ]; then
|
||||||
|
# A cached zip can be stale or truncated; the release asset itself is immutable,
|
||||||
|
# so deleting it forces fetch to pull a fresh copy.
|
||||||
|
msg "checksum mismatch for ${asset}, re-downloading"
|
||||||
|
rm -f "${fixture_zip}"
|
||||||
|
fetch "${asset_url}" "${fixture_zip}" || return 1
|
||||||
|
actual_sha256="$(sha256_of "${fixture_zip}")"
|
||||||
|
[ "${actual_sha256}" = "${expected_sha256}" ] || { msg "${asset}: expected ${expected_sha256}, got ${actual_sha256}"; return 1; }
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
msg "validating custom presets from ${version} ..."
|
||||||
|
rm -rf "${profile_tree}"
|
||||||
|
mkdir -p "${profile_tree}" || return 1
|
||||||
|
cp -a "${PROFILES_DIR}/." "${profile_tree}/" || return 1
|
||||||
|
rm -rf "${profile_tree}/user"
|
||||||
|
unzip -q "${fixture_zip}" -d "${profile_tree}" || return 1
|
||||||
|
|
||||||
|
"${VALIDATOR}" -p "${profile_tree}" -l "${LOG_LEVEL}" > "${log_path}" 2>&1
|
||||||
|
result=$?
|
||||||
|
|
||||||
|
if [ "${result}" -eq 0 ]; then
|
||||||
|
echo "| ${version} | PASS | ${version}.log |" >> "${summary}"
|
||||||
|
# Only failures are worth keeping; each tree is a full copy of resources/profiles.
|
||||||
|
rm -rf "${profile_tree}"
|
||||||
|
else
|
||||||
|
echo "| ${version} | FAIL | ${version}.log |" >> "${summary}"
|
||||||
|
failed_logs="${failed_logs}${log_path}"$'\n'
|
||||||
|
status=1
|
||||||
|
fi
|
||||||
|
done < "${fixtures_dir}/fixtures.tsv"
|
||||||
|
|
||||||
|
cat "${summary}"
|
||||||
|
if [ -n "${failed_logs}" ]; then
|
||||||
|
echo ""
|
||||||
|
echo "## Failed Fixture Logs"
|
||||||
|
while IFS= read -r log_path; do
|
||||||
|
[ -n "${log_path}" ] || continue
|
||||||
|
echo ""
|
||||||
|
echo "### $(basename "${log_path}" .log)"
|
||||||
|
echo '```'
|
||||||
|
head -c 12000 "${log_path}" || echo "No output captured"
|
||||||
|
echo '```'
|
||||||
|
done <<EOF
|
||||||
|
${failed_logs}
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
return "${status}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Heading CI puts above this check's log in the PR comment.
|
||||||
|
comment_heading() {
|
||||||
|
case "$1" in
|
||||||
|
extra_json_check) echo "### Extra JSON Check Failed" ;;
|
||||||
|
validate_system) echo "### System Profile Validation Failed" ;;
|
||||||
|
validate_slice) echo "### Slice Validation Failed (custom g-code expansion)" ;;
|
||||||
|
validate_filament_subtypes) echo "### Filament Subtype Validation Failed" ;;
|
||||||
|
validate_custom) echo "### Custom Preset Validation Failed" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------- run
|
||||||
|
|
||||||
|
run_check() {
|
||||||
|
local name="$1"
|
||||||
|
local log="${LOG_DIR}/${name}.log"
|
||||||
|
local result
|
||||||
|
printf '\n%s==> %s%s\n' "${C_BOLD}" "${name}" "${C_RESET}"
|
||||||
|
"check_${name}" 2>&1 | tee "${log}"
|
||||||
|
result="${PIPESTATUS[0]}"
|
||||||
|
if [ "${result}" -eq 0 ]; then
|
||||||
|
printf '%s %s passed%s\n' "${C_GREEN}" "${name}" "${C_RESET}"
|
||||||
|
RESULTS="${RESULTS}${name}"$'\t'"pass"$'\n'
|
||||||
|
else
|
||||||
|
printf '%s %s failed (exit %s)%s\n' "${C_RED}" "${name}" "${result}" "${C_RESET}"
|
||||||
|
RESULTS="${RESULTS}${name}"$'\t'"fail"$'\n'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
if wants validate_system || wants validate_slice || wants validate_filament_subtypes || wants validate_custom; then
|
||||||
|
resolve_validator
|
||||||
|
fi
|
||||||
|
|
||||||
|
for check in "${ALL_CHECKS[@]}"; do
|
||||||
|
wants "${check}" && run_check "${check}"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Summary, plus the comment check_profiles_comment.yml would post when something fails.
|
||||||
|
failed=0
|
||||||
|
printf '\n%s==> summary%s\n' "${C_BOLD}" "${C_RESET}"
|
||||||
|
while IFS=$'\t' read -r name result; do
|
||||||
|
[ -n "${name}" ] || continue
|
||||||
|
if [ "${result}" = "pass" ]; then
|
||||||
|
printf '%s PASS%s %s\n' "${C_GREEN}" "${C_RESET}" "${name}"
|
||||||
|
else
|
||||||
|
printf '%s FAIL%s %s (%s)\n' "${C_RED}" "${C_RESET}" "${name}" "${LOG_DIR}/${name}.log"
|
||||||
|
failed=1
|
||||||
|
fi
|
||||||
|
done <<EOF
|
||||||
|
${RESULTS}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if [ "${failed}" -eq 0 ]; then
|
||||||
|
rm -f "${WORK_DIR}/pr_comment.md"
|
||||||
|
printf '\n%sAll checks passed.%s Logs: %s\n' "${C_GREEN}" "${C_RESET}" "${LOG_DIR}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
{
|
||||||
|
# Marker matched by check_profiles_comment.yml to delete prior comments.
|
||||||
|
echo "<!-- profile-validation-comment -->"
|
||||||
|
echo "## :x: Profile Validation Errors"
|
||||||
|
echo ""
|
||||||
|
while IFS=$'\t' read -r name result; do
|
||||||
|
[ "${result}" = "fail" ] || continue
|
||||||
|
comment_heading "${name}"
|
||||||
|
echo ""
|
||||||
|
echo '```'
|
||||||
|
head -c 30000 "${LOG_DIR}/${name}.log" || echo "No output captured"
|
||||||
|
echo '```'
|
||||||
|
echo ""
|
||||||
|
done <<INNER
|
||||||
|
${RESULTS}
|
||||||
|
INNER
|
||||||
|
echo "---"
|
||||||
|
echo "*Please fix the above errors and push a new commit.*"
|
||||||
|
} > "${WORK_DIR}/pr_comment.md"
|
||||||
|
|
||||||
|
printf '\n%sOne or more profile checks failed.%s Logs: %s\n' "${C_RED}" "${C_RESET}" "${LOG_DIR}"
|
||||||
|
printf 'The comment CI would post: %s\n' "${WORK_DIR}/pr_comment.md"
|
||||||
|
exit 1
|
||||||
@@ -0,0 +1,847 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Tests build_win.bat's option handling and the commands it generates.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Cases run the script with --dry-run, so nothing is configured, built or
|
||||||
|
deleted and the suite finishes in seconds. Each case asserts on the exit
|
||||||
|
code and on the command lines the script echoes.
|
||||||
|
|
||||||
|
Adding a case means adding one row to $cases. A bare string starts a new
|
||||||
|
group. Defaults: ExpectExit is 0 and --dry-run is appended, so a row only
|
||||||
|
states what is unusual about it.
|
||||||
|
|
||||||
|
Name what the case proves, in words
|
||||||
|
Args arguments, as an array
|
||||||
|
ExpectExit expected exit code (default 0)
|
||||||
|
DryRun append --dry-run (default $true)
|
||||||
|
First regex the first output line must match
|
||||||
|
Env environment for this case only
|
||||||
|
Contains literal strings the output must have
|
||||||
|
NotContains literal strings it must not have
|
||||||
|
Match regexes; each must match at least one output line
|
||||||
|
NotMatch regexes; none may match any output line
|
||||||
|
NotExists paths that must not exist after the case runs
|
||||||
|
|
||||||
|
.PARAMETER Name
|
||||||
|
Run only the cases whose name matches this regex. Headings with no
|
||||||
|
matching case are not printed, and a pattern that matches nothing is a
|
||||||
|
failure rather than an empty pass.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
powershell -File scripts/test_build_win.ps1
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
powershell -File scripts/test_build_win.ps1 -Name solution
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[string] $Script,
|
||||||
|
[string] $Name
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
|
||||||
|
if (-not $Script) {
|
||||||
|
$here = $PSScriptRoot
|
||||||
|
if (-not $here) { $here = Split-Path -Parent $MyInvocation.MyCommand.Path }
|
||||||
|
$Script = Join-Path (Split-Path -Parent $here) 'build_win.bat'
|
||||||
|
}
|
||||||
|
if (-not (Test-Path $Script)) { throw "build_win.bat not found at $Script" }
|
||||||
|
|
||||||
|
# cmd resumes a batch file by byte offset after `call :label`, and with LF
|
||||||
|
# endings that offset lands wrong and the label lookup fails. .gitattributes
|
||||||
|
# pins CRLF; this catches a checkout or an editor that did not honour it.
|
||||||
|
if ((Get-Content -Raw $Script) -match "(?<!`r)`n") {
|
||||||
|
throw "$Script has LF line endings; cmd needs CRLF to resume after call :label"
|
||||||
|
}
|
||||||
|
$Script = (Resolve-Path $Script).Path
|
||||||
|
|
||||||
|
# Read the long options out of the script itself, so this cannot go stale when
|
||||||
|
# an option is added. Field order is: add_arg <var> <type> <short> <long>.
|
||||||
|
$longFlags = @(
|
||||||
|
Select-String -Path $Script -Pattern '^call :add_arg \S+ \S+ \S+ (\S+) ' |
|
||||||
|
ForEach-Object { '--' + $_.Matches[0].Groups[1].Value }
|
||||||
|
)
|
||||||
|
if ($longFlags.Count -lt 20) { throw "only found $($longFlags.Count) options in $Script; the parser above is wrong" }
|
||||||
|
|
||||||
|
# A winget that always fails, so the prerequisite failure path runs without
|
||||||
|
# touching the machine. It has to be an .exe: a .bat invoked without `call`
|
||||||
|
# transfers control and never comes back, which would end the script instead.
|
||||||
|
# where.exe returns 1 when its patterns match nothing and never prompts.
|
||||||
|
$fixtures = Join-Path ([IO.Path]::GetTempPath()) 'build_win_test_fixtures'
|
||||||
|
New-Item -ItemType Directory -Force -Path $fixtures | Out-Null
|
||||||
|
Copy-Item "$env:SystemRoot\System32\where.exe" (Join-Path $fixtures 'winget.exe') -Force
|
||||||
|
$stubPath = "$fixtures;C:\Windows\system32;C:\Windows"
|
||||||
|
|
||||||
|
# Stand-in ninjas that only report a version, so the 1.12 boundary in the
|
||||||
|
# progress format can be exercised on a machine whose real ninja is newer.
|
||||||
|
# A dry run skips the dev shell, so PATH here is what the script sees.
|
||||||
|
$ninjaPaths = @{}
|
||||||
|
foreach ($v in @{ old = '1.11.1'; new = '1.12.0' }.GetEnumerator()) {
|
||||||
|
$d = Join-Path $fixtures "ninja-$($v.Key)"
|
||||||
|
New-Item -ItemType Directory -Force -Path $d | Out-Null
|
||||||
|
Set-Content -Path (Join-Path $d 'ninja.bat') -Encoding ascii -Value @('@echo off', "echo $($v.Value)")
|
||||||
|
$ninjaPaths[$v.Key] = "$d;$env:PATH"
|
||||||
|
}
|
||||||
|
|
||||||
|
# The pack stamp is checked against real dates, so a locale-dependent parse
|
||||||
|
# in the script cannot pass by looking date-shaped. Yesterday is accepted too,
|
||||||
|
# so a run that crosses midnight does not flake.
|
||||||
|
$dateStamps = @((Get-Date -Format 'yyyyMMdd'), (Get-Date).AddDays(-1).ToString('yyyyMMdd'))
|
||||||
|
$stampPattern = '_(' + ($dateStamps -join '|') + ')\.zip$'
|
||||||
|
|
||||||
|
$cases = @(
|
||||||
|
'argument handling'
|
||||||
|
@{ Name = 'no arguments prints help'; Args = @(); DryRun = $false
|
||||||
|
Contains = @('Usage: build_win.bat [options]', '--clang-cl') }
|
||||||
|
@{ Name = "--help lists all $($longFlags.Count) options the script defines"; Args = @('--help'); DryRun = $false
|
||||||
|
Contains = $longFlags }
|
||||||
|
@{ Name = 'help is grouped and shows usage, examples and environment'; Args = @('--help'); DryRun = $false
|
||||||
|
Contains = @('Usage: build_win.bat [options]', 'Actions:', 'Build configuration:', 'Toolchain:',
|
||||||
|
'How much gets rebuilt:', 'Paths and extra arguments:', 'Diagnostics:',
|
||||||
|
'Examples:', 'Environment:') }
|
||||||
|
@{ Name = 'the environment section shows what to set'; Args = @('--help'); DryRun = $false
|
||||||
|
Contains = @('ORCA_DEPS_CMAKE_ARGS', 'ORCA_SLICER_CMAKE_ARGS', 'ORCA_UPDATER_SIG_KEY', 'NINJA_STATUS',
|
||||||
|
'set ORCA_SLICER_CMAKE_ARGS=-DSLIC3R_PCH=OFF', '(PowerShell)', 'debugscript') }
|
||||||
|
@{ Name = 'section headers do not widen the flag column'; Args = @('--help'); DryRun = $false
|
||||||
|
Match = @('^ -d, --deps +Download') }
|
||||||
|
# Windows Terminal opens at 120 columns and wraps at 120, so 119 is the
|
||||||
|
# limit. Anyone still on the old conhost gets 80 and will see wrapping.
|
||||||
|
@{ Name = 'every help line fits a 120 column console'; Args = @('--help'); DryRun = $false
|
||||||
|
NotMatch = @('^.{120,}$') }
|
||||||
|
@{ Name = 'an unknown long option is rejected'; Args = @('--nonsense'); ExpectExit = 1
|
||||||
|
Contains = @('Failed to find arg') }
|
||||||
|
@{ Name = 'an unknown short option is rejected'; Args = @('-Z'); ExpectExit = 1
|
||||||
|
Contains = @('Failed to find arg') }
|
||||||
|
@{ Name = 'a bare argument is rejected'; Args = @('deps'); ExpectExit = 1
|
||||||
|
Contains = @('Unknown argument') }
|
||||||
|
@{ Name = 'an unknown architecture is rejected'; Args = @('-d', '--arch', 'sparc'); ExpectExit = 1
|
||||||
|
Contains = @('Unknown architecture') }
|
||||||
|
@{ Name = 'a string option without a value is rejected'; Args = @('-d', '--arch'); ExpectExit = 1
|
||||||
|
Contains = @('requires a value') }
|
||||||
|
@{ Name = 'short options can be bundled'; Args = @('-dx')
|
||||||
|
Contains = @('-G "Ninja Multi-Config"', '--target deps') }
|
||||||
|
|
||||||
|
'generator and compiler selection'
|
||||||
|
@{ Name = 'deps default to the Visual Studio generator'; Args = @('-d')
|
||||||
|
Contains = @('-G "Visual Studio', '-A x64', '--target deps')
|
||||||
|
NotContains = @('Ninja', 'clang-cl') }
|
||||||
|
@{ Name = '-x selects Ninja without changing compiler'; Args = @('-d', '-x')
|
||||||
|
Contains = @('-G "Ninja Multi-Config"')
|
||||||
|
NotContains = @('clang-cl', '-A x64') }
|
||||||
|
@{ Name = '-l -x builds with clang-cl under Ninja'; Args = @('-d', '-l', '-x')
|
||||||
|
Contains = @('-G "Ninja Multi-Config"', '-DCMAKE_C_COMPILER=clang-cl.exe', '-DCMAKE_CXX_COMPILER=clang-cl.exe') }
|
||||||
|
@{ Name = '-l alone uses the ClangCL toolset on the VS generator'; Args = @('-d', '-l')
|
||||||
|
Contains = @('-G "Visual Studio', '-T ClangCL')
|
||||||
|
NotContains = @('-DCMAKE_C_COMPILER') }
|
||||||
|
# --msvc and --msbuild name the defaults, so a caller can be explicit and a
|
||||||
|
# contradictory pair can be caught rather than silently resolved.
|
||||||
|
@{ Name = '--msvc is the compiler default spelled out'; Args = @('-d', '--msvc')
|
||||||
|
Contains = @('-G "Visual Studio', '-A x64')
|
||||||
|
NotContains = @('clang-cl') }
|
||||||
|
@{ Name = '--msbuild is the generator default spelled out'; Args = @('-d', '--msbuild')
|
||||||
|
Contains = @('-G "Visual Studio')
|
||||||
|
NotContains = @('Ninja') }
|
||||||
|
@{ Name = '--msvc with -x gives Ninja driving cl'; Args = @('-d', '--msvc', '-x')
|
||||||
|
Contains = @('-G "Ninja Multi-Config"')
|
||||||
|
NotContains = @('clang-cl') }
|
||||||
|
@{ Name = '--msbuild with -l gives the VS generator and the ClangCL toolset'; Args = @('-d', '--msbuild', '-l')
|
||||||
|
Contains = @('-G "Visual Studio', '-T ClangCL') }
|
||||||
|
# A developer with a standalone LLVM points at it; the VS-bundled clang
|
||||||
|
# is what a bare clang-cl.exe resolves to after the dev shell runs.
|
||||||
|
@{ Name = '--clang-path names the compiler, quoted for its spaces'; Args = @('-d', '-x', '--clang-path', 'C:\Program Files\LLVM\bin\clang-cl.exe')
|
||||||
|
Contains = @('-DCMAKE_C_COMPILER="C:\Program Files\LLVM\bin\clang-cl.exe"',
|
||||||
|
'-DCMAKE_CXX_COMPILER="C:\Program Files\LLVM\bin\clang-cl.exe"') }
|
||||||
|
@{ Name = '--clang-path is a clang request on its own'; Args = @('-d', '-x', '--clang-path', 'C:\Program Files\LLVM\bin\clang-cl.exe')
|
||||||
|
Contains = @('deps/build-clang') }
|
||||||
|
@{ Name = '--clang-path needs Ninja to take effect'; Args = @('-d', '--clang-path', 'C:\Program Files\LLVM\bin\clang-cl.exe'); ExpectExit = 1
|
||||||
|
Contains = @('needs the Ninja generator') }
|
||||||
|
# `exist` is true for a directory too, and a directory would reach CMake
|
||||||
|
# as the compiler.
|
||||||
|
@{ Name = '--clang-path must name the exe, not its folder'; Args = @('-d', '-x', '--clang-path', 'C:\Program Files\LLVM'); ExpectExit = 1
|
||||||
|
Contains = @('is a directory') }
|
||||||
|
@{ Name = 'a clang-cl that is not there is caught early'; Args = @('-d', '-x', '--clang-path', 'C:\nope\clang-cl.exe'); ExpectExit = 1
|
||||||
|
Contains = @('No clang-cl at')
|
||||||
|
NotContains = @('cmake -S deps') }
|
||||||
|
@{ Name = '--clang-path contradicting --msvc is rejected'; Args = @('-d', '-x', '--msvc', '--clang-path', 'C:\Program Files\LLVM\bin\clang-cl.exe'); ExpectExit = 1
|
||||||
|
Contains = @('select different compilers') }
|
||||||
|
@{ Name = '--clang-cl and --msvc together are rejected'; Args = @('-d', '-l', '--msvc'); ExpectExit = 1
|
||||||
|
Contains = @('select different compilers') }
|
||||||
|
@{ Name = '--ninja and --msbuild together are rejected'; Args = @('-d', '-x', '--msbuild'); ExpectExit = 1
|
||||||
|
Contains = @('select different generators') }
|
||||||
|
# One option with a value, driven by a table, rather than a flag per
|
||||||
|
# release. Adding a release should not need a new flag.
|
||||||
|
@{ Name = '--vs 2019 pins that release and skips autodetect'; Args = @('-d', '--vs', '2019')
|
||||||
|
Contains = @('-G "Visual Studio 16 2019"')
|
||||||
|
NotContains = @('Detecting Visual Studio') }
|
||||||
|
@{ Name = '--vs 2022 pins that release'; Args = @('-d', '--vs', '2022')
|
||||||
|
Contains = @('-G "Visual Studio 17 2022"') }
|
||||||
|
@{ Name = '--vs 2026 pins that release'; Args = @('-d', '--vs', '2026')
|
||||||
|
Contains = @('-G "Visual Studio 18 2026"') }
|
||||||
|
@{ Name = 'an unknown release is rejected and the known ones listed'; Args = @('-d', '--vs', '2015'); ExpectExit = 1
|
||||||
|
Contains = @('Unknown Visual Studio release', '2019, 2022, 2026') }
|
||||||
|
@{ Name = '--vs and --ninja together are rejected'; Args = @('-d', '--vs', '2022', '-x'); ExpectExit = 1
|
||||||
|
Contains = @('select different generators') }
|
||||||
|
@{ Name = 'without --vs the release is autodetected'; Args = @('-d')
|
||||||
|
Contains = @('Detecting Visual Studio') }
|
||||||
|
|
||||||
|
'architecture'
|
||||||
|
@{ Name = 'x64 is the default'; Args = @('-d')
|
||||||
|
Contains = @('Configuration: Release, x64')
|
||||||
|
NotContains = @('build-arm64') }
|
||||||
|
@{ Name = 'arm64 sets the generator platform and deps tree'; Args = @('-d', '--arch', 'arm64')
|
||||||
|
Contains = @('-A ARM64', 'deps/build-arm64') }
|
||||||
|
@{ Name = 'the architecture is matched case-insensitively'; Args = @('-d', '--arch', 'ARM64')
|
||||||
|
Contains = @('-A ARM64', 'deps/build-arm64') }
|
||||||
|
@{ Name = 'arm64 under Ninja has no -A but keeps the arm64 tree'; Args = @('-d', '--arch', 'arm64', '-x', '-l')
|
||||||
|
Contains = @('deps/build-clang-arm64', '-DCMAKE_C_COMPILER=clang-cl.exe')
|
||||||
|
NotContains = @('-A ') }
|
||||||
|
|
||||||
|
'build configurations'
|
||||||
|
# One option with a value, driven by a table, so Release is named rather
|
||||||
|
# than being whatever is left when no flag is passed.
|
||||||
|
@{ Name = 'release is the default'; Args = @('-s')
|
||||||
|
Contains = @('-DCMAKE_BUILD_TYPE=Release', 'cmake -B "build" ') }
|
||||||
|
@{ Name = '--config release is the default spelled out'; Args = @('-s', '--config', 'release')
|
||||||
|
Contains = @('-DCMAKE_BUILD_TYPE=Release', 'cmake -B "build" ') }
|
||||||
|
@{ Name = '--config debug builds into build-dbg'; Args = @('-s', '--config', 'debug')
|
||||||
|
Contains = @('-DCMAKE_BUILD_TYPE=Debug', 'cmake -B "build-dbg" ') }
|
||||||
|
@{ Name = '--config relwithdebinfo builds into build-dbginfo'; Args = @('-s', '--config', 'relwithdebinfo')
|
||||||
|
Contains = @('-DCMAKE_BUILD_TYPE=RelWithDebInfo', 'cmake -B "build-dbginfo" ') }
|
||||||
|
@{ Name = '--config minsizerel builds into build-minsize'; Args = @('-s', '--config', 'minsizerel')
|
||||||
|
Contains = @('-DCMAKE_BUILD_TYPE=MinSizeRel', 'cmake -B "build-minsize" ') }
|
||||||
|
# Batch variable names are case-insensitive, so the table lookup is too.
|
||||||
|
@{ Name = 'the configuration name is matched case-insensitively'; Args = @('-s', '--config', 'RelWithDebInfo')
|
||||||
|
Contains = @('-DCMAKE_BUILD_TYPE=RelWithDebInfo') }
|
||||||
|
@{ Name = 'an unknown configuration is rejected and the known ones listed'; Args = @('-s', '--config', 'bogus'); ExpectExit = 1
|
||||||
|
Contains = @('Unknown configuration', 'release, debug, relwithdebinfo, minsizerel') }
|
||||||
|
# Release, RelWithDebInfo and MinSizeRel all link the /MD dependencies, so
|
||||||
|
# one tree serves all three. Debug is /MDd and cannot share.
|
||||||
|
@{ Name = 'minsizerel builds against the release deps'; Args = @('-d', '-s', '--config', 'minsizerel')
|
||||||
|
Contains = @('cmake -S deps -B "deps/build"', 'cmake -B "build-minsize" ')
|
||||||
|
NotContains = @('deps/build-minsize') }
|
||||||
|
@{ Name = 'relwithdebinfo builds against them too'; Args = @('-d', '-s', '-l', '--config', 'relwithdebinfo')
|
||||||
|
Contains = @('cmake -S deps -B "deps/build-clang"', 'cmake -B "build-dbginfo-clang" ')
|
||||||
|
NotContains = @('deps/build-dbginfo') }
|
||||||
|
@{ Name = 'debug keeps a dependency tree of its own'; Args = @('-d', '--config', 'debug')
|
||||||
|
Contains = @('cmake -S deps -B "deps/build-dbg"') }
|
||||||
|
# The build tree and the deps tree now have different names, so the
|
||||||
|
# derivation from the binary directory name cannot work and it is named.
|
||||||
|
@{ Name = 'a shared deps tree is named outright'; Args = @('-s', '-l', '--config', 'relwithdebinfo')
|
||||||
|
Match = @('-DDEP_BUILD_DIR="[A-Za-z]:\\.*\\deps\\build-clang"') }
|
||||||
|
@{ Name = 'configuration and arch combine into build-dbg-arm64'; Args = @('-s', '--config', 'debug', '--arch', 'arm64')
|
||||||
|
Contains = @('cmake -B "build-dbg-arm64" ') }
|
||||||
|
|
||||||
|
'what gets built'
|
||||||
|
@{ Name = 'the deps target defaults to deps'; Args = @('-d')
|
||||||
|
Contains = @('--target deps') }
|
||||||
|
@{ Name = '-t overrides the deps target'; Args = @('-d', '-t', 'dep_Boost')
|
||||||
|
Contains = @('--target dep_Boost') }
|
||||||
|
@{ Name = 'unit tests are off unless asked for'; Args = @('-s')
|
||||||
|
Contains = @('-DBUILD_TESTS=OFF') }
|
||||||
|
@{ Name = '--tests turns the unit tests on'; Args = @('-s', '--tests')
|
||||||
|
Contains = @('-DBUILD_TESTS=ON') }
|
||||||
|
@{ Name = '-a enables ASAN for the slicer'; Args = @('-s', '-a')
|
||||||
|
Contains = @('-DSLIC3R_ASAN=ON') }
|
||||||
|
@{ Name = 'the slicer build runs gettext'; Args = @('-s')
|
||||||
|
Contains = @('run_gettext.bat') }
|
||||||
|
# tools\7z.exe needs a 7z.dll beside it, which the repo does not carry,
|
||||||
|
# so the pack falls back to the bsdtar Windows ships. Either is correct;
|
||||||
|
# what matters is that one is chosen and handed the right names.
|
||||||
|
@{ Name = '-p packs the deps tree with whichever archiver is usable'; Args = @('-d', '-p')
|
||||||
|
Match = @('^\+ .*(7z\.exe a|tar\.exe -a -c -f) OrcaSlicer_dep_win-\S+\.zip OrcaSlicer_dep$') }
|
||||||
|
# The bundle is only good for what built it, so it carries the same three
|
||||||
|
# axes as the tree. Release x64 on cl keeps the historical plain name.
|
||||||
|
@{ Name = 'the bundle name carries compiler and deps flavour'; Args = @('-p', '-l', '--config', 'debug', '--arch', 'arm64')
|
||||||
|
Contains = @('OrcaSlicer_dep_win-ARM64-clang-dbg_') }
|
||||||
|
@{ Name = 'a relwithdebinfo pack is the release bundle'; Args = @('-p', '--config', 'relwithdebinfo')
|
||||||
|
Contains = @('OrcaSlicer_dep_win-x64_')
|
||||||
|
NotContains = @('-dbg', 'dbginfo') }
|
||||||
|
@{ Name = 'a plain release bundle keeps its old name'; Args = @('-p')
|
||||||
|
Contains = @('OrcaSlicer_dep_win-x64_')
|
||||||
|
NotContains = @('-clang', '-Release') }
|
||||||
|
@{ Name = 'the bundle is stamped with today, not a shuffled date'; Args = @('-p')
|
||||||
|
Match = @($stampPattern) }
|
||||||
|
# powershell.exe is not in System32 itself, so a trimmed PATH used to
|
||||||
|
# leave the stamp empty and the bundle named OrcaSlicer_dep_win-x64_.zip.
|
||||||
|
@{ Name = 'the bundle is stamped even with a bare PATH'; Args = @('-p')
|
||||||
|
Env = @{ PATH = 'C:\Windows\system32;C:\Windows' }
|
||||||
|
Match = @($stampPattern) }
|
||||||
|
@{ Name = '-p packs without rebuilding'; Args = @('-p')
|
||||||
|
Match = @('^\+ .*(7z\.exe a|tar\.exe -a -c -f) ')
|
||||||
|
NotContains = @('cmake -S deps') }
|
||||||
|
@{ Name = 'deps and slicer build in one invocation'; Args = @('-d', '-s', '-x', '-l')
|
||||||
|
Contains = @('cmake -S deps', 'cmake -B "build-clang" ') }
|
||||||
|
|
||||||
|
'the developer loop'
|
||||||
|
@{ Name = '--slicer-target builds one target'; Args = @('-s', '--slicer-target', 'libslic3r')
|
||||||
|
Contains = @('--config Release --target libslic3r') }
|
||||||
|
@{ Name = '--no-configure skips the slicer configure'; Args = @('-s', '--no-configure')
|
||||||
|
Contains = @('cmake --build "build"')
|
||||||
|
NotContains = @('cmake -B "build" ') }
|
||||||
|
@{ Name = '--no-configure skips the deps configure'; Args = @('-d', '--no-configure')
|
||||||
|
Contains = @('cmake --build "deps/build"')
|
||||||
|
NotContains = @('cmake -S deps') }
|
||||||
|
@{ Name = '--no-gettext skips the translation step'; Args = @('-s', '--no-gettext')
|
||||||
|
Contains = @('cmake --build "build"')
|
||||||
|
NotContains = @('run_gettext') }
|
||||||
|
# Installing copies the whole tree again for a layout only releases need,
|
||||||
|
# so it is asked for rather than assumed.
|
||||||
|
@{ Name = 'nothing is installed unless asked'; Args = @('-s')
|
||||||
|
Contains = @('run_gettext')
|
||||||
|
NotContains = @('--target install') }
|
||||||
|
@{ Name = '-i adds the install step'; Args = @('-s', '-i')
|
||||||
|
Contains = @('--target install') }
|
||||||
|
# install(TARGETS OrcaSlicer) has no OPTIONAL, so this would fail partway
|
||||||
|
# through a build instead of before it.
|
||||||
|
@{ Name = 'installing a tree with no executable is refused'; Args = @('-s', '-i', '--slicer-target', 'glad'); ExpectExit = 1
|
||||||
|
Contains = @('--install needs the executable')
|
||||||
|
NotContains = @('cmake --build') }
|
||||||
|
# Without -s there is no install step, so there is nothing to refuse.
|
||||||
|
@{ Name = 'the same flags without a slicer build are left alone'; Args = @('-d', '-i', '--slicer-target', 'glad')
|
||||||
|
Contains = @('cmake -S deps')
|
||||||
|
NotContains = @('--install needs the executable') }
|
||||||
|
@{ Name = 'naming the executable target is allowed'; Args = @('-s', '-i', '--slicer-target', 'OrcaSlicer')
|
||||||
|
Contains = @('--target install') }
|
||||||
|
# Ninja counts compilers, so -j goes on the command line. MSBuild's -j
|
||||||
|
# counts projects while /MP still runs one cl per core inside each, so the
|
||||||
|
# cap goes to CL_MPCount in the environment instead. A dry run can only
|
||||||
|
# show that no -j is passed.
|
||||||
|
@{ Name = '-j on Ninja passes -j to cmake'; Args = @('-s', '-x', '-j', '4')
|
||||||
|
Contains = @('Parallel jobs: 4', '--config Release -j 4') }
|
||||||
|
@{ Name = '-j on Ninja reaches the deps build'; Args = @('-d', '-x', '-j', '2')
|
||||||
|
Contains = @('--target deps -j 2') }
|
||||||
|
@{ Name = '-j on MSBuild does not pass -j, which would count projects'; Args = @('-s', '-j', '4')
|
||||||
|
Contains = @('Parallel jobs: 4')
|
||||||
|
NotContains = @('-j 4') }
|
||||||
|
@{ Name = '-j on MSBuild leaves the deps build without -j too'; Args = @('-d', '-j', '2')
|
||||||
|
Contains = @('--target deps')
|
||||||
|
NotContains = @('-j 2') }
|
||||||
|
@{ Name = 'no -j means no job limit'; Args = @('-s')
|
||||||
|
NotContains = @('parallel jobs', '-j ') }
|
||||||
|
@{ Name = 'a non-numeric -j is rejected'; Args = @('-s', '-j', 'abc'); ExpectExit = 1
|
||||||
|
Contains = @('Invalid --jobs value') }
|
||||||
|
@{ Name = 'a zero -j is rejected'; Args = @('-s', '-j', '0'); ExpectExit = 1
|
||||||
|
Contains = @('Invalid --jobs value') }
|
||||||
|
@{ Name = 'the loop options combine into a single build command'; Args = @('-s', '-x', '--no-configure', '--no-gettext', '--slicer-target', 'libslic3r_tests', '-j', '8')
|
||||||
|
Contains = @('--target libslic3r_tests -j 8')
|
||||||
|
NotContains = @('cmake -B "build" ', 'run_gettext', '--target install') }
|
||||||
|
|
||||||
|
'one tree per configuration, compiler and architecture'
|
||||||
|
# CMake resets its cache and carries on when the compiler changes under an
|
||||||
|
# existing tree, leaving stamps from the old toolchain. Give each its own.
|
||||||
|
@{ Name = 'clang builds land in their own trees'; Args = @('-d', '-s', '-l')
|
||||||
|
Contains = @('cmake -S deps -B "deps/build-clang"', 'cmake -B "build-clang" ') }
|
||||||
|
@{ Name = 'MSVC keeps the historical plain names'; Args = @('-d', '-s', '--msvc')
|
||||||
|
Contains = @('cmake -S deps -B "deps/build"', 'cmake -B "build" ')
|
||||||
|
NotContains = @('build-clang') }
|
||||||
|
@{ Name = 'configuration, compiler and arch all name the tree'; Args = @('-d', '-s', '-l', '--config', 'debug', '--arch', 'arm64')
|
||||||
|
Contains = @('cmake -S deps -B "deps/build-dbg-clang-arm64"', 'cmake -B "build-dbg-clang-arm64" ') }
|
||||||
|
|
||||||
|
'locating the dependency tree'
|
||||||
|
# Without --deps-dir nothing is passed, so CMakeLists derives the path from
|
||||||
|
# the binary directory name as it always has.
|
||||||
|
@{ Name = 'the deps path is left to CMake by default'; Args = @('-s')
|
||||||
|
NotContains = @('-DDEP_BUILD_DIR') }
|
||||||
|
@{ Name = '--deps-dir tells the slicer where the deps are'; Args = @('-s', '--deps-dir', 'D:\orca-deps')
|
||||||
|
Contains = @('-DDEP_BUILD_DIR="D:\orca-deps"') }
|
||||||
|
@{ Name = '--deps-dir also redirects the deps build'; Args = @('-d', '--deps-dir', 'D:\orca-deps')
|
||||||
|
Contains = @('cmake -S deps -B "D:\orca-deps"', 'cmake --build "D:\orca-deps"')
|
||||||
|
NotContains = @('deps/build') }
|
||||||
|
# Paths are quoted throughout, so one with spaces survives the whole run.
|
||||||
|
@{ Name = 'a deps path with spaces survives'; Args = @('-d', '-s', '--deps-dir', 'C:\Program Files\deps')
|
||||||
|
Contains = @('-B "C:\Program Files\deps"', '-DDEP_BUILD_DIR="C:\Program Files\deps"') }
|
||||||
|
@{ Name = '--deps-dir also redirects the pack'; Args = @('-p', '--deps-dir', 'D:\orca-deps')
|
||||||
|
Contains = @('cd /d "D:\orca-deps"') }
|
||||||
|
@{ Name = 'pack uses an absolute default path'; Args = @('-p')
|
||||||
|
Match = @('^\+ cd /d "[A-Za-z]:\\.*\\deps\\build"') }
|
||||||
|
# CMakeLists derives DEP_BUILD_DIR from the build directory's name, so
|
||||||
|
# pointing the build elsewhere has to name the deps tree outright.
|
||||||
|
@{ Name = '--build-dir moves the slicer build'; Args = @('-s', '-l', '-x', '--build-dir', 'out/build/x64-clang')
|
||||||
|
Contains = @('cmake -B "out/build/x64-clang" ') }
|
||||||
|
@{ Name = '--build-dir still names the deps tree'; Args = @('-s', '-l', '-x', '--build-dir', 'out/build/x64-clang')
|
||||||
|
Match = @('-DDEP_BUILD_DIR="[A-Za-z]:\\.*\\deps\\build-clang"') }
|
||||||
|
@{ Name = '--deps-dir wins over the derived tree'; Args = @('-s', '--build-dir', 'out/build/x64-clang', '--deps-dir', 'D:\orca-deps')
|
||||||
|
Contains = @('-DDEP_BUILD_DIR="D:\orca-deps"') }
|
||||||
|
# A value ending in a backslash escapes the closing quote it is spliced
|
||||||
|
# into, so cmake would receive D:\tree" and swallow the next argument.
|
||||||
|
@{ Name = 'a trailing backslash is trimmed off a path'; Args = @('-s', '--build-dir', 'D:\tree\')
|
||||||
|
Contains = @('cmake -B "D:\tree" ') }
|
||||||
|
@{ Name = 'a build directory with spaces survives'; Args = @('-s', '--build-dir', 'C:\Program Files\tree')
|
||||||
|
Contains = @('cmake -B "C:\Program Files\tree" ') }
|
||||||
|
@{ Name = 'the default build names no deps tree'; Args = @('-s')
|
||||||
|
NotContains = @('DEP_BUILD_DIR') }
|
||||||
|
|
||||||
|
'saying what mode and toolchain are in play'
|
||||||
|
@{ Name = 'a dry run announces itself before the first command'; Args = @('-u')
|
||||||
|
First = '^Dry run: printing commands without running them\.$' }
|
||||||
|
@{ Name = 'the announcement leads even a full build'; Args = @('-ds')
|
||||||
|
First = '^Dry run: ' }
|
||||||
|
# Autodetect only runs for the Visual Studio generator, and only when no
|
||||||
|
# release was pinned, so it needs a Visual Studio on the machine.
|
||||||
|
@{ Name = 'the detected Visual Studio names its release year'; Args = @('-s')
|
||||||
|
Match = @('^Detected Visual Studio \d+ \(20\d\d\)$') }
|
||||||
|
@{ Name = 'pinning a release skips detection'; Args = @('-s', '--vs', '2022')
|
||||||
|
NotContains = @('Detected Visual Studio') }
|
||||||
|
|
||||||
|
'an action has to be asked for'
|
||||||
|
# Neither can happen without building the slicer, so they stand alone the
|
||||||
|
# way --install-vs does.
|
||||||
|
@{ Name = '--run-tests is an action on its own'; Args = @('--run-tests')
|
||||||
|
Contains = @('-DBUILD_TESTS=ON', 'ctest --test-dir')
|
||||||
|
NotContains = @('Nothing to do') }
|
||||||
|
@{ Name = '--tests is too'; Args = @('--tests')
|
||||||
|
Contains = @('-DBUILD_TESTS=ON')
|
||||||
|
NotContains = @('Nothing to do', 'ctest --test-dir') }
|
||||||
|
# Naming an action means that action, not a fuller build.
|
||||||
|
@{ Name = 'they do not add a slicer build to one already asked for'; Args = @('-d', '--tests')
|
||||||
|
Contains = @('cmake -S deps')
|
||||||
|
NotContains = @('cmake -B "build"') }
|
||||||
|
@{ Name = 'shaping options alone are not an action'; Args = @('--config', 'debug'); ExpectExit = 1
|
||||||
|
Contains = @('Nothing to do.')
|
||||||
|
NotContains = @('Build completed') }
|
||||||
|
@{ Name = '-j alone is not an action either'; Args = @('-j', '4'); ExpectExit = 1
|
||||||
|
Contains = @('Nothing to do.') }
|
||||||
|
@{ Name = '--install-vs counts as an action on its own'; Args = @('--install-vs', 'ide')
|
||||||
|
NotContains = @('Nothing to do.') }
|
||||||
|
|
||||||
|
'diagnostics'
|
||||||
|
@{ Name = '-v asks cmake for the command lines'; Args = @('-s', '-v')
|
||||||
|
Contains = @('--verbose') }
|
||||||
|
@{ Name = '-v applies to the deps build too'; Args = @('-d', '-v')
|
||||||
|
Contains = @('--target deps', '--verbose') }
|
||||||
|
# ninja's own default shows neither a percentage nor a time. %w and %W
|
||||||
|
# need ninja 1.12, and an unknown placeholder is fatal, so the format is
|
||||||
|
# chosen from the version rather than hardcoded.
|
||||||
|
@{ Name = '-v names the ninja progress format'; Args = @('-s', '-x', '-v')
|
||||||
|
Match = @('^Ninja progress format: \[.*%p.*\]') }
|
||||||
|
@{ Name = 'a ninja older than 1.12 gets the format it understands'; Args = @('-s', '-x', '-v')
|
||||||
|
Env = @{ PATH = $ninjaPaths['old'] }
|
||||||
|
Contains = @('Ninja progress format: [%s/%t %p :: %e]') }
|
||||||
|
@{ Name = 'ninja 1.12 gets elapsed and remaining'; Args = @('-s', '-x', '-v')
|
||||||
|
Env = @{ PATH = $ninjaPaths['new'] }
|
||||||
|
Contains = @('Ninja progress format: [%f/%t %p :: %w / %W]') }
|
||||||
|
@{ Name = 'a format you set yourself is left alone'; Args = @('-s', '-x', '-v')
|
||||||
|
Env = @{ NINJA_STATUS = '[mine] ' }
|
||||||
|
Contains = @('Ninja progress format: [mine]') }
|
||||||
|
@{ Name = 'MSBuild builds mention no ninja format'; Args = @('-s', '-v')
|
||||||
|
NotContains = @('Ninja progress format') }
|
||||||
|
@{ Name = 'builds are quiet without -v'; Args = @('-s')
|
||||||
|
NotContains = @('--verbose') }
|
||||||
|
|
||||||
|
'installing prerequisites'
|
||||||
|
# -u installs CMake, Perl and Git. Visual Studio is a separate ask, because
|
||||||
|
# most people already have it, and which one you want is a real choice.
|
||||||
|
@{ Name = '-u alone installs no Visual Studio'; Args = @('-u')
|
||||||
|
Contains = @('Kitware.CMake', 'StrawberryPerl', 'Git.Git')
|
||||||
|
NotContains = @('Microsoft.VisualStudio') }
|
||||||
|
# Asking for Visual Studio is asking to install prerequisites, so it does
|
||||||
|
# not also need -u; requiring both meant --install-vs alone did nothing.
|
||||||
|
@{ Name = '--install-vs works without -u'; Args = @('--install-vs', 'ide')
|
||||||
|
Contains = @('id=Microsoft.VisualStudio.Community', 'Kitware.CMake', 'Git.Git') }
|
||||||
|
@{ Name = '--install-vs buildtools asks for the build tools'; Args = @('-u', '--install-vs', 'buildtools')
|
||||||
|
Contains = @('id=Microsoft.VisualStudio.BuildTools')
|
||||||
|
NotContains = @('VC.CoreIde') }
|
||||||
|
@{ Name = '--install-vs ide asks for Community with the IDE component'; Args = @('-u', '--install-vs', 'ide')
|
||||||
|
Contains = @('id=Microsoft.VisualStudio.Community', 'VC.CoreIde') }
|
||||||
|
@{ Name = 'an unknown edition is rejected and the known ones listed'; Args = @('-u', '--install-vs', 'bogus'); ExpectExit = 1
|
||||||
|
Contains = @('Unknown Visual Studio edition', 'buildtools, ide') }
|
||||||
|
@{ Name = '--vs picks which release to install'; Args = @('-u', '--vs', '2022', '--install-vs', 'buildtools')
|
||||||
|
Contains = @('id=Microsoft.VisualStudio.2022.BuildTools') }
|
||||||
|
@{ Name = '-l adds the clang compiler and the MSBuild toolset'; Args = @('-u', '--install-vs', 'buildtools', '-l')
|
||||||
|
Contains = @('VC.Llvm.Clang ', 'VC.Llvm.ClangToolset') }
|
||||||
|
# CMake 4.x breaks Boost.Context on ARM64, so the installer pins 3.31 there
|
||||||
|
# and leaves x64 on the current release.
|
||||||
|
@{ Name = 'CMake is pinned to 3.31 when installing for arm64'; Args = @('-u', '--arch', 'arm64')
|
||||||
|
Contains = @('Kitware.CMake --version 3.31.8') }
|
||||||
|
@{ Name = 'CMake is not pinned for x64'; Args = @('-u', '--arch', 'x64')
|
||||||
|
Contains = @('Kitware.CMake')
|
||||||
|
NotContains = @('--version') }
|
||||||
|
@{ Name = 'installing for arm64 asks for the ARM64 toolset'; Args = @('--install-vs', 'buildtools', '--arch', 'arm64')
|
||||||
|
Contains = @('Microsoft.VisualStudio.Component.VC.Tools.ARM64') }
|
||||||
|
@{ Name = 'an x64 install asks only for the x64 toolset'; Args = @('--install-vs', 'buildtools')
|
||||||
|
Contains = @('Microsoft.VisualStudio.Component.VC.Tools.x86.x64')
|
||||||
|
NotContains = @('VC.Tools.ARM64') }
|
||||||
|
|
||||||
|
'dry run changes nothing'
|
||||||
|
# clean_tree resolves the path before removing it, so the line echoed is
|
||||||
|
# absolute. It is the last thing printed before a directory goes.
|
||||||
|
@{ Name = '-c echoes the deps rmdir rather than running it'; Args = @('-d', '-c')
|
||||||
|
Match = @('^\+ rmdir /S /Q "[A-Za-z]:\\.*\\deps\\build"$') }
|
||||||
|
@{ Name = '-c removes the tree --deps-dir named, not the default one'; Args = @('-d', '-c', '--deps-dir', 'D:\orca-deps')
|
||||||
|
Match = @('^\+ rmdir /S /Q "D:\\orca-deps"$')
|
||||||
|
NotContains = @('\deps\build') }
|
||||||
|
@{ Name = '-c echoes the slicer rmdir rather than running it'; Args = @('-s', '-c')
|
||||||
|
Match = @('^\+ rmdir /S /Q "[A-Za-z]:\\.*\\build"$') }
|
||||||
|
@{ Name = 'cleaning a slicer build leaves the deps tree alone'; Args = @('-s', '-c')
|
||||||
|
NotMatch = @('rmdir.*\\deps\\') }
|
||||||
|
@{ Name = 'cleaning both builds removes both trees'; Args = @('-d', '-s', '-c')
|
||||||
|
Match = @('^\+ rmdir /S /Q "[A-Za-z]:\\[^"]*\\deps\\build"$',
|
||||||
|
'^\+ rmdir /S /Q "[A-Za-z]:\\(?!.*\\deps\\)[^"]*\\build"$') }
|
||||||
|
|
||||||
|
# Nothing below should be reachable. They are a floor under a bug that
|
||||||
|
# hands clean_tree a path far shorter than it looks.
|
||||||
|
@{ Name = 'a configuration whose lookup comes back empty is rejected'; Args = @('-d', '-c', '--config', 'release '); ExpectExit = 1
|
||||||
|
Contains = @('Unknown configuration')
|
||||||
|
NotContains = @('rmdir') }
|
||||||
|
@{ Name = 'a drive root is refused'; Args = @('-d', '-c', '--deps-dir', 'D:\'); ExpectExit = 1
|
||||||
|
Contains = @('that is a drive root')
|
||||||
|
NotContains = @('+ rmdir') }
|
||||||
|
@{ Name = 'the repository itself is refused'; Args = @('-d', '-c', '--deps-dir', '.'); ExpectExit = 1
|
||||||
|
Contains = @('that is the repository itself')
|
||||||
|
NotContains = @('+ rmdir') }
|
||||||
|
@{ Name = '-k echoes the taskkills rather than running them'; Args = @('-k')
|
||||||
|
Contains = @('+ taskkill /F /IM MSBuild.exe', '+ taskkill /F /IM cl.exe') }
|
||||||
|
@{ Name = '-k also covers the Ninja toolchain'; Args = @('-k')
|
||||||
|
Contains = @('+ taskkill /F /IM ninja.exe', '+ taskkill /F /IM clang-cl.exe') }
|
||||||
|
@{ Name = '-k announces the dry run like every other action'; Args = @('-k')
|
||||||
|
First = '^Dry run: ' }
|
||||||
|
@{ Name = '-u echoes the winget installs rather than running them'; Args = @('-u')
|
||||||
|
Contains = @('+ winget install', 'Kitware.CMake')
|
||||||
|
NotContains = @('cmake -S deps') }
|
||||||
|
# Not a dry run: winget is a stub that fails, so nothing is installed.
|
||||||
|
@{ Name = 'a failed install is reported, not claimed as success'; Args = @('-u')
|
||||||
|
DryRun = $false; ExpectExit = 1
|
||||||
|
Env = @{ PATH = $stubPath }
|
||||||
|
Contains = @('Failed to install:', 'CMake', 'Perl', 'Git')
|
||||||
|
NotContains = @('Installed the prerequisites') }
|
||||||
|
# The reason belongs inside the frame. Every command this path ran had
|
||||||
|
# already succeeded, so naming the last one would point at the wrong thing.
|
||||||
|
@{ Name = 'a failed install names the reason, not the last command'; Args = @('-u')
|
||||||
|
DryRun = $false; ExpectExit = 1
|
||||||
|
Env = @{ PATH = $stubPath }
|
||||||
|
Contains = @('####', 'Failed to install:')
|
||||||
|
NotContains = @('Failed: winget') }
|
||||||
|
@{ Name = 'a dry run does not claim the install happened'; Args = @('-u')
|
||||||
|
Contains = @('Dry run: nothing was installed.')
|
||||||
|
NotContains = @('are in place') }
|
||||||
|
@{ Name = '-u with a build defers that build, not a fuller one'; Args = @('-u', '-d')
|
||||||
|
Contains = @('build_win.bat -d')
|
||||||
|
NotContains = @('build_win.bat -ds') }
|
||||||
|
@{ Name = '-u on its own suggests the whole build'; Args = @('-u')
|
||||||
|
Contains = @('build_win.bat -ds') }
|
||||||
|
@{ Name = 'a dry run only echoes, it never configures'; Args = @('-d')
|
||||||
|
Contains = @('+ cmake')
|
||||||
|
NotContains = @('CMake Error', 'Configuring done') }
|
||||||
|
|
||||||
|
'extra configure arguments'
|
||||||
|
# --deps-args and --slicer-args are declared "rawstring", so a value that
|
||||||
|
# looks like an option is allowed through. Plain string options still
|
||||||
|
# reject one, since there it almost always means a forgotten value.
|
||||||
|
@{ Name = '--deps-args reaches the deps configure'; Args = @('-d', '--deps-args', 'FOO')
|
||||||
|
Contains = @('-DCMAKE_BUILD_TYPE=Release FOO') }
|
||||||
|
@{ Name = '--slicer-args reaches the slicer configure'; Args = @('-s', '--slicer-args', 'BAZ')
|
||||||
|
Contains = @('BAZ') }
|
||||||
|
@{ Name = '--deps-args accepts a value that starts with a dash'; Args = @('-d', '--deps-args', '-DFOO')
|
||||||
|
Contains = @('-DFOO') }
|
||||||
|
@{ Name = 'a plain string option still rejects a dash-leading value'; Args = @('-d', '--deps-target', '--tests'); ExpectExit = 1
|
||||||
|
Contains = @('looks like another option') }
|
||||||
|
# cmd splits arguments on "=" as well as spaces, so an unquoted -D reaches
|
||||||
|
# the script as two arguments however it was invoked.
|
||||||
|
@{ Name = 'an unquoted value containing = is rejected'; Args = @('-d', '--deps-args', 'FOO=BAR'); ExpectExit = 1
|
||||||
|
Contains = @('Unknown argument') }
|
||||||
|
# The environment overrides exist for that reason; nothing tokenises them.
|
||||||
|
@{ Name = 'ORCA_DEPS_CMAKE_ARGS reaches the deps configure'; Args = @('-d')
|
||||||
|
Env = @{ ORCA_DEPS_CMAKE_ARGS = '-DFOO=BAR -DBAZ=QUX' }
|
||||||
|
Contains = @('-DFOO=BAR -DBAZ=QUX') }
|
||||||
|
@{ Name = 'ORCA_SLICER_CMAKE_ARGS reaches the slicer configure'; Args = @('-s')
|
||||||
|
Env = @{ ORCA_SLICER_CMAKE_ARGS = '-DWANTED=1' }
|
||||||
|
Contains = @('-DWANTED=1') }
|
||||||
|
@{ Name = 'the deps override does not leak into the slicer configure'; Args = @('-s')
|
||||||
|
Env = @{ ORCA_DEPS_CMAKE_ARGS = '-DDEPSONLY=1' }
|
||||||
|
NotContains = @('-DDEPSONLY=1') }
|
||||||
|
@{ Name = 'the help points at the environment for a spaced argument'; Args = @('--help'); DryRun = $false
|
||||||
|
Contains = @('Neither form supports a value containing an ampersand') }
|
||||||
|
@{ Name = 'the help lists the environment overrides'; Args = @('--help'); DryRun = $false
|
||||||
|
Contains = @('Environment:', 'ORCA_DEPS_CMAKE_ARGS', 'ORCA_SLICER_CMAKE_ARGS') }
|
||||||
|
|
||||||
|
'running the unit tests'
|
||||||
|
@{ Name = '--tests builds them without running them'; Args = @('-s', '--tests')
|
||||||
|
Contains = @('-DBUILD_TESTS=ON')
|
||||||
|
NotContains = @('ctest') }
|
||||||
|
@{ Name = '--run-tests builds and runs them'; Args = @('-s', '--run-tests')
|
||||||
|
Contains = @('-DBUILD_TESTS=ON', 'ctest --test-dir "build/tests" -C Release --output-on-failure') }
|
||||||
|
@{ Name = '--run-tests follows the build type and directory'; Args = @('-s', '--run-tests', '--config', 'debug')
|
||||||
|
Contains = @('ctest --test-dir "build-dbg/tests" -C Debug') }
|
||||||
|
@{ Name = 'no tests are run by default'; Args = @('-s')
|
||||||
|
NotContains = @('ctest') }
|
||||||
|
|
||||||
|
'failures are reported'
|
||||||
|
@{ Name = 'a missing cmake is caught and exits non-zero'; Args = @('-d'); ExpectExit = 1
|
||||||
|
Env = @{ PATH = 'C:\Windows\system32;C:\Windows' }
|
||||||
|
Contains = @('CMake was not found') }
|
||||||
|
@{ Name = 'packing does not need cmake, only an archiver'; Args = @('-p')
|
||||||
|
Env = @{ PATH = 'C:\Windows\system32;C:\Windows' }
|
||||||
|
NotContains = @('CMake was not found') }
|
||||||
|
# Not a dry run: a cd to a missing drive is a real failure inside a
|
||||||
|
# parenthesised block, which is where exit /b silently loses its code.
|
||||||
|
# Without the jump to :die this exits 0 and a failed build reads as a
|
||||||
|
# successful one.
|
||||||
|
@{ Name = 'a failure inside a build block reaches the caller'; Args = @('-p', '--deps-dir', 'Z:\nope')
|
||||||
|
DryRun = $false; ExpectExit = 1
|
||||||
|
Contains = @('Exit code 1.', '####')
|
||||||
|
NotContains = @('Build completed', 'Try') }
|
||||||
|
# The retry follows the stage that failed. Offering it for the whole run
|
||||||
|
# would clean a dependency tree that was not at fault.
|
||||||
|
@{ Name = 'a failure names a retry scoped to the stage that failed'; Args = @('-d', '-s', '--deps-dir', 'Z:\nope')
|
||||||
|
DryRun = $false; ExpectExit = 1
|
||||||
|
Contains = @('build_win.bat -d --deps-dir "Z:\nope" -c')
|
||||||
|
NotContains = @('build_win.bat -ds') }
|
||||||
|
# CMake's own failure here is hundreds of lines about package resolution.
|
||||||
|
@{ Name = 'a missing dependency tree is named, not left to CMake'; Args = @('-s', '--deps-dir', 'Z:\nope')
|
||||||
|
DryRun = $false; ExpectExit = 1
|
||||||
|
Contains = @('Dependencies not found at', 'Build them with build_win.bat -d --deps-dir "Z:\nope"')
|
||||||
|
NotContains = @('cmake -B', 'Try') }
|
||||||
|
# Every other suggestion carries the flags that reproduce the run; a bare
|
||||||
|
# -d would point at the MSVC tree after a clang build.
|
||||||
|
@{ Name = 'the missing-deps hint names this toolchain'; Args = @('-s', '-l', '-x', '--deps-dir', 'deps/not-built')
|
||||||
|
DryRun = $false; ExpectExit = 1
|
||||||
|
Contains = @('Build them with build_win.bat -d -l -x --deps-dir "deps/not-built"')
|
||||||
|
NotExists = @('deps/not-built') }
|
||||||
|
# A dry run configures nothing, so it must not depend on which trees happen
|
||||||
|
# to exist on the machine running the suite.
|
||||||
|
@{ Name = 'a dry run does not check for the deps tree'; Args = @('-s', '--deps-dir', 'Z:\nope')
|
||||||
|
Contains = @('cmake -B "build"')
|
||||||
|
NotContains = @('Dependencies not found') }
|
||||||
|
# -d is about to build them, so there is nothing to report yet.
|
||||||
|
@{ Name = 'building the deps in the same run skips the check'; Args = @('-d', '-s', '--deps-dir', 'Z:\nope')
|
||||||
|
DryRun = $false; ExpectExit = 1
|
||||||
|
NotContains = @('Dependencies not found') }
|
||||||
|
# A configure fails before any compiler runs, so -v has nothing to show.
|
||||||
|
@{ Name = 'a configure failure is not offered a verbose rebuild'; Args = @('-d', '--deps-dir', 'Z:\nope')
|
||||||
|
DryRun = $false; ExpectExit = 1
|
||||||
|
Contains = @('-c discard that tree')
|
||||||
|
NotContains = @('-v show the failing') }
|
||||||
|
# A bare --no-configure succeeds on a machine that already has a usable
|
||||||
|
# build tree, so name one that cannot exist instead.
|
||||||
|
@{ Name = 'a build failure is'; Args = @('-s', '--no-configure', '--build-dir', 'deps/no-such-tree')
|
||||||
|
DryRun = $false; ExpectExit = 1
|
||||||
|
Contains = @('-v show the failing')
|
||||||
|
NotExists = @('deps/no-such-tree') }
|
||||||
|
# --build-dir names the slicer tree, which a deps failure has nothing to do
|
||||||
|
# with. --deps-dir stays, because that is the tree that failed.
|
||||||
|
@{ Name = 'a deps retry leaves out the slicer tree'; Args = @('-d', '-s', '--deps-dir', 'Z:\nope', '--build-dir', 'D:\b')
|
||||||
|
DryRun = $false; ExpectExit = 1
|
||||||
|
Contains = @('build_win.bat -d --deps-dir "Z:\nope" -c')
|
||||||
|
NotContains = @('--build-dir') }
|
||||||
|
@{ Name = 'an unknown configuration stays a single line'; Args = @('-s', '--config', 'bogus'); ExpectExit = 1
|
||||||
|
Contains = @('Unknown configuration')
|
||||||
|
NotContains = @('####', 'Try') }
|
||||||
|
@{ Name = 'a bad --jobs value is not framed either'; Args = @('-s', '-j', 'x'); ExpectExit = 1
|
||||||
|
Contains = @('Invalid --jobs value')
|
||||||
|
NotContains = @('####') }
|
||||||
|
|
||||||
|
'the summary says what to do next'
|
||||||
|
# Every suggested command carries the flags that reproduce this run.
|
||||||
|
@{ Name = 'a deps build points at the slicer build'; Args = @('-d', '-l')
|
||||||
|
Contains = @('Build the slicer build_win.bat -s -l')
|
||||||
|
NotContains = @('Run it') }
|
||||||
|
@{ Name = 'a ninja slicer build offers a single target'; Args = @('-s', '-l', '-x')
|
||||||
|
Contains = @('Rebuild after edits build_win.bat -s -l -x --no-configure', 'Rebuild one target')
|
||||||
|
NotContains = @('Solution', 'Open in Visual Studio') }
|
||||||
|
@{ Name = 'a visual studio build names the solution instead'; Args = @('-s')
|
||||||
|
Contains = @('Solution ', 'Open in Visual Studio build\OrcaSlicer.sln',
|
||||||
|
'Rebuild after edits build_win.bat -s --no-configure')
|
||||||
|
NotContains = @('Rebuild one target') }
|
||||||
|
@{ Name = 'the configuration and architecture come back'; Args = @('-s', '-l', '-x', '--config', 'debug', '--arch', 'arm64')
|
||||||
|
Contains = @('build_win.bat -s -l -x --config debug --arch arm64 --no-configure') }
|
||||||
|
@{ Name = 'the tree overrides come back quoted'; Args = @('-s', '--deps-dir', 'D:\d', '--build-dir', 'D:\b')
|
||||||
|
Contains = @('--deps-dir "D:\d" --build-dir "D:\b"') }
|
||||||
|
@{ Name = 'a pinned visual studio release comes back'; Args = @('-s', '--vs', '2022')
|
||||||
|
Contains = @('build_win.bat -s --vs 2022 --no-configure') }
|
||||||
|
# Autodetection writes what it found into the same variable, so a detected
|
||||||
|
# release must not come back as though it had been asked for.
|
||||||
|
@{ Name = 'a detected release does not'; Args = @('-s')
|
||||||
|
NotContains = @('--vs') }
|
||||||
|
@{ Name = 'the binary is named in the build tree it was built in'; Args = @('-s', '-l', '-x')
|
||||||
|
Contains = @('build-clang\src\Release\orca-slicer.exe') }
|
||||||
|
@{ Name = 'installing names the installed copy instead'; Args = @('-s', '-l', '-x', '-i')
|
||||||
|
Contains = @('build-clang\OrcaSlicer\orca-slicer.exe') }
|
||||||
|
# -i changes where the binary lands, so a rebuild that dropped it would
|
||||||
|
# leave the path above pointing at a stale copy.
|
||||||
|
@{ Name = 'the rebuild suggestion keeps -i'; Args = @('-s', '-l', '-x', '-i')
|
||||||
|
Contains = @('Rebuild after edits build_win.bat -s -l -x -i --no-configure') }
|
||||||
|
# A deps retry has no install step to repeat.
|
||||||
|
@{ Name = 'a deps retry drops it'; Args = @('-d', '-s', '-i', '--deps-dir', 'Z:\nope')
|
||||||
|
DryRun = $false; ExpectExit = 1
|
||||||
|
Contains = @('build_win.bat -d --deps-dir "Z:\nope" -c')
|
||||||
|
NotContains = @('-d -i') }
|
||||||
|
# Naming a target builds it and its dependencies, not its dependents, so
|
||||||
|
# the binary on disk is whatever the last full build left there.
|
||||||
|
@{ Name = 'a single-target build does not claim the whole binary'; Args = @('-s', '-l', '-x', '--slicer-target', 'glad')
|
||||||
|
Contains = @('Target glad', 'Relink the binary build_win.bat -s -l -x --no-configure')
|
||||||
|
NotContains = @('Run it', 'orca-slicer.exe', 'Rebuild after edits') }
|
||||||
|
# The executable has a target of its own, and naming that one does relink.
|
||||||
|
@{ Name = 'naming the executable target still claims the binary'; Args = @('-s', '-l', '-x', '--slicer-target', 'OrcaSlicer')
|
||||||
|
Contains = @('Run it', 'orca-slicer.exe', 'Rebuild after edits')
|
||||||
|
NotContains = @('Target OrcaSlicer', 'Relink the binary') }
|
||||||
|
@{ Name = '--run-tests offers the ctest line'; Args = @('-s', '-l', '-x', '--run-tests')
|
||||||
|
Contains = @('Re-run the tests ctest --test-dir build-clang/tests -C Release') }
|
||||||
|
@{ Name = 'packing names the bundle and how to use it'; Args = @('-p', '-l')
|
||||||
|
Contains = @('Bundle ', 'Share the bundle') }
|
||||||
|
# The line supplies its own tree, so the one this run used must not ride
|
||||||
|
# along and contradict it.
|
||||||
|
@{ Name = 'the bundle line names one tree, not two'; Args = @('-s', '-p', '-l', '-x', '--deps-dir', 'D:\shared')
|
||||||
|
Contains = @('then build_win.bat -s -l -x --deps-dir <path>')
|
||||||
|
NotContains = @('--deps-dir "D:\shared" --deps-dir') }
|
||||||
|
@{ Name = 'installing prerequisites suggests the build that follows'; Args = @('-u', '-l')
|
||||||
|
Contains = @('Restart this shell', 'build_win.bat -ds -l')
|
||||||
|
NotContains = @('Run it') }
|
||||||
|
# Everything below the header line is worked out the same way in either
|
||||||
|
# run, which is why a dry run can cover it.
|
||||||
|
@{ Name = 'a dry run does not claim a build happened'; Args = @('-s', '-l', '-x')
|
||||||
|
Contains = @('Dry run: nothing was built.')
|
||||||
|
NotContains = @('Build completed in') }
|
||||||
|
# --no-configure is the iteration loop and still gets the block; four
|
||||||
|
# lines after a rebuild is not enough to be worth suppressing.
|
||||||
|
@{ Name = '--no-configure still gets the summary'; Args = @('-s', '-l', '-x', '--no-configure')
|
||||||
|
Contains = @('Next', 'Rebuild after edits') }
|
||||||
|
|
||||||
|
'pointing at the solution'
|
||||||
|
@{ Name = 'the VS generator says where the solution is'; Args = @('-s')
|
||||||
|
Match = @('^ Solution .*\\build\\OrcaSlicer\.sln$') }
|
||||||
|
@{ Name = 'the solution path follows the configuration'; Args = @('-s', '--config', 'debug')
|
||||||
|
Match = @('^ Solution .*\\build-dbg\\OrcaSlicer\.sln$') }
|
||||||
|
@{ Name = 'the solution line survives an install'; Args = @('-s', '-i')
|
||||||
|
Contains = @(' Solution ') }
|
||||||
|
# The path is resolved, not pasted onto the repository root, so it is
|
||||||
|
# right whether --build-dir came absolute or with forward slashes.
|
||||||
|
@{ Name = 'a moved build still prints one real path'; Args = @('-s', '--build-dir', 'out/build/x64-clang')
|
||||||
|
Match = @('^ Solution [A-Za-z]:\\[^/]+\\OrcaSlicer\.sln$') }
|
||||||
|
@{ Name = 'an absolute --build-dir is not glued onto the repo root'; Args = @('-s', '--build-dir', 'D:\tree')
|
||||||
|
Contains = @('Solution D:\tree\OrcaSlicer.sln') }
|
||||||
|
)
|
||||||
|
|
||||||
|
function Invoke-BuildScript {
|
||||||
|
param([string[]] $Arguments, [hashtable] $Environment)
|
||||||
|
|
||||||
|
$saved = @{}
|
||||||
|
if ($Environment) {
|
||||||
|
foreach ($key in $Environment.Keys) {
|
||||||
|
$saved[$key] = [Environment]::GetEnvironmentVariable($key)
|
||||||
|
Set-Item -Path "env:$key" -Value $Environment[$key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
# 'Stop' turns a native command's stderr into a terminating error, and
|
||||||
|
# a case that exercises a real failure writes to stderr. Let the output
|
||||||
|
# through and judge the run by its exit code instead. The assignment is
|
||||||
|
# scoped to this function, so the rest of the suite keeps 'Stop'.
|
||||||
|
$ErrorActionPreference = 'Continue'
|
||||||
|
if ($Arguments.Count -eq 0) {
|
||||||
|
$out = & $Script 2>&1 | Out-String
|
||||||
|
} else {
|
||||||
|
$out = & $Script @Arguments 2>&1 | Out-String
|
||||||
|
}
|
||||||
|
return [pscustomobject]@{ Output = $out; Exit = $LASTEXITCODE }
|
||||||
|
} finally {
|
||||||
|
foreach ($key in $saved.Keys) {
|
||||||
|
if ($null -eq $saved[$key]) { Remove-Item -Path "env:$key" -ErrorAction SilentlyContinue }
|
||||||
|
else { Set-Item -Path "env:$key" -Value $saved[$key] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$knownFields = @(
|
||||||
|
'Name', 'Args', 'ExpectExit', 'DryRun', 'First', 'Env',
|
||||||
|
'Contains', 'NotContains', 'Match', 'NotMatch', 'NotExists'
|
||||||
|
)
|
||||||
|
|
||||||
|
function Test-Case {
|
||||||
|
param([hashtable] $Case)
|
||||||
|
|
||||||
|
# Read fields with the indexer, not dot notation. A hashtable exposes its
|
||||||
|
# own members too, so $Case.Contains returns the Contains *method* whenever
|
||||||
|
# the case has no key by that name.
|
||||||
|
$argv = @($Case['Args'])
|
||||||
|
if (-not $Case.ContainsKey('DryRun') -or $Case['DryRun']) { $argv += '--dry-run' }
|
||||||
|
|
||||||
|
$expect = 0
|
||||||
|
if ($Case.ContainsKey('ExpectExit')) { $expect = $Case['ExpectExit'] }
|
||||||
|
|
||||||
|
$result = Invoke-BuildScript -Arguments $argv -Environment $Case['Env']
|
||||||
|
|
||||||
|
$problems = @()
|
||||||
|
|
||||||
|
# A misspelled field is silently ignored by the checks below, which
|
||||||
|
# leaves the case asserting nothing at all and passing.
|
||||||
|
foreach ($field in $Case.Keys) {
|
||||||
|
if ($knownFields -notcontains $field) { $problems += "unknown field '$field'" }
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($result.Exit -ne $expect) { $problems += "exit $($result.Exit), expected $expect" }
|
||||||
|
foreach ($needle in $Case['Contains']) {
|
||||||
|
if (-not $result.Output.Contains($needle)) { $problems += "missing '$needle'" }
|
||||||
|
}
|
||||||
|
foreach ($needle in $Case['NotContains']) {
|
||||||
|
if ($result.Output.Contains($needle)) { $problems += "unexpected '$needle'" }
|
||||||
|
}
|
||||||
|
$lines = $result.Output -split "`r?`n"
|
||||||
|
if ($Case['First'] -and $lines[0] -notmatch $Case['First']) {
|
||||||
|
$problems += "first line was '$($lines[0])'"
|
||||||
|
}
|
||||||
|
foreach ($pattern in $Case['Match']) {
|
||||||
|
if (@($lines | Where-Object { $_ -match $pattern }).Count -eq 0) {
|
||||||
|
$problems += "no line matching /$pattern/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($pattern in $Case['NotMatch']) {
|
||||||
|
foreach ($line in @($lines | Where-Object { $_ -match $pattern })) {
|
||||||
|
$problems += "line matches /$pattern/: $line"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# Output cannot show what a run did not create.
|
||||||
|
foreach ($path in $Case['NotExists']) {
|
||||||
|
$full = Join-Path (Split-Path -Parent $Script) $path
|
||||||
|
if (Test-Path $full) {
|
||||||
|
$problems += "created '$path'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ,$problems
|
||||||
|
}
|
||||||
|
|
||||||
|
$pass = 0
|
||||||
|
$failed = @()
|
||||||
|
# Held back so a filtered run does not print headings for groups it skipped.
|
||||||
|
$heading = $null
|
||||||
|
|
||||||
|
foreach ($case in $cases) {
|
||||||
|
if ($case -is [string]) {
|
||||||
|
$heading = $case
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ($Name -and $case['Name'] -notmatch $Name) { continue }
|
||||||
|
if ($heading) {
|
||||||
|
Write-Host ''
|
||||||
|
Write-Host $heading -ForegroundColor Cyan
|
||||||
|
$heading = $null
|
||||||
|
}
|
||||||
|
|
||||||
|
$problems = Test-Case -Case $case
|
||||||
|
if ($problems.Count -eq 0) {
|
||||||
|
$pass++
|
||||||
|
Write-Host (' ok ' + $case['Name'])
|
||||||
|
} else {
|
||||||
|
$failed += $case['Name']
|
||||||
|
Write-Host (' FAIL ' + $case['Name']) -ForegroundColor Red
|
||||||
|
foreach ($problem in $problems) { Write-Host (' ' + $problem) -ForegroundColor Red }
|
||||||
|
Write-Host (' args: ' + (@($case['Args']) -join ' '))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Remove-Item -Recurse -Force $fixtures -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
Write-Host ''
|
||||||
|
# A pattern that matched nothing has proved nothing, so do not report it as
|
||||||
|
# a clean run.
|
||||||
|
if ($Name -and $pass -eq 0 -and $failed.Count -eq 0) {
|
||||||
|
Write-Host "no case matched /$Name/" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
Write-Host "$pass passed, $($failed.Count) failed"
|
||||||
|
if ($failed.Count -gt 0) {
|
||||||
|
foreach ($name in $failed) { Write-Host " failed: $name" -ForegroundColor Red }
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
exit 0
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include <boost/log/trivial.hpp>
|
#include <boost/log/trivial.hpp>
|
||||||
|
#include <boost/nowide/fstream.hpp>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#define DIR_SEPARATOR '\\'
|
#define DIR_SEPARATOR '\\'
|
||||||
|
|||||||
@@ -8197,9 +8197,10 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
|||||||
}
|
}
|
||||||
variable_speed = std::any_of(new_points.begin(), new_points.end(),
|
variable_speed = std::any_of(new_points.begin(), new_points.end(),
|
||||||
[speed](const ProcessedPoint &p) { return fabs(double(p.speed) - speed) > 1; }); // Ignore small speed variations (under 1mm/sec)
|
[speed](const ProcessedPoint &p) { return fabs(double(p.speed) - speed) > 1; }); // Ignore small speed variations (under 1mm/sec)
|
||||||
if (!NOZZLE_CONFIG(enable_overhang_speed) && FILAMENT_CONFIG(enable_overhang_bridge_fan) && m_enable_cooling_markers) {
|
if (FILAMENT_CONFIG(enable_overhang_bridge_fan) && m_enable_cooling_markers) {
|
||||||
for (ProcessedPoint &point : new_points)
|
if (!NOZZLE_CONFIG(enable_overhang_speed))
|
||||||
point.speed = speed;
|
for (ProcessedPoint &point : new_points)
|
||||||
|
point.speed = speed;
|
||||||
variable_speed = new_points.size() > 1;
|
variable_speed = new_points.size() > 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1024,28 +1024,29 @@ std::string CoolingBuffer::apply_layer_cooldown(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (need_set_fan) {
|
if (need_set_fan) {
|
||||||
|
const auto set_fan = [&](int speed) {
|
||||||
|
if (m_current_fan_speed != speed) {
|
||||||
|
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, speed, part_cooling_fan_min_pwm);
|
||||||
|
m_current_fan_speed = speed;
|
||||||
|
}
|
||||||
|
};
|
||||||
if (fan_speed_change_requests[CoolingLine::TYPE_OVERHANG_FAN_START]){
|
if (fan_speed_change_requests[CoolingLine::TYPE_OVERHANG_FAN_START]){
|
||||||
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, overhang_fan_speed, part_cooling_fan_min_pwm);
|
set_fan(overhang_fan_speed);
|
||||||
m_current_fan_speed = overhang_fan_speed;
|
|
||||||
} else if (fan_speed_change_requests[CoolingLine::TYPE_INTERNAL_BRIDGE_FAN_START]){ // ORCA: Add support for separate internal bridge fan speed control
|
} else if (fan_speed_change_requests[CoolingLine::TYPE_INTERNAL_BRIDGE_FAN_START]){ // ORCA: Add support for separate internal bridge fan speed control
|
||||||
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, internal_bridge_fan_speed, part_cooling_fan_min_pwm);
|
set_fan(internal_bridge_fan_speed);
|
||||||
m_current_fan_speed = internal_bridge_fan_speed;
|
|
||||||
}
|
}
|
||||||
else if (fan_speed_change_requests[CoolingLine::TYPE_SUPPORT_INTERFACE_FAN_START]){
|
else if (fan_speed_change_requests[CoolingLine::TYPE_SUPPORT_INTERFACE_FAN_START]){
|
||||||
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, supp_interface_fan_speed, part_cooling_fan_min_pwm);
|
set_fan(supp_interface_fan_speed);
|
||||||
m_current_fan_speed = supp_interface_fan_speed;
|
|
||||||
}
|
}
|
||||||
else if (fan_speed_change_requests[CoolingLine::TYPE_IRONING_FAN_START]){
|
else if (fan_speed_change_requests[CoolingLine::TYPE_IRONING_FAN_START]){
|
||||||
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, ironing_fan_speed, part_cooling_fan_min_pwm);
|
set_fan(ironing_fan_speed);
|
||||||
m_current_fan_speed = ironing_fan_speed;
|
|
||||||
}
|
}
|
||||||
else if(fan_speed_change_requests[CoolingLine::TYPE_FORCE_RESUME_FAN] && m_current_fan_speed != -1){
|
else if(fan_speed_change_requests[CoolingLine::TYPE_FORCE_RESUME_FAN] && m_current_fan_speed != -1){
|
||||||
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_current_fan_speed, part_cooling_fan_min_pwm);
|
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_current_fan_speed, part_cooling_fan_min_pwm);
|
||||||
fan_speed_change_requests[CoolingLine::TYPE_FORCE_RESUME_FAN] = false;
|
fan_speed_change_requests[CoolingLine::TYPE_FORCE_RESUME_FAN] = false;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_fan_speed, part_cooling_fan_min_pwm);
|
set_fan(m_fan_speed);
|
||||||
m_current_fan_speed = m_fan_speed;
|
|
||||||
}
|
}
|
||||||
need_set_fan = false;
|
need_set_fan = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -170,14 +170,13 @@ void FanMover::_put_in_middle_G1(std::list<BufferData>::iterator item_to_split,
|
|||||||
|
|
||||||
void FanMover::_print_in_middle_G1(BufferData& line_to_split, float nb_sec, const std::string &line_to_write) {
|
void FanMover::_print_in_middle_G1(BufferData& line_to_split, float nb_sec, const std::string &line_to_write) {
|
||||||
if (nb_sec < line_to_split.time * 0.1) {
|
if (nb_sec < line_to_split.time * 0.1) {
|
||||||
// doesn't really need to be split, print it after
|
// Doesn't need to be split: the insertion point is at the start.
|
||||||
m_process_output += line_to_split.raw + "\n";
|
|
||||||
m_process_output += line_to_write + (line_to_write.back() == '\n'?"":"\n");
|
m_process_output += line_to_write + (line_to_write.back() == '\n'?"":"\n");
|
||||||
} else if (nb_sec > line_to_split.time * 0.9) {
|
|
||||||
// doesn't really need to be split, print it before
|
|
||||||
//will also print before if line_to_split.time == 0
|
|
||||||
m_process_output += line_to_write + (line_to_write.back() == '\n' ? "" : "\n");
|
|
||||||
m_process_output += line_to_split.raw + "\n";
|
m_process_output += line_to_split.raw + "\n";
|
||||||
|
} else if (nb_sec > line_to_split.time * 0.9) {
|
||||||
|
// Doesn't need to be split: the insertion point is at the end.
|
||||||
|
m_process_output += line_to_split.raw + "\n";
|
||||||
|
m_process_output += line_to_write + (line_to_write.back() == '\n' ? "" : "\n");
|
||||||
}else if(line_to_split.raw.size() > 2
|
}else if(line_to_split.raw.size() > 2
|
||||||
&& line_to_split.raw[0] == 'G' && line_to_split.raw[1] == '1' && line_to_split.raw[2] == ' ') {
|
&& line_to_split.raw[0] == 'G' && line_to_split.raw[1] == '1' && line_to_split.raw[2] == ' ') {
|
||||||
float percent = nb_sec / line_to_split.time;
|
float percent = nb_sec / line_to_split.time;
|
||||||
|
|||||||
@@ -532,7 +532,7 @@ void GCodeProcessor::TimeMachine::calculate_time(GCodeProcessorResult& result, P
|
|||||||
const float height = interpolate ? lerp(prev_move.height, curr_move.height, t) : curr_move.height;
|
const float height = interpolate ? lerp(prev_move.height, curr_move.height, t) : curr_move.height;
|
||||||
// ORCA: Fix issue with flow rate changes being visualized incorrectly
|
// ORCA: Fix issue with flow rate changes being visualized incorrectly
|
||||||
const float mm3_per_mm = curr_move.mm3_per_mm;
|
const float mm3_per_mm = curr_move.mm3_per_mm;
|
||||||
const float fan_speed = interpolate ? lerp(prev_move.fan_speed, curr_move.fan_speed, t) : curr_move.fan_speed;
|
const float fan_speed = curr_move.fan_speed;
|
||||||
const float temperature = interpolate ? lerp(prev_move.temperature, curr_move.temperature, t) : curr_move.temperature;
|
const float temperature = interpolate ? lerp(prev_move.temperature, curr_move.temperature, t) : curr_move.temperature;
|
||||||
actual_speed_moves.push_back({
|
actual_speed_moves.push_back({
|
||||||
block.move_id,
|
block.move_id,
|
||||||
@@ -563,7 +563,7 @@ void GCodeProcessor::TimeMachine::calculate_time(GCodeProcessorResult& result, P
|
|||||||
const float height = interpolate ? lerp(prev_move.height, curr_move.height, t) : curr_move.height;
|
const float height = interpolate ? lerp(prev_move.height, curr_move.height, t) : curr_move.height;
|
||||||
// ORCA: Fix issue with flow rate changes being visualized incorrectly
|
// ORCA: Fix issue with flow rate changes being visualized incorrectly
|
||||||
const float mm3_per_mm = curr_move.mm3_per_mm;
|
const float mm3_per_mm = curr_move.mm3_per_mm;
|
||||||
const float fan_speed = interpolate ? lerp(prev_move.fan_speed, curr_move.fan_speed, t) : curr_move.fan_speed;
|
const float fan_speed = curr_move.fan_speed;
|
||||||
const float temperature = interpolate ? lerp(prev_move.temperature, curr_move.temperature, t) : curr_move.temperature;
|
const float temperature = interpolate ? lerp(prev_move.temperature, curr_move.temperature, t) : curr_move.temperature;
|
||||||
actual_speed_moves.push_back({
|
actual_speed_moves.push_back({
|
||||||
block.move_id,
|
block.move_id,
|
||||||
|
|||||||
@@ -10091,6 +10091,10 @@ int DynamicPrintConfig::update_values_from_single_to_multi(DynamicPrintConfig& m
|
|||||||
|
|
||||||
for (int index = 0; index < variant_count; index++)
|
for (int index = 0; index < variant_count; index++)
|
||||||
{
|
{
|
||||||
|
//variant_count is the variant column width, src_opt the value array;
|
||||||
|
//they disagree when the source was authored at a different width
|
||||||
|
if (index >= (int)src_opt->values.size())
|
||||||
|
break;
|
||||||
if (opt->values[index] > src_opt->values[index])
|
if (opt->values[index] > src_opt->values[index])
|
||||||
opt->values[index] = src_opt->values[index];
|
opt->values[index] = src_opt->values[index];
|
||||||
}
|
}
|
||||||
@@ -10108,6 +10112,8 @@ int DynamicPrintConfig::update_values_from_single_to_multi(DynamicPrintConfig& m
|
|||||||
|
|
||||||
for (int index = 0; index < variant_count; index++)
|
for (int index = 0; index < variant_count; index++)
|
||||||
{
|
{
|
||||||
|
if (index >= (int)src_opt->values.size())
|
||||||
|
break;
|
||||||
if (opt->values[index].value > src_opt->values[index].value)
|
if (opt->values[index].value > src_opt->values[index].value)
|
||||||
opt->values[index] = src_opt->values[index];
|
opt->values[index] = src_opt->values[index];
|
||||||
}
|
}
|
||||||
@@ -10302,6 +10308,10 @@ int DynamicPrintConfig::update_values_from_multi_to_multi(DynamicPrintConfig& ne
|
|||||||
|
|
||||||
for(auto idx : variant_indices){
|
for(auto idx : variant_indices){
|
||||||
assert(idx < old_count);
|
assert(idx < old_count);
|
||||||
|
//the counts come from the variant columns, the arrays from the options;
|
||||||
|
//they disagree when a config was authored at a different variant width
|
||||||
|
if (idx >= old_count || new_variant_index >= (int)opt->values.size())
|
||||||
|
continue;
|
||||||
if (old_values[idx] < opt->values[new_variant_index])
|
if (old_values[idx] < opt->values[new_variant_index])
|
||||||
opt->values[new_variant_index] = old_values[idx];
|
opt->values[new_variant_index] = old_values[idx];
|
||||||
}
|
}
|
||||||
@@ -10332,6 +10342,10 @@ int DynamicPrintConfig::update_values_from_multi_to_multi(DynamicPrintConfig& ne
|
|||||||
|
|
||||||
for(auto idx : variant_indices){
|
for(auto idx : variant_indices){
|
||||||
assert(idx < old_count);
|
assert(idx < old_count);
|
||||||
|
//the counts come from the variant columns, the arrays from the options;
|
||||||
|
//they disagree when a config was authored at a different variant width
|
||||||
|
if (idx >= old_count || new_variant_index >= (int)opt->values.size())
|
||||||
|
continue;
|
||||||
if (old_values[idx] < opt->values[new_variant_index])
|
if (old_values[idx] < opt->values[new_variant_index])
|
||||||
opt->values[new_variant_index] = old_values[idx];
|
opt->values[new_variant_index] = old_values[idx];
|
||||||
}
|
}
|
||||||
@@ -10362,6 +10376,8 @@ int DynamicPrintConfig::update_values_from_multi_to_multi(DynamicPrintConfig& ne
|
|||||||
|
|
||||||
for(auto idx : variant_indices){
|
for(auto idx : variant_indices){
|
||||||
assert(idx < old_count);
|
assert(idx < old_count);
|
||||||
|
if (idx >= old_count || new_variant_index >= (int)opt->values.size())
|
||||||
|
continue;
|
||||||
if (old_values[idx]) //enabled
|
if (old_values[idx]) //enabled
|
||||||
opt->values[new_variant_index] = old_values[idx];
|
opt->values[new_variant_index] = old_values[idx];
|
||||||
}
|
}
|
||||||
@@ -10402,6 +10418,15 @@ int DynamicPrintConfig::update_values_from_multi_to_multi_2(const std::vector<st
|
|||||||
same_variant_indices.emplace_back(indices);
|
same_variant_indices.emplace_back(indices);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//dst_values below is the destination PRINT preset's per-variant row, sized to its own
|
||||||
|
//print_extruder_variant; dst_extruder_variants is the PRINTER's list. They disagree until
|
||||||
|
//the print preset is re-selected, so size the row to the variant count before indexing it.
|
||||||
|
const size_t dst_variant_count = dst_extruder_variants.size();
|
||||||
|
if (dst_variant_count == 0) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: empty destination variant list")%__LINE__;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
t_config_option_keys keys = this->keys();
|
t_config_option_keys keys = this->keys();
|
||||||
for(auto& key : keys){
|
for(auto& key : keys){
|
||||||
if(key_sets.find(key) == key_sets.end())
|
if(key_sets.find(key) == key_sets.end())
|
||||||
@@ -10417,7 +10442,13 @@ int DynamicPrintConfig::update_values_from_multi_to_multi_2(const std::vector<st
|
|||||||
{
|
{
|
||||||
ConfigOptionFloatsNullable* opt = this->option<ConfigOptionFloatsNullable>(key);
|
ConfigOptionFloatsNullable* opt = this->option<ConfigOptionFloatsNullable>(key);
|
||||||
auto src_values = opt->values;
|
auto src_values = opt->values;
|
||||||
auto dst_values = dst_config.option<ConfigOptionFloatsNullable>(key) ->values;
|
const auto* dst_opt = dst_config.option<ConfigOptionFloatsNullable>(key);
|
||||||
|
if(!dst_opt){
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: %2% missing from destination config")%__LINE__%key;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
auto dst_values = dst_opt->values;
|
||||||
|
dst_values.resize(dst_variant_count, ConfigOptionFloatsNullable::nil_value());
|
||||||
for(size_t dst_idx =0; dst_idx < same_variant_indices.size(); ++dst_idx){
|
for(size_t dst_idx =0; dst_idx < same_variant_indices.size(); ++dst_idx){
|
||||||
auto& indices = same_variant_indices[dst_idx];
|
auto& indices = same_variant_indices[dst_idx];
|
||||||
if(indices.empty())
|
if(indices.empty())
|
||||||
@@ -10425,7 +10456,7 @@ int DynamicPrintConfig::update_values_from_multi_to_multi_2(const std::vector<st
|
|||||||
bool has_value = false;
|
bool has_value = false;
|
||||||
double target_value = std::numeric_limits<double>::max();
|
double target_value = std::numeric_limits<double>::max();
|
||||||
for(auto idx : indices){
|
for(auto idx : indices){
|
||||||
if(opt && idx < opt->values.size() && !opt->is_nil(idx)){
|
if(idx < (int)opt->values.size() && !opt->is_nil(idx)){
|
||||||
has_value = true;
|
has_value = true;
|
||||||
target_value = std::min(target_value, src_values[idx]);
|
target_value = std::min(target_value, src_values[idx]);
|
||||||
}
|
}
|
||||||
@@ -10441,7 +10472,13 @@ int DynamicPrintConfig::update_values_from_multi_to_multi_2(const std::vector<st
|
|||||||
{
|
{
|
||||||
ConfigOptionFloatsOrPercentsNullable* opt = this->option<ConfigOptionFloatsOrPercentsNullable>(key);
|
ConfigOptionFloatsOrPercentsNullable* opt = this->option<ConfigOptionFloatsOrPercentsNullable>(key);
|
||||||
auto src_values = opt->values;
|
auto src_values = opt->values;
|
||||||
auto dst_values = dst_config.option<ConfigOptionFloatsOrPercentsNullable>(key) ->values;
|
const auto* dst_opt = dst_config.option<ConfigOptionFloatsOrPercentsNullable>(key);
|
||||||
|
if(!dst_opt){
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: %2% missing from destination config")%__LINE__%key;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
auto dst_values = dst_opt->values;
|
||||||
|
dst_values.resize(dst_variant_count, ConfigOptionFloatsOrPercentsNullable::nil_value());
|
||||||
for(size_t dst_idx =0; dst_idx < same_variant_indices.size(); ++dst_idx){
|
for(size_t dst_idx =0; dst_idx < same_variant_indices.size(); ++dst_idx){
|
||||||
auto& indices = same_variant_indices[dst_idx];
|
auto& indices = same_variant_indices[dst_idx];
|
||||||
if(indices.empty())
|
if(indices.empty())
|
||||||
@@ -10449,7 +10486,7 @@ int DynamicPrintConfig::update_values_from_multi_to_multi_2(const std::vector<st
|
|||||||
bool has_value = false;
|
bool has_value = false;
|
||||||
FloatOrPercent target_value{9999.f, true};
|
FloatOrPercent target_value{9999.f, true};
|
||||||
for(auto idx : indices){
|
for(auto idx : indices){
|
||||||
if(opt && !opt->is_nil(idx)){
|
if(idx < (int)opt->values.size() && !opt->is_nil(idx)){
|
||||||
has_value = true;
|
has_value = true;
|
||||||
target_value = src_values[idx].value < target_value.value ? src_values[idx] : target_value;
|
target_value = src_values[idx].value < target_value.value ? src_values[idx] : target_value;
|
||||||
}
|
}
|
||||||
@@ -10465,15 +10502,21 @@ int DynamicPrintConfig::update_values_from_multi_to_multi_2(const std::vector<st
|
|||||||
{
|
{
|
||||||
ConfigOptionBoolsNullable* opt = this->option<ConfigOptionBoolsNullable>(key);
|
ConfigOptionBoolsNullable* opt = this->option<ConfigOptionBoolsNullable>(key);
|
||||||
auto src_values = opt->values;
|
auto src_values = opt->values;
|
||||||
auto dst_values = dst_config.option<ConfigOptionBoolsNullable>(key) ->values;
|
const auto* dst_opt = dst_config.option<ConfigOptionBoolsNullable>(key);
|
||||||
|
if(!dst_opt){
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: %2% missing from destination config")%__LINE__%key;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
auto dst_values = dst_opt->values;
|
||||||
|
dst_values.resize(dst_variant_count, ConfigOptionBoolsNullable::nil_value());
|
||||||
for(size_t dst_idx =0; dst_idx < same_variant_indices.size(); ++dst_idx){
|
for(size_t dst_idx =0; dst_idx < same_variant_indices.size(); ++dst_idx){
|
||||||
auto indices = same_variant_indices[dst_idx];
|
auto indices = same_variant_indices[dst_idx];
|
||||||
if(indices.empty())
|
if(indices.empty())
|
||||||
continue;
|
continue;
|
||||||
bool has_value = false;
|
bool has_value = false;
|
||||||
bool target_value;
|
bool target_value = false;
|
||||||
for(auto idx : indices){
|
for(auto idx : indices){
|
||||||
if(opt && !opt->is_nil(idx)){
|
if(idx < (int)opt->values.size() && !opt->is_nil(idx)){
|
||||||
has_value = true;
|
has_value = true;
|
||||||
target_value = src_values[idx];
|
target_value = src_values[idx];
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -453,7 +453,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
|
|||||||
|
|
||||||
if (config->opt_bool("alternate_extra_wall") &&
|
if (config->opt_bool("alternate_extra_wall") &&
|
||||||
(config->opt_enum<EnsureVerticalShellThickness>("ensure_vertical_shell_thickness") == evstAll)) {
|
(config->opt_enum<EnsureVerticalShellThickness>("ensure_vertical_shell_thickness") == evstAll)) {
|
||||||
wxString msg_text = _(L("Alternate extra wall does't work well when ensure vertical shell thickness is set to All."));
|
wxString msg_text = _(L("Alternate extra wall doesn't work well when ensure vertical shell thickness is set to All."));
|
||||||
|
|
||||||
if (is_global_config)
|
if (is_global_config)
|
||||||
msg_text += "\n\n" + _(L("Change these settings automatically?\n"
|
msg_text += "\n\n" + _(L("Change these settings automatically?\n"
|
||||||
@@ -642,7 +642,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
|
|||||||
|
|
||||||
if (config->opt_enum<SeamScarfType>("seam_slope_type") != SeamScarfType::None &&
|
if (config->opt_enum<SeamScarfType>("seam_slope_type") != SeamScarfType::None &&
|
||||||
config->get_abs_value("seam_slope_start_height") >= layer_height) {
|
config->get_abs_value("seam_slope_start_height") >= layer_height) {
|
||||||
const wxString msg_text = _(L("seam_slope_start_height need to be smaller than layer_height.\nReset to 0."));
|
const wxString msg_text = _(L("seam_slope_start_height needs to be smaller than layer_height.\nReset to 0."));
|
||||||
MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxOK);
|
MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxOK);
|
||||||
DynamicPrintConfig new_conf = *config;
|
DynamicPrintConfig new_conf = *config;
|
||||||
is_msg_dlg_already_exist = true;
|
is_msg_dlg_already_exist = true;
|
||||||
@@ -656,7 +656,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
|
|||||||
float skin_depth = config->opt_float("skin_infill_depth");
|
float skin_depth = config->opt_float("skin_infill_depth");
|
||||||
if (config->opt_float("infill_lock_depth") > skin_depth) {
|
if (config->opt_float("infill_lock_depth") > skin_depth) {
|
||||||
// xgettext:no-c-format, no-boost-format
|
// xgettext:no-c-format, no-boost-format
|
||||||
const wxString msg_text = _(L("Lock depth should smaller than skin depth.\nReset to 50% of skin depth."));
|
const wxString msg_text = _(L("Lock depth should be smaller than skin depth.\nReset to 50% of skin depth."));
|
||||||
MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxOK);
|
MessageDialog dialog(m_msg_dlg_parent, msg_text, "", wxICON_WARNING | wxOK);
|
||||||
DynamicPrintConfig new_conf = *config;
|
DynamicPrintConfig new_conf = *config;
|
||||||
is_msg_dlg_already_exist = true;
|
is_msg_dlg_already_exist = true;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
#include "libslic3r/MeshBoolean.hpp"
|
#include "libslic3r/MeshBoolean.hpp"
|
||||||
#include "libslic3r/Model.hpp"
|
#include "libslic3r/Model.hpp"
|
||||||
|
#include "libslic3r/Format/bbs_3mf.hpp"
|
||||||
#include "libslic3r/format.hpp"
|
#include "libslic3r/format.hpp"
|
||||||
#include "libslic3r/Thread.hpp"
|
#include "libslic3r/Thread.hpp"
|
||||||
#include "../GUI/I18N.hpp"
|
#include "../GUI/I18N.hpp"
|
||||||
@@ -69,6 +70,9 @@ public:
|
|||||||
// Returns false if fixing was canceled. fix_result contains error message if failed.
|
// Returns false if fixing was canceled. fix_result contains error message if failed.
|
||||||
bool fix_model_with_cgal_gui(ModelObject &model_object, int volume_idx, GUI::ProgressDialog &progress_dialog, const wxString &msg_header, std::string &fix_result, bool keep_painting)
|
bool fix_model_with_cgal_gui(ModelObject &model_object, int volume_idx, GUI::ProgressDialog &progress_dialog, const wxString &msg_header, std::string &fix_result, bool keep_painting)
|
||||||
{
|
{
|
||||||
|
// Hold SaveObjectGaurd to prevent backup manager from racing concurrent mesh mutations (use-after-free).
|
||||||
|
SaveObjectGaurd backup_gaurd(model_object);
|
||||||
|
|
||||||
// Orca: Synchronization primitives for progress updates between worker thread and GUI.
|
// Orca: Synchronization primitives for progress updates between worker thread and GUI.
|
||||||
std::mutex mtx;
|
std::mutex mtx;
|
||||||
std::condition_variable condition;
|
std::condition_variable condition;
|
||||||
|
|||||||
@@ -2,7 +2,10 @@
|
|||||||
|
|
||||||
#include "test_helpers.hpp"
|
#include "test_helpers.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
using namespace Slic3r;
|
using namespace Slic3r;
|
||||||
using namespace Slic3r::Test;
|
using namespace Slic3r::Test;
|
||||||
@@ -25,3 +28,68 @@ TEST_CASE("Cooling consumes its internal speed markers", "[Cooling]")
|
|||||||
const std::string gcode = slice({ cube(20) }, { { "layer_height", 0.2 } });
|
const std::string gcode = slice({ cube(20) }, { { "layer_height", 0.2 } });
|
||||||
CHECK(gcode.find(";_EXTRUDE_SET_SPEED") == std::string::npos);
|
CHECK(gcode.find(";_EXTRUDE_SET_SPEED") == std::string::npos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Overhang fan transitions do not depend on overhang speed", "[Cooling][Regression]")
|
||||||
|
{
|
||||||
|
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
||||||
|
config.set_deserialize_strict({
|
||||||
|
{ "bridge_speed", 2.0 },
|
||||||
|
{ "enable_arc_fitting", false },
|
||||||
|
{ "enable_overhang_bridge_fan", true },
|
||||||
|
{ "enable_overhang_speed", false },
|
||||||
|
{ "initial_layer_print_height", 0.3 },
|
||||||
|
{ "inner_wall_speed", 30.0 },
|
||||||
|
{ "layer_height", 0.3 },
|
||||||
|
{ "outer_wall_speed", 30.0 },
|
||||||
|
{ "overhang_1_4_speed", "30" },
|
||||||
|
{ "overhang_2_4_speed", "29" },
|
||||||
|
{ "overhang_3_4_speed", "6" },
|
||||||
|
{ "overhang_4_4_speed", "3" },
|
||||||
|
{ "slow_down_for_layer_cooling", false },
|
||||||
|
{ "slowdown_for_curled_perimeters", false },
|
||||||
|
});
|
||||||
|
config.set_key_value("fan_max_speed", new ConfigOptionFloats{20.0});
|
||||||
|
config.set_key_value("fan_min_speed", new ConfigOptionFloats{20.0});
|
||||||
|
config.set_key_value("overhang_fan_speed", new ConfigOptionInts{100});
|
||||||
|
config.set_key_value("overhang_fan_threshold", new ConfigOptionEnumsGeneric{Overhang_threshold_2_4});
|
||||||
|
config.set_key_value("layer_change_gcode", new ConfigOptionString{";TEST_LAYER_Z=[layer_z]"});
|
||||||
|
|
||||||
|
const auto fan_commands = [](const std::string &gcode) {
|
||||||
|
std::vector<std::pair<std::string, std::string>> commands;
|
||||||
|
std::istringstream input(gcode);
|
||||||
|
std::string layer;
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(input, line)) {
|
||||||
|
if (line.rfind(";TEST_LAYER_Z=", 0) == 0)
|
||||||
|
layer = line;
|
||||||
|
else if (!layer.empty() && (line.rfind("M106", 0) == 0 || line.rfind("M107", 0) == 0))
|
||||||
|
commands.emplace_back(layer, line);
|
||||||
|
}
|
||||||
|
return commands;
|
||||||
|
};
|
||||||
|
const auto feedrates = [](const std::string &gcode) {
|
||||||
|
std::vector<std::string> values;
|
||||||
|
std::istringstream input(gcode);
|
||||||
|
std::string word;
|
||||||
|
while (input >> word)
|
||||||
|
if (!word.empty() && word.front() == 'F')
|
||||||
|
values.push_back(word);
|
||||||
|
return values;
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr double sphere_radius = 50.0; // 100 mm diameter.
|
||||||
|
const std::string without_speed_gcode = slice({make_sphere(sphere_radius, PI / 24.0)}, config);
|
||||||
|
config.set_deserialize_strict({{"enable_overhang_speed", true}});
|
||||||
|
const std::string with_speed_gcode = slice({make_sphere(sphere_radius, PI / 24.0)}, config);
|
||||||
|
|
||||||
|
const auto without_speed_fan = fan_commands(without_speed_gcode);
|
||||||
|
const auto with_speed_fan = fan_commands(with_speed_gcode);
|
||||||
|
const auto without_speed_feedrates = feedrates(without_speed_gcode);
|
||||||
|
const auto with_speed_feedrates = feedrates(with_speed_gcode);
|
||||||
|
|
||||||
|
REQUIRE_FALSE(without_speed_fan.empty());
|
||||||
|
REQUIRE(std::any_of(without_speed_fan.begin(), without_speed_fan.end(),
|
||||||
|
[](const auto &command) { return command.second.find("S255") != std::string::npos; }));
|
||||||
|
REQUIRE(with_speed_feedrates != without_speed_feedrates);
|
||||||
|
CHECK(with_speed_fan == without_speed_fan);
|
||||||
|
}
|
||||||
|
|||||||
@@ -479,3 +479,84 @@ TEST_CASE("update_values_to_printer_extruders_for_multiple_filaments resolves pe
|
|||||||
REQUIRE(config.option<ConfigOptionInts>("filament_self_index")->values == std::vector<int>({1, 2}));
|
REQUIRE(config.option<ConfigOptionInts>("filament_self_index")->values == std::vector<int>({1, 2}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update_values_from_multi_to_multi_2 walks the DESTINATION PRINTER's variant list while writing
|
||||||
|
// into a row taken from the destination PRINT preset, whose arrays are sized to its own
|
||||||
|
// print_extruder_variant. Those two widths disagree until the print preset is re-selected for the
|
||||||
|
// new printer -- Tab::load_current_preset() runs this migration first -- so a project authored on
|
||||||
|
// a single-variant printer, opened and switched to a wider one, wrote past the end of the row.
|
||||||
|
TEST_CASE("update_values_from_multi_to_multi_2 sizes the destination row to the variant count",
|
||||||
|
"[Config][VariantExpansion]")
|
||||||
|
{
|
||||||
|
const std::vector<std::string> src_variants{"Direct Drive Standard"};
|
||||||
|
const std::vector<std::string> dst_variants{"Direct Drive Standard", "Direct Drive High Flow",
|
||||||
|
"Direct Drive Standard", "Direct Drive High Flow"};
|
||||||
|
const std::set<std::string> keys{"outer_wall_speed"};
|
||||||
|
|
||||||
|
// The per-object override as authored on the single-variant printer.
|
||||||
|
const auto object_override = [] {
|
||||||
|
DynamicPrintConfig c;
|
||||||
|
c.option<ConfigOptionFloatsNullable>("outer_wall_speed", true)->values = {42.};
|
||||||
|
return c;
|
||||||
|
};
|
||||||
|
|
||||||
|
SECTION("a row narrower than the variant list is grown, not overrun") {
|
||||||
|
DynamicPrintConfig object_config = object_override();
|
||||||
|
DynamicPrintConfig dst;
|
||||||
|
dst.option<ConfigOptionFloatsNullable>("outer_wall_speed", true)->values = {200.};
|
||||||
|
|
||||||
|
REQUIRE(object_config.update_values_from_multi_to_multi_2(src_variants, dst_variants, dst, keys) == 0);
|
||||||
|
|
||||||
|
const auto& out = object_config.option<ConfigOptionFloatsNullable>("outer_wall_speed")->values;
|
||||||
|
REQUIRE(out.size() == dst_variants.size());
|
||||||
|
// Both "Direct Drive Standard" columns match the source variant, so they take the override.
|
||||||
|
CHECK(out[0] == Catch::Approx(42.));
|
||||||
|
CHECK(out[2] == Catch::Approx(42.));
|
||||||
|
// The High Flow columns have no matching source variant: nil, so the destination keeps
|
||||||
|
// tracking the print preset rather than being pinned to another variant's value.
|
||||||
|
CHECK(std::isnan(out[1]));
|
||||||
|
CHECK(std::isnan(out[3]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The regression guard: where the row already matches the variant list -- every case that was
|
||||||
|
// not corrupting the heap -- the resize is a no-op and the output is unchanged.
|
||||||
|
SECTION("a correctly sized row is untouched") {
|
||||||
|
DynamicPrintConfig object_config = object_override();
|
||||||
|
DynamicPrintConfig dst;
|
||||||
|
dst.option<ConfigOptionFloatsNullable>("outer_wall_speed", true)->values = {200., 500., 210., 510.};
|
||||||
|
|
||||||
|
REQUIRE(object_config.update_values_from_multi_to_multi_2(src_variants, dst_variants, dst, keys) == 0);
|
||||||
|
|
||||||
|
const auto& out = object_config.option<ConfigOptionFloatsNullable>("outer_wall_speed")->values;
|
||||||
|
REQUIRE(out.size() == 4);
|
||||||
|
CHECK(out[0] == Catch::Approx(42.)); // matched -> override
|
||||||
|
CHECK(out[1] == Catch::Approx(500.)); // unmatched -> preset value preserved
|
||||||
|
CHECK(out[2] == Catch::Approx(42.));
|
||||||
|
CHECK(out[3] == Catch::Approx(510.));
|
||||||
|
}
|
||||||
|
|
||||||
|
// is_nil(idx) indexes values[idx] with no bounds check, so a source shorter than its own
|
||||||
|
// variant list read out of range before the guard was added.
|
||||||
|
SECTION("a source shorter than its variant list is read in range") {
|
||||||
|
DynamicPrintConfig object_config = object_override(); // one value...
|
||||||
|
DynamicPrintConfig dst;
|
||||||
|
dst.option<ConfigOptionFloatsNullable>("outer_wall_speed", true)->values = {200., 500.};
|
||||||
|
|
||||||
|
REQUIRE(object_config.update_values_from_multi_to_multi_2(
|
||||||
|
{"Direct Drive Standard", "Direct Drive Standard"}, // ...but two source variants
|
||||||
|
{"Direct Drive Standard", "Direct Drive High Flow"}, dst, keys) == 0);
|
||||||
|
|
||||||
|
const auto& out = object_config.option<ConfigOptionFloatsNullable>("outer_wall_speed")->values;
|
||||||
|
REQUIRE(out.size() == 2);
|
||||||
|
CHECK(out[0] == Catch::Approx(42.));
|
||||||
|
CHECK(out[1] == Catch::Approx(500.));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("an empty destination variant list is refused") {
|
||||||
|
DynamicPrintConfig object_config = object_override();
|
||||||
|
DynamicPrintConfig dst;
|
||||||
|
dst.option<ConfigOptionFloatsNullable>("outer_wall_speed", true)->values = {200.};
|
||||||
|
|
||||||
|
CHECK(object_config.update_values_from_multi_to_multi_2(src_variants, {}, dst, keys) == -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user