mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-08 01:47:34 +03:00
Compare commits
38 Commits
fix/defaul
...
feature/ca
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7b49d9d68 | ||
|
|
f512872e35 | ||
|
|
cb093b8fba | ||
|
|
34fae25ed4 | ||
|
|
c014f0403b | ||
|
|
3765549786 | ||
|
|
d7bacd5c1c | ||
|
|
fd3bee3c62 | ||
|
|
fd80ded5a8 | ||
|
|
db2f861a11 | ||
|
|
2860353b9f | ||
|
|
218ec29d74 | ||
|
|
e5ed64a307 | ||
|
|
47fd55d462 | ||
|
|
b90439adba | ||
|
|
31270d4027 | ||
|
|
c9afb98ca2 | ||
|
|
d102157138 | ||
|
|
f088a18167 | ||
|
|
f2f5bea4bf | ||
|
|
0cd9e77e95 | ||
|
|
f35c2b1ef7 | ||
|
|
97dee9349b | ||
|
|
493597f132 | ||
|
|
517fa29d6f | ||
|
|
2ab9e14525 | ||
|
|
b6a1546ff5 | ||
|
|
6e36411736 | ||
|
|
0ec6c03c83 | ||
|
|
7a8f5a88c9 | ||
|
|
83e1712ded | ||
|
|
80fbf3b405 | ||
|
|
5b80d0cc07 | ||
|
|
88901a969f | ||
|
|
5d0c640f7b | ||
|
|
d861e8af22 | ||
|
|
6186436b23 | ||
|
|
604f15e20d |
42
.github/workflows/build_orca.yml
vendored
42
.github/workflows/build_orca.yml
vendored
@@ -144,6 +144,15 @@ jobs:
|
||||
run: |
|
||||
./build_release_macos.sh -s -n -x ${{ !vars.SELF_HOSTED && '-1' || '' }} -a ${{ inputs.arch }} -t 10.15
|
||||
|
||||
- name: Generate system presets cache (macOS)
|
||||
if: runner.os == 'macOS' && !inputs.macos-combine-only
|
||||
working-directory: ${{ github.workspace }}
|
||||
shell: bash
|
||||
run: |
|
||||
tool=$(find build/${{ inputs.arch }} -name generate_system_cache -type f | head -1)
|
||||
profiles=$(find build/${{ inputs.arch }} -path "*/Resources/profiles" -type d | head -1)
|
||||
"$tool" --path "$profiles" --log_level 2
|
||||
|
||||
- name: Pack macOS app bundle ${{ inputs.arch }}
|
||||
if: runner.os == 'macOS' && !inputs.macos-combine-only
|
||||
working-directory: ${{ github.workspace }}
|
||||
@@ -340,6 +349,23 @@ jobs:
|
||||
if ($arch -eq "arm64") { .\build_release_vs.bat slicer arm64 } else { .\build_release_vs.bat slicer }
|
||||
shell: pwsh
|
||||
|
||||
- name: Generate system presets cache (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
shell: pwsh
|
||||
run: |
|
||||
$buildDir = $env:BUILD_DIR
|
||||
$tool = Get-ChildItem -Recurse -Path $buildDir -Filter "generate_system_cache.exe" | Select-Object -First 1
|
||||
if (-not $tool) { Write-Error "generate_system_cache.exe not found in $buildDir"; exit 1 }
|
||||
$profiles = Get-ChildItem -Recurse -Path $buildDir -Directory -Filter profiles |
|
||||
Where-Object { $_.FullName -match 'resources' } | Select-Object -First 1
|
||||
if (-not $profiles) { Write-Error "profiles directory not found in $buildDir"; exit 1 }
|
||||
# Add the slicer's runtime DLL directory to PATH so generate_system_cache.exe
|
||||
# can resolve its dependencies (TKernel.dll etc.) without a full install step.
|
||||
$dll_dir = Get-ChildItem -Recurse -Path $buildDir -Filter "TKernel.dll" |
|
||||
Select-Object -First 1 | Select-Object -ExpandProperty DirectoryName
|
||||
if ($dll_dir) { $env:PATH = "$dll_dir;$env:PATH" }
|
||||
& $tool.FullName --path $profiles.FullName --log_level 2
|
||||
|
||||
# NSIS is x86-only; it runs (and the installer it emits runs) under ARM64's
|
||||
# x86 emulation, packaging the native arm64 payload from build-arm64.
|
||||
- name: Create installer Win
|
||||
@@ -477,6 +503,22 @@ jobs:
|
||||
retention-days: 5
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Generate system presets cache (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
shell: bash
|
||||
run: |
|
||||
tool=$(find build -name generate_system_cache -type f | head -1)
|
||||
if [ -z "$tool" ]; then echo "ERROR: generate_system_cache not found in build tree" >&2; exit 1; fi
|
||||
"$tool" --path build/package/resources/profiles --log_level 2
|
||||
# Re-pack the AppImage so the per-vendor caches are included
|
||||
appimage=$(find build -maxdepth 1 -name "OrcaSlicer_Linux_AppImage*.AppImage" | head -1)
|
||||
chmod +x "$appimage"
|
||||
"$appimage" --appimage-extract
|
||||
cp build/package/resources/profiles/*.cache squashfs-root/resources/profiles/
|
||||
appimagetool=$(find build -name "appimagetool.AppImage" | head -1)
|
||||
ARCH=$(uname -m) "$appimagetool" --appimage-extract-and-run squashfs-root "$appimage"
|
||||
rm -rf squashfs-root
|
||||
|
||||
- name: Run external slicer regression tests
|
||||
if: runner.os == 'Linux' && inputs.arch != 'aarch64'
|
||||
timeout-minutes: 20
|
||||
|
||||
@@ -567,6 +567,8 @@ if [[ -n "${BUILD_ORCA}" ]] || [[ -n "${BUILD_TESTS}" ]] ; then
|
||||
print_and_run cmake --build $BUILD_DIR --config "${BUILD_CONFIG}" --target OrcaSlicer
|
||||
echo "Building OrcaSlicer_profile_validator .."
|
||||
print_and_run cmake --build $BUILD_DIR --config "${BUILD_CONFIG}" --target OrcaSlicer_profile_validator
|
||||
echo "Building generate_system_cache ..."
|
||||
print_and_run cmake --build $BUILD_DIR --config "${BUILD_CONFIG}" --target generate_system_cache
|
||||
./scripts/run_gettext.sh
|
||||
fi
|
||||
if [[ -n "${BUILD_TESTS}" ]] ; then
|
||||
|
||||
45
gen_cache.bat
Normal file
45
gen_cache.bat
Normal file
@@ -0,0 +1,45 @@
|
||||
@echo off
|
||||
setlocal
|
||||
|
||||
:: Usage: gen_cache.bat [Release|Debug|RelWithDebInfo]
|
||||
:: Generates per-vendor .cache files into resources/profiles/
|
||||
:: using generate_system_cache.exe from the local build tree.
|
||||
|
||||
set BUILD_CONFIG=%~1
|
||||
if "%BUILD_CONFIG%"=="" set BUILD_CONFIG=Release
|
||||
|
||||
set REPO=%~dp0
|
||||
set BUILD_DIR=%REPO%build
|
||||
set EXE=%BUILD_DIR%\src\dev-utils\%BUILD_CONFIG%\generate_system_cache.exe
|
||||
set PROFILES=%REPO%resources\profiles
|
||||
|
||||
if not exist "%EXE%" (
|
||||
echo ERROR: %EXE% not found.
|
||||
echo Build the project first with ORCA_TOOLS=ON, config %BUILD_CONFIG%.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not exist "%PROFILES%" (
|
||||
echo ERROR: profiles dir not found: %PROFILES%
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
:: DLLs live in the main Release output, not next to the tool exe
|
||||
set PATH=%BUILD_DIR%\src\%BUILD_CONFIG%;%PATH%
|
||||
|
||||
echo Generating system presets cache...
|
||||
echo Tool : %EXE%
|
||||
echo Profiles: %PROFILES%
|
||||
echo.
|
||||
|
||||
"%EXE%" --path "%PROFILES%" --log_level 2
|
||||
if %ERRORLEVEL% neq 0 (
|
||||
echo.
|
||||
echo ERROR: generate_system_cache failed ^(exit %ERRORLEVEL%^).
|
||||
exit /b %ERRORLEVEL%
|
||||
)
|
||||
|
||||
echo.
|
||||
echo Done. Cache files written to: %PROFILES%
|
||||
dir /b "%PROFILES%\*.cache" 2>nul
|
||||
endlocal
|
||||
File diff suppressed because it is too large
Load Diff
51
remove_cache.bat
Normal file
51
remove_cache.bat
Normal file
@@ -0,0 +1,51 @@
|
||||
@echo off
|
||||
setlocal
|
||||
|
||||
:: Removes all OrcaSlicer cache files so the next launch rebuilds from scratch.
|
||||
::
|
||||
:: Clears:
|
||||
:: resources/profiles/*.cache bundled per-vendor binary caches
|
||||
:: %APPDATA%\OrcaSlicer\system\*.cache user per-vendor binary caches
|
||||
:: %APPDATA%\OrcaSlicer\guide_profile_cache.json guide JSON cache
|
||||
|
||||
set REPO=%~dp0
|
||||
set PROFILES=%REPO%resources\profiles
|
||||
set USER_SYSTEM=%APPDATA%\OrcaSlicer\system
|
||||
set GUIDE_CACHE=%APPDATA%\OrcaSlicer\guide_profile_cache.json
|
||||
|
||||
echo Removing OrcaSlicer cache files...
|
||||
echo.
|
||||
|
||||
:: Bundled caches
|
||||
set FOUND=0
|
||||
for %%F in ("%PROFILES%\*.cache") do (
|
||||
del "%%F"
|
||||
echo Deleted: %%~nxF ^(bundled^)
|
||||
set FOUND=1
|
||||
)
|
||||
if "%FOUND%"=="0" echo No bundled .cache files found in resources\profiles\
|
||||
|
||||
echo.
|
||||
|
||||
:: User per-vendor caches
|
||||
set FOUND=0
|
||||
for %%F in ("%USER_SYSTEM%\*.cache") do (
|
||||
del "%%F"
|
||||
echo Deleted: %%~nxF ^(user^)
|
||||
set FOUND=1
|
||||
)
|
||||
if "%FOUND%"=="0" echo No user .cache files found in %USER_SYSTEM%\
|
||||
|
||||
echo.
|
||||
|
||||
:: Guide JSON cache
|
||||
if exist "%GUIDE_CACHE%" (
|
||||
del "%GUIDE_CACHE%"
|
||||
echo Deleted: guide_profile_cache.json
|
||||
) else (
|
||||
echo No guide_profile_cache.json found
|
||||
)
|
||||
|
||||
echo.
|
||||
echo Done. Next launch will rebuild all caches from JSON.
|
||||
endlocal
|
||||
BIN
resources/profiles/Afinia.cache
Normal file
BIN
resources/profiles/Afinia.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Anker.cache
Normal file
BIN
resources/profiles/Anker.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Anycubic.cache
Normal file
BIN
resources/profiles/Anycubic.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Artillery.cache
Normal file
BIN
resources/profiles/Artillery.cache
Normal file
Binary file not shown.
BIN
resources/profiles/BBL.cache
Normal file
BIN
resources/profiles/BBL.cache
Normal file
Binary file not shown.
BIN
resources/profiles/BIQU.cache
Normal file
BIN
resources/profiles/BIQU.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Blocks.cache
Normal file
BIN
resources/profiles/Blocks.cache
Normal file
Binary file not shown.
BIN
resources/profiles/CONSTRUCT3D.cache
Normal file
BIN
resources/profiles/CONSTRUCT3D.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Chuanying.cache
Normal file
BIN
resources/profiles/Chuanying.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Co Print.cache
Normal file
BIN
resources/profiles/Co Print.cache
Normal file
Binary file not shown.
BIN
resources/profiles/CoLiDo.cache
Normal file
BIN
resources/profiles/CoLiDo.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Comgrow.cache
Normal file
BIN
resources/profiles/Comgrow.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Creality.cache
Normal file
BIN
resources/profiles/Creality.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Cubicon.cache
Normal file
BIN
resources/profiles/Cubicon.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Custom.cache
Normal file
BIN
resources/profiles/Custom.cache
Normal file
Binary file not shown.
BIN
resources/profiles/DeltaMaker.cache
Normal file
BIN
resources/profiles/DeltaMaker.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Dremel.cache
Normal file
BIN
resources/profiles/Dremel.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Elegoo.cache
Normal file
BIN
resources/profiles/Elegoo.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Eryone.cache
Normal file
BIN
resources/profiles/Eryone.cache
Normal file
Binary file not shown.
BIN
resources/profiles/FLSun.cache
Normal file
BIN
resources/profiles/FLSun.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Flashforge.cache
Normal file
BIN
resources/profiles/Flashforge.cache
Normal file
Binary file not shown.
BIN
resources/profiles/FlyingBear.cache
Normal file
BIN
resources/profiles/FlyingBear.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Folgertech.cache
Normal file
BIN
resources/profiles/Folgertech.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Geeetech.cache
Normal file
BIN
resources/profiles/Geeetech.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Ginger Additive.cache
Normal file
BIN
resources/profiles/Ginger Additive.cache
Normal file
Binary file not shown.
BIN
resources/profiles/InfiMech.cache
Normal file
BIN
resources/profiles/InfiMech.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Kingroon.cache
Normal file
BIN
resources/profiles/Kingroon.cache
Normal file
Binary file not shown.
BIN
resources/profiles/LH.cache
Normal file
BIN
resources/profiles/LH.cache
Normal file
Binary file not shown.
BIN
resources/profiles/LONGER.cache
Normal file
BIN
resources/profiles/LONGER.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Lulzbot.cache
Normal file
BIN
resources/profiles/Lulzbot.cache
Normal file
Binary file not shown.
BIN
resources/profiles/M3D.cache
Normal file
BIN
resources/profiles/M3D.cache
Normal file
Binary file not shown.
BIN
resources/profiles/MagicMaker.cache
Normal file
BIN
resources/profiles/MagicMaker.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Mellow.cache
Normal file
BIN
resources/profiles/Mellow.cache
Normal file
Binary file not shown.
BIN
resources/profiles/OpenEYE.cache
Normal file
BIN
resources/profiles/OpenEYE.cache
Normal file
Binary file not shown.
BIN
resources/profiles/OrcaArena.cache
Normal file
BIN
resources/profiles/OrcaArena.cache
Normal file
Binary file not shown.
BIN
resources/profiles/OrcaFilamentLibrary.cache
Normal file
BIN
resources/profiles/OrcaFilamentLibrary.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Peopoly.cache
Normal file
BIN
resources/profiles/Peopoly.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Phrozen.cache
Normal file
BIN
resources/profiles/Phrozen.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Positron3D.cache
Normal file
BIN
resources/profiles/Positron3D.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Prusa.cache
Normal file
BIN
resources/profiles/Prusa.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Qidi.cache
Normal file
BIN
resources/profiles/Qidi.cache
Normal file
Binary file not shown.
BIN
resources/profiles/RH3D.cache
Normal file
BIN
resources/profiles/RH3D.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Raise3D.cache
Normal file
BIN
resources/profiles/Raise3D.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Ratrig.cache
Normal file
BIN
resources/profiles/Ratrig.cache
Normal file
Binary file not shown.
BIN
resources/profiles/RolohaunDesign.cache
Normal file
BIN
resources/profiles/RolohaunDesign.cache
Normal file
Binary file not shown.
BIN
resources/profiles/SecKit.cache
Normal file
BIN
resources/profiles/SecKit.cache
Normal file
Binary file not shown.
BIN
resources/profiles/SeeMeCNC.cache
Normal file
BIN
resources/profiles/SeeMeCNC.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Snapmaker.cache
Normal file
BIN
resources/profiles/Snapmaker.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Sovol.cache
Normal file
BIN
resources/profiles/Sovol.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Tiertime.cache
Normal file
BIN
resources/profiles/Tiertime.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Tronxy.cache
Normal file
BIN
resources/profiles/Tronxy.cache
Normal file
Binary file not shown.
BIN
resources/profiles/TwoTrees.cache
Normal file
BIN
resources/profiles/TwoTrees.cache
Normal file
Binary file not shown.
BIN
resources/profiles/UltiMaker.cache
Normal file
BIN
resources/profiles/UltiMaker.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Vivedino.cache
Normal file
BIN
resources/profiles/Vivedino.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Volumic.cache
Normal file
BIN
resources/profiles/Volumic.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Voron.cache
Normal file
BIN
resources/profiles/Voron.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Voxelab.cache
Normal file
BIN
resources/profiles/Voxelab.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Vzbot.cache
Normal file
BIN
resources/profiles/Vzbot.cache
Normal file
Binary file not shown.
BIN
resources/profiles/WEMAKE3D.cache
Normal file
BIN
resources/profiles/WEMAKE3D.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Wanhao France.cache
Normal file
BIN
resources/profiles/Wanhao France.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Wanhao.cache
Normal file
BIN
resources/profiles/Wanhao.cache
Normal file
Binary file not shown.
BIN
resources/profiles/WonderMaker.cache
Normal file
BIN
resources/profiles/WonderMaker.cache
Normal file
Binary file not shown.
BIN
resources/profiles/Z-Bolt.cache
Normal file
BIN
resources/profiles/Z-Bolt.cache
Normal file
Binary file not shown.
BIN
resources/profiles/blacklist.cache
Normal file
BIN
resources/profiles/blacklist.cache
Normal file
Binary file not shown.
BIN
resources/profiles/iQ.cache
Normal file
BIN
resources/profiles/iQ.cache
Normal file
Binary file not shown.
BIN
resources/profiles/re3D.cache
Normal file
BIN
resources/profiles/re3D.cache
Normal file
Binary file not shown.
@@ -111,6 +111,8 @@ if(ORCA_TOOLS)
|
||||
endif()
|
||||
target_link_libraries(OrcaSlicer_profile_validator libslic3r boost_headeronly libcurl OpenSSL::SSL OpenSSL::Crypto)
|
||||
target_compile_definitions(OrcaSlicer_profile_validator PRIVATE -DBOOST_ALL_NO_LIB -DBOOST_USE_WINAPI_VERSION=0x602 -DBOOST_SYSTEM_USE_UTF8)
|
||||
|
||||
|
||||
endif()
|
||||
|
||||
# Create a slic3r executable
|
||||
|
||||
@@ -1317,7 +1317,13 @@ int CLI::run(int argc, char **argv)
|
||||
return CLI_INVALID_PARAMS;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "finished setup params, argc="<< argc << std::endl;
|
||||
std::string temp_path = wxFileName::GetTempDir().utf8_str().data();
|
||||
std::string temp_path = per_user_temp_dir(wxFileName::GetTempDir().utf8_str().data(), per_user_temp_id());
|
||||
// Some consumers write into the temp root directly, so create it up front.
|
||||
try {
|
||||
boost::filesystem::create_directories(temp_path);
|
||||
} catch (const std::exception &ex) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "failed to create per-user temp dir " << temp_path << ": " << ex.what();
|
||||
}
|
||||
set_temporary_dir(temp_path);
|
||||
|
||||
m_extra_config.apply(m_config, true);
|
||||
|
||||
@@ -20,6 +20,16 @@ if (SLIC3R_ENC_CHECK)
|
||||
)
|
||||
endif()
|
||||
|
||||
if (ORCA_TOOLS)
|
||||
set(_DEV_DEFS -DBOOST_ALL_NO_LIB -DBOOST_USE_WINAPI_VERSION=0x602 -DBOOST_SYSTEM_USE_UTF8)
|
||||
|
||||
# generate_system_cache: pre-generates per-vendor resources/profiles/<id>.cache files for CI bundling.
|
||||
add_executable(generate_system_cache generate_system_cache.cpp)
|
||||
target_link_libraries(generate_system_cache libslic3r boost_headeronly)
|
||||
target_compile_definitions(generate_system_cache PRIVATE ${_DEV_DEFS})
|
||||
|
||||
endif()
|
||||
|
||||
# Function that adds source file encoding check to a target
|
||||
# using the above encoding-check binary
|
||||
|
||||
|
||||
88
src/dev-utils/generate_system_cache.cpp
Normal file
88
src/dev-utils/generate_system_cache.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "libslic3r/Preset.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/program_options.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
#include <iostream>
|
||||
|
||||
using namespace Slic3r;
|
||||
namespace fs = boost::filesystem;
|
||||
namespace po = boost::program_options;
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
po::options_description desc("OrcaSlicer System Cache Generator\nUsage");
|
||||
// clang-format off
|
||||
desc.add_options()
|
||||
("help,h", "Show help")
|
||||
#ifdef __APPLE__
|
||||
("path,p", po::value<std::string>()->default_value("../../../../../../../resources/profiles"), "Path to profiles directory")
|
||||
#else
|
||||
("path,p", po::value<std::string>()->default_value("../../../resources/profiles"), "Path to profiles directory")
|
||||
#endif
|
||||
("log_level,l", po::value<int>()->default_value(2), "Log level (0=trace, 2=info, 4=error)");
|
||||
// clang-format on
|
||||
|
||||
po::variables_map vm;
|
||||
try {
|
||||
po::store(po::parse_command_line(argc, argv, desc), vm);
|
||||
if (vm.count("help")) { std::cout << desc << "\n"; return 0; }
|
||||
po::notify(vm);
|
||||
} catch (const po::error& e) {
|
||||
std::cerr << "Error: " << e.what() << "\n" << desc << "\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
const std::string profiles_path = vm["path"].as<std::string>();
|
||||
const int log_level = vm["log_level"].as<int>();
|
||||
|
||||
if (!fs::exists(profiles_path) || !fs::is_directory(profiles_path)) {
|
||||
std::cerr << "Error: '" << profiles_path << "' is not a valid directory\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
set_logging_level(log_level);
|
||||
set_data_dir(profiles_path);
|
||||
set_resources_dir(fs::path(profiles_path).parent_path().make_preferred().string());
|
||||
|
||||
const fs::path user_dir = fs::path(data_dir()) / PRESET_USER_DIR;
|
||||
if (!fs::exists(user_dir))
|
||||
fs::create_directories(user_dir);
|
||||
|
||||
AppConfig app_config;
|
||||
app_config.set("preset_folder", "default");
|
||||
|
||||
auto preset_bundle = std::make_unique<PresetBundle>();
|
||||
preset_bundle->set_is_validation_mode(true);
|
||||
preset_bundle->set_default_suppressed(true);
|
||||
|
||||
std::cout << "Loading system presets from: " << profiles_path << "\n";
|
||||
|
||||
try {
|
||||
preset_bundle->load_presets(app_config, ForwardCompatibilitySubstitutionRule::EnableSilent);
|
||||
} catch (const std::exception& ex) {
|
||||
std::cerr << "Failed to load presets: " << ex.what() << "\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
const std::string output_path =
|
||||
(fs::path(profiles_path) / "system_presets.cache").make_preferred().string();
|
||||
|
||||
std::cout << "Saving single-bundle cache to: " << output_path << "\n";
|
||||
|
||||
const auto stats = preset_bundle->save_system_presets_cache(profiles_path, output_path);
|
||||
|
||||
if (!stats.ok) {
|
||||
std::cerr << "ERROR: verification failed\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "[ok] system_presets.cache\n"
|
||||
<< " Total print presets: " << stats.print_presets << "\n"
|
||||
<< " Total filament presets: " << stats.filament_presets << "\n"
|
||||
<< " Total printer presets: " << stats.printer_presets << "\n";
|
||||
return 0;
|
||||
}
|
||||
@@ -61,7 +61,7 @@ static bool use_brim_efc_outline(const PrintObject &object)
|
||||
&& object.config().raft_layers.value == 0;
|
||||
}
|
||||
|
||||
//ORCA: Helper for snapping painted ears to the EFC outline.
|
||||
//ORCA: Helper for projecting painted ears to the EFC outline.
|
||||
static bool closest_point_on_expolygons(const ExPolygons &polygons, const Point &from, Point &closest_out)
|
||||
{
|
||||
double min_dist2 = std::numeric_limits<double>::max();
|
||||
@@ -69,23 +69,22 @@ static bool closest_point_on_expolygons(const ExPolygons &polygons, const Point
|
||||
|
||||
for (const ExPolygon &poly : polygons) {
|
||||
for (int i = 0; i < poly.num_contours(); ++i) {
|
||||
const Point *candidate = poly.contour_or_hole(i).closest_point(from);
|
||||
if (candidate == nullptr)
|
||||
continue;
|
||||
const int64_t dx = int64_t(candidate->x()) - int64_t(from.x());
|
||||
const int64_t dy = int64_t(candidate->y()) - int64_t(from.y());
|
||||
const double dist2 = double(dx * dx + dy * dy);
|
||||
if (dist2 < min_dist2) {
|
||||
min_dist2 = dist2;
|
||||
closest_out = *candidate;
|
||||
found = true;
|
||||
const Lines lines = poly.contour_or_hole(i).lines();
|
||||
for (const Line &line : lines) {
|
||||
Point candidate;
|
||||
const double dist2 = line.distance_to_squared(from, &candidate);
|
||||
if (dist2 < min_dist2) {
|
||||
min_dist2 = dist2;
|
||||
closest_out = candidate;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
//ORCA: Helper for matching painted ears to their original island before EFC snapping.
|
||||
//ORCA: Helper for matching painted ears to their original island before EFC projection.
|
||||
static int find_containing_expolygon_index(const ExPolygons &polygons, const Point &from)
|
||||
{
|
||||
for (size_t idx = 0; idx < polygons.size(); ++idx) {
|
||||
@@ -95,7 +94,7 @@ static int find_containing_expolygon_index(const ExPolygons &polygons, const Poi
|
||||
return -1;
|
||||
}
|
||||
|
||||
//ORCA: Keep painted ear snapping on the matching island when using EFC outline.
|
||||
//ORCA: Keep painted ear projection on the matching island when using EFC outline.
|
||||
static bool closest_point_on_matching_island(const ExPolygons &raw_outline, const ExPolygons &efc_outline, const Point &from, Point &closest_out)
|
||||
{
|
||||
const int island_idx = find_containing_expolygon_index(raw_outline, from);
|
||||
@@ -106,6 +105,7 @@ static bool closest_point_on_matching_island(const ExPolygons &raw_outline, cons
|
||||
}
|
||||
return closest_point_on_expolygons(efc_outline, from, closest_out);
|
||||
}
|
||||
|
||||
//ORCA: Use post-processed first-layer slices (including EFC) for brim outline.
|
||||
// Returns ExPolygons of the bottom layer after all first-layer modifiers
|
||||
// (including elephant foot compensation, if enabled) have been applied.
|
||||
@@ -358,11 +358,12 @@ static ExPolygons make_brim_ears(const PrintObject* object, const double& flowWi
|
||||
if (brim_ear_points.size() <= 0) {
|
||||
return mouse_ears_ex;
|
||||
}
|
||||
//ORCA: Painted ears can snap to the EFC-adjusted outline when enabled.
|
||||
//ORCA: Painted ears follow the EFC-adjusted outline when enabled, while
|
||||
// preserving their position along the selected outline segment.
|
||||
const bool use_efc_outline = use_brim_efc_outline(*object);
|
||||
const ExPolygons &raw_outline = object->layers().front()->lslices;
|
||||
//ORCA: Lazily computed EFC-adjusted bottom outline.
|
||||
//Stored separately so we can avoid recomputation unless EFC snapping is used.
|
||||
//Stored separately so we can avoid recomputation unless EFC projection is used.
|
||||
ExPolygons efc_outline_storage;
|
||||
const ExPolygons* efc_outline = nullptr;
|
||||
|
||||
@@ -390,17 +391,17 @@ static ExPolygons make_brim_ears(const PrintObject* object, const double& flowWi
|
||||
int32_t pt_x = scale_(pos.x());
|
||||
int32_t pt_y = scale_(pos.y());
|
||||
|
||||
//ORCA: Snap painted ears to the EFC-adjusted outline when enabled.
|
||||
//ORCA: Project painted ears to the EFC-adjusted outline when enabled.
|
||||
if (use_efc_outline) {
|
||||
if (efc_outline == nullptr) {
|
||||
//ORCA: Compute EFC-adjusted outline lazily for painted ear snapping.
|
||||
//ORCA: Compute the EFC-adjusted outline lazily for painted ear projection.
|
||||
efc_outline_storage = get_print_object_bottom_layer_expolygons(*object);
|
||||
efc_outline = &efc_outline_storage;
|
||||
}
|
||||
|
||||
if (!efc_outline->empty()) {
|
||||
Point closest_point;
|
||||
//ORCA: Snap within the matching island to avoid drifting to another island.
|
||||
//ORCA: Project within the matching island to avoid drifting to another island.
|
||||
if (closest_point_on_matching_island(
|
||||
raw_outline,
|
||||
*efc_outline,
|
||||
|
||||
@@ -147,6 +147,20 @@ Semver get_version_from_json(std::string file_path)
|
||||
return Semver();
|
||||
//throw ConfigurationError(format("Failed loading configuration file \"%1%\": %2%", file_path, err.what()));
|
||||
}
|
||||
catch(...) {
|
||||
return Semver();
|
||||
}
|
||||
}
|
||||
|
||||
std::string get_vendor_cache_key(const std::string& json_path)
|
||||
{
|
||||
const Semver ver = get_version_from_json(json_path);
|
||||
if (ver.valid())
|
||||
return ver.to_string();
|
||||
// No version field — use mtime as change fingerprint so edits invalidate the cache.
|
||||
boost::system::error_code ec;
|
||||
const std::time_t mtime = boost::filesystem::last_write_time(json_path, ec);
|
||||
return ec ? std::string{} : ("mtime:" + std::to_string(mtime));
|
||||
}
|
||||
|
||||
//BBS: add a function to load the key-values from xxx.json
|
||||
@@ -742,6 +756,7 @@ void Preset::save(DynamicPrintConfig* parent_config)
|
||||
idx_file.replace_extension(".info");
|
||||
this->save_info(idx_file.string());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Preset::reload(Preset const &parent)
|
||||
|
||||
@@ -16,6 +16,14 @@
|
||||
#include "Semver.hpp"
|
||||
#include "ProjectTask.hpp"
|
||||
|
||||
#include <cereal/archives/binary.hpp>
|
||||
#include <cereal/cereal.hpp>
|
||||
#include <cereal/types/map.hpp>
|
||||
#include <cereal/types/polymorphic.hpp>
|
||||
#include <cereal/types/set.hpp>
|
||||
#include <cereal/types/string.hpp>
|
||||
#include <cereal/types/vector.hpp>
|
||||
|
||||
//BBS: change system directories
|
||||
#define PRESET_SYSTEM_DIR "system"
|
||||
#define PRESET_USER_DIR "user"
|
||||
@@ -113,6 +121,10 @@ extern Semver get_version_from_json(std::string file_path);
|
||||
//BBS: add a function to load the key-values from xxx.json
|
||||
extern int get_values_from_json(std::string file_path, std::vector<std::string>& keys, std::map<std::string, std::string>& key_values);
|
||||
|
||||
// Returns the cache key for a vendor JSON: the Semver string for versioned
|
||||
// vendors, or "mtime:<unix_timestamp>" for vendors without a version field.
|
||||
extern std::string get_vendor_cache_key(const std::string& json_path);
|
||||
|
||||
extern ConfigFileType guess_config_file_type(const boost::property_tree::ptree &tree);
|
||||
|
||||
extern void extend_default_config_length(DynamicPrintConfig& config, const bool set_nil_to_default, const DynamicPrintConfig& defaults);
|
||||
@@ -130,6 +142,9 @@ public:
|
||||
PrinterVariant() {}
|
||||
PrinterVariant(const std::string &name) : name(name) {}
|
||||
std::string name;
|
||||
|
||||
template<class Archive>
|
||||
void serialize(Archive& ar) { ar(name); }
|
||||
};
|
||||
|
||||
struct PrinterModel {
|
||||
@@ -160,6 +175,15 @@ public:
|
||||
}
|
||||
|
||||
const PrinterVariant* variant(const std::string &name) const { return const_cast<PrinterModel*>(this)->variant(name); }
|
||||
|
||||
template<class Archive>
|
||||
void serialize(Archive& ar)
|
||||
{
|
||||
ar(id, name, model_id, family, technology, variants, default_materials,
|
||||
not_support_bed_types, bed_model, bed_texture, image_bed_type,
|
||||
bottom_texture_end_name, use_double_extruder_default_texture,
|
||||
bottom_texture_rect, middle_texture_rect, hotend_model);
|
||||
}
|
||||
};
|
||||
std::vector<PrinterModel> models;
|
||||
|
||||
@@ -171,6 +195,13 @@ public:
|
||||
|
||||
bool valid() const { return ! name.empty() && ! id.empty() && config_version.valid(); }
|
||||
|
||||
template<class Archive>
|
||||
void serialize(Archive& ar)
|
||||
{
|
||||
ar(id, name, config_version, config_update_url, changelog_url,
|
||||
models, default_filaments, default_sla_materials);
|
||||
}
|
||||
|
||||
// Load VendorProfile from an ini file.
|
||||
// If `load_all` is false, only the header with basic info (name, version, URLs) is loaded.
|
||||
static VendorProfile from_ini(const boost::filesystem::path &path, bool load_all=true);
|
||||
@@ -418,12 +449,31 @@ public:
|
||||
|
||||
// BBS: move constructor to public
|
||||
Preset(Type type, const std::string &name, bool is_default = false) : type(type), is_default(is_default), name(name) {}
|
||||
|
||||
protected:
|
||||
// Default constructor is public so cereal can default-construct elements when
|
||||
// deserializing std::vector<Preset> (std::allocator is not a cereal::access friend).
|
||||
Preset() = default;
|
||||
|
||||
protected:
|
||||
friend class PresetCollection;
|
||||
friend class PresetBundle;
|
||||
friend class cereal::access;
|
||||
|
||||
// Serializes all value fields of Preset for the binary vendor cache.
|
||||
// Raw pointers (vendor, loading_substitutions) are excluded — vendor is reconstructed
|
||||
// by apply_vendor_cache() from the VendorProfile stored alongside the presets.
|
||||
template<class Archive>
|
||||
void serialize(Archive& ar)
|
||||
{
|
||||
ar(type, name, alias, file, version,
|
||||
filament_id, setting_id, description,
|
||||
renamed_from, is_system, is_visible,
|
||||
is_default, is_external, is_dirty, is_compatible,
|
||||
is_project_embedded, loaded,
|
||||
m_from_orca_filament_lib, m_excluded_from,
|
||||
bundle_id, user_id, base_id, sync_info,
|
||||
updated_time, key_values, ini_str,
|
||||
config);
|
||||
}
|
||||
};
|
||||
|
||||
bool is_compatible_with_print (const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_print, const PresetWithVendorProfile &active_printer);
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <ctime>
|
||||
#include <sstream>
|
||||
|
||||
#include "PresetBundle.hpp"
|
||||
|
||||
#include <boost/crc.hpp>
|
||||
#include <cereal/archives/binary.hpp>
|
||||
#include <cereal/types/map.hpp>
|
||||
#include <cereal/types/string.hpp>
|
||||
#include <cereal/types/vector.hpp>
|
||||
#include "PrintConfig.hpp"
|
||||
#include "libslic3r.h"
|
||||
#include "I18N.hpp"
|
||||
@@ -520,6 +528,8 @@ PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, Forward
|
||||
|
||||
//BBS: add config related logs
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" enter, substitution_rule %1%, preferred printer_model_id %2%")%substitution_rule%preferred_selection.printer_model_id;
|
||||
const auto startup_t0 = std::chrono::steady_clock::now();
|
||||
|
||||
//BBS: change system config to json
|
||||
std::tie(substitutions, errors_cummulative) = this->load_system_presets_from_json(substitution_rule);
|
||||
|
||||
@@ -545,6 +555,12 @@ PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, Forward
|
||||
|
||||
set_calibrate_printer("");
|
||||
|
||||
{
|
||||
const auto total_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - startup_t0).count();
|
||||
BOOST_LOG_TRIVIAL(info) << "PresetBundle: all presets loaded in " << total_ms << " ms";
|
||||
}
|
||||
|
||||
//BBS: add config related logs
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" finished, returned substitutions %1%")%substitutions.size();
|
||||
return substitutions;
|
||||
@@ -955,6 +971,8 @@ PresetsConfigSubstitutions PresetBundle::load_user_presets(std::string user, For
|
||||
bundles.m_bundles.clear();
|
||||
bundles.WriteUnlock();
|
||||
|
||||
const auto user_load_t0 = std::chrono::steady_clock::now();
|
||||
|
||||
// Load bundle metadata from _local directory first
|
||||
fs::path local_dir(folder / PRESET_LOCAL_DIR);
|
||||
if (fs::exists(local_dir)) {
|
||||
@@ -973,7 +991,6 @@ PresetsConfigSubstitutions PresetBundle::load_user_presets(std::string user, For
|
||||
metadata.filament_presets.clear();
|
||||
metadata.printer_presets.clear();
|
||||
|
||||
// Add the profiles
|
||||
this->prints.load_presets(bundle_dir, PRESET_PRINT_NAME, substitutions, substitution_rule, [&](Preset& preset) {
|
||||
metadata.print_presets.push_back(preset.name);
|
||||
}, PresetOrigin(PresetOrigin::Kind::LocalBundle, metadata.id));
|
||||
@@ -1010,7 +1027,6 @@ PresetsConfigSubstitutions PresetBundle::load_user_presets(std::string user, For
|
||||
metadata.printer_presets.clear();
|
||||
metadata.is_subscribed = true;
|
||||
|
||||
// Load presets from bundle (same logic as __local__)
|
||||
this->prints.load_presets(bundle_dir, PRESET_PRINT_NAME, substitutions, substitution_rule, [&](Preset& preset) {
|
||||
metadata.print_presets.push_back(preset.name);
|
||||
}, PresetOrigin(PresetOrigin::Kind::SubscribedBundle, metadata.id));
|
||||
@@ -1031,34 +1047,41 @@ PresetsConfigSubstitutions PresetBundle::load_user_presets(std::string user, For
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// BBS do not load sla_print
|
||||
// BBS: change directoties by design
|
||||
try {
|
||||
std::string print_selected_preset_name = prints.get_selected_preset().name;
|
||||
this->prints.load_presets(dir_user_presets, PRESET_PRINT_NAME, substitutions, substitution_rule);
|
||||
prints.select_preset_by_name(print_selected_preset_name, false);
|
||||
} catch (const std::runtime_error &err) {
|
||||
errors_cummulative += err.what();
|
||||
// BBS: change directories by design
|
||||
|
||||
{
|
||||
const auto json_t0 = std::chrono::steady_clock::now();
|
||||
try {
|
||||
std::string sel = prints.get_selected_preset().name;
|
||||
this->prints.load_presets(dir_user_presets, PRESET_PRINT_NAME, substitutions, substitution_rule);
|
||||
prints.select_preset_by_name(sel, false);
|
||||
} catch (const std::runtime_error& err) { errors_cummulative += err.what(); }
|
||||
try {
|
||||
std::string sel = filaments.get_selected_preset().name;
|
||||
this->filaments.load_presets(dir_user_presets, PRESET_FILAMENT_NAME, substitutions, substitution_rule);
|
||||
filaments.select_preset_by_name(sel, false);
|
||||
} catch (const std::runtime_error& err) { errors_cummulative += err.what(); }
|
||||
try {
|
||||
std::string sel = printers.get_selected_preset().name;
|
||||
this->printers.load_presets(dir_user_presets, PRESET_PRINTER_NAME, substitutions, substitution_rule);
|
||||
printers.select_preset_by_name(sel, false);
|
||||
} catch (const std::runtime_error& err) { errors_cummulative += err.what(); }
|
||||
if (!errors_cummulative.empty()) throw Slic3r::RuntimeError(errors_cummulative);
|
||||
|
||||
const auto json_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - json_t0).count();
|
||||
BOOST_LOG_TRIVIAL(info) << "PresetBundle: user presets loaded from JSON in " << json_ms << " ms";
|
||||
}
|
||||
try {
|
||||
std::string filament_selected_preset_name = filaments.get_selected_preset().name;
|
||||
this->filaments.load_presets(dir_user_presets, PRESET_FILAMENT_NAME, substitutions, substitution_rule);
|
||||
filaments.select_preset_by_name(filament_selected_preset_name, false);
|
||||
} catch (const std::runtime_error &err) {
|
||||
errors_cummulative += err.what();
|
||||
|
||||
{
|
||||
const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - user_load_t0).count();
|
||||
BOOST_LOG_TRIVIAL(info) << "PresetBundle: user + bundle presets loaded in " << ms << " ms";
|
||||
}
|
||||
try {
|
||||
std::string printer_selected_preset_name = printers.get_selected_preset().name;
|
||||
this->printers.load_presets(dir_user_presets, PRESET_PRINTER_NAME, substitutions, substitution_rule);
|
||||
printers.select_preset_by_name(printer_selected_preset_name, false);
|
||||
} catch (const std::runtime_error &err) {
|
||||
errors_cummulative += err.what();
|
||||
}
|
||||
if (!errors_cummulative.empty()) throw Slic3r::RuntimeError(errors_cummulative);
|
||||
|
||||
this->update_multi_material_filament_presets();
|
||||
this->update_compatible(PresetSelectCompatibleType::Never);
|
||||
|
||||
set_calibrate_printer("");
|
||||
|
||||
return PresetsConfigSubstitutions();
|
||||
@@ -2197,9 +2220,26 @@ std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_pre
|
||||
if (validation_mode)
|
||||
dir = (boost::filesystem::path(data_dir())).make_preferred();
|
||||
|
||||
// Single-bundle cache: try user cache, then bundled cache, then JSON parse.
|
||||
bool loaded_from_cache = false;
|
||||
if (!validation_mode) {
|
||||
const auto t0 = std::chrono::steady_clock::now();
|
||||
const std::string expected_key = compute_system_presets_cache_key(dir.string());
|
||||
if (try_load_system_presets_from_cache(expected_key)) {
|
||||
update_system_maps();
|
||||
const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - t0).count();
|
||||
BOOST_LOG_TRIVIAL(info) << "PresetBundle: system presets loaded from single-bundle cache in " << ms << " ms";
|
||||
return {PresetsConfigSubstitutions{}, ""};
|
||||
}
|
||||
loaded_from_cache = false; // cache miss — fall through to JSON parse
|
||||
}
|
||||
|
||||
const auto json_load_t0 = std::chrono::steady_clock::now();
|
||||
PresetsConfigSubstitutions substitutions;
|
||||
std::string errors_cummulative;
|
||||
bool first = true;
|
||||
std::set<std::string> errored_vendors; // vendors whose JSON parse failed — skip their cache save
|
||||
bool first = true;
|
||||
std::vector<std::string> vendor_names;
|
||||
// store all vendor names in vendor_names
|
||||
for (auto& dir_entry : boost::filesystem::directory_iterator(dir)) {
|
||||
@@ -2237,6 +2277,7 @@ std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_pre
|
||||
throw err;
|
||||
errors_cummulative += err.what();
|
||||
errors_cummulative += "\n";
|
||||
errored_vendors.insert(orca_lib_vendor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2273,6 +2314,7 @@ std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_pre
|
||||
throw std::runtime_error(parallel_errors[i]);
|
||||
errors_cummulative += parallel_errors[i];
|
||||
errors_cummulative += "\n";
|
||||
errored_vendors.insert(other_vendors[i]);
|
||||
continue;
|
||||
}
|
||||
if (!parallel_bundles[i])
|
||||
@@ -2300,6 +2342,28 @@ std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_pre
|
||||
}
|
||||
|
||||
this->update_system_maps();
|
||||
|
||||
{
|
||||
const auto json_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - json_load_t0).count();
|
||||
BOOST_LOG_TRIVIAL(info) << "PresetBundle: system presets loaded from JSON in " << json_ms << " ms";
|
||||
}
|
||||
|
||||
// Save single-bundle cache after successful JSON parse.
|
||||
if (!validation_mode && errored_vendors.empty()) {
|
||||
const auto save_t0 = std::chrono::steady_clock::now();
|
||||
const auto stats = save_system_presets_cache(dir.string(), user_system_presets_cache_path());
|
||||
const auto save_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - save_t0).count();
|
||||
if (stats.ok)
|
||||
BOOST_LOG_TRIVIAL(info) << "PresetBundle: single-bundle cache saved in " << save_ms << " ms"
|
||||
<< " (print=" << stats.print_presets
|
||||
<< " filament=" << stats.filament_presets
|
||||
<< " printer=" << stats.printer_presets << ")";
|
||||
else
|
||||
BOOST_LOG_TRIVIAL(warning) << "PresetBundle: single-bundle cache save failed";
|
||||
}
|
||||
|
||||
//BBS: add config related logs
|
||||
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" finished, errors_cummulative %1%")%errors_cummulative;
|
||||
return std::make_pair(std::move(substitutions), errors_cummulative);
|
||||
@@ -5789,4 +5853,305 @@ bool BundleMetadata::save_to_json(const std::string& path) const
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// ---- System presets single-bundle cache implementation ------------------
|
||||
|
||||
namespace {
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct CacheFileHeader {
|
||||
uint32_t magic;
|
||||
uint32_t version;
|
||||
uint64_t data_size;
|
||||
uint32_t crc32;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
static_assert(sizeof(CacheFileHeader) == 20, "CacheFileHeader must be 20 bytes");
|
||||
|
||||
// Presets for one vendor, grouped so vendor pointers can be re-wired on load.
|
||||
struct VendorPresetGroup {
|
||||
std::vector<Preset> prints, filaments, printers, sla_prints, sla_materials;
|
||||
template<class Archive>
|
||||
void serialize(Archive& ar) { ar(prints, filaments, printers, sla_prints, sla_materials); }
|
||||
};
|
||||
|
||||
struct SystemPresetsCache {
|
||||
static constexpr uint32_t CACHE_MAGIC = 0x4F52435A; // "ORCZ"
|
||||
static constexpr uint32_t CACHE_VERSION = 1;
|
||||
|
||||
uint32_t cache_version = CACHE_VERSION;
|
||||
uint32_t config_options_count = 0;
|
||||
std::string bundle_key;
|
||||
|
||||
VendorMap vendors;
|
||||
std::map<std::string, VendorPresetGroup> preset_groups;
|
||||
std::map<std::string, DynamicPrintConfig> config_maps;
|
||||
std::map<std::string, std::string> filament_id_maps;
|
||||
|
||||
template<class Archive>
|
||||
void serialize(Archive& ar)
|
||||
{
|
||||
ar(cache_version, config_options_count, bundle_key,
|
||||
vendors, preset_groups, config_maps, filament_id_maps);
|
||||
}
|
||||
|
||||
bool is_valid(const std::string& expected_key) const
|
||||
{
|
||||
return cache_version == CACHE_VERSION
|
||||
&& config_options_count == static_cast<uint32_t>(print_config_def.options.size())
|
||||
&& bundle_key == expected_key;
|
||||
}
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// static
|
||||
std::string PresetBundle::bundled_system_presets_cache_path()
|
||||
{
|
||||
return (boost::filesystem::path(resources_dir()) / "profiles" / "system_presets.cache")
|
||||
.make_preferred().string();
|
||||
}
|
||||
|
||||
// static
|
||||
std::string PresetBundle::user_system_presets_cache_path()
|
||||
{
|
||||
return (boost::filesystem::path(data_dir()) / PRESET_SYSTEM_DIR / "system_presets.cache")
|
||||
.make_preferred().string();
|
||||
}
|
||||
|
||||
// static
|
||||
std::string PresetBundle::compute_system_presets_cache_key(const std::string& system_dir)
|
||||
{
|
||||
// Sorted map so key is stable regardless of directory iteration order.
|
||||
std::map<std::string, std::string> keys;
|
||||
try {
|
||||
for (const auto& e : boost::filesystem::directory_iterator(system_dir)) {
|
||||
if (Slic3r::is_json_file(e.path().string()))
|
||||
keys[e.path().stem().string()] = get_vendor_cache_key(e.path().string());
|
||||
}
|
||||
} catch (const std::exception& ex) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "SystemPresetsCache: cannot scan " << system_dir << ": " << ex.what();
|
||||
}
|
||||
std::string combined;
|
||||
for (const auto& [name, key] : keys)
|
||||
combined += name + ":" + key + ";";
|
||||
return combined;
|
||||
}
|
||||
|
||||
// static
|
||||
bool PresetBundle::read_cache_blob(const std::string& path, std::string& out_blob)
|
||||
{
|
||||
try {
|
||||
boost::nowide::ifstream ifs(path, std::ios::binary);
|
||||
if (!ifs.is_open())
|
||||
return false;
|
||||
CacheFileHeader fhdr;
|
||||
if (!ifs.read(reinterpret_cast<char*>(&fhdr), sizeof(fhdr)))
|
||||
return false;
|
||||
if (fhdr.magic != SystemPresetsCache::CACHE_MAGIC)
|
||||
return false;
|
||||
if (fhdr.data_size == 0 || fhdr.data_size > 512u * 1024u * 1024u)
|
||||
return false;
|
||||
out_blob.assign(fhdr.data_size, '\0');
|
||||
if (!ifs.read(&out_blob[0], static_cast<std::streamsize>(fhdr.data_size)))
|
||||
return false;
|
||||
boost::crc_32_type crc;
|
||||
crc.process_bytes(out_blob.data(), out_blob.size());
|
||||
if (crc.checksum() != fhdr.crc32) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "SystemPresetsCache: CRC mismatch: " << path;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "SystemPresetsCache: read failed (" << path << "): " << e.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
void PresetBundle::write_cache_blob(const std::string& path, const std::string& blob)
|
||||
{
|
||||
boost::crc_32_type crc;
|
||||
crc.process_bytes(blob.data(), blob.size());
|
||||
try {
|
||||
boost::filesystem::create_directories(boost::filesystem::path(path).parent_path());
|
||||
boost::nowide::ofstream ofs(path, std::ios::binary | std::ios::trunc);
|
||||
if (!ofs.is_open()) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "SystemPresetsCache: cannot open for writing: " << path;
|
||||
return;
|
||||
}
|
||||
CacheFileHeader fhdr;
|
||||
fhdr.magic = SystemPresetsCache::CACHE_MAGIC;
|
||||
fhdr.version = SystemPresetsCache::CACHE_VERSION;
|
||||
fhdr.data_size = static_cast<uint64_t>(blob.size());
|
||||
fhdr.crc32 = crc.checksum();
|
||||
ofs.write(reinterpret_cast<const char*>(&fhdr), sizeof(fhdr));
|
||||
ofs.write(blob.data(), static_cast<std::streamsize>(blob.size()));
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "SystemPresetsCache: write failed (" << path << "): " << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
// Apply one VendorPresetGroup from a loaded cache into this PresetBundle's collections.
|
||||
static void apply_vendor_preset_group(VendorPresetGroup& grp,
|
||||
const VendorProfile* vp,
|
||||
PresetCollection& prints_coll,
|
||||
PresetCollection& filaments_coll,
|
||||
PrinterPresetCollection& printers_coll,
|
||||
PresetCollection& sla_prints_coll,
|
||||
PresetCollection& sla_materials_coll)
|
||||
{
|
||||
auto apply = [&](std::vector<Preset>& cached, PresetCollection& coll, bool is_filaments) {
|
||||
for (Preset& cp : cached) {
|
||||
// Reserve a slot in the collection, then move the fully-deserialized
|
||||
// preset into it so all serialized fields are transferred at once.
|
||||
// Only vendor (a raw pointer, excluded from serialization) is patched after.
|
||||
DynamicPrintConfig config = cp.config;
|
||||
Preset& p = coll.load_preset(cp.file, cp.name, std::move(config), false, cp.version);
|
||||
const std::string alias = cp.alias;
|
||||
p = std::move(cp);
|
||||
p.vendor = vp;
|
||||
if (is_filaments)
|
||||
coll.set_printer_hold_alias(alias, p);
|
||||
}
|
||||
};
|
||||
apply(grp.prints, prints_coll, false);
|
||||
apply(grp.filaments, filaments_coll, true);
|
||||
apply(grp.printers, printers_coll, false);
|
||||
apply(grp.sla_prints, sla_prints_coll, false);
|
||||
apply(grp.sla_materials, sla_materials_coll, false);
|
||||
}
|
||||
|
||||
bool PresetBundle::try_load_system_presets_from_cache(const std::string& expected_key)
|
||||
{
|
||||
auto try_path = [&](const std::string& path) -> bool {
|
||||
std::string blob;
|
||||
if (!read_cache_blob(path, blob))
|
||||
return false;
|
||||
try {
|
||||
std::istringstream iss(blob);
|
||||
cereal::BinaryInputArchive ar(iss);
|
||||
SystemPresetsCache cache;
|
||||
ar(cache);
|
||||
if (!cache.is_valid(expected_key))
|
||||
return false;
|
||||
|
||||
this->reset(false);
|
||||
vendors = std::move(cache.vendors);
|
||||
|
||||
for (auto& [vendor_id, grp] : cache.preset_groups) {
|
||||
auto it = vendors.find(vendor_id);
|
||||
if (it == vendors.end()) continue;
|
||||
apply_vendor_preset_group(grp, &it->second,
|
||||
prints, filaments, printers,
|
||||
sla_prints, sla_materials);
|
||||
}
|
||||
m_config_maps = std::move(cache.config_maps);
|
||||
m_filament_id_maps = std::move(cache.filament_id_maps);
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "SystemPresetsCache: apply failed (" << path << "): " << e.what();
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
if (try_path(user_system_presets_cache_path()))
|
||||
return true;
|
||||
if (try_path(bundled_system_presets_cache_path())) {
|
||||
// Promote bundled cache to user path so future loads skip JSON parse.
|
||||
const std::string user_path = user_system_presets_cache_path();
|
||||
const std::string bundled_path = bundled_system_presets_cache_path();
|
||||
try {
|
||||
boost::filesystem::create_directories(
|
||||
boost::filesystem::path(user_path).parent_path());
|
||||
boost::filesystem::copy_file(bundled_path, user_path,
|
||||
boost::filesystem::copy_options::overwrite_existing);
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "SystemPresetsCache: promote failed: " << e.what();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
PresetBundle::SaveCacheResult PresetBundle::save_system_presets_cache(
|
||||
const std::string& profiles_dir, const std::string& output_path) const
|
||||
{
|
||||
SystemPresetsCache cache;
|
||||
cache.config_options_count = static_cast<uint32_t>(print_config_def.options.size());
|
||||
cache.bundle_key = compute_system_presets_cache_key(profiles_dir);
|
||||
cache.vendors = vendors;
|
||||
cache.config_maps = m_config_maps;
|
||||
cache.filament_id_maps = m_filament_id_maps;
|
||||
|
||||
auto fill = [&](const PresetCollection& coll,
|
||||
std::vector<Preset> VendorPresetGroup::*field) {
|
||||
for (const Preset& p : coll())
|
||||
if (p.is_system && p.vendor)
|
||||
(cache.preset_groups[p.vendor->id].*field).push_back(p);
|
||||
};
|
||||
fill(prints, &VendorPresetGroup::prints);
|
||||
fill(filaments, &VendorPresetGroup::filaments);
|
||||
fill(sla_prints, &VendorPresetGroup::sla_prints);
|
||||
fill(sla_materials, &VendorPresetGroup::sla_materials);
|
||||
// PrinterPresetCollection is a PresetCollection subclass; iterate directly.
|
||||
for (const Preset& p : printers())
|
||||
if (p.is_system && p.vendor)
|
||||
cache.preset_groups[p.vendor->id].printers.push_back(p);
|
||||
|
||||
std::ostringstream oss;
|
||||
{ cereal::BinaryOutputArchive ar(oss); ar(cache); }
|
||||
const std::string blob = oss.str();
|
||||
write_cache_blob(output_path, blob);
|
||||
|
||||
SaveCacheResult result;
|
||||
// Verify: read back and check validity.
|
||||
if (std::string vblob; read_cache_blob(output_path, vblob)) {
|
||||
try {
|
||||
std::istringstream iss(vblob);
|
||||
cereal::BinaryInputArchive ar(iss);
|
||||
SystemPresetsCache v; ar(v);
|
||||
result.ok = v.is_valid(cache.bundle_key);
|
||||
} catch (...) {}
|
||||
}
|
||||
for (const auto& [vid, grp] : cache.preset_groups) {
|
||||
result.print_presets += grp.prints.size();
|
||||
result.filament_presets += grp.filaments.size();
|
||||
result.printer_presets += grp.printers.size();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// static
|
||||
bool PresetBundle::load_system_presets_cache_for_guide(const std::string& cache_path,
|
||||
PresetBundle& out_bundle)
|
||||
{
|
||||
std::string blob;
|
||||
if (!read_cache_blob(cache_path, blob))
|
||||
return false;
|
||||
try {
|
||||
std::istringstream iss(blob);
|
||||
cereal::BinaryInputArchive ar(iss);
|
||||
SystemPresetsCache cache;
|
||||
ar(cache);
|
||||
if (cache.cache_version != SystemPresetsCache::CACHE_VERSION)
|
||||
return false;
|
||||
|
||||
out_bundle.vendors = std::move(cache.vendors);
|
||||
for (auto& [vendor_id, grp] : cache.preset_groups) {
|
||||
auto it = out_bundle.vendors.find(vendor_id);
|
||||
if (it == out_bundle.vendors.end()) continue;
|
||||
apply_vendor_preset_group(grp, &it->second,
|
||||
out_bundle.prints,
|
||||
out_bundle.filaments,
|
||||
out_bundle.printers,
|
||||
out_bundle.sla_prints,
|
||||
out_bundle.sla_materials);
|
||||
}
|
||||
return !out_bundle.vendors.empty();
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "SystemPresetsCache: guide load failed (" << cache_path << "): " << e.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <unordered_set>
|
||||
|
||||
|
||||
#define DEFAULT_USER_FOLDER_NAME "default"
|
||||
#define BUNDLE_STRUCTURE_JSON_NAME "bundle_structure.json"
|
||||
|
||||
@@ -151,6 +152,32 @@ struct PresetBundleMetadata
|
||||
class PresetBundle
|
||||
{
|
||||
public:
|
||||
// ---- System presets single-bundle cache --------------------------------
|
||||
// All vendor profiles + all system presets in one file.
|
||||
// Bundled (CI): resources/profiles/system_presets.cache
|
||||
// User runtime: data_dir/system/system_presets.cache
|
||||
|
||||
struct SaveCacheResult {
|
||||
bool ok = false;
|
||||
size_t print_presets = 0;
|
||||
size_t filament_presets = 0;
|
||||
size_t printer_presets = 0;
|
||||
};
|
||||
|
||||
static std::string bundled_system_presets_cache_path();
|
||||
static std::string user_system_presets_cache_path();
|
||||
|
||||
// Capture the currently-loaded PresetBundle and write a single-bundle cache
|
||||
// to output_path. profiles_dir contains the vendor JSON files (for the key).
|
||||
// Used by generate_system_cache and tests.
|
||||
SaveCacheResult save_system_presets_cache(const std::string& profiles_dir,
|
||||
const std::string& output_path) const;
|
||||
|
||||
// Load a single-bundle cache into out_bundle for the guide wizard.
|
||||
// Returns false if the cache is missing, stale, or corrupt.
|
||||
static bool load_system_presets_cache_for_guide(const std::string& cache_path,
|
||||
PresetBundle& out_bundle);
|
||||
|
||||
static DynamicPrintConfig construct_full_config(Preset &in_printer_preset,
|
||||
Preset &in_print_preset,
|
||||
const DynamicPrintConfig &project_config,
|
||||
@@ -493,6 +520,15 @@ public:
|
||||
bool check_preset_references() const;
|
||||
|
||||
private:
|
||||
// Compute combined invalidation key from all vendor JSON files in system_dir.
|
||||
static std::string compute_system_presets_cache_key(const std::string& system_dir);
|
||||
// Try user cache then bundled cache; apply all presets on hit.
|
||||
bool try_load_system_presets_from_cache(const std::string& expected_key);
|
||||
// Read raw cache blob: verify magic, size, CRC.
|
||||
static bool read_cache_blob(const std::string& path, std::string& out_blob);
|
||||
// Write a cache blob with the standard 20-byte file header.
|
||||
static void write_cache_blob(const std::string& path, const std::string& blob);
|
||||
|
||||
// Orca: validation only - flag any printer with two or more compatible
|
||||
// filament presets sharing one filament_id (ambiguous AMS subtype match).
|
||||
bool check_duplicate_filament_subtypes() const;
|
||||
|
||||
@@ -2192,7 +2192,8 @@ namespace cereal {
|
||||
archive(serialization_key_ordinal);
|
||||
assert(serialization_key_ordinal > 0);
|
||||
auto it = Slic3r::print_config_def.by_serialization_key_ordinal.find(serialization_key_ordinal);
|
||||
assert(it != Slic3r::print_config_def.by_serialization_key_ordinal.end());
|
||||
if (it == Slic3r::print_config_def.by_serialization_key_ordinal.end())
|
||||
throw std::runtime_error("VendorCache: unknown serialization_key_ordinal " + std::to_string(serialization_key_ordinal) + " - cache is stale");
|
||||
config.set_key_value(it->second->opt_key, it->second->load_option_from_archive(archive));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,6 +190,16 @@ public:
|
||||
os << self.to_string();
|
||||
return os;
|
||||
}
|
||||
|
||||
// cereal: round-trip through the standard 3-part string (major.minor.patch).
|
||||
// to_string() uses a BBS 4-part format that semver_parse() cannot read back.
|
||||
template<class Archive>
|
||||
std::string save_minimal(const Archive&) const { return to_string_sf(); }
|
||||
template<class Archive>
|
||||
void load_minimal(const Archive&, const std::string& s) {
|
||||
if (auto v = Semver::parse(s)) *this = std::move(*v);
|
||||
}
|
||||
|
||||
private:
|
||||
semver_t ver;
|
||||
|
||||
|
||||
@@ -300,6 +300,10 @@ std::string header_gcodeviewer_generated();
|
||||
|
||||
// getpid platform wrapper
|
||||
extern unsigned get_current_pid();
|
||||
// Per-user id for isolating temp dirs; empty on Windows (its temp dir is already per-user).
|
||||
std::string per_user_temp_id();
|
||||
// Per-user temp root under `base`; an empty `user_id` returns `base` unchanged.
|
||||
std::string per_user_temp_dir(const std::string &base, const std::string &user_id);
|
||||
// BBS: backup & restore
|
||||
std::string get_process_name(int pid);
|
||||
|
||||
|
||||
@@ -1285,6 +1285,24 @@ unsigned get_current_pid()
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string per_user_temp_id()
|
||||
{
|
||||
#ifdef WIN32
|
||||
return {};
|
||||
#else
|
||||
return std::to_string(static_cast<unsigned long>(::getuid()));
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string per_user_temp_dir(const std::string &base, const std::string &user_id)
|
||||
{
|
||||
if (user_id.empty())
|
||||
return base;
|
||||
// Keep the id at the top level so each user's dir sits directly in the world-writable temp
|
||||
// root; a shared parent dir would be owned by whichever user created it first.
|
||||
return base + "/orcaslicer_" + user_id;
|
||||
}
|
||||
|
||||
// BBS: backup & restore
|
||||
std::string get_process_name(int pid)
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "ConfigWizard.hpp"
|
||||
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/iostreams/detail/select.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
@@ -9,6 +10,7 @@
|
||||
#include "I18N.hpp"
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/Preset.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "slic3r/GUI/wxExtensions.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
@@ -41,7 +43,32 @@ using namespace nlohmann;
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
json m_ProfileJson;
|
||||
static std::string guide_json_cache_version_key(const boost::filesystem::path& rsrc_dir,
|
||||
const boost::filesystem::path& user_dir)
|
||||
{
|
||||
std::vector<std::string> parts;
|
||||
auto collect = [&](const boost::filesystem::path& dir) {
|
||||
boost::system::error_code ec;
|
||||
if (!boost::filesystem::exists(dir, ec)) return;
|
||||
for (const auto& e : boost::filesystem::directory_iterator(dir, ec)) {
|
||||
if (e.path().extension().string() != ".json") continue;
|
||||
const std::string k = get_vendor_cache_key(e.path().string());
|
||||
if (!k.empty())
|
||||
parts.push_back(e.path().filename().string() + "=" + k);
|
||||
}
|
||||
};
|
||||
collect(rsrc_dir);
|
||||
if (user_dir != rsrc_dir) collect(user_dir);
|
||||
std::sort(parts.begin(), parts.end());
|
||||
std::string result;
|
||||
for (const auto& p : parts) { result += p; result += ';'; }
|
||||
return result;
|
||||
}
|
||||
|
||||
static boost::filesystem::path guide_json_cache_path()
|
||||
{
|
||||
return boost::filesystem::path(data_dir()) / "guide_profile_cache.json";
|
||||
}
|
||||
|
||||
static wxString update_custom_filaments()
|
||||
{
|
||||
@@ -191,11 +218,10 @@ GuideFrame::GuideFrame(GUI_App *pGUI, long style)
|
||||
GuideFrame::~GuideFrame()
|
||||
{
|
||||
m_destroy = true;
|
||||
if (m_load_task && m_load_task->joinable()) {
|
||||
*m_cancel_token = true; // signal any queued CallAfter lambdas before join
|
||||
if (m_load_task && m_load_task->joinable())
|
||||
m_load_task->join();
|
||||
delete m_load_task;
|
||||
m_load_task = nullptr;
|
||||
}
|
||||
m_load_task.reset();
|
||||
if (m_browser) {
|
||||
delete m_browser;
|
||||
m_browser = nullptr;
|
||||
@@ -301,15 +327,85 @@ void GuideFrame::OnNavigationRequest(wxWebViewEvent &evt)
|
||||
/**
|
||||
* Callback invoked when a navigation request was accepted
|
||||
*/
|
||||
void GuideFrame::init_guide_paths()
|
||||
{
|
||||
m_ProfileJson = json::parse("{}");
|
||||
m_ProfileJson["model"] = json::array();
|
||||
m_ProfileJson["machine"] = json::object();
|
||||
m_ProfileJson["filament"] = json::object();
|
||||
m_ProfileJson["process"] = json::array();
|
||||
|
||||
vendor_dir = (boost::filesystem::path(Slic3r::data_dir()) / PRESET_SYSTEM_DIR).make_preferred();
|
||||
rsrc_vendor_dir = (boost::filesystem::path(resources_dir()) / "profiles").make_preferred();
|
||||
orca_bundle_rsrc = true;
|
||||
|
||||
if (boost::filesystem::exists(vendor_dir)) {
|
||||
for (const auto& entry : boost::filesystem::directory_iterator(vendor_dir)) {
|
||||
if (!boost::filesystem::is_directory(entry) &&
|
||||
boost::iequals(entry.path().extension().string(), ".json") &&
|
||||
!boost::iequals(entry.path().stem().string(), PresetBundle::ORCA_FILAMENT_LIBRARY)) {
|
||||
orca_bundle_rsrc = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto lib_json = boost::filesystem::path(PresetBundle::ORCA_FILAMENT_LIBRARY).replace_extension(".json");
|
||||
m_OrcaFilaLibPath = boost::filesystem::exists(vendor_dir / lib_json)
|
||||
? (vendor_dir / PresetBundle::ORCA_FILAMENT_LIBRARY).string()
|
||||
: (rsrc_vendor_dir / PresetBundle::ORCA_FILAMENT_LIBRARY).string();
|
||||
}
|
||||
|
||||
void GuideFrame::on_profile_loaded()
|
||||
{
|
||||
// Must be called on the main thread.
|
||||
SaveProfileData();
|
||||
const std::string strAll = m_ProfileJson.dump(-1, ' ', false, json::error_handler_t::ignore);
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished, json contents:\n" << strAll;
|
||||
json res;
|
||||
res["command"] = "userguide_profile_load_finish";
|
||||
res["sequence_id"] = "10001";
|
||||
RunScript(wxString::Format("HandleStudio(%s)", res.dump(-1, ' ', true)));
|
||||
}
|
||||
|
||||
void GuideFrame::OnNavigationComplete(wxWebViewEvent &evt)
|
||||
{
|
||||
//wxLogMessage("%s", "Navigation complete; url='" + evt.GetURL() + "'");
|
||||
if (!bFirstComplete) {
|
||||
m_load_task = new boost::thread(boost::bind(&GuideFrame::LoadProfileData, this));
|
||||
// boost::thread LoadProfileThread(boost::bind(&GuideFrame::LoadProfileData, this));
|
||||
//LoadProfileThread.detach();
|
||||
|
||||
bFirstComplete = true;
|
||||
try {
|
||||
init_guide_paths();
|
||||
if (BuildProfileDataFromPresetBundle()) {
|
||||
// Persist so future opens that start before preset_bundle is ready
|
||||
// can skip the slower loading paths. Capture by value so the
|
||||
// thread is safe even if the dialog closes before it finishes.
|
||||
boost::thread([data = m_ProfileJson,
|
||||
rsrc = rsrc_vendor_dir,
|
||||
user = vendor_dir] {
|
||||
try {
|
||||
json cache;
|
||||
cache["version"] = guide_json_cache_version_key(rsrc, user);
|
||||
if (cache["version"].get<std::string>().empty()) return;
|
||||
json base = data;
|
||||
for (auto& entry : base["model"]) entry["nozzle_selected"] = "";
|
||||
cache["data"] = std::move(base);
|
||||
boost::nowide::ofstream ofs(guide_json_cache_path().string());
|
||||
ofs << cache.dump(-1, ' ', false, json::error_handler_t::ignore);
|
||||
BOOST_LOG_TRIVIAL(info) << "GuideFrame: guide JSON cache saved";
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "GuideFrame: failed to save guide JSON cache: " << e.what();
|
||||
}
|
||||
}).detach();
|
||||
if (!m_destroy)
|
||||
on_profile_loaded();
|
||||
} else {
|
||||
// Presets not yet in memory — delegate to background thread.
|
||||
m_load_task = std::make_unique<boost::thread>(boost::bind(&GuideFrame::LoadProfileData, this));
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ", init error: " << e.what();
|
||||
m_load_task = std::make_unique<boost::thread>(boost::bind(&GuideFrame::LoadProfileData, this));
|
||||
}
|
||||
}
|
||||
|
||||
m_browser->Show();
|
||||
@@ -1127,99 +1223,355 @@ int GuideFrame::GetFilamentInfo( std::string VendorDirectory, json & pFilaList,
|
||||
return status;
|
||||
}
|
||||
|
||||
int GuideFrame::LoadProfileData()
|
||||
bool GuideFrame::TryLoadGuideJsonCache()
|
||||
{
|
||||
const auto path = guide_json_cache_path();
|
||||
boost::system::error_code ec;
|
||||
if (!boost::filesystem::exists(path, ec)) return false;
|
||||
try {
|
||||
boost::nowide::ifstream ifs(path.string());
|
||||
json cache;
|
||||
ifs >> cache;
|
||||
if (!cache.contains("version") || !cache.contains("data")) return false;
|
||||
const std::string expected = guide_json_cache_version_key(rsrc_vendor_dir, vendor_dir);
|
||||
if (expected.empty() || cache["version"].get<std::string>() != expected) return false;
|
||||
m_ProfileJson = cache["data"];
|
||||
if (m_ProfileJson["machine"].empty()) return false;
|
||||
BOOST_LOG_TRIVIAL(info) << "GuideFrame: loaded profile data from guide JSON cache ("
|
||||
<< m_ProfileJson["model"].size() << " models, "
|
||||
<< m_ProfileJson["machine"].size() << " machines, "
|
||||
<< m_ProfileJson["filament"].size() << " filaments)";
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "GuideFrame: guide JSON cache load failed: " << e.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void GuideFrame::SaveGuideJsonCache()
|
||||
{
|
||||
try {
|
||||
m_ProfileJson = json::parse("{}");
|
||||
json cache;
|
||||
cache["version"] = guide_json_cache_version_key(rsrc_vendor_dir, vendor_dir);
|
||||
if (cache["version"].get<std::string>().empty()) return;
|
||||
json base = m_ProfileJson;
|
||||
// Strip user-specific state — SaveProfileData() re-applies it from AppConfig.
|
||||
for (auto& entry : base["model"])
|
||||
entry["nozzle_selected"] = "";
|
||||
cache["data"] = std::move(base);
|
||||
boost::nowide::ofstream ofs(guide_json_cache_path().string());
|
||||
ofs << cache.dump(-1, ' ', false, json::error_handler_t::ignore);
|
||||
BOOST_LOG_TRIVIAL(info) << "GuideFrame: guide JSON cache saved";
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "GuideFrame: failed to save guide JSON cache: " << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
bool GuideFrame::BuildProfileDataFromPresetBundle()
|
||||
{
|
||||
PresetBundle* pb = wxGetApp().preset_bundle;
|
||||
if (!pb || pb->vendors.empty())
|
||||
return false;
|
||||
|
||||
try {
|
||||
// Models from vendor profiles
|
||||
for (const auto& [vendor_id, vp] : pb->vendors) {
|
||||
for (const auto& model : vp.models) {
|
||||
std::string nozzle_str;
|
||||
for (const auto& v : model.variants) {
|
||||
if (!nozzle_str.empty()) nozzle_str += ";";
|
||||
nozzle_str += v.name;
|
||||
}
|
||||
std::string materials_str;
|
||||
for (const auto& m : model.default_materials) {
|
||||
if (!materials_str.empty()) materials_str += ";";
|
||||
materials_str += m;
|
||||
}
|
||||
boost::filesystem::path cover_path =
|
||||
(boost::filesystem::path(resources_dir()) / "profiles" / vendor_id / (model.id + "_cover.png"))
|
||||
.make_preferred();
|
||||
if (!boost::filesystem::exists(cover_path))
|
||||
cover_path =
|
||||
(boost::filesystem::path(resources_dir()) / "web/image/printer" / (model.id + "_cover.png"))
|
||||
.make_preferred();
|
||||
|
||||
json entry;
|
||||
entry["model"] = model.id;
|
||||
entry["name"] = model.name;
|
||||
entry["vendor"] = vendor_id;
|
||||
entry["nozzle_diameter"] = nozzle_str;
|
||||
entry["materials"] = materials_str;
|
||||
entry["cover"] = cover_path.string();
|
||||
entry["nozzle_selected"] = "";
|
||||
entry["sub_path"] = "";
|
||||
m_ProfileJson["model"].push_back(entry);
|
||||
}
|
||||
}
|
||||
|
||||
// Machine map: preset name -> {model, nozzle variant}
|
||||
for (const Preset& p : pb->printers()) {
|
||||
if (!p.is_system) continue;
|
||||
const auto* printer_model = p.config.option<ConfigOptionString>("printer_model");
|
||||
const auto* printer_variant = p.config.option<ConfigOptionString>("printer_variant");
|
||||
if (!printer_model || printer_model->value.empty() || !printer_variant) continue;
|
||||
|
||||
json mach;
|
||||
mach["model"] = printer_model->value;
|
||||
mach["nozzle"] = printer_variant->value;
|
||||
m_ProfileJson["machine"][p.name] = mach;
|
||||
}
|
||||
|
||||
// Filament map from system filament presets (vendor/type already resolved in config)
|
||||
for (const Preset& p : pb->filaments()) {
|
||||
if (!p.is_system) continue;
|
||||
const auto* fila_vendor = p.config.option<ConfigOptionStrings>("filament_vendor");
|
||||
const auto* fila_type = p.config.option<ConfigOptionStrings>("filament_type");
|
||||
const auto* compat_printers = p.config.option<ConfigOptionStrings>("compatible_printers");
|
||||
|
||||
std::string vendor = (fila_vendor && !fila_vendor->values.empty()) ? fila_vendor->values[0] : "";
|
||||
std::string type = (fila_type && !fila_type->values.empty()) ? fila_type->values[0] : "";
|
||||
|
||||
std::string model_list;
|
||||
if (compat_printers) {
|
||||
for (const std::string& pname : compat_printers->values) {
|
||||
if (m_ProfileJson["machine"].contains(pname)) {
|
||||
std::string m = m_ProfileJson["machine"][pname]["model"];
|
||||
std::string n = m_ProfileJson["machine"][pname]["nozzle"];
|
||||
model_list += "[" + m + "++" + n + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
json ff;
|
||||
ff["name"] = p.name;
|
||||
ff["sub_path"] = p.file;
|
||||
ff["vendor"] = vendor;
|
||||
ff["type"] = type;
|
||||
ff["models"] = model_list;
|
||||
ff["selected"] = 0;
|
||||
m_ProfileJson["filament"][p.name] = ff;
|
||||
}
|
||||
|
||||
// Process list from visible system print presets
|
||||
for (const Preset& p : pb->prints()) {
|
||||
if (!p.is_system || !p.is_visible) continue;
|
||||
json entry;
|
||||
entry["name"] = p.name;
|
||||
entry["sub_path"] = p.file;
|
||||
m_ProfileJson["process"].push_back(entry);
|
||||
}
|
||||
|
||||
// If rsrc_vendor_dir has vendor JSONs not covered by the current bundle, the
|
||||
// bundle is incomplete (e.g. dev env where data_dir/system only has
|
||||
// OrcaFilamentLibrary+Custom). Fall back so LoadProfileFamily reads both dirs.
|
||||
try {
|
||||
for (const auto& e : boost::filesystem::directory_iterator(rsrc_vendor_dir)) {
|
||||
if (e.path().extension().string() != ".json") continue;
|
||||
const std::string stem = e.path().stem().string();
|
||||
if (pb->vendors.find(stem) == pb->vendors.end()) {
|
||||
BOOST_LOG_TRIVIAL(info) << "GuideFrame: vendor '" << stem
|
||||
<< "' in resources but not in preset_bundle — falling back to JSON loading";
|
||||
m_ProfileJson["model"] = json::array();
|
||||
m_ProfileJson["machine"] = json::object();
|
||||
m_ProfileJson["filament"] = json::object();
|
||||
m_ProfileJson["process"] = json::array();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (const std::exception&) {}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "GuideFrame: built profile data from preset_bundle ("
|
||||
<< m_ProfileJson["model"].size() << " models, "
|
||||
<< m_ProfileJson["machine"].size() << " machines, "
|
||||
<< m_ProfileJson["filament"].size() << " filaments)";
|
||||
return !m_ProfileJson["machine"].empty();
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "GuideFrame::BuildProfileDataFromPresetBundle failed: " << e.what()
|
||||
<< " — falling back to JSON loading";
|
||||
m_ProfileJson["model"] = json::array();
|
||||
m_ProfileJson["machine"] = json::object();
|
||||
m_ProfileJson["filament"] = json::object();
|
||||
m_ProfileJson["process"] = json::array();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
vendor_dir = (boost::filesystem::path(Slic3r::data_dir()) / PRESET_SYSTEM_DIR).make_preferred();
|
||||
rsrc_vendor_dir = (boost::filesystem::path(resources_dir()) / "profiles").make_preferred();
|
||||
// Builds guide profile JSON from the per-vendor bundled caches
|
||||
// (resources/profiles/system_presets.cache, generated by CI).
|
||||
// This avoids the 90-second LoadProfileFamily fallback on first launch.
|
||||
bool GuideFrame::BuildProfileDataFromBundledCache()
|
||||
{
|
||||
const std::string cache_path = PresetBundle::bundled_system_presets_cache_path();
|
||||
if (!boost::filesystem::exists(cache_path))
|
||||
return false;
|
||||
|
||||
// Orca: add custom as default
|
||||
// Orca: add json logic for vendor bundle
|
||||
orca_bundle_rsrc = true;
|
||||
try {
|
||||
PresetBundle bundle;
|
||||
if (!PresetBundle::load_system_presets_cache_for_guide(cache_path, bundle))
|
||||
return false;
|
||||
|
||||
// search if there exists a .json file in vendor_dir folder, if exists, set orca_bundle_rsrc to false
|
||||
for (const auto& entry : boost::filesystem::directory_iterator(vendor_dir)) {
|
||||
if (!boost::filesystem::is_directory(entry) && boost::iequals(entry.path().extension().string(), ".json") && !boost::iequals(entry.path().stem().string(), PresetBundle::ORCA_FILAMENT_LIBRARY)) {
|
||||
orca_bundle_rsrc = false;
|
||||
break;
|
||||
for (const auto& [vendor_id, vp] : bundle.vendors) {
|
||||
for (const auto& cm : vp.models) {
|
||||
std::string nozzle_str;
|
||||
for (const auto& v : cm.variants) {
|
||||
if (!nozzle_str.empty()) nozzle_str += ";";
|
||||
nozzle_str += v.name;
|
||||
}
|
||||
std::string materials_str;
|
||||
for (const auto& m : cm.default_materials) {
|
||||
if (!materials_str.empty()) materials_str += ";";
|
||||
materials_str += m;
|
||||
}
|
||||
boost::filesystem::path cover_path =
|
||||
(boost::filesystem::path(resources_dir()) / "profiles" / vp.id / (cm.id + "_cover.png"))
|
||||
.make_preferred();
|
||||
if (!boost::filesystem::exists(cover_path))
|
||||
cover_path =
|
||||
(boost::filesystem::path(resources_dir()) / "web/image/printer" / (cm.id + "_cover.png"))
|
||||
.make_preferred();
|
||||
|
||||
json entry;
|
||||
entry["model"] = cm.id;
|
||||
entry["name"] = cm.name;
|
||||
entry["vendor"] = vp.id;
|
||||
entry["nozzle_diameter"] = nozzle_str;
|
||||
entry["materials"] = materials_str;
|
||||
entry["cover"] = cover_path.string();
|
||||
entry["nozzle_selected"] = "";
|
||||
entry["sub_path"] = "";
|
||||
m_ProfileJson["model"].push_back(entry);
|
||||
}
|
||||
}
|
||||
|
||||
// load the default filament library first
|
||||
std::set<std::string> loaded_vendors;
|
||||
auto filament_library_name = boost::filesystem::path(PresetBundle::ORCA_FILAMENT_LIBRARY).replace_extension(".json");
|
||||
if (boost::filesystem::exists(vendor_dir / filament_library_name)) {
|
||||
m_OrcaFilaLibPath = (vendor_dir / PresetBundle::ORCA_FILAMENT_LIBRARY).string();
|
||||
LoadProfileFamily(PresetBundle::ORCA_FILAMENT_LIBRARY, (vendor_dir / filament_library_name).string());
|
||||
} else {
|
||||
m_OrcaFilaLibPath = (rsrc_vendor_dir / PresetBundle::ORCA_FILAMENT_LIBRARY).string();
|
||||
LoadProfileFamily(PresetBundle::ORCA_FILAMENT_LIBRARY, (rsrc_vendor_dir / filament_library_name).string());
|
||||
}
|
||||
loaded_vendors.insert(PresetBundle::ORCA_FILAMENT_LIBRARY);
|
||||
for (const Preset& cp : bundle.printers()) {
|
||||
if (!cp.is_system || !cp.vendor) continue;
|
||||
const auto* pm = cp.config.option<ConfigOptionString>("printer_model");
|
||||
const auto* pv = cp.config.option<ConfigOptionString>("printer_variant");
|
||||
if (!pm || pm->value.empty() || !pv) continue;
|
||||
|
||||
//load custom bundle from user data path
|
||||
boost::filesystem::directory_iterator endIter;
|
||||
for (boost::filesystem::directory_iterator iter(vendor_dir); iter != endIter; iter++) {
|
||||
if (!boost::filesystem::is_directory(*iter)) {
|
||||
wxString strVendor = from_u8(iter->path().string()).BeforeLast('.');
|
||||
strVendor = strVendor.AfterLast('\\');
|
||||
strVendor = strVendor.AfterLast('/');
|
||||
|
||||
wxString strExtension = from_u8(iter->path().string()).AfterLast('.').Lower();
|
||||
if(strExtension.CmpNoCase("json") != 0 || loaded_vendors.find(w2s(strVendor)) != loaded_vendors.end())
|
||||
continue;
|
||||
|
||||
LoadProfileFamily(w2s(strVendor), iter->path().string());
|
||||
loaded_vendors.insert(w2s(strVendor));
|
||||
}
|
||||
if (m_destroy)
|
||||
return 0;
|
||||
json mach;
|
||||
mach["model"] = pm->value;
|
||||
mach["nozzle"] = pv->value;
|
||||
m_ProfileJson["machine"][cp.name] = mach;
|
||||
}
|
||||
|
||||
boost::filesystem::directory_iterator others_endIter;
|
||||
for (boost::filesystem::directory_iterator iter(rsrc_vendor_dir); iter != others_endIter; iter++) {
|
||||
if (!boost::filesystem::is_directory(*iter)) {
|
||||
wxString strVendor = from_u8(iter->path().string()).BeforeLast('.');
|
||||
strVendor = strVendor.AfterLast('\\');
|
||||
strVendor = strVendor.AfterLast('/');
|
||||
wxString strExtension = from_u8(iter->path().string()).AfterLast('.').Lower();
|
||||
if (strExtension.CmpNoCase("json") != 0 || loaded_vendors.find(w2s(strVendor)) != loaded_vendors.end())
|
||||
continue;
|
||||
for (const Preset& cp : bundle.filaments()) {
|
||||
if (!cp.is_system || !cp.vendor) continue;
|
||||
const auto* fv = cp.config.option<ConfigOptionStrings>("filament_vendor");
|
||||
const auto* ft = cp.config.option<ConfigOptionStrings>("filament_type");
|
||||
const auto* compat = cp.config.option<ConfigOptionStrings>("compatible_printers");
|
||||
|
||||
LoadProfileFamily(w2s(strVendor), iter->path().string());
|
||||
loaded_vendors.insert(w2s(strVendor));
|
||||
std::string vendor = (fv && !fv->values.empty()) ? fv->values[0] : "";
|
||||
std::string type = (ft && !ft->values.empty()) ? ft->values[0] : "";
|
||||
|
||||
std::string model_list;
|
||||
if (compat) {
|
||||
for (const std::string& pname : compat->values) {
|
||||
if (m_ProfileJson["machine"].contains(pname)) {
|
||||
std::string m = m_ProfileJson["machine"][pname]["model"];
|
||||
std::string n = m_ProfileJson["machine"][pname]["nozzle"];
|
||||
model_list += "[" + m + "++" + n + "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m_destroy)
|
||||
return 0;
|
||||
|
||||
json ff;
|
||||
ff["name"] = cp.name;
|
||||
ff["sub_path"] = cp.file;
|
||||
ff["vendor"] = vendor;
|
||||
ff["type"] = type;
|
||||
ff["models"] = model_list;
|
||||
ff["selected"] = 0;
|
||||
m_ProfileJson["filament"][cp.name] = ff;
|
||||
}
|
||||
|
||||
wxGetApp().CallAfter([this] {
|
||||
if (!m_destroy) {
|
||||
//sync to appconfig first to populate current selections
|
||||
SaveProfileData();
|
||||
for (const Preset& cp : bundle.prints()) {
|
||||
if (!cp.is_system || !cp.vendor || !cp.is_visible) continue;
|
||||
json entry;
|
||||
entry["name"] = cp.name;
|
||||
entry["sub_path"] = cp.file;
|
||||
m_ProfileJson["process"].push_back(entry);
|
||||
}
|
||||
|
||||
//sync to web after selections are populated
|
||||
std::string strAll = m_ProfileJson.dump(-1, ' ', false, json::error_handler_t::ignore);
|
||||
BOOST_LOG_TRIVIAL(info) << "GuideFrame: built profile data from single-bundle cache ("
|
||||
<< m_ProfileJson["model"].size() << " models, "
|
||||
<< m_ProfileJson["machine"].size() << " machines, "
|
||||
<< m_ProfileJson["filament"].size() << " filaments)";
|
||||
return !m_ProfileJson["machine"].empty();
|
||||
} catch (const std::exception& ex) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "GuideFrame::BuildProfileDataFromBundledCache failed: " << ex.what();
|
||||
m_ProfileJson["model"] = json::array();
|
||||
m_ProfileJson["machine"] = json::object();
|
||||
m_ProfileJson["filament"] = json::object();
|
||||
m_ProfileJson["process"] = json::array();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished, json contents: " << std::endl << strAll;
|
||||
json m_Res = json::object();
|
||||
m_Res["command"] = "userguide_profile_load_finish";
|
||||
m_Res["sequence_id"] = "10001";
|
||||
wxString strJS = wxString::Format("HandleStudio(%s)", m_Res.dump(-1, ' ', true));
|
||||
int GuideFrame::LoadProfileData()
|
||||
{
|
||||
// Background thread: the fast path in OnNavigationComplete failed (presets not yet loaded).
|
||||
// Loading order (fastest to slowest):
|
||||
// 1. Guide JSON cache (data_dir/guide_profile_cache.json, sub-second)
|
||||
// 2. Bundled per-vendor binary caches (CI-generated, ~1-2s)
|
||||
// 3. Read all vendor JSONs (~90s)
|
||||
// After paths 2 or 3 the guide JSON cache is written so next open uses path 1.
|
||||
try {
|
||||
if (!TryLoadGuideJsonCache()) {
|
||||
if (!BuildProfileDataFromBundledCache()) {
|
||||
// Last resort — read all vendor JSONs (~90s)
|
||||
std::set<std::string> loaded_vendors;
|
||||
auto filament_library_name = boost::filesystem::path(PresetBundle::ORCA_FILAMENT_LIBRARY).replace_extension(".json");
|
||||
if (boost::filesystem::exists(vendor_dir / filament_library_name))
|
||||
LoadProfileFamily(PresetBundle::ORCA_FILAMENT_LIBRARY, (vendor_dir / filament_library_name).string());
|
||||
else
|
||||
LoadProfileFamily(PresetBundle::ORCA_FILAMENT_LIBRARY, (rsrc_vendor_dir / filament_library_name).string());
|
||||
loaded_vendors.insert(PresetBundle::ORCA_FILAMENT_LIBRARY);
|
||||
|
||||
RunScript(strJS);
|
||||
boost::filesystem::directory_iterator endIter;
|
||||
for (boost::filesystem::directory_iterator iter(vendor_dir); iter != endIter; iter++) {
|
||||
if (!boost::filesystem::is_directory(*iter)) {
|
||||
wxString strVendor = from_u8(iter->path().string()).BeforeLast('.');
|
||||
strVendor = strVendor.AfterLast('\\');
|
||||
strVendor = strVendor.AfterLast('/');
|
||||
wxString strExtension = from_u8(iter->path().string()).AfterLast('.').Lower();
|
||||
if (strExtension.CmpNoCase("json") != 0 || loaded_vendors.find(w2s(strVendor)) != loaded_vendors.end())
|
||||
continue;
|
||||
LoadProfileFamily(w2s(strVendor), iter->path().string());
|
||||
loaded_vendors.insert(w2s(strVendor));
|
||||
}
|
||||
if (m_destroy) return 0;
|
||||
}
|
||||
|
||||
boost::filesystem::directory_iterator others_endIter;
|
||||
for (boost::filesystem::directory_iterator iter(rsrc_vendor_dir); iter != others_endIter; iter++) {
|
||||
if (!boost::filesystem::is_directory(*iter)) {
|
||||
wxString strVendor = from_u8(iter->path().string()).BeforeLast('.');
|
||||
strVendor = strVendor.AfterLast('\\');
|
||||
strVendor = strVendor.AfterLast('/');
|
||||
wxString strExtension = from_u8(iter->path().string()).AfterLast('.').Lower();
|
||||
if (strExtension.CmpNoCase("json") != 0 || loaded_vendors.find(w2s(strVendor)) != loaded_vendors.end())
|
||||
continue;
|
||||
LoadProfileFamily(w2s(strVendor), iter->path().string());
|
||||
loaded_vendors.insert(w2s(strVendor));
|
||||
}
|
||||
if (m_destroy) return 0;
|
||||
}
|
||||
}
|
||||
// Persist the result so subsequent opens skip both the bundled cache and
|
||||
// the slow JSON loading path entirely.
|
||||
SaveGuideJsonCache();
|
||||
}
|
||||
|
||||
// Capture the cancel token by value (shared_ptr) so the lambda doesn't
|
||||
// touch `this` if GuideFrame is destroyed before the event fires.
|
||||
auto tok = m_cancel_token;
|
||||
wxGetApp().CallAfter([this, tok] {
|
||||
if (!*tok)
|
||||
on_profile_loaded();
|
||||
});
|
||||
} catch (std::exception& e) {
|
||||
// wxLogMessage("GUIDE: load_profile_error %s ", e.what());
|
||||
// wxMessageBox(e.what(), "", MB_OK);
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ", error: " << e.what() << std::endl;
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ", error: " << e.what();
|
||||
}
|
||||
|
||||
filament_info_cache.clear();
|
||||
|
||||
@@ -30,10 +30,14 @@
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "slic3r/Utils/PresetUpdater.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <boost/thread.hpp>
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
class GuideFrame : public DPIDialog
|
||||
@@ -78,6 +82,12 @@ public:
|
||||
int LoadProfileData();
|
||||
int SaveProfileData();
|
||||
int LoadProfileFamily(std::string strVendor, std::string strFilePath);
|
||||
void init_guide_paths();
|
||||
void on_profile_loaded();
|
||||
bool BuildProfileDataFromPresetBundle();
|
||||
bool BuildProfileDataFromBundledCache();
|
||||
bool TryLoadGuideJsonCache();
|
||||
void SaveGuideJsonCache();
|
||||
int SaveProfile();
|
||||
int GetFilamentInfo( std::string VendorDirectory,json & pFilaList, std::string filepath, std::string &sVendor, std::string &sType);
|
||||
|
||||
@@ -112,8 +122,11 @@ private:
|
||||
|
||||
//First Load
|
||||
bool bFirstComplete{false};
|
||||
bool m_destroy{false};
|
||||
boost::thread* m_load_task{ nullptr };
|
||||
std::atomic<bool> m_destroy{false};
|
||||
// Shared cancel token captured by CallAfter lambdas so they don't touch
|
||||
// `this` after the destructor has run and the object is freed.
|
||||
std::shared_ptr<std::atomic<bool>> m_cancel_token{std::make_shared<std::atomic<bool>>(false)};
|
||||
std::unique_ptr<boost::thread> m_load_task;
|
||||
|
||||
// User Config
|
||||
bool PrivacyUse;
|
||||
@@ -123,6 +136,7 @@ private:
|
||||
bool InstallNetplugin;
|
||||
bool network_plugin_ready {false};
|
||||
|
||||
json m_ProfileJson;
|
||||
json m_OrcaFilaList;
|
||||
std::string m_OrcaFilaLibPath;
|
||||
|
||||
|
||||
@@ -436,7 +436,9 @@ wxBitmap create_scaled_bitmap( const std::string& bmp_name_in,
|
||||
return create_scaled_bitmap2(bmp_name_in, cache, win, px_cnt, grayscale, resize, array_new_color);
|
||||
}
|
||||
unsigned int width = 0;
|
||||
unsigned int height = (unsigned int) (win->FromDIP(px_cnt) + 0.5f);
|
||||
// win may be nullptr; use the static overload, which falls back to the primary display DPI.
|
||||
// Calling win->FromDIP() on a null win is UB and lets the optimizer drop later null checks.
|
||||
unsigned int height = (unsigned int) (wxWindow::FromDIP(px_cnt, win) + 0.5f);
|
||||
|
||||
std::string bmp_name = bmp_name_in;
|
||||
boost::replace_last(bmp_name, ".png", "");
|
||||
@@ -450,7 +452,7 @@ wxBitmap create_scaled_bitmap( const std::string& bmp_name_in,
|
||||
// Try loading an SVG first, then PNG if SVG was not found:
|
||||
wxBitmap *bmp = cache.load_svg(bmp_name, width, height, grayscale, dark_mode, new_color, resize ? em_unit(win) * 0.1f : 0.f);
|
||||
if (bmp == nullptr) {
|
||||
bmp = cache.load_png(bmp_name, width, height, grayscale, resize ? win->FromDIP(10) * 0.1f : 0.f);
|
||||
bmp = cache.load_png(bmp_name, width, height, grayscale, resize ? wxWindow::FromDIP(10, win) * 0.1f : 0.f);
|
||||
}
|
||||
|
||||
if (bmp == nullptr) {
|
||||
@@ -471,7 +473,8 @@ wxBitmap create_scaled_bitmap2(const std::string& bmp_name_in, Slic3r::GUI::Bitm
|
||||
const vector<std::string>& array_new_color/* = vector<std::string>()*/) // color witch will used instead of orange
|
||||
{
|
||||
unsigned int width = 0;
|
||||
unsigned int height = (unsigned int)(win->FromDIP(px_cnt) + 0.5f);
|
||||
// win may be nullptr; see create_scaled_bitmap() above.
|
||||
unsigned int height = (unsigned int)(wxWindow::FromDIP(px_cnt, win) + 0.5f);
|
||||
|
||||
std::string bmp_name = bmp_name_in;
|
||||
boost::replace_last(bmp_name, ".png", "");
|
||||
|
||||
@@ -14,6 +14,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_config.cpp
|
||||
test_preset_bundle_loading.cpp
|
||||
test_preset_setting_id.cpp
|
||||
test_vendor_cache.cpp
|
||||
test_elephant_foot_compensation.cpp
|
||||
test_geometry.cpp
|
||||
test_placeholder_parser.cpp
|
||||
@@ -23,6 +24,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_stl.cpp
|
||||
test_meshboolean.cpp
|
||||
test_marchingsquares.cpp
|
||||
test_utils.cpp
|
||||
test_timeutils.cpp
|
||||
test_voronoi.cpp
|
||||
test_optimizers.cpp
|
||||
|
||||
54
tests/libslic3r/test_utils.cpp
Normal file
54
tests/libslic3r/test_utils.cpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#include <catch2/catch_all.hpp>
|
||||
|
||||
#include "libslic3r/Utils.hpp"
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h> // getuid
|
||||
#endif
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
TEST_CASE("per_user_temp_dir composes a per-user temp root", "[utils]") {
|
||||
const std::string base = "/tmp";
|
||||
|
||||
SECTION("an empty id returns base unchanged") {
|
||||
REQUIRE(per_user_temp_dir(base, "") == base);
|
||||
}
|
||||
SECTION("a non-empty id is appended at the top level") {
|
||||
REQUIRE(per_user_temp_dir(base, "1000") == base + "/orcaslicer_1000");
|
||||
}
|
||||
SECTION("distinct ids produce distinct roots") {
|
||||
REQUIRE(per_user_temp_dir(base, "1000") != per_user_temp_dir(base, "1001"));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("per_user_temp_id follows the platform contract", "[utils]") {
|
||||
const std::string id = per_user_temp_id();
|
||||
|
||||
SECTION("stable across calls") {
|
||||
REQUIRE(per_user_temp_id() == id);
|
||||
}
|
||||
#ifdef _WIN32
|
||||
SECTION("empty on Windows (its temp dir is already per-user)") {
|
||||
REQUIRE(id.empty());
|
||||
}
|
||||
#else
|
||||
SECTION("the current uid on Linux/macOS") {
|
||||
REQUIRE_FALSE(id.empty());
|
||||
REQUIRE(id == std::to_string(static_cast<unsigned long>(::getuid())));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// The end-to-end contract callers depend on: the temp root is left alone on
|
||||
// Windows and isolated per user on Linux/macOS.
|
||||
TEST_CASE("per-user temp root is unchanged on Windows, isolated elsewhere", "[utils]") {
|
||||
const std::string base = "/tmp";
|
||||
const std::string root = per_user_temp_dir(base, per_user_temp_id());
|
||||
#ifdef _WIN32
|
||||
REQUIRE(root == base);
|
||||
#else
|
||||
REQUIRE(root != base);
|
||||
REQUIRE_THAT(root, Catch::Matchers::StartsWith(base + "/orcaslicer_"));
|
||||
#endif
|
||||
}
|
||||
458
tests/libslic3r/test_vendor_cache.cpp
Normal file
458
tests/libslic3r/test_vendor_cache.cpp
Normal file
@@ -0,0 +1,458 @@
|
||||
#include <catch2/catch_all.hpp>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/crc.hpp>
|
||||
#include <fstream>
|
||||
#include <set>
|
||||
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "libslic3r/Preset.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
namespace {
|
||||
|
||||
struct TempDir {
|
||||
fs::path path;
|
||||
TempDir() {
|
||||
path = fs::temp_directory_path() / fs::unique_path("orca-cache-test-%%%%-%%%%");
|
||||
fs::create_directories(path);
|
||||
}
|
||||
~TempDir() { boost::system::error_code ec; fs::remove_all(path, ec); }
|
||||
};
|
||||
|
||||
std::string write_vendor_json(const fs::path& dir, const std::string& vendor_id,
|
||||
const std::string& version = "1.0.0")
|
||||
{
|
||||
const fs::path p = dir / (vendor_id + ".json");
|
||||
std::ofstream f(p.string());
|
||||
f << R"({"version":")" << version << R"(","name":")" << vendor_id << R"("})";
|
||||
return p.string();
|
||||
}
|
||||
|
||||
std::string write_versionless_vendor_json(const fs::path& dir, const std::string& vendor_id)
|
||||
{
|
||||
const fs::path p = dir / (vendor_id + ".json");
|
||||
std::ofstream f(p.string());
|
||||
f << R"({"name":")" << vendor_id << R"("})";
|
||||
return p.string();
|
||||
}
|
||||
|
||||
void corrupt_blob_byte(const std::string& path)
|
||||
{
|
||||
std::fstream f(path, std::ios::in | std::ios::out | std::ios::binary);
|
||||
f.seekp(30);
|
||||
char b = 0; f.read(&b, 1);
|
||||
f.seekp(30);
|
||||
b ^= 0xFF;
|
||||
f.write(&b, 1);
|
||||
}
|
||||
|
||||
// Patch cache_version (blob[0..3]) and recompute CRC so the file passes
|
||||
// the CRC check but fails the cache_version check in load_system_presets_cache_for_guide.
|
||||
void patch_cache_version(const std::string& path, uint32_t wrong_version)
|
||||
{
|
||||
std::ifstream in(path, std::ios::binary);
|
||||
std::vector<char> data(std::istreambuf_iterator<char>(in), {});
|
||||
in.close();
|
||||
if (data.size() < 24) return;
|
||||
std::memcpy(&data[20], &wrong_version, 4);
|
||||
boost::crc_32_type crc;
|
||||
crc.process_bytes(&data[20], data.size() - 20);
|
||||
const uint32_t new_crc = crc.checksum();
|
||||
std::memcpy(&data[16], &new_crc, 4);
|
||||
std::ofstream out(path, std::ios::binary | std::ios::trunc);
|
||||
out.write(data.data(), static_cast<std::streamsize>(data.size()));
|
||||
}
|
||||
|
||||
void add_vendor(PresetBundle& bundle, const std::string& vendor_id,
|
||||
const std::string& name = "", Semver ver = Semver(1, 0, 0))
|
||||
{
|
||||
VendorProfile vp(vendor_id);
|
||||
vp.name = name.empty() ? vendor_id + " Corp" : name;
|
||||
vp.config_version = ver;
|
||||
bundle.vendors.emplace(vendor_id, vp);
|
||||
}
|
||||
|
||||
Preset& add_system_preset(PresetCollection& coll, const std::string& name,
|
||||
const VendorProfile* vp)
|
||||
{
|
||||
Preset& p = coll.load_preset("", name, DynamicPrintConfig(coll.default_preset().config), false);
|
||||
p.is_system = true;
|
||||
p.vendor = vp;
|
||||
return p;
|
||||
}
|
||||
|
||||
PresetBundle::SaveCacheResult save_one_vendor(PresetBundle& src,
|
||||
const fs::path& profiles_dir,
|
||||
const fs::path& cache_path)
|
||||
{
|
||||
return src.save_system_presets_cache(profiles_dir.string(), cache_path.string());
|
||||
}
|
||||
|
||||
// Helper: filter a collection by vendor_id.
|
||||
std::vector<const Preset*> presets_for(const PresetCollection& coll, const std::string& vendor_id)
|
||||
{
|
||||
std::vector<const Preset*> out;
|
||||
for (const Preset& p : coll())
|
||||
if (p.is_system && p.vendor && p.vendor->id == vendor_id)
|
||||
out.push_back(&p);
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("SystemPresetsCache: save and load via guide API", "[VendorCache]")
|
||||
{
|
||||
TempDir tmp;
|
||||
const std::string vid = "Acme";
|
||||
write_vendor_json(tmp.path, vid);
|
||||
const fs::path cache = tmp.path / "system_presets.cache";
|
||||
|
||||
PresetBundle src;
|
||||
add_vendor(src, vid);
|
||||
const VendorProfile* vp = &src.vendors.at(vid);
|
||||
|
||||
Preset& fp = add_system_preset(src.filaments, vid + " PLA @0.4", vp);
|
||||
fp.alias = "Acme PLA";
|
||||
fp.filament_id = "GFL_acme_pla";
|
||||
add_system_preset(src.printers, vid + " Printer 0.4", vp);
|
||||
|
||||
const auto stats = save_one_vendor(src, tmp.path, cache);
|
||||
REQUIRE(stats.ok);
|
||||
CHECK(stats.filament_presets == 1);
|
||||
CHECK(stats.printer_presets == 1);
|
||||
CHECK(stats.print_presets == 0);
|
||||
|
||||
PresetBundle out;
|
||||
REQUIRE(PresetBundle::load_system_presets_cache_for_guide(cache.string(), out));
|
||||
REQUIRE(out.vendors.count(vid) == 1);
|
||||
|
||||
auto fi = presets_for(out.filaments, vid);
|
||||
auto pr = presets_for(out.printers, vid);
|
||||
REQUIRE(fi.size() == 1);
|
||||
CHECK(fi[0]->name == vid + " PLA @0.4");
|
||||
CHECK(fi[0]->alias == "Acme PLA");
|
||||
CHECK(fi[0]->filament_id == "GFL_acme_pla");
|
||||
REQUIRE(pr.size() == 1);
|
||||
CHECK(pr[0]->name == vid + " Printer 0.4");
|
||||
}
|
||||
|
||||
TEST_CASE("SystemPresetsCache: missing file returns false", "[VendorCache]")
|
||||
{
|
||||
TempDir tmp;
|
||||
PresetBundle out;
|
||||
CHECK_FALSE(PresetBundle::load_system_presets_cache_for_guide(
|
||||
(tmp.path / "nonexistent.cache").string(), out));
|
||||
}
|
||||
|
||||
TEST_CASE("SystemPresetsCache: corrupt data rejected by CRC check", "[VendorCache]")
|
||||
{
|
||||
TempDir tmp;
|
||||
const std::string vid = "Acme";
|
||||
write_vendor_json(tmp.path, vid);
|
||||
const fs::path cache = tmp.path / "system_presets.cache";
|
||||
|
||||
PresetBundle src;
|
||||
add_vendor(src, vid);
|
||||
add_system_preset(src.filaments, vid + " PLA", &src.vendors.at(vid));
|
||||
save_one_vendor(src, tmp.path, cache);
|
||||
corrupt_blob_byte(cache.string());
|
||||
|
||||
PresetBundle out;
|
||||
CHECK_FALSE(PresetBundle::load_system_presets_cache_for_guide(cache.string(), out));
|
||||
}
|
||||
|
||||
TEST_CASE("SystemPresetsCache: multiple vendors in one bundle are isolated", "[VendorCache]")
|
||||
{
|
||||
TempDir tmp;
|
||||
const std::string vid1 = "VendorA";
|
||||
const std::string vid2 = "VendorB";
|
||||
write_vendor_json(tmp.path, vid1);
|
||||
write_vendor_json(tmp.path, vid2);
|
||||
const fs::path cache = tmp.path / "system_presets.cache";
|
||||
|
||||
PresetBundle src;
|
||||
for (const auto& vid : {vid1, vid2}) {
|
||||
add_vendor(src, vid);
|
||||
const VendorProfile* vp = &src.vendors.at(vid);
|
||||
add_system_preset(src.filaments, vid + " PLA", vp);
|
||||
add_system_preset(src.printers, vid + " Printer", vp);
|
||||
}
|
||||
|
||||
REQUIRE(save_one_vendor(src, tmp.path, cache).ok);
|
||||
|
||||
PresetBundle out;
|
||||
REQUIRE(PresetBundle::load_system_presets_cache_for_guide(cache.string(), out));
|
||||
REQUIRE(out.vendors.count(vid1) == 1);
|
||||
REQUIRE(out.vendors.count(vid2) == 1);
|
||||
|
||||
for (const auto& vid : {vid1, vid2}) {
|
||||
auto fi = presets_for(out.filaments, vid);
|
||||
auto pr = presets_for(out.printers, vid);
|
||||
REQUIRE(fi.size() == 1);
|
||||
CHECK(fi[0]->name == vid + " PLA");
|
||||
REQUIRE(pr.size() == 1);
|
||||
CHECK(pr[0]->name == vid + " Printer");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("SystemPresetsCache: vendor profile fields are preserved", "[VendorCache]")
|
||||
{
|
||||
TempDir tmp;
|
||||
const std::string vid = "Acme";
|
||||
write_vendor_json(tmp.path, vid, "2.5.1");
|
||||
const fs::path cache = tmp.path / "system_presets.cache";
|
||||
|
||||
PresetBundle src;
|
||||
VendorProfile vp(vid);
|
||||
vp.name = "Acme Corporation";
|
||||
vp.config_version = Semver(2, 5, 1);
|
||||
VendorProfile::PrinterModel model;
|
||||
model.id = "AcmePro";
|
||||
model.name = "Acme Pro";
|
||||
VendorProfile::PrinterVariant v0_4; v0_4.name = "0.4";
|
||||
model.variants.push_back(v0_4);
|
||||
vp.models.push_back(model);
|
||||
src.vendors.emplace(vid, vp);
|
||||
save_one_vendor(src, tmp.path, cache);
|
||||
|
||||
PresetBundle out;
|
||||
REQUIRE(PresetBundle::load_system_presets_cache_for_guide(cache.string(), out));
|
||||
REQUIRE(out.vendors.count(vid) == 1);
|
||||
const VendorProfile& gvp = out.vendors.at(vid);
|
||||
CHECK(gvp.id == vid);
|
||||
CHECK(gvp.name == "Acme Corporation");
|
||||
REQUIRE(gvp.models.size() == 1);
|
||||
CHECK(gvp.models[0].id == "AcmePro");
|
||||
CHECK(gvp.models[0].name == "Acme Pro");
|
||||
REQUIRE(gvp.models[0].variants.size() == 1);
|
||||
CHECK(gvp.models[0].variants[0].name == "0.4");
|
||||
}
|
||||
|
||||
TEST_CASE("SystemPresetsCache: config option values are preserved", "[VendorCache]")
|
||||
{
|
||||
TempDir tmp;
|
||||
const std::string vid = "Acme";
|
||||
write_vendor_json(tmp.path, vid);
|
||||
const fs::path cache = tmp.path / "system_presets.cache";
|
||||
|
||||
PresetBundle src;
|
||||
add_vendor(src, vid);
|
||||
Preset& fp = add_system_preset(src.filaments, vid + " PETG @0.4", &src.vendors.at(vid));
|
||||
fp.config.set_key_value("filament_type", new ConfigOptionStrings({"PETG"}));
|
||||
save_one_vendor(src, tmp.path, cache);
|
||||
|
||||
PresetBundle out;
|
||||
REQUIRE(PresetBundle::load_system_presets_cache_for_guide(cache.string(), out));
|
||||
|
||||
auto fi = presets_for(out.filaments, vid);
|
||||
REQUIRE(fi.size() == 1);
|
||||
const auto* ft = fi[0]->config.option<ConfigOptionStrings>("filament_type");
|
||||
REQUIRE(ft != nullptr);
|
||||
REQUIRE(ft->values.size() >= 1);
|
||||
CHECK(ft->values[0] == "PETG");
|
||||
}
|
||||
|
||||
TEST_CASE("SystemPresetsCache: multiple presets per collection round-trip", "[VendorCache]")
|
||||
{
|
||||
TempDir tmp;
|
||||
const std::string vid = "Acme";
|
||||
write_vendor_json(tmp.path, vid);
|
||||
const fs::path cache = tmp.path / "system_presets.cache";
|
||||
|
||||
PresetBundle src;
|
||||
add_vendor(src, vid);
|
||||
const VendorProfile* vp = &src.vendors.at(vid);
|
||||
|
||||
const std::vector<std::string> fi_names = {vid + " PLA", vid + " PETG", vid + " ABS"};
|
||||
const std::vector<std::string> pr_names = {vid + " Printer 0.4", vid + " Printer 0.6"};
|
||||
for (const auto& n : fi_names) add_system_preset(src.filaments, n, vp);
|
||||
for (const auto& n : pr_names) add_system_preset(src.printers, n, vp);
|
||||
|
||||
const auto stats = save_one_vendor(src, tmp.path, cache);
|
||||
REQUIRE(stats.ok);
|
||||
CHECK(stats.filament_presets == 3);
|
||||
CHECK(stats.printer_presets == 2);
|
||||
|
||||
PresetBundle out;
|
||||
REQUIRE(PresetBundle::load_system_presets_cache_for_guide(cache.string(), out));
|
||||
|
||||
auto fi = presets_for(out.filaments, vid);
|
||||
auto pr = presets_for(out.printers, vid);
|
||||
REQUIRE(fi.size() == 3);
|
||||
REQUIRE(pr.size() == 2);
|
||||
|
||||
std::set<std::string> fi_got, pr_got;
|
||||
for (const auto* p : fi) fi_got.insert(p->name);
|
||||
for (const auto* p : pr) pr_got.insert(p->name);
|
||||
for (const auto& n : fi_names) CHECK(fi_got.count(n) == 1);
|
||||
for (const auto& n : pr_names) CHECK(pr_got.count(n) == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("SystemPresetsCache: truncated file is rejected", "[VendorCache]")
|
||||
{
|
||||
TempDir tmp;
|
||||
const fs::path cache = tmp.path / "truncated.cache";
|
||||
{
|
||||
std::ofstream f(cache.string(), std::ios::binary);
|
||||
const char data[] = {0x4F, 0x52, 0x43};
|
||||
f.write(data, sizeof(data));
|
||||
}
|
||||
PresetBundle out;
|
||||
CHECK_FALSE(PresetBundle::load_system_presets_cache_for_guide(cache.string(), out));
|
||||
}
|
||||
|
||||
TEST_CASE("SystemPresetsCache: wrong magic is rejected", "[VendorCache]")
|
||||
{
|
||||
TempDir tmp;
|
||||
const std::string vid = "Acme";
|
||||
write_vendor_json(tmp.path, vid);
|
||||
const fs::path cache = tmp.path / "system_presets.cache";
|
||||
|
||||
PresetBundle src;
|
||||
add_vendor(src, vid);
|
||||
add_system_preset(src.filaments, vid + " PLA", &src.vendors.at(vid));
|
||||
save_one_vendor(src, tmp.path, cache);
|
||||
|
||||
{
|
||||
std::fstream f(cache.string(), std::ios::in | std::ios::out | std::ios::binary);
|
||||
const uint32_t bad = 0xDEADBEEFu;
|
||||
f.write(reinterpret_cast<const char*>(&bad), sizeof(bad));
|
||||
}
|
||||
|
||||
PresetBundle out;
|
||||
CHECK_FALSE(PresetBundle::load_system_presets_cache_for_guide(cache.string(), out));
|
||||
}
|
||||
|
||||
TEST_CASE("SystemPresetsCache: vendor with no presets saves and loads cleanly", "[VendorCache]")
|
||||
{
|
||||
TempDir tmp;
|
||||
const std::string vid = "Acme";
|
||||
write_vendor_json(tmp.path, vid);
|
||||
const fs::path cache = tmp.path / "system_presets.cache";
|
||||
|
||||
PresetBundle src;
|
||||
VendorProfile vp(vid);
|
||||
vp.name = "Acme Corporation";
|
||||
vp.config_version = Semver(1, 0, 0);
|
||||
src.vendors.emplace(vid, vp);
|
||||
|
||||
const auto stats = save_one_vendor(src, tmp.path, cache);
|
||||
REQUIRE(stats.ok);
|
||||
CHECK(stats.print_presets == 0);
|
||||
CHECK(stats.filament_presets == 0);
|
||||
CHECK(stats.printer_presets == 0);
|
||||
|
||||
PresetBundle out;
|
||||
REQUIRE(PresetBundle::load_system_presets_cache_for_guide(cache.string(), out));
|
||||
REQUIRE(out.vendors.count(vid) == 1);
|
||||
CHECK(out.vendors.at(vid).id == vid);
|
||||
CHECK(out.vendors.at(vid).name == "Acme Corporation");
|
||||
CHECK(presets_for(out.filaments, vid).empty());
|
||||
CHECK(presets_for(out.printers, vid).empty());
|
||||
CHECK(presets_for(out.prints, vid).empty());
|
||||
}
|
||||
|
||||
TEST_CASE("SystemPresetsCache: all Preset metadata fields are preserved", "[VendorCache]")
|
||||
{
|
||||
TempDir tmp;
|
||||
const std::string vid = "Acme";
|
||||
write_vendor_json(tmp.path, vid);
|
||||
const fs::path cache = tmp.path / "system_presets.cache";
|
||||
|
||||
PresetBundle src;
|
||||
add_vendor(src, vid);
|
||||
Preset& fp = add_system_preset(src.filaments, vid + " PLA @0.4", &src.vendors.at(vid));
|
||||
fp.setting_id = "sid-test-001";
|
||||
fp.description = "A test filament preset";
|
||||
fp.bundle_id = "bundle-xyz";
|
||||
fp.user_id = "user-abc";
|
||||
fp.base_id = "base-123";
|
||||
fp.sync_info = "update";
|
||||
fp.updated_time = 1700000000LL;
|
||||
fp.key_values = {{"color", "red"}, {"diameter", "1.75"}};
|
||||
fp.ini_str = "[filament]\nnozzle_temperature = 230\n";
|
||||
save_one_vendor(src, tmp.path, cache);
|
||||
|
||||
PresetBundle out;
|
||||
REQUIRE(PresetBundle::load_system_presets_cache_for_guide(cache.string(), out));
|
||||
|
||||
auto fi = presets_for(out.filaments, vid);
|
||||
REQUIRE(fi.size() == 1);
|
||||
CHECK(fi[0]->setting_id == "sid-test-001");
|
||||
CHECK(fi[0]->description == "A test filament preset");
|
||||
CHECK(fi[0]->bundle_id == "bundle-xyz");
|
||||
CHECK(fi[0]->user_id == "user-abc");
|
||||
CHECK(fi[0]->base_id == "base-123");
|
||||
CHECK(fi[0]->sync_info == "update");
|
||||
CHECK(fi[0]->updated_time == 1700000000LL);
|
||||
REQUIRE(fi[0]->key_values.count("color") == 1);
|
||||
CHECK(fi[0]->key_values.at("color") == "red");
|
||||
REQUIRE(fi[0]->key_values.count("diameter") == 1);
|
||||
CHECK(fi[0]->key_values.at("diameter") == "1.75");
|
||||
CHECK(fi[0]->ini_str == "[filament]\nnozzle_temperature = 230\n");
|
||||
}
|
||||
|
||||
TEST_CASE("SystemPresetsCache: wrong cache_version is rejected", "[VendorCache]")
|
||||
{
|
||||
TempDir tmp;
|
||||
const std::string vid = "Acme";
|
||||
write_vendor_json(tmp.path, vid);
|
||||
const fs::path cache = tmp.path / "system_presets.cache";
|
||||
|
||||
PresetBundle src;
|
||||
add_vendor(src, vid);
|
||||
add_system_preset(src.filaments, vid + " PLA", &src.vendors.at(vid));
|
||||
save_one_vendor(src, tmp.path, cache);
|
||||
patch_cache_version(cache.string(), 0xFFFFFFFFu);
|
||||
|
||||
PresetBundle out;
|
||||
CHECK_FALSE(PresetBundle::load_system_presets_cache_for_guide(cache.string(), out));
|
||||
}
|
||||
|
||||
TEST_CASE("SystemPresetsCache: mid-blob truncation is rejected", "[VendorCache]")
|
||||
{
|
||||
TempDir tmp;
|
||||
const std::string vid = "Acme";
|
||||
write_vendor_json(tmp.path, vid);
|
||||
const fs::path cache = tmp.path / "system_presets.cache";
|
||||
|
||||
PresetBundle src;
|
||||
add_vendor(src, vid);
|
||||
add_system_preset(src.filaments, vid + " PLA", &src.vendors.at(vid));
|
||||
save_one_vendor(src, tmp.path, cache);
|
||||
|
||||
{
|
||||
std::ifstream in(cache.string(), std::ios::binary);
|
||||
std::vector<char> buf(30); // 20-byte header + 10 bytes of blob
|
||||
in.read(buf.data(), 30);
|
||||
in.close();
|
||||
std::ofstream out(cache.string(), std::ios::binary | std::ios::trunc);
|
||||
out.write(buf.data(), 30);
|
||||
}
|
||||
|
||||
PresetBundle out;
|
||||
CHECK_FALSE(PresetBundle::load_system_presets_cache_for_guide(cache.string(), out));
|
||||
}
|
||||
|
||||
TEST_CASE("SystemPresetsCache: versionless vendor uses mtime key in bundle", "[VendorCache]")
|
||||
{
|
||||
TempDir tmp;
|
||||
const std::string vid = "Acme";
|
||||
write_versionless_vendor_json(tmp.path, vid);
|
||||
const fs::path cache = tmp.path / "system_presets.cache";
|
||||
|
||||
PresetBundle src;
|
||||
add_vendor(src, vid);
|
||||
add_system_preset(src.filaments, vid + " PLA", &src.vendors.at(vid));
|
||||
REQUIRE(save_one_vendor(src, tmp.path, cache).ok);
|
||||
|
||||
PresetBundle out;
|
||||
REQUIRE(PresetBundle::load_system_presets_cache_for_guide(cache.string(), out));
|
||||
auto fi = presets_for(out.filaments, vid);
|
||||
REQUIRE(fi.size() == 1);
|
||||
CHECK(fi[0]->name == vid + " PLA");
|
||||
}
|
||||
Reference in New Issue
Block a user