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
+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