mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-18 22:42:37 +00:00
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.
This commit is contained in:
@@ -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 = "";
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user