mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-18 14:32:36 +00:00
CAD persistence: implement recipe round-trip on the BBS 3mf backend (mirror)
Mirror of snaporca-cad ad822bf. The earlier mirror (2c0140afb9) implemented
persistence in the PrusaSlicer 3mf.cpp, but the GUI saves/loads projects via the
BBS-native backend (store_bbs_3mf / load_bbs_3mf), so the recipe was never
written to nor read from GUI-saved projects.
- bbs_3mf.cpp: BBS_CAD_RECIPE_FILE = "Metadata/SnapOrca_cad.bin"; writer
_add_cad_recipe_file_to_archive (called after layer-height in store_bbs_3mf);
reader branch in _load_model_from_file's metadata dispatch loop (iterate +
iequals on m_filename — mz_zip_reader_locate_file does not work on these
archives).
- Plater.cpp: load_files carries the Model-level cad_recipe onto q->model().
- test_3mf.cpp: [3mf] test asserting store_bbs_3mf embeds the recipe entry
byte-for-byte (read back via miniz, Catch2 v3).
Verified on behemoth: libslic3r_tests + the new [3mf] BBS test pass; orca-slicer
GUI links clean. Round-trip verified live on snaporca-cad (identical code path).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVzKmX6Y1aEteit1HTXG4Q
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
e985d95dfb
commit
36b2bd8bfc
@@ -213,6 +213,7 @@ const std::string BBS_MODEL_CONFIG_RELS_FILE = "Metadata/_rels/model_settings.co
|
||||
const std::string SLICE_INFO_CONFIG_FILE = "Metadata/slice_info.config";
|
||||
const std::string FILAMENT_SEQUENCE_FILE = "Metadata/filament_sequence.json";
|
||||
const std::string BBS_LAYER_HEIGHTS_PROFILE_FILE = "Metadata/layer_heights_profile.txt";
|
||||
const std::string BBS_CAD_RECIPE_FILE = "Metadata/SnapOrca_cad.bin";
|
||||
const std::string LAYER_CONFIG_RANGES_FILE = "Metadata/layer_config_ranges.xml";
|
||||
const std::string BRIM_EAR_POINTS_FILE = "Metadata/brim_ear_points.txt";
|
||||
/*const std::string SLA_SUPPORT_POINTS_FILE = "Metadata/Slic3r_PE_sla_support_points.txt";
|
||||
@@ -1924,6 +1925,14 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
// extract slic3r print config file
|
||||
_extract_project_config_from_archive(archive, stat, config, config_substitutions, model);
|
||||
}
|
||||
else if (boost::algorithm::iequals(name, BBS_CAD_RECIPE_FILE)) {
|
||||
// SnapOrca: restore the editable CAD recipe (optional; absent in non-CAD projects).
|
||||
if (stat.m_uncomp_size > 0) {
|
||||
std::string buf((size_t)stat.m_uncomp_size, '\0');
|
||||
if (mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, buf.data(), buf.size(), 0))
|
||||
model.cad_recipe = std::move(buf);
|
||||
}
|
||||
}
|
||||
else if (boost::algorithm::iequals(name, CUT_INFORMATION_FILE)) {
|
||||
// extract object cut info
|
||||
_extract_cut_information_from_archive(archive, stat, config_substitutions);
|
||||
@@ -5893,6 +5902,7 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
bool _add_mesh_to_object_stream(std::function<bool(std::string &, bool)> const &flush, ObjectData const &object_data) const;
|
||||
bool _add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items) const;
|
||||
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_brim_ear_points_file_to_archive(mz_zip_archive& archive, Model& model);
|
||||
bool _add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model);
|
||||
@@ -6288,6 +6298,11 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_add_cad_recipe_file_to_archive(archive, model)) {
|
||||
close_zip_writer(&archive);
|
||||
return false;
|
||||
}
|
||||
|
||||
// BBS progress point
|
||||
/*BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" <<__LINE__ << boost::format("export 3mf EXPORT_STAGE_ADD_LAYER_RANGE\n");
|
||||
if (proFn) {
|
||||
@@ -7514,6 +7529,19 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _BBS_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, BBS_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 _BBS_3MF_Exporter::_add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model)
|
||||
{
|
||||
std::string out = "";
|
||||
|
||||
@@ -6801,6 +6801,12 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
auto loaded_idxs = load_model_objects(model.objects, is_project_file);
|
||||
obj_idxs.insert(obj_idxs.end(), loaded_idxs.begin(), loaded_idxs.end());
|
||||
|
||||
// load_model_objects only transfers ModelObjects; carry the Model-level CAD
|
||||
// recipe (Metadata/SnapOrca_cad.bin) onto the plater model so the Design tab
|
||||
// can rehydrate the editable feature tree on reopen.
|
||||
if (!model.cad_recipe.empty())
|
||||
q->model().cad_recipe = model.cad_recipe;
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ":" << __LINE__ << boost::format(", finished load_model_objects");
|
||||
wxString msg = wxString::Format(_L("Loading file: %s"), from_path(real_filename));
|
||||
dlg_cont = dlg.Update(progress_percent, msg);
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/Format/3mf.hpp"
|
||||
#include "libslic3r/Format/bbs_3mf.hpp"
|
||||
#include "libslic3r/Format/STL.hpp"
|
||||
#include "libslic3r/miniz_extension.hpp"
|
||||
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <algorithm>
|
||||
|
||||
#include <catch2/catch_tostring.hpp>
|
||||
#include <Eigen/Core>
|
||||
@@ -169,6 +173,61 @@ SCENARIO("CAD recipe blob survives a 3mf save/load cycle", "[3mf]") {
|
||||
}
|
||||
}
|
||||
|
||||
// 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 that store_bbs_3mf embeds the
|
||||
// CAD recipe (Metadata/SnapOrca_cad.bin) byte-for-byte, read back the same iterate-and-match
|
||||
// way load_bbs_3mf does. The full GUI reopen is verified live on the Design tab.
|
||||
SCENARIO("CAD recipe is embedded in the BBS 3mf archive", "[3mf]") {
|
||||
GIVEN("a model carrying a binary cad_recipe") {
|
||||
Model model;
|
||||
std::string src = std::string(TEST_DATA_DIR) + "/test_3mf/Prusa.stl";
|
||||
load_stl(src.c_str(), &model);
|
||||
model.add_default_instances();
|
||||
|
||||
std::string recipe;
|
||||
recipe.push_back('\x01');
|
||||
recipe.append("SNAPORCA");
|
||||
recipe.push_back('\0');
|
||||
recipe.append("\xff\xfe\x00\x10cad-features-blob");
|
||||
model.cad_recipe = recipe;
|
||||
|
||||
WHEN("saved through the BBS backend (the format the GUI uses)") {
|
||||
std::string test_file = std::string(TEST_DATA_DIR) + "/test_3mf/cad_bbs.3mf";
|
||||
DynamicPrintConfig cfg;
|
||||
StoreParams sp;
|
||||
sp.path = test_file.c_str();
|
||||
sp.model = &model;
|
||||
sp.config = &cfg;
|
||||
sp.strategy = SaveStrategy::Zip64;
|
||||
REQUIRE(store_bbs_3mf(sp));
|
||||
|
||||
mz_zip_archive zip;
|
||||
mz_zip_zero_struct(&zip);
|
||||
REQUIRE(open_zip_reader(&zip, test_file));
|
||||
std::string got;
|
||||
mz_uint n = mz_zip_reader_get_num_files(&zip);
|
||||
for (mz_uint i = 0; i < n; ++i) {
|
||||
mz_zip_archive_file_stat st;
|
||||
if (!mz_zip_reader_file_stat(&zip, i, &st)) continue;
|
||||
std::string name(st.m_filename);
|
||||
std::replace(name.begin(), name.end(), '\\', '/');
|
||||
if (boost::algorithm::iequals(name, std::string("Metadata/SnapOrca_cad.bin"))) {
|
||||
got.resize(st.m_uncomp_size);
|
||||
mz_zip_reader_extract_to_mem(&zip, i, got.data(), got.size(), 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
close_zip_reader(&zip);
|
||||
boost::filesystem::remove(test_file);
|
||||
|
||||
THEN("the recipe is present byte-for-byte") {
|
||||
REQUIRE(got.size() == recipe.size());
|
||||
REQUIRE(got == recipe);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("2D convex hull of sinking object", "[3mf][.]") {
|
||||
GIVEN("model") {
|
||||
// load a model
|
||||
|
||||
Reference in New Issue
Block a user