mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-18 14:32:36 +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
@@ -42,6 +42,10 @@
|
||||
#include <cmath>
|
||||
#include <stdexcept>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
|
||||
#include <cereal/archives/binary.hpp>
|
||||
#include <BRepTools.hxx>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@@ -1537,4 +1541,52 @@ bool CadDocument::preview(const CadFeature& candidate, TriangleMesh& out_mesh, s
|
||||
return preview(candidate, out_mesh, ignore, err);
|
||||
}
|
||||
|
||||
std::string brep_to_string(const TopoDS_Shape& s)
|
||||
{
|
||||
if (s.IsNull()) return {};
|
||||
std::ostringstream oss;
|
||||
BRepTools::Write(s, oss);
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
TopoDS_Shape brep_from_string(const std::string& d)
|
||||
{
|
||||
if (d.empty()) return {};
|
||||
std::istringstream iss(d);
|
||||
TopoDS_Shape s;
|
||||
BRep_Builder b;
|
||||
BRepTools::Read(s, iss, b);
|
||||
return s;
|
||||
}
|
||||
|
||||
std::string CadDocument::serialize_recipe() const
|
||||
{
|
||||
std::ostringstream oss;
|
||||
{
|
||||
cereal::BinaryOutputArchive ar(oss);
|
||||
uint32_t v = SNAPORCA_CAD_RECIPE_VERSION;
|
||||
ar(v);
|
||||
ar(features);
|
||||
}
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
bool CadDocument::deserialize_recipe(const std::string& blob)
|
||||
{
|
||||
try {
|
||||
std::istringstream iss(blob);
|
||||
cereal::BinaryInputArchive ar(iss);
|
||||
uint32_t v;
|
||||
ar(v);
|
||||
if (v > SNAPORCA_CAD_RECIPE_VERSION)
|
||||
return false;
|
||||
ar(features);
|
||||
return recompute();
|
||||
} catch (const Standard_Failure&) {
|
||||
return false;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
|
||||
#include <TopoDS_Shape.hxx>
|
||||
#include <TopoDS_Wire.hxx>
|
||||
#include <cereal/cereal.hpp>
|
||||
#include <cereal/types/vector.hpp>
|
||||
#include <cereal/types/string.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
@@ -20,6 +23,11 @@ enum class BooleanMode { New, Add, Cut, Intersect };
|
||||
|
||||
enum class ExtrudeEnd { Blind, Symmetric, TwoSided, ThroughAll, UpToFace, UpToVertex };
|
||||
|
||||
// Serialize a TopoDS_Shape to/from a BRep string (declared before CadFeature so its
|
||||
// inline cereal save()/load() can resolve these non-dependent calls).
|
||||
std::string brep_to_string(const TopoDS_Shape& s);
|
||||
TopoDS_Shape brep_from_string(const std::string& d);
|
||||
|
||||
struct CadFeature {
|
||||
CadFeatureType type{CadFeatureType::Sketch};
|
||||
std::string name;
|
||||
@@ -181,8 +189,56 @@ struct CadFeature {
|
||||
bool cut_flip{false}; // flip the normal => swaps which side is "upper"
|
||||
bool cut_keep_upper{true}; // keep the +normal half
|
||||
bool cut_keep_lower{false}; // keep the -normal half (both => split into two bodies)
|
||||
|
||||
template<class Archive>
|
||||
void save(Archive& ar) const {
|
||||
std::string brep = (type == CadFeatureType::Import) ? brep_to_string(imported_solid) : std::string();
|
||||
ar(type, name, enabled, shape, plane, width, height, radius,
|
||||
profile, entities, constraints, entity_constraints, imported_regions,
|
||||
import_offset, import_scale_x, import_scale_y, import_on_face, import_face_body,
|
||||
sketch_ref, distance, symmetric, mode, extrude_end, distance2, taper_deg, flip,
|
||||
up_to_face, extrude_src_face, up_to_point, target_body,
|
||||
dressup_size, face_group, dressup_edge,
|
||||
hole_diameter, hole_depth, hole_through, hole_x, hole_y,
|
||||
thread_radius, thread_pitch, thread_height, thread_depth, thread_internal, thread_x, thread_y,
|
||||
shell_thickness, shell_face,
|
||||
draft_face, draft_angle,
|
||||
revolve_angle, revolve_axis,
|
||||
sweep_path_ref, loft_profile_refs, loft_ruled,
|
||||
pattern_circular, pattern_count, pattern_spacing, pattern_dir, pattern_angle,
|
||||
plane_base, plane_offset, plane_angle_tilt, plane_axis,
|
||||
bool_tool_body, bool_keep_tool, bool_tolerance, bool_target_face, bool_tool_face,
|
||||
cut_offset, cut_flip, cut_keep_upper, cut_keep_lower,
|
||||
brep);
|
||||
}
|
||||
template<class Archive>
|
||||
void load(Archive& ar) {
|
||||
std::string brep;
|
||||
ar(type, name, enabled, shape, plane, width, height, radius,
|
||||
profile, entities, constraints, entity_constraints, imported_regions,
|
||||
import_offset, import_scale_x, import_scale_y, import_on_face, import_face_body,
|
||||
sketch_ref, distance, symmetric, mode, extrude_end, distance2, taper_deg, flip,
|
||||
up_to_face, extrude_src_face, up_to_point, target_body,
|
||||
dressup_size, face_group, dressup_edge,
|
||||
hole_diameter, hole_depth, hole_through, hole_x, hole_y,
|
||||
thread_radius, thread_pitch, thread_height, thread_depth, thread_internal, thread_x, thread_y,
|
||||
shell_thickness, shell_face,
|
||||
draft_face, draft_angle,
|
||||
revolve_angle, revolve_axis,
|
||||
sweep_path_ref, loft_profile_refs, loft_ruled,
|
||||
pattern_circular, pattern_count, pattern_spacing, pattern_dir, pattern_angle,
|
||||
plane_base, plane_offset, plane_angle_tilt, plane_axis,
|
||||
bool_tool_body, bool_keep_tool, bool_tolerance, bool_target_face, bool_tool_face,
|
||||
cut_offset, cut_flip, cut_keep_upper, cut_keep_lower,
|
||||
brep);
|
||||
imported_solid = brep_from_string(brep);
|
||||
}
|
||||
};
|
||||
|
||||
// Serialize a TopoDS_Shape to/from a BRep string for cereal persistence.
|
||||
std::string brep_to_string(const TopoDS_Shape& s);
|
||||
TopoDS_Shape brep_from_string(const std::string& d);
|
||||
|
||||
// One independent solid in a multi-body document.
|
||||
struct CadBody {
|
||||
TopoDS_Shape shape;
|
||||
@@ -279,6 +335,10 @@ public:
|
||||
void clear();
|
||||
bool recompute(); // replay features -> body + display_mesh; false on error
|
||||
|
||||
static constexpr uint32_t SNAPORCA_CAD_RECIPE_VERSION = 1;
|
||||
std::string serialize_recipe() const;
|
||||
bool deserialize_recipe(const std::string& blob);
|
||||
|
||||
// Undo/redo of the feature recipe (Onshape-style Ctrl+Z). The caller marks a
|
||||
// user-action boundary by calling checkpoint() BEFORE the mutation(s) for that
|
||||
// action (add/delete/move/replace, or a direct features edit). undo()/redo() then
|
||||
|
||||
@@ -73,6 +73,7 @@ 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/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";
|
||||
@@ -805,6 +806,17 @@ ModelVolumeType type_from_string(const std::string &s)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (boost::algorithm::iequals(name, 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2317,6 +2329,7 @@ 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);
|
||||
@@ -2387,6 +2400,13 @@ ModelVolumeType type_from_string(const std::string &s)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Adds CAD recipe file ("Metadata/SnapOrca_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!
|
||||
@@ -2919,6 +2939,19 @@ 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 = "";
|
||||
|
||||
@@ -105,6 +105,8 @@ Model& Model::assign_copy(const Model &rhs)
|
||||
this->md_name = rhs.md_name;
|
||||
this->md_value = rhs.md_value;
|
||||
|
||||
this->cad_recipe = rhs.cad_recipe;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -148,6 +150,7 @@ Model& Model::assign_copy(Model &&rhs)
|
||||
rhs.model_info.reset();
|
||||
this->profile_info = rhs.profile_info;
|
||||
rhs.profile_info.reset();
|
||||
this->cad_recipe = std::move(rhs.cad_recipe);
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
@@ -1547,6 +1547,10 @@ public:
|
||||
std::vector<std::string> md_name;
|
||||
std::vector<std::string> md_value;
|
||||
|
||||
// SnapOrca: opaque parametric CAD recipe (CadDocument::serialize_recipe()),
|
||||
// round-tripped through the 3MF as Metadata/SnapOrca_cad.bin. Empty for non-CAD projects.
|
||||
std::string cad_recipe;
|
||||
|
||||
void SetDesigner(std::string designer, std::string designer_user_id) {
|
||||
if (design_info == nullptr) {
|
||||
design_info = std::make_shared<ModelDesignInfo>();
|
||||
|
||||
@@ -2652,6 +2652,29 @@ int DesignPanel::tree_icon_for(CadFeatureType t)
|
||||
void DesignPanel::on_tab_shown()
|
||||
{
|
||||
if (m_viewport) m_viewport->refresh_bed();
|
||||
|
||||
// Rehydrate the parametric model from a freshly loaded project (the 3MF carried the
|
||||
// recipe in Metadata/SnapOrca_cad.bin). Only when nothing is in progress here, so we
|
||||
// never clobber an active design when the user just toggles back to the Design tab.
|
||||
if (m_doc.features.empty()) {
|
||||
if (Plater* plater = wxGetApp().plater()) {
|
||||
const std::string& blob = plater->model().cad_recipe;
|
||||
if (!blob.empty()) load_recipe(blob);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DesignPanel::load_recipe(const std::string& blob)
|
||||
{
|
||||
if (blob.empty()) return;
|
||||
if (!m_doc.deserialize_recipe(blob)) {
|
||||
m_status->SetLabel(_L("Could not restore the CAD model from this project"));
|
||||
return;
|
||||
}
|
||||
m_feature_counter = int(m_doc.features.size());
|
||||
feed_bodies(); // push the restored bodies into the viewport
|
||||
refresh_tree(); // rebuild the feature tree from the restored recipe
|
||||
set_status_ok();
|
||||
}
|
||||
|
||||
void DesignPanel::refresh_tree()
|
||||
@@ -4526,6 +4549,13 @@ void DesignPanel::on_commit()
|
||||
obj_list->load_mesh_object(m_disp_pick_mesh, "Design Body");
|
||||
}
|
||||
|
||||
// Persist the editable parametric recipe alongside the committed meshes so the
|
||||
// saved 3MF reopens with the full feature tree, not just the baked solid. An empty
|
||||
// doc clears it, keeping non-CAD projects clean.
|
||||
if (Plater* plater = wxGetApp().plater())
|
||||
plater->model().cad_recipe =
|
||||
m_doc.features.empty() ? std::string() : m_doc.serialize_recipe();
|
||||
|
||||
if (wxGetApp().mainframe != nullptr)
|
||||
wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
|
||||
}
|
||||
|
||||
@@ -86,6 +86,9 @@ private:
|
||||
// a modal dialog editing the feature's placement transform in place.
|
||||
void on_transform_imported(int feat_idx);
|
||||
void on_commit();
|
||||
// Rehydrate the parametric model from a project's saved recipe (3MF
|
||||
// Metadata/SnapOrca_cad.bin): deserialize -> recompute -> refresh viewport + tree.
|
||||
void load_recipe(const std::string& blob);
|
||||
void refresh_tree();
|
||||
void set_status_ok();
|
||||
|
||||
|
||||
@@ -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