Minimize field duplication by moving Cache thing into PresetBundle

This commit is contained in:
ExPikaPaka
2026-07-06 08:37:32 +02:00
parent fd3bee3c62
commit d7bacd5c1c
6 changed files with 296 additions and 372 deletions

View File

@@ -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

View File

@@ -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) {

View File

@@ -1,134 +0,0 @@
#include "libslic3r/PresetBundle.hpp"
#include <boost/filesystem.hpp>
#include <boost/program_options.hpp>
#include <iostream>
#include <iomanip>
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<ConfigOptionString>("printer_model");
const auto* pv = cp.config.option<ConfigOptionString>("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<ConfigOptionStrings>("filament_vendor");
const auto* ft = cp.config.option<ConfigOptionStrings>("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<std::string>(), "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<std::string>();
if (fs::is_directory(path)) {
// Inspect all .cache files in the directory.
std::vector<fs::path> 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;
}

View File

@@ -2233,20 +2233,7 @@ std::pair<PresetsConfigSubstitutions, std::string> 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<PresetsConfigSubstitutions, std::string> 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::milliseconds>(
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<class T>
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<char*>(&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<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) << "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<Preset> 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<uint32_t>(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<const Preset*> 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<cereal::size_type>(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<std::string, DynamicPrintConfig> empty_cm;
const std::map<std::string, std::string> 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<uint64_t>(blob.size());
hdr.crc32 = crc.checksum();
ofs.write(reinterpret_cast<const char*>(&hdr), sizeof(hdr));
CacheFileHeader fhdr;
fhdr.magic = VendorCacheHeader::CACHE_MAGIC;
fhdr.version = VendorCacheHeader::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) << "VendorCache: write failed (" << path << "): " << e.what();
}
}
template<class T>
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<char*>(&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<std::streamsize>(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<Preset> col_print, col_filament, col_printer, col_sla_print, col_sla_material;
std::map<std::string, DynamicPrintConfig> config_maps;
std::map<std::string, std::string> 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<Preset>& 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<uint32_t>(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<Preset>& 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<Preset>& 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<Preset>& out_printer_presets,
std::vector<Preset>& out_filament_presets,
std::vector<Preset>& 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<Preset> col_sla_print, col_sla_material;
std::map<std::string, DynamicPrintConfig> cm;
std::map<std::string, std::string> 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;
}
}

View File

@@ -156,59 +156,34 @@ public:
// One file per vendor:
// Bundled (CI-generated): resources/profiles/<vendor_id>.cache
// User (runtime): data_dir/system/<vendor_id>.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:<timestamp> for version-less vendors
VendorProfile profile;
std::vector<Preset> print_presets;
std::vector<Preset> filament_presets;
std::vector<Preset> printer_presets;
std::vector<Preset> sla_print_presets;
std::vector<Preset> sla_material_presets;
// Only populated for the ORCA_FILAMENT_LIBRARY vendor.
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, 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<uint32_t>(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/<id>.cache
static std::string bundled_path(const std::string& vendor_id); // resources/profiles/<id>.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<Preset>& out_printer_presets,
std::vector<Preset>& out_filament_presets,
std::vector<Preset>& 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<class Archive>
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<uint32_t>(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;

View File

@@ -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<Preset> vc_printer_presets;
std::vector<Preset> vc_filament_presets;
std::vector<Preset> 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<ConfigOptionString>("printer_model");
const auto* pv = cp.config.option<ConfigOptionString>("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<ConfigOptionStrings>("filament_vendor");
const auto* ft = cp.config.option<ConfigOptionStrings>("filament_type");
const auto* compat = cp.config.option<ConfigOptionStrings>("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;