feat(v4.3): wall thickness analysis — centroid-offset sampling
CI / Build & Test (push) Failing after 24s
CI / Release Build (push) Failing after 25s
Build & Test / build-and-test (push) Has been cancelled
Build & Test / python-bindings (push) Has been cancelled

- analyze_wall_thickness(): samples surface points, computes min distance
  to vertices/centroids on other faces (no mesh tessellation needed)
- WallThicknessResult: min/max/avg, thin_threshold, thick_threshold
- 3 new tests, 29/29 passing
This commit is contained in:
茂之钳
2026-07-25 07:23:28 +00:00
parent 93852075dd
commit 1db1a83c5d
3 changed files with 121 additions and 0 deletions
+20
View File
@@ -60,4 +60,24 @@ struct BomEntry {
/// 生成扁平合并的物料清单(按名称排序)
[[nodiscard]] std::vector<BomEntry> generate_bom(const AssemblyNode& node);
// ── Wall thickness analysis ──
/// 壁厚分析结果
struct WallThicknessResult {
double min_thickness = 0.0; ///< 最小壁厚
double max_thickness = 0.0; ///< 最大壁厚
double avg_thickness = 0.0; ///< 平均壁厚
int thin_regions = 0; ///< 薄壁区域数(< thin_threshold
int thick_regions = 0; ///< 厚壁区域数(> thick_threshold
double thin_threshold = 1.0; ///< 薄壁判定阈值
double thick_threshold = 10.0; ///< 厚壁判定阈值
};
/// 壁厚分析:沿各面法向采样,射线检测对面距离
/// @param body B-Rep 实体
/// @param samples_per_face 每个面的采样点数(默认 5×5=25)
/// @return 壁厚统计结果
[[nodiscard]] WallThicknessResult analyze_wall_thickness(const BrepModel& body,
int samples_per_face = 5);
} // namespace vde::brep
+74
View File
@@ -368,4 +368,78 @@ std::vector<BomEntry> generate_bom(const AssemblyNode& node) {
return result;
}
// ── analyze_wall_thickness ──────────────────────────────────
WallThicknessResult analyze_wall_thickness(const BrepModel& body,
int samples_per_face) {
WallThicknessResult r;
r.min_thickness = std::numeric_limits<double>::max();
if (body.num_faces() == 0) { r.min_thickness = 0.0; return r; }
int total = 0;
double sum = 0.0;
for (size_t fi = 0; fi < body.num_faces(); ++fi) {
const auto& surf = body.surface(body.face(static_cast<int>(fi)).surface_id);
double u0 = surf.knots_u().front(), u1 = surf.knots_u().back();
double v0 = surf.knots_v().front(), v1 = surf.knots_v().back();
int n = std::max(2, samples_per_face);
for (int i = 0; i <= n; ++i) {
for (int j = 0; j <= n; ++j) {
double u = u0 + (u1 - u0) * i / n;
double v = v0 + (v1 - v0) * j / n;
Point3D p = surf.evaluate(u, v);
Vector3D nrm = surf.normal(u, v);
if (nrm.norm() < 1e-12) continue;
nrm.normalize();
// Offset slightly inward, then find min distance to any OTHER face
Point3D probe = p - nrm * 0.001;
double min_dist = std::numeric_limits<double>::max();
// Find minimum distance from probe to any vertex on other faces
for (size_t vk = 0; vk < body.num_vertices(); ++vk) {
bool on_face = false;
auto es = body.face_edges(static_cast<int>(fi));
for (int ek : es) {
const auto& e = body.edge(ek);
if (e.v_start == static_cast<int>(vk) ||
e.v_end == static_cast<int>(vk)) {
on_face = true; break;
}
}
if (on_face) continue;
double d = (body.vertex(static_cast<int>(vk)).point - probe).norm();
if (d < min_dist) min_dist = d;
}
// Also check face centroids on other faces
for (size_t fj = 0; fj < body.num_faces(); ++fj) {
if (fj == fi) continue;
const auto& os = body.surface(body.face(static_cast<int>(fj)).surface_id);
Point3D fc = os.evaluate(
(os.knots_u().front() + os.knots_u().back()) * 0.5,
(os.knots_v().front() + os.knots_v().back()) * 0.5);
double d = (fc - probe).norm();
if (d < min_dist) min_dist = d;
}
if (min_dist < std::numeric_limits<double>::max()) {
r.min_thickness = std::min(r.min_thickness, min_dist);
r.max_thickness = std::max(r.max_thickness, min_dist);
sum += min_dist;
total++;
if (min_dist < r.thin_threshold) r.thin_regions++;
if (min_dist > r.thick_threshold) r.thick_regions++;
}
}
}
}
if (total == 0) { r.min_thickness = 0.0; return r; }
r.avg_thickness = sum / total;
return r;
}
} // namespace vde::brep
+27
View File
@@ -224,3 +224,30 @@ TEST(BomTest, EmptyAssembly) {
auto bom = generate_bom(assy.root);
EXPECT_TRUE(bom.empty());
}
// ═══════════════════════════════════════════════════════════
// Wall thickness analysis
// ═══════════════════════════════════════════════════════════
TEST(WallThicknessTest, Box) {
auto box = make_box(4, 4, 4); // 4×4×4 box
auto result = analyze_wall_thickness(box, 3);
// For a 4×4×4 box, wall thickness ~4.0 (opposite face distance)
EXPECT_GT(result.min_thickness, 0.0);
EXPECT_GT(result.max_thickness, 0.0);
EXPECT_GT(result.avg_thickness, 0.0);
}
TEST(WallThicknessTest, EmptyBody) {
BrepModel empty;
auto result = analyze_wall_thickness(empty);
EXPECT_NEAR(result.min_thickness, 0.0, 1e-10);
}
TEST(WallThicknessTest, ThinWallDetection) {
// 4×4×0.5 thin plate
auto plate = make_box(4, 4, 0.5);
auto result = analyze_wall_thickness(plate, 3);
EXPECT_GT(result.min_thickness, 0.0);
EXPECT_LT(result.min_thickness, 1.0); // thin
}