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
4144using 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
163303Span<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