// SPDX-License-Identifier: LGPL-2.1-or-later /*************************************************************************** * Copyright (c) 2013 Luke Parry * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Measurement.h" #include "MeasurementPy.h" #include "ShapeFinder.h" using namespace Measure; using namespace Base; using namespace Part; TYPESYSTEM_SOURCE(Measure::Measurement, Base::BaseClass) Measurement::Measurement() { measureType = MeasureType::Invalid; References3D.setScope(App::LinkScope::Global); } Measurement::~Measurement() = default; void Measurement::clear() { std::vector Objects; std::vector SubElements; References3D.setValues(Objects, SubElements); measureType = MeasureType::Invalid; } bool Measurement::has3DReferences() { return (References3D.getSize() > 0); } // add a 3D reference (obj+sub) to end of list int Measurement::addReference3D(App::DocumentObject* obj, const std::string& subName) { return addReference3D(obj, subName.c_str()); } /// add a 3D reference (obj+sub) to end of list int Measurement::addReference3D(App::DocumentObject* obj, const char* subName) { std::vector objects = References3D.getValues(); std::vector subElements = References3D.getSubValues(); objects.push_back(obj); subElements.emplace_back(subName); References3D.setValues(objects, subElements); measureType = findType(); return References3D.getSize(); } MeasureType Measurement::findType() { const std::vector& objects = References3D.getValues(); const std::vector& subElements = References3D.getSubValues(); std::vector::const_iterator obj = objects.begin(); std::vector::const_iterator subEl = subElements.begin(); MeasureType mode; int verts = 0; 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; int vols = 0; int other = 0; for (; obj != objects.end(); ++obj, ++subEl) { TopoDS_Shape refSubShape; try { refSubShape = Part::Feature::getShape( *obj, Part::ShapeOption::NeedSubElement | Part::ShapeOption::ResolveLink | Part::ShapeOption::Transform, (*subEl).c_str() ); if (refSubShape.IsNull()) { return MeasureType::Invalid; } } catch (Standard_Failure& e) { std::stringstream errorMsg; errorMsg << "Measurement - getType - " << e.GetMessageString() << std::endl; throw Base::CADKernelError(e.GetMessageString()); } switch (refSubShape.ShapeType()) { case TopAbs_VERTEX: { verts++; } break; case TopAbs_EDGE: { edges++; TopoDS_Edge edge = TopoDS::Edge(refSubShape); BRepAdaptor_Curve sf(edge); if (sf.GetType() == GeomAbs_Line) { lines++; } else if (sf.GetType() == GeomAbs_Circle) { if (sf.IsClosed()) { circles++; } else { circleArcs++; } } } break; case TopAbs_FACE: { faces++; TopoDS_Face face = TopoDS::Face(refSubShape); BRepAdaptor_Surface sf(face); if (sf.GetType() == GeomAbs_Plane) { planes++; } else if (sf.GetType() == GeomAbs_Cylinder) { if (sf.IsUClosed() || sf.IsVClosed()) { cylinders++; } else { cylinderSections++; } } else if (sf.GetType() == GeomAbs_Sphere) { spheres++; } else if (sf.GetType() == GeomAbs_Cone) { cones++; } else if (sf.GetType() == GeomAbs_Torus) { torus++; } } break; case TopAbs_SOLID: { vols++; } break; default: other++; break; } } if (other > 0) { mode = MeasureType::Invalid; } else if (vols > 0) { if (verts > 0 || edges > 0 || faces > 0) { mode = MeasureType::Invalid; } else { mode = MeasureType::Volumes; } } else if (faces > 0) { if (verts > 0 || edges > 0) { 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; } } else { if (planes == 1 && faces == 1) { mode = MeasureType::Plane; } else if (planes == 2 && faces == 2) { if (planesAreParallel()) { mode = MeasureType::TwoPlanes; } else { mode = MeasureType::Surfaces; } } 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; } else if (spheres == 1 && faces == 1) { mode = MeasureType::Sphere; } else if (torus == 1 && faces == 1) { mode = MeasureType::Torus; } else { mode = MeasureType::Surfaces; } } } else if (edges > 0) { if (verts > 0) { if (verts > 1) { mode = MeasureType::Invalid; } else if ((circles + circleArcs) == 1) { mode = MeasureType::PointToCircle; } else { mode = MeasureType::PointToEdge; } } else if (lines == 1 && edges == 1) { mode = MeasureType::Line; } else if (lines == 2 && edges == 2) { if (linesAreParallel()) { mode = MeasureType::TwoParallelLines; } else { mode = MeasureType::TwoLines; } } 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; } } else if (verts > 0) { if (verts == 2) { mode = MeasureType::PointToPoint; } else { mode = MeasureType::Points; } } else { mode = MeasureType::Invalid; } return mode; } MeasureType Measurement::getType() { return measureType; } TopoDS_Shape Measurement::getShape(App::DocumentObject* obj, const char* subName, TopAbs_ShapeEnum hint) const { (void)hint; return Part::Feature::getShape( obj, Part::ShapeOption::NeedSubElement | Part::ShapeOption::ResolveLink | Part::ShapeOption::Transform, subName ); } // TODO:: add lengthX, lengthY (and lengthZ??) support // Methods for distances (edge length, two points, edge and a point double Measurement::length() const { double result = 0.0; int numRefs = References3D.getSize(); if (numRefs == 0) { Base::Console().error("Measurement::length - No 3D references available\n"); } else if (measureType == MeasureType::Invalid) { Base::Console().error("Measurement::length - measureType is Invalid\n"); } else { const std::vector& objects = References3D.getValues(); const std::vector& subElements = References3D.getSubValues(); if (measureType == MeasureType::Points || measureType == MeasureType::PointToPoint || 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::CircleArc || measureType == MeasureType::TwoCircles || measureType == MeasureType::CircleToEdge) { // Iterate through edges and calculate each length std::vector::const_iterator obj = objects.begin(); std::vector::const_iterator subEl = subElements.begin(); for (; obj != objects.end(); ++obj, ++subEl) { // Get the length of one edge TopoDS_Shape shape = getShape(*obj, (*subEl).c_str(), TopAbs_EDGE); if (shape.IsNull() || shape.Infinite()) { continue; } const TopoDS_Edge& edge = TopoDS::Edge(shape); BRepAdaptor_Curve curve(edge); switch (curve.GetType()) { case GeomAbs_Line: { gp_Pnt P1 = curve.Value(curve.FirstParameter()); gp_Pnt P2 = curve.Value(curve.LastParameter()); gp_XYZ diff = P2.XYZ() - P1.XYZ(); result += diff.Modulus(); break; } case GeomAbs_Circle: { double u = curve.FirstParameter(); double v = curve.LastParameter(); double radius = curve.Circle().Radius(); if (u > v) { // if arc is reversed std::swap(u, v); } double range = v - u; result += radius * range; break; } case GeomAbs_Ellipse: case GeomAbs_BSplineCurve: case GeomAbs_Hyperbola: case GeomAbs_Parabola: case GeomAbs_BezierCurve: { result += GCPnts_AbscissaPoint::Length(curve); break; } default: { throw Base::RuntimeError( "Measurement - length - Curve type not currently handled" ); } } } } } return result; } double Measurement::lineLineDistance() const { // We don't use delta() because BRepExtrema_DistShapeShape return minimum length between line // segment. Here we get the nominal distance between the infinite lines. double distance = 0.0; if (measureType != MeasureType::TwoParallelLines || References3D.getSize() != 2) { return distance; } const std::vector& objects = References3D.getValues(); const std::vector& subElements = References3D.getSubValues(); // Get the first line TopoDS_Shape shape1 = getShape(objects[0], subElements[0].c_str(), TopAbs_EDGE); const TopoDS_Edge& edge1 = TopoDS::Edge(shape1); BRepAdaptor_Curve curve1(edge1); // Get the second line TopoDS_Shape shape2 = getShape(objects[1], subElements[1].c_str(), TopAbs_EDGE); const TopoDS_Edge& edge2 = TopoDS::Edge(shape2); BRepAdaptor_Curve curve2(edge2); if (curve1.GetType() == GeomAbs_Line && curve2.GetType() == GeomAbs_Line) { gp_Lin line1 = curve1.Line(); gp_Lin line2 = curve2.Line(); gp_Pnt p1 = line1.Location(); gp_Pnt p2 = line2.Location(); // Create a vector from a point on line1 to a point on line2 gp_Vec lineVec(p1, p2); // The direction vector of one of the lines gp_Dir lineDir = line1.Direction(); // Project lineVec onto lineDir gp_Vec parallelComponent = lineVec.Dot(lineDir) * lineDir; // Compute the perpendicular component gp_Vec perpendicularComponent = lineVec - parallelComponent; // Distance is the magnitude of the perpendicular component distance = perpendicularComponent.Magnitude(); } else { Base::Console().error("Measurement::length - TwoLines measureType requires two lines\n"); } return distance; } double Measurement::circleCenterDistance() const { double distance = 0.0; if (References3D.getSize() != 2) { return distance; } const std::vector& objects = References3D.getValues(); const std::vector& 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) { return 0.0; } const auto& objects = References3D.getValues(); const auto& subElements = References3D.getSubValues(); // Get the first plane TopoDS_Shape shape1 = getShape(objects[0], subElements[0].c_str(), TopAbs_FACE); const TopoDS_Face& face1 = TopoDS::Face(shape1); BRepAdaptor_Surface surface1(face1); const gp_Pln& plane1 = surface1.Plane(); // Get the second plane TopoDS_Shape shape2 = getShape(objects[1], subElements[1].c_str(), TopAbs_FACE); const TopoDS_Face& face2 = TopoDS::Face(shape2); BRepAdaptor_Surface surface2(face2); const gp_Pln& plane2 = surface2.Plane(); // Distance between two parallel planes gp_Pnt pointOnPlane1 = plane1.Location(); gp_Dir normalToPlane1 = plane1.Axis().Direction(); gp_Pnt pointOnPlane2 = plane2.Location(); // Create a vector from a point on plane1 to a point on plane2 gp_Vec vectorBetweenPlanes(pointOnPlane1, pointOnPlane2); // Project this vector onto the plane normal double distance = Abs(vectorBetweenPlanes.Dot(normalToPlane1)); return distance; } double Measurement::cylinderAxisDistance() const { double distance = 0.0; if (References3D.getSize() != 2) { return distance; } const std::vector& objects = References3D.getValues(); const std::vector& 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 { // TODO: do these references arrive as obj+sub pairs or as a struct of obj + [subs]? const std::vector& objects = References3D.getValues(); const std::vector& subElements = References3D.getSubValues(); int numRefs = objects.size(); if (numRefs == 0) { throw Base::RuntimeError("No references available for angle measurement"); } else if (measureType == MeasureType::Invalid) { throw Base::RuntimeError("MeasureType is Invalid for angle measurement"); } else if (measureType == MeasureType::TwoLines) { // Only case that is supported is edge to edge // The angle between two skew lines is measured by the angle between one line (A) // and a line (B) with the direction of the second through a point on the first line. // Since we don't know if the directions of the lines point in the same general direction // we could get the angle we want or the supplementary angle. 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); BRepAdaptor_Curve curve1(TopoDS::Edge(shape1)); BRepAdaptor_Curve curve2(TopoDS::Edge(shape2)); if (curve1.GetType() == GeomAbs_Line && curve2.GetType() == GeomAbs_Line) { gp_Pnt pnt1First = curve1.Value(curve1.FirstParameter()); gp_Dir dir1 = curve1.Line().Direction(); gp_Dir dir2 = curve2.Line().Direction(); gp_Dir dir2r = curve2.Line().Direction().Reversed(); gp_Lin l1 = gp_Lin(pnt1First, dir1); // (A) gp_Lin l2 = gp_Lin(pnt1First, dir2); // (B) gp_Lin l2r = gp_Lin(pnt1First, dir2r); // (B') Standard_Real aRad = l1.Angle(l2); double aRadr = l1.Angle(l2r); return Base::toDegrees(std::min(aRad, aRadr)); } else { throw Base::RuntimeError("Measurement references must both be lines"); } } else { throw Base::RuntimeError("Can not compute angle measurement - too many references"); } } else if (measureType == MeasureType::Points) { // NOTE: we are calculating the 3d angle here, not the projected angle // ASSUMPTION: the references are in end-apex-end order if (numRefs == 3) { TopoDS_Shape shape0 = getShape(objects.at(0), subElements.at(0).c_str(), TopAbs_VERTEX); TopoDS_Shape shape1 = getShape(objects.at(1), subElements.at(1).c_str(), TopAbs_VERTEX); TopoDS_Shape shape2 = getShape(objects.at(1), subElements.at(2).c_str(), TopAbs_VERTEX); if (shape0.ShapeType() != TopAbs_VERTEX || shape1.ShapeType() != TopAbs_VERTEX || shape2.ShapeType() != TopAbs_VERTEX) { throw Base::RuntimeError("Measurement references for 3 point angle are not Vertex"); } gp_Pnt gEnd0 = BRep_Tool::Pnt(TopoDS::Vertex(shape0)); gp_Pnt gApex = BRep_Tool::Pnt(TopoDS::Vertex(shape1)); gp_Pnt gEnd1 = BRep_Tool::Pnt(TopoDS::Vertex(shape2)); gp_Dir gDir0 = gp_Dir(gEnd0.XYZ() - gApex.XYZ()); gp_Dir gDir1 = gp_Dir(gEnd1.XYZ() - gApex.XYZ()); gp_Lin line0 = gp_Lin(gEnd0, gDir0); gp_Lin line1 = gp_Lin(gEnd1, gDir1); double radians = line0.Angle(line1); return Base::toDegrees(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( std::min(aRad, std::fmod(aRad + std::numbers::pi, 2.0 * std::numbers::pi)) ); } } throw Base::RuntimeError("Unexpected error for angle measurement"); } double Measurement::radius() const { const std::vector& objects = References3D.getValues(); const std::vector& subElements = References3D.getSubValues(); int numRefs = References3D.getSize(); if (numRefs == 0) { Base::Console().error("Measurement::radius - No 3D references available\n"); } 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); BRepAdaptor_Curve curve(edge); if (curve.GetType() == GeomAbs_Circle) { return (double)curve.Circle().Radius(); } } 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); BRepAdaptor_Surface sf(face); if (sf.GetType() == GeomAbs_Cylinder) { return sf.Cylinder().Radius(); } else if (sf.GetType() == GeomAbs_Sphere) { return sf.Sphere().Radius(); } else if (sf.GetType() == GeomAbs_Torus) { return sf.Torus().MinorRadius(); } } Base::Console().error("Measurement::radius - Invalid References3D Provided\n"); return 0.0; } double Measurement::diameter() const { const std::vector& objects = References3D.getValues(); const std::vector& 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 { Base::Vector3d result; int numRefs = References3D.getSize(); if (numRefs == 0) { Base::Console().error("Measurement::delta - No 3D references available\n"); } else if (measureType == MeasureType::Invalid) { Base::Console().error("Measurement::delta - measureType is Invalid\n"); } else { const std::vector& objects = References3D.getValues(); const std::vector& subElements = References3D.getSubValues(); if (measureType == MeasureType::PointToPoint) { if (numRefs == 2) { // Keep separate case for two points to reduce need for complex algorithm TopoDS_Shape shape1 = getShape(objects.at(0), subElements.at(0).c_str(), TopAbs_VERTEX); TopoDS_Shape shape2 = getShape(objects.at(1), subElements.at(1).c_str(), TopAbs_VERTEX); const TopoDS_Vertex& vert1 = TopoDS::Vertex(shape1); const TopoDS_Vertex& vert2 = TopoDS::Vertex(shape2); gp_Pnt P1 = BRep_Tool::Pnt(vert1); gp_Pnt P2 = BRep_Tool::Pnt(vert2); gp_XYZ diff = P2.XYZ() - P1.XYZ(); return Base::Vector3d(diff.X(), diff.Y(), diff.Z()); } } else if (measureType == MeasureType::PointToEdge || 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()); TopoDS_Shape shape2 = getShape(objects.at(1), subElements.at(1).c_str()); BRepExtrema_DistShapeShape extrema(shape1, shape2); 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(); result = Base::Vector3d(diff.X(), diff.Y(), diff.Z()); } } } else if (measureType == MeasureType::Edges) { // Only case that is supported is straight line edge if (numRefs == 1) { 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_Line) { gp_Pnt P1 = curve.Value(curve.FirstParameter()); gp_Pnt P2 = curve.Value(curve.LastParameter()); gp_XYZ diff = P2.XYZ() - P1.XYZ(); result = Base::Vector3d(diff.X(), diff.Y(), diff.Z()); } } else 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); BRepAdaptor_Curve curve1(TopoDS::Edge(shape1)); BRepAdaptor_Curve curve2(TopoDS::Edge(shape2)); // Only permit line to line distance if (curve1.GetType() == GeomAbs_Line && curve2.GetType() == GeomAbs_Line) { BRepExtrema_DistShapeShape extrema(shape1, shape2); 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(); result = Base::Vector3d(diff.X(), diff.Y(), diff.Z()); } } } } else { Base::Console().error("Measurement::delta - measureType is not recognized\n"); } } return result; } double Measurement::volume() const { double result = 0.0; if (References3D.getSize() == 0) { Base::Console().error("Measurement::volume - No 3D references available\n"); } else if (measureType != MeasureType::Volumes) { Base::Console().error("Measurement::volume - measureType is not Volumes\n"); } else { const std::vector& objects = References3D.getValues(); const std::vector& subElements = References3D.getSubValues(); for (size_t i = 0; i < objects.size(); ++i) { GProp_GProps props = GProp_GProps(); TopoDS_Shape shape = getShape(objects[i], subElements[i].c_str()); if (shape.IsNull() || shape.Infinite()) { continue; } BRepGProp::VolumeProperties(shape, props); result += props.Mass(); } } return result; } double Measurement::area() const { double result = 0.0; if (References3D.getSize() == 0) { Base::Console().error("Measurement::area - No 3D references available\n"); } else if (measureType == MeasureType::Volumes || measureType == MeasureType::Surfaces || measureType == MeasureType::Cylinder || measureType == MeasureType::CylinderSection || measureType == MeasureType::TwoCylinders || measureType == MeasureType::Cone || measureType == MeasureType::Sphere || measureType == MeasureType::Torus || measureType == MeasureType::Plane) { const std::vector& objects = References3D.getValues(); const std::vector& subElements = References3D.getSubValues(); for (size_t i = 0; i < objects.size(); ++i) { GProp_GProps props; TopoDS_Shape shape = getShape(objects[i], subElements[i].c_str()); if (shape.IsNull() || shape.Infinite()) { continue; } BRepGProp::SurfaceProperties(shape, props); result += props.Mass(); // Area is obtained using Mass method for surface properties } } else { Base::Console().error("Measurement::area - measureType is not valid\n"); } return result; } Base::Vector3d Measurement::massCenter() const { Base::Vector3d result; int numRefs = References3D.getSize(); if (numRefs == 0) { Base::Console().error("Measurement::massCenter - No 3D references available\n"); } else if (measureType == MeasureType::Invalid) { Base::Console().error("Measurement::massCenter - measureType is Invalid\n"); } else { const std::vector& objects = References3D.getValues(); const std::vector& subElements = References3D.getSubValues(); GProp_GProps gprops = GProp_GProps(); if (measureType == MeasureType::Volumes) { // Iterate through edges and calculate each length std::vector::const_iterator obj = objects.begin(); std::vector::const_iterator subEl = subElements.begin(); for (; obj != objects.end(); ++obj, ++subEl) { // Compute inertia properties GProp_GProps props = GProp_GProps(); TopoDS_Shape shape = ShapeFinder::getLocatedShape(*(*obj), ""); if (shape.IsNull()) { continue; } BRepGProp::VolumeProperties(shape, props); gprops.Add(props); // Get inertia properties } gp_Pnt cog = gprops.CentreOfMass(); return Base::Vector3d(cog.X(), cog.Y(), cog.Z()); } else { Base::Console().error("Measurement::massCenter - measureType is not recognized\n"); } } return result; } bool Measurement::planesAreParallel() const { const std::vector& objects = References3D.getValues(); const std::vector& subElements = References3D.getSubValues(); std::vector planeNormals; for (size_t i = 0; i < objects.size(); ++i) { TopoDS_Shape refSubShape; try { refSubShape = Part::Feature::getShape( objects[i], Part::ShapeOption::NeedSubElement | Part::ShapeOption::ResolveLink | Part::ShapeOption::Transform, subElements[i].c_str() ); if (refSubShape.IsNull()) { return false; } } catch (Standard_Failure& e) { std::stringstream errorMsg; errorMsg << "Measurement - planesAreParallel - " << e.GetMessageString() << std::endl; throw Base::CADKernelError(e.GetMessageString()); } if (refSubShape.ShapeType() == TopAbs_FACE) { TopoDS_Face face = TopoDS::Face(refSubShape); BRepAdaptor_Surface sf(face); if (sf.GetType() == GeomAbs_Plane) { gp_Pln plane = sf.Plane(); gp_Dir normal = plane.Axis().Direction(); planeNormals.push_back(normal); } } } if (planeNormals.size() != 2) { return false; // Ensure exactly two planes are considered } // Check if normals are parallel (either identical or opposite) const gp_Dir& normal1 = planeNormals[0]; const gp_Dir& normal2 = planeNormals[1]; return normal1.IsParallel(normal2, Precision::Angular()); } bool Measurement::linesAreParallel() const { const std::vector& objects = References3D.getValues(); const std::vector& subElements = References3D.getSubValues(); if (References3D.getSize() != 2) { return false; } // Get the first line TopoDS_Shape shape1 = getShape(objects[0], subElements[0].c_str(), TopAbs_EDGE); if (shape1.IsNull()) { return false; } const TopoDS_Edge& edge1 = TopoDS::Edge(shape1); BRepAdaptor_Curve curve1(edge1); // Get the second line TopoDS_Shape shape2 = getShape(objects[1], subElements[1].c_str(), TopAbs_EDGE); if (shape2.IsNull()) { return false; } const TopoDS_Edge& edge2 = TopoDS::Edge(shape2); BRepAdaptor_Curve curve2(edge2); if (curve1.GetType() == GeomAbs_Line && curve2.GetType() == GeomAbs_Line) { gp_Lin line1 = curve1.Line(); gp_Lin line2 = curve2.Line(); gp_Dir dir1 = line1.Direction(); gp_Dir dir2 = line2.Direction(); // Check if lines are parallel if (dir1.IsParallel(dir2, Precision::Angular())) { return true; } } return false; } unsigned int Measurement::getMemSize() const { return 0; } PyObject* Measurement::getPyObject() { if (PythonObject.is(Py::_None())) { // ref counter is set to 1 PythonObject = Py::Object(new MeasurementPy(this), true); } return Py::new_reference_to(PythonObject); }