Files
ViewDesignEngine/tests/foundation/test_vde_format.cpp
T

371 lines
13 KiB
C++
Raw Normal View History

#include <gtest/gtest.h>
#include <fstream>
#include <cstdio>
#include <cmath>
#include "vde/foundation/vde_format.h"
#include "vde/brep/modeling.h"
#include "vde/brep/feature_tree.h"
using namespace vde::foundation;
using namespace vde::brep;
using namespace vde::core;
// ═══════════════════════════════════════════════════════════════
// 辅助函数
// ═══════════════════════════════════════════════════════════════
static BrepModel make_test_box() {
return make_box(2.0, 3.0, 4.0);
}
static BrepModel make_test_cylinder() {
return make_cylinder(1.0, 5.0);
}
static void clean(const char* path) {
std::remove(path);
}
static void expect_valid_body(const BrepModel& m) {
EXPECT_GT(m.num_vertices(), 0);
EXPECT_GT(m.num_edges(), 0);
EXPECT_GT(m.num_faces(), 0);
EXPECT_GT(m.num_bodies(), 0);
}
// ═══════════════════════════════════════════════════════════════
// 1. VdeHeader 测试
// ═══════════════════════════════════════════════════════════════
TEST(VdeHeaderTest, DefaultHeader) {
VdeHeader hdr;
EXPECT_EQ(hdr.magic, VDE_MAGIC_FOURCC);
EXPECT_EQ(hdr.version, VDE_FILE_VERSION);
EXPECT_EQ(hdr.flags, 0u);
EXPECT_EQ(hdr.data_offset, 32u);
EXPECT_TRUE(hdr.is_valid());
}
TEST(VdeHeaderTest, EncodeDecodeRoundtrip) {
VdeHeader hdr;
hdr.flags = VDE_FLAG_HAS_ATTRIBUTES | VDE_FLAG_HAS_HISTORY;
hdr.data_offset = 64;
std::vector<uint8_t> buf;
hdr.encode(buf);
EXPECT_EQ(buf.size(), 32u);
VdeHeader parsed = VdeHeader::parse(buf.data(), buf.size());
EXPECT_EQ(parsed.magic, VDE_MAGIC_FOURCC);
EXPECT_EQ(parsed.version, VDE_FILE_VERSION);
EXPECT_EQ(parsed.flags, VDE_FLAG_HAS_ATTRIBUTES | VDE_FLAG_HAS_HISTORY);
EXPECT_EQ(parsed.data_offset, 64u);
EXPECT_TRUE(parsed.is_valid());
}
TEST(VdeHeaderTest, InvalidMagicThrows) {
uint8_t bad[32] = {};
bad[0] = 'B'; bad[1] = 'A'; bad[2] = 'D'; bad[3] = '!';
VdeHeader hdr = VdeHeader::parse(bad, 32);
EXPECT_FALSE(hdr.is_valid());
}
TEST(VdeHeaderTest, TooSmallThrows) {
uint8_t tiny[16] = {};
EXPECT_THROW(VdeHeader::parse(tiny, 16), std::runtime_error);
}
// ═══════════════════════════════════════════════════════════════
// 2. 基本保存与加载
// ═══════════════════════════════════════════════════════════════
TEST(VdeFormat, SaveBoxToVde) {
auto box = make_test_box();
const char* path = "/tmp/test_box.vde";
clean(path);
ASSERT_TRUE(save_vde(box, path));
std::ifstream f(path, std::ios::binary);
ASSERT_TRUE(f.good());
// 检查 magic bytes "VDE1" (little-endian uint32 0x31454456)
char magic[4];
f.read(magic, 4);
EXPECT_EQ(magic[0], 'V');
EXPECT_EQ(magic[1], 'D');
EXPECT_EQ(magic[2], 'E');
EXPECT_EQ(magic[3], '1');
clean(path);
}
TEST(VdeFormat, LoadBoxFromVde) {
auto box = make_test_box();
const char* path = "/tmp/test_load.vde";
clean(path);
ASSERT_TRUE(save_vde(box, path));
auto loaded = load_vde(path);
expect_valid_body(loaded);
// 拓扑计数应一致
EXPECT_EQ(loaded.num_vertices(), box.num_vertices());
EXPECT_EQ(loaded.num_edges(), box.num_edges());
EXPECT_EQ(loaded.num_faces(), box.num_faces());
EXPECT_EQ(loaded.num_bodies(), box.num_bodies());
clean(path);
}
TEST(VdeFormat, CylinderRoundtrip) {
auto cyl = make_test_cylinder();
const char* path = "/tmp/test_cyl.vde";
clean(path);
ASSERT_TRUE(save_vde(cyl, path));
auto loaded = load_vde(path);
expect_valid_body(loaded);
EXPECT_EQ(loaded.num_vertices(), cyl.num_vertices());
EXPECT_EQ(loaded.num_faces(), cyl.num_faces());
EXPECT_EQ(loaded.num_surfaces(), cyl.num_surfaces());
clean(path);
}
TEST(VdeFormat, NonexistentFileThrows) {
EXPECT_THROW(load_vde("/tmp/nonexistent_vde_file.vde"), std::runtime_error);
}
// ═══════════════════════════════════════════════════════════════
// 3. JSON 导出测试
// ═══════════════════════════════════════════════════════════════
TEST(VdeJson, SaveJsonFile) {
auto box = make_test_box();
const char* path = "/tmp/test_box.vde.json";
clean(path);
ASSERT_TRUE(save_vde_json(box, path));
std::ifstream f(path);
ASSERT_TRUE(f.good());
std::string content((std::istreambuf_iterator<char>(f)), {});
// JSON 应包含关键字段
EXPECT_NE(content.find("\"format\": \"VDE1\""), std::string::npos);
EXPECT_NE(content.find("\"vertices\""), std::string::npos);
EXPECT_NE(content.find("\"edges\""), std::string::npos);
EXPECT_NE(content.find("\"faces\""), std::string::npos);
EXPECT_NE(content.find("\"loops\""), std::string::npos);
EXPECT_NE(content.find("\"surfaces\""), std::string::npos);
EXPECT_NE(content.find("\"tolerance\""), std::string::npos);
clean(path);
}
TEST(VdeJson, JsonContainsVertexData) {
auto box = make_test_box();
const char* path = "/tmp/test_json_vertices.vde.json";
clean(path);
save_vde_json(box, path);
std::ifstream f(path);
std::string content((std::istreambuf_iterator<char>(f)), {});
// 正方体应有 8 个顶点
EXPECT_NE(content.find("\"point\""), std::string::npos);
EXPECT_NE(content.find("\"tolerance\""), std::string::npos);
clean(path);
}
// ═══════════════════════════════════════════════════════════════
// 4. 容差测试
// ═══════════════════════════════════════════════════════════════
TEST(VdeTolerance, ToleranceConfigRoundtrip) {
// 设置非默认容差
ToleranceConfig orig;
orig.vertex_merge = 1e-5;
orig.edge_merge = 2e-5;
orig.boolean = 3e-5;
ToleranceConfig::set_global(orig);
auto box = make_test_box();
const char* path = "/tmp/test_tol.vde";
clean(path);
save_vde(box, path);
auto loaded = load_vde(path);
const auto& restored = ToleranceConfig::global();
EXPECT_DOUBLE_EQ(restored.vertex_merge, orig.vertex_merge);
EXPECT_DOUBLE_EQ(restored.edge_merge, orig.edge_merge);
EXPECT_DOUBLE_EQ(restored.boolean, orig.boolean);
// 恢复默认
ToleranceConfig::set_global(ToleranceConfig{});
clean(path);
}
// ═══════════════════════════════════════════════════════════════
// 5. 属性测试
// ═══════════════════════════════════════════════════════════════
TEST(VdeAttributes, SaveWithVertexAttributes) {
auto box = make_test_box();
VdeAttributes attrs;
attrs.vertex_attrs.push_back({0, "corner_0", 0xFF0000FF, "key=value"});
attrs.vertex_attrs.push_back({1, "corner_1", 0x00FF00FF, ""});
const char* path = "/tmp/test_attrs.vde";
clean(path);
ASSERT_TRUE(save_vde(box, path, attrs));
// 重新加载并检查属性
VdeAttributes loaded_attrs;
load_vde_with_attrs(path, loaded_attrs);
ASSERT_EQ(loaded_attrs.vertex_attrs.size(), 2u);
EXPECT_EQ(loaded_attrs.vertex_attrs[0].vertex_id, 0);
EXPECT_EQ(loaded_attrs.vertex_attrs[0].name, "corner_0");
EXPECT_EQ(loaded_attrs.vertex_attrs[0].color_rgba, 0xFF0000FFu);
EXPECT_EQ(loaded_attrs.vertex_attrs[0].custom_data, "key=value");
EXPECT_EQ(loaded_attrs.vertex_attrs[1].vertex_id, 1);
clean(path);
}
TEST(VdeAttributes, SaveWithBodyAttributes) {
auto box = make_test_box();
VdeAttributes attrs;
attrs.body_attrs.push_back({0, "MyPart", "material=steel"});
const char* path = "/tmp/test_body_attrs.vde";
clean(path);
save_vde(box, path, attrs);
VdeAttributes loaded_attrs;
load_vde_with_attrs(path, loaded_attrs);
ASSERT_EQ(loaded_attrs.body_attrs.size(), 1u);
EXPECT_EQ(loaded_attrs.body_attrs[0].name, "MyPart");
EXPECT_EQ(loaded_attrs.body_attrs[0].custom_data, "material=steel");
clean(path);
}
// ═══════════════════════════════════════════════════════════════
// 6. 建模历史测试
// ═══════════════════════════════════════════════════════════════
TEST(VdeHistory, SaveWithFeatureHistory) {
auto box = make_test_box();
std::vector<FeatureNode> history;
FeatureNode root;
root.type = FeatureType::PrimitiveBox;
root.params.values = {2.0, 3.0, 4.0};
root.params.name = "Box";
history.push_back(std::move(root));
const char* path = "/tmp/test_hist.vde";
clean(path);
ASSERT_TRUE(save_vde(box, path, {}, &history));
// 检查文件大小合理
std::ifstream f(path, std::ios::binary | std::ios::ate);
ASSERT_TRUE(f.good());
EXPECT_GT(f.tellg(), std::streampos(64));
// 加载应该成功(历史在 load_vde 中被静默跳过)
auto loaded = load_vde(path);
expect_valid_body(loaded);
clean(path);
}
// ═══════════════════════════════════════════════════════════════
// 7. 序列化缓冲区往返测试
// ═══════════════════════════════════════════════════════════════
TEST(VdeSerialize, BufferRoundtrip) {
auto box = make_test_box();
auto data = serialize_vde(box);
EXPECT_GT(data.size(), 32u);
auto restored = deserialize_vde(data);
expect_valid_body(restored);
EXPECT_EQ(restored.num_vertices(), box.num_vertices());
EXPECT_EQ(restored.num_faces(), box.num_faces());
}
TEST(VdeSerialize, EmptyModelBuffer) {
BrepModel empty;
auto data = serialize_vde(empty);
EXPECT_GE(data.size(), 32u);
auto restored = deserialize_vde(data);
EXPECT_EQ(restored.num_vertices(), 0u);
}
// ═══════════════════════════════════════════════════════════════
// 8. 文件格式验证
// ═══════════════════════════════════════════════════════════════
TEST(VdeFormatValidation, FileHasCorrectMagic) {
auto box = make_test_box();
const char* path = "/tmp/test_magic.vde";
clean(path);
save_vde(box, path);
std::ifstream f(path, std::ios::binary);
char magic[4];
f.read(magic, 4);
EXPECT_EQ(std::string(magic, 4), "VDE1");
clean(path);
}
TEST(VdeFormatValidation, FileHasSections) {
auto box = make_test_box();
const char* path = "/tmp/test_sections.vde";
clean(path);
save_vde(box, path);
std::ifstream f(path, std::ios::binary | std::ios::ate);
auto sz = f.tellg();
f.seekg(0);
std::vector<uint8_t> data(static_cast<size_t>(sz));
f.read(reinterpret_cast<char*>(data.data()), sz);
// 头部后应至少有 拓扑 + 几何 + 容差 三个段
EXPECT_GT(data.size(), 32u + 30u); // header + 3 section headers minimum
clean(path);
}
// ═══════════════════════════════════════════════════════════════
// 9. 边界情况
// ═══════════════════════════════════════════════════════════════
TEST(VdeEdgeCase, VerySmallFileThrows) {
std::vector<uint8_t> tiny(8, 0);
EXPECT_THROW(deserialize_vde(tiny), std::runtime_error);
}
TEST(VdeEdgeCase, CorruptedMagicThrows) {
auto box = make_test_box();
auto data = serialize_vde(box);
// 损坏 magic
data[0] = 'X'; data[1] = 'X'; data[2] = 'X'; data[3] = 'X';
EXPECT_THROW(deserialize_vde(data), std::runtime_error);
}