From 507b45431c6969bef596e6d1569474c27ea55101 Mon Sep 17 00:00:00 2001 From: SoftFever Date: Mon, 31 Aug 2026 18:22:52 +0800 Subject: [PATCH] Keep the CAD recipe in the one 3mf backend that actually runs Format/3mf.cpp also saved and loaded it, but nothing calls its store_3mf and its load_3mf only sees files fingerprinted as PrusaSlicer's, which never carry a recipe. Its round-trip test only exercised that dead loop. The BBS backend, which every save and load goes through, is untouched. --- src/libslic3r/Format/3mf.cpp | 37 ------------------ tests/libslic3r/test_3mf.cpp | 76 +++++------------------------------- 2 files changed, 10 insertions(+), 103 deletions(-) diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index d5a2d928e4..812e9f0154 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -73,10 +73,6 @@ const std::string THUMBNAIL_FILE = "Metadata/thumbnail.png"; const std::string PRINT_CONFIG_FILE = "Metadata/Slic3r_PE.config"; const std::string MODEL_CONFIG_FILE = "Metadata/Slic3r_PE_model.config"; const std::string LAYER_HEIGHTS_PROFILE_FILE = "Metadata/Slic3r_PE_layer_heights_profile.txt"; -const std::string CAD_RECIPE_FILE = "Metadata/orca_cad.bin"; -// Read-only: the recipe entry's pre-rename name. A reader that knows only the new one drops the -// feature tree of every project written before the move, without a word. Never written. -const std::string LEGACY_CAD_RECIPE_FILE = "Metadata/SnapOrca_cad.bin"; const std::string LAYER_CONFIG_RANGES_FILE = "Metadata/Prusa_Slicer_layer_config_ranges.xml"; const std::string SLA_SUPPORT_POINTS_FILE = "Metadata/Slic3r_PE_sla_support_points.txt"; const std::string SLA_DRAIN_HOLES_FILE = "Metadata/Slic3r_PE_sla_drain_holes.txt"; @@ -809,18 +805,6 @@ ModelVolumeType type_from_string(const std::string &s) return false; } } - if (boost::algorithm::iequals(name, CAD_RECIPE_FILE) - || boost::algorithm::iequals(name, LEGACY_CAD_RECIPE_FILE)) { - if (stat.m_uncomp_size > 0) { - std::string buffer((size_t)stat.m_uncomp_size, 0); - if (mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, - (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0) != 0) { - model.cad_recipe = std::move(buffer); - } else { - add_error("Error while reading CAD recipe data"); - } - } - } } } @@ -2333,7 +2317,6 @@ ModelVolumeType type_from_string(const std::string &s) bool _add_mesh_to_object_stream(mz_zip_writer_staged_context &context, ModelObject& object, VolumeToOffsetsMap& volumes_offsets); bool _add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items); bool _add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model); - bool _add_cad_recipe_file_to_archive(mz_zip_archive& archive, Model& model); bool _add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model); bool _add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model); bool _add_sla_drain_holes_file_to_archive(mz_zip_archive& archive, Model& model); @@ -2404,13 +2387,6 @@ ModelVolumeType type_from_string(const std::string &s) return false; } - // Adds CAD recipe file ("Metadata/orca_cad.bin"). - if (!_add_cad_recipe_file_to_archive(archive, model)) { - close_zip_writer(&archive); - boost::filesystem::remove(filename); - return false; - } - // Adds layer config ranges file ("Metadata/Slic3r_PE_layer_config_ranges.txt"). // All layer height profiles of all ModelObjects are stored here, indexed by 1 based index of the ModelObject in Model. // The index differes from the index of an object ID of an object instance of a 3MF file! @@ -2943,19 +2919,6 @@ ModelVolumeType type_from_string(const std::string &s) return true; } - bool _3MF_Exporter::_add_cad_recipe_file_to_archive(mz_zip_archive& archive, Model& model) - { - if (model.cad_recipe.empty()) - return true; - if (!mz_zip_writer_add_mem(&archive, CAD_RECIPE_FILE.c_str(), - (const void*)model.cad_recipe.data(), model.cad_recipe.length(), - MZ_DEFAULT_COMPRESSION)) { - add_error("Unable to add CAD recipe file to archive"); - return false; - } - return true; - } - bool _3MF_Exporter::_add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model) { std::string out = ""; diff --git a/tests/libslic3r/test_3mf.cpp b/tests/libslic3r/test_3mf.cpp index 93de4c8dbd..1bf8944b51 100644 --- a/tests/libslic3r/test_3mf.cpp +++ b/tests/libslic3r/test_3mf.cpp @@ -145,8 +145,8 @@ SCENARIO("Export+Import geometry to/from 3mf file cycle", "[3mf]") { } } -// The recipe is an opaque binary blob (CadDocument::serialize_recipe()), so both 3mf backends -// have to carry it byte-for-byte — no XML/text mangling, embedded NULs intact. +// The recipe is an opaque binary blob (CadDocument::serialize_recipe()), so the 3mf backend has +// to carry it byte-for-byte — no XML/text mangling, embedded NULs intact. static std::string make_cad_recipe() { // Built from an explicit length, not append(const char*), which would stop at the first @@ -224,53 +224,11 @@ static void rename_cad_recipe_entry_to_legacy(const std::string& path) out.finalize(); } -SCENARIO("CAD recipe blob survives a 3mf save/load cycle", "[3mf][CAD]") { - GIVEN("a model carrying a binary cad_recipe") { - Model src_model; - std::string src_file = std::string(TEST_DATA_DIR) + "/test_3mf/Prusa.stl"; - REQUIRE(load_stl(src_file.c_str(), &src_model)); - src_model.add_default_instances(); - - const std::string recipe = make_cad_recipe(); - src_model.cad_recipe = recipe; - - WHEN("the model is saved+loaded to/from a 3mf file") { - ScopedTemporaryFile temp(".3mf"); - const std::string test_file = temp.string(); - REQUIRE(store_3mf(test_file.c_str(), &src_model, nullptr, false)); - - Model dst_model; - DynamicPrintConfig dst_config; - ConfigSubstitutionContext ctxt{ ForwardCompatibilitySubstitutionRule::Disable }; - REQUIRE(load_3mf(test_file.c_str(), dst_config, ctxt, &dst_model, false)); - - THEN("the recipe round-trips byte-for-byte") { - REQUIRE(dst_model.cad_recipe.size() == recipe.size()); - REQUIRE(dst_model.cad_recipe == recipe); - } - } - - WHEN("the same model is saved with no recipe") { - src_model.cad_recipe.clear(); - ScopedTemporaryFile temp(".3mf"); - const std::string test_file = temp.string(); - REQUIRE(store_3mf(test_file.c_str(), &src_model, nullptr, false)); - - Model dst_model; - DynamicPrintConfig dst_config; - ConfigSubstitutionContext ctxt{ ForwardCompatibilitySubstitutionRule::Disable }; - REQUIRE(load_3mf(test_file.c_str(), dst_config, ctxt, &dst_model, false)); - - THEN("nothing is written and nothing is read back") { - REQUIRE(dst_model.cad_recipe.empty()); - } - } - } -} - -// The GUI saves/loads projects via the BBS-native 3mf backend (store_bbs_3mf / load_bbs_3mf), -// NOT the PrusaSlicer 3mf.cpp. This locks in both halves: the archive entry is at the exact -// path the importer looks for, and the recipe comes back through the real importer. +// The recipe lives only in the BBS-native backend, because that is the only one that runs: +// store_bbs_3mf is the sole exporter the app calls, and 3mf.cpp's load_3mf is reached only for +// files fingerprinted as PrusaSlicer's, which never carry a recipe. This locks in both halves: +// the archive entry is at the exact path the importer looks for, and the recipe comes back +// through the real importer. SCENARIO("CAD recipe is embedded in the BBS 3mf archive", "[3mf][CAD]") { GIVEN("a model carrying a binary cad_recipe") { Model model; @@ -351,7 +309,8 @@ SCENARIO("CAD recipe is embedded in the BBS 3mf archive", "[3mf][CAD]") { // The recipe entry was renamed from Metadata/SnapOrca_cad.bin to Metadata/orca_cad.bin. Nothing // in the blob marks that move, so a reader that knows only the new name loads a project written // before it with an empty cad_recipe and no error at all — a feature tree gone with no symptom -// but an empty Design tab. Both backends must still accept the old name; neither may write it. +// but an empty Design tab. The importer must still accept the old name; the exporter may never +// write it. SCENARIO("a project saved under the pre-rename recipe name still loads", "[3mf][CAD]") { GIVEN("a project whose recipe entry carries the old name") { Model model; @@ -361,22 +320,7 @@ SCENARIO("a project saved under the pre-rename recipe name still loads", "[3mf][ const std::string recipe = make_cad_recipe(); model.cad_recipe = recipe; - WHEN("it was written by the PrusaSlicer-format backend") { - ScopedTemporaryFile temp(".3mf"); - const std::string test_file = temp.string(); - REQUIRE(store_3mf(test_file.c_str(), &model, nullptr, false)); - rename_cad_recipe_entry_to_legacy(test_file); - - THEN("the recipe still comes back byte-for-byte") { - Model dst_model; - DynamicPrintConfig dst_config; - ConfigSubstitutionContext ctxt{ ForwardCompatibilitySubstitutionRule::Disable }; - REQUIRE(load_3mf(test_file.c_str(), dst_config, ctxt, &dst_model, false)); - REQUIRE(dst_model.cad_recipe == recipe); - } - } - - WHEN("it was written by the BBS backend (the format the GUI uses)") { + WHEN("it was written by the BBS backend") { ScopedTemporaryDir backup_dir("orca_cad_legacy"); model.set_backup_path(backup_dir.string());