Merge branch 'main' into publish_3mf

This commit is contained in:
Lam Wei Lun
2026-09-04 12:24:57 +08:00
22 changed files with 494 additions and 94 deletions
+72 -7
View File
@@ -370,13 +370,13 @@ if "%use_ninja%" == "ON" (
set "using_ninja=ON" set "using_ninja=ON"
) )
call :resolve_clang_cl
%repeat_error%
if "%using_ninja%" == "ON" ( if "%using_ninja%" == "ON" (
if "%use_clang_cl%" == "ON" ( if "%use_clang_cl%" == "ON" (
REM Bare, so it resolves from the PATH the dev shell just set up, which REM Quoted, because the resolved path has spaces in it.
REM is the clang shipped with Visual Studio. --clang-path names another. set "gen_args=-DCMAKE_C_COMPILER="!clang_exe!" -DCMAKE_CXX_COMPILER="!clang_exe!""
set "clang_exe=clang-cl.exe"
if not "%clang_path%" == "" set "clang_exe="%clang_path%""
set "gen_args=-DCMAKE_C_COMPILER=!clang_exe! -DCMAKE_CXX_COMPILER=!clang_exe!"
) )
) else ( ) else (
set "gen_args=-A !arch!" set "gen_args=-A !arch!"
@@ -473,6 +473,7 @@ REM forward slashes, and anything that prints the directory has to show a
REM real path rather than one glued onto the repository root. REM real path rather than one glued onto the repository root.
for %%p in ("!build_dir!") do set "build_full=%%~fp" for %%p in ("!build_dir!") do set "build_full=%%~fp"
echo Configuration: %build_type%, %arch% echo Configuration: %build_type%, %arch%
if not "%clang_exe%" == "" echo Compiler: %clang_exe%
set "SIG_FLAG=" set "SIG_FLAG="
if defined ORCA_UPDATER_SIG_KEY set "SIG_FLAG=-DORCA_UPDATER_SIG_KEY=%ORCA_UPDATER_SIG_KEY%" if defined ORCA_UPDATER_SIG_KEY set "SIG_FLAG=-DORCA_UPDATER_SIG_KEY=%ORCA_UPDATER_SIG_KEY%"
@@ -734,6 +735,14 @@ REM worked out the same way in either run.
set "slicer_exe=%build_dir%\src\%build_type%\orca-slicer.exe" set "slicer_exe=%build_dir%\src\%build_type%\orca-slicer.exe"
if "%install_slicer%" == "ON" set "slicer_exe=%build_dir%\OrcaSlicer\orca-slicer.exe" if "%install_slicer%" == "ON" set "slicer_exe=%build_dir%\OrcaSlicer\orca-slicer.exe"
for %%p in ("!slicer_exe!") do set "slicer_full=%%~fp" for %%p in ("!slicer_exe!") do set "slicer_full=%%~fp"
REM The 2026 generator writes OrcaSlicer.slnx, the releases before it
REM OrcaSlicer.sln. A file already there wins, in case an older CMake
REM configured the build.
set "solution=OrcaSlicer.sln"
if "%vs_version%" == "2026" set "solution=OrcaSlicer.slnx"
if exist "!build_full!\OrcaSlicer.sln" set "solution=OrcaSlicer.sln"
if exist "!build_full!\OrcaSlicer.slnx" set "solution=OrcaSlicer.slnx"
REM Naming a target builds it and its dependencies, not its dependents, REM Naming a target builds it and its dependencies, not its dependents,
REM so only a full build or the executable's own target relinks. REM so only a full build or the executable's own target relinks.
set "linked=ON" set "linked=ON"
@@ -751,14 +760,14 @@ REM worked out the same way in either run.
if "%build_deps%" == "ON" echo Dependencies !dep_full! if "%build_deps%" == "ON" echo Dependencies !dep_full!
if "%build_slicer%" == "ON" if "%linked%" == "ON" echo OrcaSlicer !slicer_full! if "%build_slicer%" == "ON" if "%linked%" == "ON" echo OrcaSlicer !slicer_full!
if "%build_slicer%" == "ON" if not "%linked%" == "ON" echo Target %slicer_target% if "%build_slicer%" == "ON" if not "%linked%" == "ON" echo Target %slicer_target%
if "%build_slicer%" == "ON" if not "%using_ninja%" == "ON" echo Solution %build_full%\OrcaSlicer.sln if "%build_slicer%" == "ON" if not "%using_ninja%" == "ON" echo Solution %build_full%\!solution!
if "%pack_deps%" == "ON" if defined bundle echo Bundle !bundle! if "%pack_deps%" == "ON" if defined bundle echo Bundle !bundle!
echo. echo.
echo Next echo Next
if "%build_slicer%" == "ON" ( if "%build_slicer%" == "ON" (
if "%linked%" == "ON" echo Run it !slicer_exe! if "%linked%" == "ON" echo Run it !slicer_exe!
if not "%using_ninja%" == "ON" echo Open in Visual Studio %build_dir%\OrcaSlicer.sln if not "%using_ninja%" == "ON" echo Open in Visual Studio %build_dir%\!solution!
if "%linked%" == "ON" echo Rebuild after edits build_win.bat -s!recall! --no-configure if "%linked%" == "ON" echo Rebuild after edits build_win.bat -s!recall! --no-configure
if not "%linked%" == "ON" echo Relink the binary build_win.bat -s!recall! --no-configure if not "%linked%" == "ON" echo Relink the binary build_win.bat -s!recall! --no-configure
if "%linked%" == "ON" if "%using_ninja%" == "ON" echo Rebuild one target build_win.bat -s!recall! --no-configure --slicer-target libslic3r if "%linked%" == "ON" if "%using_ninja%" == "ON" echo Rebuild one target build_win.bat -s!recall! --no-configure --slicer-target libslic3r
@@ -1080,6 +1089,62 @@ REM echo_var <variable>
exit /b 0 exit /b 0
REM resolve_clang_cl - set clang_exe to the clang-cl a Ninja build uses.
REM VsDevCmd appends the Visual Studio LLVM directory to the end of PATH,
REM so a standalone LLVM already there shadows it. Name a full path.
:resolve_clang_cl
set "clang_exe="
if not "%using_ninja%" == "ON" exit /b 0
if not "%use_clang_cl%" == "ON" exit /b 0
REM Only a configure uses it, so -p and --no-configure need none.
if "%no_configure%" == "ON" exit /b 0
if "%build_deps%%build_slicer%" == "" exit /b 0
REM --clang-path wins. Forward slashes either way, so CMake does not
REM read a backslash as an escape.
if not "%clang_path%" == "" (
set "clang_exe=%clang_path:\=/%"
exit /b 0
)
setlocal
REM Keyed on the host, not %arch%, because the x64 compiler
REM cross-compiles to ARM64.
set "llvm_host=x64"
if /I "%PROCESSOR_ARCHITECTURE%" == "ARM64" set "llvm_host=ARM64"
set "found="
%VSWHERE% -nologo >nul 2>nul
if !errorlevel! == 0 (
for /f "tokens=*" %%i in ('%VSWHERE% -nologo -products * -latest -property resolvedInstallationPath') do (
if exist "%%i\VC\Tools\Llvm\!llvm_host!\bin\clang-cl.exe" (
set "found=%%i\VC\Tools\Llvm\!llvm_host!\bin\clang-cl.exe"
)
)
)
REM No clang toolset in Visual Studio. where lists every match, and the
REM first is the one PATH would resolve.
if "!found!" == "" (
for /f "tokens=*" %%i in ('where clang-cl.exe 2^>nul') do (
if "!found!" == "" set "found=%%i"
)
if not "!found!" == "" echo Visual Studio has no clang-cl; using !found! from PATH.
)
if "!found!" == "" (
echo No clang-cl found. Add the C++ Clang Compiler component with
echo %script_name% --install-vs ide -l
echo or name a standalone one with --clang-path.
endlocal
exit /b 1
)
endlocal & set "clang_exe=%found:\=/%"
exit /b 0
REM clean_tree <path> - remove a build tree, refusing anything that is not REM clean_tree <path> - remove a build tree, refusing anything that is not
REM one. Nothing here should ever fire; it is a floor under a bug that REM one. Nothing here should ever fire; it is a floor under a bug that
REM produced a path far shorter than it looks. REM produced a path far shorter than it looks.
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "Snapmaker", "name": "Snapmaker",
"version": "02.04.00.09", "version": "02.04.00.10",
"force_update": "0", "force_update": "0",
"description": "Snapmaker configurations", "description": "Snapmaker configurations",
"machine_model_list": [ "machine_model_list": [
@@ -184,7 +184,7 @@
"nozzle_type": "undefine", "nozzle_type": "undefine",
"auxiliary_fan": "0", "auxiliary_fan": "0",
"support_multi_bed_types": "1", "support_multi_bed_types": "1",
"default_bed_type": "4", "default_bed_type": "Textured PEI Plate",
"printable_area": [ "printable_area": [
"0.5x1", "0.5x1",
"270.5x1", "270.5x1",
+70 -10
View File
@@ -85,6 +85,24 @@ foreach ($v in @{ old = '1.11.1'; new = '1.12.0' }.GetEnumerator()) {
$ninjaPaths[$v.Key] = "$d;$env:PATH" $ninjaPaths[$v.Key] = "$d;$env:PATH"
} }
# A clang-cl earlier on PATH than the Visual Studio one, which is what the
# compiler used to resolve to. Nothing runs it; the script only locates it.
$clangDir = Join-Path $fixtures 'clang'
New-Item -ItemType Directory -Force -Path $clangDir | Out-Null
Copy-Item "$env:SystemRoot\System32\where.exe" (Join-Path $clangDir 'clang-cl.exe') -Force
$clangOnPath = "$clangDir;$env:PATH"
# ProgramFiles(x86) is where the script looks for vswhere, so an empty one
# stands in for a machine whose Visual Studio has no clang toolset.
$noVs = Join-Path $fixtures 'no-vs'
New-Item -ItemType Directory -Force -Path $noVs | Out-Null
# A build directory that already holds a classic solution, for the case where
# what is on disk disagrees with what the generator would write.
$slnDir = Join-Path $fixtures 'sln'
New-Item -ItemType Directory -Force -Path $slnDir | Out-Null
Set-Content -Path (Join-Path $slnDir 'OrcaSlicer.sln') -Value '' -Encoding ascii
# The pack stamp is checked against real dates, so a locale-dependent parse # 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, # in the script cannot pass by looking date-shaped. Yesterday is accepted too,
# so a run that crosses midnight does not flake. # so a run that crosses midnight does not flake.
@@ -131,7 +149,36 @@ $cases = @(
Contains = @('-G "Ninja Multi-Config"') Contains = @('-G "Ninja Multi-Config"')
NotContains = @('clang-cl', '-A x64') } NotContains = @('clang-cl', '-A x64') }
@{ Name = '-l -x builds with clang-cl under Ninja'; Args = @('-d', '-l', '-x') @{ 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') } Contains = @('-G "Ninja Multi-Config"')
Match = @('-DCMAKE_C_COMPILER="[^"]+/clang-cl\.exe"', '-DCMAKE_CXX_COMPILER="[^"]+/clang-cl\.exe"') }
# PATH order used to decide the compiler. VsDevCmd appends the Visual
# Studio LLVM directory to the end of PATH, so a standalone LLVM already
# there was resolved instead, and an old one failed the compiler check.
@{ Name = 'the compiler is resolved from Visual Studio, not PATH'; Args = @('-s', '-l', '-x')
Env = @{ PATH = $clangOnPath }
Match = @('^Compiler: .*/VC/Tools/Llvm/[^/]+/bin/clang-cl\.exe$') }
@{ Name = 'msvc names no compiler, having resolved none'; Args = @('-s')
NotContains = @('Compiler: ') }
@{ Name = '-l without -x names none either, the toolset picks it'; Args = @('-s', '-l')
NotContains = @('Compiler: ') }
# An empty ProgramFiles(x86) puts vswhere out of reach, which is a machine
# whose Visual Studio has no clang toolset.
@{ Name = 'without a Visual Studio clang the one on PATH is used and named'; Args = @('-s', '-l', '-x')
Env = @{ 'ProgramFiles(x86)' = $noVs; PATH = $clangOnPath }
Contains = @('Visual Studio has no clang-cl')
Match = @('^Compiler: .*/clang/clang-cl\.exe$') }
@{ Name = 'no clang-cl anywhere stops before configuring'; Args = @('-s', '-l', '-x'); ExpectExit = 1
Env = @{ 'ProgramFiles(x86)' = $noVs; PATH = 'C:\Windows\system32;C:\Windows' }
Contains = @('No clang-cl found', '--install-vs ide -l')
NotContains = @('cmake -B') }
# Only a configure passes the compiler to CMake, so an action that does
# not configure resolves none, and cannot start needing one installed.
@{ Name = 'packing resolves no compiler'; Args = @('-p', '-l', '-x')
Contains = @('Packing the dependencies')
NotContains = @('Compiler: ') }
@{ Name = '--no-configure resolves none either'; Args = @('-s', '-l', '-x', '--no-configure')
Contains = @('cmake --build "build-clang"')
NotContains = @('Compiler: ') }
@{ Name = '-l alone uses the ClangCL toolset on the VS generator'; Args = @('-d', '-l') @{ Name = '-l alone uses the ClangCL toolset on the VS generator'; Args = @('-d', '-l')
Contains = @('-G "Visual Studio', '-T ClangCL') Contains = @('-G "Visual Studio', '-T ClangCL')
NotContains = @('-DCMAKE_C_COMPILER') } NotContains = @('-DCMAKE_C_COMPILER') }
@@ -148,11 +195,13 @@ $cases = @(
NotContains = @('clang-cl') } NotContains = @('clang-cl') }
@{ Name = '--msbuild with -l gives the VS generator and the ClangCL toolset'; Args = @('-d', '--msbuild', '-l') @{ Name = '--msbuild with -l gives the VS generator and the ClangCL toolset'; Args = @('-d', '--msbuild', '-l')
Contains = @('-G "Visual Studio', '-T ClangCL') } Contains = @('-G "Visual Studio', '-T ClangCL') }
# A developer with a standalone LLVM points at it; the VS-bundled clang # A developer with a standalone LLVM points at it, and the path is passed
# is what a bare clang-cl.exe resolves to after the dev shell runs. # with forward slashes so CMake cannot read a backslash as an escape.
@{ Name = '--clang-path names the compiler, quoted for its spaces'; Args = @('-d', '-x', '--clang-path', 'C:\Program Files\LLVM\bin\clang-cl.exe') @{ 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"', Contains = @('-DCMAKE_C_COMPILER="C:/Program Files/LLVM/bin/clang-cl.exe"',
'-DCMAKE_CXX_COMPILER="C:\Program Files\LLVM\bin\clang-cl.exe"') } '-DCMAKE_CXX_COMPILER="C:/Program Files/LLVM/bin/clang-cl.exe"') }
@{ Name = '--clang-path beats the Visual Studio clang'; Args = @('-s', '-x', '--clang-path', 'C:\Program Files\LLVM\bin\clang-cl.exe')
Contains = @('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') @{ 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') } 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 @{ Name = '--clang-path needs Ninja to take effect'; Args = @('-d', '--clang-path', 'C:\Program Files\LLVM\bin\clang-cl.exe'); ExpectExit = 1
@@ -195,7 +244,8 @@ $cases = @(
@{ Name = 'the architecture is matched case-insensitively'; Args = @('-d', '--arch', 'ARM64') @{ Name = 'the architecture is matched case-insensitively'; Args = @('-d', '--arch', 'ARM64')
Contains = @('-A ARM64', 'deps/build-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') @{ 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') Contains = @('deps/build-clang-arm64')
Match = @('-DCMAKE_C_COMPILER="[^"]+/clang-cl\.exe"')
NotContains = @('-A ') } NotContains = @('-A ') }
'build configurations' 'build configurations'
@@ -699,18 +749,28 @@ $cases = @(
Contains = @('Next', 'Rebuild after edits') } Contains = @('Next', 'Rebuild after edits') }
'pointing at the solution' 'pointing at the solution'
# The extension follows the generator, so these two pin the release and a
# build directory that cannot already hold a solution of either kind.
@{ Name = 'the 2026 generator gets the XML solution'; Args = @('-s', '--vs', '2026', '--build-dir', 'D:\tree')
Contains = @('Solution D:\tree\OrcaSlicer.slnx', 'Open in Visual Studio D:\tree\OrcaSlicer.slnx') }
@{ Name = 'the releases before it get the classic one'; Args = @('-s', '--vs', '2022', '--build-dir', 'D:\tree')
Contains = @('Solution D:\tree\OrcaSlicer.sln', 'Open in Visual Studio D:\tree\OrcaSlicer.sln') }
@{ Name = 'a solution already on disk wins over the generator'; Args = @('-s', '--vs', '2026', '--build-dir', $slnDir)
Match = @('^ Solution .*\\OrcaSlicer\.sln$') }
# Extension-agnostic from here: these cases are about the directory, and
# the release is whatever is installed.
@{ Name = 'the VS generator says where the solution is'; Args = @('-s') @{ Name = 'the VS generator says where the solution is'; Args = @('-s')
Match = @('^ Solution .*\\build\\OrcaSlicer\.sln$') } Match = @('^ Solution .*\\build\\OrcaSlicer\.slnx?$') }
@{ Name = 'the solution path follows the configuration'; Args = @('-s', '--config', 'debug') @{ Name = 'the solution path follows the configuration'; Args = @('-s', '--config', 'debug')
Match = @('^ Solution .*\\build-dbg\\OrcaSlicer\.sln$') } Match = @('^ Solution .*\\build-dbg\\OrcaSlicer\.slnx?$') }
@{ Name = 'the solution line survives an install'; Args = @('-s', '-i') @{ Name = 'the solution line survives an install'; Args = @('-s', '-i')
Contains = @(' Solution ') } Contains = @(' Solution ') }
# The path is resolved, not pasted onto the repository root, so it is # The path is resolved, not pasted onto the repository root, so it is
# right whether --build-dir came absolute or with forward slashes. # 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') @{ Name = 'a moved build still prints one real path'; Args = @('-s', '--build-dir', 'out/build/x64-clang')
Match = @('^ Solution [A-Za-z]:\\[^/]+\\OrcaSlicer\.sln$') } Match = @('^ Solution [A-Za-z]:\\[^/]+\\OrcaSlicer\.slnx?$') }
@{ Name = 'an absolute --build-dir is not glued onto the repo root'; Args = @('-s', '--build-dir', 'D:\tree') @{ Name = 'an absolute --build-dir is not glued onto the repo root'; Args = @('-s', '--build-dir', 'D:\tree')
Contains = @('Solution D:\tree\OrcaSlicer.sln') } Match = @('^ Solution D:\\tree\\OrcaSlicer\.slnx?$') }
) )
function Invoke-BuildScript { function Invoke-BuildScript {
+1 -1
View File
@@ -7384,7 +7384,7 @@ void CLI::print_help(bool include_print_options, PrinterTechnology printer_techn
<< std::endl << std::endl
<< "Print setting priorities:" << std::endl << "Print setting priorities:" << std::endl
<< "\t1) setting values from the command line (highest priority)"<< std::endl << "\t1) setting values from the command line (highest priority)"<< std::endl
<< "\t2) setting values loaded with --load_settings and --load_filaments" << std::endl << "\t2) setting values loaded with --load-settings and --load-filaments" << std::endl
<< "\t3) setting values loaded from 3mf(lowest priority)" << std::endl; << "\t3) setting values loaded from 3mf(lowest priority)" << std::endl;
/*if (include_print_options) { /*if (include_print_options) {
+49 -4
View File
@@ -1715,6 +1715,36 @@ const ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key) co
return (it == options.end()) ? nullptr : it->second.get(); return (it == options.end()) ? nullptr : it->second.get();
} }
// ConfigOptionBool(s)::deserialize only understands "1" and "0", but scripts commonly spell CLI
// flags as --opt=true or --opt=no. Map the usual spellings onto what deserialize() accepts, per
// comma-separated item so vector options keep working, and pass anything else through unchanged
// so a genuine typo is still reported as invalid.
static std::string normalize_cli_bool_value(const std::string &value)
{
static const char* true_values[] = { "1", "true", "yes", "on", "enabled" };
static const char* false_values[] = { "0", "false", "no", "off", "disabled" };
auto matches = [](const std::string &item, const char* const* candidates, size_t count) {
return std::any_of(candidates, candidates + count, [&item](const char* candidate) { return boost::iequals(item, candidate); });
};
std::string normalized;
std::istringstream is(value);
std::string item;
while (std::getline(is, item, ',')) {
boost::trim(item);
if (! normalized.empty())
normalized += ",";
if (matches(item, true_values, std::size(true_values)))
normalized += "1";
else if (matches(item, false_values, std::size(false_values)))
normalized += "0";
else
normalized += item;
}
return normalized;
}
bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option_keys* extra, t_config_option_keys* keys) bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option_keys* extra, t_config_option_keys* keys)
{ {
// cache the CLI option => opt_key mapping // cache the CLI option => opt_key mapping
@@ -1812,17 +1842,32 @@ bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option
// to the end of the value. // to the end of the value.
if (opt_base->type() == coBools && value.empty()) if (opt_base->type() == coBools && value.empty())
static_cast<ConfigOptionBools*>(opt_base)->values.push_back(!no); static_cast<ConfigOptionBools*>(opt_base)->values.push_back(!no);
else else {
// Deserialize any other vector value (ConfigOptionInts, Floats, Percents, Points) the same way // Deserialize any other vector value (ConfigOptionInts, Floats, Percents, Points) the same way
// they get deserialized from an .ini file. For ConfigOptionStrings, that means that the C-style unescape // they get deserialized from an .ini file. For ConfigOptionStrings, that means that the C-style unescape
// will be applied for values enclosed in quotes, while values non-enclosed in quotes are left to be // will be applied for values enclosed in quotes, while values non-enclosed in quotes are left to be
// unescaped by the calling shell. // unescaped by the calling shell.
opt_vector->deserialize(value, true); const std::string vector_value = opt_base->type() == coBools ? normalize_cli_bool_value(value) : value;
bool deserialized = false;
try {
deserialized = opt_vector->deserialize(vector_value, true);
} catch (const std::exception &ex) {
// e.g. "nil" deserialized into a non-nullable vector option throws instead of
// returning false - treat that the same as any other invalid value here.
deserialized = false;
}
if (! deserialized) {
boost::nowide::cerr << "Invalid value for option --" << token.c_str() << std::endl;
return false;
}
}
} else if (opt_base->type() == coBool) { } else if (opt_base->type() == coBool) {
if (value.empty()) if (value.empty())
static_cast<ConfigOptionBool*>(opt_base)->value = !no; static_cast<ConfigOptionBool*>(opt_base)->value = !no;
else else if (! opt_base->deserialize(normalize_cli_bool_value(value))) {
opt_base->deserialize(value); boost::nowide::cerr << "Invalid value for option --" << token.c_str() << std::endl;
return false;
}
} else if (opt_base->type() == coString) { } else if (opt_base->type() == coString) {
// Do not unescape single string values, the unescaping is left to the calling shell. // Do not unescape single string values, the unescaping is left to the calling shell.
static_cast<ConfigOptionString*>(opt_base)->value = value; static_cast<ConfigOptionString*>(opt_base)->value = value;
-9
View File
@@ -33,15 +33,6 @@ public:
SurfaceFeature(const Vec3d& pt) SurfaceFeature(const Vec3d& pt)
: m_type{SurfaceFeatureType::Point}, m_pt1{pt} {} : m_type{SurfaceFeatureType::Point}, m_pt1{pt} {}
SurfaceFeature(const SurfaceFeature& sf){
this->clone(sf);
volume = sf.volume;
plane_indices = sf.plane_indices;
world_tran = sf.world_tran;
world_plane_features = sf.world_plane_features;
origin_surface_feature = sf.origin_surface_feature;
}
void clone(const SurfaceFeature &sf) void clone(const SurfaceFeature &sf)
{ {
m_type = sf.get_type(); m_type = sf.get_type();
-1
View File
@@ -39,7 +39,6 @@ namespace orientation {
float height_to_bottom_hull_ratio = 0; // affects stability, the lower the better float height_to_bottom_hull_ratio = 0; // affects stability, the lower the better
float unprintability = 0; float unprintability = 0;
Eigen::VectorXf areas_cooling; Eigen::VectorXf areas_cooling;
CostItems(CostItems const & other) = default;
CostItems() = default; CostItems() = default;
static std::string field_names() { static std::string field_names() {
return " overhang, bottom, bothull, contour, A_laf, A_prj, unprintability"; return " overhang, bottom, bothull, contour, A_laf, A_prj, unprintability";
-2
View File
@@ -195,7 +195,6 @@ public:
Point(int64_t x, int32_t y) : Vec2crd(coord_t(x), coord_t(y)) {} Point(int64_t x, int32_t y) : Vec2crd(coord_t(x), coord_t(y)) {}
Point(int32_t x, int64_t y) : Vec2crd(coord_t(x), coord_t(y)) {} Point(int32_t x, int64_t y) : Vec2crd(coord_t(x), coord_t(y)) {}
Point(double x, double y) : Vec2crd(coord_t(std::round(x)), coord_t(std::round(y))) {} Point(double x, double y) : Vec2crd(coord_t(std::round(x)), coord_t(std::round(y))) {}
Point(const Point &rhs) { *this = rhs; }
explicit Point(const Vec2d& rhs) : Vec2crd(coord_t(std::round(rhs.x())), coord_t(std::round(rhs.y()))) {} explicit Point(const Vec2d& rhs) : Vec2crd(coord_t(std::round(rhs.x())), coord_t(std::round(rhs.y()))) {}
// This constructor allows you to construct Point from Eigen expressions // This constructor allows you to construct Point from Eigen expressions
// This constructor has to be implicit (non-explicit) to allow implicit conversion from Eigen expressions. // This constructor has to be implicit (non-explicit) to allow implicit conversion from Eigen expressions.
@@ -278,7 +277,6 @@ public:
Point3(int32_t x, int32_t y, int32_t z = 0) : Vec3crd(coord_t(x), coord_t(y), coord_t(z)) {} Point3(int32_t x, int32_t y, int32_t z = 0) : Vec3crd(coord_t(x), coord_t(y), coord_t(z)) {}
Point3(int64_t x, int64_t y, int64_t z = 0) : Vec3crd(coord_t(x), coord_t(y), coord_t(z)) {} Point3(int64_t x, int64_t y, int64_t z = 0) : Vec3crd(coord_t(x), coord_t(y), coord_t(z)) {}
Point3(double x, double y, double z = 0.0) : Vec3crd(coord_t(std::round(x)), coord_t(std::round(y)), coord_t(std::round(z))) {} Point3(double x, double y, double z = 0.0) : Vec3crd(coord_t(std::round(x)), coord_t(std::round(y)), coord_t(std::round(z))) {}
Point3(const Point3 &rhs) { *this = rhs; }
explicit Point3(const Vec2crd& vec2crd, coord_t z = 0) : Vec3crd(vec2crd.x(), vec2crd.y(), z) {} explicit Point3(const Vec2crd& vec2crd, coord_t z = 0) : Vec3crd(vec2crd.x(), vec2crd.y(), z) {}
explicit Point3(const Vec3crd &vec3crd) : Vec3crd(vec3crd) {} explicit Point3(const Vec3crd &vec3crd) : Vec3crd(vec3crd) {}
// This constructor allows you to construct Point from Eigen expressions // This constructor allows you to construct Point from Eigen expressions
+9 -5
View File
@@ -983,15 +983,19 @@ BedType Preset::get_default_bed_type(PresetBundle* preset_bundle)
if (config.has("default_bed_type") && !config.opt_string("default_bed_type").empty()) { if (config.has("default_bed_type") && !config.opt_string("default_bed_type").empty()) {
try { try {
std::string str_bed_type = config.opt_string("default_bed_type"); std::string str_bed_type = config.opt_string("default_bed_type");
BedType bed_type;
if (ConfigOptionEnum<BedType>::from_string(str_bed_type, bed_type) &&
bed_type > btDefault && bed_type < btCount) {
return bed_type;
}
// Try parsing as integer first (legacy format) // Try parsing as integer (legacy format)
int bed_type_value = atoi(str_bed_type.c_str()); int bed_type_value = atoi(str_bed_type.c_str());
if (bed_type_value > 0) { if (bed_type_value > 0 && bed_type_value < BedType::btCount) {
return BedType(bed_type_value); return BedType(bed_type_value);
} }
else {
BOOST_LOG_TRIVIAL(error) << "default_bed_type: invalid bed type: " << str_bed_type; BOOST_LOG_TRIVIAL(error) << "default_bed_type: invalid bed type: " << str_bed_type;
}
return BedType::btPEI; return BedType::btPEI;
} catch(...) { } catch(...) {
+7 -7
View File
@@ -856,13 +856,12 @@ public:
protected: protected:
PresetCollection() = default; PresetCollection() = default;
// Copy constructor and copy operators are not to be used from outside PresetBundle, // Deleted by the std::recursive_mutex member. PresetBundle copies by assignment.
// as the Profile::vendor points to an instance of VendorProfile stored at parent PresetBundle! PresetCollection(const PresetCollection &other) = delete;
PresetCollection(const PresetCollection &other) = default; //BBS: hand-written because m_mutex cannot be copy-assigned.
//BBS: add operator= logic insteadof default
PresetCollection& operator=(const PresetCollection &other); PresetCollection& operator=(const PresetCollection &other);
// After copying a collection with the default operators above, call this function // Copying leaves every Preset::vendor pointing into the source bundle's vendor map.
// to adjust Profile::vendor pointers. // This re-points them at the matching entries in vendors.
void update_vendor_ptrs_after_copy(const VendorMap &vendors); void update_vendor_ptrs_after_copy(const VendorMap &vendors);
// Select a preset, if it exists. If it does not exist, select an invalid (-1) index. // Select a preset, if it exists. If it does not exist, select an invalid (-1) index.
@@ -1000,7 +999,8 @@ public:
bool only_default_printers() const; bool only_default_printers() const;
private: private:
PrinterPresetCollection() = default; PrinterPresetCollection() = default;
PrinterPresetCollection(const PrinterPresetCollection &other) = default; // Deleted along with the base copy constructor.
PrinterPresetCollection(const PrinterPresetCollection &other) = delete;
PrinterPresetCollection& operator=(const PrinterPresetCollection &other) = default; PrinterPresetCollection& operator=(const PrinterPresetCollection &other) = default;
friend class PresetBundle; friend class PresetBundle;
+10
View File
@@ -2941,6 +2941,16 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p
// If executed due to a Config Wizard update, preferred_printer contains the first newly installed printer, otherwise nullptr. // If executed due to a Config Wizard update, preferred_printer contains the first newly installed printer, otherwise nullptr.
const Preset *preferred_printer = printers.find_system_preset_by_model_and_variant(preferred_selection.printer_model_id, preferred_selection.printer_variant); const Preset *preferred_printer = printers.find_system_preset_by_model_and_variant(preferred_selection.printer_model_id, preferred_selection.printer_variant);
printers.select_preset_by_name(preferred_printer ? preferred_printer->name : initial_printer_profile_name, true); printers.select_preset_by_name(preferred_printer ? preferred_printer->name : initial_printer_profile_name, true);
Preset &selected_printer = printers.get_edited_preset();
if (selected_printer.printer_technology() == ptFFF) {
BedType bed_type = selected_printer.get_default_bed_type(this);
const std::string saved_bed_type = config.get_printer_setting(selected_printer.name, "curr_bed_type");
const int saved_bed_type_value = atoi(saved_bed_type.c_str());
if (saved_bed_type_value > btDefault && saved_bed_type_value < btCount)
bed_type = static_cast<BedType>(saved_bed_type_value);
project_config.set_key_value("curr_bed_type", new ConfigOptionEnum<BedType>(bed_type));
config.set("curr_bed_type", std::to_string(static_cast<int>(bed_type)));
}
CNumericLocalesSetter locales_setter; CNumericLocalesSetter locales_setter;
// Orca: load from orca_presets // Orca: load from orca_presets
+1 -7
View File
@@ -11990,13 +11990,11 @@ CLIActionsConfigDef::CLIActionsConfigDef()
def = this->add("load_defaultfila", coBool); def = this->add("load_defaultfila", coBool);
def->label = L("Load default filaments"); def->label = L("Load default filaments");
def->tooltip = L("Load first filament as default for those not loaded."); def->tooltip = L("Load first filament as default for those not loaded.");
def->cli_params = "option";
def->set_default_value(new ConfigOptionBool(false)); def->set_default_value(new ConfigOptionBool(false));
def = this->add("min_save", coBool); def = this->add("min_save", coBool);
def->label = L("Minimum save"); def->label = L("Minimum save");
def->tooltip = L("Export 3MF with minimum size."); def->tooltip = L("Export 3MF with minimum size.");
def->cli_params = "option";
def->set_default_value(new ConfigOptionBool(false)); def->set_default_value(new ConfigOptionBool(false));
def = this->add("mtcpp", coInt); def = this->add("mtcpp", coInt);
@@ -12022,7 +12020,6 @@ CLIActionsConfigDef::CLIActionsConfigDef()
def = this->add("normative_check", coBool); def = this->add("normative_check", coBool);
def->label = L("Normative check"); def->label = L("Normative check");
def->tooltip = L("Check the normative items."); def->tooltip = L("Check the normative items.");
def->cli_params = "option";
def->set_default_value(new ConfigOptionBool(true)); def->set_default_value(new ConfigOptionBool(true));
/*def = this->add("help_fff", coBool); /*def = this->add("help_fff", coBool);
@@ -12289,7 +12286,7 @@ CLIMiscConfigDef::CLIMiscConfigDef()
def->cli_params = "level"; def->cli_params = "level";
def->set_default_value(new ConfigOptionInt(1)); def->set_default_value(new ConfigOptionInt(1));
def = this->add("logfile", coInt); def = this->add("logfile", coString);
def->label = L("Log file"); def->label = L("Log file");
def->tooltip = L("Redirects debug logging to file.\n"); def->tooltip = L("Redirects debug logging to file.\n");
def->cli_params = "file"; def->cli_params = "file";
@@ -12337,7 +12334,6 @@ CLIMiscConfigDef::CLIMiscConfigDef()
def = this->add("skip_modified_gcodes", coBool); def = this->add("skip_modified_gcodes", coBool);
def->label = L("Skip modified G-code in 3MF"); def->label = L("Skip modified G-code in 3MF");
def->tooltip = L("Skip the modified G-code in 3MF from printer or filament presets."); def->tooltip = L("Skip the modified G-code in 3MF from printer or filament presets.");
def->cli_params = "option";
def->set_default_value(new ConfigOptionBool(false)); def->set_default_value(new ConfigOptionBool(false));
def = this->add("makerlab_name", coString); def = this->add("makerlab_name", coString);
@@ -12367,14 +12363,12 @@ CLIMiscConfigDef::CLIMiscConfigDef()
def = this->add("allow_newer_file", coBool); def = this->add("allow_newer_file", coBool);
def->label = L("Allow 3MF with newer version to be sliced"); def->label = L("Allow 3MF with newer version to be sliced");
def->tooltip = L("Allow 3MF with newer version to be sliced."); def->tooltip = L("Allow 3MF with newer version to be sliced.");
def->cli_params = "option";
def->set_default_value(new ConfigOptionBool(false)); def->set_default_value(new ConfigOptionBool(false));
def = this->add("allow_mix_temp", coBool); def = this->add("allow_mix_temp", coBool);
// internal use only, don't need translation // internal use only, don't need translation
def->label = "Allow filaments with high/low temperature to be printed together"; def->label = "Allow filaments with high/low temperature to be printed together";
def->tooltip = "Allow filaments with high/low temperature to be printed together."; def->tooltip = "Allow filaments with high/low temperature to be printed together.";
def->cli_params = "option";
def->set_default_value(new ConfigOptionBool(false)); def->set_default_value(new ConfigOptionBool(false));
} }
-3
View File
@@ -44,9 +44,6 @@ struct DrainHole
: pos(p), normal(n), radius(r), height(h), failed(fl) : pos(p), normal(n), radius(r), height(h), failed(fl)
{} {}
DrainHole(const DrainHole& rhs) :
DrainHole(rhs.pos, rhs.normal, rhs.radius, rhs.height, rhs.failed) {}
bool operator==(const DrainHole &sp) const; bool operator==(const DrainHole &sp) const;
bool operator!=(const DrainHole &sp) const { return !(sp == (*this)); } bool operator!=(const DrainHole &sp) const { return !(sp == (*this)); }
-4
View File
@@ -1234,10 +1234,6 @@ static void modulate_extrusion_by_overlapping_layers(
(fragment_end.is_start ? &polyline.points.front() : &polyline.points.back()); (fragment_end.is_start ? &polyline.points.front() : &polyline.points.back());
} }
private: private:
ExtrusionPathFragmentEndPointAccessor& operator=(const ExtrusionPathFragmentEndPointAccessor&) {
return *this;
}
const std::vector<ExtrusionPathFragment> &m_path_fragments; const std::vector<ExtrusionPathFragment> &m_path_fragments;
}; };
const coord_t search_radius = 7; const coord_t search_radius = 7;
+3 -2
View File
@@ -204,8 +204,9 @@ public:
clear_nodes(); clear_nodes();
} }
TreeSupportData(TreeSupportData&&) = default; // Deleted by the tbb::spin_mutex member.
TreeSupportData& operator=(TreeSupportData&&) = default; TreeSupportData(TreeSupportData&&) = delete;
TreeSupportData& operator=(TreeSupportData&&) = delete;
TreeSupportData(const TreeSupportData&) = delete; TreeSupportData(const TreeSupportData&) = delete;
TreeSupportData& operator=(const TreeSupportData&) = delete; TreeSupportData& operator=(const TreeSupportData&) = delete;
+3 -17
View File
@@ -91,29 +91,15 @@ class CaliPresetInfo
{ {
public: public:
int tray_id; int tray_id;
int extruder_id; int extruder_id = 0;
NozzleVolumeType nozzle_volume_type; NozzleVolumeType nozzle_volume_type{nvtStandard};
BedType bed_type; BedType bed_type{btDefault};
float nozzle_diameter; float nozzle_diameter;
int nozzle_pos_id{-1}; int nozzle_pos_id{-1};
std::string nozzle_sn; std::string nozzle_sn;
std::string filament_id; std::string filament_id;
std::string setting_id; std::string setting_id;
std::string name; std::string name;
CaliPresetInfo &operator=(const CaliPresetInfo &other)
{
this->tray_id = other.tray_id;
this->extruder_id = other.extruder_id;
this->nozzle_volume_type = other.nozzle_volume_type;
this->nozzle_diameter = other.nozzle_diameter;
this->nozzle_pos_id = other.nozzle_pos_id;
this->nozzle_sn = other.nozzle_sn;
this->filament_id = other.filament_id;
this->setting_id = other.setting_id;
this->name = other.name;
return *this;
}
}; };
struct PrinterCaliInfo struct PrinterCaliInfo
-10
View File
@@ -675,16 +675,6 @@ public:
offset = Vec2d(0, 0); offset = Vec2d(0, 0);
} }
TexturePart(const TexturePart& part) {
this->x = part.x;
this->y = part.y;
this->w = part.w;
this->h = part.h;
this->offset = part.offset;
this->buffer = part.buffer;
this->filename = part.filename;
this->texture = part.texture;
}
void update_pos(float xx, float yy, float ww, float hh) { void update_pos(float xx, float yy, float ww, float hh) {
x = xx; x = xx;
y = yy; y = yy;
+4 -1
View File
@@ -149,7 +149,10 @@ SavePresetDialog::Item::Item(Preset::Type type, const std::string &suffix, wxBox
// Set initial state (unchecked by default) // Set initial state (unchecked by default)
detach_checkbox->SetValue(m_detach); detach_checkbox->SetValue(m_detach);
// Bind the checkbox event to update the detach state for this item // Bind the checkbox event to update the detach state for this item
detach_checkbox->Bind(wxEVT_TOGGLEBUTTON, [this, detach_checkbox](wxCommandEvent&) { m_detach = detach_checkbox->GetValue(); }); detach_checkbox->Bind(wxEVT_TOGGLEBUTTON, [this, detach_checkbox](wxCommandEvent& event) {
m_detach = detach_checkbox->GetValue();
event.Skip(); // Let CheckBox update its bitmap for the new state.
});
detach_label->SetForegroundColour(wxColour("#363636")); detach_label->SetForegroundColour(wxColour("#363636"));
+1 -1
View File
@@ -5131,7 +5131,7 @@ void TabPrinter::build_fff()
optgroup->append_single_option_line("adaptive_bed_mesh_margin", "printer_basic_information_adaptive_bed_mesh#mesh-margin"); optgroup->append_single_option_line("adaptive_bed_mesh_margin", "printer_basic_information_adaptive_bed_mesh#mesh-margin");
optgroup = page->new_optgroup(L("Accessory"), "param_accessory"); optgroup = page->new_optgroup(L("Accessory"), "param_accessory");
optgroup->append_single_option_line("nozzle_type", "printer_basic_information_accessory#nozzle-type"); optgroup->append_single_option_line("nozzle_type", "printer_basic_information_accessory#nozzle-type", 0);
optgroup->append_single_option_line("nozzle_hrc", "printer_basic_information_accessory#nozzle-hrc"); optgroup->append_single_option_line("nozzle_hrc", "printer_basic_information_accessory#nozzle-hrc");
optgroup->append_single_option_line("auxiliary_fan", "printer_basic_information_accessory#auxiliary-part-cooling-fan"); optgroup->append_single_option_line("auxiliary_fan", "printer_basic_information_accessory#auxiliary-part-cooling-fan");
optgroup->append_single_option_line("fan_direction"); optgroup->append_single_option_line("fan_direction");
+223
View File
@@ -828,3 +828,226 @@ SCENARIO("ConfigOptionVector::set_to_index throws on incompatible type", "[Confi
} }
} }
} }
TEST_CASE("read_cli applies valid values and collects non-option arguments", "[Config]") {
Slic3r::DynamicPrintConfig config;
t_config_option_keys extra, keys;
const char* argv[] = {"orca-slicer", "--nozzle-temperature", "210,190", "--reduce-crossing-wall=1", "model.3mf"};
REQUIRE(config.read_cli(5, argv, &extra, &keys));
REQUIRE(config.opt<ConfigOptionInts>("nozzle_temperature")->values == std::vector<int>{210, 190});
REQUIRE(config.opt<ConfigOptionBool>("reduce_crossing_wall")->value);
REQUIRE(extra == t_config_option_keys{"model.3mf"});
REQUIRE(keys == t_config_option_keys{"nozzle_temperature", "reduce_crossing_wall"});
}
TEST_CASE("read_cli rejects nil for a non-nullable vector option", "[Config]") {
Slic3r::DynamicPrintConfig config;
t_config_option_keys extra, keys;
const char* argv[] = {"orca-slicer", "--nozzle-temperature", "nil"};
REQUIRE_FALSE(config.read_cli(3, argv, &extra, &keys));
}
TEST_CASE("read_cli rejects an invalid boolean value", "[Config]") {
Slic3r::DynamicPrintConfig config;
t_config_option_keys extra, keys;
const char* argv[] = {"orca-slicer", "--reduce-crossing-wall=maybe"};
REQUIRE_FALSE(config.read_cli(2, argv, &extra, &keys));
}
TEST_CASE("read_cli accepts the common spellings of a boolean value", "[Config]") {
const auto [text, expected] = GENERATE(table<const char*, bool>({
{"--reduce-crossing-wall=1", true },
{"--reduce-crossing-wall=true", true },
{"--reduce-crossing-wall=Yes", true },
{"--reduce-crossing-wall=on", true },
{"--reduce-crossing-wall=enabled", true },
{"--reduce-crossing-wall=TRUE", true },
{"--reduce-crossing-wall=oN", true },
{"--reduce-crossing-wall=0", false},
{"--reduce-crossing-wall=false", false},
{"--reduce-crossing-wall=No", false},
{"--reduce-crossing-wall=off", false},
{"--reduce-crossing-wall=disabled", false},
{"--reduce-crossing-wall=FALSE", false},
{"--reduce-crossing-wall=DiSaBlEd", false},
}));
DYNAMIC_SECTION(text) {
Slic3r::DynamicPrintConfig config;
t_config_option_keys extra, keys;
const char* argv[] = {"orca-slicer", text};
REQUIRE(config.read_cli(2, argv, &extra, &keys));
REQUIRE(config.opt<ConfigOptionBool>("reduce_crossing_wall")->value == expected);
}
}
TEST_CASE("read_cli accepts the common boolean spellings inside a bools vector", "[Config]") {
Slic3r::DynamicPrintConfig config;
t_config_option_keys extra, keys;
const char* argv[] = {"orca-slicer", "--filament-soluble=true,no,1"};
REQUIRE(config.read_cli(2, argv, &extra, &keys));
REQUIRE(config.opt<ConfigOptionBools>("filament_soluble")->values == std::vector<unsigned char>{1, 0, 1});
}
TEST_CASE("read_cli trims whitespace around boolean spellings", "[Config]") {
Slic3r::DynamicPrintConfig config;
t_config_option_keys extra, keys;
const char* argv[] = {"orca-slicer", "--reduce-crossing-wall= true ", "--filament-soluble= true , no ,1"};
REQUIRE(config.read_cli(3, argv, &extra, &keys));
REQUIRE(config.opt<ConfigOptionBool>("reduce_crossing_wall")->value);
REQUIRE(config.opt<ConfigOptionBools>("filament_soluble")->values == std::vector<unsigned char>{1, 0, 1});
}
TEST_CASE("read_cli normalizes boolean spellings when a bools vector is repeated", "[Config]") {
Slic3r::DynamicPrintConfig config;
t_config_option_keys extra, keys;
const char* argv[] = {"orca-slicer", "--filament-soluble=true", "--filament-soluble=off"};
REQUIRE(config.read_cli(3, argv, &extra, &keys));
REQUIRE(config.opt<ConfigOptionBools>("filament_soluble")->values == std::vector<unsigned char>{1, 0});
}
TEST_CASE("read_cli keeps nil alongside boolean spellings in a nullable bools vector", "[Config]") {
Slic3r::DynamicPrintConfig config;
t_config_option_keys extra, keys;
const char* argv[] = {"orca-slicer", "--enable-overhang-speed=nil,yes,off"};
REQUIRE(config.read_cli(2, argv, &extra, &keys));
auto* opt = config.opt<ConfigOptionBoolsNullable>("enable_overhang_speed");
REQUIRE(opt != nullptr);
REQUIRE(opt->values.size() == 3);
REQUIRE(opt->is_nil(0));
REQUIRE(opt->values[1] == 1);
REQUIRE(opt->values[2] == 0);
}
TEST_CASE("read_cli rejects an empty item inside a bools vector", "[Config]") {
Slic3r::DynamicPrintConfig config;
t_config_option_keys extra, keys;
const char* argv[] = {"orca-slicer", "--filament-soluble=true,,1"};
REQUIRE_FALSE(config.read_cli(2, argv, &extra, &keys));
}
TEST_CASE("read_cli rejects an unknown spelling next to a valid one in a bools vector", "[Config]") {
Slic3r::DynamicPrintConfig config;
t_config_option_keys extra, keys;
const char* argv[] = {"orca-slicer", "--filament-soluble=true,affirmative"};
REQUIRE_FALSE(config.read_cli(2, argv, &extra, &keys));
}
// The normalization lives in read_cli's boolean branches, so options of other types keep the
// value verbatim - a path named "on" or a colour named "true" must not turn into "1".
TEST_CASE("read_cli leaves boolean spellings alone for non-boolean options", "[Config]") {
SECTION("string option") {
Slic3r::DynamicPrintAndCLIConfig config;
t_config_option_keys extra, keys;
const char* argv[] = {"orca-slicer", "--logfile=true"};
REQUIRE(config.read_cli(2, argv, &extra, &keys));
REQUIRE(config.opt<ConfigOptionString>("logfile")->value == "true");
}
SECTION("strings vector option") {
Slic3r::DynamicPrintConfig config;
t_config_option_keys extra, keys;
const char* argv[] = {"orca-slicer", "--filament-colour=on;off"};
REQUIRE(config.read_cli(2, argv, &extra, &keys));
REQUIRE(config.opt<ConfigOptionStrings>("filament_colour")->values == std::vector<std::string>{"on", "off"});
}
}
TEST_CASE("read_cli treats a bare boolean flag as true without consuming the next argument", "[Config]") {
Slic3r::DynamicPrintConfig config;
t_config_option_keys extra, keys;
const char* argv[] = {"orca-slicer", "--reduce-crossing-wall", "model.3mf"};
REQUIRE(config.read_cli(3, argv, &extra, &keys));
REQUIRE(config.opt<ConfigOptionBool>("reduce_crossing_wall")->value);
REQUIRE(extra == t_config_option_keys{"model.3mf"});
}
TEST_CASE("read_cli rejects an invalid scalar numeric value", "[Config]") {
Slic3r::DynamicPrintConfig config;
t_config_option_keys extra, keys;
const char* argv[] = {"orca-slicer", "--top-shell-layers", "several"};
REQUIRE_FALSE(config.read_cli(3, argv, &extra, &keys));
}
TEST_CASE("read_cli appends values when a vector option is repeated", "[Config]") {
Slic3r::DynamicPrintConfig config;
t_config_option_keys extra, keys;
const char* argv[] = {"orca-slicer", "--nozzle-temperature", "210", "--nozzle-temperature", "190,200"};
REQUIRE(config.read_cli(5, argv, &extra, &keys));
REQUIRE(config.opt<ConfigOptionInts>("nozzle_temperature")->values == std::vector<int>{210, 190, 200});
// the key is recorded once, on first use
REQUIRE(keys == t_config_option_keys{"nozzle_temperature"});
}
TEST_CASE("read_cli parses a bools vector given in the --flag=values form", "[Config]") {
Slic3r::DynamicPrintConfig config;
t_config_option_keys extra, keys;
const char* argv[] = {"orca-slicer", "--filament-soluble=1,0,1"};
REQUIRE(config.read_cli(2, argv, &extra, &keys));
REQUIRE(config.opt<ConfigOptionBools>("filament_soluble")->values == std::vector<unsigned char>{1, 0, 1});
}
TEST_CASE("read_cli rejects an invalid value inside a bools vector", "[Config]") {
Slic3r::DynamicPrintConfig config;
t_config_option_keys extra, keys;
const char* argv[] = {"orca-slicer", "--filament-soluble=1,maybe"};
REQUIRE_FALSE(config.read_cli(2, argv, &extra, &keys));
}
TEST_CASE("read_cli appends true for a bare bools vector flag", "[Config]") {
Slic3r::DynamicPrintConfig config;
t_config_option_keys extra, keys;
const char* argv[] = {"orca-slicer", "--filament-soluble"};
REQUIRE(config.read_cli(2, argv, &extra, &keys));
REQUIRE(config.opt<ConfigOptionBools>("filament_soluble")->values == std::vector<unsigned char>{1});
}
TEST_CASE("read_cli splits a strings vector on semicolons and unescapes quoted items", "[Config]") {
Slic3r::DynamicPrintConfig config;
t_config_option_keys extra, keys;
const char* argv[] = {"orca-slicer", "--filament-colour", "#FF0000;\"a\\nb\";#00FF00"};
REQUIRE(config.read_cli(3, argv, &extra, &keys));
auto& values = config.opt<ConfigOptionStrings>("filament_colour")->values;
REQUIRE(values == std::vector<std::string>{"#FF0000", "a\nb", "#00FF00"});
}
TEST_CASE("read_cli rejects a strings vector with an unterminated quote", "[Config]") {
Slic3r::DynamicPrintConfig config;
t_config_option_keys extra, keys;
const char* argv[] = {"orca-slicer", "--filament-colour", "\"oops"};
REQUIRE_FALSE(config.read_cli(3, argv, &extra, &keys));
}
TEST_CASE("read_cli parses a points vector in the NxM coordinate form", "[Config]") {
Slic3r::DynamicPrintConfig config;
t_config_option_keys extra, keys;
const char* argv[] = {"orca-slicer", "--printable-area", "0x0,200x0,200x200,0x200"};
REQUIRE(config.read_cli(3, argv, &extra, &keys));
auto& points = config.opt<ConfigOptionPoints>("printable_area")->values;
REQUIRE(points.size() == 4);
REQUIRE_THAT(points[1].x(), Catch::Matchers::WithinAbs(200.0, 1e-9));
REQUIRE_THAT(points[1].y(), Catch::Matchers::WithinAbs(0.0, 1e-9));
REQUIRE_THAT(points[3].x(), Catch::Matchers::WithinAbs(0.0, 1e-9));
REQUIRE_THAT(points[3].y(), Catch::Matchers::WithinAbs(200.0, 1e-9));
}
// logfile is a CLI-only option, so it needs the config type whose def pulls in cli_misc_config_def.
TEST_CASE("read_cli stores the log file path as a string", "[Config]") {
Slic3r::DynamicPrintAndCLIConfig config;
t_config_option_keys extra, keys;
const char* argv[] = {"orca-slicer", "--logfile", "orca.log"};
REQUIRE(config.read_cli(3, argv, &extra, &keys));
REQUIRE(config.opt<ConfigOptionString>("logfile")->value == "orca.log");
}
TEST_CASE("read_cli accepts nil entries for a nullable vector option", "[Config]") {
Slic3r::DynamicPrintConfig config;
t_config_option_keys extra, keys;
const char* argv[] = {"orca-slicer", "--filament-retraction-length", "nil,2.5"};
REQUIRE(config.read_cli(3, argv, &extra, &keys));
auto* opt = config.opt<ConfigOptionFloatsNullable>("filament_retraction_length");
REQUIRE(opt != nullptr);
REQUIRE(opt->values.size() == 2);
REQUIRE(opt->is_nil(0));
REQUIRE_FALSE(opt->is_nil(1));
REQUIRE_THAT(opt->values[1], Catch::Matchers::WithinAbs(2.5, 1e-9));
}
@@ -220,6 +220,44 @@ TEST_CASE("Printer extruder count tolerates missing nozzle diameter", "[Preset][
CHECK(bundle.get_printer_extruder_count() == 2); CHECK(bundle.get_printer_extruder_count() == 2);
} }
TEST_CASE("Selected printer uses its default or saved bed type", "[Preset][Bundle]")
{
PresetBundle bundle;
Preset& printer = add_inmemory_preset(bundle.printers, "Test Printer");
printer.is_system = true;
printer.config.option<ConfigOptionString>("printer_model")->value = "TEST-MODEL";
printer.config.option<ConfigOptionString>("printer_variant")->value = "0.4";
printer.config.option<ConfigOptionString>("default_bed_type")->value = "Engineering Plate";
AppConfig app_config;
app_config.set("curr_bed_type", std::to_string(static_cast<int>(btPTE)));
PresetBundle::PresetPreferences preferred_selection;
BedType expected_bed_type;
SECTION("New printer uses its symbolic default") {
expected_bed_type = btEP;
preferred_selection = {"TEST-MODEL", "0.4"};
}
SECTION("Re-enabled printer uses its saved selection") {
expected_bed_type = btPC;
preferred_selection = {"TEST-MODEL", "0.4"};
app_config.set_printer_setting("Test Printer", "curr_bed_type",
std::to_string(static_cast<int>(expected_bed_type)));
}
SECTION("Existing printer keeps its saved selection after presets reload") {
expected_bed_type = btPCT;
app_config.set("presets", PRESET_PRINTER_NAME, "Test Printer");
app_config.set_printer_setting("Test Printer", "curr_bed_type",
std::to_string(static_cast<int>(expected_bed_type)));
}
bundle.load_selections(app_config, preferred_selection);
bundle.export_selections(app_config);
CHECK(bundle.project_config.opt_enum<BedType>("curr_bed_type") == expected_bed_type);
CHECK(app_config.get_printer_setting("Test Printer", "curr_bed_type") == std::to_string(static_cast<int>(expected_bed_type)));
}
TEST_CASE("find_preset resolves a system preset's renamed_from", "[Preset][Rename]") TEST_CASE("find_preset resolves a system preset's renamed_from", "[Preset][Rename]")
{ {
RenameTestCollection coll; RenameTestCollection coll;