Skip to content

Commit 5464eda

Browse files
committed
Merge branch 'enhance/fougue#366' into develop
2 parents 8d618a1 + 4c323c2 commit 5464eda

File tree

1 file changed

+185
-5
lines changed

1 file changed

+185
-5
lines changed

src/measure/measure_tool_brep.cpp

Lines changed: 185 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,27 @@
1414
#include "../base/text_id.h"
1515
#include "../graphics/graphics_shape_object_driver.h"
1616

17-
#include <gp_Elips.hxx>
1817
#include <AIS_Shape.hxx>
19-
#include <Bnd_OBB.hxx>
20-
#include <BRep_Tool.hxx>
2118
#include <BRepAdaptor_Curve.hxx>
2219
#include <BRepAdaptor_Surface.hxx>
2320
#include <BRepBndLib.hxx>
2421
#include <BRepBuilderAPI_Transform.hxx>
2522
#include <BRepExtrema_DistShapeShape.hxx>
2623
#include <BRepGProp.hxx>
27-
#include <GC_MakeCircle.hxx>
24+
#include <BRep_Tool.hxx>
25+
#include <Bnd_OBB.hxx>
26+
#include <ElSLib.hxx>
2827
#include <GCPnts_AbscissaPoint.hxx>
2928
#include <GCPnts_QuasiUniformAbscissa.hxx>
29+
#include <GC_MakeCircle.hxx>
3030
#include <GProp_GProps.hxx>
3131
#include <Precision.hxx>
32+
#include <ProjLib.hxx>
3233
#include <StdSelect_BRepOwner.hxx>
3334
#include <TopoDS.hxx>
3435
#include <TopoDS_Shape.hxx>
36+
#include <gp_Elips.hxx>
37+
#include <math_Jacobi.hxx>
3538

3639
#include <Standard_Version.hxx>
3740
#if OCC_VERSION_HEX >= 0x070500
@@ -41,6 +44,8 @@
4144
using PrsDim_AngleDimension = AIS_AngleDimension;
4245
#endif
4346

47+
#include <algorithm>
48+
#include <array>
4449
#include <cmath>
4550
#include <optional>
4651

@@ -158,6 +163,141 @@ gp_Pnt computeShapeCenter(const TopoDS_Shape& shape)
158163
throwErrorIf<ErrorCode::CenterFailure>(shapeProps.Mass() < Precision::Confusion());
159164
return shapeProps.CentreOfMass();
160165
}
166+
167+
// Defines the result of fittedPlane() function
168+
struct FittedPlaneResult {
169+
bool success = false;
170+
gp_Pln value;
171+
double coplanarity = Precision::Infinite();
172+
};
173+
174+
// Computes optimal plane from input 3D points
175+
FittedPlaneResult fittedPlane(Span<const gp_Pnt> points)
176+
{
177+
// Compute centroid point
178+
gp_Pnt centroid;
179+
for (const gp_Pnt& pnt : points)
180+
centroid.ChangeCoord() += pnt.Coord();
181+
182+
centroid.ChangeCoord() /= int(points.size());
183+
184+
// Compute covariance matrix of centered coordinates
185+
math_Matrix covMatrix(1, 3, 1, 3, 0.);
186+
for (const gp_Pnt& pnt : points) {
187+
const gp_XYZ v = pnt.XYZ() - centroid.XYZ();
188+
for (int i = 1; i <= 3; ++i) {
189+
for (int j = 1; j <= 3; ++j)
190+
covMatrix(i, j) += v.Coord(i) * v.Coord(j);
191+
}
192+
}
193+
194+
// Find eigen vectors and values of the covariance matrix
195+
math_Jacobi jacobiSolver(covMatrix);
196+
if (!jacobiSolver.IsDone())
197+
return {};
198+
199+
const math_Vector& eigenValues = jacobiSolver.Values();
200+
const math_Matrix& eigenVectors = jacobiSolver.Vectors();
201+
202+
int countOfNonNullEigenValues = 0;
203+
for (int i = 1; i <= 3; ++i) {
204+
if (!MathUtils::fuzzyIsNull(eigenValues(i)))
205+
++countOfNonNullEigenValues;
206+
}
207+
208+
if (countOfNonNullEigenValues == 0 || countOfNonNullEigenValues == 1)
209+
return {};
210+
211+
const int eigenMinIndex = eigenValues.Min();
212+
const int eigenMaxIndex = eigenValues.Max();
213+
214+
// Plane normal is the eigen vector associated to smallest eigen value
215+
gp_Vec normal(
216+
eigenVectors(1, eigenMinIndex),
217+
eigenVectors(2, eigenMinIndex),
218+
eigenVectors(3, eigenMinIndex)
219+
);
220+
if (normal.IsEqual(gp_Vec{}, Precision::Confusion(), Precision::Angular()))
221+
return {};
222+
223+
FittedPlaneResult result;
224+
result.success = true;
225+
result.value = gp_Pln{centroid, normal.Normalized()};
226+
if (!MathUtils::fuzzyIsNull(eigenValues(eigenMaxIndex)))
227+
result.coplanarity = eigenValues(eigenMinIndex) / eigenValues(eigenMaxIndex);
228+
else
229+
result.coplanarity = 0.;
230+
231+
return result;
232+
}
233+
234+
// Defines the result of fittedCircle_taubin() function
235+
struct FittedCircle2DResult {
236+
bool success = false;
237+
gp_Pnt2d center;
238+
double radius = 0.;
239+
};
240+
241+
// Compute optimal circle from input 3D points and plane
242+
// This function uses Taubin method that is considered one of the best for circular regression(more
243+
// stable than Kasa method)
244+
FittedCircle2DResult fittedCircle2D_taubin(Span<const gp_Pnt2d> points)
245+
{
246+
const size_t N = points.size();
247+
248+
// Center input points
249+
double meanX = 0;
250+
double meanY = 0;
251+
for (const gp_Pnt2d& pnt : points) {
252+
meanX += pnt.X();
253+
meanY += pnt.Y();
254+
}
255+
256+
meanX /= N;
257+
meanY /= N;
258+
259+
// Build needed matrix
260+
double Sxx = 0, Syy = 0, Sxy = 0;
261+
double Sxz = 0, Syz = 0, Szz = 0;
262+
for (const gp_Pnt2d& pnt : points) {
263+
const double x = pnt.X() - meanX;
264+
const double y = pnt.Y() - meanY;
265+
const double z = x*x + y*y;
266+
Sxx += x * x;
267+
Syy += y * y;
268+
Sxy += x * y;
269+
Sxz += x * z;
270+
Syz += y * z;
271+
Szz += z * z;
272+
}
273+
274+
// Solve quadratic system
275+
const double Cov_xy = Sxx * Syy - Sxy * Sxy;
276+
// If Cov_xy is null this means input points are aligned
277+
if (MathUtils::fuzzyIsNull(Cov_xy))
278+
return {};
279+
280+
const double A2 = 0.5 * (Sxz * Syy - Syz * Sxy) / Cov_xy;
281+
const double B2 = 0.5 * (Syz * Sxx - Sxz * Sxy) / Cov_xy;
282+
283+
const double centerX = A2 + meanX;
284+
const double centerY = B2 + meanY;
285+
286+
double radius = 0;
287+
for (const gp_Pnt2d& pnt : points) {
288+
const double dx = pnt.X() - centerX;
289+
const double dy = pnt.Y() - centerY;
290+
radius += std::sqrt(dx*dx + dy*dy);
291+
}
292+
293+
radius /= N;
294+
FittedCircle2DResult result;
295+
result.success = true;
296+
result.center = gp_Pnt2d{centerX, centerY};
297+
result.radius = radius;
298+
return result;
299+
}
300+
161301
} // namespace
162302

