From 818e1224208fee1bfe1fa09ecf2c5ab2bd3c74de Mon Sep 17 00:00:00 2001 From: PaddleStroke Date: Tue, 30 Apr 2024 17:10:25 +0200 Subject: [PATCH] Core / Measure: Introduce QuickMeasure --- src/Gui/MainWindow.cpp | 12 + src/Gui/MainWindow.h | 1 + src/Mod/Measure/App/Measurement.cpp | 573 +++++++++++++++++----- src/Mod/Measure/App/Measurement.h | 26 +- src/Mod/Measure/App/MeasurementPy.xml | 20 + src/Mod/Measure/App/MeasurementPyImp.cpp | 44 ++ src/Mod/Measure/App/PreCompiled.h | 38 +- src/Mod/Measure/Gui/AppMeasureGui.cpp | 6 +- src/Mod/Measure/Gui/CMakeLists.txt | 7 + src/Mod/Measure/Gui/QuickMeasure.cpp | 156 ++++++ src/Mod/Measure/Gui/QuickMeasure.h | 57 +++ src/Mod/Measure/Gui/QuickMeasurePy.xml | 18 + src/Mod/Measure/Gui/QuickMeasurePyImp.cpp | 64 +++ src/Mod/Measure/InitGui.py | 2 + 14 files changed, 882 insertions(+), 142 deletions(-) create mode 100644 src/Mod/Measure/Gui/QuickMeasure.cpp create mode 100644 src/Mod/Measure/Gui/QuickMeasure.h create mode 100644 src/Mod/Measure/Gui/QuickMeasurePy.xml create mode 100644 src/Mod/Measure/Gui/QuickMeasurePyImp.cpp diff --git a/src/Gui/MainWindow.cpp b/src/Gui/MainWindow.cpp index 27627b9a58..1f454358ab 100644 --- a/src/Gui/MainWindow.cpp +++ b/src/Gui/MainWindow.cpp @@ -269,6 +269,7 @@ struct MainWindowP { DimensionWidget* sizeLabel; QLabel* actionLabel; + QLabel* rightSideLabel; QTimer* actionTimer; QTimer* statusTimer; QTimer* activityTimer; @@ -445,6 +446,7 @@ MainWindow::MainWindow(QWidget * parent, Qt::WindowFlags f) // labels and progressbar d->status = new StatusBarObserver(); d->actionLabel = new QLabel(statusBar()); + d->actionLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); // d->actionLabel->setMinimumWidth(120); d->sizeLabel = new DimensionWidget(statusBar()); @@ -454,6 +456,10 @@ MainWindow::MainWindow(QWidget * parent, Qt::WindowFlags f) statusBar()->addPermanentWidget(progressBar, 0); statusBar()->addPermanentWidget(d->sizeLabel, 0); + d->rightSideLabel = new QLabel(statusBar()); + d->rightSideLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); + statusBar()->addPermanentWidget(d->rightSideLabel); + auto hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/NotificationArea"); auto notificationAreaEnabled = hGrp->GetBool("NotificationAreaEnabled", true); @@ -464,6 +470,7 @@ MainWindow::MainWindow(QWidget * parent, Qt::WindowFlags f) notificationArea->setStyleSheet(QStringLiteral("text-align:left;")); statusBar()->addPermanentWidget(notificationArea); } + // clears the action label d->actionTimer = new QTimer( this ); d->actionTimer->setObjectName(QString::fromLatin1("actionTimer")); @@ -2431,6 +2438,11 @@ void MainWindow::showMessage(const QString& message, int timeout) { d->actionTimer->stop(); } +void MainWindow::setRightSideMessage(const QString& message) +{ + d->rightSideLabel->setText(message.simplified()); +} + void MainWindow::showStatus(int type, const QString& message) { if(QApplication::instance()->thread() != QThread::currentThread()) { diff --git a/src/Gui/MainWindow.h b/src/Gui/MainWindow.h index 01098b69b5..5f4ba6ede7 100644 --- a/src/Gui/MainWindow.h +++ b/src/Gui/MainWindow.h @@ -265,6 +265,7 @@ public Q_SLOTS: void statusMessageChanged(); void showMessage (const QString & message, int timeout = 0); + void setRightSideMessage(const QString & message); // Set main window title void setWindowTitle(const QString& string); diff --git a/src/Mod/Measure/App/Measurement.cpp b/src/Mod/Measure/App/Measurement.cpp index 1d8fd954f4..07f135f6f5 100644 --- a/src/Mod/Measure/App/Measurement.cpp +++ b/src/Mod/Measure/App/Measurement.cpp @@ -24,16 +24,22 @@ #ifndef _PreComp_ # include # include +# include # include # include # include +# include # include +# include +# include +# include # include # include # include # include #endif + #include #include #include @@ -55,7 +61,7 @@ TYPESYSTEM_SOURCE(Measure::Measurement, Base::BaseClass) Measurement::Measurement() { - measureType = Invalid; + measureType = MeasureType::Invalid; References3D.setScope(App::LinkScope::Global); } @@ -66,7 +72,7 @@ void Measurement::clear() std::vector Objects; std::vector SubElements; References3D.setValues(Objects, SubElements); - measureType = Invalid; + measureType = MeasureType::Invalid; } bool Measurement::has3DReferences() @@ -91,11 +97,11 @@ int Measurement::addReference3D(App::DocumentObject *obj, const char* subName) References3D.setValues(objects, subElements); - measureType = getType(); + measureType = findType(); return References3D.getSize(); } -MeasureType Measurement::getType() +MeasureType Measurement::findType() { const std::vector &objects = References3D.getValues(); const std::vector &subElements = References3D.getSubValues(); @@ -107,7 +113,14 @@ MeasureType Measurement::getType() int verts = 0; int edges = 0; + int lines = 0; + int circles = 0; int faces = 0; + int planes = 0; + int cylinders = 0; + int cones = 0; + int torus = 0; + int spheres = 0; int vols = 0; for (;obj != objects.end(); ++obj, ++subEl) { @@ -115,13 +128,15 @@ MeasureType Measurement::getType() // Check if solid object if(strcmp((*subEl).c_str(), "") == 0) { vols++; - } else { + } + else { TopoDS_Shape refSubShape; try { refSubShape = Part::Feature::getShape(*obj,(*subEl).c_str(),true); - if(refSubShape.IsNull()) - return Invalid; + if(refSubShape.IsNull()){ + return MeasureType::Invalid; + } } catch (Standard_Failure& e) { std::stringstream errorMsg; @@ -139,11 +154,38 @@ MeasureType Measurement::getType() 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) { + circles++; + } } 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) { + cylinders++; + } + else if (sf.GetType() == GeomAbs_Sphere) { + spheres++; + } + else if (sf.GetType() == GeomAbs_Cone) { + cones++; + } + else if (sf.GetType() == GeomAbs_Torus) { + torus++; + } } break; default: @@ -154,46 +196,105 @@ MeasureType Measurement::getType() if(vols > 0) { if(verts > 0 || edges > 0 || faces > 0) { - mode = Invalid; - } else { - mode = Volumes; + mode = MeasureType::Invalid; } - } else if(faces > 0) { + else { + mode = MeasureType::Volumes; + } + } + else if(faces > 0) { if(verts > 0 || edges > 0) { - if(faces > 1 && verts > 1 && edges > 0) { - mode = Invalid; - } else { - // One Surface and One Point - mode = PointToSurface; + if (faces == 1 && verts == 1) { + mode = MeasureType::PointToSurface; + } + else { + mode = MeasureType::Invalid; } - } else { - mode = Surfaces; } - } else if(edges > 0) { + 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 (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 && edges > 0) { - mode = Invalid; - } else { - mode = PointToEdge; + mode = MeasureType::Invalid; + } + else { + mode = MeasureType::PointToEdge; } - } else { - mode = Edges; } - } else if (verts > 0) { - mode = Points; - } else { - mode = Invalid; + 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 { + 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) const { -// Base::Console().Message("Meas::getShape(%s, %s)\n", obj->getNameInDocument(), subName); //temporary fix to get "Vertex7" from "Body003.Pocket020.Vertex7" //when selected, Body features are provided as featureName and subNameAndIndex //other sources provide the full extended name with index + if (strcmp(subName, "") == 0) { + return Part::Feature::getShape(obj); + } std::string workingSubName(subName); size_t lastDot = workingSubName.rfind('.'); if (lastDot != std::string::npos) { @@ -229,20 +330,24 @@ double Measurement::length() const int numRefs = References3D.getSize(); if(numRefs == 0) { Base::Console().Error("Measurement::length - No 3D references available\n"); - } else if (measureType == Invalid) { + } + else if (measureType == MeasureType::Invalid) { Base::Console().Error("Measurement::length - measureType is Invalid\n"); - } else { + } + else { const std::vector &objects = References3D.getValues(); const std::vector &subElements = References3D.getSubValues(); - if(measureType == Points || - measureType == PointToEdge || - measureType == PointToSurface) { + if(measureType == MeasureType::Points || + measureType == MeasureType::PointToPoint || + measureType == MeasureType::PointToEdge || + measureType == MeasureType::PointToSurface) { Base::Vector3d diff = this->delta(); - //return diff.Length(); result = diff.Length(); - } else if(measureType == Edges) { + } + else if(measureType == MeasureType::Edges || measureType == MeasureType::Line + || measureType == MeasureType::TwoLines || measureType == MeasureType::Circle) { // Iterate through edges and calculate each length std::vector::const_iterator obj = objects.begin(); @@ -287,11 +392,99 @@ double Measurement::length() const } } //end switch } //end for - } //end measureType == Edges + } } 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()); + 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()); + 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::planePlaneDistance() const { + if (measureType != MeasureType::TwoPlanes || References3D.getSize() != 2) { + return 0.0; + } + + const auto& objects = References3D.getValues(); + const auto& subElements = References3D.getSubValues(); + + std::vector planes; + + // Get the first plane + TopoDS_Shape shape1 = getShape(objects[0], subElements[0].c_str()); + 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()); + 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::angle(const Base::Vector3d & /*param*/) const { //TODO: do these references arrive as obj+sub pairs or as a struct of obj + [subs]? @@ -300,89 +493,107 @@ double Measurement::angle(const Base::Vector3d & /*param*/) const int numRefs = objects.size(); if(numRefs == 0) { throw Base::RuntimeError("No references available for angle measurement"); - } else if (measureType == Invalid) { + } + else if (measureType == MeasureType::Invalid) { throw Base::RuntimeError("MeasureType is Invalid for angle measurement"); - } else { - if(measureType == Edges) { - //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()); - TopoDS_Shape shape2 = getShape(objects.at(1), subElements.at(1).c_str()); + } + 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()); + TopoDS_Shape shape2 = getShape(objects.at(1), subElements.at(1).c_str()); - BRepAdaptor_Curve curve1(TopoDS::Edge(shape1)); - BRepAdaptor_Curve curve2(TopoDS::Edge(shape2)); + BRepAdaptor_Curve curve1(TopoDS::Edge(shape1)); + BRepAdaptor_Curve curve2(TopoDS::Edge(shape2)); - if(curve1.GetType() == GeomAbs_Line && - curve2.GetType() == GeomAbs_Line) { + 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_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 std::min(aRad, aRadr) * 180 / M_PI; - } else { - throw Base::RuntimeError("Measurement references must both be lines"); - } - } else { - throw Base::RuntimeError("Can not compute angle measurement - too many references"); + 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 std::min(aRad, aRadr) * 180 / M_PI; } - } else if (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()); - TopoDS_Shape shape1 = getShape(objects.at(1), subElements.at(1).c_str()); - TopoDS_Shape shape2 = getShape(objects.at(1), subElements.at(2).c_str()); - 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 radians * 180 / M_PI; + 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()); + TopoDS_Shape shape1 = getShape(objects.at(1), subElements.at(1).c_str()); + TopoDS_Shape shape2 = getShape(objects.at(1), subElements.at(2).c_str()); + 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 radians * 180 / M_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) { - throw Base::RuntimeError("Measurement - radius - No References3D provided"); - } else { - if(numRefs == 1 || measureType == Edges) { - const std::vector &objects = References3D.getValues(); - const std::vector &subElements = References3D.getSubValues(); + Base::Console().Error("Measurement::radius - No 3D references available\n"); + } + else if (measureType == MeasureType::Circle) { + TopoDS_Shape shape = getShape(objects.at(0), subElements.at(0).c_str()); + const TopoDS_Edge& edge = TopoDS::Edge(shape); - TopoDS_Shape shape = getShape(objects.at(0), subElements.at(0).c_str()); - const TopoDS_Edge& edge = TopoDS::Edge(shape); - - BRepAdaptor_Curve curve(edge); - if(curve.GetType() == GeomAbs_Circle) { - return (double) curve.Circle().Radius(); - } + BRepAdaptor_Curve curve(edge); + if(curve.GetType() == GeomAbs_Circle) { + return (double) curve.Circle().Radius(); } } - throw Base::RuntimeError("Measurement - radius - Invalid References3D Provided"); + else if (measureType == MeasureType::Cylinder || measureType == MeasureType::Sphere || measureType == MeasureType::Torus) { + TopoDS_Shape shape = getShape(objects.at(0), subElements.at(0).c_str()); + 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; } Base::Vector3d Measurement::delta() const @@ -391,13 +602,15 @@ Base::Vector3d Measurement::delta() const int numRefs = References3D.getSize(); if (numRefs == 0) { Base::Console().Error("Measurement::delta - No 3D references available\n"); - } else if (measureType == Invalid) { + } + else if (measureType == MeasureType::Invalid) { Base::Console().Error("Measurement::delta - measureType is Invalid\n"); - } else { + } + else { const std::vector &objects = References3D.getValues(); const std::vector &subElements = References3D.getSubValues(); - if(measureType == Points) { + 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()); @@ -411,8 +624,8 @@ Base::Vector3d Measurement::delta() const gp_XYZ diff = P2.XYZ() - P1.XYZ(); return Base::Vector3d(diff.X(), diff.Y(), diff.Z()); } - } else if(measureType == PointToEdge || - measureType == PointToSurface) { + } + else if(measureType == MeasureType::PointToEdge || measureType == MeasureType::PointToSurface) { // 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()); @@ -427,10 +640,10 @@ Base::Vector3d Measurement::delta() const gp_Pnt P2 = extrema.PointOnShape2(1); gp_XYZ diff = P2.XYZ() - P1.XYZ(); result = Base::Vector3d(diff.X(), diff.Y(), diff.Z()); -// return Base::Vector3d(diff.X(), diff.Y(), diff.Z()); } } - } else if(measureType == Edges) { + } + 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()); @@ -442,9 +655,9 @@ Base::Vector3d Measurement::delta() const gp_Pnt P2 = curve.Value(curve.LastParameter()); gp_XYZ diff = P2.XYZ() - P1.XYZ(); result = Base::Vector3d(diff.X(), diff.Y(), diff.Z()); -// return Base::Vector3d(diff.X(), diff.Y(), diff.Z()); } - } else if(numRefs == 2) { + } + else 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()); @@ -463,18 +676,64 @@ Base::Vector3d Measurement::delta() const gp_Pnt P2 = extrema.PointOnShape2(1); gp_XYZ diff = P2.XYZ() - P1.XYZ(); result = Base::Vector3d(diff.X(), diff.Y(), diff.Z()); -// return Base::Vector3d(diff.X(), diff.Y(), diff.Z()); } } } - } else { + } + else { Base::Console().Error("Measurement::delta - measureType is not recognized\n"); } } -// throw Base::ValueError("An invalid selection was made"); 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(); + BRepGProp::VolumeProperties(getShape(objects[i], subElements[i].c_str()), 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::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; + BRepGProp::SurfaceProperties(getShape(objects[i], subElements[i].c_str()), 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 { @@ -482,14 +741,16 @@ Base::Vector3d Measurement::massCenter() const int numRefs = References3D.getSize(); if (numRefs == 0) { Base::Console().Error("Measurement::massCenter - No 3D references available\n"); - } else if (measureType == Invalid) { + } + else if (measureType == MeasureType::Invalid) { Base::Console().Error("Measurement::massCenter - measureType is Invalid\n"); - } else { + } + else { const std::vector &objects = References3D.getValues(); const std::vector &subElements = References3D.getSubValues(); GProp_GProps gprops = GProp_GProps(); - if(measureType == Volumes) { + 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(); @@ -510,7 +771,8 @@ Base::Vector3d Measurement::massCenter() const gp_Pnt cog = gprops.CentreOfMass(); return Base::Vector3d(cog.X(), cog.Y(), cog.Z()); - } else { + } + else { Base::Console().Error("Measurement::massCenter - measureType is not recognized\n"); // throw Base::ValueError("Measurement - massCenter - Invalid References3D Provided"); } @@ -518,6 +780,83 @@ Base::Vector3d Measurement::massCenter() const 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], subElements[i].c_str(), true); + 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()); + 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()); + 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; diff --git a/src/Mod/Measure/App/Measurement.h b/src/Mod/Measure/App/Measurement.h index 3faa2485e5..340125efd5 100644 --- a/src/Mod/Measure/App/Measurement.h +++ b/src/Mod/Measure/App/Measurement.h @@ -36,10 +36,20 @@ class TopoDS_Shape; namespace Measure { - enum MeasureType { + enum class MeasureType { Volumes, // Measure the Volume(s) Edges, // Measure the Edge(s) + Line, // One Line + 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 Points, PointToPoint, // Measure between TWO points PointToEdge, // Measure between ONE point and ONE edge @@ -66,6 +76,7 @@ public: int addReference3D(App::DocumentObject* obj, const char *subName); MeasureType getType(); + MeasureType findType(); // from base class PyObject *getPyObject() override; @@ -74,6 +85,8 @@ public: // Methods for distances (edge length, two points, edge and a point double length() const; Base::Vector3d delta() const; //when would client use delta?? + double lineLineDistance() const; + double planePlaneDistance() const; // Calculates the radius for an arc or circular edge double radius() const; @@ -81,11 +94,20 @@ public: // Calculates the angle between two edges double angle(const Base::Vector3d ¶m = Base::Vector3d(0,0,0)) const; //param is never used??? - // Calculate volumetric/mass properties + // Calculate the center of mass Base::Vector3d massCenter() const; + // Calculate the volume of selected volumes + double volume() const; + + // Calculate the area of selection + double area() const; + static Base::Vector3d toVector3d(const gp_Pnt gp) { return Base::Vector3d(gp.X(), gp.Y(), gp.Z()); } + bool planesAreParallel() const; + bool linesAreParallel() const; + protected: TopoDS_Shape getShape(App::DocumentObject *obj , const char *subName) const; diff --git a/src/Mod/Measure/App/MeasurementPy.xml b/src/Mod/Measure/App/MeasurementPy.xml index ae4f37b0f9..d6a994409d 100644 --- a/src/Mod/Measure/App/MeasurementPy.xml +++ b/src/Mod/Measure/App/MeasurementPy.xml @@ -40,6 +40,26 @@ measure the length of the references + + + measure the volume of the references + + + + + measure the area of the references + + + + + measure the line-Line Distance of the references. Returns 0 if references are not 2 lines. + + + + + measure the plane-plane distance of the references. Returns 0 if references are not 2 planes. + + measure the angle between two edges diff --git a/src/Mod/Measure/App/MeasurementPyImp.cpp b/src/Mod/Measure/App/MeasurementPyImp.cpp index 2843793734..092b53b6db 100644 --- a/src/Mod/Measure/App/MeasurementPyImp.cpp +++ b/src/Mod/Measure/App/MeasurementPyImp.cpp @@ -126,6 +126,50 @@ PyObject* MeasurementPy::length(PyObject *args) return Py::new_reference_to(length); } +PyObject* MeasurementPy::lineLineDistance(PyObject *args) +{ + if (!PyArg_ParseTuple(args, "")) + return nullptr; + + Py::Float length; + length = this->getMeasurementPtr()->lineLineDistance(); + + return Py::new_reference_to(length); +} + +PyObject* MeasurementPy::planePlaneDistance(PyObject *args) +{ + if (!PyArg_ParseTuple(args, "")) + return nullptr; + + Py::Float length; + length = this->getMeasurementPtr()->planePlaneDistance(); + + return Py::new_reference_to(length); +} + +PyObject* MeasurementPy::volume(PyObject *args) +{ + if (!PyArg_ParseTuple(args, "")) + return nullptr; + + Py::Float length; + length = this->getMeasurementPtr()->volume(); + + return Py::new_reference_to(length); +} + +PyObject* MeasurementPy::area(PyObject *args) +{ + if (!PyArg_ParseTuple(args, "")) + return nullptr; + + Py::Float length; + length = this->getMeasurementPtr()->area(); + + return Py::new_reference_to(length); +} + PyObject* MeasurementPy::radius(PyObject *args) { if (!PyArg_ParseTuple(args, "")) diff --git a/src/Mod/Measure/App/PreCompiled.h b/src/Mod/Measure/App/PreCompiled.h index c3312d4d1c..534906fef8 100644 --- a/src/Mod/Measure/App/PreCompiled.h +++ b/src/Mod/Measure/App/PreCompiled.h @@ -20,36 +20,30 @@ * * ***************************************************************************/ -#ifndef __PRECOMPILED__ -#define __PRECOMPILED__ +#ifndef MEASUREGUI_PRECOMPILED_H +#define MEASUREGUI_PRECOMPILED_H #include -#ifdef FC_OS_WIN32 -#define WIN32_LEAN_AND_MEAN -#ifndef NOMINMAX -# define NOMINMAX -#endif -#endif +#include #ifdef _PreComp_ // standard +#include +#include + +// STL +#include +#include #include +#include +#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +// OpenCasCade +#include -#elif defined(FC_OS_WIN32) -# include -#endif // _PreComp_ -#endif +#endif //_PreComp_ + +#endif // MEASUREGUI_PRECOMPILED_H diff --git a/src/Mod/Measure/Gui/AppMeasureGui.cpp b/src/Mod/Measure/Gui/AppMeasureGui.cpp index 41bbb81712..ec717a13bc 100644 --- a/src/Mod/Measure/Gui/AppMeasureGui.cpp +++ b/src/Mod/Measure/Gui/AppMeasureGui.cpp @@ -30,6 +30,8 @@ #include #include "DlgPrefsMeasureAppearanceImp.h" +#include "QuickMeasure.h" +#include "QuickMeasurePy.h" #include "ViewProviderMeasureAngle.h" #include "ViewProviderMeasureDistance.h" #include "ViewProviderMeasureBase.h" @@ -86,7 +88,7 @@ PyMOD_INIT_FUNC(MeasureGui) CreateMeasureCommands(); MeasureGui::ViewProviderMeasureBase ::init(); - MeasureGui::ViewProviderMeasure ::init(); + MeasureGui::ViewProviderMeasure ::init(); MeasureGui::ViewProviderMeasureAngle ::init(); MeasureGui::ViewProviderMeasureDistance ::init(); @@ -95,5 +97,7 @@ PyMOD_INIT_FUNC(MeasureGui) // Q_INIT_RESOURCE(Measure); + Base::Interpreter().addType(&MeasureGui::QuickMeasurePy::Type, mod, "QuickMeasure"); + PyMOD_Return(mod); } diff --git a/src/Mod/Measure/Gui/CMakeLists.txt b/src/Mod/Measure/Gui/CMakeLists.txt index 343906e412..e6b491ae28 100644 --- a/src/Mod/Measure/Gui/CMakeLists.txt +++ b/src/Mod/Measure/Gui/CMakeLists.txt @@ -16,6 +16,7 @@ link_directories(${OCC_LIBRARY_DIR}) set(MeasureGui_LIBS Measure + #Part FreeCADGui ) @@ -36,6 +37,8 @@ SET(MeasureGui_UIC_SRCS DlgPrefsMeasureAppearanceImp.ui ) +generate_from_xml(QuickMeasurePy) + SET(MeasureGui_SRCS ${CMAKE_SOURCE_DIR}/src/Mod/Measure/InitGui.py ${MeasureGui_SRCS} @@ -44,6 +47,10 @@ SET(MeasureGui_SRCS Resources/Measure.qrc PreCompiled.cpp PreCompiled.h + QuickMeasurePy.xml + QuickMeasurePyImp.cpp + QuickMeasure.cpp + QuickMeasure.h ViewProviderMeasureBase.cpp ViewProviderMeasureBase.h ViewProviderMeasureAngle.cpp diff --git a/src/Mod/Measure/Gui/QuickMeasure.cpp b/src/Mod/Measure/Gui/QuickMeasure.cpp new file mode 100644 index 0000000000..12ac2dcb43 --- /dev/null +++ b/src/Mod/Measure/Gui/QuickMeasure.cpp @@ -0,0 +1,156 @@ +/*************************************************************************** + * Copyright (c) 2023 Pierre-Louis Boyer * + * * + * 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 "PreCompiled.h" + +#ifndef _PreComp_ +#include +#include +#include +#include +#include +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "QuickMeasure.h" + +using namespace Measure; +using namespace MeasureGui; + +QuickMeasure::QuickMeasure(QObject* parent) : QObject(parent) +{ + measurement = new Measure::Measurement(); +} + +QuickMeasure::~QuickMeasure() +{ + delete measurement; +} + + +void QuickMeasure::onSelectionChanged(const Gui::SelectionChanges& msg) +{ + if (msg.Type == Gui::SelectionChanges::SetPreselect || msg.Type == Gui::SelectionChanges::RmvPreselect) { + return; + } + + Gui::Document* doc = Gui::Application::Instance->activeDocument(); + if (!doc) { return; } + + measurement->clear(); + + std::vector subShapes; + + std::vector docsToMove; + for (auto& selObj : Gui::Selection().getSelectionEx()) { + App::DocumentObject* obj = selObj.getObject(); + const std::vector subNames = selObj.getSubNames(); + if (subNames.empty()) { + measurement->addReference3D(obj, ""); + } + else { + for (auto& subName : subNames) { + measurement->addReference3D(obj, subName); + } + } + } + + MeasureType mtype = measurement->getType(); + if (mtype == MeasureType::Volumes) { + Base::Quantity area(measurement->area(), Base::Unit::Area); + Base::Quantity vol(measurement->volume(), Base::Unit::Volume); + print(tr("Volume: %1, Area: %2").arg(vol.getSafeUserString()).arg(area.getSafeUserString())); + } + else if (mtype == MeasureType::Surfaces) { + Base::Quantity area(measurement->area(), Base::Unit::Area); + print(tr("Total area: %1").arg(area.getUserString())); + } + else if (mtype == MeasureType::TwoPlanes) { + Base::Quantity dist(measurement->planePlaneDistance(), Base::Unit::Length); + print(tr("Nominal distance: %1").arg(dist.getSafeUserString())); + } + else if (mtype == MeasureType::Cone || mtype == MeasureType::Plane) { + Base::Quantity area(measurement->area(), Base::Unit::Area); + print(tr("Area: %1").arg(area.getUserString())); + } + else if (mtype == MeasureType::Cylinder || mtype == MeasureType::Sphere || mtype == MeasureType::Torus) { + Base::Quantity area(measurement->area(), Base::Unit::Area); + Base::Quantity rad(measurement->radius(), Base::Unit::Length); + print(tr("Area: %1, Radius: %2").arg(area.getSafeUserString()).arg(rad.getSafeUserString())); + } + else if (mtype == MeasureType::Edges) { + Base::Quantity dist(measurement->length(), Base::Unit::Length); + print(tr("Total length: %1").arg(dist.getSafeUserString())); + } + else if (mtype == MeasureType::TwoParallelLines) { + Base::Quantity dist(measurement->lineLineDistance(), Base::Unit::Length); + print(tr("Nominal distance: %1").arg(dist.getSafeUserString())); + } + else if (mtype == MeasureType::TwoLines) { + Base::Quantity angle(measurement->angle(), Base::Unit::Length); + Base::Quantity dist(measurement->length(), Base::Unit::Length); + print(tr("Angle: %1, Total length: %2").arg(angle.getSafeUserString()).arg(dist.getSafeUserString())); + } + else if (mtype == MeasureType::Line) { + Base::Quantity dist(measurement->length(), Base::Unit::Length); + print(tr("Length: %1").arg(dist.getSafeUserString())); + } + else if (mtype == MeasureType::Circle) { + Base::Quantity dist(measurement->radius(), Base::Unit::Length); + print(tr("Radius: %1").arg(dist.getSafeUserString())); + } + else if (mtype == MeasureType::PointToPoint) { + Base::Quantity dist(measurement->length(), Base::Unit::Length); + print(tr("Distance: %1").arg(dist.getSafeUserString())); + } + else if (mtype == MeasureType::PointToEdge || mtype == MeasureType::PointToSurface) { + Base::Quantity dist(measurement->length(), Base::Unit::Length); + print(tr("Minimum distance: %1").arg(dist.getSafeUserString())); + } + else { + print(QString::fromLatin1("")); + } + +} + +void QuickMeasure::print(const QString& message) +{ + Gui::getMainWindow()->setRightSideMessage(message); +} + + +#include "moc_QuickMeasure.cpp" \ No newline at end of file diff --git a/src/Mod/Measure/Gui/QuickMeasure.h b/src/Mod/Measure/Gui/QuickMeasure.h new file mode 100644 index 0000000000..bef411e984 --- /dev/null +++ b/src/Mod/Measure/Gui/QuickMeasure.h @@ -0,0 +1,57 @@ +/*************************************************************************** + * Copyright (c) 2023 Pierre-Louis Boyer * + * * + * 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 * + * * + ***************************************************************************/ + + +#ifndef MEASUREGUI_QUICKMEASURE_H +#define MEASUREGUI_QUICKMEASURE_H + +#include + +#include + +#include +namespace Measure { + class Measurement; +} + +namespace MeasureGui { + +class QuickMeasure : public QObject, Gui::SelectionObserver +{ + Q_OBJECT + +public: + explicit QuickMeasure(QObject* parent = nullptr); + ~QuickMeasure() override; + +private: + void onSelectionChanged(const Gui::SelectionChanges& msg) override; + + void print(const QString& message); + + Measure::Measurement* measurement; + +}; + +} //namespace MeasureGui + +#endif // MEASUREGUI_QUICKMEASURE_H diff --git a/src/Mod/Measure/Gui/QuickMeasurePy.xml b/src/Mod/Measure/Gui/QuickMeasurePy.xml new file mode 100644 index 0000000000..ff244b094e --- /dev/null +++ b/src/Mod/Measure/Gui/QuickMeasurePy.xml @@ -0,0 +1,18 @@ + + + + + + Selection Observer for the QuickMeasure label. + + + diff --git a/src/Mod/Measure/Gui/QuickMeasurePyImp.cpp b/src/Mod/Measure/Gui/QuickMeasurePyImp.cpp new file mode 100644 index 0000000000..b88ceeeebf --- /dev/null +++ b/src/Mod/Measure/Gui/QuickMeasurePyImp.cpp @@ -0,0 +1,64 @@ +/*************************************************************************** + * Copyright (c) 2023 Pierre-Louis Boyer * + * * + * 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 "PreCompiled.h" +#ifndef _PreComp_ +#endif + +#include +#include +#include + +// inclusion of the generated files (generated out of QuickMeasurePy.xml) +#include "QuickMeasurePy.h" +#include "QuickMeasurePy.cpp" + + +using namespace MeasureGui; + +// returns a string which represents the object e.g. when printed in python +std::string QuickMeasurePy::representation() const +{ + return ""; +} + +PyObject *QuickMeasurePy::PyMake(struct _typeobject *, PyObject *, PyObject *) // Python wrapper +{ + // create a new instance of BoundBoxPy and the Twin object + return new QuickMeasurePy(new QuickMeasure); +} + +// constructor method +int QuickMeasurePy::PyInit(PyObject* /*args*/, PyObject* /*kwd*/) +{ + return 0; +} + +PyObject *QuickMeasurePy::getCustomAttributes(const char* /*attr*/) const +{ + return nullptr; +} + +int QuickMeasurePy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/) +{ + return 0; +} diff --git a/src/Mod/Measure/InitGui.py b/src/Mod/Measure/InitGui.py index 22952279b8..08a138e89a 100644 --- a/src/Mod/Measure/InitGui.py +++ b/src/Mod/Measure/InitGui.py @@ -41,3 +41,5 @@ FreeCAD.MeasureManager.addMeasureType( MeasureCOM, ) +import MeasureGui +MeasureGui.QuickMeasure() \ No newline at end of file