diff --git a/.github/workflows/build_orca.yml b/.github/workflows/build_orca.yml index 7ccb52b0e6..457f76f7c8 100644 --- a/.github/workflows/build_orca.yml +++ b/.github/workflows/build_orca.yml @@ -117,6 +117,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 }} @@ -292,6 +301,14 @@ jobs: # WindowsSDKVersion: '10.0.26100.0\' run: .\build_release_vs.bat slicer + - name: Generate system presets cache (Windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + $profiles = Get-ChildItem -Recurse -Path build -Directory -Filter profiles | + Where-Object { $_.FullName -match 'resources' } | Select-Object -First 1 + .\build\src\Release\generate_system_cache.exe --path $profiles.FullName --log_level 2 + - name: Create installer Win if: runner.os == 'Windows' && !vars.SELF_HOSTED working-directory: ${{ github.workspace }}/build @@ -400,6 +417,20 @@ jobs: retention-days: 5 if-no-files-found: error + - name: Generate system presets cache (Linux) + if: runner.os == 'Linux' + shell: bash + run: | + ./build/src/Release/generate_system_cache --path build/package/resources/profiles --log_level 2 + # Re-pack the AppImage so the cache is included + appimage=$(find build -maxdepth 1 -name "OrcaSlicer_Linux_AppImage*.AppImage" | head -1) + chmod +x "$appimage" + "$appimage" --appimage-extract + cp build/package/resources/profiles/system_presets_cache.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' timeout-minutes: 20 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 73c767dad2..1f20834d22 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -111,6 +111,11 @@ 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) + + # generate_system_cache: pre-generates resources/profiles/system_presets_cache.cache for CI bundling. + add_executable(generate_system_cache dev-utils/generate_system_cache.cpp) + target_link_libraries(generate_system_cache libslic3r boost_headeronly) + target_compile_definitions(generate_system_cache PRIVATE -DBOOST_ALL_NO_LIB -DBOOST_USE_WINAPI_VERSION=0x602 -DBOOST_SYSTEM_USE_UTF8) endif() # Create a slic3r executable diff --git a/src/dev-utils/generate_system_cache.cpp b/src/dev-utils/generate_system_cache.cpp new file mode 100644 index 0000000000..a46a761c20 --- /dev/null +++ b/src/dev-utils/generate_system_cache.cpp @@ -0,0 +1,86 @@ +#include "libslic3r/PresetBundle.hpp" +#include "libslic3r/PresetBundleCache.hpp" +#include "libslic3r/Utils.hpp" + +#include +#include +#include +#include + +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()->default_value("../../../../../../../resources/profiles"), "Path to profiles directory") +#else + ("path,p", po::value()->default_value("../../../resources/profiles"), "Path to profiles directory") +#endif + ("log_level,l", po::value()->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(); + const int log_level = vm["log_level"].as(); + + 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); + // In validation_mode, load_system_presets_from_json uses data_dir() directly + // (no /system/ suffix), so point data_dir at the profiles directory. + set_data_dir(profiles_path); + set_resources_dir(fs::path(profiles_path).parent_path().make_preferred().string()); + + // load_presets creates user preset dirs under data_dir(). + 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(); + 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 cache_path = + (fs::path(profiles_path) / "system_presets_cache.cache").make_preferred().string(); + + PresetBundleCache::SystemPresetsCache cache; + cache.capture(*preset_bundle, profiles_path); + cache.save(cache_path); + + std::cout << "Cache written: " << cache_path << "\n" + << " Vendor profiles: " << cache.vendor_profiles.size() << "\n" + << " Print presets: " << cache.print_presets.size() << "\n" + << " Filament presets: " << cache.filament_presets.size() << "\n" + << " Printer presets: " << cache.printer_presets.size() << "\n"; + return 0; +} diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index aa146b0e48..c3d77dcae3 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -2208,6 +2208,26 @@ std::pair PresetBundle::load_system_pre BOOST_LOG_TRIVIAL(info) << "PresetBundle: system presets loaded from cache in " << ms << " ms"; return {PresetsConfigSubstitutions{}, ""}; } + + // Try bundled cache shipped with the installer (first launch, before user cache exists). + // Validates against resources/profiles/ — the same directory it was generated from in CI. + { + const std::string bundled_dir = + (boost::filesystem::path(resources_dir()) / "profiles").make_preferred().string(); + PresetBundleCache::SystemPresetsCache bundled; + if (bundled.load(PresetBundleCache::SystemPresetsCache::bundled_cache_path()) && + bundled.is_valid(bundled_dir)) { + bundled.apply(*this); + update_system_maps(); + // Promote to user cache so subsequent launches skip this check. + bundled.save(cache_file); + const auto ms = std::chrono::duration_cast( + std::chrono::steady_clock::now() - t0).count(); + BOOST_LOG_TRIVIAL(info) << "PresetBundle: system presets loaded from bundled cache in " << ms << " ms"; + return {PresetsConfigSubstitutions{}, ""}; + } + } + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " cache miss, falling back to JSON load"; } diff --git a/src/libslic3r/PresetBundleCache.cpp b/src/libslic3r/PresetBundleCache.cpp index febba0e9f7..48df8acf53 100644 --- a/src/libslic3r/PresetBundleCache.cpp +++ b/src/libslic3r/PresetBundleCache.cpp @@ -116,6 +116,12 @@ std::string SystemPresetsCache::cache_path() .make_preferred().string(); } +std::string SystemPresetsCache::bundled_cache_path() +{ + return (boost::filesystem::path(resources_dir()) / "profiles" / "system_presets_cache.cache") + .make_preferred().string(); +} + bool SystemPresetsCache::is_valid(const std::string& system_dir) const { if (format_version != FORMAT_VERSION) diff --git a/src/libslic3r/PresetBundleCache.hpp b/src/libslic3r/PresetBundleCache.hpp index 6e87adc7aa..b0a178eb29 100644 --- a/src/libslic3r/PresetBundleCache.hpp +++ b/src/libslic3r/PresetBundleCache.hpp @@ -119,6 +119,7 @@ struct SystemPresetsCache { } static std::string cache_path(); + static std::string bundled_cache_path(); bool is_valid(const std::string& system_dir) const; void capture(const PresetBundle& bundle, const std::string& system_dir); void apply(PresetBundle& bundle) const;