mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-27 10:51:22 +00:00
Mirror CAD persistence (K1+K2+G1) from snaporca-cad onto mainline OrcaSlicer
Dual-fork mandate: port the parametric-recipe 3MF persistence from snaporca-cad commits ed19eac+147e1c4 so a saved project reopens with the editable CAD feature tree, not just the baked mesh. Shared kernel/format/GUI (identical to snaporca-cad): - CadDocument serialize_recipe/deserialize_recipe (cereal BinaryArchive, versioned) + CadFeature split save/load + imported_solid<->BRep string. - Model::cad_recipe carried through 3MF zip entry Metadata/SnapOrca_cad.bin (writer + binary-verbatim reader branch). - DesignPanel on_commit() stamps the recipe; on_tab_shown() rehydrates a loaded project via load_recipe() (deserialize -> feed_bodies + refresh_tree). Mainline-only adapters (no snaporca-cad counterpart — Catch2 v3 vs v2): - tests/libslic3r/test_caddocument.cpp: <catch2/catch_all.hpp> + `using Catch::Approx;` (v3 scopes Approx under Catch::). - tests/libslic3r/CMakeLists.txt: register test_caddocument.cpp (the original CAD port had left it out of the test build). Verified on behemoth (snaporca-deps toolchain): libslic3r_tests clean; [CadDocument] 17/18 (only the pre-existing tangent-to-circle SIGABRT fails, identical to snaporca-tkz); K1 serialize round-trip + version-reject pass (13 assertions); new [3mf] "CAD recipe blob survives a 3mf save/load cycle" passes byte-for-byte; orca-slicer GUI links clean (186/186, DesignPanel.cpp compiled). Interactive :10 click-through pending (no Design-tab automation). 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
c22351f63a
commit
2c0140afb9
@@ -3,6 +3,7 @@ get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
add_executable(${_TEST_NAME}_tests
|
||||
${_TEST_NAME}_tests.cpp
|
||||
test_3mf.cpp
|
||||
test_caddocument.cpp
|
||||
test_aabbindirect.cpp
|
||||
test_appconfig.cpp
|
||||
test_arachne_walls.cpp
|
||||
|
||||
@@ -133,6 +133,42 @@ SCENARIO("Export+Import geometry to/from 3mf file cycle", "[3mf]") {
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("CAD recipe blob survives a 3mf save/load cycle", "[3mf]") {
|
||||
GIVEN("a model carrying a binary cad_recipe") {
|
||||
Model src_model;
|
||||
std::string src_file = std::string(TEST_DATA_DIR) + "/test_3mf/Prusa.stl";
|
||||
load_stl(src_file.c_str(), &src_model);
|
||||
src_model.add_default_instances();
|
||||
|
||||
// Binary payload with an embedded NUL to prove the carrier is byte-safe
|
||||
// (no XML/text mangling) — mirrors the cereal blob from serialize_recipe().
|
||||
std::string recipe;
|
||||
recipe.push_back('\x01');
|
||||
recipe.append("SNAPORCA");
|
||||
recipe.push_back('\0');
|
||||
recipe.append("\xff\xfe\x00\x10cad-features-blob");
|
||||
src_model.cad_recipe = recipe;
|
||||
|
||||
WHEN("the model is saved+loaded to/from a 3mf file") {
|
||||
std::string test_file = std::string(TEST_DATA_DIR) + "/test_3mf/cad_recipe.3mf";
|
||||
store_3mf(test_file.c_str(), &src_model, nullptr, false);
|
||||
|
||||
Model dst_model;
|
||||
DynamicPrintConfig dst_config;
|
||||
{
|
||||
ConfigSubstitutionContext ctxt{ ForwardCompatibilitySubstitutionRule::Disable };
|
||||
load_3mf(test_file.c_str(), dst_config, ctxt, &dst_model, false);
|
||||
}
|
||||
boost::filesystem::remove(test_file);
|
||||
|
||||
THEN("the recipe round-trips byte-for-byte") {
|
||||
REQUIRE(dst_model.cad_recipe.size() == recipe.size());
|
||||
REQUIRE(dst_model.cad_recipe == recipe);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SCENARIO("2D convex hull of sinking object", "[3mf][.]") {
|
||||
GIVEN("model") {
|
||||
// load a model
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include <catch2/catch.hpp>
|
||||
#include <catch2/catch_all.hpp> // mainline OrcaSlicer ships Catch2 v3 (v2 was catch2/catch.hpp)
|
||||
|
||||
#include "libslic3r/CadDocument.hpp"
|
||||
#include "libslic3r/SketchEngine.hpp"
|
||||
@@ -13,8 +13,10 @@
|
||||
#include <Bnd_Box.hxx>
|
||||
#include <BRepBndLib.hxx>
|
||||
#include <BRepPrimAPI_MakeBox.hxx>
|
||||
#include <cereal/archives/binary.hpp>
|
||||
|
||||
using namespace Slic3r;
|
||||
using Catch::Approx; // Catch2 v3 scopes Approx under Catch:: (v2 had it unqualified)
|
||||
|
||||
TEST_CASE("CadDocument profile sketch -> extrude -> solid", "[CadDocument]")
|
||||
{
|
||||
@@ -1364,3 +1366,58 @@ TEST_CASE("cut splits a body with a plane", "[cut]")
|
||||
REQUIRE_FALSE(doc.recompute());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("serialize_recipe roundtrip with two bodies", "[CadDocument]")
|
||||
{
|
||||
using Catch::Matchers::WithinRel;
|
||||
CadDocument doc;
|
||||
|
||||
// Body 1: rectangle sketch + extrude + fillet
|
||||
int sk1 = doc.add_sketch(SketchShape::Rectangle, SketchPlane::XY(),
|
||||
20, 20, 10, "Rect1");
|
||||
REQUIRE(sk1 >= 0);
|
||||
doc.add_extrude(sk1, 10.0, false, BooleanMode::New, "Extrude1");
|
||||
doc.add_fillet(2.0, FaceGroup::All, "Fillet1");
|
||||
REQUIRE(doc.recompute());
|
||||
REQUIRE(doc.error.empty());
|
||||
|
||||
// Body 2: circle sketch + extrude (separate New body)
|
||||
SketchEntity circ;
|
||||
circ.type = SketchEntity::Type::Circle;
|
||||
circ.center = Vec2d(0, 0);
|
||||
circ.radius = 8.0;
|
||||
int sk2 = doc.add_sketch_entities({circ}, SketchPlane::XZ(), "Circle2");
|
||||
doc.add_extrude(sk2, 6.0, false, BooleanMode::New, "Extrude2");
|
||||
REQUIRE(doc.recompute());
|
||||
REQUIRE(doc.error.empty());
|
||||
|
||||
REQUIRE(doc.bodies.size() >= 2);
|
||||
std::vector<double> orig_vols;
|
||||
for (const auto& b : doc.bodies)
|
||||
orig_vols.push_back(double(SketchEngine::tessellate(b.shape).volume()));
|
||||
|
||||
auto blob = doc.serialize_recipe();
|
||||
REQUIRE_FALSE(blob.empty());
|
||||
|
||||
CadDocument doc2;
|
||||
REQUIRE(doc2.deserialize_recipe(blob));
|
||||
REQUIRE(doc2.error.empty());
|
||||
REQUIRE(doc2.bodies.size() == doc.bodies.size());
|
||||
|
||||
for (size_t i = 0; i < doc.bodies.size(); ++i) {
|
||||
double v2 = double(SketchEngine::tessellate(doc2.bodies[i].shape).volume());
|
||||
REQUIRE_THAT(v2, WithinRel(orig_vols[i], 1e-6));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("deserialize_recipe rejects future version", "[CadDocument]")
|
||||
{
|
||||
CadDocument doc;
|
||||
std::ostringstream oss;
|
||||
{
|
||||
cereal::BinaryOutputArchive ar(oss);
|
||||
uint32_t v = 999;
|
||||
ar(v);
|
||||
}
|
||||
REQUIRE_FALSE(doc.deserialize_recipe(oss.str()));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user