Merge branch 'main' into zaa

This commit is contained in:
SoftFever
2026-05-01 18:04:05 +08:00
committed by GitHub
116 changed files with 9029 additions and 3590 deletions

View File

@@ -10,6 +10,7 @@
#include "format.hpp"
#include "nlohmann/json.hpp"
#include <algorithm>
#include <utility>
#include <vector>
#include <stdexcept>
@@ -40,7 +41,7 @@ using namespace nlohmann;
namespace Slic3r {
static const std::string VERSION_CHECK_URL = "https://check-version.orcaslicer.com/latest";
static const std::string PROFILE_UPDATE_URL = "https://api.github.com/repos/OrcaSlicer/orcaslicer-profiles/releases/tags";
static const std::string PROFILE_UPDATE_URL = "https://check-version.orcaslicer.com/profile";
static const std::string MODELS_STR = "models";
const std::string AppConfig::SECTION_FILAMENTS = "filaments";
@@ -320,6 +321,11 @@ void AppConfig::set_defaults()
if (get("allow_abnormal_storage").empty()) {
set_bool("allow_abnormal_storage", false);
}
if (get("preset_bundle_auto_update").empty()) {
set_bool("preset_bundle_auto_update", false);
}
#ifdef __linux__
if (get(SETTING_USE_ENCRYPTED_TOKEN_FILE).empty())
set_bool(SETTING_USE_ENCRYPTED_TOKEN_FILE, true);
@@ -357,6 +363,10 @@ void AppConfig::set_defaults()
set_bool("show_daily_tips", true);
}
if (get("show_shared_profiles_notification").empty()) {
set_bool("show_shared_profiles_notification", true);
}
if (get("auto_calculate_flush").empty()){
set("auto_calculate_flush","all");
}
@@ -802,6 +812,18 @@ std::string AppConfig::load()
if (it_section->second.empty())
m_storage.erase(it_section);
}
// Default for new installs
if (get(SETTING_CLOUD_PROVIDERS).empty()) {
// Migrate add bbl cloud if installed_networking is true
bool enable_bbl_cloud = get_bool("installed_networking");
if (enable_bbl_cloud) {
// Legacy Bambu-only user: give them both providers
set(SETTING_CLOUD_PROVIDERS, "orca;bbl");
} else {
set(SETTING_CLOUD_PROVIDERS, "orca");
}
}
}
// Override missing or keys with their defaults.
@@ -1572,6 +1594,62 @@ void AppConfig::clear_remind_network_update_later()
set_bool(SETTING_NETWORK_PLUGIN_REMIND_LATER, false);
}
std::vector<std::string> AppConfig::get_cloud_providers() const
{
std::vector<std::string> result;
std::string providers = get(SETTING_CLOUD_PROVIDERS);
if (providers.empty()) {
result.push_back("orca");
return result;
}
std::stringstream ss(providers);
std::string provider;
while (std::getline(ss, provider, ';')) {
if (!provider.empty())
result.push_back(provider);
}
// Ensure "orca" is always present
if (std::find(result.begin(), result.end(), "orca") == result.end()) {
result.insert(result.begin(), "orca");
}
return result;
}
void AppConfig::set_cloud_providers(const std::vector<std::string>& providers)
{
std::string joined;
for (size_t i = 0; i < providers.size(); ++i) {
if (i > 0) joined += ";";
joined += providers[i];
}
set(SETTING_CLOUD_PROVIDERS, joined);
}
bool AppConfig::has_cloud_provider(const std::string& provider) const
{
auto providers = get_cloud_providers();
return std::find(providers.begin(), providers.end(), provider) != providers.end();
}
void AppConfig::add_cloud_provider(const std::string& provider)
{
auto providers = get_cloud_providers();
if (std::find(providers.begin(), providers.end(), provider) == providers.end()) {
providers.push_back(provider);
set_cloud_providers(providers);
}
}
void AppConfig::remove_cloud_provider(const std::string& provider)
{
if (provider == "orca")
return; // Cannot remove orca
auto providers = get_cloud_providers();
providers.erase(std::remove(providers.begin(), providers.end(), provider), providers.end());
set_cloud_providers(providers);
}
void AppConfig::reset_selections()
{
auto it = m_storage.find("presets");

View File

@@ -29,6 +29,7 @@ using namespace nlohmann;
#define SETTING_NETWORK_PLUGIN_UPDATE_DISABLED "network_plugin_update_prompts_disabled"
#define SETTING_NETWORK_PLUGIN_REMIND_LATER "network_plugin_remind_later"
#define SETTING_USE_ENCRYPTED_TOKEN_FILE "use_encrypted_token_file"
#define SETTING_CLOUD_PROVIDERS "cloud_providers"
#if defined(_WIN32) || defined(_WIN64)
#define BAMBU_NETWORK_AGENT_VERSION_LEGACY "01.10.01.09"
@@ -374,6 +375,13 @@ public:
void set_remind_network_update_later(bool remind);
void clear_remind_network_update_later();
// Cloud providers (semicolon-delimited, e.g. "orca;bambu")
std::vector<std::string> get_cloud_providers() const;
void set_cloud_providers(const std::vector<std::string>& providers);
bool has_cloud_provider(const std::string& provider) const;
void add_cloud_provider(const std::string& provider);
void remove_cloud_provider(const std::string& provider);
private:
template<typename T>
bool get_3dmouse_device_numeric_value(const std::string &device_name, const char *parameter_name, T &out) const

View File

@@ -1838,6 +1838,12 @@ void PerimeterGenerator::process_no_bridge(Surfaces& all_surfaces, coord_t perim
}
//TODO: add other polys as holes inside this one (-margin)
} else if (/*this->config->counterbore_hole_bridging.value == chbBridgesOverhangs || */this->config->counterbore_hole_bridging.value == chbBridges) {
// Partially bridged counterbore handling should not rewrite generic bridge islands
// because by doing so regular bridges will lose their overhang-wall perimeters.
if (surface->expolygon.holes.empty()) {
unsupported_filtered.clear(); // "Partially bridged" only applies to hole-bearing bridge islands.
continue;
}
//simplify to avoid most of artefacts from printing lines.
ExPolygons bridgeable_simplified;
for (ExPolygon& poly : bridgeable) {

View File

@@ -52,6 +52,78 @@ using boost::property_tree::ptree;
namespace Slic3r {
namespace {
struct ParsedName {
PresetOrigin::Kind kind { PresetOrigin::Kind::User };
std::string bundle_id;
std::string bare;
};
// Canonical names are built in-memory with '/' separators, so a straight prefix+split match is enough.
static ParsedName parse_preset_name(const std::string &raw_name)
{
ParsedName out;
auto try_prefix = [&](const char *dir, PresetOrigin::Kind kind) {
const std::string prefix = std::string(dir) + "/";
if (! boost::starts_with(raw_name, prefix))
return false;
const size_t id_start = prefix.size();
const size_t id_end = raw_name.find('/', id_start);
if (id_end == std::string::npos || id_end == id_start)
return false;
out.kind = kind;
out.bundle_id = raw_name.substr(id_start, id_end - id_start);
out.bare = raw_name.substr(id_end + 1);
return true;
};
if (! try_prefix(PRESET_LOCAL_DIR, PresetOrigin::Kind::LocalBundle) &&
! try_prefix(PRESET_SUBSCRIBED_DIR, PresetOrigin::Kind::SubscribedBundle))
out.bare = raw_name;
return out;
}
} // namespace
std::string get_preset_canonical_name(const std::string &preset_bare_name, const PresetOrigin &origin)
{
switch (origin.kind) {
case PresetOrigin::Kind::LocalBundle:
return origin.bundle_id.empty() ? preset_bare_name : std::string(PRESET_LOCAL_DIR) + "/" + origin.bundle_id + "/" + preset_bare_name;
case PresetOrigin::Kind::SubscribedBundle:
return origin.bundle_id.empty() ? preset_bare_name : std::string(PRESET_SUBSCRIBED_DIR) + "/" + origin.bundle_id + "/" + preset_bare_name;
default:
return preset_bare_name;
}
}
std::string get_preset_bare_name(const std::string &canonical_name)
{
const auto pos = canonical_name.find_last_of('/');
return pos == std::string::npos ? canonical_name : canonical_name.substr(pos + 1);
}
PresetOrigin detect_origin_from_path(const boost::filesystem::path &path, const PresetOrigin &explicit_origin)
{
if (explicit_origin.kind != PresetOrigin::Kind::Auto)
return explicit_origin;
for (auto it = path.begin(); it != path.end(); ++ it) {
const auto next = std::next(it);
if (next == path.end())
break;
const std::string segment = it->string();
if (segment == PRESET_LOCAL_DIR)
return PresetOrigin(PresetOrigin::Kind::LocalBundle, next->string());
if (segment == PRESET_SUBSCRIBED_DIR)
return PresetOrigin(PresetOrigin::Kind::SubscribedBundle, next->string());
}
return PresetOrigin(PresetOrigin::Kind::User);
}
//BBS: add a function to load the version from xxx.json
Semver get_version_from_json(std::string file_path)
{
@@ -522,7 +594,7 @@ void Preset::load_info(const std::string& file)
void Preset::save_info(std::string file)
{
//BBS: add project embedded preset logic
if (this->is_project_embedded)
if (this->is_project_embedded || this->is_from_bundle())
return;
if (file.empty()) {
fs::path idx_file(this->file);
@@ -544,17 +616,26 @@ void Preset::save_info(std::string file)
c.close();
}
void Preset::remove_files()
void Preset::remove_files(bool cloud_already_deleted)
{
//BBS: add project embedded preset logic
if (this->is_project_embedded)
if (this->is_project_embedded) {
return;
}
// Erase the preset file.
boost::nowide::remove(this->file.c_str());
fs::path idx_path(this->file);
idx_path.replace_extension(".info");
if (fs::exists(idx_path))
boost::nowide::remove(idx_path.string().c_str());
if (fs::exists(idx_path)) {
if (!this->setting_id.empty() && !cloud_already_deleted) {
// Cloud-synced preset - mark for deletion and keep .info file until sync confirms
this->sync_info = "delete";
this->save_info(idx_path.string());
} else {
// Local-only preset or cloud already confirmed deletion - safe to delete .info immediately
boost::nowide::remove(idx_path.string().c_str());
}
}
}
//BBS: add logic for only difference save
@@ -570,12 +651,15 @@ void Preset::save(DynamicPrintConfig* parent_config)
from_str = std::string("User");
else if (this->is_project_embedded)
from_str = std::string("Project");
else if (this->is_from_bundle())
from_str = std::string("Bundle");
else if (this->is_system)
from_str = std::string("System");
else
from_str = std::string("Default");
boost::filesystem::create_directories(fs::path(this->file).parent_path());
const std::string bare_name = get_preset_bare_name(this->name);
//BBS: only save difference if it has parent
if (parent_config) {
@@ -615,19 +699,22 @@ void Preset::save(DynamicPrintConfig* parent_config)
opt_dst->set(opt_src);
}
}
temp_config.save_to_json(this->file, this->name, from_str, this->version.to_string());
temp_config.save_to_json(this->file, bare_name, from_str, this->version.to_string());
} else if (!filament_id.empty() && inherits().empty()) {
DynamicPrintConfig temp_config = config;
temp_config.set_key_value(BBL_JSON_KEY_FILAMENT_ID, new ConfigOptionString(filament_id));
temp_config.save_to_json(this->file, this->name, from_str, this->version.to_string());
temp_config.save_to_json(this->file, bare_name, from_str, this->version.to_string());
} else {
this->config.save_to_json(this->file, this->name, from_str, this->version.to_string());
this->config.save_to_json(this->file, bare_name, from_str, this->version.to_string());
}
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " save config for: " << this->name << " and filament_id: " << filament_id << " and base_id: " << this->base_id;
fs::path idx_file(this->file);
idx_file.replace_extension(".info");
this->save_info(idx_file.string());
// Bundle presets are synced via bundle_id and don't need individual .info files.
if (! this->is_from_bundle()) {
fs::path idx_file(this->file);
idx_file.replace_extension(".info");
this->save_info(idx_file.string());
}
}
void Preset::reload(Preset const &parent)
@@ -1429,19 +1516,34 @@ void PresetCollection::add_default_preset(const std::vector<std::string> &keys,
++ m_num_default_presets;
}
std::string PresetCollection::canonical_preset_name(const std::string &name, const PresetOrigin &load_origin) const
{
const ParsedName parsed = parse_preset_name(name);
PresetOrigin origin = load_origin;
if (origin.kind == PresetOrigin::Kind::Auto) {
origin.kind = parsed.kind;
origin.bundle_id = parsed.bundle_id;
} else if (origin.is_bundle() && origin.bundle_id.empty()) {
origin.bundle_id = parsed.bundle_id;
}
return get_preset_canonical_name(parsed.bare, origin);
}
// Load all presets found in dir_path.
// Throws an exception on error.
void PresetCollection::load_presets(
const std::string &dir_path, const std::string &subdir,
PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule substitution_rule)
PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule substitution_rule,
std::function<void(Preset&)> preset_loaded_fn, const PresetOrigin &load_origin)
{
// Don't use boost::filesystem::canonical() on Windows, it is broken in regard to reparse points,
// see https://github.com/prusa3d/PrusaSlicer/issues/732
boost::filesystem::path dir = boost::filesystem::absolute(boost::filesystem::path(dir_path) / subdir).make_preferred();
const PresetOrigin resolved_origin = detect_origin_from_path(dir, load_origin);
// Load custom roots first
if (fs::exists(dir / "base")) {
load_presets(dir.string(), "base", substitutions, substitution_rule);
load_presets(dir.string(), "base", substitutions, substitution_rule, nullptr, resolved_origin);
}
//BBS: add config related logs
@@ -1471,14 +1573,16 @@ void PresetCollection::load_presets(
if (Slic3r::is_json_file(file_name)) {
// Remove the .ini suffix.
std::string name = file_name.erase(file_name.size() - 5);
if (this->find_preset(name, false)) {
std::string canonical_name = this->canonical_preset_name(name, resolved_origin);
if (this->find_preset(canonical_name, false)) {
// This happens when there's is a preset (most likely legacy one) with the same name as a system preset
// that's already been loaded from a bundle.
BOOST_LOG_TRIVIAL(warning) << "Preset already present, not loading: " << name;
BOOST_LOG_TRIVIAL(warning) << "Preset already present, not loading: " << canonical_name;
continue;
}
try {
Preset preset(m_type, name, false);
Preset preset(m_type, canonical_name, false);
preset.bundle_id = resolved_origin.bundle_id;
preset.file = dir_entry.path().string();
// Load the preset file, apply preset values on top of defaults.
try {
@@ -1529,7 +1633,6 @@ void PresetCollection::load_presets(
std::string inherits_value = option_str->value;
// Orca: try to find if the parent preset has been renamed
inherit_preset = this->find_preset2(inherits_value);
} else {
;
}
@@ -1607,6 +1710,9 @@ void PresetCollection::load_presets(
fs::remove(file_path);
}
if (preset_loaded_fn != nullptr)
preset_loaded_fn(preset);
presets_loaded.emplace_back(preset);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << " load config successful and preset name is:" << preset.name;
} catch (const std::runtime_error &err) {
@@ -1778,7 +1884,7 @@ void PresetCollection::load_project_embedded_presets(std::vector<Preset*>& proje
inherits_value.replace(pos, 1, 1, '~');
option_str->value = inherits_value;
}*/
inherit_preset = this->find_preset(inherits_value, false, true);
inherit_preset = this->find_preset2(inherits_value, true);
}
const Preset& default_preset = this->default_preset_for(config);
if (inherit_preset) {
@@ -1879,9 +1985,10 @@ bool PresetCollection::reset_project_embedded_presets()
void PresetCollection::set_sync_info_and_save(std::string name, std::string setting_id, std::string syncinfo, long long update_time)
{
lock();
const std::string canonical_name = this->canonical_preset_name(name);
for (auto it = m_presets.begin(); it != m_presets.end(); it++) {
Preset* preset = &m_presets[it - m_presets.begin()];
if (preset->name == name) {
if (preset->name == canonical_name) {
if (syncinfo.empty())
preset->sync_info.clear();
else
@@ -1946,7 +2053,7 @@ void PresetCollection::update_user_presets_directory(const std::string& dir_path
}
//BBS: save user presets to local
void PresetCollection::save_user_presets(const std::string& dir_path, const std::string& type, std::vector<std::string>& need_to_delete_list)
void PresetCollection::save_user_presets(const std::string& dir_path, const std::string& type, std::map<std::string, std::string>& need_to_delete_list)
{
boost::filesystem::path dir = boost::filesystem::absolute(boost::filesystem::path(dir_path) / type).make_preferred();
@@ -2000,31 +2107,37 @@ void PresetCollection::save_user_presets(const std::string& dir_path, const std:
}
//BBS: load one user preset from key-values
bool PresetCollection::load_user_preset(std::string name, std::map<std::string, std::string> preset_values, PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule rule)
bool PresetCollection::load_user_preset(std::string name, std::map<std::string, std::string> preset_values, PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule rule, const PresetOrigin &load_origin)
{
std::string errors_cummulative;
// Store the loaded presets into a new vector, otherwise the binary search for already existing presets would be broken.
// (see the "Preset already present, not loading" message).
//std::deque<Preset> presets_loaded;
int count = 0;
const std::string canonical_name = this->canonical_preset_name(name, load_origin);
auto update_alias = [this](Preset &preset) {
if (! preset.alias.empty())
return;
set_custom_preset_alias(preset);
};
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" enter, name %1% , total value counts %2%")%name %preset_values.size();
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(" enter, name %1% , total value counts %2%")%canonical_name %preset_values.size();
//if the version is not matching, skip it
if (preset_values.find(BBL_JSON_KEY_VERSION) == preset_values.end()) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("can not find version, not loading for user preset %1%")%name;
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("can not find version, not loading for user preset %1%")%canonical_name;
return false;
}
std::string version_str = preset_values[BBL_JSON_KEY_VERSION];
boost::optional<Semver> cloud_version = Semver::parse(version_str);
if (!cloud_version) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("invalid version %1%, not loading for user preset %2%")%version_str %name;
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("invalid version %1%, not loading for user preset %2%")%version_str %canonical_name;
return false;
}
//setting_id
if (preset_values.find(BBL_JSON_KEY_SETTING_ID) == preset_values.end()) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("can not find setting_id, not loading for user preset %1%")%name;
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("can not find setting_id, not loading for user preset %1%")%canonical_name;
return false;
}
std::string cloud_setting_id = preset_values[BBL_JSON_KEY_SETTING_ID];
@@ -2037,17 +2150,17 @@ bool PresetCollection::load_user_preset(std::string name, std::map<std::string,
//user_id
if (preset_values.find(BBL_JSON_KEY_USER_ID) == preset_values.end()) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("can not find user_id, not loading for user preset %1%")%name;
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("can not find user_id, not loading for user preset %1%")%canonical_name;
return false;
}
std::string cloud_user_id = preset_values[BBL_JSON_KEY_USER_ID];
lock();
//std::string name = preset->name;
auto iter = this->find_preset_internal(name);
auto iter = this->find_preset_internal(canonical_name);
bool need_update = false;
if ((iter != m_presets.end()) && (iter->name == name)) {
BOOST_LOG_TRIVIAL(info) << "Found the Preset locally: " << name;
if ((iter != m_presets.end()) && (iter->name == canonical_name)) {
BOOST_LOG_TRIVIAL(info) << "Found the Preset locally: " << canonical_name;
//BBS: we should compare the time between cloud and local
if ((cloud_update_time == 0) || (cloud_update_time <= iter->updated_time)) {
if (cloud_update_time < iter->updated_time)
@@ -2059,7 +2172,7 @@ bool PresetCollection::load_user_preset(std::string name, std::map<std::string,
fs::path idx_file(iter->file);
idx_file.replace_extension(".info");
iter->save_info(idx_file.string());
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("preset %1%'s update_time is eqaul or newer, cloud update_time %2%, local update_time %3%")%name %cloud_update_time %iter->updated_time;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format("preset %1%'s update_time is eqaul or newer, cloud update_time %2%, local update_time %3%")%canonical_name %cloud_update_time %iter->updated_time;
unlock();
return false;
}
@@ -2071,7 +2184,7 @@ bool PresetCollection::load_user_preset(std::string name, std::map<std::string,
// base_id
if (preset_values.find(BBL_JSON_KEY_BASE_ID) == preset_values.end()) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("can not find base_id, not loading for user preset %1%") % name;
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format("can not find base_id, not loading for user preset %1%") % canonical_name;
unlock();
return false;
}
@@ -2081,14 +2194,14 @@ bool PresetCollection::load_user_preset(std::string name, std::map<std::string,
std::string cloud_filament_id;
if ((m_type == Preset::TYPE_FILAMENT) && preset_values.find(BBL_JSON_KEY_FILAMENT_ID) != preset_values.end()) {
cloud_filament_id = preset_values[BBL_JSON_KEY_FILAMENT_ID];
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << name << " filament_id: " << cloud_filament_id << " base_id: " << cloud_base_id;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << canonical_name << " filament_id: " << cloud_filament_id << " base_id: " << cloud_base_id;
}
DynamicPrintConfig new_config, cloud_config;
try {
ConfigSubstitutions config_substitutions = cloud_config.load_string_map(preset_values, rule);
if (! config_substitutions.empty())
substitutions.push_back({ name, m_type, PresetConfigSubstitutions::Source::UserCloud, name, std::move(config_substitutions) });
substitutions.push_back({ canonical_name, m_type, PresetConfigSubstitutions::Source::UserCloud, canonical_name, std::move(config_substitutions) });
//BBS: use inherit config as the base
Preset* inherit_preset = nullptr;
@@ -2096,12 +2209,7 @@ bool PresetCollection::load_user_preset(std::string name, std::map<std::string,
if (inherits_config) {
ConfigOptionString * option_str = dynamic_cast<ConfigOptionString *> (inherits_config);
std::string inherits_value = option_str->value;
/*size_t pos = inherits_value.find_first_of('*');
if (pos != std::string::npos) {
inherits_value.replace(pos, 1, 1, '~');
option_str->value = inherits_value;
}*/
inherit_preset = this->find_preset(inherits_value, false, true);
inherit_preset = this->find_preset2(inherits_value, true);
}
const Preset& default_preset = this->default_preset_for(cloud_config);
if (inherit_preset) {
@@ -2115,7 +2223,7 @@ bool PresetCollection::load_user_preset(std::string name, std::map<std::string,
auto inherits_config2 = dynamic_cast<ConfigOptionString *>(inherits_config);
if (inherits_config2 && !inherits_config2->value.empty()) {
//we should skip this preset here
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", can not find inherit preset for user preset %1%, just skip")%name;
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", can not find inherit preset for user preset %1%, just skip")%canonical_name;
unlock();
return false;
}
@@ -2141,7 +2249,7 @@ bool PresetCollection::load_user_preset(std::string name, std::map<std::string,
std::string incorrect_keys = Preset::remove_invalid_keys(new_config, default_preset.config);
if (!incorrect_keys.empty()) {
++m_errors;
BOOST_LOG_TRIVIAL(error) << "Error in a preset file: The preset \"" << name
BOOST_LOG_TRIVIAL(error) << "Error in a preset file: The preset \"" << canonical_name
<< "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed";
}
if (need_update) {
@@ -2157,15 +2265,17 @@ bool PresetCollection::load_user_preset(std::string name, std::map<std::string,
iter->setting_id = cloud_setting_id;
iter->base_id = cloud_base_id;
iter->filament_id = cloud_filament_id;
update_alias(*iter);
//presets_loaded.emplace_back(*it->second);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", update the user preset %1% from cloud, type %2%, setting_id %3%, base_id %4%, sync_info %5% inherits %6%, filament_id %7%")
% iter->name %Preset::get_type_string(m_type) %iter->setting_id %iter->base_id %iter->sync_info %iter->inherits() % iter->filament_id;
}
else {
//create a new one
Preset preset(m_type, name, false);
Preset preset(m_type, canonical_name, false);
preset.is_system = false;
preset.loaded = true;
preset.bundle_id = load_origin.bundle_id;
preset.config = new_config;
preset.updated_time = cloud_update_time;
preset.sync_info = "save";
@@ -2174,6 +2284,7 @@ bool PresetCollection::load_user_preset(std::string name, std::map<std::string,
preset.setting_id = cloud_setting_id;
preset.base_id = cloud_base_id;
preset.filament_id = cloud_filament_id;
update_alias(preset);
size_t cur_index = iter - m_presets.begin();
m_presets.insert(iter, preset);
@@ -2195,7 +2306,7 @@ bool PresetCollection::load_user_preset(std::string name, std::map<std::string,
if (! errors_cummulative.empty())
throw Slic3r::RuntimeError(errors_cummulative);
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" finished, load user preset %1% , type %2%, errors_cummulative %3%")%name %Preset::get_type_string(m_type) %errors_cummulative;
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" finished, load user preset %1% , type %2%, errors_cummulative %3%")%canonical_name %Preset::get_type_string(m_type) %errors_cummulative;
return (need_update)?false:true;
}
@@ -2216,16 +2327,23 @@ void PresetCollection::update_after_user_presets_loaded()
//BBS: validate_preset
bool PresetCollection::validate_preset(const std::string &preset_name, std::string &inherit_name)
{
std::deque<Preset>::iterator it = this->find_preset_internal(preset_name);
bool found = (it != m_presets.end()) && (it->name == preset_name) && (it->is_system || it->is_default);
// Presets that came from system vendors, the built-in defaults, or any loaded bundle (local or
// subscribed) are trusted — their g-code isn't user-authored, so the 3MF importer should not
// warn about them.
auto is_trusted = [](const Preset &p) { return p.is_system || p.is_default || p.is_from_bundle(); };
const std::string canonical_name = this->canonical_preset_name(preset_name);
std::deque<Preset>::iterator it = this->find_preset_internal(canonical_name);
bool found = (it != m_presets.end()) && (it->name == canonical_name) && is_trusted(*it);
if (!found) {
it = this->find_preset_renamed(preset_name);
found = it != m_presets.end() && (it->is_system || it->is_default);
it = this->find_preset_renamed(canonical_name);
found = it != m_presets.end() && is_trusted(*it);
}
if (!found) {
if (!inherit_name.empty()) {
it = this->find_preset_internal(inherit_name);
found = it != m_presets.end() && it->name == inherit_name && (it->is_system || it->is_default);
const std::string canonical_inherit_name = this->canonical_preset_name(inherit_name);
it = this->find_preset_internal(canonical_inherit_name);
found = it != m_presets.end() && it->name == canonical_inherit_name && is_trusted(*it);
if (found)
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": preset_name %1%, inherit_name %2%, found inherit in list")%preset_name %inherit_name;
else
@@ -2302,7 +2420,7 @@ std::pair<Preset*, bool> PresetCollection::load_external_preset(
cfg.apply_only(combined_config, keys, true);
std::string &inherits = Preset::inherits(cfg);
//BBS: add different settings check logic, replace the old system preset's default value with new system preset's default values
//add different settings check logic, replace the old system preset's default value with new system preset's default values
std::deque<Preset>::iterator it = this->find_preset_internal(original_name);
bool found = it != m_presets.end() && it->name == original_name;
if (! found) {
@@ -2664,7 +2782,7 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det
// Preset with the same name found.
Preset &preset = *it;
//BBS: add project embedded preset logic
if (preset.is_default || preset.is_system) {
if (!preset.can_overwrite()) {
//if (preset.is_default || preset.is_external || preset.is_system)
// Cannot overwrite the default preset.
//BBS: add lock logic for sync preset in background
@@ -2723,6 +2841,9 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det
preset.is_default = false;
preset.is_system = false;
preset.is_external = false;
preset.bundle_id.clear();
preset.file = this->path_for_preset(preset);
// The newly saved preset will be activated -> make it visible.
preset.is_visible = true;
@@ -2765,7 +2886,7 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det
bool PresetCollection::delete_current_preset()
{
Preset &selected = this->get_selected_preset();
if (selected.is_default)
if (!selected.can_overwrite())
return false;
if (get_preset_base(selected) == &selected) {
@@ -2796,18 +2917,24 @@ bool PresetCollection::delete_current_preset()
return true;
}
bool PresetCollection::delete_preset(const std::string& name)
bool PresetCollection::delete_preset(const std::string& name, bool force)
{
auto it = this->find_preset_internal(name);
Preset *preset_ptr = this->find_preset(name, false, true);
if (preset_ptr == nullptr)
return false;
auto it = this->find_preset_internal(preset_ptr->name);
if (it == m_presets.end() || it->name != preset_ptr->name)
return false;
Preset& preset = *it;
if (preset.is_default)
// ORCA: if the preset can't be overridden then don't allow deletion
// force=true bypasses this for bundle preset cleanup from cloud sync
if (!force && !preset.can_overwrite())
return false;
//BBS: add project embedded preset logic and refine is_external
//if (!preset.is_external && !preset.is_system) {
if (! preset.is_system) {
preset.remove_files();
}
preset.remove_files();
//BBS: add lock logic for sync preset in background
lock();
set_printer_hold_alias(it->alias, *it, true);
@@ -2817,6 +2944,30 @@ bool PresetCollection::delete_preset(const std::string& name)
return true;
}
void PresetCollection::check_and_fix_syncinfo(Preset& preset, const std::string& user_id)
{
// user id can't be empty
if (user_id.empty())
return;
// correct the sync info if preset.user_id is empty(the profile json file is copied to the user folder with missing .info file) or preset.user_id
// is not equal to the current user id or preset.setting_id is not in expected format(the .info is copied from the older format)
if (preset.user_id.empty() || preset.user_id != user_id || preset.setting_id.find('-') == std::string::npos) {
preset.user_id = user_id;
preset.setting_id = "";
if (preset.base_id.empty()) {
const std::string inherits = Preset::inherits(preset.config);
Preset* parent_preset = find_preset2(inherits, true);
if (parent_preset)
preset.base_id = parent_preset->setting_id;
}
// tell the sync logic to sync it as a new preset
preset.updated_time = 0;
preset.sync_info = "create";
preset.save_info();
}
}
const Preset* PresetCollection::get_selected_preset_parent() const
{
if (this->get_selected_idx() == size_t(-1))
@@ -2879,7 +3030,7 @@ const Preset *PresetCollection::get_preset_base(const Preset &child) const
// Handle user preset
if (child.inherits().empty())
return &child; // this is user root
auto inherits = find_preset2(child.inherits(),true);
auto inherits = find_preset2(child.inherits(), true);
return inherits ? get_preset_base(*inherits) : nullptr;
}
@@ -2953,20 +3104,21 @@ const std::string& PresetCollection::get_suffix_modified() {
// If a preset was not found by its name, null is returned.
Preset* PresetCollection::find_preset(const std::string &name, bool first_visible_if_not_found, bool real, bool only_from_library)
{
Preset key(m_type, name, false);
auto it = this->find_preset_internal(name, only_from_library);
// Ensure that a temporary copy is returned if the preset found is currently selected.
return (it != m_presets.end() && it->name == key.name) ? &this->preset(it - m_presets.begin(), real) :
first_visible_if_not_found ? &this->first_visible() : nullptr;
const ParsedName parsed = parse_preset_name(name);
const std::string canonical = get_preset_canonical_name(parsed.bare, PresetOrigin(parsed.kind, parsed.bundle_id));
auto it = this->find_preset_internal(canonical, only_from_library);
if (it != m_presets.end() && it->name == canonical)
return &this->preset(it - m_presets.begin(), real);
return first_visible_if_not_found ? &this->first_visible() : nullptr;
}
Preset* PresetCollection::find_preset2(const std::string& name, bool auto_match/* = true */)
{
auto preset = find_preset(name,false,true);
auto preset = find_preset(name, false, true);
if (preset == nullptr) {
auto _name = get_preset_name_renamed(name);
if (_name != nullptr)
preset = find_preset(*_name,false,true);
preset = find_preset(*_name, false, true);
if (auto_match && preset == nullptr) {
//Orca: one more try, find the most likely preset in OrcaFilamentLibrary
if (name.find("Generic") != std::string::npos) {
@@ -2982,7 +3134,6 @@ Preset* PresetCollection::find_preset2(const std::string& name, bool auto_match/
}
}
}
return preset;
}
@@ -3334,10 +3485,11 @@ bool PresetCollection::select_preset_by_name(const std::string &name_w_suffix, b
//BBS: add config related logs
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1%, try to select by name %2%, force %3%")%Preset::get_type_string(m_type) %name_w_suffix %force;
std::string name = Preset::remove_suffix_modified(name_w_suffix);
const std::string normalized_name = this->canonical_preset_name(name);
// 1) Try to find the preset by its name.
auto it = this->find_preset_internal(name);
auto it = this->find_preset_internal(normalized_name);
size_t idx = 0;
if (it != m_presets.end() && it->name == name && it->is_visible)
if (it != m_presets.end() && it->name == normalized_name && it->is_visible)
// Preset found by its name and it is visible.
idx = it - m_presets.begin();
else {
@@ -3370,11 +3522,12 @@ bool PresetCollection::select_preset_by_name_strict(const std::string &name)
{
//BBS: add config related logs
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": %1%, try to select by name %2%")%Preset::get_type_string(m_type) %name;
const std::string canonical_name = this->canonical_preset_name(name);
// 1) Try to find the preset by its name.
auto it = this->find_preset_internal(name);
auto it = this->find_preset_internal(canonical_name);
size_t idx = (size_t)-1;
if (it != m_presets.end() && it->name == name && it->is_visible)
if (it != m_presets.end() && it->name == canonical_name && it->is_visible)
// Preset found by its name.
idx = it - m_presets.begin();
// 2) Select the new preset.
@@ -3486,9 +3639,13 @@ void PresetCollection::update_map_system_profile_renamed()
void PresetCollection::set_custom_preset_alias(Preset &preset)
{
// For filaments, remove the postfix
// For printers, there is nothing to remove
// For prints AKA processes, the postfix should be kept
// Alias should be set here, as the preset name may be augmented further later (i.e., prefixing relative path for bundles)
std::string alias_name;
std::string preset_name = get_preset_bare_name(preset.name);
if (m_type == Preset::Type::TYPE_FILAMENT && preset.config.has(BBL_JSON_KEY_INHERITS) && preset.config.option<ConfigOptionString>(BBL_JSON_KEY_INHERITS)->value.empty()) {
std::string alias_name;
std::string preset_name = preset.name;
if (alias_name.empty()) {
size_t end_pos = preset_name.find_first_of("@");
if (end_pos != std::string::npos) {
@@ -3496,14 +3653,14 @@ void PresetCollection::set_custom_preset_alias(Preset &preset)
boost::trim_right(alias_name);
}
}
if (alias_name.empty() || is_alias_exist(alias_name, &preset))
preset.alias = "";
else {
preset.alias = std::move(alias_name);
m_map_alias_to_profile_name[preset.alias].push_back(preset.name);
set_printer_hold_alias(preset.alias, preset);
}
}
else {
alias_name = preset_name;
}
preset.alias = std::move(alias_name);
m_map_alias_to_profile_name[preset.alias].push_back(preset.name);
set_printer_hold_alias(preset.alias, preset);
}
void PresetCollection::set_printer_hold_alias(const std::string &alias, Preset &preset, bool remove)
@@ -3604,7 +3761,7 @@ std::string PresetCollection::path_from_name(const std::string &new_name, bool d
std::string PresetCollection::path_for_preset(const Preset &preset) const
{
return path_from_name(preset.name, is_base_preset(preset));
return path_from_name(get_preset_bare_name(preset.name), is_base_preset(preset));
}
const Preset& PrinterPresetCollection::default_preset_for(const DynamicPrintConfig &config) const

View File

@@ -29,6 +29,11 @@
#define PRESET_TEMPLATE_DIR "Template"
#define PRESET_CUSTOM_VENDOR "Custom"
// Orca: bundle import directories
#define PRESET_LOCAL_DIR "_local"
#define PRESET_SUBSCRIBED_DIR "_subscribed"
#define PRESET_BUNDLE_METADATA "bundle_metadata.json"
//BBS: iot preset type strings
#define PRESET_IOT_PRINTER_TYPE "printer"
#define PRESET_IOT_FILAMENT_TYPE "filament"
@@ -228,7 +233,8 @@ public:
//BBS: add type for project-embedded
bool is_project_embedded = false;
ConfigSubstitutions *loading_substitutions{nullptr};
bool is_user() const { return ! this->is_default && ! this->is_system && ! this->is_project_embedded; }
bool is_user() const { return ! this->is_default && ! this->is_system && ! this->is_project_embedded && ! this->is_from_bundle(); }
bool can_overwrite() const { return ! this->is_default && ! this->is_system && ! this->is_from_bundle(); }
//bool is_user() const { return ! this->is_default && ! this->is_system; }
// Name of the preset, usually derived form the file name.
@@ -261,6 +267,11 @@ public:
// Orca: flag to indicate if this preset is from Orca Filament Library
bool m_from_orca_filament_lib = false;
// Orca: bundle tracking - imported preset bundles. Bundle ID: UUID (OrcaCloud) or name+timestamp (external).
// Presence of bundle_id is the source of truth for "came from a bundle".
std::string bundle_id;
bool is_from_bundle() const { return ! bundle_id.empty(); }
//BBS
Semver version; // version of preset
std::string ini_str; // ini string of preset
@@ -279,7 +290,7 @@ public:
static Preset::Type get_type_from_string(std::string type_str);
void load_info(const std::string& file);
void save_info(std::string file = "");
void remove_files();
void remove_files(bool cloud_already_deleted = false);
//BBS: add logic for only difference save
//if parent_config is null, save all keys, otherwise, only save difference
@@ -395,6 +406,28 @@ bool is_compatible_with_print (const PresetWithVendorProfile &preset, const Pre
bool is_compatible_with_printer(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_printer, const DynamicPrintConfig *extra_config);
bool is_compatible_with_printer(const PresetWithVendorProfile &preset, const PresetWithVendorProfile &active_printer);
// Where a preset is being loaded from. `Auto` lets load_presets() infer from the directory path.
struct PresetOrigin {
enum class Kind { Auto, User, LocalBundle, SubscribedBundle };
Kind kind { Kind::Auto };
std::string bundle_id;
PresetOrigin() = default;
PresetOrigin(Kind kind, std::string bundle_id = {}) : kind(kind), bundle_id(std::move(bundle_id)) {}
bool is_bundle() const { return kind == Kind::LocalBundle || kind == Kind::SubscribedBundle; }
};
// Prepend the bundle folder to `preset_bare_name` based on `origin`. No-op for non-bundle origins.
std::string get_preset_canonical_name(const std::string &preset_bare_name, const PresetOrigin &origin);
// Tail segment of a canonical name — what's written to the bundle's .json filename and JSON "name" field.
std::string get_preset_bare_name(const std::string &canonical_name);
// Resolve an origin from a directory path when the caller passes Kind::Auto.
PresetOrigin detect_origin_from_path(const boost::filesystem::path &path, const PresetOrigin &explicit_origin = PresetOrigin());
enum class PresetSelectCompatibleType {
// Never select a compatible preset if the newly selected profile is not compatible.
Never,
@@ -471,12 +504,12 @@ public:
void add_default_preset(const std::vector<std::string> &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &preset_name);
// Load ini files of the particular type from the provided directory path.
void load_presets(const std::string &dir_path, const std::string &subdir, PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule rule);
void load_presets(const std::string &dir_path, const std::string &subdir, PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule rule, std::function<void(Preset&)> preset_loaded_fn = nullptr, const PresetOrigin &load_origin = PresetOrigin());
//BBS: update user presets directory
void update_user_presets_directory(const std::string& dir_path, const std::string& type);
void save_user_presets(const std::string& dir_path, const std::string& type, std::vector<std::string>& need_to_delete_list);
bool load_user_preset(std::string name, std::map<std::string, std::string> preset_values, PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule rule);
void save_user_presets(const std::string& dir_path, const std::string& type, std::map<std::string, std::string>& need_to_delete_list);
bool load_user_preset(std::string name, std::map<std::string, std::string> preset_values, PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule rule, const PresetOrigin &load_origin = PresetOrigin(PresetOrigin::Kind::User));
void update_after_user_presets_loaded();
//BBS: get user presets
int get_user_presets(PresetBundle *preset_bundle, std::vector<Preset> &result_presets);
@@ -550,7 +583,11 @@ public:
bool delete_current_preset();
// Delete the current preset, activate the first visible preset.
// returns true if the preset was deleted successfully.
bool delete_preset(const std::string& name);
// When force=true, bypasses the can_overwrite() check (used for bundle preset cleanup).
bool delete_preset(const std::string& name, bool force = false);
// Verify and correct the sync metadata for the preset to ensure proper cloud synchronization.
void check_and_fix_syncinfo(Preset& preset, const std::string& user_id);
// Enable / disable the "- default -" preset.
void set_default_suppressed(bool default_suppressed);
@@ -637,6 +674,7 @@ public:
{
return const_cast<PresetCollection*>(this)->find_preset2(name, auto_match);
}
size_t first_visible_idx() const;
// Return the index of the first visible, compatible, system base preset
// matching the given filament_type. Falls back to base type, then any visible.
@@ -774,6 +812,8 @@ protected:
void set_custom_preset_alias(Preset &preset);
private:
std::string canonical_preset_name(const std::string &name, const PresetOrigin &load_origin = PresetOrigin()) const;
// Comparator that sorts "Generic " prefixed presets before others, then alphabetically within each group.
static bool filament_preset_less(const Preset &a, const Preset &b) {
bool a_generic = boost::starts_with(a.name, GENERIC_PREFIX);
@@ -795,6 +835,7 @@ private:
// The "-- default -- " preset is always the first, so it needs
// to be handled differently.
// If a preset does not exist, an iterator is returned indicating where to insert a preset with the same name.
// `name` must already be canonical — callers canonicalize via find_preset / canonical_preset_name.
std::deque<Preset>::iterator find_preset_internal(const std::string &name, bool from_orca_lib_only = false)
{
auto it = Slic3r::lower_bound_by_predicate(m_presets.begin() + m_num_default_presets, m_presets.end(),
@@ -864,7 +905,7 @@ private:
friend class PresetBundle;
//BBS: mutex
std::mutex m_mutex;
std::recursive_mutex m_mutex;
// Orca: used for validation only
int m_errors = 0;

File diff suppressed because it is too large Load Diff

View File

@@ -6,10 +6,12 @@
#include "enum_bitmask.hpp"
#include <memory>
#include <shared_mutex>
#include <unordered_map>
#include <optional>
#include <array>
#include <boost/filesystem/path.hpp>
#include <unordered_set>
#define DEFAULT_USER_FOLDER_NAME "default"
#define BUNDLE_STRUCTURE_JSON_NAME "bundle_structure.json"
@@ -72,6 +74,79 @@ struct FilamentBaseInfo
int filament_printable = 3;
};
enum BundleType{
Default = 0,
Local,
Subscribed,
};
// Orca: Bundle metadata structure for imported preset bundles
struct BundleMetadata
{
std::string id; // Bundle ID: UUID (OrcaCloud) or name+timestamp (external)
std::string name; // Display name
std::string version; // Bundle version
std::string description;
std::string author;
long long imported_time{0};
long long updated_time{0};
BundleType bundle_type{Default};
std::string path;
// Cached preset names by type (populated on load)
std::vector<std::string> print_presets;
std::vector<std::string> filament_presets;
std::vector<std::string> printer_presets;
// Runtime-only flags
bool is_subscribed{false};
bool update_available{false};
bool not_found{false};
bool unauthorized{false};
bool load_from_json(const std::string& path);
bool save_to_json(const std::string& path) const;
};
struct PresetBundleMetadata
{
// To make sure write locks take precedent, pausereads needs to be true for when Orca needs to read or manipulate the container
// We only need to explicitly pause reads when entering a region in Orca which we deem necessary to quickly acquire write locks.
std::unordered_map<std::string, BundleMetadata> m_bundles;
std::shared_mutex RWMtx;
std::atomic<bool> pauseReads{false};
void PauseRead()
{
pauseReads.store(true);
}
void UnpauseRead()
{
pauseReads.store(false);
}
void ReadLock()
{
RWMtx.lock_shared();
}
void ReadUnlock()
{
RWMtx.unlock_shared();
}
void WriteLock()
{
RWMtx.lock();
}
void WriteUnlock()
{
RWMtx.unlock();
}
};
// Bundle of Print + Filament + Printer presets.
class PresetBundle
{
@@ -82,6 +157,10 @@ public:
std::vector<Preset> &in_filament_presets,
bool apply_extruder,
std::optional<std::vector<int>> filament_maps_new);
// ORCA: utility function to find the vendor for a given preset name
static std::string find_preset_vendor(const std::string& preset_name, Preset::Type type);
PresetBundle();
PresetBundle(const PresetBundle &rhs);
PresetBundle& operator=(const PresetBundle &rhs);
@@ -114,19 +193,53 @@ public:
// BBS Load user presets
PresetsConfigSubstitutions load_user_presets(std::string user, ForwardCompatibilitySubstitutionRule rule);
PresetsConfigSubstitutions load_user_presets(AppConfig &config, std::map<std::string, std::map<std::string, std::string>>& my_presets, ForwardCompatibilitySubstitutionRule rule);
PresetsConfigSubstitutions import_presets(std::vector<std::string> &files, std::function<int(std::string const &)> override_confirm, ForwardCompatibilitySubstitutionRule rule);
bool import_json_presets(PresetsConfigSubstitutions & substitutions,
std::string & file,
std::function<int(std::string const &)> override_confirm,
ForwardCompatibilitySubstitutionRule rule,
int & overwrite,
std::vector<std::string> & result);
void save_user_presets(AppConfig& config, std::vector<std::string>& need_to_delete_list);
// Orca: Import subscribed bundle presets (load and save to disk in one operation), handles one bundle at a time
PresetsConfigSubstitutions update_subscribed_presets(AppConfig& config,
const std::map<std::string, std::map<std::string, std::string>>& bundle_presets,
const BundleMetadata& remote_metadata,
ForwardCompatibilitySubstitutionRule rule);
PresetsConfigSubstitutions import_presets(std::vector<std::string>& files,
std::function<int(std::string const&)> override_confirm,
ForwardCompatibilitySubstitutionRule rule,
AppConfig& config);
bool import_json_presets(PresetsConfigSubstitutions& substitutions,
std::string& file,
std::function<int(std::string const&)> override_confirm,
ForwardCompatibilitySubstitutionRule rule,
int& overwrite,
std::vector<std::string>& result,
const std::string& bundle_dir = "");
void save_user_presets(AppConfig& config, std::map<std::string, std::string>& need_to_delete_list);
void check_and_fix_user_presets_syncinfo(const std::string& user_id);
void remove_users_preset(AppConfig &config, std::map<std::string, std::map<std::string, std::string>> * my_presets = nullptr);
void update_user_presets_directory(const std::string preset_folder);
void remove_user_presets_directory(const std::string preset_folder);
void update_system_preset_setting_ids(std::map<std::string, std::map<std::string, std::string>>& system_presets);
// Apply vendor configuration changes (Core library version, no GUI dependencies)
// This function installs vendors from resources and loads them into the preset bundle
//
// Parameters:
// new_vendors: Map of vendor names to their enabled printer models and variants
// new_filaments: Map of filament names to their settings
// app_config: Pointer to AppConfig to update with new vendor/filament selections
// preferred_printer_model: Optional preferred printer model to select
// preferred_printer_variant: Optional preferred printer variant to select
// preferred_filament: Optional preferred filament to select
//
// Returns: true if successful, false otherwise
bool apply_vendor_config(
const std::map<std::string, std::map<std::string, std::set<std::string>>>& new_vendors,
const std::map<std::string, std::string>& new_filaments,
AppConfig* app_config,
bool overwrite = true,
const std::string& preferred_printer_model = std::string(),
const std::string& preferred_printer_variant = std::string(),
const std::string& preferred_filament = std::string());
//BBS: add API to get previous machine
int validate_presets(const std::string &file_name, DynamicPrintConfig& config, std::set<std::string>& different_gcodes);
@@ -232,6 +345,12 @@ public:
std::map<std::string, DynamicPrintConfig> m_config_maps;
std::map<std::string, std::string> m_filament_id_maps;
// Orca: Bundle metadata and cached preset names
// std::map<std::string, BundleMetadata> m_bundles;
fs::path dir_user_presets_local;
fs::path dir_user_presets_subscribed;
PresetBundleMetadata bundles;
struct ObsoletePresets
{
std::vector<std::string> prints;
@@ -385,9 +504,14 @@ private:
// Orca: used for validation only
bool validation_mode = false;
std::string vendor_to_validate = "";
std::string vendor_to_validate = "";
int m_errors = 0;
// Helper function: save preset to bundle directory with common logic
bool save_preset_to_bundle_dir(Preset& preset, PresetCollection* collection,
const std::string& bundle_id, const std::string& type_subdir,
const std::string& bundle_base_dir);
};
ENABLE_ENUM_BITMASK_OPERATORS(PresetBundle::LoadConfigBundleAttribute)

View File

@@ -707,7 +707,19 @@ inline std::string filter_characters(const std::string& str, const std::string&
return filteredStr;
}
void copy_directory_recursively(const boost::filesystem::path &source, const boost::filesystem::path &target, std::function<bool(const std::string)> filter = nullptr);
void copy_directory_recursively(const boost::filesystem::path& source,
const boost::filesystem::path& target,
std::function<bool(const std::string)> filter = nullptr,
bool merge_mode = false);
// Install vendor bundles from resources directory to data directory
// bundle_names: vector of vendor bundle names (without .json extension)
// resource_subdir: subdirectory under resources_dir() (default: "profiles")
// data_subdir: subdirectory under data_dir() (default: "system")
// Returns: true if all bundles installed successfully, false otherwise
bool install_vendor_bundles_from_resources(const std::vector<std::string>& bundle_names,
const std::string& resource_subdir = "profiles",
const std::string& data_subdir = "system");
// Orca: Since 1.7.9 Boost deprecated save_string_file and load_string_file, copy and modified from boost 1.7.8
void save_string_file(const boost::filesystem::path& p, const std::string& str);

View File

@@ -1597,12 +1597,15 @@ bool bbl_calc_md5(std::string &filename, std::string &md5_out)
}
// SoftFever: copy directory recursively
void copy_directory_recursively(const boost::filesystem::path &source, const boost::filesystem::path &target, std::function<bool(const std::string)> filter)
void copy_directory_recursively(const boost::filesystem::path& source,
const boost::filesystem::path& target,
std::function<bool(const std::string)> filter,
bool merge_mode)
{
BOOST_LOG_TRIVIAL(info) << Slic3r::format("copy_directory_recursively %1% -> %2%", source, target);
std::string error_message;
if (boost::filesystem::exists(target))
if (!merge_mode && boost::filesystem::exists(target))
boost::filesystem::remove_all(target);
boost::filesystem::create_directories(target);
for (auto &dir_entry : boost::filesystem::directory_iterator(source))
@@ -1613,7 +1616,7 @@ void copy_directory_recursively(const boost::filesystem::path &source, const boo
if (boost::filesystem::is_directory(dir_entry)) {
const auto target_path = target / name;
copy_directory_recursively(dir_entry, target_path);
copy_directory_recursively(dir_entry, target_path, filter, merge_mode);
}
else {
if(filter && filter(name))
@@ -1630,6 +1633,76 @@ void copy_directory_recursively(const boost::filesystem::path &source, const boo
return;
}
bool install_vendor_bundles_from_resources(
const std::vector<std::string>& bundle_names,
const std::string& resource_subdir,
const std::string& data_subdir)
{
namespace fs = boost::filesystem;
fs::path rsrc_path = fs::path(Slic3r::resources_dir()) / resource_subdir;
fs::path vendor_path = fs::path(Slic3r::data_dir()) / data_subdir;
BOOST_LOG_TRIVIAL(info) << "Installing " << bundle_names.size() << " bundles from resources...";
for (const auto &bundle : bundle_names) {
try {
// Install the JSON file
auto path_in_rsrc = (rsrc_path / bundle).replace_extension(".json");
auto path_in_vendors = (vendor_path / bundle).replace_extension(".json");
if (!fs::exists(path_in_rsrc)) {
BOOST_LOG_TRIVIAL(warning) << "Bundle not found in resources: " << bundle;
return false;
}
// Create target directory if needed
if (!fs::exists(vendor_path))
fs::create_directories(vendor_path);
// Copy JSON file
std::string error_message;
CopyFileResult cfr = copy_file(path_in_rsrc.string(), path_in_vendors.string(), error_message, false);
if (cfr != CopyFileResult::SUCCESS) {
BOOST_LOG_TRIVIAL(error) << "Failed to copy " << bundle << ".json: " << error_message;
return false;
}
// Copy the vendor directory (if it exists)
auto dir_in_rsrc = rsrc_path / bundle;
auto dir_in_vendors = vendor_path / bundle;
if (fs::exists(dir_in_rsrc) && fs::is_directory(dir_in_rsrc)) {
// Remove existing directory
if (fs::exists(dir_in_vendors))
fs::remove_all(dir_in_vendors);
fs::create_directories(dir_in_vendors);
// Copy with file filter (same as PresetUpdater::install_bundles_rsrc)
// Filter out certain file types: .stl, .png, .svg, .jpeg, .jpg, .3mf
auto file_filter = [](const std::string name) -> bool {
return boost::iends_with(name, ".stl") ||
boost::iends_with(name, ".png") ||
boost::iends_with(name, ".svg") ||
boost::iends_with(name, ".jpeg") ||
boost::iends_with(name, ".jpg") ||
boost::iends_with(name, ".3mf");
};
copy_directory_recursively(dir_in_rsrc, dir_in_vendors, file_filter);
}
BOOST_LOG_TRIVIAL(info) << "Successfully installed bundle: " << bundle;
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "Exception installing bundle " << bundle << ": " << e.what();
return false;
}
}
return true;
}
void save_string_file(const boost::filesystem::path& p, const std::string& str)
{
boost::nowide::ofstream file;