Quickmeasure: Make measuring holes easier (#23385)

* Measure: Add circle center distance types to quickmeasure

* Measure: Add cylinder axis distance types to quickmeasure

* Measure: Fix PointToCylinder minimum distance not computing

* Quickmeasure: compute circle and cylinder diameters when they are closed

* Quickmeasure: add angle measurement between cylinder & circle axes

* Quickmeasure: Remove minimum distance between circle center and cylinder surface

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
theo-vt
2025-08-31 15:57:00 -04:00
committed by GitHub
parent 25d1a51fb4
commit 0c4e125e44
3 changed files with 351 additions and 23 deletions

View File

@@ -27,6 +27,7 @@
#include <BRepAdaptor_Surface.hxx>
#include <BRepExtrema_DistShapeShape.hxx>
#include <BRepGProp.hxx>
#include <BRepBuilderAPI_MakeVertex.hxx>
#include <GCPnts_AbscissaPoint.hxx>
#include <gp_Pln.hxx>
#include <gp_Circ.hxx>
@@ -113,9 +114,11 @@ MeasureType Measurement::findType()
int edges = 0;
int lines = 0;
int circles = 0;
int circleArcs = 0;
int faces = 0;
int planes = 0;
int cylinders = 0;
int cylinderSections = 0;
int cones = 0;
int torus = 0;
int spheres = 0;
@@ -155,7 +158,12 @@ MeasureType Measurement::findType()
lines++;
}
else if (sf.GetType() == GeomAbs_Circle) {
circles++;
if (sf.IsClosed()) {
circles++;
}
else {
circleArcs++;
}
}
} break;
case TopAbs_FACE: {
@@ -167,7 +175,12 @@ MeasureType Measurement::findType()
planes++;
}
else if (sf.GetType() == GeomAbs_Cylinder) {
cylinders++;
if (sf.IsUClosed() || sf.IsVClosed()) {
cylinders++;
}
else {
cylinderSections++;
}
}
else if (sf.GetType() == GeomAbs_Sphere) {
spheres++;
@@ -201,9 +214,19 @@ MeasureType Measurement::findType()
}
else if (faces > 0) {
if (verts > 0 || edges > 0) {
if (faces == 1 && verts == 1) {
if (faces == 1 && (cylinders + cylinderSections) == 1 && verts == 1 && edges == 0) {
mode = MeasureType::PointToCylinder;
}
else if (faces == 1 && verts == 1) {
mode = MeasureType::PointToSurface;
}
else if (faces == 1 && (cylinders + cylinderSections) == 1
&& (circles + circleArcs) == 1 && edges == 1) {
mode = MeasureType::CircleToCylinder;
}
else if (faces == 1 && (circles + circleArcs) == 1 && edges == 1) {
mode = MeasureType::CircleToSurface;
}
else {
mode = MeasureType::Invalid;
}
@@ -223,6 +246,12 @@ MeasureType Measurement::findType()
else if (cylinders == 1 && faces == 1) {
mode = MeasureType::Cylinder;
}
else if (cylinderSections == 1 && faces == 1) {
mode = MeasureType::CylinderSection;
}
else if ((cylinders + cylinderSections) == 2 && faces == 2) {
mode = MeasureType::TwoCylinders;
}
else if (cones == 1 && faces == 1) {
mode = MeasureType::Cone;
}
@@ -242,6 +271,9 @@ MeasureType Measurement::findType()
if (verts > 1) {
mode = MeasureType::Invalid;
}
else if ((circles + circleArcs) == 1) {
mode = MeasureType::PointToCircle;
}
else {
mode = MeasureType::PointToEdge;
}
@@ -260,6 +292,15 @@ MeasureType Measurement::findType()
else if (circles == 1 && edges == 1) {
mode = MeasureType::Circle;
}
else if (circleArcs == 1 && edges == 1) {
mode = MeasureType::CircleArc;
}
else if ((circles + circleArcs) == 2 && edges == 2) {
mode = MeasureType::TwoCircles;
}
else if ((circles + circleArcs == 1) && edges == 2) {
mode = MeasureType::CircleToEdge;
}
else {
mode = MeasureType::Edges;
}
@@ -312,14 +353,17 @@ double Measurement::length() const
const std::vector<std::string>& subElements = References3D.getSubValues();
if (measureType == MeasureType::Points || measureType == MeasureType::PointToPoint
|| measureType == MeasureType::PointToEdge
|| measureType == MeasureType::PointToSurface) {
|| measureType == MeasureType::PointToEdge || measureType == MeasureType::PointToSurface
|| measureType == MeasureType::PointToCircle
|| measureType == MeasureType::PointToCylinder) {
Base::Vector3d diff = this->delta();
result = diff.Length();
}
else if (measureType == MeasureType::Edges || measureType == MeasureType::Line
|| measureType == MeasureType::TwoLines || measureType == MeasureType::Circle) {
|| measureType == MeasureType::TwoLines || measureType == MeasureType::Circle
|| measureType == MeasureType::CircleArc || measureType == MeasureType::TwoCircles
|| measureType == MeasureType::CircleToEdge) {
// Iterate through edges and calculate each length
std::vector<App::DocumentObject*>::const_iterator obj = objects.begin();
@@ -423,7 +467,84 @@ double Measurement::lineLineDistance() const
}
return distance;
}
double Measurement::circleCenterDistance() const
{
double distance = 0.0;
if (References3D.getSize() != 2) {
return distance;
}
const std::vector<App::DocumentObject*>& objects = References3D.getValues();
const std::vector<std::string>& subElements = References3D.getSubValues();
// Get the first circle
TopoDS_Shape shape1 = getShape(objects[0], subElements[0].c_str());
TopoDS_Shape shape2 = getShape(objects[1], subElements[1].c_str());
if (shape1.ShapeType() != TopAbs_EDGE) {
std::swap(shape1, shape2);
}
if (measureType == MeasureType::TwoCircles) {
const TopoDS_Edge& edge1 = TopoDS::Edge(shape1);
BRepAdaptor_Curve curve1(edge1);
const TopoDS_Edge& edge2 = TopoDS::Edge(shape2);
BRepAdaptor_Curve curve2(edge2);
if (curve1.GetType() == GeomAbs_Circle && curve2.GetType() == GeomAbs_Circle) {
gp_Circ circle1 = curve1.Circle();
gp_Circ circle2 = curve2.Circle();
distance = circle1.Location().Distance(circle2.Location());
}
}
else if (measureType == MeasureType::CircleToEdge || measureType == MeasureType::CircleToSurface
|| measureType == MeasureType::CircleToCylinder) {
const TopoDS_Edge& edge1 = TopoDS::Edge(shape1);
BRepAdaptor_Curve curve1(edge1);
TopoDS_Vertex circleCenter;
const TopoDS_Shape* otherShape;
if (curve1.GetType() == GeomAbs_Circle) {
circleCenter = BRepBuilderAPI_MakeVertex(curve1.Circle().Location());
otherShape = &shape2;
}
else {
const TopoDS_Edge& edge2 = TopoDS::Edge(shape2);
BRepAdaptor_Curve curve2(edge2);
circleCenter = BRepBuilderAPI_MakeVertex(curve2.Circle().Location());
otherShape = &shape1;
}
BRepExtrema_DistShapeShape extrema(circleCenter, *otherShape);
if (extrema.IsDone()) {
// Found the nearest point between point and curve
// NOTE we will assume there is only 1 solution (cyclic topology will create
// multiple solutions.
gp_Pnt P1 = extrema.PointOnShape1(1);
gp_Pnt P2 = extrema.PointOnShape2(1);
gp_XYZ diff = P2.XYZ() - P1.XYZ();
distance = Base::Vector3d(diff.X(), diff.Y(), diff.Z()).Length();
}
}
else if (measureType == MeasureType::PointToCircle) {
const TopoDS_Edge& edge1 = TopoDS::Edge(shape1);
BRepAdaptor_Curve curve1(edge1);
TopoDS_Vertex& vert1 = TopoDS::Vertex(shape2);
gp_Circ circle1 = curve1.Circle();
gp_Pnt pt = BRep_Tool::Pnt(vert1);
distance = circle1.Location().Distance(pt);
}
return distance;
}
double Measurement::planePlaneDistance() const
{
if (measureType != MeasureType::TwoPlanes || References3D.getSize() != 2) {
@@ -459,6 +580,61 @@ double Measurement::planePlaneDistance() const
return distance;
}
double Measurement::cylinderAxisDistance() const
{
double distance = 0.0;
if (References3D.getSize() != 2) {
return distance;
}
const std::vector<App::DocumentObject*>& objects = References3D.getValues();
const std::vector<std::string>& subElements = References3D.getSubValues();
// Get the first circle
TopoDS_Shape shape1 = getShape(objects[0], subElements[0].c_str());
TopoDS_Shape shape2 = getShape(objects[1], subElements[1].c_str());
if (shape1.ShapeType() != TopAbs_FACE) {
std::swap(shape1, shape2);
}
if (measureType == MeasureType::PointToCylinder) {
TopoDS_Face face = TopoDS::Face(shape1);
BRepAdaptor_Surface cylinderFace(face);
const TopoDS_Vertex& vert1 = TopoDS::Vertex(shape2);
gp_Pnt pt = BRep_Tool::Pnt(vert1);
if (cylinderFace.GetType() == GeomAbs_Cylinder) {
distance = gp_Lin(cylinderFace.Cylinder().Axis()).Distance(pt);
}
}
else if (measureType == MeasureType::TwoCylinders) {
TopoDS_Face face1 = TopoDS::Face(shape1);
BRepAdaptor_Surface surface1(face1);
TopoDS_Face face2 = TopoDS::Face(shape2);
BRepAdaptor_Surface surface2(face2);
if (surface1.GetType() == GeomAbs_Cylinder && surface2.GetType() == GeomAbs_Cylinder) {
distance =
gp_Lin(surface1.Cylinder().Axis()).Distance(gp_Lin(surface2.Cylinder().Axis()));
}
}
else if (measureType == MeasureType::CircleToCylinder) {
TopoDS_Face face1 = TopoDS::Face(shape1);
BRepAdaptor_Surface surface1(face1);
TopoDS_Edge edge1 = TopoDS::Edge(shape2);
BRepAdaptor_Curve curve1(edge1);
if (surface1.GetType() == GeomAbs_Cylinder && curve1.GetType() == GeomAbs_Circle) {
distance = gp_Lin(surface1.Cylinder().Axis()).Distance(curve1.Circle().Location());
}
}
return distance;
}
double Measurement::angle(const Base::Vector3d& /*param*/) const
{
@@ -529,6 +705,44 @@ double Measurement::angle(const Base::Vector3d& /*param*/) const
return Base::toDegrees<double>(radians);
}
}
else if (measureType == MeasureType::TwoCylinders || measureType == MeasureType::TwoCircles
|| measureType == MeasureType::CircleToCylinder) {
if (numRefs == 2) {
TopoDS_Shape shape1 = getShape(objects.at(0), subElements.at(0).c_str(), TopAbs_EDGE);
TopoDS_Shape shape2 = getShape(objects.at(1), subElements.at(1).c_str(), TopAbs_EDGE);
gp_Ax1 axis1;
gp_Ax1 axis2;
if (measureType == MeasureType::TwoCylinders) {
BRepAdaptor_Surface surface1(TopoDS::Face(shape1));
BRepAdaptor_Surface surface2(TopoDS::Face(shape2));
axis1 = surface1.Cylinder().Axis();
axis2 = surface2.Cylinder().Axis();
}
else if (measureType == MeasureType::TwoCircles) {
BRepAdaptor_Curve curve1(TopoDS::Edge(shape1));
BRepAdaptor_Curve curve2(TopoDS::Edge(shape2));
axis1 = curve1.Circle().Axis();
axis2 = curve2.Circle().Axis();
}
else if (measureType == MeasureType::CircleToCylinder) {
if (shape1.ShapeType() == TopAbs_FACE) {
std::swap(shape1, shape2);
}
BRepAdaptor_Curve curve1(TopoDS::Edge(shape1));
BRepAdaptor_Surface surface2(TopoDS::Face(shape2));
axis1 = curve1.Circle().Axis();
axis2 = surface2.Cylinder().Axis();
}
double aRad = axis1.Angle(axis2);
return Base::toDegrees<double>(
std::min(aRad, std::fmod(aRad + std::numbers::pi, 2.0 * std::numbers::pi)));
}
}
throw Base::RuntimeError("Unexpected error for angle measurement");
}
@@ -541,7 +755,7 @@ double Measurement::radius() const
if (numRefs == 0) {
Base::Console().error("Measurement::radius - No 3D references available\n");
}
else if (measureType == MeasureType::Circle) {
else if (measureType == MeasureType::Circle || measureType == MeasureType::CircleArc) {
TopoDS_Shape shape = getShape(objects.at(0), subElements.at(0).c_str(), TopAbs_EDGE);
const TopoDS_Edge& edge = TopoDS::Edge(shape);
@@ -550,8 +764,8 @@ double Measurement::radius() const
return (double)curve.Circle().Radius();
}
}
else if (measureType == MeasureType::Cylinder || measureType == MeasureType::Sphere
|| measureType == MeasureType::Torus) {
else if (measureType == MeasureType::Cylinder || measureType == MeasureType::CylinderSection
|| measureType == MeasureType::Sphere || measureType == MeasureType::Torus) {
TopoDS_Shape shape = getShape(objects.at(0), subElements.at(0).c_str(), TopAbs_FACE);
TopoDS_Face face = TopoDS::Face(shape);
@@ -569,6 +783,36 @@ double Measurement::radius() const
Base::Console().error("Measurement::radius - Invalid References3D Provided\n");
return 0.0;
}
double Measurement::diameter() const
{
const std::vector<App::DocumentObject*>& objects = References3D.getValues();
const std::vector<std::string>& subElements = References3D.getSubValues();
int numRefs = References3D.getSize();
if (numRefs == 0) {
Base::Console().error("Measurement::diameter - No 3D references available\n");
}
else if (measureType == MeasureType::Circle) {
TopoDS_Shape shape = getShape(objects.at(0), subElements.at(0).c_str(), TopAbs_EDGE);
const TopoDS_Edge& edge = TopoDS::Edge(shape);
BRepAdaptor_Curve curve(edge);
if (curve.GetType() == GeomAbs_Circle) {
return (double)curve.Circle().Radius() * 2.0;
}
}
else if (measureType == MeasureType::Cylinder) {
TopoDS_Shape shape = getShape(objects.at(0), subElements.at(0).c_str(), TopAbs_FACE);
TopoDS_Face face = TopoDS::Face(shape);
BRepAdaptor_Surface sf(face);
if (sf.GetType() == GeomAbs_Cylinder) {
return sf.Cylinder().Radius() * 2.0;
}
}
Base::Console().error("Measurement::diameter - Invalid References3D Provided\n");
return 0.0;
}
Base::Vector3d Measurement::delta() const
{
@@ -602,7 +846,9 @@ Base::Vector3d Measurement::delta() const
}
}
else if (measureType == MeasureType::PointToEdge
|| measureType == MeasureType::PointToSurface) {
|| measureType == MeasureType::PointToSurface
|| measureType == MeasureType::PointToCircle
|| measureType == MeasureType::PointToCylinder) {
// BrepExtema can calculate minimum distance between any set of topology sets.
if (numRefs == 2) {
TopoDS_Shape shape1 = getShape(objects.at(0), subElements.at(0).c_str());
@@ -701,7 +947,8 @@ double Measurement::area() const
Base::Console().error("Measurement::area - No 3D references available\n");
}
else if (measureType == MeasureType::Volumes || measureType == MeasureType::Surfaces
|| measureType == MeasureType::Cylinder || measureType == MeasureType::Cone
|| measureType == MeasureType::Cylinder || measureType == MeasureType::CylinderSection
|| measureType == MeasureType::TwoCylinders || measureType == MeasureType::Cone
|| measureType == MeasureType::Sphere || measureType == MeasureType::Torus
|| measureType == MeasureType::Plane) {

View File

@@ -45,18 +45,27 @@ enum class MeasureType
TwoLines, // Two lines
TwoParallelLines, // Two parallel lines
Circle, // One circle
Surfaces, // Measure the surface(s)
Cylinder, // One Cylinder
Cone, // One Cone
Sphere, // One Sphere
Torus, // One Torus
Plane, // One Plane
TwoPlanes, // One Plane
CircleArc, // One circle arc
TwoCircles,
CircleToEdge,
CircleToSurface,
CircleToCylinder,
Surfaces, // Measure the surface(s)
Cylinder, // One Cylinder
CylinderSection, // One cylinder section
TwoCylinders,
Cone, // One Cone
Sphere, // One Sphere
Torus, // One Torus
Plane, // One Plane
TwoPlanes, // One Plane
Points,
PointToPoint, // Measure between TWO points
PointToEdge, // Measure between ONE point and ONE edge
PointToPoint, // Measure between TWO points
PointToEdge, // Measure between ONE point and ONE edge
PointToCircle,
PointToSurface, // Measure between ONE point and ONE surface
EdgeToEdge, // Measure between TWO edges
PointToCylinder,
EdgeToEdge, // Measure between TWO edges
Invalid
};
@@ -89,11 +98,16 @@ public:
double length() const;
Base::Vector3d delta() const; // when would client use delta??
double lineLineDistance() const;
double circleCenterDistance() const;
double planePlaneDistance() const;
double cylinderAxisDistance() const;
// Calculates the radius for an arc or circular edge
double radius() const;
// Calculates the diameter for a circle or a cylinder
double diameter() const;
// Calculates the angle between two edges
double
angle(const Base::Vector3d& param = Base::Vector3d(0, 0, 0)) const; // param is never used???

View File

@@ -218,11 +218,31 @@ void QuickMeasure::printResult()
else if (mtype == MeasureType::Cone || mtype == MeasureType::Plane) {
print(tr("Area: %1").arg(areaStr(measurement->area())));
}
else if (mtype == MeasureType::Cylinder || mtype == MeasureType::Sphere
else if (mtype == MeasureType::CylinderSection || mtype == MeasureType::Sphere
|| mtype == MeasureType::Torus) {
print(tr("Area: %1, Radius: %2")
.arg(areaStr(measurement->area()), lengthStr(measurement->radius())));
}
else if (mtype == MeasureType::Cylinder) {
print(tr("Area: %1, Diameter: %2")
.arg(areaStr(measurement->area()), lengthStr(measurement->diameter())));
}
else if (mtype == MeasureType::TwoCylinders) {
double angle = measurement->angle();
if (angle <= Precision::Confusion()) {
print(tr("Total area: %1, Axis distance: %2")
.arg(areaStr(measurement->area()),
lengthStr(measurement->cylinderAxisDistance())));
}
else {
print(tr("Total area: %1, Axis distance: %2, Axis angle: %3")
.arg(areaStr(measurement->area()),
lengthStr(measurement->cylinderAxisDistance()),
angleStr(angle)));
}
}
else if (mtype == MeasureType::Edges) {
print(tr("Total length: %1").arg(lengthStr(measurement->length())));
}
@@ -236,15 +256,62 @@ void QuickMeasure::printResult()
else if (mtype == MeasureType::Line) {
print(tr("Length: %1").arg(lengthStr(measurement->length())));
}
else if (mtype == MeasureType::Circle) {
else if (mtype == MeasureType::CircleArc) {
print(tr("Radius: %1").arg(lengthStr(measurement->radius())));
}
else if (mtype == MeasureType::Circle) {
print(tr("Diameter: %1").arg(lengthStr(measurement->diameter())));
}
else if (mtype == MeasureType::PointToPoint) {
print(tr("Distance: %1").arg(lengthStr(measurement->length())));
}
else if (mtype == MeasureType::PointToEdge || mtype == MeasureType::PointToSurface) {
print(tr("Minimum distance: %1").arg(lengthStr(measurement->length())));
}
else if (mtype == MeasureType::PointToCylinder) {
print(tr("Minimum distance: %1, Axis distance: %2")
.arg(lengthStr(measurement->length()),
lengthStr(measurement->cylinderAxisDistance())));
}
else if (mtype == MeasureType::PointToCircle) {
print(tr("Minimum distance: %1, Center distance: %2")
.arg(lengthStr(measurement->length()),
lengthStr(measurement->circleCenterDistance())));
}
else if (mtype == MeasureType::TwoCircles) {
double angle = measurement->angle();
if (angle <= Precision::Confusion()) {
print(tr("Total length: %1, Center distance: %2")
.arg(lengthStr(measurement->length()),
lengthStr(measurement->circleCenterDistance())));
}
else {
print(tr("Total length: %1, Center distance: %2, Axis angle: %3")
.arg(lengthStr(measurement->length()),
lengthStr(measurement->circleCenterDistance()),
angleStr(angle)));
}
}
else if (mtype == MeasureType::CircleToEdge) {
print(tr("Total length: %1, Center distance: %2")
.arg(lengthStr(measurement->length()),
lengthStr(measurement->circleCenterDistance())));
}
else if (mtype == MeasureType::CircleToSurface) {
print(
tr("Center surface distance: %1").arg(lengthStr(measurement->circleCenterDistance())));
}
else if (mtype == MeasureType::CircleToCylinder) {
double angle = measurement->angle();
if (angle <= Precision::Confusion()) {
print(
tr("Center axis distance: %1").arg(lengthStr(measurement->cylinderAxisDistance())));
}
else {
print(tr("Center axis distance: %1, Axis angle: %2")
.arg(lengthStr(measurement->cylinderAxisDistance()), angleStr(angle)));
}
}
else {
print(QStringLiteral(""));
}