mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-10 10:47:16 +00:00
Simplify code by mergin it in PresetBundle
This commit is contained in:
@@ -1,9 +1,16 @@
|
|||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
#include "PresetBundle.hpp"
|
#include "PresetBundle.hpp"
|
||||||
#include "PresetBundleCache.hpp"
|
|
||||||
|
#include <boost/crc.hpp>
|
||||||
|
#include <cereal/archives/binary.hpp>
|
||||||
|
#include <cereal/types/map.hpp>
|
||||||
|
#include <cereal/types/polymorphic.hpp>
|
||||||
|
#include <cereal/types/string.hpp>
|
||||||
|
#include <cereal/types/vector.hpp>
|
||||||
#include "PrintConfig.hpp"
|
#include "PrintConfig.hpp"
|
||||||
#include "libslic3r.h"
|
#include "libslic3r.h"
|
||||||
#include "I18N.hpp"
|
#include "I18N.hpp"
|
||||||
@@ -2260,60 +2267,84 @@ std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_pre
|
|||||||
if (validation_mode)
|
if (validation_mode)
|
||||||
dir = (boost::filesystem::path(data_dir())).make_preferred();
|
dir = (boost::filesystem::path(data_dir())).make_preferred();
|
||||||
|
|
||||||
// Try loading from binary cache first (skips JSON parsing on cache hit).
|
// Per-vendor binary cache: try user cache, then bundled cache, then JSON parse.
|
||||||
// partial_dirty_vendors is non-empty when some vendors changed but others are still cached.
|
// Vendors that hit a cache are applied immediately; misses go through JSON parsing below.
|
||||||
std::set<std::string> partial_dirty_vendors;
|
std::set<std::string> cache_miss_vendors;
|
||||||
|
std::map<std::string, std::string> vendor_json_versions; // vendor_id → version string on disk
|
||||||
|
|
||||||
if (!validation_mode) {
|
if (!validation_mode) {
|
||||||
const auto t0 = std::chrono::steady_clock::now();
|
const auto t0 = std::chrono::steady_clock::now();
|
||||||
PresetBundleCache::SystemPresetsCache cache;
|
this->reset(false);
|
||||||
const std::string cache_file = PresetBundleCache::SystemPresetsCache::cache_path();
|
bool all_from_cache = true;
|
||||||
if (cache.load(cache_file) && cache.is_plausible()) {
|
|
||||||
std::set<std::string> dirty;
|
// Collect all vendor names from the system directory.
|
||||||
if (cache.get_dirty_vendors(dir.string(), dirty)) {
|
std::vector<std::string> all_vendor_names;
|
||||||
if (dirty.empty()) {
|
try {
|
||||||
// Full hit — every vendor is unchanged.
|
for (const auto& e : boost::filesystem::directory_iterator(dir)) {
|
||||||
cache.apply(*this);
|
if (Slic3r::is_json_file(e.path().string()))
|
||||||
update_system_maps();
|
all_vendor_names.push_back(e.path().stem().string());
|
||||||
const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
||||||
std::chrono::steady_clock::now() - t0).count();
|
|
||||||
BOOST_LOG_TRIVIAL(info) << "PresetBundle: system presets loaded from cache in " << ms << " ms";
|
|
||||||
return {PresetsConfigSubstitutions{}, ""};
|
|
||||||
}
|
|
||||||
// Partial hit — restore clean vendors from cache, re-parse only dirty ones.
|
|
||||||
BOOST_LOG_TRIVIAL(info) << "PresetBundle: partial cache hit, " << dirty.size()
|
|
||||||
<< " vendor(s) changed — re-parsing those only";
|
|
||||||
cache.apply_partial(*this, dirty);
|
|
||||||
partial_dirty_vendors = std::move(dirty);
|
|
||||||
}
|
}
|
||||||
// else: structural mismatch (format version or option count changed) → full re-parse.
|
} catch (const std::exception& ex) {
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "PresetBundle: cannot scan system dir: " << ex.what();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (partial_dirty_vendors.empty()) {
|
// Load ORCA_FILAMENT_LIBRARY first (other vendors' filaments inherit from it).
|
||||||
// No partial hit — try bundled cache shipped with the installer (first launch).
|
// Then load remaining vendors from per-vendor caches.
|
||||||
{
|
auto try_vendor_cache = [&](const std::string& vendor_name) -> bool {
|
||||||
const std::string bundled_dir =
|
const std::string json_path = (dir / (vendor_name + ".json")).string();
|
||||||
(boost::filesystem::path(resources_dir()) / "profiles").make_preferred().string();
|
const Semver ver = get_version_from_json(json_path);
|
||||||
PresetBundleCache::SystemPresetsCache bundled;
|
const std::string ver_str = ver.valid() ? ver.to_string() : "";
|
||||||
if (bundled.load(PresetBundleCache::SystemPresetsCache::bundled_cache_path()) &&
|
vendor_json_versions[vendor_name] = ver_str;
|
||||||
bundled.is_valid(bundled_dir) && bundled.is_plausible()) {
|
|
||||||
bundled.apply(*this);
|
VendorCache vc;
|
||||||
update_system_maps();
|
// 1. User per-vendor cache
|
||||||
// Promote to user cache so subsequent launches skip this check.
|
if (vc.load(VendorCache::user_path(vendor_name)) && vc.is_valid(ver_str)) {
|
||||||
bundled.save(cache_file);
|
vc.apply(*this);
|
||||||
const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
return true;
|
||||||
std::chrono::steady_clock::now() - t0).count();
|
}
|
||||||
BOOST_LOG_TRIVIAL(info) << "PresetBundle: system presets loaded from bundled cache in " << ms << " ms";
|
// 2. Bundled per-vendor cache (ships with installer)
|
||||||
return {PresetsConfigSubstitutions{}, ""};
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Sort: ORCA_FILAMENT_LIBRARY goes first, rest alphabetical.
|
||||||
|
std::sort(all_vendor_names.begin(), all_vendor_names.end(),
|
||||||
|
[](const std::string& a, const std::string& b) {
|
||||||
|
if (a == ORCA_FILAMENT_LIBRARY) return true;
|
||||||
|
if (b == ORCA_FILAMENT_LIBRARY) return false;
|
||||||
|
return a < b;
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const auto& name : all_vendor_names) {
|
||||||
|
if (!try_vendor_cache(name)) {
|
||||||
|
cache_miss_vendors.insert(name);
|
||||||
|
all_from_cache = false;
|
||||||
}
|
}
|
||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " cache miss, falling back to JSON load";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (all_from_cache && !all_vendor_names.empty()) {
|
||||||
|
update_system_maps();
|
||||||
|
const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
|
std::chrono::steady_clock::now() - t0).count();
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "PresetBundle: system presets loaded from per-vendor cache in " << ms << " ms";
|
||||||
|
return {PresetsConfigSubstitutions{}, ""};
|
||||||
|
}
|
||||||
|
if (!cache_miss_vendors.empty())
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "PresetBundle: " << cache_miss_vendors.size()
|
||||||
|
<< " vendor(s) need JSON parse: "
|
||||||
|
<< [&]{ std::string s; for (auto& v : cache_miss_vendors) s += v + " "; return s; }();
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto json_load_t0 = std::chrono::steady_clock::now();
|
const auto json_load_t0 = std::chrono::steady_clock::now();
|
||||||
PresetsConfigSubstitutions substitutions;
|
PresetsConfigSubstitutions substitutions;
|
||||||
std::string errors_cummulative;
|
std::string errors_cummulative;
|
||||||
bool first = partial_dirty_vendors.empty(); // false in partial mode: clean vendors already applied
|
// first = true means no vendor has been loaded yet (from cache or JSON).
|
||||||
|
// false = at least one vendor was applied from the per-vendor cache above.
|
||||||
|
bool first = validation_mode || cache_miss_vendors.size() == vendor_json_versions.size();
|
||||||
std::vector<std::string> vendor_names;
|
std::vector<std::string> vendor_names;
|
||||||
// store all vendor names in vendor_names
|
// store all vendor names in vendor_names
|
||||||
for (auto& dir_entry : boost::filesystem::directory_iterator(dir)) {
|
for (auto& dir_entry : boost::filesystem::directory_iterator(dir)) {
|
||||||
@@ -2335,8 +2366,8 @@ std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_pre
|
|||||||
std::vector<std::string> other_vendors;
|
std::vector<std::string> other_vendors;
|
||||||
other_vendors.reserve(vendor_names.size());
|
other_vendors.reserve(vendor_names.size());
|
||||||
for (auto& vn : vendor_names) {
|
for (auto& vn : vendor_names) {
|
||||||
// In partial mode, skip vendors already loaded from cache.
|
// Skip vendors already loaded from the per-vendor cache.
|
||||||
if (!partial_dirty_vendors.empty() && !partial_dirty_vendors.count(vn))
|
if (!validation_mode && !cache_miss_vendors.count(vn))
|
||||||
continue;
|
continue;
|
||||||
if (vn == ORCA_FILAMENT_LIBRARY)
|
if (vn == ORCA_FILAMENT_LIBRARY)
|
||||||
orca_lib_vendor = vn;
|
orca_lib_vendor = vn;
|
||||||
@@ -2424,15 +2455,20 @@ std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_pre
|
|||||||
BOOST_LOG_TRIVIAL(info) << "PresetBundle: system presets loaded from JSON in " << json_ms << " ms";
|
BOOST_LOG_TRIVIAL(info) << "PresetBundle: system presets loaded from JSON in " << json_ms << " ms";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Persist a binary cache so the next startup can skip JSON parsing.
|
// Save per-vendor binary caches for vendors that had to be parsed from JSON.
|
||||||
if (!validation_mode && errors_cummulative.empty()) {
|
if (!validation_mode && !cache_miss_vendors.empty() && errors_cummulative.empty()) {
|
||||||
const auto save_t0 = std::chrono::steady_clock::now();
|
const auto save_t0 = std::chrono::steady_clock::now();
|
||||||
PresetBundleCache::SystemPresetsCache cache;
|
for (const auto& vendor_name : cache_miss_vendors) {
|
||||||
cache.capture(*this, dir.string());
|
const bool is_orca_lib = (vendor_name == ORCA_FILAMENT_LIBRARY);
|
||||||
cache.save(PresetBundleCache::SystemPresetsCache::cache_path());
|
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));
|
||||||
|
}
|
||||||
const auto save_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
const auto save_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
std::chrono::steady_clock::now() - save_t0).count();
|
std::chrono::steady_clock::now() - save_t0).count();
|
||||||
BOOST_LOG_TRIVIAL(info) << "PresetBundle: system presets cache saved in " << save_ms << " ms";
|
BOOST_LOG_TRIVIAL(info) << "PresetBundle: per-vendor caches saved in " << save_ms << " ms";
|
||||||
}
|
}
|
||||||
|
|
||||||
//BBS: add config related logs
|
//BBS: add config related logs
|
||||||
@@ -6042,4 +6078,277 @@ bool BundleMetadata::save_to_json(const std::string& path) const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// ---- VendorCache implementation -----------------------------------------
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
struct CacheFileHeader {
|
||||||
|
uint32_t magic;
|
||||||
|
uint32_t version;
|
||||||
|
uint64_t data_size;
|
||||||
|
uint32_t crc32;
|
||||||
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
static_assert(sizeof(CacheFileHeader) == 20, "CacheFileHeader must be 20 bytes");
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
static void save_blob(const std::string& path, const T& obj)
|
||||||
|
{
|
||||||
|
std::ostringstream oss;
|
||||||
|
{
|
||||||
|
cereal::BinaryOutputArchive ar(oss);
|
||||||
|
ar(obj);
|
||||||
|
}
|
||||||
|
const std::string blob = oss.str();
|
||||||
|
boost::crc_32_type crc;
|
||||||
|
crc.process_bytes(blob.data(), blob.size());
|
||||||
|
try {
|
||||||
|
boost::filesystem::create_directories(boost::filesystem::path(path).parent_path());
|
||||||
|
boost::nowide::ofstream ofs(path, std::ios::binary | std::ios::trunc);
|
||||||
|
if (!ofs.is_open()) {
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "VendorCache: cannot open for writing: " << path;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CacheFileHeader hdr;
|
||||||
|
hdr.magic = VendorCache::CACHE_MAGIC;
|
||||||
|
hdr.version = 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));
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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 != VendorCache::CACHE_MAGIC || hdr.version != 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);
|
||||||
|
return true;
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "VendorCache: load failed (" << path << "): " << e.what();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
|
std::string VendorCache::user_path(const std::string& vendor_id)
|
||||||
|
{
|
||||||
|
return (boost::filesystem::path(data_dir()) / PRESET_SYSTEM_DIR / (vendor_id + ".cache"))
|
||||||
|
.make_preferred().string();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string VendorCache::bundled_path(const std::string& vendor_id)
|
||||||
|
{
|
||||||
|
return (boost::filesystem::path(resources_dir()) / "profiles" / (vendor_id + ".cache"))
|
||||||
|
.make_preferred().string();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VendorCache::load(const std::string& path)
|
||||||
|
{
|
||||||
|
return load_blob(path, *this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VendorCache::save(const std::string& path) const
|
||||||
|
{
|
||||||
|
save_blob(path, *this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void 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 = 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
|
||||||
|
auto vp_it = bundle.vendors.find(vendor_id);
|
||||||
|
if (vp_it != bundle.vendors.end()) {
|
||||||
|
const VendorProfile& vp = vp_it->second;
|
||||||
|
profile.id = vp.id;
|
||||||
|
profile.name = vp.name;
|
||||||
|
profile.config_version = vp.config_version.valid() ? vp.config_version.to_string() : "";
|
||||||
|
profile.config_update_url = vp.config_update_url;
|
||||||
|
profile.changelog_url = vp.changelog_url;
|
||||||
|
profile.models.clear();
|
||||||
|
for (const auto& model : vp.models) {
|
||||||
|
CachedPrinterModel cm;
|
||||||
|
cm.id = model.id;
|
||||||
|
cm.name = model.name;
|
||||||
|
cm.model_id = model.model_id;
|
||||||
|
cm.family = model.family;
|
||||||
|
cm.technology = static_cast<int>(model.technology);
|
||||||
|
for (const auto& v : model.variants)
|
||||||
|
cm.variants.push_back({v.name});
|
||||||
|
cm.default_materials = model.default_materials;
|
||||||
|
cm.not_support_bed_types = model.not_support_bed_types;
|
||||||
|
cm.bed_model = model.bed_model;
|
||||||
|
cm.bed_texture = model.bed_texture;
|
||||||
|
cm.image_bed_type = model.image_bed_type;
|
||||||
|
cm.bottom_texture_end_name = model.bottom_texture_end_name;
|
||||||
|
cm.use_double_extruder_default_texture = model.use_double_extruder_default_texture;
|
||||||
|
cm.bottom_texture_rect = model.bottom_texture_rect;
|
||||||
|
cm.middle_texture_rect = model.middle_texture_rect;
|
||||||
|
cm.hotend_model = model.hotend_model;
|
||||||
|
profile.models.push_back(std::move(cm));
|
||||||
|
}
|
||||||
|
profile.default_filaments.clear();
|
||||||
|
for (const auto& f : vp.default_filaments)
|
||||||
|
profile.default_filaments.push_back(f);
|
||||||
|
profile.default_sla_materials.clear();
|
||||||
|
for (const auto& m : vp.default_sla_materials)
|
||||||
|
profile.default_sla_materials.push_back(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Presets — only those belonging to this vendor
|
||||||
|
auto capture_col = [&](const PresetCollection& coll, std::vector<CachedPreset>& out) {
|
||||||
|
for (const Preset& p : coll()) {
|
||||||
|
if (!p.is_system) continue;
|
||||||
|
if (p.vendor == nullptr || p.vendor->id != vendor_id) continue;
|
||||||
|
CachedPreset cp;
|
||||||
|
cp.type = static_cast<int>(p.type);
|
||||||
|
cp.name = p.name;
|
||||||
|
cp.alias = p.alias;
|
||||||
|
cp.file = p.file;
|
||||||
|
cp.version = p.version.valid() ? p.version.to_string() : "";
|
||||||
|
cp.vendor_id = vendor_id;
|
||||||
|
cp.filament_id = p.filament_id;
|
||||||
|
cp.setting_id = p.setting_id;
|
||||||
|
cp.description = p.description;
|
||||||
|
cp.renamed_from = p.renamed_from;
|
||||||
|
cp.is_system = p.is_system;
|
||||||
|
cp.is_visible = p.is_visible;
|
||||||
|
cp.m_from_orca_filament_lib = p.m_from_orca_filament_lib;
|
||||||
|
cp.config = p.config;
|
||||||
|
out.push_back(std::move(cp));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VendorCache::apply(PresetBundle& bundle) const
|
||||||
|
{
|
||||||
|
// Restore vendor profile (additive — does not reset the bundle)
|
||||||
|
{
|
||||||
|
VendorProfile vp(profile.id);
|
||||||
|
vp.name = profile.name;
|
||||||
|
vp.config_update_url = profile.config_update_url;
|
||||||
|
vp.changelog_url = profile.changelog_url;
|
||||||
|
if (!profile.config_version.empty()) {
|
||||||
|
auto v = Semver::parse(profile.config_version);
|
||||||
|
if (v) vp.config_version = *v;
|
||||||
|
}
|
||||||
|
for (const auto& cm : profile.models) {
|
||||||
|
VendorProfile::PrinterModel model;
|
||||||
|
model.id = cm.id;
|
||||||
|
model.name = cm.name;
|
||||||
|
model.model_id = cm.model_id;
|
||||||
|
model.family = cm.family;
|
||||||
|
model.technology = static_cast<PrinterTechnology>(cm.technology);
|
||||||
|
for (const auto& v : cm.variants)
|
||||||
|
model.variants.emplace_back(v.name);
|
||||||
|
model.default_materials = cm.default_materials;
|
||||||
|
model.not_support_bed_types = cm.not_support_bed_types;
|
||||||
|
model.bed_model = cm.bed_model;
|
||||||
|
model.bed_texture = cm.bed_texture;
|
||||||
|
model.image_bed_type = cm.image_bed_type;
|
||||||
|
model.bottom_texture_end_name = cm.bottom_texture_end_name;
|
||||||
|
model.use_double_extruder_default_texture = cm.use_double_extruder_default_texture;
|
||||||
|
model.bottom_texture_rect = cm.bottom_texture_rect;
|
||||||
|
model.middle_texture_rect = cm.middle_texture_rect;
|
||||||
|
model.hotend_model = cm.hotend_model;
|
||||||
|
vp.models.push_back(std::move(model));
|
||||||
|
}
|
||||||
|
for (const auto& f : profile.default_filaments)
|
||||||
|
vp.default_filaments.insert(f);
|
||||||
|
for (const auto& m : profile.default_sla_materials)
|
||||||
|
vp.default_sla_materials.insert(m);
|
||||||
|
bundle.vendors.emplace(profile.id, std::move(vp));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore presets
|
||||||
|
auto apply_col = [&](const std::vector<CachedPreset>& cached,
|
||||||
|
PresetCollection& coll,
|
||||||
|
bool is_filaments) {
|
||||||
|
for (const auto& cp : cached) {
|
||||||
|
Semver version;
|
||||||
|
if (!cp.version.empty()) {
|
||||||
|
auto v = Semver::parse(cp.version);
|
||||||
|
if (v) version = *v;
|
||||||
|
}
|
||||||
|
DynamicPrintConfig config = cp.config;
|
||||||
|
Preset& p = coll.load_preset(cp.file, cp.name, std::move(config), /*select=*/false, 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;
|
||||||
|
if (!cp.vendor_id.empty()) {
|
||||||
|
auto it = bundle.vendors.find(cp.vendor_id);
|
||||||
|
if (it != bundle.vendors.end())
|
||||||
|
p.vendor = &it->second;
|
||||||
|
}
|
||||||
|
if (is_filaments)
|
||||||
|
coll.set_printer_hold_alias(p.alias, p);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (!config_maps.empty()) {
|
||||||
|
bundle.m_config_maps = config_maps;
|
||||||
|
bundle.m_filament_id_maps = filament_id_maps;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|||||||
@@ -13,6 +13,13 @@
|
|||||||
#include <boost/filesystem/path.hpp>
|
#include <boost/filesystem/path.hpp>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|
||||||
|
#include <cereal/archives/binary.hpp>
|
||||||
|
#include <cereal/cereal.hpp>
|
||||||
|
#include <cereal/types/map.hpp>
|
||||||
|
#include <cereal/types/polymorphic.hpp>
|
||||||
|
#include <cereal/types/string.hpp>
|
||||||
|
#include <cereal/types/vector.hpp>
|
||||||
|
|
||||||
#define DEFAULT_USER_FOLDER_NAME "default"
|
#define DEFAULT_USER_FOLDER_NAME "default"
|
||||||
#define BUNDLE_STRUCTURE_JSON_NAME "bundle_structure.json"
|
#define BUNDLE_STRUCTURE_JSON_NAME "bundle_structure.json"
|
||||||
|
|
||||||
@@ -166,6 +173,126 @@ struct PresetBundleMetadata
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ---- Per-vendor binary preset cache ----------------------------------------
|
||||||
|
// One file per vendor:
|
||||||
|
// Bundled (CI-generated): resources/profiles/<vendor_id>.cache
|
||||||
|
// User (runtime): data_dir/system/<vendor_id>.cache
|
||||||
|
|
||||||
|
struct CachedPrinterVariant {
|
||||||
|
std::string name;
|
||||||
|
template<class Archive> void serialize(Archive& ar) { ar(name); }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CachedPrinterModel {
|
||||||
|
std::string id, name, model_id, family;
|
||||||
|
int technology = 0;
|
||||||
|
std::vector<CachedPrinterVariant> variants;
|
||||||
|
std::vector<std::string> default_materials;
|
||||||
|
std::vector<std::string> not_support_bed_types;
|
||||||
|
std::string bed_model, bed_texture, image_bed_type;
|
||||||
|
std::string bottom_texture_end_name, use_double_extruder_default_texture;
|
||||||
|
std::string bottom_texture_rect, middle_texture_rect, hotend_model;
|
||||||
|
|
||||||
|
template<class Archive>
|
||||||
|
void serialize(Archive& ar)
|
||||||
|
{
|
||||||
|
ar(id, name, model_id, family, technology, variants, default_materials,
|
||||||
|
not_support_bed_types, bed_model, bed_texture, image_bed_type,
|
||||||
|
bottom_texture_end_name, use_double_extruder_default_texture,
|
||||||
|
bottom_texture_rect, middle_texture_rect, hotend_model);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CachedVendorProfile {
|
||||||
|
std::string id, name, config_version, config_update_url, changelog_url;
|
||||||
|
std::vector<CachedPrinterModel> models;
|
||||||
|
std::vector<std::string> default_filaments;
|
||||||
|
std::vector<std::string> default_sla_materials;
|
||||||
|
|
||||||
|
template<class Archive>
|
||||||
|
void serialize(Archive& ar)
|
||||||
|
{
|
||||||
|
ar(id, name, config_version, config_update_url, changelog_url,
|
||||||
|
models, default_filaments, default_sla_materials);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CachedPreset {
|
||||||
|
int type = 0;
|
||||||
|
std::string name, alias, file, version;
|
||||||
|
std::string vendor_id;
|
||||||
|
std::string filament_id, setting_id, description;
|
||||||
|
std::string base_id, user_id, sync_info;
|
||||||
|
long long updated_time = 0;
|
||||||
|
std::vector<std::string> renamed_from;
|
||||||
|
bool is_system = true;
|
||||||
|
bool is_visible = true;
|
||||||
|
bool m_from_orca_filament_lib = false;
|
||||||
|
DynamicPrintConfig config;
|
||||||
|
|
||||||
|
template<class Archive>
|
||||||
|
void serialize(Archive& ar)
|
||||||
|
{
|
||||||
|
ar(type, name, alias, file, version, vendor_id, filament_id, setting_id,
|
||||||
|
description, base_id, user_id, sync_info, updated_time, renamed_from,
|
||||||
|
is_system, is_visible, m_from_orca_filament_lib, config);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VendorCache {
|
||||||
|
static constexpr uint32_t CACHE_MAGIC = 0x4F52435A; // "ORCZ"
|
||||||
|
static constexpr uint32_t CACHE_VERSION = 3;
|
||||||
|
|
||||||
|
uint32_t cache_version = CACHE_VERSION;
|
||||||
|
size_t config_options_count = 0;
|
||||||
|
std::string vendor_json_version; // Semver string, or "" for version-less vendors
|
||||||
|
|
||||||
|
CachedVendorProfile profile;
|
||||||
|
std::vector<CachedPreset> print_presets;
|
||||||
|
std::vector<CachedPreset> filament_presets;
|
||||||
|
std::vector<CachedPreset> printer_presets;
|
||||||
|
std::vector<CachedPreset> sla_print_presets;
|
||||||
|
std::vector<CachedPreset> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// True when this cache entry is current and can be used without re-parsing JSON.
|
||||||
|
bool is_valid(const std::string& current_vendor_json_version) const
|
||||||
|
{
|
||||||
|
return cache_version == CACHE_VERSION
|
||||||
|
&& config_options_count == 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;
|
||||||
|
|
||||||
|
// Capture one vendor's data from an already-loaded PresetBundle.
|
||||||
|
// Set capture_filament_maps=true only for the ORCA_FILAMENT_LIBRARY vendor.
|
||||||
|
void capture(const PresetBundle& bundle,
|
||||||
|
const std::string& vendor_id,
|
||||||
|
const std::string& vendor_json_version,
|
||||||
|
bool capture_filament_maps);
|
||||||
|
|
||||||
|
// Apply this vendor's data into bundle (additive — does NOT reset the bundle).
|
||||||
|
void apply(PresetBundle& bundle) const;
|
||||||
|
};
|
||||||
|
|
||||||
// Bundle of Print + Filament + Printer presets.
|
// Bundle of Print + Filament + Printer presets.
|
||||||
class PresetBundle
|
class PresetBundle
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,346 +0,0 @@
|
|||||||
#include "PresetBundleCache.hpp"
|
|
||||||
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
#include <boost/crc.hpp>
|
|
||||||
#include <boost/filesystem.hpp>
|
|
||||||
#include <boost/log/trivial.hpp>
|
|
||||||
#include <boost/nowide/fstream.hpp>
|
|
||||||
#include <cereal/archives/binary.hpp>
|
|
||||||
#include <cereal/types/map.hpp>
|
|
||||||
#include <cereal/types/polymorphic.hpp>
|
|
||||||
#include <cereal/types/string.hpp>
|
|
||||||
#include <cereal/types/vector.hpp>
|
|
||||||
|
|
||||||
#include "PresetBundle.hpp"
|
|
||||||
#include "PrintConfig.hpp"
|
|
||||||
#include "Semver.hpp"
|
|
||||||
#include "Utils.hpp"
|
|
||||||
|
|
||||||
namespace Slic3r {
|
|
||||||
namespace PresetBundleCache {
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// Binary cache file format: raw 20-byte header followed by cereal blob.
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
static constexpr uint32_t CACHE_MAGIC = 0x4F52435A; // "ORCZ"
|
|
||||||
static constexpr uint32_t CACHE_FILE_VERSION = 1;
|
|
||||||
|
|
||||||
#pragma pack(push, 1)
|
|
||||||
struct CacheFileHeader {
|
|
||||||
uint32_t magic;
|
|
||||||
uint32_t file_version;
|
|
||||||
uint64_t data_size;
|
|
||||||
uint32_t crc32;
|
|
||||||
};
|
|
||||||
#pragma pack(pop)
|
|
||||||
static_assert(sizeof(CacheFileHeader) == 20, "CacheFileHeader must be 20 bytes");
|
|
||||||
|
|
||||||
template<class T>
|
|
||||||
static void save_blob(const std::string& path, const T& obj)
|
|
||||||
{
|
|
||||||
std::ostringstream oss(std::ios::out | std::ios::binary);
|
|
||||||
{
|
|
||||||
cereal::BinaryOutputArchive ar(oss);
|
|
||||||
ar(obj);
|
|
||||||
}
|
|
||||||
const std::string blob = oss.str();
|
|
||||||
boost::crc_32_type crc;
|
|
||||||
crc.process_bytes(blob.data(), blob.size());
|
|
||||||
try {
|
|
||||||
boost::filesystem::create_directories(boost::filesystem::path(path).parent_path());
|
|
||||||
boost::nowide::ofstream ofs(path, std::ios::binary | std::ios::trunc);
|
|
||||||
if (!ofs.is_open()) {
|
|
||||||
BOOST_LOG_TRIVIAL(warning) << "PresetBundleCache: cannot open for writing: " << path;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
CacheFileHeader hdr;
|
|
||||||
hdr.magic = CACHE_MAGIC;
|
|
||||||
hdr.file_version = CACHE_FILE_VERSION;
|
|
||||||
hdr.data_size = static_cast<uint64_t>(blob.size());
|
|
||||||
hdr.crc32 = crc.checksum();
|
|
||||||
ofs.write(reinterpret_cast<const char*>(&hdr), sizeof(hdr));
|
|
||||||
ofs.write(blob.data(), static_cast<std::streamsize>(blob.size()));
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
BOOST_LOG_TRIVIAL(warning) << "PresetBundleCache: write failed (" << path << "): " << e.what();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class T>
|
|
||||||
static bool load_blob(const std::string& path, T& obj)
|
|
||||||
{
|
|
||||||
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 != CACHE_MAGIC || hdr.file_version != CACHE_FILE_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) << "PresetBundleCache: CRC32 mismatch: " << path;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
std::istringstream iss(blob, std::ios::in | std::ios::binary);
|
|
||||||
cereal::BinaryInputArchive ar(iss);
|
|
||||||
ar(obj);
|
|
||||||
return true;
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
BOOST_LOG_TRIVIAL(warning) << "PresetBundleCache: load failed (" << path << "): " << e.what();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// Helpers
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
static std::string vendor_root_json(const std::string& system_dir, const std::string& vendor_id)
|
|
||||||
{
|
|
||||||
return (boost::filesystem::path(system_dir) / (vendor_id + ".json")).make_preferred().string();
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// SystemPresetsCache
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
std::string SystemPresetsCache::cache_path()
|
|
||||||
{
|
|
||||||
return (boost::filesystem::path(data_dir()) / PRESET_SYSTEM_DIR / "system_presets_cache.cache")
|
|
||||||
.make_preferred().string();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string SystemPresetsCache::bundled_cache_path()
|
|
||||||
{
|
|
||||||
return (boost::filesystem::path(resources_dir()) / "profiles" / "system_presets_cache.cache")
|
|
||||||
.make_preferred().string();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SystemPresetsCache::is_valid(const std::string& system_dir) const
|
|
||||||
{
|
|
||||||
std::set<std::string> dummy;
|
|
||||||
return get_dirty_vendors(system_dir, dummy) && dummy.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SystemPresetsCache::get_dirty_vendors(const std::string& system_dir, std::set<std::string>& out_dirty) const
|
|
||||||
{
|
|
||||||
out_dirty.clear();
|
|
||||||
if (format_version != FORMAT_VERSION || config_options_count != print_config_def.options.size())
|
|
||||||
return false;
|
|
||||||
std::map<std::string, std::string> current;
|
|
||||||
try {
|
|
||||||
for (const auto& entry : boost::filesystem::directory_iterator(system_dir)) {
|
|
||||||
const std::string path = entry.path().string();
|
|
||||||
if (!Slic3r::is_json_file(path))
|
|
||||||
continue;
|
|
||||||
const std::string vendor_name = entry.path().stem().string();
|
|
||||||
Semver ver = get_version_from_json(path);
|
|
||||||
if (ver.valid())
|
|
||||||
current[vendor_name] = ver.to_string();
|
|
||||||
}
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
BOOST_LOG_TRIVIAL(warning) << "PresetBundleCache: directory scan failed: " << e.what();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// A vendor was removed from disk — safest to force a full re-parse.
|
|
||||||
for (const auto& [name, ver] : vendor_versions)
|
|
||||||
if (current.find(name) == current.end())
|
|
||||||
return false;
|
|
||||||
// Collect vendors with changed or new version strings.
|
|
||||||
for (const auto& [name, ver] : current) {
|
|
||||||
auto it = vendor_versions.find(name);
|
|
||||||
if (it == vendor_versions.end() || it->second != ver)
|
|
||||||
out_dirty.insert(name);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SystemPresetsCache::capture(const PresetBundle& bundle, const std::string& system_dir)
|
|
||||||
{
|
|
||||||
format_version = FORMAT_VERSION;
|
|
||||||
config_options_count = print_config_def.options.size();
|
|
||||||
vendor_versions.clear();
|
|
||||||
vendor_profiles.clear();
|
|
||||||
print_presets.clear();
|
|
||||||
filament_presets.clear();
|
|
||||||
printer_presets.clear();
|
|
||||||
sla_print_presets.clear();
|
|
||||||
sla_material_presets.clear();
|
|
||||||
config_maps = bundle.m_config_maps;
|
|
||||||
filament_id_maps = bundle.m_filament_id_maps;
|
|
||||||
|
|
||||||
for (const auto& [id, vp] : bundle.vendors) {
|
|
||||||
CachedVendorProfile cvp;
|
|
||||||
cvp.id = vp.id;
|
|
||||||
cvp.name = vp.name;
|
|
||||||
cvp.config_version = vp.config_version.valid() ? vp.config_version.to_string() : "";
|
|
||||||
cvp.config_update_url = vp.config_update_url;
|
|
||||||
cvp.changelog_url = vp.changelog_url;
|
|
||||||
for (const auto& model : vp.models) {
|
|
||||||
CachedPrinterModel cm;
|
|
||||||
cm.id = model.id;
|
|
||||||
cm.name = model.name;
|
|
||||||
cm.model_id = model.model_id;
|
|
||||||
cm.family = model.family;
|
|
||||||
cm.technology = static_cast<int>(model.technology);
|
|
||||||
for (const auto& v : model.variants)
|
|
||||||
cm.variants.push_back({v.name});
|
|
||||||
cm.default_materials = model.default_materials;
|
|
||||||
cm.not_support_bed_types = model.not_support_bed_types;
|
|
||||||
cm.bed_model = model.bed_model;
|
|
||||||
cm.bed_texture = model.bed_texture;
|
|
||||||
cm.image_bed_type = model.image_bed_type;
|
|
||||||
cm.bottom_texture_end_name = model.bottom_texture_end_name;
|
|
||||||
cm.use_double_extruder_default_texture = model.use_double_extruder_default_texture;
|
|
||||||
cm.bottom_texture_rect = model.bottom_texture_rect;
|
|
||||||
cm.middle_texture_rect = model.middle_texture_rect;
|
|
||||||
cm.hotend_model = model.hotend_model;
|
|
||||||
cvp.models.push_back(std::move(cm));
|
|
||||||
}
|
|
||||||
for (const auto& f : vp.default_filaments)
|
|
||||||
cvp.default_filaments.push_back(f);
|
|
||||||
for (const auto& m : vp.default_sla_materials)
|
|
||||||
cvp.default_sla_materials.push_back(m);
|
|
||||||
vendor_profiles.push_back(std::move(cvp));
|
|
||||||
Semver ver = get_version_from_json(vendor_root_json(system_dir, id));
|
|
||||||
vendor_versions[id] = ver.valid() ? ver.to_string() : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
auto capture_col = [](const PresetCollection& coll, std::vector<CachedPreset>& out) {
|
|
||||||
for (const Preset& p : coll()) {
|
|
||||||
if (!p.is_system)
|
|
||||||
continue;
|
|
||||||
CachedPreset cp;
|
|
||||||
cp.type = static_cast<int>(p.type);
|
|
||||||
cp.name = p.name;
|
|
||||||
cp.alias = p.alias;
|
|
||||||
cp.file = p.file;
|
|
||||||
cp.version = p.version.valid() ? p.version.to_string() : "";
|
|
||||||
cp.vendor_id = (p.vendor != nullptr) ? p.vendor->id : "";
|
|
||||||
cp.filament_id = p.filament_id;
|
|
||||||
cp.setting_id = p.setting_id;
|
|
||||||
cp.description = p.description;
|
|
||||||
cp.renamed_from = p.renamed_from;
|
|
||||||
cp.is_system = p.is_system;
|
|
||||||
cp.is_visible = p.is_visible;
|
|
||||||
cp.m_from_orca_filament_lib = p.m_from_orca_filament_lib;
|
|
||||||
cp.config = p.config;
|
|
||||||
out.push_back(std::move(cp));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SystemPresetsCache::apply(PresetBundle& bundle) const
|
|
||||||
{
|
|
||||||
apply_partial(bundle, {});
|
|
||||||
}
|
|
||||||
|
|
||||||
void SystemPresetsCache::apply_partial(PresetBundle& bundle, const std::set<std::string>& skip_vendor_ids) const
|
|
||||||
{
|
|
||||||
bundle.reset(false);
|
|
||||||
for (const auto& cvp : vendor_profiles) {
|
|
||||||
if (skip_vendor_ids.count(cvp.id))
|
|
||||||
continue;
|
|
||||||
VendorProfile vp(cvp.id);
|
|
||||||
vp.name = cvp.name;
|
|
||||||
vp.config_update_url = cvp.config_update_url;
|
|
||||||
vp.changelog_url = cvp.changelog_url;
|
|
||||||
if (!cvp.config_version.empty()) {
|
|
||||||
auto v = Semver::parse(cvp.config_version);
|
|
||||||
if (v) vp.config_version = *v;
|
|
||||||
}
|
|
||||||
for (const auto& cm : cvp.models) {
|
|
||||||
VendorProfile::PrinterModel model;
|
|
||||||
model.id = cm.id;
|
|
||||||
model.name = cm.name;
|
|
||||||
model.model_id = cm.model_id;
|
|
||||||
model.family = cm.family;
|
|
||||||
model.technology = static_cast<PrinterTechnology>(cm.technology);
|
|
||||||
for (const auto& v : cm.variants)
|
|
||||||
model.variants.emplace_back(v.name);
|
|
||||||
model.default_materials = cm.default_materials;
|
|
||||||
model.not_support_bed_types = cm.not_support_bed_types;
|
|
||||||
model.bed_model = cm.bed_model;
|
|
||||||
model.bed_texture = cm.bed_texture;
|
|
||||||
model.image_bed_type = cm.image_bed_type;
|
|
||||||
model.bottom_texture_end_name = cm.bottom_texture_end_name;
|
|
||||||
model.use_double_extruder_default_texture = cm.use_double_extruder_default_texture;
|
|
||||||
model.bottom_texture_rect = cm.bottom_texture_rect;
|
|
||||||
model.middle_texture_rect = cm.middle_texture_rect;
|
|
||||||
model.hotend_model = cm.hotend_model;
|
|
||||||
vp.models.push_back(std::move(model));
|
|
||||||
}
|
|
||||||
for (const auto& f : cvp.default_filaments)
|
|
||||||
vp.default_filaments.insert(f);
|
|
||||||
for (const auto& m : cvp.default_sla_materials)
|
|
||||||
vp.default_sla_materials.insert(m);
|
|
||||||
bundle.vendors.emplace(cvp.id, std::move(vp));
|
|
||||||
}
|
|
||||||
|
|
||||||
auto apply_col = [&](const std::vector<CachedPreset>& cached,
|
|
||||||
PresetCollection& coll,
|
|
||||||
bool is_filaments) {
|
|
||||||
for (const auto& cp : cached) {
|
|
||||||
if (skip_vendor_ids.count(cp.vendor_id))
|
|
||||||
continue;
|
|
||||||
Semver version;
|
|
||||||
if (!cp.version.empty()) {
|
|
||||||
auto v = Semver::parse(cp.version);
|
|
||||||
if (v) version = *v;
|
|
||||||
}
|
|
||||||
DynamicPrintConfig config = cp.config;
|
|
||||||
Preset& p = coll.load_preset(cp.file, cp.name, std::move(config), /*select=*/false, 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;
|
|
||||||
if (!cp.vendor_id.empty()) {
|
|
||||||
auto it = bundle.vendors.find(cp.vendor_id);
|
|
||||||
if (it != bundle.vendors.end())
|
|
||||||
p.vendor = &it->second;
|
|
||||||
}
|
|
||||||
if (is_filaments)
|
|
||||||
coll.set_printer_hold_alias(p.alias, p);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
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);
|
|
||||||
|
|
||||||
// config_maps and filament_id_maps are derived from ORCA_FILAMENT_LIBRARY.
|
|
||||||
// If that vendor is in skip_vendor_ids it will be re-loaded from JSON and will
|
|
||||||
// overwrite these; otherwise the cached values are still valid.
|
|
||||||
bundle.m_config_maps = config_maps;
|
|
||||||
bundle.m_filament_id_maps = filament_id_maps;
|
|
||||||
// Caller must invoke bundle.update_system_maps() after all loading is done.
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SystemPresetsCache::load(const std::string& path)
|
|
||||||
{
|
|
||||||
return load_blob(path, *this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SystemPresetsCache::save(const std::string& path) const
|
|
||||||
{
|
|
||||||
save_blob(path, *this);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace PresetBundleCache
|
|
||||||
} // namespace Slic3r
|
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <map>
|
|
||||||
#include <set>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <cereal/archives/binary.hpp>
|
|
||||||
#include <cereal/cereal.hpp>
|
|
||||||
#include <cereal/types/map.hpp>
|
|
||||||
#include <cereal/types/polymorphic.hpp>
|
|
||||||
#include <cereal/types/string.hpp>
|
|
||||||
#include <cereal/types/vector.hpp>
|
|
||||||
|
|
||||||
#include "PrintConfig.hpp"
|
|
||||||
|
|
||||||
namespace Slic3r {
|
|
||||||
|
|
||||||
class PresetBundle;
|
|
||||||
|
|
||||||
namespace PresetBundleCache {
|
|
||||||
|
|
||||||
// ---- Vendor profile structures ----
|
|
||||||
|
|
||||||
struct CachedPrinterVariant {
|
|
||||||
std::string name;
|
|
||||||
template<class Archive> void serialize(Archive& ar) { ar(name); }
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CachedPrinterModel {
|
|
||||||
std::string id, name, model_id, family;
|
|
||||||
int technology = 0;
|
|
||||||
std::vector<CachedPrinterVariant> variants;
|
|
||||||
std::vector<std::string> default_materials;
|
|
||||||
std::vector<std::string> not_support_bed_types;
|
|
||||||
std::string bed_model, bed_texture, image_bed_type;
|
|
||||||
std::string bottom_texture_end_name, use_double_extruder_default_texture;
|
|
||||||
std::string bottom_texture_rect, middle_texture_rect, hotend_model;
|
|
||||||
|
|
||||||
template<class Archive>
|
|
||||||
void serialize(Archive& ar)
|
|
||||||
{
|
|
||||||
ar(id, name, model_id, family, technology, variants, default_materials,
|
|
||||||
not_support_bed_types, bed_model, bed_texture, image_bed_type,
|
|
||||||
bottom_texture_end_name, use_double_extruder_default_texture,
|
|
||||||
bottom_texture_rect, middle_texture_rect, hotend_model);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CachedVendorProfile {
|
|
||||||
std::string id, name, config_version, config_update_url, changelog_url;
|
|
||||||
std::vector<CachedPrinterModel> models;
|
|
||||||
std::vector<std::string> default_filaments;
|
|
||||||
std::vector<std::string> default_sla_materials;
|
|
||||||
|
|
||||||
template<class Archive>
|
|
||||||
void serialize(Archive& ar)
|
|
||||||
{
|
|
||||||
ar(id, name, config_version, config_update_url, changelog_url,
|
|
||||||
models, default_filaments, default_sla_materials);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// ---- Per-preset cache entry ----
|
|
||||||
|
|
||||||
struct CachedPreset {
|
|
||||||
int type = 0;
|
|
||||||
std::string name, alias, file, version;
|
|
||||||
std::string vendor_id;
|
|
||||||
std::string filament_id, setting_id, description;
|
|
||||||
std::string base_id, user_id, sync_info;
|
|
||||||
long long updated_time = 0;
|
|
||||||
std::vector<std::string> renamed_from;
|
|
||||||
bool is_system = true;
|
|
||||||
bool is_visible = true;
|
|
||||||
bool m_from_orca_filament_lib = false;
|
|
||||||
DynamicPrintConfig config;
|
|
||||||
|
|
||||||
template<class Archive>
|
|
||||||
void serialize(Archive& ar)
|
|
||||||
{
|
|
||||||
ar(type, name, alias, file, version, vendor_id, filament_id, setting_id,
|
|
||||||
description, base_id, user_id, sync_info, updated_time, renamed_from,
|
|
||||||
is_system, is_visible, m_from_orca_filament_lib, config);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// ---- System preset cache ----
|
|
||||||
// Single blob at <data_dir>/system/system_presets_cache.cache
|
|
||||||
// Covers all vendor profiles and system presets.
|
|
||||||
// Invalidated when any vendor version string changes or config option count changes.
|
|
||||||
|
|
||||||
struct SystemPresetsCache {
|
|
||||||
static constexpr uint32_t FORMAT_VERSION = 2;
|
|
||||||
|
|
||||||
uint32_t format_version = FORMAT_VERSION;
|
|
||||||
size_t config_options_count = 0;
|
|
||||||
|
|
||||||
std::map<std::string, std::string> vendor_versions;
|
|
||||||
std::vector<CachedVendorProfile> vendor_profiles;
|
|
||||||
|
|
||||||
std::vector<CachedPreset> print_presets;
|
|
||||||
std::vector<CachedPreset> filament_presets;
|
|
||||||
std::vector<CachedPreset> printer_presets;
|
|
||||||
std::vector<CachedPreset> sla_print_presets;
|
|
||||||
std::vector<CachedPreset> sla_material_presets;
|
|
||||||
|
|
||||||
std::map<std::string, DynamicPrintConfig> config_maps;
|
|
||||||
std::map<std::string, std::string> filament_id_maps;
|
|
||||||
|
|
||||||
template<class Archive>
|
|
||||||
void serialize(Archive& ar)
|
|
||||||
{
|
|
||||||
ar(format_version, config_options_count, vendor_versions,
|
|
||||||
vendor_profiles,
|
|
||||||
print_presets, filament_presets, printer_presets,
|
|
||||||
sla_print_presets, sla_material_presets,
|
|
||||||
config_maps, filament_id_maps);
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::string cache_path();
|
|
||||||
static std::string bundled_cache_path();
|
|
||||||
bool is_valid(const std::string& system_dir) const;
|
|
||||||
// Rejects caches that loaded structurally but contain no data or are internally inconsistent
|
|
||||||
// (e.g. vendor_versions lists all vendors but vendor_profiles only captured some of them).
|
|
||||||
bool is_plausible() const {
|
|
||||||
return !vendor_profiles.empty() &&
|
|
||||||
!printer_presets.empty() &&
|
|
||||||
vendor_versions.size() == vendor_profiles.size();
|
|
||||||
}
|
|
||||||
// Returns false on structural mismatch (full re-parse required).
|
|
||||||
// On true, populates out_dirty with vendor IDs whose version strings changed.
|
|
||||||
// Empty out_dirty means fully valid (cache hit, no re-parse needed).
|
|
||||||
bool get_dirty_vendors(const std::string& system_dir, std::set<std::string>& out_dirty) const;
|
|
||||||
void capture(const PresetBundle& bundle, const std::string& system_dir);
|
|
||||||
void apply(PresetBundle& bundle) const;
|
|
||||||
// Same as apply() but skips vendors listed in skip_vendor_ids and their presets.
|
|
||||||
void apply_partial(PresetBundle& bundle, const std::set<std::string>& skip_vendor_ids) const;
|
|
||||||
bool load(const std::string& path);
|
|
||||||
void save(const std::string& path) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace PresetBundleCache
|
|
||||||
} // namespace Slic3r
|
|
||||||
@@ -10,8 +10,8 @@
|
|||||||
#include "I18N.hpp"
|
#include "I18N.hpp"
|
||||||
#include "libslic3r/AppConfig.hpp"
|
#include "libslic3r/AppConfig.hpp"
|
||||||
#include "libslic3r/Config.hpp"
|
#include "libslic3r/Config.hpp"
|
||||||
|
#include "libslic3r/Preset.hpp"
|
||||||
#include "libslic3r/PresetBundle.hpp"
|
#include "libslic3r/PresetBundle.hpp"
|
||||||
#include "libslic3r/PresetBundleCache.hpp"
|
|
||||||
#include "slic3r/GUI/wxExtensions.hpp"
|
#include "slic3r/GUI/wxExtensions.hpp"
|
||||||
#include "slic3r/GUI/GUI_App.hpp"
|
#include "slic3r/GUI/GUI_App.hpp"
|
||||||
#include "libslic3r_version.h"
|
#include "libslic3r_version.h"
|
||||||
@@ -43,8 +43,6 @@ using namespace nlohmann;
|
|||||||
|
|
||||||
namespace Slic3r { namespace GUI {
|
namespace Slic3r { namespace GUI {
|
||||||
|
|
||||||
json m_ProfileJson;
|
|
||||||
|
|
||||||
static wxString update_custom_filaments()
|
static wxString update_custom_filaments()
|
||||||
{
|
{
|
||||||
json m_Res = json::object();
|
json m_Res = json::object();
|
||||||
@@ -303,15 +301,73 @@ void GuideFrame::OnNavigationRequest(wxWebViewEvent &evt)
|
|||||||
/**
|
/**
|
||||||
* Callback invoked when a navigation request was accepted
|
* Callback invoked when a navigation request was accepted
|
||||||
*/
|
*/
|
||||||
|
void GuideFrame::init_guide_paths()
|
||||||
|
{
|
||||||
|
m_ProfileJson = json::parse("{}");
|
||||||
|
m_ProfileJson["model"] = json::array();
|
||||||
|
m_ProfileJson["machine"] = json::object();
|
||||||
|
m_ProfileJson["filament"] = json::object();
|
||||||
|
m_ProfileJson["process"] = json::array();
|
||||||
|
|
||||||
|
vendor_dir = (boost::filesystem::path(Slic3r::data_dir()) / PRESET_SYSTEM_DIR).make_preferred();
|
||||||
|
rsrc_vendor_dir = (boost::filesystem::path(resources_dir()) / "profiles").make_preferred();
|
||||||
|
orca_bundle_rsrc = true;
|
||||||
|
|
||||||
|
if (boost::filesystem::exists(vendor_dir)) {
|
||||||
|
for (const auto& entry : boost::filesystem::directory_iterator(vendor_dir)) {
|
||||||
|
if (!boost::filesystem::is_directory(entry) &&
|
||||||
|
boost::iequals(entry.path().extension().string(), ".json") &&
|
||||||
|
!boost::iequals(entry.path().stem().string(), PresetBundle::ORCA_FILAMENT_LIBRARY)) {
|
||||||
|
orca_bundle_rsrc = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto lib_json = boost::filesystem::path(PresetBundle::ORCA_FILAMENT_LIBRARY).replace_extension(".json");
|
||||||
|
m_OrcaFilaLibPath = boost::filesystem::exists(vendor_dir / lib_json)
|
||||||
|
? (vendor_dir / PresetBundle::ORCA_FILAMENT_LIBRARY).string()
|
||||||
|
: (rsrc_vendor_dir / PresetBundle::ORCA_FILAMENT_LIBRARY).string();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GuideFrame::on_profile_loaded()
|
||||||
|
{
|
||||||
|
// Must be called on the main thread.
|
||||||
|
SaveProfileData();
|
||||||
|
const std::string strAll = m_ProfileJson.dump(-1, ' ', false, json::error_handler_t::ignore);
|
||||||
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished, json contents:\n" << strAll;
|
||||||
|
json res;
|
||||||
|
res["command"] = "userguide_profile_load_finish";
|
||||||
|
res["sequence_id"] = "10001";
|
||||||
|
RunScript(wxString::Format("HandleStudio(%s)", res.dump(-1, ' ', true)));
|
||||||
|
}
|
||||||
|
|
||||||
void GuideFrame::OnNavigationComplete(wxWebViewEvent &evt)
|
void GuideFrame::OnNavigationComplete(wxWebViewEvent &evt)
|
||||||
{
|
{
|
||||||
//wxLogMessage("%s", "Navigation complete; url='" + evt.GetURL() + "'");
|
//wxLogMessage("%s", "Navigation complete; url='" + evt.GetURL() + "'");
|
||||||
if (!bFirstComplete) {
|
if (!bFirstComplete) {
|
||||||
m_load_task = new boost::thread(boost::bind(&GuideFrame::LoadProfileData, this));
|
|
||||||
// boost::thread LoadProfileThread(boost::bind(&GuideFrame::LoadProfileData, this));
|
|
||||||
//LoadProfileThread.detach();
|
|
||||||
|
|
||||||
bFirstComplete = true;
|
bFirstComplete = true;
|
||||||
|
try {
|
||||||
|
// Steps 1 and 2 run on the main thread: safe to access preset_bundle and
|
||||||
|
// filesystem. Only fall through to the background thread for slow paths.
|
||||||
|
init_guide_paths();
|
||||||
|
const bool cache_hit = try_load_guide_cache();
|
||||||
|
if (cache_hit) {
|
||||||
|
if (!m_destroy)
|
||||||
|
on_profile_loaded();
|
||||||
|
} else if (BuildProfileDataFromPresetBundle()) {
|
||||||
|
if (!m_destroy) {
|
||||||
|
save_guide_cache();
|
||||||
|
on_profile_loaded();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Steps 3+4 are slow — delegate to background thread.
|
||||||
|
m_load_task = new boost::thread(boost::bind(&GuideFrame::LoadProfileData, this));
|
||||||
|
}
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ", init error: " << e.what();
|
||||||
|
m_load_task = new boost::thread(boost::bind(&GuideFrame::LoadProfileData, this));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_browser->Show();
|
m_browser->Show();
|
||||||
@@ -1266,30 +1322,6 @@ static std::string guide_cache_path()
|
|||||||
.make_preferred().string();
|
.make_preferred().string();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reads the "version" field from a vendor root JSON without parsing the full file.
|
|
||||||
// The version field is always within the first few hundred bytes, so we read
|
|
||||||
// only a small prefix instead of loading (potentially) megabyte-sized vendor files.
|
|
||||||
static std::string read_vendor_json_version(const boost::filesystem::path& path)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
boost::nowide::ifstream f(path.string());
|
|
||||||
if (!f.is_open()) return {};
|
|
||||||
char buf[512];
|
|
||||||
f.read(buf, sizeof(buf));
|
|
||||||
const std::string head(buf, static_cast<size_t>(f.gcount()));
|
|
||||||
const size_t kpos = head.find("\"version\"");
|
|
||||||
if (kpos == std::string::npos) return {};
|
|
||||||
const size_t colon = head.find(':', kpos);
|
|
||||||
if (colon == std::string::npos) return {};
|
|
||||||
const size_t q1 = head.find('"', colon + 1);
|
|
||||||
if (q1 == std::string::npos) return {};
|
|
||||||
const size_t q2 = head.find('"', q1 + 1);
|
|
||||||
if (q2 == std::string::npos) return {};
|
|
||||||
return head.substr(q1 + 1, q2 - q1 - 1);
|
|
||||||
} catch (...) {}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tries to load the user-side guide profile JSON cache.
|
// Tries to load the user-side guide profile JSON cache.
|
||||||
// Returns true and populates m_ProfileJson on cache hit.
|
// Returns true and populates m_ProfileJson on cache hit.
|
||||||
bool GuideFrame::try_load_guide_cache()
|
bool GuideFrame::try_load_guide_cache()
|
||||||
@@ -1314,7 +1346,9 @@ bool GuideFrame::try_load_guide_cache()
|
|||||||
fp = vendor_dir / filename;
|
fp = vendor_dir / filename;
|
||||||
if (!boost::filesystem::exists(fp))
|
if (!boost::filesystem::exists(fp))
|
||||||
return false;
|
return false;
|
||||||
if (read_vendor_json_version(fp) != cached_ver.get<std::string>())
|
const Semver disk_ver = get_version_from_json(fp.string());
|
||||||
|
const std::string disk_ver_str = disk_ver.valid() ? disk_ver.to_string() : "";
|
||||||
|
if (disk_ver_str != cached_ver.get<std::string>())
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1336,24 +1370,39 @@ bool GuideFrame::try_load_guide_cache()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Builds guide profile JSON from the bundled system preset cache
|
// Builds guide profile JSON from the per-vendor bundled caches
|
||||||
// (resources/profiles/system_presets_cache.cache, generated by CI from all vendor JSONs).
|
// (resources/profiles/<vendor>.cache, generated by CI).
|
||||||
// This avoids the 90-second LoadProfileFamily fallback on first launch.
|
// This avoids the 90-second LoadProfileFamily fallback on first launch.
|
||||||
bool GuideFrame::BuildProfileDataFromBundledCache()
|
bool GuideFrame::BuildProfileDataFromBundledCache()
|
||||||
{
|
{
|
||||||
PresetBundleCache::SystemPresetsCache cache;
|
// Enumerate per-vendor .cache files in resources/profiles/.
|
||||||
if (!cache.load(PresetBundleCache::SystemPresetsCache::bundled_cache_path()))
|
std::vector<boost::filesystem::path> cache_files;
|
||||||
|
try {
|
||||||
|
for (const auto& e : boost::filesystem::directory_iterator(rsrc_vendor_dir)) {
|
||||||
|
if (e.path().extension() == ".cache")
|
||||||
|
cache_files.push_back(e.path());
|
||||||
|
}
|
||||||
|
} catch (const std::exception& ex) {
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "GuideFrame::BuildProfileDataFromBundledCache: cannot scan " << rsrc_vendor_dir << ": " << ex.what();
|
||||||
return false;
|
return false;
|
||||||
if (!cache.is_plausible())
|
}
|
||||||
return false;
|
if (cache_files.empty())
|
||||||
// Validate version strings against the current resources/profiles/ directory.
|
|
||||||
if (!cache.is_valid(rsrc_vendor_dir.string()))
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Models from cached vendor profiles
|
for (const auto& cache_path : cache_files) {
|
||||||
for (const auto& cvp : cache.vendor_profiles) {
|
const std::string vendor_id = cache_path.stem().string();
|
||||||
for (const auto& cm : cvp.models) {
|
// Validate against the version in the corresponding vendor JSON.
|
||||||
|
const boost::filesystem::path json_path = rsrc_vendor_dir / (vendor_id + ".json");
|
||||||
|
const Semver ver = get_version_from_json(json_path.string());
|
||||||
|
const std::string ver_str = ver.valid() ? ver.to_string() : "";
|
||||||
|
|
||||||
|
VendorCache vc;
|
||||||
|
if (!vc.load(cache_path.string()) || !vc.is_valid(ver_str))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Models from this vendor's cached profile
|
||||||
|
for (const auto& cm : vc.profile.models) {
|
||||||
std::string nozzle_str;
|
std::string nozzle_str;
|
||||||
for (const auto& v : cm.variants) {
|
for (const auto& v : cm.variants) {
|
||||||
if (!nozzle_str.empty()) nozzle_str += ";";
|
if (!nozzle_str.empty()) nozzle_str += ";";
|
||||||
@@ -1365,7 +1414,7 @@ bool GuideFrame::BuildProfileDataFromBundledCache()
|
|||||||
materials_str += m;
|
materials_str += m;
|
||||||
}
|
}
|
||||||
boost::filesystem::path cover_path =
|
boost::filesystem::path cover_path =
|
||||||
(boost::filesystem::path(resources_dir()) / "profiles" / cvp.id / (cm.id + "_cover.png"))
|
(boost::filesystem::path(resources_dir()) / "profiles" / vc.profile.id / (cm.id + "_cover.png"))
|
||||||
.make_preferred();
|
.make_preferred();
|
||||||
if (!boost::filesystem::exists(cover_path))
|
if (!boost::filesystem::exists(cover_path))
|
||||||
cover_path =
|
cover_path =
|
||||||
@@ -1375,7 +1424,7 @@ bool GuideFrame::BuildProfileDataFromBundledCache()
|
|||||||
json entry;
|
json entry;
|
||||||
entry["model"] = cm.id;
|
entry["model"] = cm.id;
|
||||||
entry["name"] = cm.name;
|
entry["name"] = cm.name;
|
||||||
entry["vendor"] = cvp.id;
|
entry["vendor"] = vc.profile.id;
|
||||||
entry["nozzle_diameter"] = nozzle_str;
|
entry["nozzle_diameter"] = nozzle_str;
|
||||||
entry["materials"] = materials_str;
|
entry["materials"] = materials_str;
|
||||||
entry["cover"] = cover_path.string();
|
entry["cover"] = cover_path.string();
|
||||||
@@ -1383,60 +1432,60 @@ bool GuideFrame::BuildProfileDataFromBundledCache()
|
|||||||
entry["sub_path"] = "";
|
entry["sub_path"] = "";
|
||||||
m_ProfileJson["model"].push_back(entry);
|
m_ProfileJson["model"].push_back(entry);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Machines from cached printer presets
|
// Machines from cached printer presets
|
||||||
for (const auto& cp : cache.printer_presets) {
|
for (const auto& cp : vc.printer_presets) {
|
||||||
const auto* pm = cp.config.option<ConfigOptionString>("printer_model");
|
const auto* pm = cp.config.option<ConfigOptionString>("printer_model");
|
||||||
const auto* pv = cp.config.option<ConfigOptionString>("printer_variant");
|
const auto* pv = cp.config.option<ConfigOptionString>("printer_variant");
|
||||||
if (!pm || pm->value.empty() || !pv) continue;
|
if (!pm || pm->value.empty() || !pv) continue;
|
||||||
|
|
||||||
json mach;
|
json mach;
|
||||||
mach["model"] = pm->value;
|
mach["model"] = pm->value;
|
||||||
mach["nozzle"] = pv->value;
|
mach["nozzle"] = pv->value;
|
||||||
m_ProfileJson["machine"][cp.name] = mach;
|
m_ProfileJson["machine"][cp.name] = mach;
|
||||||
}
|
|
||||||
|
|
||||||
// Filaments from cached filament presets
|
|
||||||
for (const auto& cp : cache.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");
|
|
||||||
|
|
||||||
std::string vendor = (fv && !fv->values.empty()) ? fv->values[0] : "";
|
|
||||||
std::string type = (ft && !ft->values.empty()) ? ft->values[0] : "";
|
|
||||||
|
|
||||||
std::string model_list;
|
|
||||||
if (compat) {
|
|
||||||
for (const std::string& pname : compat->values) {
|
|
||||||
if (m_ProfileJson["machine"].contains(pname)) {
|
|
||||||
std::string m = m_ProfileJson["machine"][pname]["model"];
|
|
||||||
std::string n = m_ProfileJson["machine"][pname]["nozzle"];
|
|
||||||
model_list += "[" + m + "++" + n + "]";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
json ff;
|
// Filaments from cached filament presets
|
||||||
ff["name"] = cp.name;
|
for (const auto& cp : vc.filament_presets) {
|
||||||
ff["sub_path"] = cp.file;
|
const auto* fv = cp.config.option<ConfigOptionStrings>("filament_vendor");
|
||||||
ff["vendor"] = vendor;
|
const auto* ft = cp.config.option<ConfigOptionStrings>("filament_type");
|
||||||
ff["type"] = type;
|
const auto* compat = cp.config.option<ConfigOptionStrings>("compatible_printers");
|
||||||
ff["models"] = model_list;
|
|
||||||
ff["selected"] = 0;
|
std::string vendor = (fv && !fv->values.empty()) ? fv->values[0] : "";
|
||||||
m_ProfileJson["filament"][cp.name] = ff;
|
std::string type = (ft && !ft->values.empty()) ? ft->values[0] : "";
|
||||||
|
|
||||||
|
std::string model_list;
|
||||||
|
if (compat) {
|
||||||
|
for (const std::string& pname : compat->values) {
|
||||||
|
if (m_ProfileJson["machine"].contains(pname)) {
|
||||||
|
std::string m = m_ProfileJson["machine"][pname]["model"];
|
||||||
|
std::string n = m_ProfileJson["machine"][pname]["nozzle"];
|
||||||
|
model_list += "[" + m + "++" + n + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
json ff;
|
||||||
|
ff["name"] = cp.name;
|
||||||
|
ff["sub_path"] = cp.file;
|
||||||
|
ff["vendor"] = vendor;
|
||||||
|
ff["type"] = type;
|
||||||
|
ff["models"] = model_list;
|
||||||
|
ff["selected"] = 0;
|
||||||
|
m_ProfileJson["filament"][cp.name] = ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process from cached print presets
|
||||||
|
for (const auto& cp : vc.print_presets) {
|
||||||
|
if (!cp.is_visible) continue;
|
||||||
|
json entry;
|
||||||
|
entry["name"] = cp.name;
|
||||||
|
entry["sub_path"] = cp.file;
|
||||||
|
m_ProfileJson["process"].push_back(entry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process from cached print presets
|
BOOST_LOG_TRIVIAL(info) << "GuideFrame: built profile data from bundled per-vendor caches ("
|
||||||
for (const auto& cp : cache.print_presets) {
|
|
||||||
if (!cp.is_visible) continue;
|
|
||||||
json entry;
|
|
||||||
entry["name"] = cp.name;
|
|
||||||
entry["sub_path"] = cp.file;
|
|
||||||
m_ProfileJson["process"].push_back(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
BOOST_LOG_TRIVIAL(info) << "GuideFrame: built profile data from bundled system cache ("
|
|
||||||
<< m_ProfileJson["model"].size() << " models, "
|
<< m_ProfileJson["model"].size() << " models, "
|
||||||
<< m_ProfileJson["machine"].size() << " machines, "
|
<< m_ProfileJson["machine"].size() << " machines, "
|
||||||
<< m_ProfileJson["filament"].size() << " filaments)";
|
<< m_ProfileJson["filament"].size() << " filaments)";
|
||||||
@@ -1462,8 +1511,9 @@ void GuideFrame::save_guide_cache() const
|
|||||||
if (e.path().extension().string() != ".json") continue;
|
if (e.path().extension().string() != ".json") continue;
|
||||||
// Store version for all files, including version-less ones (empty string).
|
// Store version for all files, including version-less ones (empty string).
|
||||||
// try_load_guide_cache checks ALL json files, so blacklist.json etc. must be present.
|
// try_load_guide_cache checks ALL json files, so blacklist.json etc. must be present.
|
||||||
|
const Semver ver = get_version_from_json(e.path().string());
|
||||||
cache["vendor_versions"][e.path().filename().string()] =
|
cache["vendor_versions"][e.path().filename().string()] =
|
||||||
read_vendor_json_version(e.path());
|
ver.valid() ? ver.to_string() : "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cache["profile_data"] = m_ProfileJson;
|
cache["profile_data"] = m_ProfileJson;
|
||||||
@@ -1480,113 +1530,62 @@ void GuideFrame::save_guide_cache() const
|
|||||||
|
|
||||||
int GuideFrame::LoadProfileData()
|
int GuideFrame::LoadProfileData()
|
||||||
{
|
{
|
||||||
|
// Background thread: Steps 1+2 already ran on the main thread in OnNavigationComplete
|
||||||
|
// and both failed. This function handles only Steps 3 (bundled cache) and 4 (slow JSON walk).
|
||||||
try {
|
try {
|
||||||
m_ProfileJson = json::parse("{}");
|
// Step 3: bundled system preset cache (CI-generated, ~1-2s)
|
||||||
m_ProfileJson["model"] = json::array();
|
bool slow_path = !BuildProfileDataFromBundledCache();
|
||||||
m_ProfileJson["machine"] = json::object();
|
if (slow_path) {
|
||||||
m_ProfileJson["filament"] = json::object();
|
// Step 4: last resort — read all vendor JSONs (~90s)
|
||||||
m_ProfileJson["process"] = json::array();
|
std::set<std::string> loaded_vendors;
|
||||||
|
auto filament_library_name = boost::filesystem::path(PresetBundle::ORCA_FILAMENT_LIBRARY).replace_extension(".json");
|
||||||
|
if (boost::filesystem::exists(vendor_dir / filament_library_name))
|
||||||
|
LoadProfileFamily(PresetBundle::ORCA_FILAMENT_LIBRARY, (vendor_dir / filament_library_name).string());
|
||||||
|
else
|
||||||
|
LoadProfileFamily(PresetBundle::ORCA_FILAMENT_LIBRARY, (rsrc_vendor_dir / filament_library_name).string());
|
||||||
|
loaded_vendors.insert(PresetBundle::ORCA_FILAMENT_LIBRARY);
|
||||||
|
|
||||||
vendor_dir = (boost::filesystem::path(Slic3r::data_dir()) / PRESET_SYSTEM_DIR).make_preferred();
|
boost::filesystem::directory_iterator endIter;
|
||||||
rsrc_vendor_dir = (boost::filesystem::path(resources_dir()) / "profiles").make_preferred();
|
for (boost::filesystem::directory_iterator iter(vendor_dir); iter != endIter; iter++) {
|
||||||
|
if (!boost::filesystem::is_directory(*iter)) {
|
||||||
// Orca: add custom as default
|
wxString strVendor = from_u8(iter->path().string()).BeforeLast('.');
|
||||||
// Orca: add json logic for vendor bundle
|
strVendor = strVendor.AfterLast('\\');
|
||||||
orca_bundle_rsrc = true;
|
strVendor = strVendor.AfterLast('/');
|
||||||
|
wxString strExtension = from_u8(iter->path().string()).AfterLast('.').Lower();
|
||||||
// search if there exists a .json file in vendor_dir folder, if exists, set orca_bundle_rsrc to false
|
if (strExtension.CmpNoCase("json") != 0 || loaded_vendors.find(w2s(strVendor)) != loaded_vendors.end())
|
||||||
for (const auto& entry : boost::filesystem::directory_iterator(vendor_dir)) {
|
continue;
|
||||||
if (!boost::filesystem::is_directory(entry) && boost::iequals(entry.path().extension().string(), ".json") && !boost::iequals(entry.path().stem().string(), PresetBundle::ORCA_FILAMENT_LIBRARY)) {
|
LoadProfileFamily(w2s(strVendor), iter->path().string());
|
||||||
orca_bundle_rsrc = false;
|
loaded_vendors.insert(w2s(strVendor));
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve OrcaFilamentLibrary path regardless of whether we hit the cache
|
|
||||||
// (used later by GetFilamentInfo for custom filaments).
|
|
||||||
{
|
|
||||||
auto lib_json = boost::filesystem::path(PresetBundle::ORCA_FILAMENT_LIBRARY).replace_extension(".json");
|
|
||||||
m_OrcaFilaLibPath = boost::filesystem::exists(vendor_dir / lib_json)
|
|
||||||
? (vendor_dir / PresetBundle::ORCA_FILAMENT_LIBRARY).string()
|
|
||||||
: (rsrc_vendor_dir / PresetBundle::ORCA_FILAMENT_LIBRARY).string();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 1: user guide JSON cache (~50ms, version-string validated)
|
|
||||||
bool cache_hit = try_load_guide_cache();
|
|
||||||
if (!cache_hit) {
|
|
||||||
if (!BuildProfileDataFromPresetBundle()) {
|
|
||||||
// Step 2: live preset bundle covers all rsrc vendors — done.
|
|
||||||
// Step 3: bundled system preset cache (CI-generated, ~1-2s)
|
|
||||||
bool slow_path = !BuildProfileDataFromBundledCache();
|
|
||||||
if (slow_path) {
|
|
||||||
// Step 4: last resort — read all vendor JSONs (~90s)
|
|
||||||
std::set<std::string> loaded_vendors;
|
|
||||||
auto filament_library_name = boost::filesystem::path(PresetBundle::ORCA_FILAMENT_LIBRARY).replace_extension(".json");
|
|
||||||
if (boost::filesystem::exists(vendor_dir / filament_library_name))
|
|
||||||
LoadProfileFamily(PresetBundle::ORCA_FILAMENT_LIBRARY, (vendor_dir / filament_library_name).string());
|
|
||||||
else
|
|
||||||
LoadProfileFamily(PresetBundle::ORCA_FILAMENT_LIBRARY, (rsrc_vendor_dir / filament_library_name).string());
|
|
||||||
loaded_vendors.insert(PresetBundle::ORCA_FILAMENT_LIBRARY);
|
|
||||||
|
|
||||||
boost::filesystem::directory_iterator endIter;
|
|
||||||
for (boost::filesystem::directory_iterator iter(vendor_dir); iter != endIter; iter++) {
|
|
||||||
if (!boost::filesystem::is_directory(*iter)) {
|
|
||||||
wxString strVendor = from_u8(iter->path().string()).BeforeLast('.');
|
|
||||||
strVendor = strVendor.AfterLast('\\');
|
|
||||||
strVendor = strVendor.AfterLast('/');
|
|
||||||
wxString strExtension = from_u8(iter->path().string()).AfterLast('.').Lower();
|
|
||||||
if (strExtension.CmpNoCase("json") != 0 || loaded_vendors.find(w2s(strVendor)) != loaded_vendors.end())
|
|
||||||
continue;
|
|
||||||
LoadProfileFamily(w2s(strVendor), iter->path().string());
|
|
||||||
loaded_vendors.insert(w2s(strVendor));
|
|
||||||
}
|
|
||||||
if (m_destroy) return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
boost::filesystem::directory_iterator others_endIter;
|
|
||||||
for (boost::filesystem::directory_iterator iter(rsrc_vendor_dir); iter != others_endIter; iter++) {
|
|
||||||
if (!boost::filesystem::is_directory(*iter)) {
|
|
||||||
wxString strVendor = from_u8(iter->path().string()).BeforeLast('.');
|
|
||||||
strVendor = strVendor.AfterLast('\\');
|
|
||||||
strVendor = strVendor.AfterLast('/');
|
|
||||||
wxString strExtension = from_u8(iter->path().string()).AfterLast('.').Lower();
|
|
||||||
if (strExtension.CmpNoCase("json") != 0 || loaded_vendors.find(w2s(strVendor)) != loaded_vendors.end())
|
|
||||||
continue;
|
|
||||||
LoadProfileFamily(w2s(strVendor), iter->path().string());
|
|
||||||
loaded_vendors.insert(w2s(strVendor));
|
|
||||||
}
|
|
||||||
if (m_destroy) return 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (m_destroy) return 0;
|
||||||
|
}
|
||||||
|
|
||||||
// After either bundled cache (~1-2s) or slow path (~90s), promote to
|
boost::filesystem::directory_iterator others_endIter;
|
||||||
// user guide cache so every subsequent open takes ~50ms instead.
|
for (boost::filesystem::directory_iterator iter(rsrc_vendor_dir); iter != others_endIter; iter++) {
|
||||||
if (!m_destroy)
|
if (!boost::filesystem::is_directory(*iter)) {
|
||||||
save_guide_cache();
|
wxString strVendor = from_u8(iter->path().string()).BeforeLast('.');
|
||||||
|
strVendor = strVendor.AfterLast('\\');
|
||||||
|
strVendor = strVendor.AfterLast('/');
|
||||||
|
wxString strExtension = from_u8(iter->path().string()).AfterLast('.').Lower();
|
||||||
|
if (strExtension.CmpNoCase("json") != 0 || loaded_vendors.find(w2s(strVendor)) != loaded_vendors.end())
|
||||||
|
continue;
|
||||||
|
LoadProfileFamily(w2s(strVendor), iter->path().string());
|
||||||
|
loaded_vendors.insert(w2s(strVendor));
|
||||||
|
}
|
||||||
|
if (m_destroy) return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Promote to user guide cache so every subsequent open takes ~50ms.
|
||||||
|
if (!m_destroy)
|
||||||
|
save_guide_cache();
|
||||||
|
|
||||||
wxGetApp().CallAfter([this] {
|
wxGetApp().CallAfter([this] {
|
||||||
if (!m_destroy) {
|
if (!m_destroy)
|
||||||
//sync to appconfig first to populate current selections
|
on_profile_loaded();
|
||||||
SaveProfileData();
|
|
||||||
|
|
||||||
//sync to web after selections are populated
|
|
||||||
std::string strAll = m_ProfileJson.dump(-1, ' ', false, json::error_handler_t::ignore);
|
|
||||||
|
|
||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ", finished, json contents: " << std::endl << strAll;
|
|
||||||
json m_Res = json::object();
|
|
||||||
m_Res["command"] = "userguide_profile_load_finish";
|
|
||||||
m_Res["sequence_id"] = "10001";
|
|
||||||
wxString strJS = wxString::Format("HandleStudio(%s)", m_Res.dump(-1, ' ', true));
|
|
||||||
|
|
||||||
RunScript(strJS);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} catch (std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
// wxLogMessage("GUIDE: load_profile_error %s ", e.what());
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ", error: " << e.what();
|
||||||
// wxMessageBox(e.what(), "", MB_OK);
|
|
||||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ", error: " << e.what() << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
filament_info_cache.clear();
|
filament_info_cache.clear();
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
#include "libslic3r/PresetBundle.hpp"
|
#include "libslic3r/PresetBundle.hpp"
|
||||||
#include "slic3r/Utils/PresetUpdater.hpp"
|
#include "slic3r/Utils/PresetUpdater.hpp"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
@@ -78,6 +79,8 @@ public:
|
|||||||
int LoadProfileData();
|
int LoadProfileData();
|
||||||
int SaveProfileData();
|
int SaveProfileData();
|
||||||
int LoadProfileFamily(std::string strVendor, std::string strFilePath);
|
int LoadProfileFamily(std::string strVendor, std::string strFilePath);
|
||||||
|
void init_guide_paths();
|
||||||
|
void on_profile_loaded();
|
||||||
bool BuildProfileDataFromPresetBundle();
|
bool BuildProfileDataFromPresetBundle();
|
||||||
bool BuildProfileDataFromBundledCache();
|
bool BuildProfileDataFromBundledCache();
|
||||||
bool try_load_guide_cache();
|
bool try_load_guide_cache();
|
||||||
@@ -116,7 +119,7 @@ private:
|
|||||||
|
|
||||||
//First Load
|
//First Load
|
||||||
bool bFirstComplete{false};
|
bool bFirstComplete{false};
|
||||||
bool m_destroy{false};
|
std::atomic<bool> m_destroy{false};
|
||||||
boost::thread* m_load_task{ nullptr };
|
boost::thread* m_load_task{ nullptr };
|
||||||
|
|
||||||
// User Config
|
// User Config
|
||||||
@@ -127,6 +130,7 @@ private:
|
|||||||
bool InstallNetplugin;
|
bool InstallNetplugin;
|
||||||
bool network_plugin_ready {false};
|
bool network_plugin_ready {false};
|
||||||
|
|
||||||
|
json m_ProfileJson;
|
||||||
json m_OrcaFilaList;
|
json m_OrcaFilaList;
|
||||||
std::string m_OrcaFilaLibPath;
|
std::string m_OrcaFilaLibPath;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user