mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-29 03:41:31 +00:00
Revert clang-format changes then reapplied chagnes for Plater/PresetBundle. Fixed extruder masking incorrectness. Fix warning notifications stacking
This commit is contained in:
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
+1731
-1941
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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> ¬ification : 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> ¬ification : 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> ¬ification : 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()
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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]") {
|
||||
|
||||
Reference in New Issue
Block a user