163303
Span<const GraphicsObjectSelectionMode> MeasureToolBRep::selectionModes(MeasureType type) const
@@ -266,11 +406,50 @@ MeasureCircle MeasureToolBRep::brepCircleFromGeometricEdge(const TopoDS_Edge& ed
266406
circle = curve.Circle();
267407
}
268408
else if (curve.GetType() == GeomAbs_Ellipse) {
269-
const gp_Elips ellipse = curve.Ellipse();
409+
const gp_Elips ellipse = curve.Ellipse();
270410
if (std::abs(ellipse.MinorRadius() - ellipse.MajorRadius()) < Precision::Confusion())
271411
circle = gp_Circ{ ellipse.Position(), ellipse.MinorRadius() };
272412
}
273413
else {
414+
// Discretize input curve
415+
constexpr size_t DiscreteCount = 64;
416+
std::array<gp_Pnt, DiscreteCount> discrete;
417+
{
418+
const GCPnts_QuasiUniformAbscissa pnts(curve, int(discrete.size()));
419+
throwErrorIf<ErrorCode::NotCircularEdge>(!pnts.IsDone());
420+
for (int i = 1; i <= pnts.NbPoints(); ++i)
421+
discrete[i - 1] = GeomUtils::d0(curve, pnts.Parameter(i));
422+
}
423+
424+
// Compute plane containing the points
425+
const FittedPlaneResult plane = fittedPlane(discrete);
426+
throwErrorIf<ErrorCode::NotCircularEdge>(!plane.success);
427+
throwErrorIf<ErrorCode::NotCircularEdge>(plane.coplanarity > 1e-4);
428+
429+
// Project points on the plane
430+
std::array<gp_Pnt2d, DiscreteCount> discrete2d;
431+
for (size_t i = 0; i < DiscreteCount; ++i)
432+
discrete2d[i] = ProjLib::Project(plane.value, discrete[i]);
433+
434+
// Compute optimal circle from 2D points
435+
const FittedCircle2DResult circle2d = fittedCircle2D_taubin(discrete2d);
436+
throwErrorIf<ErrorCode::NotCircularEdge>(!circle2d.success);
437+
438+
// Check mean(average) relative error
439+
double sumError = 0.;
440+
for (const gp_Pnt2d& pnt : discrete2d) {
441+
const double distCenter = pnt.Distance(circle2d.center);
442+
sumError += std::abs(distCenter - circle2d.radius);
443+
}
444+
445+
const double meanError = sumError / circle2d.radius;
446+
throwErrorIf<ErrorCode::NotCircularEdge>(meanError > 0.01); // error must be <= 1%
447+
448+
// Project the 2D center in 3D space to get the circle result
449+
const gp_Pnt2d& center2d = circle2d.center;
450+
const gp_Pnt center = ElSLib::Value(center2d.X(), center2d.Y(), plane.value);
451+
circle = gp_Circ{gp_Ax2{center, plane.value.Axis().Direction()}, circle2d.radius};
452+
#if 0
274453
// Try to create a circle from 3 sample points on the curve
275454
{
276455
const GCPnts_QuasiUniformAbscissa pnts(curve, 4); // More points to avoid confusion
@@ -295,6 +474,7 @@ MeasureCircle MeasureToolBRep::brepCircleFromGeometricEdge(const TopoDS_Edge& ed
295474
throwErrorIf<ErrorCode::NotCircularEdge>(std::abs(dist - circle->Radius()) > 1e-4);
296475
}
297476
}
477+
#endif
298478
}
299479

300480
throwErrorIf<ErrorCode::NotCircularEdge>(!circle);

0 commit comments

Comments
 (0)