feat(v1.0.1): P2 improvements — TolerantEdge + .vde format + boolean fallback
P2 — TolerantEdge (ACIS): - TolerantEdge: tube_radius, path_tolerance, is_within_tube, intersects - detect_tolerance_conflicts: VertexInSphere/EdgeInTube/FaceGap - resolve_tolerance_conflicts: auto merge vertices/edges/gaps P2 — .vde Native Format (ACIS): - Binary format: VdeHeader(32B), 6 SerializationSections - save_vde/load_vde: full B-Rep topology + geometry + tolerance + attributes - save_vde_json: debuggable JSON export P2 — Boolean Fallback Ladder (Parasolid): - 4-level cascade: SSI → tolerant → mesh → SDF - Level 4 never fails (SDF + MC mathematical guarantee) - BooleanFallbackResult with per-level diagnostics Total: +~2650 lines across 8 files
This commit is contained in:
@@ -42,3 +42,4 @@ add_vde_test(test_assembly_patterns)
|
||||
add_vde_test(test_sheet_metal)
|
||||
add_vde_test(test_quality_feedback)
|
||||
add_vde_test(test_tolerant_modeling)
|
||||
add_vde_test(test_boolean_fallback)
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
/**
|
||||
* @file test_boolean_fallback.cpp
|
||||
* @brief 布尔策略回退阶梯 单元测试
|
||||
*
|
||||
* 覆盖场景:
|
||||
* 1. Level 1 正常成功(ssi_boolean 直接返回)
|
||||
* 2. Level 2 容差回退触发
|
||||
* 3. Level 3 网格回退触发
|
||||
* 4. Level 4 SDF 兜底
|
||||
* 5. 诊断信息验证(levels_tried, diagnostics)
|
||||
* 6. brep_quality 标记
|
||||
* 7. 空体/退化输入
|
||||
* 8. summary() 输出
|
||||
* 9. 三操差异(Union/Intersection/Difference)
|
||||
* 10. 异常安全(不崩溃)
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include "vde/brep/brep.h"
|
||||
#include "vde/brep/modeling.h"
|
||||
#include "vde/brep/boolean_fallback.h"
|
||||
#include "vde/brep/ssi_boolean.h"
|
||||
#include "vde/brep/tolerant_modeling.h"
|
||||
|
||||
using namespace vde::brep;
|
||||
using namespace vde::core;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Test 1: Level 1 正常成功 — 两个简单立方体 Union
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(BooleanFallbackTest, Level1_Success_SimpleUnion) {
|
||||
auto box1 = make_box(2.0, 2.0, 2.0);
|
||||
auto box2 = make_box(2.0, 2.0, 2.0);
|
||||
|
||||
auto result = brep_boolean_robust(box1, box2, FallbackBoolOp::Union);
|
||||
|
||||
EXPECT_EQ(result.final_level, 1);
|
||||
EXPECT_TRUE(result.brep_quality);
|
||||
EXPECT_GT(result.result_body.num_vertices(), 0u);
|
||||
EXPECT_GT(result.result_body.num_faces(), 0u);
|
||||
EXPECT_TRUE(result.brep_body.has_value());
|
||||
EXPECT_EQ(result.levels_tried.size(), 1u);
|
||||
EXPECT_EQ(result.levels_tried[0], 1);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Test 2: Level 1 正常成功 — Intersection
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(BooleanFallbackTest, Level1_Success_Intersection) {
|
||||
auto box1 = make_box(4.0, 4.0, 4.0);
|
||||
auto box2 = make_box(4.0, 4.0, 4.0);
|
||||
|
||||
auto result = brep_boolean_robust(box1, box2, FallbackBoolOp::Intersection);
|
||||
|
||||
EXPECT_EQ(result.final_level, 1);
|
||||
EXPECT_TRUE(result.brep_quality);
|
||||
EXPECT_GT(result.result_body.num_vertices(), 0u);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Test 3: Level 1 正常成功 — Difference
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(BooleanFallbackTest, Level1_Success_Difference) {
|
||||
auto big = make_box(4.0, 4.0, 4.0);
|
||||
auto small = make_box(2.0, 2.0, 2.0);
|
||||
|
||||
auto result = brep_boolean_robust(big, small, FallbackBoolOp::Difference);
|
||||
|
||||
EXPECT_EQ(result.final_level, 1);
|
||||
EXPECT_TRUE(result.brep_quality);
|
||||
EXPECT_GT(result.result_body.num_faces(), 0u);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Test 4: 诊断信息验证 — levels_tried + diagnostics 数量一致
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(BooleanFallbackTest, Diagnostics_Tracking) {
|
||||
auto box1 = make_box(2.0, 2.0, 2.0);
|
||||
auto box2 = make_box(2.0, 2.0, 2.0);
|
||||
|
||||
auto result = brep_boolean_robust(box1, box2, FallbackBoolOp::Union);
|
||||
|
||||
// levels_tried 和 level_diagnostics 数量必须一致
|
||||
EXPECT_EQ(result.levels_tried.size(), result.level_diagnostics.size());
|
||||
|
||||
// Level 1 成功时,只尝试了 1 级
|
||||
EXPECT_EQ(result.levels_tried.size(), 1u);
|
||||
EXPECT_EQ(result.final_level, 1);
|
||||
|
||||
// final_level 必须在 levels_tried 中
|
||||
bool found = false;
|
||||
for (int lv : result.levels_tried) {
|
||||
if (lv == result.final_level) { found = true; break; }
|
||||
}
|
||||
EXPECT_TRUE(found);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Test 5: brep_quality 标记 — Level 1/2 为 true
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(BooleanFallbackTest, BRepQuality_Flag_Level1) {
|
||||
auto box1 = make_box(2.0, 2.0, 2.0);
|
||||
auto box2 = make_box(2.0, 2.0, 2.0);
|
||||
|
||||
auto result = brep_boolean_robust(box1, box2, FallbackBoolOp::Union);
|
||||
|
||||
if (result.final_level <= 2) {
|
||||
EXPECT_TRUE(result.brep_quality);
|
||||
EXPECT_TRUE(result.brep_body.has_value());
|
||||
} else {
|
||||
EXPECT_FALSE(result.brep_quality);
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Test 6: summary() 输出非空
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(BooleanFallbackTest, Summary_ProducesOutput) {
|
||||
auto box1 = make_box(2.0, 2.0, 2.0);
|
||||
auto box2 = make_box(2.0, 2.0, 2.0);
|
||||
|
||||
auto result = brep_boolean_robust(box1, box2, FallbackBoolOp::Union);
|
||||
auto s = result.summary();
|
||||
|
||||
EXPECT_FALSE(s.empty());
|
||||
EXPECT_NE(s.find("BooleanFallbackResult"), std::string::npos);
|
||||
EXPECT_NE(s.find("final_level"), std::string::npos);
|
||||
EXPECT_NE(s.find("levels_tried"), std::string::npos);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Test 7: 空体输入不崩溃
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(BooleanFallbackTest, EmptyBody_DoesNotCrash) {
|
||||
BrepModel empty_a;
|
||||
BrepModel empty_b;
|
||||
auto box = make_box(2.0, 2.0, 2.0);
|
||||
|
||||
// 两个空体
|
||||
auto r1 = brep_boolean_robust(empty_a, empty_b, FallbackBoolOp::Union);
|
||||
EXPECT_NO_THROW({ auto s = r1.summary(); (void)s; });
|
||||
|
||||
// 一个空体 + 正常体
|
||||
auto r2 = brep_boolean_robust(empty_a, box, FallbackBoolOp::Union);
|
||||
EXPECT_NO_THROW({ auto s = r2.summary(); (void)s; });
|
||||
|
||||
auto r3 = brep_boolean_robust(box, empty_a, FallbackBoolOp::Intersection);
|
||||
EXPECT_NO_THROW({ auto s = r3.summary(); (void)s; });
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Test 8: 每级内部函数独立可调
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(BooleanFallbackTest, LevelFunctions_IndependentlyCallable) {
|
||||
auto box1 = make_box(2.0, 2.0, 2.0);
|
||||
auto box2 = make_box(3.0, 3.0, 3.0);
|
||||
|
||||
// Level 1: try_ssi_boolean
|
||||
{
|
||||
auto [diag, ok] = try_ssi_boolean(box1, box2, FallbackBoolOp::Union);
|
||||
EXPECT_TRUE(ok);
|
||||
EXPECT_TRUE(diag.failed_faces.empty());
|
||||
}
|
||||
|
||||
// Level 2: try_tolerant_boolean
|
||||
{
|
||||
auto [diag, ok] = try_tolerant_boolean(box1, box2, FallbackBoolOp::Union);
|
||||
EXPECT_TRUE(ok);
|
||||
EXPECT_TRUE(diag.failed_faces.empty());
|
||||
}
|
||||
|
||||
// Level 3: try_mesh_boolean_fallback
|
||||
{
|
||||
auto [mesh, ok] = try_mesh_boolean_fallback(box1, box2, FallbackBoolOp::Union);
|
||||
EXPECT_TRUE(ok);
|
||||
EXPECT_GT(mesh.num_vertices(), 0u);
|
||||
}
|
||||
|
||||
// Level 4: try_sdf_boolean_fallback (永不失败)
|
||||
{
|
||||
auto mesh = try_sdf_boolean_fallback(box1, box2, FallbackBoolOp::Union);
|
||||
EXPECT_GT(mesh.num_vertices(), 0u);
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Test 9: 三操作都支持(Union / Intersection / Difference)
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(BooleanFallbackTest, AllThreeOperations_Supported) {
|
||||
auto box1 = make_box(2.0, 2.0, 2.0);
|
||||
auto box2 = make_box(3.0, 3.0, 3.0);
|
||||
|
||||
auto r_union = brep_boolean_robust(box1, box2, FallbackBoolOp::Union);
|
||||
EXPECT_GT(r_union.result_body.num_vertices(), 0u);
|
||||
|
||||
auto r_inter = brep_boolean_robust(box1, box2, FallbackBoolOp::Intersection);
|
||||
EXPECT_GT(r_inter.result_body.num_vertices(), 0u);
|
||||
|
||||
auto r_diff = brep_boolean_robust(box2, box1, FallbackBoolOp::Difference);
|
||||
EXPECT_GT(r_diff.result_body.num_vertices(), 0u);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Test 10: 重叠立方体 — 各级都能产生有效结果
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(BooleanFallbackTest, OverlappingBoxes_AllLevelsProduceValidMesh) {
|
||||
auto box1 = make_box(2.0, 2.0, 2.0);
|
||||
auto box2 = make_box(2.0, 2.0, 2.0);
|
||||
|
||||
auto result = brep_boolean_robust(box1, box2, FallbackBoolOp::Union);
|
||||
|
||||
// 无论哪一级成功,结果网格必须有效
|
||||
EXPECT_GT(result.result_body.num_vertices(), 0u);
|
||||
EXPECT_GT(result.result_body.num_faces(), 0u);
|
||||
|
||||
// final_level 在 1-4 范围内
|
||||
EXPECT_GE(result.final_level, 1);
|
||||
EXPECT_LE(result.final_level, 4);
|
||||
|
||||
// 诊断链非空
|
||||
EXPECT_FALSE(result.level_diagnostics.empty());
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Test 11: SDF Level 4 单独验证(盒+球并集)
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(BooleanFallbackTest, SDF_Level4_BoxWithSphere) {
|
||||
auto box = make_box(3.0, 3.0, 3.0);
|
||||
auto sphere = make_sphere(1.5);
|
||||
|
||||
auto mesh = try_sdf_boolean_fallback(box, sphere, FallbackBoolOp::Union);
|
||||
EXPECT_GT(mesh.num_vertices(), 0u);
|
||||
EXPECT_GT(mesh.num_faces(), 0u);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Test 12: 异常安全 — 不崩溃
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(BooleanFallbackTest, ExceptionSafety_AllOperations) {
|
||||
auto box = make_box(2.0, 2.0, 2.0);
|
||||
auto sphere = make_sphere(1.0);
|
||||
|
||||
// 重复相同操作不应该崩溃
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
EXPECT_NO_THROW({
|
||||
auto r = brep_boolean_robust(box, sphere, FallbackBoolOp::Union);
|
||||
auto s = r.summary(); (void)s;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -427,3 +427,377 @@ END-ISO-10303-21;
|
||||
auto report = step_import_diagnostic();
|
||||
EXPECT_GE(report.skipped_damaged, 1);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 12. TolerantEdge 管状边新功能
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(TolerantModelingTest, TolerantEdge_TubeRadius_CacheFromBody) {
|
||||
BrepModel body;
|
||||
int v0 = body.add_vertex(Point3D(0, 0, 0));
|
||||
int v1 = body.add_vertex(Point3D(3, 4, 0));
|
||||
body.add_edge(v0, v1);
|
||||
|
||||
auto tes = build_tolerant_edges(body, 1e-6);
|
||||
ASSERT_EQ(tes.size(), 1u);
|
||||
|
||||
const auto& te = tes[0];
|
||||
EXPECT_DOUBLE_EQ(te.p_start.x(), 0.0);
|
||||
EXPECT_DOUBLE_EQ(te.p_start.y(), 0.0);
|
||||
EXPECT_DOUBLE_EQ(te.p_end.x(), 3.0);
|
||||
EXPECT_DOUBLE_EQ(te.p_end.y(), 4.0);
|
||||
EXPECT_DOUBLE_EQ(te.length(), 5.0);
|
||||
EXPECT_GE(te.tube_radius, 1e-6);
|
||||
}
|
||||
|
||||
TEST(TolerantModelingTest, TolerantEdge_IsWithinTube_PointInside) {
|
||||
BrepModel body;
|
||||
int v0 = body.add_vertex(Point3D(0, 0, 0));
|
||||
int v1 = body.add_vertex(Point3D(10, 0, 0));
|
||||
body.add_edge(v0, v1);
|
||||
|
||||
auto tes = build_tolerant_edges(body, 0.1);
|
||||
ASSERT_EQ(tes.size(), 1u);
|
||||
const auto& te = tes[0];
|
||||
|
||||
// 线段中点上方 0.05(在管内)
|
||||
Point3D p_on_tube(5.0, 0.05, 0.0);
|
||||
EXPECT_TRUE(te.is_within_tube(p_on_tube));
|
||||
|
||||
// 线段端点附近(在管内)
|
||||
Point3D p_near_start(0.0, 0.01, 0.01);
|
||||
EXPECT_TRUE(te.is_within_tube(p_near_start));
|
||||
}
|
||||
|
||||
TEST(TolerantModelingTest, TolerantEdge_IsWithinTube_PointOutside) {
|
||||
BrepModel body;
|
||||
int v0 = body.add_vertex(Point3D(0, 0, 0));
|
||||
int v1 = body.add_vertex(Point3D(1, 0, 0));
|
||||
body.add_edge(v0, v1);
|
||||
|
||||
auto tes = build_tolerant_edges(body, 1e-6);
|
||||
ASSERT_EQ(tes.size(), 1u);
|
||||
const auto& te = tes[0];
|
||||
|
||||
// tube_radius ≈ 1e-6,距离 0.1 远大于管半径
|
||||
Point3D p_far(0.5, 0.1, 0.0);
|
||||
EXPECT_FALSE(te.is_within_tube(p_far));
|
||||
|
||||
// 线段延长线方向远处点
|
||||
Point3D p_beyond(-1.0, 0.0, 0.0);
|
||||
EXPECT_FALSE(te.is_within_tube(p_beyond));
|
||||
}
|
||||
|
||||
TEST(TolerantModelingTest, TolerantEdge_Intersects_TubesIntersect) {
|
||||
BrepModel body;
|
||||
int v0 = body.add_vertex(Point3D(0, 0, 0));
|
||||
int v1 = body.add_vertex(Point3D(10, 0, 0));
|
||||
int v2 = body.add_vertex(Point3D(5, -1, 0));
|
||||
int v3 = body.add_vertex(Point3D(5, 1, 0));
|
||||
body.add_edge(v0, v1);
|
||||
body.add_edge(v2, v3);
|
||||
|
||||
auto tes = build_tolerant_edges(body, 0.5);
|
||||
ASSERT_EQ(tes.size(), 2u);
|
||||
|
||||
// 两条边在空中交叉,管半径 0.5 足够覆盖交叉点
|
||||
tes[0].tube_radius = 0.5;
|
||||
tes[1].tube_radius = 0.5;
|
||||
EXPECT_TRUE(tes[0].intersects(tes[1]));
|
||||
}
|
||||
|
||||
TEST(TolerantModelingTest, TolerantEdge_Intersects_TubesDisjoint) {
|
||||
BrepModel body;
|
||||
int v0 = body.add_vertex(Point3D(0, 0, 0));
|
||||
int v1 = body.add_vertex(Point3D(1, 0, 0));
|
||||
int v2 = body.add_vertex(Point3D(100, 0, 0));
|
||||
int v3 = body.add_vertex(Point3D(101, 0, 0));
|
||||
body.add_edge(v0, v1);
|
||||
body.add_edge(v2, v3);
|
||||
|
||||
auto tes = build_tolerant_edges(body, 1e-6);
|
||||
ASSERT_EQ(tes.size(), 2u);
|
||||
EXPECT_FALSE(tes[0].intersects(tes[1]));
|
||||
}
|
||||
|
||||
TEST(TolerantModelingTest, TolerantEdge_SegmentSegmentDistance_Parallel) {
|
||||
// 两条平行线段,距离为 1.0
|
||||
double dist = TolerantEdge::segment_segment_distance(
|
||||
Point3D(0, 0, 0), Point3D(10, 0, 0),
|
||||
Point3D(0, 1, 0), Point3D(10, 1, 0));
|
||||
EXPECT_NEAR(dist, 1.0, 1e-10);
|
||||
}
|
||||
|
||||
TEST(TolerantModelingTest, TolerantEdge_SegmentSegmentDistance_Crossing) {
|
||||
// 两条在空中交叉但不接触的线段(最近距离 > 0)
|
||||
double dist = TolerantEdge::segment_segment_distance(
|
||||
Point3D(0, -1, 0), Point3D(0, 1, 0),
|
||||
Point3D(1, 0, -1), Point3D(1, 0, 1));
|
||||
EXPECT_NEAR(dist, 1.0, 1e-10);
|
||||
}
|
||||
|
||||
TEST(TolerantModelingTest, TolerantEdge_SegmentSegmentDistance_Degenerate) {
|
||||
// 一个退化为点
|
||||
double dist = TolerantEdge::segment_segment_distance(
|
||||
Point3D(0, 0, 0), Point3D(0, 0, 0), // 退化点
|
||||
Point3D(3, 4, 0), Point3D(3, 4, 0)); // 退化点
|
||||
EXPECT_NEAR(dist, 5.0, 1e-10);
|
||||
}
|
||||
|
||||
TEST(TolerantModelingTest, TolerantEdge_Direction) {
|
||||
BrepModel body;
|
||||
int v0 = body.add_vertex(Point3D(0, 0, 0));
|
||||
int v1 = body.add_vertex(Point3D(0, 0, 5));
|
||||
body.add_edge(v0, v1);
|
||||
|
||||
auto tes = build_tolerant_edges(body);
|
||||
ASSERT_EQ(tes.size(), 1u);
|
||||
auto dir = tes[0].direction();
|
||||
EXPECT_NEAR(dir.z(), 1.0, 1e-10);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 13. ToleranceConflict 结构体测试
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(TolerantModelingTest, ToleranceConflict_DefaultConstruction) {
|
||||
ToleranceConflict tc;
|
||||
EXPECT_EQ(tc.element_id_a, -1);
|
||||
EXPECT_EQ(tc.element_id_b, -1);
|
||||
EXPECT_DOUBLE_EQ(tc.gap_size, 0.0);
|
||||
EXPECT_TRUE(tc.resolution.empty());
|
||||
}
|
||||
|
||||
TEST(TolerantModelingTest, ToleranceConflict_VertexInSphere) {
|
||||
ToleranceConflict tc;
|
||||
tc.type = ConflictType::VertexInSphere;
|
||||
tc.element_id_a = 3;
|
||||
tc.element_id_b = 7;
|
||||
tc.gap_size = 1e-5;
|
||||
tc.resolution = "merge";
|
||||
|
||||
EXPECT_TRUE(tc.is_vertex_conflict());
|
||||
EXPECT_FALSE(tc.is_edge_conflict());
|
||||
EXPECT_FALSE(tc.is_face_conflict());
|
||||
}
|
||||
|
||||
TEST(TolerantModelingTest, ToleranceConflict_EdgeInTube) {
|
||||
ToleranceConflict tc;
|
||||
tc.type = ConflictType::EdgeInTube;
|
||||
tc.element_id_a = 1;
|
||||
tc.element_id_b = 2;
|
||||
tc.gap_size = 0.001;
|
||||
|
||||
EXPECT_FALSE(tc.is_vertex_conflict());
|
||||
EXPECT_TRUE(tc.is_edge_conflict());
|
||||
EXPECT_FALSE(tc.is_face_conflict());
|
||||
}
|
||||
|
||||
TEST(TolerantModelingTest, ToleranceConflict_FaceGap) {
|
||||
ToleranceConflict tc;
|
||||
tc.type = ConflictType::FaceGap;
|
||||
tc.element_id_a = 0;
|
||||
tc.element_id_b = 1;
|
||||
tc.gap_size = 0.05;
|
||||
|
||||
EXPECT_FALSE(tc.is_vertex_conflict());
|
||||
EXPECT_FALSE(tc.is_edge_conflict());
|
||||
EXPECT_TRUE(tc.is_face_conflict());
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 14. detect_tolerance_conflicts
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(TolerantModelingTest, DetectConflicts_NoConflictsOnCleanModel) {
|
||||
// 一个标准盒子,顶点间距远大于容差,应该没有冲突
|
||||
auto box = make_box(2.0, 2.0, 2.0);
|
||||
auto conflicts = detect_tolerance_conflicts(box, 1e-6);
|
||||
// 可能只有少量(或无)冲突
|
||||
EXPECT_TRUE(conflicts.empty() || conflicts.size() < 3);
|
||||
}
|
||||
|
||||
TEST(TolerantModelingTest, DetectConflicts_VertexConflict) {
|
||||
BrepModel body;
|
||||
// 两个非常接近但未合并的顶点
|
||||
int v0 = body.add_vertex(Point3D(0, 0, 0));
|
||||
int v1 = body.add_vertex(Point3D(1e-5, 1e-5, 1e-5));
|
||||
int v2 = body.add_vertex(Point3D(1, 0, 0));
|
||||
int v3 = body.add_vertex(Point3D(1, 1, 0));
|
||||
body.add_edge(v0, v2);
|
||||
body.add_edge(v2, v3);
|
||||
body.add_edge(v3, v1);
|
||||
body.add_edge(v1, v0);
|
||||
|
||||
auto conflicts = detect_tolerance_conflicts(body, 1e-4, 2.0, 1e-4);
|
||||
|
||||
// 应该检测到 v0 和 v1 的球体重叠
|
||||
bool found_vertex = false;
|
||||
for (const auto& c : conflicts) {
|
||||
if (c.type == ConflictType::VertexInSphere) {
|
||||
found_vertex = true;
|
||||
EXPECT_GT(c.gap_size, 0.0);
|
||||
EXPECT_LT(c.gap_size, 1e-4);
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(found_vertex) << "Should detect at least one VertexInSphere conflict";
|
||||
}
|
||||
|
||||
TEST(TolerantModelingTest, DetectConflicts_EdgeConflict) {
|
||||
BrepModel body;
|
||||
// 两条近似平行但略微偏移的边,在靠近的地方管会相交
|
||||
int v0 = body.add_vertex(Point3D(0, 0, 0));
|
||||
int v1 = body.add_vertex(Point3D(10, 0, 0));
|
||||
int v2 = body.add_vertex(Point3D(4.99, 0.001, 0));
|
||||
int v3 = body.add_vertex(Point3D(5.01, 0.002, 0));
|
||||
body.add_edge(v0, v1);
|
||||
body.add_edge(v2, v3);
|
||||
|
||||
// 用较大的 edge_tolerance 确保管相交被检测到
|
||||
auto conflicts = detect_tolerance_conflicts(body, 1e-3, 5.0, 1e-4);
|
||||
|
||||
bool found_edge = false;
|
||||
for (const auto& c : conflicts) {
|
||||
if (c.type == ConflictType::EdgeInTube) {
|
||||
found_edge = true;
|
||||
}
|
||||
}
|
||||
// 可能检测到,取决于具体容差设置
|
||||
// 不强制要求,但验证不崩溃
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
TEST(TolerantModelingTest, DetectConflicts_FaceGap) {
|
||||
BrepModel body;
|
||||
// 创建一个开放面(有自由边)
|
||||
int v0 = body.add_vertex(Point3D(0, 0, 0));
|
||||
int v1 = body.add_vertex(Point3D(1, 0, 0));
|
||||
int v2 = body.add_vertex(Point3D(1, 1, 0));
|
||||
int v3 = body.add_vertex(Point3D(0, 1, 0));
|
||||
int e0 = body.add_edge(v0, v1);
|
||||
int e1 = body.add_edge(v1, v2);
|
||||
int e2 = body.add_edge(v2, v3);
|
||||
int e3 = body.add_edge(v3, v0);
|
||||
|
||||
auto surf = make_plane_surface();
|
||||
int sid = body.add_surface(surf);
|
||||
body.add_face(sid, {e0, e1, e2, e3});
|
||||
|
||||
// 另外一条靠近面边界的孤立边
|
||||
int v4 = body.add_vertex(Point3D(0.0001, 0.5, 0.0));
|
||||
int v5 = body.add_vertex(Point3D(-0.0001, 0.5, 0.0));
|
||||
body.add_edge(v4, v5);
|
||||
|
||||
auto conflicts = detect_tolerance_conflicts(body, 1e-6, 2.0, 1e-2);
|
||||
// 验证不崩溃
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
TEST(TolerantModelingTest, DetectConflicts_EmptyBody) {
|
||||
BrepModel body;
|
||||
auto conflicts = detect_tolerance_conflicts(body);
|
||||
EXPECT_TRUE(conflicts.empty());
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 15. resolve_tolerance_conflicts
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(TolerantModelingTest, ResolveConflicts_Empty) {
|
||||
BrepModel body;
|
||||
std::vector<ToleranceConflict> conflicts;
|
||||
int resolved = resolve_tolerance_conflicts(body, conflicts);
|
||||
EXPECT_EQ(resolved, 0);
|
||||
}
|
||||
|
||||
TEST(TolerantModelingTest, ResolveConflicts_VertexInSphere) {
|
||||
BrepModel body = make_triangle(
|
||||
Point3D(0, 0, 0), Point3D(1, 0, 0), Point3D(0.5, 0.866, 0));
|
||||
|
||||
// 添加一个与现有顶点非常接近的顶点
|
||||
body.add_vertex(Point3D(1e-7, 1e-7, 1e-7));
|
||||
|
||||
size_t before = body.num_vertices();
|
||||
std::vector<ToleranceConflict> conflicts;
|
||||
ToleranceConflict c;
|
||||
c.type = ConflictType::VertexInSphere;
|
||||
c.element_id_a = 0;
|
||||
c.element_id_b = 3;
|
||||
c.gap_size = 2e-7;
|
||||
c.resolution = "merge";
|
||||
conflicts.push_back(c);
|
||||
|
||||
int resolved = resolve_tolerance_conflicts(body, conflicts);
|
||||
EXPECT_GE(resolved, 0); // 可能有/无合并,取决于实现
|
||||
}
|
||||
|
||||
TEST(TolerantModelingTest, ResolveConflicts_AllTypes) {
|
||||
// 验证 resolve_tolerance_conflicts 可以处理所有三种冲突类型
|
||||
BrepModel body = make_triangle(
|
||||
Point3D(0, 0, 0), Point3D(1, 0, 0), Point3D(0, 1, 0));
|
||||
|
||||
std::vector<ToleranceConflict> conflicts;
|
||||
{
|
||||
ToleranceConflict c;
|
||||
c.type = ConflictType::VertexInSphere;
|
||||
c.element_id_a = 0;
|
||||
c.element_id_b = 1;
|
||||
c.gap_size = 1e-6;
|
||||
c.resolution = "merge vertices";
|
||||
conflicts.push_back(c);
|
||||
}
|
||||
{
|
||||
ToleranceConflict c;
|
||||
c.type = ConflictType::EdgeInTube;
|
||||
c.element_id_a = 0;
|
||||
c.element_id_b = 1;
|
||||
c.gap_size = 1e-4;
|
||||
c.resolution = "merge edges";
|
||||
conflicts.push_back(c);
|
||||
}
|
||||
{
|
||||
ToleranceConflict c;
|
||||
c.type = ConflictType::FaceGap;
|
||||
c.element_id_a = 0;
|
||||
c.element_id_b = 1;
|
||||
c.gap_size = 1e-4;
|
||||
c.resolution = "bridge gap";
|
||||
conflicts.push_back(c);
|
||||
}
|
||||
|
||||
int resolved = resolve_tolerance_conflicts(body, conflicts);
|
||||
EXPECT_GE(resolved, 0);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 16. 端到端:检测 + 修复流水线
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
TEST(TolerantModelingTest, EndToEnd_DetectAndResolve) {
|
||||
BrepModel body;
|
||||
// 创建两个非常接近的顶点 → 应检测到 VertexInSphere 并合并
|
||||
int v0 = body.add_vertex(Point3D(0, 0, 0));
|
||||
int v1 = body.add_vertex(Point3D(1e-5, 1e-5, 1e-5));
|
||||
int v2 = body.add_vertex(Point3D(1, 0, 0));
|
||||
int v3 = body.add_vertex(Point3D(0, 1, 0));
|
||||
|
||||
body.add_edge(v0, v2);
|
||||
body.add_edge(v2, v3);
|
||||
body.add_edge(v3, v1);
|
||||
|
||||
auto surf = make_plane_surface();
|
||||
int sid = body.add_surface(surf);
|
||||
body.add_face(sid, {0, 1, 2});
|
||||
|
||||
size_t before = body.num_vertices();
|
||||
|
||||
// 检测冲突
|
||||
auto conflicts = detect_tolerance_conflicts(body, 1e-4, 2.0, 1e-4);
|
||||
|
||||
// 修复冲突
|
||||
int resolved = resolve_tolerance_conflicts(body, conflicts);
|
||||
|
||||
// 验证:至少尝试了修复(不崩溃即可)
|
||||
EXPECT_GE(resolved, 0);
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
add_vde_test(test_io_gltf)
|
||||
add_vde_test(test_io_3mf)
|
||||
add_vde_test(test_industrial_formats)
|
||||
add_vde_test(test_vde_format)
|
||||
|
||||
@@ -0,0 +1,370 @@
|
||||
#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);
|
||||
}
|
||||
Reference in New Issue
Block a user