diff --git a/src/libslic3r/Format/bbs_3mf.cpp b/src/libslic3r/Format/bbs_3mf.cpp index 6415f29413..4a7cac7055 100644 --- a/src/libslic3r/Format/bbs_3mf.cpp +++ b/src/libslic3r/Format/bbs_3mf.cpp @@ -1224,7 +1224,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result) // Reads the parse-time metadata: the model XML carries it before its resources, while // m_model->model_info is only filled in after the whole XML has been parsed. bool _is_published_3mf() const { - return this->model_info.metadata_items.find("published") != this->model_info.metadata_items.end(); + return this->model_info.metadata_items.find(ORCA_PUBLISHED_TAG) != this->model_info.metadata_items.end(); } bool _is_svg_shape_file(const std::string &filename) const; diff --git a/src/libslic3r/Format/bbs_3mf.hpp b/src/libslic3r/Format/bbs_3mf.hpp index 57a40c2012..647488ad55 100644 --- a/src/libslic3r/Format/bbs_3mf.hpp +++ b/src/libslic3r/Format/bbs_3mf.hpp @@ -154,6 +154,15 @@ enum class SaveStrategy Backup = 0x10000 | WithGcode | Silence | SkipStatic | SplitModel, }; +// Model metadata keys of a "published" 3MF (see MinimalPublished): the flag marks a minimal, +// tag-less publish export, the others carry the author-selected settings payload. Namespaced +// with the "orca_published" prefix because metadata_items round-trips verbatim through other +// slicers, where a bare "published" key could collide. +inline constexpr const char *ORCA_PUBLISHED_TAG = "orca_published"; +inline constexpr const char *ORCA_PUBLISHED_KEYS_TAG = "orca_published_keys"; +inline constexpr const char *ORCA_PUBLISHED_MATERIAL_TAG = "orca_published_material_keys"; +inline constexpr const char *ORCA_PUBLISHED_CONFIG_TAG = "orca_published_config"; + inline SaveStrategy operator | (SaveStrategy lhs, SaveStrategy rhs) { using T = std::underlying_type_t ; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index bdca6b7c77..dfeca24a19 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -6920,11 +6920,11 @@ std::vector Plater::priv::load_files(const std::vector& input_ // metadata payload, which must fill config_loaded before the chain decides // whether to import geometry only. if (model.model_info != nullptr) { - auto published_it = model.model_info->metadata_items.find("published"); + auto published_it = model.model_info->metadata_items.find(ORCA_PUBLISHED_TAG); if (published_it != model.model_info->metadata_items.end() && (published_it->second == "true" || published_it->second == "1")) { published_config.published = true; - auto keys_it = model.model_info->metadata_items.find("published_keys"); + auto keys_it = model.model_info->metadata_items.find(ORCA_PUBLISHED_KEYS_TAG); if (keys_it != model.model_info->metadata_items.end()) { try { auto j = nlohmann::json::parse(keys_it->second); @@ -6937,7 +6937,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ } } - auto material_keys_it = model.model_info->metadata_items.find("published_material_keys"); + auto material_keys_it = model.model_info->metadata_items.find(ORCA_PUBLISHED_MATERIAL_TAG); if (material_keys_it != model.model_info->metadata_items.end()) { try { auto jm = nlohmann::json::parse(material_keys_it->second); @@ -6995,7 +6995,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ // file carries no project_settings.config, so config_loaded is filled // from here; a missing or malformed payload leaves it empty and the // fallback chain below imports the geometry only. - auto payload_it = model.model_info->metadata_items.find("published_config"); + auto payload_it = model.model_info->metadata_items.find(ORCA_PUBLISHED_CONFIG_TAG); if (payload_it != model.model_info->metadata_items.end()) { try { ConfigSubstitutions payload_substitutions = config_loaded.load_from_ini_string(payload_it->second, ForwardCompatibilitySubstitutionRule::Enable); @@ -7320,10 +7320,10 @@ std::vector Plater::priv::load_files(const std::vector& input_ if (published_out != nullptr && published_config.published) *published_out = true; if (published_config.published && load_config && this->model.model_info != nullptr) { - this->model.model_info->metadata_items.erase("published"); - this->model.model_info->metadata_items.erase("published_keys"); - this->model.model_info->metadata_items.erase("published_material_keys"); - this->model.model_info->metadata_items.erase("published_config"); + this->model.model_info->metadata_items.erase(ORCA_PUBLISHED_TAG); + this->model.model_info->metadata_items.erase(ORCA_PUBLISHED_KEYS_TAG); + this->model.model_info->metadata_items.erase(ORCA_PUBLISHED_MATERIAL_TAG); + this->model.model_info->metadata_items.erase(ORCA_PUBLISHED_CONFIG_TAG); } if (load_config) { @@ -16289,19 +16289,19 @@ int Plater::export_published_3mf(const std::vector& published_keys, // Save the previous metadata so it can be restored after the export, keeping the in-memory // project pristine (the published flag lives only in the exported file). const bool had_model_info = (model.model_info != nullptr); - const bool had_published = had_model_info && (model.model_info->metadata_items.find("published") != model.model_info->metadata_items.end()); - const bool had_published_keys = had_model_info && (model.model_info->metadata_items.find("published_keys") != model.model_info->metadata_items.end()); - const bool had_material_keys = had_model_info && (model.model_info->metadata_items.find("published_material_keys") != model.model_info->metadata_items.end()); - const bool had_payload = had_model_info && (model.model_info->metadata_items.find("published_config") != model.model_info->metadata_items.end()); - const std::string prev_published = had_published ? model.model_info->metadata_items.at("published") : std::string(); - const std::string prev_published_keys = had_published_keys ? model.model_info->metadata_items.at("published_keys") : std::string(); - const std::string prev_material_keys = had_material_keys ? model.model_info->metadata_items.at("published_material_keys") : std::string(); - const std::string prev_payload = had_payload ? model.model_info->metadata_items.at("published_config") : std::string(); + const bool had_published = had_model_info && (model.model_info->metadata_items.find(ORCA_PUBLISHED_TAG) != model.model_info->metadata_items.end()); + const bool had_published_keys = had_model_info && (model.model_info->metadata_items.find(ORCA_PUBLISHED_KEYS_TAG) != model.model_info->metadata_items.end()); + const bool had_material_keys = had_model_info && (model.model_info->metadata_items.find(ORCA_PUBLISHED_MATERIAL_TAG) != model.model_info->metadata_items.end()); + const bool had_payload = had_model_info && (model.model_info->metadata_items.find(ORCA_PUBLISHED_CONFIG_TAG) != model.model_info->metadata_items.end()); + const std::string prev_published = had_published ? model.model_info->metadata_items.at(ORCA_PUBLISHED_TAG) : std::string(); + const std::string prev_published_keys = had_published_keys ? model.model_info->metadata_items.at(ORCA_PUBLISHED_KEYS_TAG) : std::string(); + const std::string prev_material_keys = had_material_keys ? model.model_info->metadata_items.at(ORCA_PUBLISHED_MATERIAL_TAG) : std::string(); + const std::string prev_payload = had_payload ? model.model_info->metadata_items.at(ORCA_PUBLISHED_CONFIG_TAG) : std::string(); if (model.model_info == nullptr) model.model_info = std::make_shared(); - model.model_info->metadata_items["published"] = "1"; - model.model_info->metadata_items["published_keys"] = j.dump(); - model.model_info->metadata_items["published_material_keys"] = jm.dump(); + model.model_info->metadata_items[ORCA_PUBLISHED_TAG] = "1"; + model.model_info->metadata_items[ORCA_PUBLISHED_KEYS_TAG] = j.dump(); + model.model_info->metadata_items[ORCA_PUBLISHED_MATERIAL_TAG] = jm.dump(); // Minimal published export: filter full_config to the published keys, material keys, // identity fields and plate geometry keys, and omit the project config file, the @@ -16314,7 +16314,7 @@ int Plater::export_published_3mf(const std::vector& published_keys, std::string payload; for (const std::string &key : filtered_cfg.keys()) payload += key + " = " + filtered_cfg.opt_serialize(key) + "\n"; - model.model_info->metadata_items["published_config"] = std::move(payload); + model.model_info->metadata_items[ORCA_PUBLISHED_CONFIG_TAG] = std::move(payload); // Same file layout as save_project(), plus Silence (so export_3mf does not set the project // filename on success, keeping this a pure export like export_core_3mf()) and MinimalPublished. @@ -16332,21 +16332,21 @@ int Plater::export_published_3mf(const std::vector& published_keys, model.model_info = nullptr; } else { if (had_published) - model.model_info->metadata_items["published"] = prev_published; + model.model_info->metadata_items[ORCA_PUBLISHED_TAG] = prev_published; else - model.model_info->metadata_items.erase("published"); + model.model_info->metadata_items.erase(ORCA_PUBLISHED_TAG); if (had_published_keys) - model.model_info->metadata_items["published_keys"] = prev_published_keys; + model.model_info->metadata_items[ORCA_PUBLISHED_KEYS_TAG] = prev_published_keys; else - model.model_info->metadata_items.erase("published_keys"); + model.model_info->metadata_items.erase(ORCA_PUBLISHED_KEYS_TAG); if (had_material_keys) - model.model_info->metadata_items["published_material_keys"] = prev_material_keys; + model.model_info->metadata_items[ORCA_PUBLISHED_MATERIAL_TAG] = prev_material_keys; else - model.model_info->metadata_items.erase("published_material_keys"); + model.model_info->metadata_items.erase(ORCA_PUBLISHED_MATERIAL_TAG); if (had_payload) - model.model_info->metadata_items["published_config"] = prev_payload; + model.model_info->metadata_items[ORCA_PUBLISHED_CONFIG_TAG] = prev_payload; else - model.model_info->metadata_items.erase("published_config"); + model.model_info->metadata_items.erase(ORCA_PUBLISHED_CONFIG_TAG); } }; diff --git a/src/slic3r/GUI/PublishSettingsDialog.hpp b/src/slic3r/GUI/PublishSettingsDialog.hpp index 2b094da75b..2cf49e7eb9 100644 --- a/src/slic3r/GUI/PublishSettingsDialog.hpp +++ b/src/slic3r/GUI/PublishSettingsDialog.hpp @@ -11,7 +11,6 @@ #include #include #include -#include // Forward declarations (all are global classes, see Widgets/TextInput.hpp and // Widgets/StaticLine.hpp). @@ -30,7 +29,7 @@ struct PublishMaterialIdentity // Dialog letting a model author select which settings get embedded in a 3MF. Nested tab layout // mirroring the Process settings (Printer / Filament / Process outer tabs, category or material // tabs inside each). Dirty settings are pre-checked and shown bold; on OK the print rows become -// "published_keys" and the material rows become "published_material_keys". +// "orca_published_keys" and the material rows become "orca_published_material_keys". class PublishSettingsDialog : public DPIDialog { public: diff --git a/tests/libslic3r/test_3mf.cpp b/tests/libslic3r/test_3mf.cpp index 8898dae2fe..089cc7a40d 100644 --- a/tests/libslic3r/test_3mf.cpp +++ b/tests/libslic3r/test_3mf.cpp @@ -501,8 +501,8 @@ SCENARIO("Nozzle-group metadata .3mf round-trip", "[3mf][MultiNozzle]") { } } -// Locks the serialization contract of the "Publish" metadata: the published flag and the -// published_keys JSON array in model.model_info->metadata_items must survive a store_bbs_3mf -> +// Locks the serialization contract of the "Publish" metadata: the orca_published flag and the +// orca_published_keys JSON array in model.model_info->metadata_items must survive a store_bbs_3mf -> // load_bbs_3mf round-trip unchanged. (The full preset-preservation behavior is exercised // headlessly in test_preset_bundle_loading.cpp.) SCENARIO("Published 3MF round-trips the published flag and published_keys metadata", "[3mf]") { @@ -513,8 +513,8 @@ SCENARIO("Published 3MF round-trips the published flag and published_keys metada model.add_default_instances(); model.model_info = std::make_shared(); - model.model_info->metadata_items["published"] = "1"; - model.model_info->metadata_items["published_keys"] = R"(["layer_height","wall_thickness"])"; + model.model_info->metadata_items[ORCA_PUBLISHED_TAG] = "1"; + model.model_info->metadata_items[ORCA_PUBLISHED_KEYS_TAG] = R"(["layer_height","wall_thickness"])"; // store_bbs_3mf stages project_settings.config through the model's backup path; point // it at a writable temp dir (the default lives under a read-only root in CI). @@ -546,12 +546,12 @@ SCENARIO("Published 3MF round-trips the published flag and published_keys metada THEN("the published metadata round-trips unchanged") { REQUIRE(loaded); REQUIRE(dst_model.model_info != nullptr); - REQUIRE(dst_model.model_info->metadata_items["published"] == "1"); - REQUIRE(dst_model.model_info->metadata_items["published_keys"] == R"(["layer_height","wall_thickness"])"); + REQUIRE(dst_model.model_info->metadata_items[ORCA_PUBLISHED_TAG] == "1"); + REQUIRE(dst_model.model_info->metadata_items[ORCA_PUBLISHED_KEYS_TAG] == R"(["layer_height","wall_thickness"])"); - // The published_keys value is a JSON array of setting keys; it must parse back to + // The orca_published_keys value is a JSON array of setting keys; it must parse back to // the same keys that were selected. - nlohmann::json keys = nlohmann::json::parse(dst_model.model_info->metadata_items["published_keys"]); + nlohmann::json keys = nlohmann::json::parse(dst_model.model_info->metadata_items[ORCA_PUBLISHED_KEYS_TAG]); REQUIRE(keys.is_array()); REQUIRE(keys.size() == 2); REQUIRE(keys[0] == "layer_height"); @@ -563,7 +563,7 @@ SCENARIO("Published 3MF round-trips the published flag and published_keys metada } // A normal 3MF (no Publish metadata) must load identically: the loader must not fabricate a -// "published" flag or published_keys for files that never carried them. +// "orca_published" flag or orca_published_keys for files that never carried them. SCENARIO("Legacy 3MF without published metadata loads unchanged", "[3mf]") { GIVEN("a model without any published metadata") { Model model; @@ -599,8 +599,8 @@ SCENARIO("Legacy 3MF without published metadata loads unchanged", "[3mf]") { THEN("no published key is fabricated") { REQUIRE(loaded); if (dst_model.model_info != nullptr) { - REQUIRE(dst_model.model_info->metadata_items.count("published") == 0); - REQUIRE(dst_model.model_info->metadata_items.count("published_keys") == 0); + REQUIRE(dst_model.model_info->metadata_items.count(ORCA_PUBLISHED_TAG) == 0); + REQUIRE(dst_model.model_info->metadata_items.count(ORCA_PUBLISHED_KEYS_TAG) == 0); } } release_PlateData_list(dst_plates); @@ -608,8 +608,8 @@ SCENARIO("Legacy 3MF without published metadata loads unchanged", "[3mf]") { } } -// Locks the serialization contract of the published_material_keys metadata: the per-entry JSON -// must survive a store_bbs_3mf -> load_bbs_3mf round-trip verbatim, exactly like published_keys. +// Locks the serialization contract of the orca_published_material_keys metadata: the per-entry JSON +// must survive a store_bbs_3mf -> load_bbs_3mf round-trip verbatim, exactly like orca_published_keys. SCENARIO("Published 3MF round-trips the published_material_keys metadata", "[3mf]") { GIVEN("a model carrying published material keys metadata") { Model model; @@ -621,7 +621,7 @@ SCENARIO("Published 3MF round-trips the published_material_keys metadata", "[3mf R"([{"material":{"filament_type":"PLA","filament_vendor":"Generic","filament_id":"GFL99"},"slot":0,"keys":["filament_retraction_length","filament_z_hop"]}])"; model.model_info = std::make_shared(); - model.model_info->metadata_items["published_material_keys"] = material_keys_json; + model.model_info->metadata_items[ORCA_PUBLISHED_MATERIAL_TAG] = material_keys_json; ScopedTemporaryDir backup_dir("orca_pub_mat"); model.set_backup_path(backup_dir.string()); @@ -651,7 +651,7 @@ SCENARIO("Published 3MF round-trips the published_material_keys metadata", "[3mf THEN("the published material keys metadata round-trips unchanged") { REQUIRE(loaded); REQUIRE(dst_model.model_info != nullptr); - REQUIRE(dst_model.model_info->metadata_items["published_material_keys"] == material_keys_json); + REQUIRE(dst_model.model_info->metadata_items[ORCA_PUBLISHED_MATERIAL_TAG] == material_keys_json); // The value must parse back to one material entry carrying the nested identity // object, the author slot ordinal and the key list. @@ -706,9 +706,9 @@ SCENARIO("Minimal published 3MF omits project config, preset dumps and slicer ta payload += key + " = " + filtered_cfg.opt_serialize(key) + "\n"; model.model_info = std::make_shared(); - model.model_info->metadata_items["published"] = "1"; - model.model_info->metadata_items["published_keys"] = R"(["layer_height","retraction_length"])"; - model.model_info->metadata_items["published_config"] = payload; + model.model_info->metadata_items[ORCA_PUBLISHED_TAG] = "1"; + model.model_info->metadata_items[ORCA_PUBLISHED_KEYS_TAG] = R"(["layer_height","retraction_length"])"; + model.model_info->metadata_items[ORCA_PUBLISHED_CONFIG_TAG] = payload; ScopedTemporaryDir backup_dir("orca_min_pub"); model.set_backup_path(backup_dir.string()); @@ -760,13 +760,13 @@ SCENARIO("Minimal published 3MF omits project config, preset dumps and slicer ta } THEN("the published metadata and payload round-trip unchanged") { REQUIRE(dst_model.model_info != nullptr); - REQUIRE(dst_model.model_info->metadata_items["published"] == "1"); - REQUIRE(dst_model.model_info->metadata_items["published_keys"] == R"(["layer_height","retraction_length"])"); - REQUIRE(dst_model.model_info->metadata_items["published_config"] == payload); + REQUIRE(dst_model.model_info->metadata_items[ORCA_PUBLISHED_TAG] == "1"); + REQUIRE(dst_model.model_info->metadata_items[ORCA_PUBLISHED_KEYS_TAG] == R"(["layer_height","retraction_length"])"); + REQUIRE(dst_model.model_info->metadata_items[ORCA_PUBLISHED_CONFIG_TAG] == payload); } THEN("the payload parses back to the published values") { DynamicPrintConfig parsed_payload; - parsed_payload.load_from_ini_string(dst_model.model_info->metadata_items["published_config"], ForwardCompatibilitySubstitutionRule::Enable); + parsed_payload.load_from_ini_string(dst_model.model_info->metadata_items[ORCA_PUBLISHED_CONFIG_TAG], ForwardCompatibilitySubstitutionRule::Enable); REQUIRE(parsed_payload.option("layer_height") != nullptr); REQUIRE_THAT(parsed_payload.opt_float("layer_height"), Catch::Matchers::WithinAbs(0.24, 1e-6)); REQUIRE(parsed_payload.option("retraction_length") != nullptr); @@ -887,7 +887,7 @@ SCENARIO("Published 3MF round-trips the extended material metadata", "[3mf]") { R"([{"material":{"filament_type":"PLA","filament_vendor":"Generic","filament_id":"GFL99","setting_id":"RFs9eCKYOMUSmvZf","name":"Generic PLA Matte @System"},"slot":1,"keys":[],"full":true,"full_keys":["filament_retraction_length","filament_colour"],"publish_type":true,"type":"PLA","publish_color":false,"color":""}])"; model.model_info = std::make_shared(); - model.model_info->metadata_items["published_material_keys"] = material_keys_json; + model.model_info->metadata_items[ORCA_PUBLISHED_MATERIAL_TAG] = material_keys_json; ScopedTemporaryDir backup_dir("orca_pub_mat2"); model.set_backup_path(backup_dir.string()); @@ -917,7 +917,7 @@ SCENARIO("Published 3MF round-trips the extended material metadata", "[3mf]") { THEN("the extended material metadata round-trips unchanged") { REQUIRE(loaded); REQUIRE(dst_model.model_info != nullptr); - REQUIRE(dst_model.model_info->metadata_items["published_material_keys"] == material_keys_json); + REQUIRE(dst_model.model_info->metadata_items[ORCA_PUBLISHED_MATERIAL_TAG] == material_keys_json); // The value must parse back with every extended field intact. nlohmann::json entries = nlohmann::json::parse(material_keys_json);