Merge branch 'main' into feat/plugin-lifecycle-evts

This commit is contained in:
Ian Chua
2026-09-18 21:35:09 +08:00
committed by GitHub
7433 changed files with 274917 additions and 159438 deletions
+112 -18
View File
@@ -546,7 +546,7 @@ std::string generate_preset_setting_id(const std::string& vendor, const std::str
return "";
// Dedicated namespace for preset setting_ids, distinct from the cloud per-user
// namespace (OrcaCloudServiceAgent). Keep in sync with scripts/assign_vendor_setting_ids.py;
// namespace (OrcaCloudServiceAgent). Keep in sync with scripts/orca_profile_tool.py;
// never change this constant.
static const boost::uuids::uuid vendor_namespace =
boost::uuids::string_generator()("c1f4d9e2-7a3b-5c8d-9e0f-1a2b3c4d5e6f");
@@ -868,6 +868,20 @@ bool is_compatible_with_printer(const PresetWithVendorProfile &preset, const Pre
return is_compatible_with_printer(preset, active_printer, &config);
}
// ORCA: see the header. The CLI resolves --load-settings into bare DynamicPrintConfigs and has no
// Preset objects to hand; without this it would have to reimplement the policy or build the shells
// at every call site.
bool is_compatible_with_printer(const DynamicPrintConfig &preset_config, Preset::Type preset_type,
const DynamicPrintConfig &printer_config, const std::string &printer_name)
{
Preset preset(preset_type, std::string("__compat_check"));
preset.config = preset_config;
Preset printer(Preset::TYPE_PRINTER, printer_name);
printer.config = printer_config;
return is_compatible_with_printer(PresetWithVendorProfile(preset, nullptr),
PresetWithVendorProfile(printer, nullptr));
}
void Preset::set_visible_from_appconfig(const AppConfig &app_config)
{
//BBS: add config related log
@@ -984,15 +998,19 @@ BedType Preset::get_default_bed_type(PresetBundle* preset_bundle)
if (config.has("default_bed_type") && !config.opt_string("default_bed_type").empty()) {
try {
std::string str_bed_type = config.opt_string("default_bed_type");
// Try parsing as integer first (legacy format)
BedType bed_type;
if (ConfigOptionEnum<BedType>::from_string(str_bed_type, bed_type) &&
bed_type > btDefault && bed_type < btCount) {
return bed_type;
}
// Try parsing as integer (legacy format)
int bed_type_value = atoi(str_bed_type.c_str());
if (bed_type_value > 0) {
if (bed_type_value > 0 && bed_type_value < BedType::btCount) {
return BedType(bed_type_value);
}
else {
BOOST_LOG_TRIVIAL(error) << "default_bed_type: invalid bed type: " << str_bed_type;
}
BOOST_LOG_TRIVIAL(error) << "default_bed_type: invalid bed type: " << str_bed_type;
return BedType::btPEI;
} catch(...) {
@@ -1041,6 +1059,7 @@ static std::vector<std::string> s_Preset_print_options{
"reduce_crossing_wall",
"detect_thin_wall",
"detect_overhang_wall",
"unsupported_wall_last",
"overhang_reverse",
"overhang_reverse_threshold",
"overhang_reverse_internal_only",
@@ -1265,6 +1284,8 @@ static std::vector<std::string> s_Preset_print_options{
"accel_to_decel_enable",
"accel_to_decel_factor",
"wipe_on_loops",
"wipe_inward",
"wipe_inward_distance",
"wipe_before_external_loop",
"bridge_density",
"internal_bridge_density",
@@ -1301,6 +1322,8 @@ static std::vector<std::string> s_Preset_print_options{
"wipe_tower_extra_flow",
"single_extruder_multi_material_priming",
"toolchange_ordering",
"toolchange_cyclic_order",
"toolchange_cyclic_first_layer",
"wipe_tower_rotation_angle",
"tree_support_branch_distance_organic",
"tree_support_branch_diameter_organic",
@@ -1426,7 +1449,7 @@ static std::vector<std::string> s_Preset_printer_options {
"gcode_skip_config_block", "fan_kickstart", "part_cooling_fan_min_pwm", "fan_speedup_time", "fan_speedup_overhangs",
"single_extruder_multi_material", "manual_filament_change", "file_start_gcode", "machine_start_gcode", "machine_end_gcode", "before_layer_change_gcode", "printing_by_object_gcode", "layer_change_gcode", "time_lapse_gcode", "wrapping_detection_gcode", "change_filament_gcode", "change_extrusion_role_gcode",
"printer_model", "printer_variant", "printer_extruder_id", "printer_extruder_variant", "extruder_variant_list", "default_nozzle_volume_type",
"printable_height", "extruder_printable_height", "extruder_clearance_radius", "extruder_clearance_height_to_lid", "extruder_clearance_height_to_rod",
"printable_height", "extruder_printable_height", "extruder_clearance_radius", "extruder_clearance_height_to_lid", "extruder_clearance_height_to_rod", "extruder_clearance_dist_to_rod",
"nozzle_height", "master_extruder_id",
"default_print_profile", "inherits",
"silent_mode",
@@ -1650,7 +1673,7 @@ std::string PresetCollection::canonical_preset_name(const std::string &name, con
void PresetCollection::load_presets(
const std::string &dir_path, const std::string &subdir,
PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule substitution_rule,
std::function<void(Preset&)> preset_loaded_fn, const PresetOrigin &load_origin)
std::function<void(Preset&)> preset_loaded_fn, const PresetOrigin &load_origin, bool read_only)
{
// Don't use boost::filesystem::canonical() on Windows, it is broken in regard to reparse points,
// see https://github.com/prusa3d/PrusaSlicer/issues/732
@@ -1659,7 +1682,7 @@ void PresetCollection::load_presets(
// Load custom roots first
if (fs::exists(dir / "base")) {
load_presets(dir.string(), "base", substitutions, substitution_rule, nullptr, resolved_origin);
load_presets(dir.string(), "base", substitutions, substitution_rule, nullptr, resolved_origin, read_only);
}
//BBS: add config related logs
@@ -1667,7 +1690,8 @@ void PresetCollection::load_presets(
//BBS do not parse folder if not exists
m_dir_path = dir.string();
if (!fs::exists(dir)) {
fs::create_directory(dir);
if (!read_only)
fs::create_directory(dir);
return;
}
@@ -1717,10 +1741,10 @@ void PresetCollection::load_presets(
substitutions.push_back({ preset.name, m_type, PresetConfigSubstitutions::Source::UserFile, preset.file, std::move(config_substitutions) });
if (!reason.empty()) {
fs::path file_path(preset.file);
if (fs::exists(file_path))
if (!read_only && fs::exists(file_path))
fs::remove(file_path);
file_path.replace_extension(".info");
if (fs::exists(file_path))
if (!read_only && fs::exists(file_path))
fs::remove(file_path);
BOOST_LOG_TRIVIAL(error) << boost::format("parse config %1% failed")%preset.file;
++m_errors;
@@ -1791,7 +1815,8 @@ void PresetCollection::load_presets(
size_t at_pos = name.find('@');
if (at_pos != std::string::npos && at_pos + 1 < name.length()) {
compatible_printers->values.push_back(name.substr(at_pos + 1));
preset.save(nullptr);
if (!read_only)
preset.save(nullptr);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " added compatible_printers for preset: " << name;
}
}
@@ -1809,10 +1834,10 @@ void PresetCollection::load_presets(
++m_errors;
BOOST_LOG_TRIVIAL(error) << boost::format("The user-config cannot be loaded: %1%. Reason: %2%")%preset.file %err.what();
fs::path file_path(preset.file);
if (fs::exists(file_path))
if (!read_only && fs::exists(file_path))
fs::remove(file_path);
file_path.replace_extension(".info");
if (fs::exists(file_path))
if (!read_only && fs::exists(file_path))
fs::remove(file_path);
//throw Slic3r::RuntimeError(std::string("The selected preset cannot be loaded: ") + preset.file + "\n\tReason: " + err.what());
} catch (const std::runtime_error &err) {
@@ -1820,10 +1845,10 @@ void PresetCollection::load_presets(
BOOST_LOG_TRIVIAL(error) << boost::format("Failed loading the user-config file: %1%. Reason: %2%")%preset.file %err.what();
//throw Slic3r::RuntimeError(std::string("Failed loading the preset file: ") + preset.file + "\n\tReason: " + err.what());
fs::path file_path(preset.file);
if (fs::exists(file_path))
if (!read_only && fs::exists(file_path))
fs::remove(file_path);
file_path.replace_extension(".info");
if (fs::exists(file_path))
if (!read_only && fs::exists(file_path))
fs::remove(file_path);
}
@@ -3066,6 +3091,75 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det
}
}
// A detached standalone preset for the Full Publish receiver: create a user preset holding
// the full resolved filament config (no inheritance, no vendor/alias links), parentless.
// Note: universal printer compatibility is not enforced here - callers apply
// make_publish_universal() to the config before handing it over when they need it.
// Mirrors save_current_preset(detach=true)'s creation branch but does not force-select or
// diff against a parent; the caller decides whether to select it.
// The published entry's filament_id is forwarded so user bases keep their stable
// material grouping (get_filament_presets() groups user bases by filament_id).
// The copy is a project-embedded preset: it lives inside the loaded project only
// (serialized into the saved .3mf, restored by load_project_embedded_presets) and
// never touches the user's library directory; Preset::save() early-returns for
// embedded presets, so persistence is skipped here too.
// Returns the final (uniquified) name; on collision "<base>" -> "<base> (Published)" ->
// "<base> (Published 2)" ...
std::string PresetCollection::add_detached_preset(const std::string &name_base, DynamicPrintConfig config,
const std::string &filament_id)
{
if (name_base.empty())
return std::string();
Preset stored(m_type, name_base);
stored.config = std::move(config);
stored.filament_id = filament_id;
// Uniquify verbatim; only on collision append " (Published)" then " (Published 2)".
const std::string base_name = name_base;
std::string final_name = base_name;
auto exists = [this](const std::string &candidate) -> bool {
const auto it = this->find_preset_internal(candidate);
return it != m_presets.end() && it->name == candidate;
};
if (exists(final_name)) {
final_name = base_name + " (Published)";
for (int i = 2; exists(final_name); ++i)
final_name = base_name + " (Published " + std::to_string(i) + ")";
}
// Creation branch of save_current_preset(detach=true), without its selection side
// effects or project-embedded path.
lock();
const auto it = this->find_preset_internal(final_name);
if (m_presets.begin() + m_idx_selected >= it)
++m_idx_selected;
Preset &preset = *m_presets.insert(it, stored);
preset.name = final_name;
preset.vendor = nullptr;
preset.alias.clear();
preset.renamed_from.clear();
preset.m_excluded_from.clear();
preset.setting_id.clear();
preset.inherits().clear();
preset.version = Semver::parse(SoftFever_VERSION).value_or(Semver());
preset.is_default = false;
preset.is_system = false;
preset.is_external = false;
preset.bundle_id.clear();
preset.file = this->path_for_preset(preset);
preset.is_visible = true;
preset.is_project_embedded = true;
if (m_type == Preset::TYPE_PRINT)
preset.config.option<ConfigOptionString>("print_settings_id", true)->value = final_name;
else if (m_type == Preset::TYPE_FILAMENT)
preset.config.option<ConfigOptionStrings>("filament_settings_id", true)->values[0] = final_name;
else if (m_type == Preset::TYPE_PRINTER)
preset.config.option<ConfigOptionString>("printer_settings_id", true)->value = final_name;
unlock();
return final_name;
}
bool PresetCollection::delete_current_preset()
{
Preset &selected = this->get_selected_preset();