Revert clang-format changes then reapplied chagnes for Plater/PresetBundle. Fixed extruder masking incorrectness. Fix warning notifications stacking

This commit is contained in:
Lam Wei Lun
2026-09-03 13:17:13 +08:00
parent 686b7f66ac
commit 8c7160079e
8 changed files with 7286 additions and 7208 deletions
+10 -2
View File
@@ -6997,8 +6997,16 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
// receivers onto a geometry-only fallback whose baked-in popup misreports the
// file ("old OrcaSlicer version" / "BambuStudio"), while tag-less files classify
// as From_Other and import the geometry silently.
if (!m_minimal_published)
if (m_minimal_published) {
// metadata_item_map is seeded from the input file's metadata_items above, so a
// project opened from a regular Orca/BBS 3MF still carries the Application /
// OrcaSlicer tags it came with. Erase them: skipping the overwrite is not enough,
// and an empty value would still emit a "present-looking" tag to old receivers.
metadata_item_map.erase(BBL_APPLICATION_TAG);
metadata_item_map.erase(ORCASLICER_TAG);
} else {
metadata_item_map[BBL_APPLICATION_TAG] = (boost::format("%1%-%2%") % "BambuStudio" % SLIC3R_VERSION).str();
}
}
metadata_item_map[BBS_3MF_VERSION] = std::to_string(VERSION_BBS_3MF);
@@ -7025,7 +7033,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
<< xml_escape(item.second) << "</" << METADATA_TAG << ">\n";
if (item.first == BBL_APPLICATION_TAG) {
// The OrcaSlicer tag is only written for files that carry the Application
// tag, which a minimal published 3MF omits (see the map assignment above):
// tag, which a minimal published 3MF erases (see the map assignment above):
// the branch below is unreachable in minimal mode.
stream << " <" << METADATA_TAG << " name=\"" << ORCASLICER_TAG << "\">"
<< xml_escape(SoftFever_VERSION) << "</" << METADATA_TAG << ">\n";
+2
View File
@@ -3096,6 +3096,8 @@ std::string PresetCollection::add_detached_preset(const std::string &name_base,
// 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;
File diff suppressed because it is too large Load Diff
+38 -6
View File
@@ -19,6 +19,27 @@ std::string publish_base_key(const std::string &key)
return pos == std::string::npos ? key : key.substr(0, pos);
}
// Parse the trailing "#N" variant index ("retraction_length#2" -> 2). Returns -1 when the key
// carries no '#' separator or its suffix is malformed; mirrors the importer's strict parse
// (PresetBundle.cpp) so the export side rejects the same variants the receiver would skip.
static int publish_variant_index(const std::string &key, const std::string &base_key)
{
if (key.size() <= base_key.size() || key.compare(0, base_key.size(), base_key) != 0 || key[base_key.size()] != '#')
return -1;
const std::string suffix = key.substr(base_key.size() + 1);
if (suffix.empty())
return -1;
int idx = 0;
for (const char c : suffix) {
if (c < '0' || c > '9')
return -1;
idx = idx * 10 + (c - '0');
if (idx > 1000000) // overflow guard; real vector sizes are tiny
return -1;
}
return idx;
}
std::string normalize_filament_type(const std::string& type)
{
if (type.empty())
@@ -174,8 +195,8 @@ DynamicPrintConfig filter_published_config(
DynamicPrintConfig filtered;
std::set<std::string> base_keys_to_include;
// Never masked (whole-vector serialization): identity, plate geometry and process/printer
// keys.
// Never masked (whole-vector serialization): identity, plate geometry, process keys and
// printer keys without a "#N" variant.
std::set<std::string> mask_exempt_keys;
// Material entries: base key -> author slots whose values must survive; other slots are
// masked to their defaults so a publish (partial or full) does not leak unrelated slot
@@ -209,12 +230,23 @@ DynamicPrintConfig filter_published_config(
mask_exempt_keys.insert(key);
}
// 3. Process and printer published keys
// 3. Process and printer published keys. Printer per-extruder keys carry a "#N" variant
// (e.g. retraction_length#2): mask the base to the author's extruder index so a partial
// publish does not serialize every extruder's value (same slot-masking as the material side).
const std::set<std::string> &printer_keys = publishable_printer_keys();
for (const std::string &key : published_keys) {
const std::string base_key = publish_base_key(key);
if (!base_key.empty()) {
base_keys_to_include.insert(base_key);
mask_exempt_keys.insert(base_key);
if (base_key.empty())
continue;
base_keys_to_include.insert(base_key);
if (printer_keys.count(base_key) != 0) {
const int variant_idx = publish_variant_index(key, base_key);
if (variant_idx >= 0)
slot_mask_map[base_key].insert(variant_idx);
else
mask_exempt_keys.insert(base_key); // bare printer key or malformed variant: whole vector
} else {
mask_exempt_keys.insert(base_key); // process key: whole vector
}
}
+3 -33
View File
@@ -3343,17 +3343,7 @@ size_t NotificationManager::get_notification_count() const
void NotificationManager::bbl_show_plateinfo_notification(const std::string &text)
{
NotificationData data{NotificationType::BBLPlateInfo, NotificationLevel::PrintInfoNotificationLevel, BBL_NOTICE_MAX_INTERVAL, text};
for (std::unique_ptr<PopNotification> &notification : m_pop_notifications) {
if (notification->get_type() == NotificationType::BBLPlateInfo) {
notification->reinit();
notification->update(data);
return;
}
}
auto notification = std::make_unique<NotificationManager::PopNotification>(data, m_id_provider, m_evt_handler);
push_notification_data(std::move(notification), 0);
push_notification_data(data, 0);
}
void NotificationManager::bbl_close_3mf_warn_notification()
@@ -3367,17 +3357,7 @@ void NotificationManager::bbl_close_3mf_warn_notification()
void NotificationManager::bbl_show_3mf_warn_notification(const std::string &text, NotificationLevel level)
{
NotificationData data{NotificationType::BBL3MFInfo, level, BBL_NOTICE_MAX_INTERVAL, text};
for (std::unique_ptr<PopNotification> &notification : m_pop_notifications) {
if (notification->get_type() == NotificationType::BBL3MFInfo) {
notification->reinit();
notification->update(data);
return;
}
}
auto notification = std::make_unique<NotificationManager::PopNotification>(data, m_id_provider, m_evt_handler);
push_notification_data(std::move(notification), 0);
push_notification_data(data, 0);
}
void NotificationManager::bbl_close_plateinfo_notification()
@@ -3392,17 +3372,7 @@ void NotificationManager::bbl_close_plateinfo_notification()
void NotificationManager::bbl_show_preview_only_notification(const std::string &text)
{
NotificationData data{NotificationType::BBLPreviewOnlyMode, NotificationLevel::WarningNotificationLevel, 0, text};
for (std::unique_ptr<PopNotification> &notification : m_pop_notifications) {
if (notification->get_type() == NotificationType::BBLPreviewOnlyMode) {
notification->reinit();
notification->update(data);
return;
}
}
auto notification = std::make_unique<NotificationManager::PopNotification>(data, m_id_provider, m_evt_handler);
push_notification_data(std::move(notification), 0);
push_notification_data(data, 0);
}
void NotificationManager::bbl_close_preview_only_notification()
+4 -1
View File
@@ -1083,7 +1083,10 @@ private:
NotificationType::ProgressBar,
NotificationType::PrintHostUpload,
NotificationType::SimplifySuggestion,
NotificationType::ValidateWarning
NotificationType::ValidateWarning,
// A published file load can produce several distinct 3MF warnings (invalid values,
// skipped settings, changed slots); let them stack rather than clobber each other.
NotificationType::BBL3MFInfo
};
//prepared (basic) notifications
// non-static so its not loaded too early. If static, the translations wont load correctly.
+5360 -5180
View File
File diff suppressed because it is too large Load Diff
+93
View File
@@ -866,6 +866,65 @@ SCENARIO("Minimal published 3MF omits project config, preset dumps and slicer ta
}
}
// A minimal published 3MF must not leak the slicer tags of the source project. The exporter seeds
// metadata_item_map from the input file's metadata_items, so re-publishing a project opened from a
// regular Orca/BBS 3MF (the typical remix flow) must strip the Application / OrcaSlicer tags it
// came with, otherwise old receivers route onto the baked-in "old version" popup.
SCENARIO("MinimalPublished strips slicer tags carried by the source project", "[3mf]") {
GIVEN("a model loaded from a regular Orca/BBS 3MF whose metadata carries the slicer tags") {
Model model;
std::string src_file = std::string(TEST_DATA_DIR) + "/test_3mf/Prusa.stl";
REQUIRE(load_stl(src_file.c_str(), &model));
model.add_default_instances();
model.model_info = std::make_shared<ModelInfo>();
model.model_info->metadata_items[ORCA_PUBLISHED_TAG] = "1";
model.model_info->metadata_items["Application"] = "BambuStudio-2.0.0";
model.model_info->metadata_items["OrcaSlicer"] = "2.1.0";
ScopedTemporaryDir backup_dir("orca_strip_tags");
model.set_backup_path(backup_dir.string());
WHEN("stored using SaveStrategy::MinimalPublished and reloaded") {
ScopedTemporaryFile temp(".3mf");
const std::string test_file = temp.string();
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
StoreParams store_params;
store_params.path = test_file.c_str();
store_params.model = &model;
store_params.config = &config;
store_params.strategy = SaveStrategy::Zip64 | SaveStrategy::Silence | SaveStrategy::MinimalPublished;
REQUIRE(store_bbs_3mf(store_params));
Model dst_model;
DynamicPrintConfig dst_config;
ConfigSubstitutionContext ctxt{ ForwardCompatibilitySubstitutionRule::Enable };
PlateDataPtrs dst_plates;
std::vector<Preset*> loaded_presets;
bool is_bbl_3mf = false, is_orca_3mf = false;
Semver file_version;
bool loaded = load_bbs_3mf(test_file.c_str(), &dst_config, &ctxt, &dst_model, &dst_plates,
&loaded_presets, &is_bbl_3mf, &is_orca_3mf, &file_version, nullptr,
LoadStrategy::LoadModel | LoadStrategy::LoadConfig);
THEN("the source slicer tags are stripped, not carried through") {
REQUIRE(loaded);
REQUIRE(dst_model.model_info != nullptr);
REQUIRE(dst_model.model_info->metadata_items.count("Application") == 0);
REQUIRE(dst_model.model_info->metadata_items.count("OrcaSlicer") == 0);
// The published marker itself must survive.
REQUIRE(dst_model.model_info->metadata_items[ORCA_PUBLISHED_TAG] == "1");
}
THEN("the file classifies as a generic 3MF without a version popup") {
REQUIRE_FALSE(is_bbl_3mf);
REQUIRE_FALSE(is_orca_3mf);
REQUIRE_FALSE(file_version.valid());
}
release_PlateData_list(dst_plates);
}
}
}
// An entry masks the non-published slots to their defaults so publishing slot 1 never leaks slot
// 0's value into the file. Both a full entry (the whole-slot key list) and a partial entry (a
// per-slot key) go through the same masking path in filter_published_config (keys and full_keys
@@ -938,6 +997,40 @@ SCENARIO("Unmaskable keys are dropped from the published payload instead of leak
}
}
// A per-extruder printer key carrying a "#N" variant (e.g. retraction_length#1) must not serialize
// every extruder's value: the base is masked to the author's extruder and the other slots are
// restored to their option default, matching the material-side slot-masking invariant. A bare
// printer base key (no variant) keeps whole-vector serialization.
SCENARIO("Published per-extruder printer keys mask the other extruders to their defaults", "[3mf]") {
GIVEN("a full print configuration with three extruders carrying per-extruder retraction values") {
DynamicPrintConfig full_cfg = DynamicPrintConfig::full_print_config();
// Non-default values on the un-selected slots, so a leak is distinguishable from the mask
// restoring the option default (retraction_length defaults to {0.8}).
full_cfg.opt<ConfigOptionFloats>("retraction_length")->values = { 3.0, 1.2, 4.0 };
WHEN("filtering with only extruder 1's retraction_length checked") {
DynamicPrintConfig filtered_cfg = filter_published_config(full_cfg, { "retraction_length#1" }, {});
THEN("the author's extruder value survives") {
REQUIRE_THAT(filtered_cfg.opt<ConfigOptionFloats>("retraction_length")->values[1], Catch::Matchers::WithinAbs(1.2, 1e-6));
}
THEN("the other extruders are masked to their default") {
REQUIRE_THAT(filtered_cfg.opt<ConfigOptionFloats>("retraction_length")->values[0], Catch::Matchers::WithinAbs(0.8, 1e-6));
REQUIRE_THAT(filtered_cfg.opt<ConfigOptionFloats>("retraction_length")->values[2], Catch::Matchers::WithinAbs(0.8, 1e-6));
}
}
WHEN("filtering the bare base key without a '#N' variant") {
DynamicPrintConfig filtered_cfg = filter_published_config(full_cfg, { "retraction_length" }, {});
THEN("the whole vector is serialized unmasked") {
REQUIRE_THAT(filtered_cfg.opt<ConfigOptionFloats>("retraction_length")->values[0], Catch::Matchers::WithinAbs(3.0, 1e-6));
REQUIRE_THAT(filtered_cfg.opt<ConfigOptionFloats>("retraction_length")->values[1], Catch::Matchers::WithinAbs(1.2, 1e-6));
REQUIRE_THAT(filtered_cfg.opt<ConfigOptionFloats>("retraction_length")->values[2], Catch::Matchers::WithinAbs(4.0, 1e-6));
}
}
}
}
// The extended per-entry fields (full dump list, published type and colour) travel inside the
// published_material_keys metadata and round-trip unchanged.
SCENARIO("Published 3MF round-trips the extended material metadata", "[3mf]") {