mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-17 16:02:11 +00:00
Minimize field duplication by moving Cache thing into PresetBundle
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user