OCCT 大饼芝麻算法: 揭露曲面比较本质
XXX::ComparisonResults XXX::CompareSurfaces(const TopoDS_Shape& baselineFace,const TopoDS_Shape& modelSurface,double minEdgeLength,double maxEdgeLength,double uniformity){ComparisonResults results;qDebug() << \"=== 曲面比较算法 ===\";qDebug() << \"三角剖分参数:\";qDebug() << \" 最小边长:\" << minEdgeLength;qDebug() << \" 最大边长:\" << maxEdgeLength;qDebug() << \" 均匀度:\" << uniformity;try {// 1. 输入验证if (baselineFace.IsNull() || modelSurface.IsNull()) {qDebug() << \"❌ 错误:输入的曲面为空\";return results;}if (baselineFace.ShapeType() != TopAbs_FACE || modelSurface.ShapeType() != TopAbs_FACE) {qDebug() << \"❌ 错误:输入的形状不是面\";return results;}TopoDS_Face baseFace = TopoDS::Face(baselineFace);TopoDS_Face modelFace = TopoDS::Face(modelSurface);qDebug() << \"✅ 输入验证通过\";// 2. 三角剖分参数处理double meshDeflection = std::min(minEdgeLength, maxEdgeLength);if (meshDeflection <= 0) {meshDeflection = 0.1; // 默认值qDebug() << \"⚠️ 使用默认网格偏差:\" << meshDeflection;}// uniformity参数用于控制网格质量,这里可以用作角度偏差的调节double angularDeflection = 0.5 / uniformity; // uniformity越大,角度偏差越小,网格越精细if (angularDeflection < 0.1) angularDeflection = 0.1;if (angularDeflection > 1.0) angularDeflection = 1.0;qDebug() << \"计算得出的网格参数:\";qDebug() << \" 线性偏差:\" << meshDeflection;qDebug() << \" 角度偏差:\" << angularDeflection;// 3. 生成三角网格BRepMesh_IncrementalMesh baseMesh(baseFace, meshDeflection, Standard_False, angularDeflection, Standard_False);BRepMesh_IncrementalMesh modelMesh(modelFace, meshDeflection, Standard_False, angularDeflection, Standard_False);if (!baseMesh.IsDone() || !modelMesh.IsDone()) {qDebug() << \"❌ 网格生成失败,尝试更粗糙的参数\";// 尝试使用更粗糙的网格double coarseMeshDeflection = meshDeflection * 2.0;BRepMesh_IncrementalMesh baseMesh2(baseFace, coarseMeshDeflection, Standard_False, angularDeflection, Standard_False);BRepMesh_IncrementalMesh modelMesh2(modelFace, coarseMeshDeflection, Standard_False, angularDeflection, Standard_False);if (!baseMesh2.IsDone() || !modelMesh2.IsDone()) {qDebug() << \"❌ 网格生成完全失败\";return results;}qDebug() << \"✅ 使用粗糙网格成功\";}else {qDebug() << \"✅ 网格生成成功\";}// 4. 获取基准面的三角网格TopLoc_Location baseLocation;Handle(Poly_Triangulation) baseTriangulation = BRep_Tool::Triangulation(baseFace, baseLocation);if (baseTriangulation.IsNull()) {qDebug() << \"❌ 无法获取基准面的三角网格\";return results;}Standard_Integer nbNodes = baseTriangulation->NbNodes();qDebug() << \"✅ 基准面网格信息:节点数\" << nbNodes << \",三角形数\" << baseTriangulation->NbTriangles();if (nbNodes == 0) {qDebug() << \"❌ 网格节点数为0\";return results;}// 5. 创建模型曲面适配器BRepAdaptor_Surface modelSurfaceAdaptor(modelFace);// 6. 初始化计算变量std::vector<gp_Pnt> deviationPoints;std::vector<double> errorValues;double totalError = 0.0;double totalSquaredError = 0.0;double minError = std::numeric_limits<double>::max();double maxError = std::numeric_limits<double>::lowest();int validPointCount = 0;int failedProjections = 0;qDebug() << \"开始偏差计算,遍历\" << nbNodes << \"个节点...\";// 7. 主要计算循环for (Standard_Integer i = 1; i <= nbNodes; i++) {// 进度显示if (i % (std::max(1, nbNodes / 10)) == 0) {qDebug() << \"计算进度:\" << (i * 100 / nbNodes) << \"% (\" << validPointCount << \"个有效点)\";}// 获取基准点gp_Pnt basePoint;try {basePoint = baseTriangulation->Node(i);// 应用位置变换if (!baseLocation.IsIdentity()) {basePoint.Transform(baseLocation.Transformation());}}catch (...) {continue;}results.detectedPoints++;// 投影计算try {GeomAPI_ProjectPointOnSurf projector(basePoint, modelSurfaceAdaptor.Surface().Surface());if (projector.NbPoints() == 0) {failedProjections++;continue;}gp_Pnt projectedPoint = projector.NearestPoint();double distance = basePoint.Distance(projectedPoint);// 记录所有成功投影的点validPointCount++;deviationPoints.push_back(basePoint);errorValues.push_back(distance);totalError += distance;totalSquaredError += distance * distance;if (distance < minError) minError = distance;if (distance > maxError) maxError = distance;}catch (...) {failedProjections++;}}qDebug() << \"=== 计算完成 ===\";qDebug() << \"总检测点数:\" << results.detectedPoints;qDebug() << \"投影失败点数:\" << failedProjections;qDebug() << \"有效计算点数:\" << validPointCount;// 8. 计算统计结果results.validPoints = validPointCount;results.hitRate = results.detectedPoints > 0 ?(double(validPointCount) / double(results.detectedPoints)) * 100.0 : 0.0;if (validPointCount > 0) {results.averageError = totalError / validPointCount;results.minError = minError;results.maxError = maxError;// 计算标准差double variance = (totalSquaredError / validPointCount) - (results.averageError * results.averageError);results.standardDeviation = variance > 0 ? std::sqrt(variance) : 0.0;qDebug() << \"=== 统计结果 ===\";qDebug() << \"有效点数:\" << results.validPoints;qDebug() << \"命中率:\" << results.hitRate << \"%\";qDebug() << \"误差范围:[\" << results.minError << \",\" << results.maxError << \"]\";qDebug() << \"平均误差:\" << results.averageError;qDebug() << \"标准差:\" << results.standardDeviation;}else {qDebug() << \"❌ 没有有效的计算结果\";results.averageError = 0.0;results.minError = 0.0;results.maxError = 0.0;results.standardDeviation = 0.0;}// 9. 保存计算数据results.deviationPoints = deviationPoints;results.errorValues = errorValues;}catch (const Standard_Failure& e) {qDebug() << \"❌ OpenCASCADE异常:\" << e.GetMessageString();}catch (const std::exception& e) {qDebug() << \"❌ 标准异常:\" << e.what();}catch (...) {qDebug() << \"❌ 未知异常\";}return results;}
算法的基本思路
想象你有两张薄饼(两个曲面),你想知道它们之间的差别有多大。
计算步骤详解
1️⃣ 生成测量点
基准面 → 三角网格化 → 得到很多测量点
- 就像在基准面上撒芝麻,芝麻就是测量点
- 三角剖分参数控制芝麻的密度:
minEdgeLength/maxEdgeLength
越小 → 芝麻越密 → 测量越精确uniformity
越大 → 芝麻分布越均匀
检测点数 (detectedPoints) = 撒下的芝麻总数
2️⃣ 测量距离
对每个测量点:
// 伪代码for (每个基准面上的点) { 1. 从这个点垂直投影到模型面 2. 测量两点之间的距离 3. 记录这个距离值}
想象用一把尺子,从基准面上的每个芝麻,垂直测量到另一张薄饼的距离。
有效点数 (validPoints) = 成功测量到距离的芝麻数量
(有些芝麻可能投影失败,比如掉到薄饼外面了)
3️⃣ 统计分析
拿到所有的距离数据后,开始统计:
误差范围 (minError, maxError)
minError = 所有距离中的最小值maxError = 所有距离中的最大值
就像找出:“最近的地方差了0.1mm,最远的地方差了5.2mm”
平均误差 (averageError)
averageError = (所有距离的总和) ÷ (有效点数)
就是把所有距离加起来除以点数,得到平均差距
标准偏差 (standardDeviation)
// 1. 计算每个点与平均值的差的平方variance = Σ(每个距离 - 平均距离)² ÷ 有效点数// 2. 开平方根standardDeviation = √variance
标准偏差告诉你距离值的\"离散程度\":
- 标准偏差小 → 大部分点的距离都接近平均值 → 差异比较均匀
- 标准偏差大 → 有些地方很接近,有些地方差很远 → 差异不均匀
命中率 (hitRate)
hitRate = (有效点数 ÷ 检测点数) × 100%
就是成功测量的比例,比如\"95%的点都成功测量了距离\"
实际例子
假设我们有100个测量点:
- 98个点成功测量到距离:
[0.1, 0.2, 0.15, 0.3, 0.25, ...]
- 2个点投影失败
计算结果:
- 检测点数: 100
- 有效点数: 98
- 命中率: 98%
- 误差范围: [0.1, 0.3](最小0.1,最大0.3)
- 平均误差: (0.1+0.2+0.15+0.3+0.25+…) ÷ 98 = 0.18
- 标准偏差: 大约0.06(表示大部分距离在0.12-0.24之间)
参数影响
- 网格密度越高(minEdgeLength越小)→ 测量点越多 → 结果越精确
- uniformity越大 → 网格质量越好 → 测量越准确
- 两个面越相似 → 平均误差越小,标准偏差越小
- 两个面差异越不均匀 → 标准偏差越大
这就是整个算法的计算逻辑!简单说就是:在一个面上撒点 → 测距离 → 统计分析。
🚫 算法失效的情况
1️⃣ 投影失败
// 当距离过远时,这个调用可能失败GeomAPI_ProjectPointOnSurf projector(basePoint, modelSurface);if (projector.NbPoints() == 0) { // 投影失败!找不到投影点}
失效场景:
- 两个面相距几百个单位以上
- 面的形状差异巨大(比如球面投影到平面)
- 基准面的点\"看不到\"模型面
2️⃣ 投影结果不合理
想象这个场景:
基准面(球面) 距离很远 模型面(平面) ● ←—————————————————————→ | / \\ | ● ● | \\ / | ● |
当距离很远时:
- 球面边缘的点投影到平面上,投影点可能聚集在一小块区域
- 测量的不是真实的\"对应关系\"
3️⃣ 数值精度问题
// 距离很大时精度下降double distance = basePoint.Distance(projectedPoint);// 比如: distance = 12543.7890123456789// 可能变成: distance = 12543.789012346 (精度丢失)
✅ 算法适用的场景
理想距离范围
- 很好: 0.1 - 10 个单位
- 可以: 10 - 100 个单位
- 勉强: 100 - 1000 个单位
- 不推荐: > 1000 个单位
适用的几何关系
// ✅ 好的场景:相似形状的小偏差基准面:圆柱面(半径=50)模型面:圆柱面(半径=50.5) // 相差0.5,很近→ 算法工作很好// ❌ 不好的场景:完全不同的形状基准面:球面(半径=50)模型面:平面 // 距离球心100个单位→ 投影结果没有意义
🔧 改进方案
距离预检查
GeometrySamples::ComparisonResults GeometrySamples::CompareSurfaces(...){ // 1. 先做粗略的距离估算 Bnd_Box baseBox, modelBox; BRepBndLib::Add(baselineFace, baseBox); BRepBndLib::Add(modelSurface, modelBox); double distance = baseBox.Distance(modelBox); if (distance > 1000.0) { // 设定阈值 qDebug() << \"⚠️ 警告:两个面距离过远(\" << distance << \"),算法可能不准确\"; // 可以选择返回或继续但给出警告 } // 2. 继续原算法...}
投影失败率监控
// 在算法中加入检查double failureRate = (double)failedProjections / results.detectedPoints;if (failureRate > 0.3) { // 30%以上失败 qDebug() << \"❌ 投影失败率过高:\" << (failureRate*100) << \"%\"; qDebug() << \"可能原因:两面距离太远或形状差异太大\";}
最近距离算法
对于距离很远的情况,可以考虑用最近距离而不是投影距离:
// 替代方案:计算最近距离BRepExtrema_DistShapeShape distCalculator(basePoint, modelSurface);if (distCalculator.IsDone() && distCalculator.NbSolution() > 0) { double minDistance = distCalculator.Value(); // 这个更稳定,但计算更慢}
📊 实际建议
使用前先检查:
- 两个面的大致距离范围
- 形状相似性
- 测试少量点的投影成功率
如果距离太远:
- 考虑先做配准(alignment)
- 使用其他距离测量方法
- 调整期望值,可能只能得到粗略结果
总之,这个算法最适合检查相似形状的小偏差,对于距离很远或形状差异很大的情况,需要谨慎使用并做好失效处理。