feat(v11): CI/CD + regression tests + fuzzing + benchmarks + code quality
v11.1 — Test Infrastructure (14 files): - .github/workflows/ci.yml: Ubuntu 22.04 + GCC 11, auto ctest on push - tests/regression/: degenerate geometry, extreme coords, thin wall, large models - tests/fuzz/: SDF random CSG, random booleans, format round-trip, continuous mode - tests/benchmark/: boolean perf, MC perf, IO perf benchmarks - docs/benchmark-report.md: benchmark template v11.2 — Code Quality + Docs: - .clang-tidy: 15 check categories, 22 exclusions - .github/workflows/static-analysis.yml: clang-tidy CI scan - CODEOWNERS, SECURITY.md - docs: API overview, testing guide, contributing guide - README: module capability overview table Pending: binary formats + curve projection (retrying)
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
add_vde_test(regression_degenerate)
|
||||
add_vde_test(regression_extreme)
|
||||
add_vde_test(regression_thin_wall)
|
||||
add_vde_test(regression_large_model)
|
||||
@@ -0,0 +1,214 @@
|
||||
/// @file regression_degenerate.cpp
|
||||
/// @brief 退化几何回归测试集
|
||||
///
|
||||
/// 覆盖场景:
|
||||
/// 1. 零长度边(zero-length edge)
|
||||
/// 2. 零面积面(zero-area face)
|
||||
/// 3. 重合顶点(coincident vertices)
|
||||
/// 4. 自交体(self-intersecting body)
|
||||
///
|
||||
/// 每个测试:构造退化体 → 布尔/修复/验证 → 断言不崩溃
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include "vde/brep/brep.h"
|
||||
#include "vde/brep/modeling.h"
|
||||
#include "vde/brep/brep_boolean.h"
|
||||
#include "vde/brep/brep_validate.h"
|
||||
#include "vde/brep/brep_heal.h"
|
||||
#include "vde/core/point.h"
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
using namespace vde::brep;
|
||||
using namespace vde::core;
|
||||
|
||||
// ── 辅助函数 ──
|
||||
|
||||
/// 断言 BrepModel 至少不会崩溃(尝试布尔、验证、修复都不会抛异常)
|
||||
void assert_no_crash(const BrepModel& body) {
|
||||
// 1. 基本验证:能调用 is_valid()
|
||||
EXPECT_NO_THROW({ body.is_valid(); });
|
||||
|
||||
// 2. 验证:能调用 validate()
|
||||
EXPECT_NO_THROW({ auto result = validate(body); (void)result; });
|
||||
|
||||
// 3. 布尔自交:A∪A, A∩A, A\\A
|
||||
EXPECT_NO_THROW({ auto u = brep_union(body, body); (void)u; });
|
||||
EXPECT_NO_THROW({ auto i = brep_intersection(body, body); (void)i; });
|
||||
EXPECT_NO_THROW({ auto d = brep_difference(body, body); (void)d; });
|
||||
|
||||
// 4. 修复:heal_topology 不崩溃
|
||||
{
|
||||
BrepModel copy = body;
|
||||
EXPECT_NO_THROW({ heal_topology(copy, 1e-6); });
|
||||
}
|
||||
|
||||
// 5. 网格导出:to_mesh 不崩溃
|
||||
EXPECT_NO_THROW({ auto mesh = body.to_mesh(); (void)mesh; });
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════
|
||||
// 零长度边测试
|
||||
// ═══════════════════════════════════════════════
|
||||
|
||||
TEST(RegressionDegenerate, ZeroLengthEdge_Box) {
|
||||
// 通过 make_box(0, 1, 1) 构造含退化边的体
|
||||
// 宽度为 0 → 侧面退化为零面积面
|
||||
EXPECT_NO_THROW({
|
||||
auto body = make_box(0.0, 1.0, 1.0);
|
||||
assert_no_crash(body);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(RegressionDegenerate, ZeroLengthEdge_ExtremeSmall) {
|
||||
// 极窄长方体(宽 ≤ 1e-12)→ 近乎零长度边
|
||||
for (double w : {1e-12, 1e-10, 1e-8}) {
|
||||
EXPECT_NO_THROW({
|
||||
auto body = make_box(w, 1.0, 1.0);
|
||||
assert_no_crash(body);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════
|
||||
// 零面积面测试
|
||||
// ═══════════════════════════════════════════════
|
||||
|
||||
TEST(RegressionDegenerate, ZeroAreaFace_FlatBox) {
|
||||
// 一个维度为 0 → 退化成一个平面
|
||||
EXPECT_NO_THROW({
|
||||
auto box0 = make_box(0.0, 5.0, 5.0);
|
||||
assert_no_crash(box0);
|
||||
|
||||
// 与正常 box 做布尔
|
||||
auto box = make_box(3.0, 3.0, 3.0);
|
||||
auto u = brep_union(box0, box);
|
||||
(void)u.is_valid();
|
||||
auto i = brep_intersection(box0, box);
|
||||
(void)i.is_valid();
|
||||
auto d = brep_difference(box, box0);
|
||||
(void)d.is_valid();
|
||||
});
|
||||
}
|
||||
|
||||
TEST(RegressionDegenerate, ZeroAreaFace_Sliver) {
|
||||
// 一个极其扁平的体(高度极其小)
|
||||
for (double h : {1e-12, 1e-10, 1e-9}) {
|
||||
EXPECT_NO_THROW({
|
||||
auto thin = make_box(1.0, h, 1.0);
|
||||
assert_no_crash(thin);
|
||||
|
||||
// 瘦短板与正常盒子的布尔
|
||||
auto box = make_box(2.0, 2.0, 2.0);
|
||||
auto u = brep_union(thin, box);
|
||||
(void)u.is_valid();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════
|
||||
// 重合顶点测试
|
||||
// ═══════════════════════════════════════════════
|
||||
|
||||
TEST(RegressionDegenerate, CoincidentVertices_SelfUnion) {
|
||||
// 两个完全相同的盒子做布尔 → 所有面/边/顶点重合
|
||||
auto box = make_box(2.0, 2.0, 2.0);
|
||||
EXPECT_NO_THROW({
|
||||
auto u = brep_union(box, box);
|
||||
EXPECT_TRUE(u.is_valid());
|
||||
auto result = validate(u);
|
||||
(void)result;
|
||||
});
|
||||
}
|
||||
|
||||
TEST(RegressionDegenerate, CoincidentVertices_SelfIntersection) {
|
||||
auto box = make_box(2.0, 2.0, 2.0);
|
||||
EXPECT_NO_THROW({
|
||||
auto i = brep_intersection(box, box);
|
||||
EXPECT_TRUE(i.is_valid());
|
||||
});
|
||||
}
|
||||
|
||||
TEST(RegressionDegenerate, CoincidentVertices_SelfDifference) {
|
||||
auto box = make_box(2.0, 2.0, 2.0);
|
||||
EXPECT_NO_THROW({
|
||||
auto d = brep_difference(box, box);
|
||||
EXPECT_TRUE(d.is_valid());
|
||||
});
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════
|
||||
// 自交体测试(通过治疗修复验证)
|
||||
// ═══════════════════════════════════════════════
|
||||
|
||||
TEST(RegressionDegenerate, SelfIntersecting_Heal) {
|
||||
// 使用极小偏移的重复体模拟自交场景
|
||||
auto box = make_box(2.0, 2.0, 2.0);
|
||||
|
||||
// 重复布尔运算产生复杂拓扑,至少不应该崩溃
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
EXPECT_NO_THROW({
|
||||
auto u = brep_union(box, box);
|
||||
auto result = validate(u);
|
||||
(void)result;
|
||||
BrepModel copy = u;
|
||||
heal_topology(copy, 1e-4);
|
||||
});
|
||||
}
|
||||
|
||||
// 使用不同尺寸的盒子布尔链式操作
|
||||
auto small = make_box(0.5, 0.5, 0.5);
|
||||
EXPECT_NO_THROW({
|
||||
auto diff = brep_difference(box, small);
|
||||
auto result = validate(diff);
|
||||
(void)result;
|
||||
heal_topology(diff, 1e-4);
|
||||
});
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════
|
||||
// 退化圆柱测试
|
||||
// ═══════════════════════════════════════════════
|
||||
|
||||
TEST(RegressionDegenerate, DegenerateCylinder_ZeroRadius) {
|
||||
// 半径为 0 的圆柱(退化为线)
|
||||
EXPECT_NO_THROW({
|
||||
auto cyl = make_cylinder(0.0, 1.0, 8);
|
||||
assert_no_crash(cyl);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(RegressionDegenerate, DegenerateCylinder_ZeroHeight) {
|
||||
// 高度为 0 的圆柱(退化为圆盘)
|
||||
EXPECT_NO_THROW({
|
||||
auto cyl = make_cylinder(1.0, 0.0, 16);
|
||||
assert_no_crash(cyl);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(RegressionDegenerate, DegenerateSphere_ZeroRadius) {
|
||||
// 半径为 0 的球(退化为点)
|
||||
EXPECT_NO_THROW({
|
||||
auto sph = make_sphere(0.0, 8, 4);
|
||||
assert_no_crash(sph);
|
||||
});
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════
|
||||
// 退化修复流水线测试
|
||||
// ═══════════════════════════════════════════════
|
||||
|
||||
TEST(RegressionDegenerate, HealDegenerateModel) {
|
||||
// 从退化体开始,通过修复流水线处理
|
||||
auto thin = make_box(1.0, 1e-12, 1.0);
|
||||
EXPECT_NO_THROW({
|
||||
int n_gaps = heal_gaps(thin, 1e-6);
|
||||
(void)n_gaps;
|
||||
int n_slivers = heal_slivers(thin);
|
||||
(void)n_slivers;
|
||||
int n_oriented = heal_orientation(thin);
|
||||
(void)n_oriented;
|
||||
bool ok = heal_topology(thin, 1e-4);
|
||||
(void)ok;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
/// @file regression_extreme.cpp
|
||||
/// @brief 极端坐标回归测试
|
||||
///
|
||||
/// 覆盖场景:
|
||||
/// 1. 极小坐标(1e-15)
|
||||
/// 2. 极大坐标(1e15)
|
||||
/// 3. NaN 输入
|
||||
/// 4. Inf 输入
|
||||
///
|
||||
/// 每个测试:构造极端体 → 布尔/修复/验证 → 断言不崩溃
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include "vde/brep/brep.h"
|
||||
#include "vde/brep/modeling.h"
|
||||
#include "vde/brep/brep_boolean.h"
|
||||
#include "vde/brep/brep_validate.h"
|
||||
#include "vde/brep/brep_heal.h"
|
||||
#include "vde/core/point.h"
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
using namespace vde::brep;
|
||||
using namespace vde::core;
|
||||
|
||||
// ── 极小坐标测试 ──
|
||||
|
||||
TEST(RegressionExtreme, TinyCoordinates_1eMinus15) {
|
||||
// 极小的盒子:尺寸为 1e-15
|
||||
double s = 1e-15;
|
||||
EXPECT_NO_THROW({
|
||||
auto tiny = make_box(s, s, s);
|
||||
// 验证合法性
|
||||
EXPECT_TRUE(tiny.is_valid());
|
||||
|
||||
// 布尔运算:与正常盒子
|
||||
auto box = make_box(1.0, 1.0, 1.0);
|
||||
auto u = brep_union(tiny, box);
|
||||
EXPECT_TRUE(u.is_valid());
|
||||
auto i = brep_intersection(tiny, box);
|
||||
EXPECT_TRUE(i.is_valid());
|
||||
|
||||
// 导出网格
|
||||
auto mesh = tiny.to_mesh();
|
||||
(void)mesh;
|
||||
});
|
||||
}
|
||||
|
||||
TEST(RegressionExtreme, TinyCoordinates_1eMinus10) {
|
||||
double s = 1e-10;
|
||||
EXPECT_NO_THROW({
|
||||
auto tiny = make_box(s, s, s);
|
||||
EXPECT_TRUE(tiny.is_valid());
|
||||
auto mesh = tiny.to_mesh();
|
||||
(void)mesh;
|
||||
|
||||
// 修复流水线
|
||||
BrepModel copy = tiny;
|
||||
heal_topology(copy, s * 0.1);
|
||||
});
|
||||
}
|
||||
|
||||
TEST(RegressionExtreme, TinyCoordinates_Mixed) {
|
||||
// 一个维度极小,其他正常
|
||||
EXPECT_NO_THROW({
|
||||
auto thin = make_box(1e-15, 1.0, 1.0);
|
||||
EXPECT_NO_THROW({ thin.is_valid(); });
|
||||
auto result = validate(thin);
|
||||
(void)result;
|
||||
|
||||
auto mesh = thin.to_mesh();
|
||||
(void)mesh;
|
||||
});
|
||||
}
|
||||
|
||||
// ── 极大坐标测试 ──
|
||||
|
||||
TEST(RegressionExtreme, HugeCoordinates_1e15) {
|
||||
double s = 1e15;
|
||||
EXPECT_NO_THROW({
|
||||
auto huge = make_box(s, s, s);
|
||||
// 基本验证不崩溃
|
||||
EXPECT_TRUE(huge.is_valid());
|
||||
|
||||
// 包围盒计算
|
||||
auto bb = huge.bounds();
|
||||
EXPECT_TRUE(std::isfinite(bb.min().x()));
|
||||
EXPECT_TRUE(std::isfinite(bb.max().x()));
|
||||
|
||||
// 布尔自交
|
||||
auto u = brep_union(huge, huge);
|
||||
EXPECT_TRUE(u.is_valid());
|
||||
|
||||
// 网格导出(大尺寸模型)
|
||||
auto mesh = huge.to_mesh(0.01 * s);
|
||||
(void)mesh;
|
||||
});
|
||||
}
|
||||
|
||||
TEST(RegressionExtreme, HugeVsTiny) {
|
||||
// 巨大体与极小体的布尔
|
||||
EXPECT_NO_THROW({
|
||||
auto huge = make_box(1e12, 1e12, 1e12);
|
||||
auto tiny = make_box(1e-12, 1e-12, 1e-12);
|
||||
|
||||
auto u = brep_union(huge, tiny);
|
||||
EXPECT_TRUE(u.is_valid());
|
||||
auto i = brep_intersection(huge, tiny);
|
||||
EXPECT_TRUE(i.is_valid());
|
||||
});
|
||||
}
|
||||
|
||||
// ── NaN 输入测试 ──
|
||||
|
||||
TEST(RegressionExtreme, NaN_Handling) {
|
||||
double nan = std::numeric_limits<double>::quiet_NaN();
|
||||
|
||||
// make_box 应对 NaN 表现得优雅(返回无效体或抛出,但绝不能崩溃)
|
||||
EXPECT_NO_THROW({
|
||||
auto body = make_box(nan, 1.0, 1.0);
|
||||
(void)body;
|
||||
});
|
||||
|
||||
EXPECT_NO_THROW({
|
||||
auto body = make_box(1.0, nan, 1.0);
|
||||
(void)body;
|
||||
});
|
||||
|
||||
EXPECT_NO_THROW({
|
||||
auto body = make_box(1.0, 1.0, nan);
|
||||
(void)body;
|
||||
});
|
||||
|
||||
EXPECT_NO_THROW({
|
||||
auto body = make_box(nan, nan, nan);
|
||||
(void)body;
|
||||
});
|
||||
}
|
||||
|
||||
TEST(RegressionExtreme, NaN_BooleanResilience) {
|
||||
// 若 NaN 产生了有效的 body,检查布尔运算不崩溃
|
||||
auto good = make_box(2.0, 2.0, 2.0);
|
||||
|
||||
// 将所有尝试包裹在 EXPECT_NO_THROW 中
|
||||
EXPECT_NO_THROW({
|
||||
try {
|
||||
auto bad = make_box(std::numeric_limits<double>::quiet_NaN(), 1.0, 1.0);
|
||||
auto u = brep_union(good, bad);
|
||||
(void)u;
|
||||
} catch (...) {
|
||||
// 允许抛异常但不应崩溃
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ── Inf 输入测试 ──
|
||||
|
||||
TEST(RegressionExtreme, Inf_Handling) {
|
||||
double inf = std::numeric_limits<double>::infinity();
|
||||
|
||||
EXPECT_NO_THROW({
|
||||
auto body = make_box(inf, 1.0, 1.0);
|
||||
(void)body;
|
||||
});
|
||||
|
||||
EXPECT_NO_THROW({
|
||||
auto body = make_box(1.0, inf, 1.0);
|
||||
(void)body;
|
||||
});
|
||||
|
||||
EXPECT_NO_THROW({
|
||||
auto body = make_box(1.0, 1.0, inf);
|
||||
(void)body;
|
||||
});
|
||||
}
|
||||
|
||||
// ── 极大值 vs 极小值布尔 ──
|
||||
|
||||
TEST(RegressionExtreme, MaxDouble_Boolean) {
|
||||
double big = 1e100;
|
||||
double small = 1e-100;
|
||||
|
||||
EXPECT_NO_THROW({
|
||||
auto a = make_box(big, 1.0, 1.0);
|
||||
auto b = make_box(small, 1.0, 1.0);
|
||||
auto u = brep_union(a, b);
|
||||
(void)u.is_valid();
|
||||
});
|
||||
}
|
||||
|
||||
// ── 边界值批处理 ──
|
||||
|
||||
TEST(RegressionExtreme, BoundaryValuesBatch) {
|
||||
// 一系列边界值,确保每个都不会崩溃
|
||||
double values[] = {
|
||||
0.0,
|
||||
std::numeric_limits<double>::min(),
|
||||
std::numeric_limits<double>::denorm_min(),
|
||||
std::numeric_limits<double>::epsilon(),
|
||||
1e-15, 1e-10, 1e-5,
|
||||
1.0, 10.0, 100.0,
|
||||
1e5, 1e10, 1e15,
|
||||
std::numeric_limits<double>::max() * 0.5,
|
||||
};
|
||||
|
||||
for (double v : values) {
|
||||
if (!std::isfinite(v)) continue;
|
||||
EXPECT_NO_THROW({
|
||||
auto body = make_box(v, v, v);
|
||||
body.is_valid();
|
||||
auto mesh = body.to_mesh();
|
||||
(void)mesh;
|
||||
}) << "failed at v=" << v;
|
||||
}
|
||||
|
||||
// 混合边界值布尔
|
||||
auto a = make_box(values[0], 1.0, 1.0);
|
||||
for (int i = 1; i < 10; ++i) {
|
||||
if (!std::isfinite(values[i])) continue;
|
||||
EXPECT_NO_THROW({
|
||||
auto b = make_box(values[i], 1.0, 1.0);
|
||||
auto u = brep_union(a, b);
|
||||
(void)u.is_valid();
|
||||
}) << "failed at i=" << i;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
/// @file regression_large_model.cpp
|
||||
/// @brief 大面数回归测试(1000+ 面模型)
|
||||
///
|
||||
/// 覆盖场景:
|
||||
/// 1. 通过高细分球体/圆柱产生 1000+ 面的模型
|
||||
/// 2. 大面数模型的布尔运算
|
||||
/// 3. 大面数模型的验证(validate)
|
||||
/// 4. 大面数模型的修复
|
||||
///
|
||||
/// 每个测试:构造大面数体 → 布尔/修复/验证 → 断言不崩溃
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include "vde/brep/brep.h"
|
||||
#include "vde/brep/modeling.h"
|
||||
#include "vde/brep/brep_boolean.h"
|
||||
#include "vde/brep/brep_validate.h"
|
||||
#include "vde/brep/brep_heal.h"
|
||||
#include "vde/core/point.h"
|
||||
#include <cmath>
|
||||
|
||||
using namespace vde::brep;
|
||||
using namespace vde::core;
|
||||
|
||||
/// 生成一个高面数的球体
|
||||
/// seg_u × seg_v 段 → 大约 2 * seg_u * seg_v 个面
|
||||
/// 例如 seg_u=50, seg_v=50 → 约 5000 面
|
||||
static BrepModel make_highres_sphere(double radius, int seg_u, int seg_v) {
|
||||
return make_sphere(radius, seg_u, seg_v);
|
||||
}
|
||||
|
||||
/// 生成一个高面数的圆柱体
|
||||
/// segments 段 → 侧面有 segments * 1 个面,加上顶/底面
|
||||
static BrepModel make_highres_cylinder(double radius, double height, int segments) {
|
||||
return make_cylinder(radius, height, segments);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════
|
||||
// 1000+ 面模型 - 球体
|
||||
// ═══════════════════════════════════════════════
|
||||
|
||||
TEST(RegressionLargeModel, Sphere_1000PlusFaces) {
|
||||
// seg_u=25, seg_v=20 → 2*25*20 = 1000 faces
|
||||
EXPECT_NO_THROW({
|
||||
auto sphere = make_sphere(10.0, 25, 20);
|
||||
EXPECT_TRUE(sphere.is_valid());
|
||||
EXPECT_GE(sphere.num_faces(), 800u); // 至少 800 面
|
||||
|
||||
// 验证
|
||||
auto result = validate(sphere);
|
||||
(void)result;
|
||||
|
||||
// 导出网格
|
||||
auto mesh = sphere.to_mesh(0.05);
|
||||
(void)mesh;
|
||||
});
|
||||
}
|
||||
|
||||
TEST(RegressionLargeModel, Sphere_5000PlusFaces) {
|
||||
// seg_u=55, seg_v=50 → 2*55*50 = 5500 faces
|
||||
EXPECT_NO_THROW({
|
||||
auto sphere = make_sphere(5.0, 55, 50);
|
||||
EXPECT_TRUE(sphere.is_valid());
|
||||
EXPECT_GE(sphere.num_faces(), 4000u);
|
||||
|
||||
auto result = validate(sphere);
|
||||
(void)result;
|
||||
|
||||
auto mesh = sphere.to_mesh(0.01);
|
||||
(void)mesh;
|
||||
});
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════
|
||||
// 1000+ 面模型 - 圆柱体
|
||||
// ═══════════════════════════════════════════════
|
||||
|
||||
TEST(RegressionLargeModel, Cylinder_1000PlusFaces) {
|
||||
// segments=1000 → 侧面有很多面
|
||||
EXPECT_NO_THROW({
|
||||
auto cyl = make_cylinder(5.0, 10.0, 1000);
|
||||
EXPECT_TRUE(cyl.is_valid());
|
||||
// seg=1000 时侧面有 1000 个面 + 上下各 1(或更多)面
|
||||
EXPECT_GE(cyl.num_faces(), 900u);
|
||||
|
||||
auto result = validate(cyl);
|
||||
(void)result;
|
||||
|
||||
auto mesh = cyl.to_mesh();
|
||||
(void)mesh;
|
||||
});
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════
|
||||
// 大面数模型布尔运算
|
||||
// ═══════════════════════════════════════════════
|
||||
|
||||
TEST(RegressionLargeModel, Boolean_LargeSphereUnion) {
|
||||
auto s1 = make_sphere(5.0, 30, 24); // ~1440 faces
|
||||
auto s2 = make_sphere(3.0, 20, 16); // ~640 faces
|
||||
|
||||
EXPECT_NO_THROW({
|
||||
auto u = brep_union(s1, s2);
|
||||
EXPECT_TRUE(u.is_valid());
|
||||
auto result = validate(u);
|
||||
(void)result;
|
||||
});
|
||||
}
|
||||
|
||||
TEST(RegressionLargeModel, Boolean_LargeSphereIntersection) {
|
||||
auto s1 = make_sphere(5.0, 30, 24);
|
||||
auto s2 = make_sphere(3.0, 24, 20);
|
||||
|
||||
EXPECT_NO_THROW({
|
||||
auto i = brep_intersection(s1, s2);
|
||||
EXPECT_TRUE(i.is_valid());
|
||||
});
|
||||
}
|
||||
|
||||
TEST(RegressionLargeModel, Boolean_LargeCylinders) {
|
||||
// 高分辨率圆柱布尔运算
|
||||
auto c1 = make_cylinder(3.0, 10.0, 500);
|
||||
auto c2 = make_cylinder(2.0, 8.0, 300);
|
||||
|
||||
EXPECT_NO_THROW({
|
||||
auto u = brep_union(c1, c2);
|
||||
EXPECT_TRUE(u.is_valid());
|
||||
auto d = brep_difference(c1, c2);
|
||||
EXPECT_TRUE(d.is_valid());
|
||||
});
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════
|
||||
// 大面数模型修复
|
||||
// ═══════════════════════════════════════════════
|
||||
|
||||
TEST(RegressionLargeModel, Heal_LargeModel) {
|
||||
auto sphere = make_sphere(10.0, 30, 24);
|
||||
EXPECT_GE(sphere.num_faces(), 1000u);
|
||||
|
||||
EXPECT_NO_THROW({
|
||||
// 修复大面数模型
|
||||
heal_gaps(sphere, 1e-6);
|
||||
heal_slivers(sphere);
|
||||
bool ok = heal_topology(sphere, 1e-4);
|
||||
(void)ok;
|
||||
});
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════
|
||||
// 多体布尔链式操作(压力测试)
|
||||
// ═══════════════════════════════════════════════
|
||||
|
||||
TEST(RegressionLargeModel, Stress_ChainedBooleans) {
|
||||
auto box = make_box(10.0, 10.0, 10.0);
|
||||
auto cyl = make_cylinder(0.5, 15.0, 128);
|
||||
|
||||
// 连续执行多次布尔运算,最终模型可能包含大量面
|
||||
EXPECT_NO_THROW({
|
||||
auto u = brep_union(box, cyl);
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
auto d = brep_difference(u, make_sphere(0.3 + i * 0.1, 16, 8));
|
||||
u = d; // chain
|
||||
}
|
||||
EXPECT_TRUE(u.is_valid());
|
||||
|
||||
auto result = validate(u);
|
||||
(void)result;
|
||||
|
||||
auto mesh = u.to_mesh(0.05);
|
||||
(void)mesh;
|
||||
});
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════
|
||||
// 大面数模型导出
|
||||
// ═══════════════════════════════════════════════
|
||||
|
||||
TEST(RegressionLargeModel, Export_LargeMesh) {
|
||||
auto sphere = make_sphere(10.0, 40, 32); // ~2560 faces
|
||||
EXPECT_GE(sphere.num_faces(), 2000u);
|
||||
|
||||
EXPECT_NO_THROW({
|
||||
// 导出为精细网格
|
||||
auto mesh = sphere.to_mesh(0.001);
|
||||
(void)mesh.num_vertices();
|
||||
(void)mesh.num_faces();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
/// @file regression_thin_wall.cpp
|
||||
/// @brief 薄壁件回归测试(壁厚 0.001mm)
|
||||
///
|
||||
/// 覆盖场景:
|
||||
/// 1. 极薄壁盒子
|
||||
/// 2. 薄壁壳的布尔运算
|
||||
/// 3. 薄壁圆柱和球体
|
||||
/// 4. 抽壳后的薄壁验证
|
||||
///
|
||||
/// 每个测试:构造薄壁体 → 布尔/修复/验证 → 断言不崩溃
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include "vde/brep/brep.h"
|
||||
#include "vde/brep/modeling.h"
|
||||
#include "vde/brep/brep_boolean.h"
|
||||
#include "vde/brep/brep_validate.h"
|
||||
#include "vde/brep/brep_heal.h"
|
||||
#include "vde/core/point.h"
|
||||
#include <cmath>
|
||||
|
||||
using namespace vde::brep;
|
||||
using namespace vde::core;
|
||||
|
||||
// 行业标准薄壁厚度:0.001mm = 1e-6 米(假设模型单位为米)
|
||||
static constexpr double THIN_WALL = 1e-6;
|
||||
|
||||
// ── 极薄壁盒子测试 ──
|
||||
|
||||
TEST(RegressionThinWall, Box_ThinWall1um) {
|
||||
// 1 μm 壁厚的盒子
|
||||
EXPECT_NO_THROW({
|
||||
auto body = make_box(THIN_WALL, 1.0, 1.0);
|
||||
EXPECT_TRUE(body.is_valid());
|
||||
|
||||
// 验证
|
||||
auto result = validate(body);
|
||||
(void)result;
|
||||
|
||||
// 导出网格不应崩溃
|
||||
auto mesh = body.to_mesh();
|
||||
(void)mesh;
|
||||
});
|
||||
}
|
||||
|
||||
TEST(RegressionThinWall, Box_ThinWallVarious) {
|
||||
// 多种薄壁厚度
|
||||
for (double t : {1e-3, 1e-4, 1e-5, 1e-6, 1e-7}) {
|
||||
EXPECT_NO_THROW({
|
||||
auto body = make_box(t, 1.0, 1.0);
|
||||
body.is_valid();
|
||||
auto mesh = body.to_mesh();
|
||||
(void)mesh;
|
||||
}) << "failed at thickness=" << t;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RegressionThinWall, Box_AllDimensionsThin) {
|
||||
// 三个维度都极薄 → 几乎是一个点
|
||||
EXPECT_NO_THROW({
|
||||
auto body = make_box(THIN_WALL, THIN_WALL, THIN_WALL);
|
||||
body.is_valid();
|
||||
auto mesh = body.to_mesh();
|
||||
(void)mesh;
|
||||
});
|
||||
}
|
||||
|
||||
// ── 薄壁布尔运算 ──
|
||||
|
||||
TEST(RegressionThinWall, Boolean_ThinWallUnion) {
|
||||
auto thin = make_box(THIN_WALL, 1.0, 1.0);
|
||||
auto normal = make_box(2.0, 2.0, 2.0);
|
||||
|
||||
EXPECT_NO_THROW({
|
||||
auto u = brep_union(thin, normal);
|
||||
EXPECT_TRUE(u.is_valid());
|
||||
auto i = brep_intersection(thin, normal);
|
||||
EXPECT_TRUE(i.is_valid());
|
||||
auto d = brep_difference(normal, thin);
|
||||
EXPECT_TRUE(d.is_valid());
|
||||
});
|
||||
}
|
||||
|
||||
TEST(RegressionThinWall, Boolean_TwoThinWalls) {
|
||||
// 两个薄壁体之间的布尔运算
|
||||
auto a = make_box(THIN_WALL, 2.0, 2.0);
|
||||
auto b = make_box(2.0, THIN_WALL, 2.0);
|
||||
|
||||
EXPECT_NO_THROW({
|
||||
auto u = brep_union(a, b);
|
||||
EXPECT_TRUE(u.is_valid());
|
||||
auto i = brep_intersection(a, b);
|
||||
EXPECT_TRUE(i.is_valid());
|
||||
});
|
||||
}
|
||||
|
||||
// ── 薄壁圆柱 ──
|
||||
|
||||
TEST(RegressionThinWall, Cylinder_ThinRadius) {
|
||||
// 极薄半径的圆柱(近似线)
|
||||
for (double r : {1e-3, 1e-4, 1e-5, 1e-6}) {
|
||||
EXPECT_NO_THROW({
|
||||
auto cyl = make_cylinder(r, 2.0, 16);
|
||||
EXPECT_TRUE(cyl.is_valid());
|
||||
auto mesh = cyl.to_mesh();
|
||||
(void)mesh;
|
||||
}) << "failed at radius=" << r;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RegressionThinWall, Cylinder_ThinHeight) {
|
||||
// 极薄高度的圆柱(近似圆盘)
|
||||
for (double h : {1e-3, 1e-4, 1e-5, 1e-6}) {
|
||||
EXPECT_NO_THROW({
|
||||
auto cyl = make_cylinder(1.0, h, 16);
|
||||
EXPECT_TRUE(cyl.is_valid());
|
||||
auto mesh = cyl.to_mesh();
|
||||
(void)mesh;
|
||||
}) << "failed at height=" << h;
|
||||
}
|
||||
}
|
||||
|
||||
// ── 薄壁球体 ──
|
||||
|
||||
TEST(RegressionThinWall, Sphere_ThinRadius) {
|
||||
for (double r : {1e-3, 1e-4, 1e-5, 1e-6}) {
|
||||
EXPECT_NO_THROW({
|
||||
auto sph = make_sphere(r, 16, 8);
|
||||
EXPECT_TRUE(sph.is_valid());
|
||||
auto mesh = sph.to_mesh();
|
||||
(void)mesh;
|
||||
}) << "failed at radius=" << r;
|
||||
}
|
||||
}
|
||||
|
||||
// ── 抽壳后的薄壁体 ──
|
||||
|
||||
TEST(RegressionThinWall, Shell_ThinThickness) {
|
||||
// shell() 生成一个挖空的体 → 然后对其做薄壁操作
|
||||
auto box = make_box(10.0, 10.0, 10.0);
|
||||
|
||||
EXPECT_NO_THROW({
|
||||
auto shelled = shell(box, 0, THIN_WALL);
|
||||
EXPECT_TRUE(shelled.is_valid());
|
||||
|
||||
auto result = validate(shelled);
|
||||
(void)result;
|
||||
|
||||
// 壳上的布尔运算
|
||||
auto cyl = make_cylinder(1.0, 20.0, 32);
|
||||
auto u = brep_union(shelled, cyl);
|
||||
(void)u.is_valid();
|
||||
});
|
||||
}
|
||||
|
||||
// ── 薄壁修复 ──
|
||||
|
||||
TEST(RegressionThinWall, Heal_ThinWall) {
|
||||
auto thin = make_box(THIN_WALL, 1.0, 1.0);
|
||||
|
||||
EXPECT_NO_THROW({
|
||||
// 使用容差修复(容差远大于壁厚的情况)
|
||||
heal_gaps(thin, 1e-6);
|
||||
heal_slivers(thin);
|
||||
|
||||
bool ok = heal_topology(thin, 1e-4);
|
||||
(void)ok;
|
||||
|
||||
auto result = validate(thin);
|
||||
(void)result;
|
||||
});
|
||||
}
|
||||
|
||||
// ── 压力测试:薄壁链式布尔 ──
|
||||
|
||||
TEST(RegressionThinWall, Stress_ChainedBoolean) {
|
||||
auto base = make_box(5.0, 5.0, 5.0);
|
||||
|
||||
// 连续对薄壁体做布尔运算
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
double t = THIN_WALL * (i + 1) * 10;
|
||||
EXPECT_NO_THROW({
|
||||
auto thin = make_box(t, 0.5, 0.5);
|
||||
auto u = brep_union(base, thin);
|
||||
(void)u.is_valid();
|
||||
}) << "failed at iteration " << i;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user