From d7bacd5c1cf1d6273fa96b6c0d289370d988fcc1 Mon Sep 17 00:00:00 2001 From: ExPikaPaka Date: Mon, 6 Jul 2026 08:37:32 +0200 Subject: [PATCH] Minimize field duplication by moving Cache thing into PresetBundle --- src/dev-utils/CMakeLists.txt | 4 - src/dev-utils/generate_system_cache.cpp | 27 +- src/dev-utils/inspect_system_cache.cpp | 134 --------- src/libslic3r/PresetBundle.cpp | 370 ++++++++++++++---------- src/libslic3r/PresetBundle.hpp | 112 +++---- src/slic3r/GUI/WebGuideDialog.cpp | 21 +- 6 files changed, 296 insertions(+), 372 deletions(-) delete mode 100644 src/dev-utils/inspect_system_cache.cpp diff --git a/src/dev-utils/CMakeLists.txt b/src/dev-utils/CMakeLists.txt index 5312221a42..6b43cff319 100644 --- a/src/dev-utils/CMakeLists.txt +++ b/src/dev-utils/CMakeLists.txt @@ -28,10 +28,6 @@ if (ORCA_TOOLS) target_link_libraries(generate_system_cache libslic3r boost_headeronly) target_compile_definitions(generate_system_cache PRIVATE ${_DEV_DEFS}) - # inspect_system_cache: dumps contents of a .cache file for debugging. - add_executable(inspect_system_cache inspect_system_cache.cpp) - target_link_libraries(inspect_system_cache libslic3r boost_headeronly) - target_compile_definitions(inspect_system_cache PRIVATE ${_DEV_DEFS}) endif() # Function that adds source file encoding check to a target diff --git a/src/dev-utils/generate_system_cache.cpp b/src/dev-utils/generate_system_cache.cpp index 391b34a657..42d79b85d5 100644 --- a/src/dev-utils/generate_system_cache.cpp +++ b/src/dev-utils/generate_system_cache.cpp @@ -91,30 +91,23 @@ int main(int argc, char* argv[]) for (const auto& vendor_name : vendor_names) { try { - const std::string json_path = (fs::path(profiles_path) / (vendor_name + ".json")).string(); - const std::string ver_str = get_vendor_cache_key(json_path); - + const std::string json_path = (fs::path(profiles_path) / (vendor_name + ".json")).string(); + const std::string cache_path = (fs::path(profiles_path) / (vendor_name + ".cache")).make_preferred().string(); const bool is_orca_lib = (vendor_name == PresetBundle::ORCA_FILAMENT_LIBRARY); - Slic3r::PresetBundle::VendorCache vc; - vc.capture(*preset_bundle, vendor_name, ver_str, is_orca_lib); - const std::string cache_path = - (fs::path(profiles_path) / (vendor_name + ".cache")).make_preferred().string(); - vc.save(cache_path); + const auto stats = preset_bundle->save_bundled_vendor_cache(vendor_name, json_path, is_orca_lib, cache_path); - // Verify the file was written and can be reloaded. - Slic3r::PresetBundle::VendorCache verify; - if (!verify.load(cache_path) || !verify.is_valid(ver_str)) { + if (!stats.ok) { std::cerr << "ERROR: " << vendor_name << ": verification failed\n"; ++failed; } else { std::cout << " [ok] " << vendor_name << ".cache" - << " (" << vc.print_presets.size() << " print, " - << vc.filament_presets.size() << " filament, " - << vc.printer_presets.size() << " printer)\n"; - total_print += vc.print_presets.size(); - total_filament += vc.filament_presets.size(); - total_printer += vc.printer_presets.size(); + << " (" << stats.print_presets << " print, " + << stats.filament_presets << " filament, " + << stats.printer_presets << " printer)\n"; + total_print += stats.print_presets; + total_filament += stats.filament_presets; + total_printer += stats.printer_presets; ++saved; } } catch (const std::exception& ex) { diff --git a/src/dev-utils/inspect_system_cache.cpp b/src/dev-utils/inspect_system_cache.cpp deleted file mode 100644 index da1494cc77..0000000000 --- a/src/dev-utils/inspect_system_cache.cpp +++ /dev/null @@ -1,134 +0,0 @@ -#include "libslic3r/PresetBundle.hpp" - -#include -#include -#include -#include - -using namespace Slic3r; -namespace fs = boost::filesystem; -namespace po = boost::program_options; - -static void print_bar(char c, int n) { std::cout << std::string(n, c) << "\n"; } - -static void inspect_one(const std::string& path, const po::variables_map& vm) -{ - Slic3r::PresetBundle::VendorCache vc; - if (!vc.load(path)) { - std::cerr << "Failed to load cache: " << path << "\n" - << " (wrong format version, truncated file, CRC mismatch, or not a .cache file)\n"; - return; - } - - bool show_all = !vm.count("vendors") && !vm.count("models") && - !vm.count("presets") && !vm.count("filaments") && - !vm.count("printers") && !vm.count("process"); - - // ---- Summary ---- - print_bar('=', 60); - std::cout << "Cache file : " << path << "\n"; - std::cout << "Vendor : " << vc.profile.id << " v" << vc.profile.config_version << "\n"; - std::cout << "JSON version: " << (vc.vendor_json_version.empty() ? "(none)" : vc.vendor_json_version) << "\n"; - std::cout << "Cache ver : " << vc.cache_version << "\n"; - std::cout << "Config opts : " << vc.config_options_count << "\n"; - print_bar('-', 60); - std::cout << "Models : " << vc.profile.models.size() << "\n"; - std::cout << "Printers : " << vc.printer_presets.size() << "\n"; - std::cout << "Filaments : " << vc.filament_presets.size() << "\n"; - std::cout << "Print proc : " << vc.print_presets.size() << "\n"; - std::cout << "SLA print : " << vc.sla_print_presets.size() << "\n"; - std::cout << "SLA material: " << vc.sla_material_presets.size() << "\n"; - std::cout << "config_maps : " << vc.config_maps.size() << "\n"; - std::cout << "filament_id_maps: " << vc.filament_id_maps.size() << "\n"; - print_bar('=', 60); - - // ---- Models ---- - if (show_all || vm.count("vendors") || vm.count("models")) { - std::cout << "\nVENDOR PROFILE [" << vc.profile.id << "]\n"; - print_bar('-', 60); - std::cout << " Name: " << vc.profile.name << "\n"; - std::cout << " Config version: " << vc.profile.config_version << "\n"; - if (vm.count("models") || show_all) { - for (const auto& m : vc.profile.models) { - std::cout << " " << std::left << std::setw(40) << m.name - << " variants:" << m.variants.size() << "\n"; - } - } - } - - // ---- Printer presets ---- - if (vm.count("presets") || vm.count("printers")) { - std::cout << "\nPRINTER PRESETS (" << vc.printer_presets.size() << ")\n"; - print_bar('-', 60); - for (const auto& cp : vc.printer_presets) { - const auto* pm = cp.config.option("printer_model"); - const auto* pv = cp.config.option("printer_variant"); - std::cout << " " << std::left << std::setw(50) << cp.name - << " model=" << (pm ? pm->value : "?") - << " nozzle=" << (pv ? pv->value : "?") - << (cp.is_visible ? "" : " [hidden]") << "\n"; - } - } - - // ---- Filament presets ---- - if (vm.count("presets") || vm.count("filaments")) { - std::cout << "\nFILAMENT PRESETS (" << vc.filament_presets.size() << ")\n"; - print_bar('-', 60); - for (const auto& cp : vc.filament_presets) { - const auto* fv = cp.config.option("filament_vendor"); - const auto* ft = cp.config.option("filament_type"); - std::cout << " " << std::left << std::setw(50) << cp.name - << " vendor=" << (fv && !fv->values.empty() ? fv->values[0] : "?") - << " type=" << (ft && !ft->values.empty() ? ft->values[0] : "?") - << (cp.is_visible ? "" : " [hidden]") << "\n"; - } - } - - // ---- Print process presets ---- - if (vm.count("presets") || vm.count("process")) { - std::cout << "\nPRINT PROCESS PRESETS (" << vc.print_presets.size() << ")\n"; - print_bar('-', 60); - for (const auto& cp : vc.print_presets) - std::cout << " " << cp.name << (cp.is_visible ? "" : " [hidden]") << "\n"; - } -} - -int main(int argc, char* argv[]) -{ - po::options_description desc("OrcaSlicer Cache Inspector\nUsage"); - desc.add_options() - ("help,h", "Show help") - ("path,p", po::value(), "Path to a .cache file or a directory of .cache files (required)") - ("vendors,V", "Show vendor profile summary") - ("models,m", "List all printer models") - ("presets,P", "List all preset names") - ("filaments,f", "List filament presets") - ("printers,r", "List printer presets") - ("process,p2", "List print process presets"); - - po::variables_map vm; - try { - po::store(po::parse_command_line(argc, argv, desc), vm); - if (vm.count("help") || !vm.count("path")) { 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 path = vm["path"].as(); - - if (fs::is_directory(path)) { - // Inspect all .cache files in the directory. - std::vector files; - for (const auto& e : fs::directory_iterator(path)) - if (e.path().extension() == ".cache") - files.push_back(e.path()); - std::sort(files.begin(), files.end()); - for (const auto& f : files) - inspect_one(f.string(), vm); - } else { - inspect_one(path, vm); - } - - return 0; -} diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 67b67968cf..ad65f693dd 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -2233,20 +2233,7 @@ std::pair PresetBundle::load_system_pre const std::string json_path = (dir / (vendor_name + ".json")).string(); const std::string ver_str = get_vendor_cache_key(json_path); vendor_json_versions[vendor_name] = ver_str; - - VendorCache vc; - // 1. User per-vendor cache - if (vc.load(VendorCache::user_path(vendor_name)) && vc.is_valid(ver_str)) { - vc.apply(*this); - return true; - } - // 2. Bundled per-vendor cache (ships with installer) - if (vc.load(VendorCache::bundled_path(vendor_name)) && vc.is_valid(ver_str)) { - vc.apply(*this); - vc.save(VendorCache::user_path(vendor_name)); // promote - return true; - } - return false; + return try_load_and_apply_vendor_cache(vendor_name, ver_str); }; // Sort: ORCA_FILAMENT_LIBRARY goes first, rest alphabetical. @@ -2407,9 +2394,7 @@ std::pair PresetBundle::load_system_pre const bool is_orca_lib = (vendor_name == ORCA_FILAMENT_LIBRARY); const std::string ver_str = vendor_json_versions.count(vendor_name) ? vendor_json_versions.at(vendor_name) : ""; - VendorCache vc; - vc.capture(*this, vendor_name, ver_str, is_orca_lib); - vc.save(VendorCache::user_path(vendor_name)); + write_vendor_cache(vendor_cache_user_path(vendor_name), vendor_name, ver_str, is_orca_lib); } const auto save_ms = std::chrono::duration_cast( std::chrono::steady_clock::now() - save_t0).count(); @@ -5797,7 +5782,10 @@ bool BundleMetadata::save_to_json(const std::string& path) const return false; } } -// ---- VendorCache implementation ----------------------------------------- +// ---- VendorCache implementation (PresetBundle methods) ------------------ +// The VendorCacheHeader struct holds only metadata. Preset collections are +// serialized directly from/into PresetBundle's own prints/filaments/printers/… +// to avoid ever duplicating those vectors in memory. namespace { @@ -5811,26 +5799,96 @@ struct CacheFileHeader { #pragma pack(pop) static_assert(sizeof(CacheFileHeader) == 20, "CacheFileHeader must be 20 bytes"); -// Returns the string used as vendor_json_version in the cache validity check. -// For versioned vendors this is the Semver string from the JSON. -// For version-less vendors we use the file mtime so any edit invalidates the cache. -static std::string get_vendor_cache_key(const std::string& json_path) +} // anonymous namespace + +// static +std::string PresetBundle::vendor_cache_user_path(const std::string& vendor_id) { - const Semver ver = get_version_from_json(json_path); - if (ver.valid()) - return ver.to_string(); - 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)); + return (boost::filesystem::path(data_dir()) / PRESET_SYSTEM_DIR / (vendor_id + ".cache")) + .make_preferred().string(); } -template -static void save_blob(const std::string& path, const T& obj) +// static +std::string PresetBundle::vendor_cache_bundled_path(const std::string& vendor_id) +{ + return (boost::filesystem::path(resources_dir()) / "profiles" / (vendor_id + ".cache")) + .make_preferred().string(); +} + +// static — raw file I/O + magic/version/CRC check, shared by all cache readers. +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(&fhdr), sizeof(fhdr))) + return false; + if (fhdr.magic != VendorCacheHeader::CACHE_MAGIC || fhdr.version != VendorCacheHeader::CACHE_VERSION) + 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(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) << "VendorCache: CRC mismatch: " << path; + return false; + } + return true; + } catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(warning) << "VendorCache: read failed (" << path << "): " << e.what(); + return false; + } +} + +// Serialize PresetBundle's preset collections for vendor_id directly to file. +// Uses cereal's size-tag protocol so the binary layout matches what a +// std::vector archive would produce — no intermediate vector copy. +void PresetBundle::write_vendor_cache(const std::string& path, + const std::string& vendor_id, + const std::string& json_ver, + bool capture_filament_maps) const { std::ostringstream oss; { cereal::BinaryOutputArchive ar(oss); - ar(obj); + + VendorCacheHeader hdr; + hdr.cache_version = VendorCacheHeader::CACHE_VERSION; + hdr.config_options_count = static_cast(print_config_def.options.size()); + hdr.vendor_json_version = json_ver; + auto vp_it = vendors.find(vendor_id); + if (vp_it != vendors.end()) + hdr.profile = vp_it->second; + ar(hdr); + + // Write each collection filtered to vendor_id without copying into a temp vector. + auto write_col = [&](const PresetCollection& coll) { + std::vector ptrs; + for (const Preset& p : coll()) + if (p.is_system && p.vendor && p.vendor->id == vendor_id) + ptrs.push_back(&p); + ar(cereal::make_size_tag(static_cast(ptrs.size()))); + for (const Preset* p : ptrs) + ar(*p); + }; + write_col(prints); + write_col(filaments); + write_col(printers); + write_col(sla_prints); + write_col(sla_materials); + + if (capture_filament_maps) { + ar(m_config_maps, m_filament_id_maps); + } else { + const std::map empty_cm; + const std::map empty_fi; + ar(empty_cm, empty_fi); + } } const std::string blob = oss.str(); boost::crc_32_type crc; @@ -5842,44 +5900,74 @@ static void save_blob(const std::string& path, const T& obj) BOOST_LOG_TRIVIAL(warning) << "VendorCache: cannot open for writing: " << path; return; } - CacheFileHeader hdr; - hdr.magic = PresetBundle::VendorCache::CACHE_MAGIC; - hdr.version = PresetBundle::VendorCache::CACHE_VERSION; - hdr.data_size = static_cast(blob.size()); - hdr.crc32 = crc.checksum(); - ofs.write(reinterpret_cast(&hdr), sizeof(hdr)); + CacheFileHeader fhdr; + fhdr.magic = VendorCacheHeader::CACHE_MAGIC; + fhdr.version = VendorCacheHeader::CACHE_VERSION; + fhdr.data_size = static_cast(blob.size()); + fhdr.crc32 = crc.checksum(); + ofs.write(reinterpret_cast(&fhdr), sizeof(fhdr)); ofs.write(blob.data(), static_cast(blob.size())); } catch (const std::exception& e) { BOOST_LOG_TRIVIAL(warning) << "VendorCache: write failed (" << path << "): " << e.what(); } } -template -static bool load_blob(const std::string& path, T& obj) +// Read a cache file and — if valid — apply its presets directly into PresetBundle's +// collections. Deserializes all 5 collections into temp vectors first so that +// PresetBundle is not modified if deserialization fails partway through. +bool PresetBundle::load_and_apply_vendor_cache(const std::string& path, const std::string& json_ver) { + std::string blob; + if (!read_cache_blob(path, blob)) + return false; try { - boost::nowide::ifstream ifs(path, std::ios::binary); - if (!ifs.is_open()) - return false; - CacheFileHeader hdr; - if (!ifs.read(reinterpret_cast(&hdr), sizeof(hdr))) - return false; - if (hdr.magic != PresetBundle::VendorCache::CACHE_MAGIC || hdr.version != PresetBundle::VendorCache::CACHE_VERSION) - return false; - if (hdr.data_size == 0 || hdr.data_size > 512u * 1024u * 1024u) - return false; - std::string blob(hdr.data_size, '\0'); - if (!ifs.read(&blob[0], static_cast(hdr.data_size))) - return false; - boost::crc_32_type crc; - crc.process_bytes(blob.data(), blob.size()); - if (crc.checksum() != hdr.crc32) { - BOOST_LOG_TRIVIAL(warning) << "VendorCache: CRC mismatch: " << path; - return false; - } std::istringstream iss(blob); cereal::BinaryInputArchive ar(iss); - ar(obj); + + VendorCacheHeader hdr; + ar(hdr); + if (!hdr.is_valid(json_ver)) + return false; + + // Deserialize all collections before touching PresetBundle (rollback safety). + std::vector col_print, col_filament, col_printer, col_sla_print, col_sla_material; + std::map config_maps; + std::map filament_id_maps; + ar(col_print, col_filament, col_printer, col_sla_print, col_sla_material); + ar(config_maps, filament_id_maps); + + // Full deserialization succeeded — apply to PresetBundle. + vendors.emplace(hdr.profile.id, hdr.profile); + const VendorProfile* vp = &vendors.at(hdr.profile.id); + + auto apply_col = [&](std::vector& cached, PresetCollection& coll, bool is_filaments) { + for (Preset& cp : cached) { + DynamicPrintConfig config = cp.config; + Preset& p = coll.load_preset(cp.file, cp.name, std::move(config), /*select=*/false, cp.version); + p.is_system = true; + p.is_visible = cp.is_visible; + p.alias = cp.alias; + p.renamed_from = cp.renamed_from; + p.filament_id = cp.filament_id; + p.setting_id = cp.setting_id; + p.description = cp.description; + p.m_from_orca_filament_lib = cp.m_from_orca_filament_lib; + p.m_excluded_from = cp.m_excluded_from; + p.vendor = vp; + if (is_filaments) + coll.set_printer_hold_alias(p.alias, p); + } + }; + apply_col(col_print, prints, false); + apply_col(col_filament, filaments, true); + apply_col(col_printer, printers, false); + apply_col(col_sla_print, sla_prints, false); + apply_col(col_sla_material, sla_materials, false); + + if (!config_maps.empty()) { + m_config_maps = std::move(config_maps); + m_filament_id_maps = std::move(filament_id_maps); + } return true; } catch (const std::exception& e) { BOOST_LOG_TRIVIAL(warning) << "VendorCache: load failed (" << path << "): " << e.what(); @@ -5887,114 +5975,78 @@ static bool load_blob(const std::string& path, T& obj) } } -} // anonymous namespace - -std::string PresetBundle::VendorCache::user_path(const std::string& vendor_id) +bool PresetBundle::try_load_and_apply_vendor_cache(const std::string& vendor_id, + const std::string& json_ver) { - return (boost::filesystem::path(data_dir()) / PRESET_SYSTEM_DIR / (vendor_id + ".cache")) - .make_preferred().string(); -} - -std::string PresetBundle::VendorCache::bundled_path(const std::string& vendor_id) -{ - return (boost::filesystem::path(resources_dir()) / "profiles" / (vendor_id + ".cache")) - .make_preferred().string(); -} - -bool PresetBundle::VendorCache::load(const std::string& path) -{ - return load_blob(path, *this); -} - -void PresetBundle::VendorCache::save(const std::string& path) const -{ - save_blob(path, *this); -} - -void PresetBundle::VendorCache::capture(const PresetBundle& bundle, - const std::string& vendor_id, - const std::string& vendor_json_ver, - bool capture_filament_maps) -{ - cache_version = CACHE_VERSION; - config_options_count = static_cast(print_config_def.options.size()); - vendor_json_version = vendor_json_ver; - - print_presets.clear(); - filament_presets.clear(); - printer_presets.clear(); - sla_print_presets.clear(); - sla_material_presets.clear(); - config_maps.clear(); - filament_id_maps.clear(); - - // Vendor profile — copy directly; VendorProfile now carries its own serialize() - auto vp_it = bundle.vendors.find(vendor_id); - if (vp_it != bundle.vendors.end()) - profile = vp_it->second; - - // Presets — only those belonging to this vendor; Preset now carries its own serialize() - auto capture_col = [&](const PresetCollection& coll, std::vector& out) { - for (const Preset& p : coll()) { - if (!p.is_system) continue; - if (p.vendor == nullptr || p.vendor->id != vendor_id) continue; - out.push_back(p); // vendor pointer not serialized; apply() reconstructs it - } - }; - capture_col(bundle.prints, print_presets); - capture_col(bundle.filaments, filament_presets); - capture_col(bundle.printers, printer_presets); - capture_col(bundle.sla_prints, sla_print_presets); - capture_col(bundle.sla_materials, sla_material_presets); - - if (capture_filament_maps) { - config_maps = bundle.m_config_maps; - filament_id_maps = bundle.m_filament_id_maps; + if (load_and_apply_vendor_cache(vendor_cache_user_path(vendor_id), json_ver)) + return true; + if (load_and_apply_vendor_cache(vendor_cache_bundled_path(vendor_id), json_ver)) { + // Promote bundled cache to user cache so future loads skip this path. + write_vendor_cache(vendor_cache_user_path(vendor_id), vendor_id, json_ver, + vendor_id == ORCA_FILAMENT_LIBRARY); + return true; } + return false; } -void PresetBundle::VendorCache::apply(PresetBundle& bundle) const +PresetBundle::VendorCacheStats PresetBundle::save_bundled_vendor_cache( + const std::string& vendor_id, const std::string& json_path, + bool capture_filament_maps, const std::string& output_path) const { - // Restore vendor profile (additive — does not reset the bundle) - bundle.vendors.emplace(profile.id, profile); + const std::string json_ver = get_vendor_cache_key(json_path); + write_vendor_cache(output_path, vendor_id, json_ver, capture_filament_maps); - // Restore presets; vendor pointer is reconstructed from profile.id since all - // presets in this cache belong to the same vendor. - const VendorProfile* vp = nullptr; - { - auto it = bundle.vendors.find(profile.id); - if (it != bundle.vendors.end()) - vp = &it->second; - } - - auto apply_col = [&](const std::vector& cached, - PresetCollection& coll, - bool is_filaments) { - for (const Preset& cp : cached) { - DynamicPrintConfig config = cp.config; - Preset& p = coll.load_preset(cp.file, cp.name, std::move(config), /*select=*/false, cp.version); - p.is_system = true; - p.is_visible = cp.is_visible; - p.alias = cp.alias; - p.renamed_from = cp.renamed_from; - p.filament_id = cp.filament_id; - p.setting_id = cp.setting_id; - p.description = cp.description; - p.m_from_orca_filament_lib = cp.m_from_orca_filament_lib; - p.vendor = vp; - if (is_filaments) - coll.set_printer_hold_alias(p.alias, p); - } + VendorCacheStats stats; + auto count_col = [&](const PresetCollection& coll) -> size_t { + size_t n = 0; + for (const Preset& p : coll()) + if (p.is_system && p.vendor && p.vendor->id == vendor_id) ++n; + return n; }; - apply_col(print_presets, bundle.prints, false); - apply_col(filament_presets, bundle.filaments, true); - apply_col(printer_presets, bundle.printers, false); - apply_col(sla_print_presets, bundle.sla_prints, false); - apply_col(sla_material_presets, bundle.sla_materials, false); + stats.print_presets = count_col(prints); + stats.filament_presets = count_col(filaments); + stats.printer_presets = count_col(printers); - if (!config_maps.empty()) { - bundle.m_config_maps = config_maps; - bundle.m_filament_id_maps = filament_id_maps; + // Verify: read back header and check validity. + if (std::string blob; read_cache_blob(output_path, blob)) { + try { + std::istringstream iss(blob); + cereal::BinaryInputArchive ar(iss); + VendorCacheHeader hdr; ar(hdr); + stats.ok = hdr.is_valid(json_ver); + } catch (...) {} + } + return stats; +} + +// static +bool PresetBundle::load_vendor_cache_for_guide(const std::string& cache_path, + const std::string& json_ver, + VendorProfile& out_profile, + std::vector& out_printer_presets, + std::vector& out_filament_presets, + std::vector& out_print_presets) +{ + std::string blob; + if (!read_cache_blob(cache_path, blob)) + return false; + try { + std::istringstream iss(blob); + cereal::BinaryInputArchive ar(iss); + VendorCacheHeader hdr; ar(hdr); + if (!hdr.is_valid(json_ver)) + return false; + out_profile = std::move(hdr.profile); + // Must read in serialization order: print, filament, printer, sla_print, sla_material. + std::vector col_sla_print, col_sla_material; + std::map cm; + std::map fi; + ar(out_print_presets, out_filament_presets, out_printer_presets, col_sla_print, col_sla_material); + ar(cm, fi); + return true; + } catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(warning) << "VendorCache: guide load failed (" << cache_path << "): " << e.what(); + return false; } } diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index 62edbc14b2..e5def0269b 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -156,59 +156,34 @@ public: // One file per vendor: // Bundled (CI-generated): resources/profiles/.cache // User (runtime): data_dir/system/.cache - // VendorProfile, VendorProfile::PrinterModel, and Preset carry their own - // cereal serialize() methods so no mirror structs are needed. - struct VendorCache { - static constexpr uint32_t CACHE_MAGIC = 0x4F52435A; // "ORCZ" - static constexpr uint32_t CACHE_VERSION = 6; - - uint32_t cache_version = CACHE_VERSION; - uint32_t config_options_count = 0; // fixed-width: size_t varies between 32/64-bit builds - std::string vendor_json_version; // Semver string, or mtime: for version-less vendors - - VendorProfile profile; - std::vector print_presets; - std::vector filament_presets; - std::vector printer_presets; - std::vector sla_print_presets; - std::vector sla_material_presets; - - // Only populated for the ORCA_FILAMENT_LIBRARY vendor. - std::map config_maps; - std::map filament_id_maps; - - template - void serialize(Archive& ar) - { - ar(cache_version, config_options_count, vendor_json_version, - profile, - print_presets, filament_presets, printer_presets, - sla_print_presets, sla_material_presets, - config_maps, filament_id_maps); - } - - bool is_valid(const std::string& current_vendor_json_version) const - { - return cache_version == CACHE_VERSION - && config_options_count == static_cast(print_config_def.options.size()) - && vendor_json_version == current_vendor_json_version; - } - - static std::string user_path(const std::string& vendor_id); // data_dir/system/.cache - static std::string bundled_path(const std::string& vendor_id); // resources/profiles/.cache - - bool load(const std::string& path); - void save(const std::string& path) const; - - void capture(const PresetBundle& bundle, - const std::string& vendor_id, - const std::string& vendor_json_version, - bool capture_filament_maps); - - void apply(PresetBundle& bundle) const; + struct VendorCacheStats { + bool ok = false; + size_t print_presets = 0; + size_t filament_presets = 0; + size_t printer_presets = 0; }; + static std::string vendor_cache_user_path(const std::string& vendor_id); + static std::string vendor_cache_bundled_path(const std::string& vendor_id); + + // Capture the given vendor from the loaded bundle, write to output_path, + // and verify the result. Used by generate_system_cache and tests. + // json_path is the vendor's .json profile file; its version/mtime is computed internally. + VendorCacheStats save_bundled_vendor_cache(const std::string& vendor_id, + const std::string& json_path, + bool capture_filament_maps, + const std::string& output_path) const; + + // Load a vendor cache file and extract the data needed by the guide wizard. + // json_ver must match the cache's stored version key (use get_vendor_cache_key()). + // Returns false if the file is missing, stale, or corrupt. + static bool load_vendor_cache_for_guide(const std::string& cache_path, + const std::string& json_ver, + VendorProfile& out_profile, + std::vector& out_printer_presets, + std::vector& out_filament_presets, + std::vector& out_print_presets); static DynamicPrintConfig construct_full_config(Preset &in_printer_preset, Preset &in_print_preset, @@ -543,6 +518,43 @@ public: bool has_errors(bool check_duplicate_filament_subtypes = false) const; private: + // ---- VendorCache — header-only metadata struct -------------------------- + // Preset collections are NOT stored here; they are serialized directly from + // PresetBundle's own prints/filaments/printers/… on save, and applied directly + // into those collections on load. This avoids duplicating the preset vectors. + struct VendorCacheHeader { + static constexpr uint32_t CACHE_MAGIC = 0x4F52435A; // "ORCZ" + static constexpr uint32_t CACHE_VERSION = 8; + + uint32_t cache_version = CACHE_VERSION; + uint32_t config_options_count = 0; + std::string vendor_json_version; + VendorProfile profile; + + template + void serialize(Archive& ar) + { + ar(cache_version, config_options_count, vendor_json_version, profile); + } + + bool is_valid(const std::string& current_vendor_json_version) const + { + return cache_version == CACHE_VERSION + && config_options_count == static_cast(print_config_def.options.size()) + && vendor_json_version == current_vendor_json_version; + } + }; + + // Returns true and applies the cache if a valid file exists for vendor_id. + bool try_load_and_apply_vendor_cache(const std::string& vendor_id, const std::string& json_ver); + // Read a cache file, verify it, and apply its presets directly into PresetBundle's collections. + bool load_and_apply_vendor_cache(const std::string& path, const std::string& json_ver); + // Write PresetBundle's presets for vendor_id directly to a cache file — no intermediate copy. + void write_vendor_cache(const std::string& path, const std::string& vendor_id, + const std::string& json_ver, bool capture_filament_maps) const; + // Read raw blob bytes (file I/O + magic/CRC check) shared by all cache readers. + static bool read_cache_blob(const std::string& path, std::string& out_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; diff --git a/src/slic3r/GUI/WebGuideDialog.cpp b/src/slic3r/GUI/WebGuideDialog.cpp index 238700a4cc..389938aa28 100644 --- a/src/slic3r/GUI/WebGuideDialog.cpp +++ b/src/slic3r/GUI/WebGuideDialog.cpp @@ -1422,12 +1422,17 @@ bool GuideFrame::BuildProfileDataFromBundledCache() const boost::filesystem::path json_path = rsrc_vendor_dir / (vendor_id + ".json"); const std::string ver_str = get_vendor_cache_key(json_path.string()); - PresetBundle::VendorCache vc; - if (!vc.load(cache_path.string()) || !vc.is_valid(ver_str)) + VendorProfile vc_profile; + std::vector vc_printer_presets; + std::vector vc_filament_presets; + std::vector vc_print_presets; + if (!PresetBundle::load_vendor_cache_for_guide(cache_path.string(), ver_str, + vc_profile, vc_printer_presets, + vc_filament_presets, vc_print_presets)) continue; // Models from this vendor's cached profile - for (const auto& cm : vc.profile.models) { + for (const auto& cm : vc_profile.models) { std::string nozzle_str; for (const auto& v : cm.variants) { if (!nozzle_str.empty()) nozzle_str += ";"; @@ -1439,7 +1444,7 @@ bool GuideFrame::BuildProfileDataFromBundledCache() materials_str += m; } boost::filesystem::path cover_path = - (boost::filesystem::path(resources_dir()) / "profiles" / vc.profile.id / (cm.id + "_cover.png")) + (boost::filesystem::path(resources_dir()) / "profiles" / vc_profile.id / (cm.id + "_cover.png")) .make_preferred(); if (!boost::filesystem::exists(cover_path)) cover_path = @@ -1449,7 +1454,7 @@ bool GuideFrame::BuildProfileDataFromBundledCache() json entry; entry["model"] = cm.id; entry["name"] = cm.name; - entry["vendor"] = vc.profile.id; + entry["vendor"] = vc_profile.id; entry["nozzle_diameter"] = nozzle_str; entry["materials"] = materials_str; entry["cover"] = cover_path.string(); @@ -1459,7 +1464,7 @@ bool GuideFrame::BuildProfileDataFromBundledCache() } // Machines from cached printer presets - for (const auto& cp : vc.printer_presets) { + for (const auto& cp : vc_printer_presets) { const auto* pm = cp.config.option("printer_model"); const auto* pv = cp.config.option("printer_variant"); if (!pm || pm->value.empty() || !pv) continue; @@ -1471,7 +1476,7 @@ bool GuideFrame::BuildProfileDataFromBundledCache() } // Filaments from cached filament presets - for (const auto& cp : vc.filament_presets) { + for (const auto& cp : vc_filament_presets) { const auto* fv = cp.config.option("filament_vendor"); const auto* ft = cp.config.option("filament_type"); const auto* compat = cp.config.option("compatible_printers"); @@ -1501,7 +1506,7 @@ bool GuideFrame::BuildProfileDataFromBundledCache() } // Process from cached print presets - for (const auto& cp : vc.print_presets) { + for (const auto& cp : vc_print_presets) { if (!cp.is_visible) continue; json entry; entry["name"] = cp.name;