From cb0e5b6f9c691d749e5a1f6f65f8e447b250620e Mon Sep 17 00:00:00 2001 From: hlorus Date: Sun, 14 Jan 2024 21:00:03 +0100 Subject: [PATCH 01/11] [Base] Expose type's "getModuleName" method --- src/Base/Type.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Base/Type.h b/src/Base/Type.h index cf138ade2f..ee120c7333 100644 --- a/src/Base/Type.h +++ b/src/Base/Type.h @@ -128,7 +128,6 @@ public: static void init(); static void destruct(); -protected: static std::string getModuleName(const char* ClassName); From c054fe64b31b6a8f019bfc7ceeb8961fffc22f48 Mon Sep 17 00:00:00 2001 From: hlorus Date: Sun, 14 Jan 2024 21:05:20 +0100 Subject: [PATCH 02/11] [Part] Move ArcEngine into own file in Gui module --- src/Gui/ArcEngine.cpp | 135 ++++++++++++++++++++++++++++++++ src/Gui/ArcEngine.h | 73 +++++++++++++++++ src/Gui/CMakeLists.txt | 2 + src/Gui/SoFCDB.cpp | 3 + src/Mod/Part/Gui/AppPartGui.cpp | 1 - 5 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 src/Gui/ArcEngine.cpp create mode 100644 src/Gui/ArcEngine.h diff --git a/src/Gui/ArcEngine.cpp b/src/Gui/ArcEngine.cpp new file mode 100644 index 0000000000..a0edab0d00 --- /dev/null +++ b/src/Gui/ArcEngine.cpp @@ -0,0 +1,135 @@ +/*************************************************************************** + * Copyright (c) 2013 Thomas Anderson * + * * + * 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" + +#include "ArcEngine.h" +#include + +# include +# include +# include +# include +# include + +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include + +using namespace Gui; + + + +SO_ENGINE_SOURCE(ArcEngine) + +ArcEngine::ArcEngine() +{ + SO_ENGINE_CONSTRUCTOR(ArcEngine); + + SO_ENGINE_ADD_INPUT(radius, (10.0)); + SO_ENGINE_ADD_INPUT(angle, (1.0)); + SO_ENGINE_ADD_INPUT(deviation, (0.25)); + + SO_ENGINE_ADD_OUTPUT(points, SoMFVec3f); + SO_ENGINE_ADD_OUTPUT(pointCount, SoSFInt32); + SO_ENGINE_ADD_OUTPUT(midpoint, SoSFVec3f); +} + +void ArcEngine::initClass() +{ + SO_ENGINE_INIT_CLASS(ArcEngine, SoEngine, "Engine"); +} + +void ArcEngine::evaluate() +{ + float angle = abs(this->angle.getValue()); + + if (radius.getValue() < std::numeric_limits::epsilon() || + deviation.getValue() < std::numeric_limits::epsilon()) + { + defaultValues(); + return; + } + + float deviationAngle(acos((radius.getValue() - deviation.getValue()) / radius.getValue())); + std::vector tempPoints; + int segmentCount; + if (deviationAngle >= angle) { + segmentCount = 1; + } + else { + segmentCount = static_cast(angle / deviationAngle) + 1; + if (segmentCount < 2) { + defaultValues(); + return; + } + } + float angleIncrement = (this->angle.getValue() > 0 ? angle : -angle) / static_cast(segmentCount); + for (int index = 0; index < segmentCount + 1; ++index) + { + SbVec3f currentNormal(1.0, 0.0, 0.0); + float currentAngle = index * angleIncrement; + SbRotation rotation(SbVec3f(0.0, 0.0, 1.0), currentAngle); + rotation.multVec(currentNormal, currentNormal); + tempPoints.push_back(currentNormal * radius.getValue()); + } + int tempCount = tempPoints.size(); //for macro. + SO_ENGINE_OUTPUT(points, SoMFVec3f, setNum(tempCount)); + SO_ENGINE_OUTPUT(pointCount, SoSFInt32, setValue(tempCount)); + std::vector::const_iterator it; + for (it = tempPoints.begin(); it != tempPoints.end(); ++it) + { + int currentIndex = it-tempPoints.begin(); //for macro. + SbVec3f temp(*it); //for macro + SO_ENGINE_OUTPUT(points, SoMFVec3f, set1Value(currentIndex, temp)); + } + + // Get Midpoint + float a = angle / 2; + SbRotation rot(SbVec3f(0.0, 0.0, 1.0), a); + SbVec3f midPnt(1.0, 0.0, 0.0); + rot.multVec(midPnt, midPnt); + midPnt = midPnt * radius.getValue(); + + SO_ENGINE_OUTPUT(midpoint, SoSFVec3f, setValue(midPnt)); + +} + +void ArcEngine::defaultValues() +{ + //just some non-failing info. + SO_ENGINE_OUTPUT(points, SoMFVec3f, setNum(2)); + SbVec3f point1(10.0, 0.0, 0.0); + SO_ENGINE_OUTPUT(points, SoMFVec3f, set1Value(0, point1)); + SbVec3f point2(7.07f, 7.07f, 0.0); + SO_ENGINE_OUTPUT(points, SoMFVec3f, set1Value(1, point2)); + SO_ENGINE_OUTPUT(pointCount, SoSFInt32, setValue(2)); + SbVec3f point3(7.07f, 7.07f, 0.0); + SO_ENGINE_OUTPUT(midpoint, SoSFVec3f, setValue(point3)); +} \ No newline at end of file diff --git a/src/Gui/ArcEngine.h b/src/Gui/ArcEngine.h new file mode 100644 index 0000000000..fdba1b6299 --- /dev/null +++ b/src/Gui/ArcEngine.h @@ -0,0 +1,73 @@ +/*************************************************************************** + * Copyright (c) 2013 Thomas Anderson * + * * + * 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 GUI_ARCENGINE_H +#define GUI_ARCENGINE_H + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class SoText2; +class SoTranslation; +class SoCoordinate3; +class SoIndexedLineSet; + +namespace Gui { + + +// /*used for generating points for arc display*/ +class GuiExport ArcEngine : public SoEngine +{ + SO_ENGINE_HEADER(ArcEngine); +public: + ArcEngine(); + static void initClass(); + + SoSFFloat radius; + SoSFFloat angle; + SoSFFloat deviation; + + SoEngineOutput points; + SoEngineOutput pointCount; + SoEngineOutput midpoint; + +protected: + void evaluate() override; +private: + ~ArcEngine() override{} + void defaultValues(); //some non error values if something goes wrong. +}; + +} // namespace Gui + +#endif // GUI_ARCENGINE_H \ No newline at end of file diff --git a/src/Gui/CMakeLists.txt b/src/Gui/CMakeLists.txt index c0a0202ed1..7a06a512eb 100644 --- a/src/Gui/CMakeLists.txt +++ b/src/Gui/CMakeLists.txt @@ -1009,6 +1009,7 @@ SET(Inventor_CPP_SRCS SoTouchEvents.cpp SoMouseWheelEvent.cpp SoFCCSysDragger.cpp + ArcEngine.cpp ) SET(Inventor_SRCS ${Inventor_CPP_SRCS} @@ -1037,6 +1038,7 @@ SET(Inventor_SRCS SoTouchEvents.h SoMouseWheelEvent.h SoFCCSysDragger.h + ArcEngine.h ) SOURCE_GROUP("View3D\\Inventor" FILES ${Inventor_SRCS}) diff --git a/src/Gui/SoFCDB.cpp b/src/Gui/SoFCDB.cpp index 582d35cb5a..cdd8c6f5ac 100644 --- a/src/Gui/SoFCDB.cpp +++ b/src/Gui/SoFCDB.cpp @@ -74,6 +74,7 @@ #include "Inventor/SoAutoZoomTranslation.h" #include "Inventor/SoDrawingGrid.h" #include "propertyeditor/PropertyItem.h" +#include "ArcEngine.h" using namespace Gui; @@ -193,6 +194,8 @@ void Gui::SoFCDB::init() SelectionObject ::init(); + ArcEngine ::initClass(); + qRegisterMetaType("Base::Vector3f"); qRegisterMetaType("Base::Vector3d"); qRegisterMetaType("Base::Quantity"); diff --git a/src/Mod/Part/Gui/AppPartGui.cpp b/src/Mod/Part/Gui/AppPartGui.cpp index a224a3806d..4aef8a4efe 100644 --- a/src/Mod/Part/Gui/AppPartGui.cpp +++ b/src/Mod/Part/Gui/AppPartGui.cpp @@ -214,7 +214,6 @@ PyMOD_INIT_FUNC(PartGui) PartGui::ViewProviderProjectOnSurface ::init(); PartGui::DimensionLinear ::initClass(); PartGui::DimensionAngular ::initClass(); - PartGui::ArcEngine ::initClass(); PartGui::Workbench ::init(); auto manip = std::make_shared(); From ce36dcc379b030eeb08640a43f852007673edd96 Mon Sep 17 00:00:00 2001 From: hlorus Date: Sun, 14 Jan 2024 21:12:04 +0100 Subject: [PATCH 03/11] [Part] Move VectorAdapter into own file in PartGui --- src/Mod/Part/App/CMakeLists.txt | 2 + src/Mod/Part/App/VectorAdapter.cpp | 156 +++++++++++++++++++++++++++++ src/Mod/Part/App/VectorAdapter.h | 90 +++++++++++++++++ src/Mod/Part/Gui/TaskDimension.cpp | 94 +---------------- src/Mod/Part/Gui/TaskDimension.h | 43 +------- 5 files changed, 252 insertions(+), 133 deletions(-) create mode 100644 src/Mod/Part/App/VectorAdapter.cpp create mode 100644 src/Mod/Part/App/VectorAdapter.h diff --git a/src/Mod/Part/App/CMakeLists.txt b/src/Mod/Part/App/CMakeLists.txt index 0e6e3f3637..2c0267a77f 100644 --- a/src/Mod/Part/App/CMakeLists.txt +++ b/src/Mod/Part/App/CMakeLists.txt @@ -219,6 +219,8 @@ SET(Features_SRCS AttachExtension.cpp PrismExtension.cpp PrismExtension.h + VectorAdapter.cpp + VectorAdapter.h ) SOURCE_GROUP("Features" FILES ${Features_SRCS}) diff --git a/src/Mod/Part/App/VectorAdapter.cpp b/src/Mod/Part/App/VectorAdapter.cpp new file mode 100644 index 0000000000..a2f9cdec58 --- /dev/null +++ b/src/Mod/Part/App/VectorAdapter.cpp @@ -0,0 +1,156 @@ +/*************************************************************************** + * Copyright (c) 2022 Werner Mayer * + * * + * 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" + +#include +#include +#include "VectorAdapter.h" +#include "Base/Console.h" +#include + +#include "PrimitiveFeature.h" +#include "PartFeature.h" + +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace Part { + + +VectorAdapter::VectorAdapter() : status(false), vector() +{ +} + +VectorAdapter::VectorAdapter(const TopoDS_Face &faceIn, const gp_Vec &pickedPointIn) : + status(false), vector(), origin(pickedPointIn) +{ + Handle(Geom_Surface) surface = BRep_Tool::Surface(faceIn); + if (surface->IsKind(STANDARD_TYPE(Geom_ElementarySurface))) + { + Handle(Geom_ElementarySurface) eSurface = Handle(Geom_ElementarySurface)::DownCast(surface); + gp_Dir direction = eSurface->Axis().Direction(); + vector = direction; + vector.Normalize(); + if (faceIn.Orientation() == TopAbs_REVERSED) { + vector.Reverse(); + } + if (surface->IsKind(STANDARD_TYPE(Geom_CylindricalSurface)) || + surface->IsKind(STANDARD_TYPE(Geom_SphericalSurface)) + ) + { + origin = eSurface->Axis().Location().XYZ(); + projectOriginOntoVector(pickedPointIn); + } + else { + origin = pickedPointIn + vector; + } + status = true; + } +} + +VectorAdapter::VectorAdapter(const TopoDS_Edge &edgeIn, const gp_Vec &pickedPointIn) : + status(false), vector(), origin(pickedPointIn) +{ + TopoDS_Vertex firstVertex = TopExp::FirstVertex(edgeIn, Standard_True); + TopoDS_Vertex lastVertex = TopExp::LastVertex(edgeIn, Standard_True); + vector = convert(lastVertex) - convert(firstVertex); + if (vector.Magnitude() < Precision::Confusion()) { + return; + } + vector.Normalize(); + + status = true; + projectOriginOntoVector(pickedPointIn); +} + +VectorAdapter::VectorAdapter(const TopoDS_Vertex &vertex1In, const TopoDS_Vertex &vertex2In) : + status(false), vector(), origin() +{ + vector = convert(vertex2In) - convert(vertex1In); + vector.Normalize(); + + //build origin half way. + gp_Vec tempVector = (convert(vertex2In) - convert(vertex1In)); + double mag = tempVector.Magnitude(); + tempVector.Normalize(); + tempVector *= (mag / 2.0); + origin = tempVector + convert(vertex1In); + + status = true; +} + +VectorAdapter::VectorAdapter(const gp_Vec &vector1, const gp_Vec &vector2) : + status(false), vector(), origin() +{ + vector = vector2- vector1; + vector.Normalize(); + + //build origin half way. + gp_Vec tempVector = vector2 - vector1; + double mag = tempVector.Magnitude(); + tempVector.Normalize(); + tempVector *= (mag / 2.0); + origin = tempVector + vector1; + + status = true; +} + +void VectorAdapter::projectOriginOntoVector(const gp_Vec &pickedPointIn) +{ + Handle(Geom_Curve) heapLine = new Geom_Line(origin.XYZ(), vector.XYZ()); + gp_Pnt tempPoint(pickedPointIn.XYZ()); + GeomAPI_ProjectPointOnCurve projection(tempPoint, heapLine); + if (projection.NbPoints() < 1) { + return; + } + origin.SetXYZ(projection.Point(1).XYZ()); +} + +VectorAdapter::operator gp_Lin() const +{ + gp_Pnt tempOrigin; + tempOrigin.SetXYZ(origin.XYZ()); + return gp_Lin(tempOrigin, gp_Dir(vector)); +} + + +/*convert a vertex to vector*/ +gp_Vec VectorAdapter::convert(const TopoDS_Vertex &vertex) +{ + gp_Pnt point = BRep_Tool::Pnt(vertex); + gp_Vec out(point.X(), point.Y(), point.Z()); + return out; +} + + + +} + diff --git a/src/Mod/Part/App/VectorAdapter.h b/src/Mod/Part/App/VectorAdapter.h new file mode 100644 index 0000000000..2196401305 --- /dev/null +++ b/src/Mod/Part/App/VectorAdapter.h @@ -0,0 +1,90 @@ +/*************************************************************************** + * Copyright (c) 2022 Werner Mayer * + * * + * 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 PART_VECTORADAPTER_H +#define PART_VECTORADAPTER_H + +#include + +#include +#include +#include +#include +#include +#include + +namespace Part +{ + + +/*! @brief Convert to vector + * + * Used to construct a vector from various input types + */ +class VectorAdapter +{ +public: + /*!default construction isValid is set to false*/ + VectorAdapter(); + /*!Build a vector from a faceIn + * @param faceIn vector will be normal to plane and equal to cylindrical axis. + * @param pickedPointIn location of pick. straight conversion from sbvec. not accurate.*/ + VectorAdapter(const TopoDS_Face &faceIn, const gp_Vec &pickedPointIn); + /*!Build a vector from an edgeIn + * @param edgeIn vector will be lastPoint - firstPoint. + * @param pickedPointIn location of pick. straight conversion from sbvec. not accurate.*/ + VectorAdapter(const TopoDS_Edge &edgeIn, const gp_Vec &pickedPointIn); + /*!Build a vector From 2 vertices. + *vector will be equal to @param vertex2In - @param vertex1In.*/ + VectorAdapter(const TopoDS_Vertex &vertex1In, const TopoDS_Vertex &vertex2In); + /*!Build a vector From 2 vectors. + *vector will be equal to @param vector2 - @param vector1.*/ + VectorAdapter(const gp_Vec &vector1, const gp_Vec &vector2); + + /*!make sure no errors in vector construction. + * @return true = vector is good. false = vector is NOT good.*/ + bool isValid() const {return status;} + /*!get the calculated vector. + * @return the vector. use isValid to ensure correct results.*/ + operator gp_Vec() const {return vector;}//explicit bombs + /*!build occ line used for extrema calculation*/ + operator gp_Lin() const;//explicit bombs + gp_Vec getPickPoint() const {return origin;} + + operator Base::Vector3d() const { + return Base::Vector3d(vector.X(), vector.Y(), vector.Z()); + } + + + static gp_Vec convert(const TopoDS_Vertex& vertex); + + private: + void projectOriginOntoVector(const gp_Vec &pickedPointIn); + bool status; + gp_Vec vector; + gp_Vec origin; +}; + + +} //namespace Part + +#endif diff --git a/src/Mod/Part/Gui/TaskDimension.cpp b/src/Mod/Part/Gui/TaskDimension.cpp index 8812d4dbb9..aba9cff004 100644 --- a/src/Mod/Part/Gui/TaskDimension.cpp +++ b/src/Mod/Part/Gui/TaskDimension.cpp @@ -737,98 +737,6 @@ void PartGui::TaskMeasureLinear::clearAllSlot(bool) PartGui::eraseAllDimensions(); } -PartGui::VectorAdapter::VectorAdapter() : status(false), vector() -{ -} - -PartGui::VectorAdapter::VectorAdapter(const TopoDS_Face &faceIn, const gp_Vec &pickedPointIn) : - status(false), vector(), origin(pickedPointIn) -{ - Handle(Geom_Surface) surface = BRep_Tool::Surface(faceIn); - if (surface->IsKind(STANDARD_TYPE(Geom_ElementarySurface))) - { - Handle(Geom_ElementarySurface) eSurface = Handle(Geom_ElementarySurface)::DownCast(surface); - gp_Dir direction = eSurface->Axis().Direction(); - vector = direction; - vector.Normalize(); - if (faceIn.Orientation() == TopAbs_REVERSED) - vector.Reverse(); - if (surface->IsKind(STANDARD_TYPE(Geom_CylindricalSurface)) || - surface->IsKind(STANDARD_TYPE(Geom_SphericalSurface)) - ) - { - origin = eSurface->Axis().Location().XYZ(); - projectOriginOntoVector(pickedPointIn); - } - else - origin = pickedPointIn + vector; - status = true; - } -} - -PartGui::VectorAdapter::VectorAdapter(const TopoDS_Edge &edgeIn, const gp_Vec &pickedPointIn) : - status(false), vector(), origin(pickedPointIn) -{ - TopoDS_Vertex firstVertex = TopExp::FirstVertex(edgeIn, Standard_True); - TopoDS_Vertex lastVertex = TopExp::LastVertex(edgeIn, Standard_True); - vector = PartGui::convert(lastVertex) - PartGui::convert(firstVertex); - if (vector.Magnitude() < Precision::Confusion()) - return; - vector.Normalize(); - - status = true; - projectOriginOntoVector(pickedPointIn); -} - -PartGui::VectorAdapter::VectorAdapter(const TopoDS_Vertex &vertex1In, const TopoDS_Vertex &vertex2In) : - status(false), vector(), origin() -{ - vector = PartGui::convert(vertex2In) - PartGui::convert(vertex1In); - vector.Normalize(); - - //build origin half way. - gp_Vec tempVector = (PartGui::convert(vertex2In) - PartGui::convert(vertex1In)); - double mag = tempVector.Magnitude(); - tempVector.Normalize(); - tempVector *= (mag / 2.0); - origin = tempVector + PartGui::convert(vertex1In); - - status = true; -} - -PartGui::VectorAdapter::VectorAdapter(const gp_Vec &vector1, const gp_Vec &vector2) : - status(false), vector(), origin() -{ - vector = vector2- vector1; - vector.Normalize(); - - //build origin half way. - gp_Vec tempVector = vector2 - vector1; - double mag = tempVector.Magnitude(); - tempVector.Normalize(); - tempVector *= (mag / 2.0); - origin = tempVector + vector1; - - status = true; -} - -void PartGui::VectorAdapter::projectOriginOntoVector(const gp_Vec &pickedPointIn) -{ - Handle(Geom_Curve) heapLine = new Geom_Line(origin.XYZ(), vector.XYZ()); - gp_Pnt tempPoint(pickedPointIn.XYZ()); - GeomAPI_ProjectPointOnCurve projection(tempPoint, heapLine); - if (projection.NbPoints() < 1) - return; - origin.SetXYZ(projection.Point(1).XYZ()); -} - -PartGui::VectorAdapter::operator gp_Lin() const -{ - gp_Pnt tempOrigin; - tempOrigin.SetXYZ(origin.XYZ()); - return gp_Lin(tempOrigin, gp_Dir(vector)); -} - gp_Vec PartGui::convert(const TopoDS_Vertex &vertex) { gp_Pnt point = BRep_Tool::Pnt(vertex); @@ -1694,7 +1602,7 @@ void PartGui::TaskMeasureAngular::selectionClearDelayedSlot() this->blockSelection(false); } -PartGui::VectorAdapter PartGui::TaskMeasureAngular::buildAdapter(const PartGui::DimSelections& selection) +VectorAdapter PartGui::TaskMeasureAngular::buildAdapter(const PartGui::DimSelections& selection) { Base::Matrix4D mat; assert(selection.selections.size() > 0 && selection.selections.size() < 3); diff --git a/src/Mod/Part/Gui/TaskDimension.h b/src/Mod/Part/Gui/TaskDimension.h index 5a2eb07943..5f17ed8de3 100644 --- a/src/Mod/Part/Gui/TaskDimension.h +++ b/src/Mod/Part/Gui/TaskDimension.h @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -52,6 +53,8 @@ class QPushButton; class QPixmap; class QLabel; +using namespace Part; + namespace Gui{class View3dInventorViewer;} namespace PartGui @@ -280,46 +283,6 @@ private: }; -/*! @brief Convert to vector - * - * Used to construct a vector from various input types - */ -class VectorAdapter -{ -public: - /*!default construction isValid is set to false*/ - VectorAdapter(); - /*!Build a vector from a faceIn - * @param faceIn vector will be normal to plane and equal to cylindrical axis. - * @param pickedPointIn location of pick. straight conversion from sbvec. not accurate.*/ - VectorAdapter(const TopoDS_Face &faceIn, const gp_Vec &pickedPointIn); - /*!Build a vector from an edgeIn - * @param edgeIn vector will be lastPoint - firstPoint. - * @param pickedPointIn location of pick. straight conversion from sbvec. not accurate.*/ - VectorAdapter(const TopoDS_Edge &edgeIn, const gp_Vec &pickedPointIn); - /*!Build a vector From 2 vertices. - *vector will be equal to @param vertex2In - @param vertex1In.*/ - VectorAdapter(const TopoDS_Vertex &vertex1In, const TopoDS_Vertex &vertex2In); - /*!Build a vector From 2 vectors. - *vector will be equal to @param vector2 - @param vector1.*/ - VectorAdapter(const gp_Vec &vector1, const gp_Vec &vector2); - - /*!make sure no errors in vector construction. - * @return true = vector is good. false = vector is NOT good.*/ - bool isValid() const {return status;} - /*!get the calculated vector. - * @return the vector. use isValid to ensure correct results.*/ - operator gp_Vec() const {return vector;}//explicit bombs - /*!build occ line used for extrema calculation*/ - operator gp_Lin() const;//explicit bombs - gp_Vec getPickPoint() const {return origin;} - -private: - void projectOriginOntoVector(const gp_Vec &pickedPointIn); - bool status; - gp_Vec vector; - gp_Vec origin; -}; /*!angular dialog class*/ class TaskMeasureAngular : public Gui::TaskView::TaskDialog, public Gui::SelectionObserver From 2acf5ccab081629d5ae7c396ec6563430c0e71b1 Mon Sep 17 00:00:00 2001 From: hlorus Date: Sun, 14 Jan 2024 20:58:33 +0100 Subject: [PATCH 04/11] [App] Add registration functions for unified measurement facility --- src/App/Application.cpp | 3 + src/App/CMakeLists.txt | 5 + src/App/MeasureManager.cpp | 180 ++++++++++++++++++++++++++++++++ src/App/MeasureManager.h | 112 ++++++++++++++++++++ src/App/MeasureManagerPy.xml | 43 ++++++++ src/App/MeasureManagerPyImp.cpp | 79 ++++++++++++++ 6 files changed, 422 insertions(+) create mode 100644 src/App/MeasureManager.cpp create mode 100644 src/App/MeasureManager.h create mode 100644 src/App/MeasureManagerPy.xml create mode 100644 src/App/MeasureManagerPyImp.cpp diff --git a/src/App/Application.cpp b/src/App/Application.cpp index e092f1d5c4..49646c5eb0 100644 --- a/src/App/Application.cpp +++ b/src/App/Application.cpp @@ -102,6 +102,7 @@ #include "VarSet.h" #include "MaterialObject.h" #include "MeasureDistance.h" +#include "MeasureManagerPy.h" #include "Origin.h" #include "OriginFeature.h" #include "OriginGroupExtension.h" @@ -314,6 +315,8 @@ void Application::setupPythonTypes() Base::Interpreter().addType(&App::MaterialPy::Type, pAppModule, "Material"); Base::Interpreter().addType(&App::MetadataPy::Type, pAppModule, "Metadata"); + Base::Interpreter().addType(&App::MeasureManagerPy::Type, pAppModule, "MeasureManager"); + Base::Interpreter().addType(&App::StringHasherPy::Type, pAppModule, "StringHasher"); Base::Interpreter().addType(&App::StringIDPy::Type, pAppModule, "StringID"); diff --git a/src/App/CMakeLists.txt b/src/App/CMakeLists.txt index c809a0aeec..e8a8dfc93a 100644 --- a/src/App/CMakeLists.txt +++ b/src/App/CMakeLists.txt @@ -97,6 +97,7 @@ generate_from_xml(StringIDPy) generate_from_xml(ComplexGeoDataPy) generate_from_xml(PropertyContainerPy) generate_from_xml(MaterialPy) +generate_from_xml(MeasureManagerPy) generate_from_py(FreeCADInit InitScript.h) generate_from_py(FreeCADTest TestScript.h) @@ -119,6 +120,7 @@ SET(FreeCADApp_XML_SRCS PropertyContainerPy.xml ComplexGeoDataPy.xml MaterialPy.xml + MeasureManagerPy.xml StringHasherPy.xml StringIDPy.xml ) @@ -282,6 +284,8 @@ SET(FreeCADApp_CPP_SRCS MappedName.cpp Material.cpp MaterialPyImp.cpp + MeasureManager.cpp + MeasureManagerPyImp.cpp Metadata.cpp MetadataPyImp.cpp ElementNamingUtils.cpp @@ -305,6 +309,7 @@ SET(FreeCADApp_HPP_SRCS MappedName.h MappedElement.h Material.h + MeasureManager.h Metadata.h ElementNamingUtils.h StringHasher.h diff --git a/src/App/MeasureManager.cpp b/src/App/MeasureManager.cpp new file mode 100644 index 0000000000..bcf996b103 --- /dev/null +++ b/src/App/MeasureManager.cpp @@ -0,0 +1,180 @@ +/*************************************************************************** + * Copyright (c) 2023 David Friedli * + * Copyright (c) 2023 Wandererfan * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + +#include +#include +#include +#include + +#include "MeasureManager.h" + +namespace App { + + std::vector MeasureManager::_mMeasureHandlers; + std::vector MeasureManager::_mMeasureTypes; + + MeasureManager::MeasureManager() + { + // Constructor implementation + } + + + void MeasureManager::addMeasureHandler(const char* module, MeasureTypeMethod typeCb) { + auto item = new MeasureHandler{module, typeCb}; + _mMeasureHandlers.push_back(*item); + } + + bool MeasureManager::hasMeasureHandler(const char* module) { + for(MeasureHandler& handler : _mMeasureHandlers) { + if (strcmp(handler.module.c_str(), module) == 0) { + return true; + } + } + return false; + } + + MeasureHandler MeasureManager::getMeasureHandler(const char* module) { + for(MeasureHandler handler : _mMeasureHandlers) { + if (!strcmp(handler.module.c_str(), module)) { + return handler; + } + } + + MeasureHandler empty; + return empty; + } + + void MeasureManager::addMeasureType(MeasureType* measureType) { + _mMeasureTypes.push_back(measureType); + } + + void MeasureManager::addMeasureType(std::string id, std::string label, std::string measureObj, MeasureValidateMethod validatorCb, MeasurePrioritizeMethod prioritizeCb) { + MeasureType* mType = new MeasureType{id, label, measureObj, validatorCb, prioritizeCb, false, nullptr}; + _mMeasureTypes.push_back(mType); + } + + void MeasureManager::addMeasureType(const char* id, const char* label, const char* measureObj, MeasureValidateMethod validatorCb, MeasurePrioritizeMethod prioritizeCb) { + addMeasureType(std::string(id), std::string(label), std::string(measureObj), validatorCb, prioritizeCb); + } + + const std::vector MeasureManager::getMeasureTypes() { + return _mMeasureTypes; + } + + + Py::Tuple MeasureManager::getSelectionPy(const App::MeasureSelection& selection) { + // Convert selection to python list + Py::Tuple selectionPy(selection.size()); + + int i = 0; + for (auto it : selection) { + + Py::Dict sel; + sel.setItem("object", Py::asObject(it.object.getObject()->getPyObject())); + sel.setItem("subName", Py::String(it.object.getSubName())); + sel.setItem("pickedPoint", Py::asObject(new Base::VectorPy(it.pickedPoint))); + + selectionPy.setItem(i, sel); + + i++; + } + return selectionPy; + } + + + std::vector MeasureManager::getValidMeasureTypes(App::MeasureSelection selection, std::string mode) { + Base::PyGILStateLocker lock; + + // Convert selection to python list + Py::Tuple selectionPy = getSelectionPy(selection); + + // Store valid measure types + std::vector validTypes; + std::pair(); + + + // Loop through measure types and check if they work with given selection + for (App::MeasureType* mType : getMeasureTypes()){ + + if (mode != "" && mType->label != mode) { + continue; + } + + + if (mType->isPython) { + // Parse Python measure types + auto measurePyClass = Py::Object(mType->pythonClass); + + Py::Tuple args(1); + args.setItem(0, selectionPy); + + Py::Object isValid; + try { + isValid = measurePyClass.callMemberFunction(std::string("isValidSelection"), args); + } catch (const Py::Exception&) { + Base::PyException e; + e.ReportException(); + isValid = Py::False(); + } + + if (isValid.as_bool()) { + + // Check priority + Py::Object isPriority; + try { + isPriority = measurePyClass.callMemberFunction("isPrioritySelection", args); + } catch (const Py::Exception&) { + Base::PyException e; + e.ReportException(); + isPriority = Py::False(); + } + + if (isPriority.as_bool()) { + validTypes.insert(validTypes.begin(), mType); + } else { + validTypes.push_back(mType); + } + } + } else { + // Parse c++ measure types + + if (mType->validatorCb && !mType->validatorCb(selection)) { + continue; + } + + // Check if the measurement type prioritizes the given selection + if (mType->prioritizeCb && mType->prioritizeCb(selection)) { + validTypes.insert(validTypes.begin(), mType); + } else { + validTypes.push_back(mType); + } + + } + } + + return validTypes; + } + + + + +} // namespace App \ No newline at end of file diff --git a/src/App/MeasureManager.h b/src/App/MeasureManager.h new file mode 100644 index 0000000000..2c93b59ebe --- /dev/null +++ b/src/App/MeasureManager.h @@ -0,0 +1,112 @@ +/*************************************************************************** + * Copyright (c) 2023 David Friedli * + * Copyright (c) 2023 Wandererfan * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + +#ifndef MEASUREMANAGER_H +#define MEASUREMANAGER_H + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace App { + +// Add your class methods and member variables here +enum class MeasureElementType { + INVALID, + POINT, + LINE, + LINESEGMENT, + CIRCLE, + ARC, + CURVE, // Has a length but no radius or axis + PLANE, + CYLINDER, + Volume, +}; + + +struct MeasureSelectionItem { + App::SubObjectT object; + Base::Vector3d pickedPoint; +}; + + +using MeasureSelection = std::vector; +using MeasureValidateMethod = std::function; +using MeasurePrioritizeMethod = std::function; +using MeasureTypeMethod = std::function; + +struct MeasureType { + std::string identifier; + std::string label; + std::string measureObject; + + // Checks if the measurement works with a given selection + MeasureValidateMethod validatorCb; + + // Allows to prioritize this over other measurement types when the measurement type is picked implicitly from the selection. + // Gets called only when validatorCb returned true for the given selection + MeasurePrioritizeMethod prioritizeCb; + + bool isPython; + PyObject* pythonClass; +}; + +struct MeasureHandler { + std::string module; + MeasureTypeMethod typeCb; +}; + + +class AppExport MeasureManager { +public: + MeasureManager(); + + static void addMeasureHandler(const char* module, MeasureTypeMethod typeCb); + static bool hasMeasureHandler(const char* module); + static MeasureHandler getMeasureHandler(const char* module); + static void addMeasureType(MeasureType* measureType); + static void addMeasureType(std::string id, std::string label, std::string measureObj, MeasureValidateMethod validatorCb, MeasurePrioritizeMethod prioritizeCb); + static void addMeasureType(const char* id, const char* label, const char* measureObj, MeasureValidateMethod validatorCb, MeasurePrioritizeMethod prioritizeCb); + static const std::vector getMeasureTypes(); + static Py::Tuple getSelectionPy(const App::MeasureSelection& selection); + static std::vector getValidMeasureTypes(App::MeasureSelection selection, std::string mode); + + +private: + static std::vector _mMeasureHandlers; + static std::vector _mMeasureTypes; +}; + + +} // namespace App + +#endif // MEASUREMANAGER_H diff --git a/src/App/MeasureManagerPy.xml b/src/App/MeasureManagerPy.xml new file mode 100644 index 0000000000..e2cd74efec --- /dev/null +++ b/src/App/MeasureManagerPy.xml @@ -0,0 +1,43 @@ + + + + + + MeasureManager class. + +The MeasureManager handles measure types and geometry handler accross FreeCAD. + MeasureManager + + + + addMeasureType(id, label, measureType) -> None + +Add a new measure type. + +id : str + Unique identifier of the measure type. +label : str + Name of the module. +measureType : Measure.MeasureBasePython + The actual measure type. + + + + + getMeasureTypes() -> List[(id, label, pythonMeasureType)] + +Returns a list of all registered measure types. + + + + diff --git a/src/App/MeasureManagerPyImp.cpp b/src/App/MeasureManagerPyImp.cpp new file mode 100644 index 0000000000..46103b7f51 --- /dev/null +++ b/src/App/MeasureManagerPyImp.cpp @@ -0,0 +1,79 @@ +/*************************************************************************** + * Copyright (c) 2023 David Friedli * + * Copyright (c) 2023 Wandererfan * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + +#include "PreCompiled.h" + +// inclusion of the generated files (generated out of MeasureManagerPy.xml) +#include "MeasureManagerPy.h" +#include "MeasureManagerPy.cpp" + + +using namespace App; + + +// returns a string which represents the object e.g. when printed in python +std::string MeasureManagerPy::representation() const +{ + return ""; +} + +PyObject* MeasureManagerPy::getCustomAttributes(const char* /*attr*/) const +{ + return nullptr; +} + +int MeasureManagerPy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/) +{ + return 0; +} + + +PyObject* MeasureManagerPy::addMeasureType(PyObject *args) +{ + PyObject *pyobj = Py_None; + char *id, *label; + + if (!PyArg_ParseTuple(args, "ssO", &id, &label, &pyobj)) + return nullptr; + + MeasureManager::addMeasureType( + new App::MeasureType{id, label, "", nullptr, nullptr, true, pyobj} + ); + + Py_Return; +} + + +PyObject* MeasureManagerPy::getMeasureTypes(PyObject *args) +{ + Py::List types; + for (auto & it : MeasureManager::getMeasureTypes()) { + Py::Tuple type(3); + type.setItem(0, Py::String(it->identifier)); + type.setItem(1, Py::String(it->label)); + type.setItem(2, Py::Object(it->pythonClass)); + + types.append(type); + } + + return Py::new_reference_to(types); +} \ No newline at end of file From f4c90c07f1c0061fe8ca067f51c39c4e5e56592f Mon Sep 17 00:00:00 2001 From: hlorus Date: Sun, 14 Jan 2024 21:31:26 +0100 Subject: [PATCH 05/11] [Part] Add handlers for unified measurement facility --- src/Mod/Part/App/AppPart.cpp | 6 +- src/Mod/Part/App/CMakeLists.txt | 3 + src/Mod/Part/App/Measure.cpp | 440 +++++++++++++++++++++++++++++++ src/Mod/Part/App/MeasureClient.h | 52 ++++ src/Mod/Part/App/MeasureInfo.h | 160 +++++++++++ 5 files changed, 660 insertions(+), 1 deletion(-) create mode 100644 src/Mod/Part/App/Measure.cpp create mode 100644 src/Mod/Part/App/MeasureClient.h create mode 100644 src/Mod/Part/App/MeasureInfo.h diff --git a/src/Mod/Part/App/AppPart.cpp b/src/Mod/Part/App/AppPart.cpp index 62b29c7b0e..23ab086e84 100644 --- a/src/Mod/Part/App/AppPart.cpp +++ b/src/Mod/Part/App/AppPart.cpp @@ -185,6 +185,8 @@ #include #include +#include "MeasureClient.h" + namespace Part { extern PyObject* initModule(); @@ -552,10 +554,12 @@ PyMOD_INIT_FUNC(Part) Part::Geom2dOffsetCurve ::init(); Part::Geom2dTrimmedCurve ::init(); + IGESControl_Controller::Init(); STEPControl_Controller::Init(); OCAF::ImportExportSettings::initialize(); - + Part::MeasureClient::initialize(); + PyMOD_Return(partModule); } diff --git a/src/Mod/Part/App/CMakeLists.txt b/src/Mod/Part/App/CMakeLists.txt index 2c0267a77f..93bf3aa52c 100644 --- a/src/Mod/Part/App/CMakeLists.txt +++ b/src/Mod/Part/App/CMakeLists.txt @@ -545,6 +545,9 @@ SET(Part_SRCS TopoShapeOpCode.h edgecluster.cpp edgecluster.h + MeasureClient.cpp + MeasureClient.h + MeasureInfo.h modelRefine.cpp modelRefine.h Tools.cpp diff --git a/src/Mod/Part/App/Measure.cpp b/src/Mod/Part/App/Measure.cpp new file mode 100644 index 0000000000..e46b01261f --- /dev/null +++ b/src/Mod/Part/App/Measure.cpp @@ -0,0 +1,440 @@ +/*************************************************************************** + * Copyright (c) 2023 David Friedli * + * * + * 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" + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "VectorAdapter.h" +#include "PartFeature.h" + +#include "Measure.h" + +// From: https://github.com/Celemation/FreeCAD/blob/joel_selection_summary_demo/src/Gui/SelectionSummary.cpp + +// Should work with edges and wires +static float getLength(TopoDS_Shape& wire){ + GProp_GProps gprops; + BRepGProp::LinearProperties(wire, gprops); + return gprops.Mass(); +} + +static float getFaceArea(TopoDS_Shape& face){ + GProp_GProps gprops; + BRepGProp::SurfaceProperties(face, gprops); + return gprops.Mass(); +} + +static float getRadius(TopoDS_Shape& edge){ + // gprops.Mass() would be the circumference (length) of the circle (arc) + if (edge.ShapeType() == TopAbs_EDGE) { + BRepAdaptor_Curve adapt(TopoDS::Edge(edge)); + if (adapt.GetType() != GeomAbs_Circle) { + // TODO: not sure what the error handling here should be. nan? 0.0? + return 0.0; + } + gp_Circ circle = adapt.Circle(); + return circle.Radius(); + } + return 0.0; +} + +TopoDS_Shape getLocatedShape(const std::string& objectName, const std::string& subName) +{ + App::DocumentObject* obj = App::GetApplication().getActiveDocument()->getObject(objectName.c_str()); + // should this be getTopoShape(obj, subName, true)? + Part::TopoShape shape = Part::Feature::getTopoShape(obj); + auto geoFeat = dynamic_cast(obj); + if (geoFeat) { + shape.setPlacement(geoFeat->globalPlacement()); + } + + // Don't get the subShape from datum elements + if (obj->getTypeId().isDerivedFrom(Part::Datum::getClassTypeId())) { + return shape.getShape(); + } + + if (subName.empty()) + { + return shape.getShape(); + } + return shape.getSubShape(subName.c_str()); +} + + +App::MeasureElementType PartMeasureTypeCb(const char* objectName, const char* subName) { + App::DocumentObject* ob = App::GetApplication().getActiveDocument()->getObject(objectName); +// auto sub = ob->getSubObject(subName); + + TopoDS_Shape shape = Part::Feature::getShape(ob, subName, true); + if (shape.IsNull()) { + // failure here on loading document with existing measurement. + Base::Console().Message("Part::VectorAdapter did not retrieve shape for %s, %s\n", objectName, subName); + return App::MeasureElementType(); + } + TopAbs_ShapeEnum shapeType = shape.ShapeType(); + + switch (shapeType) { + case TopAbs_VERTEX: { + return App::MeasureElementType::POINT; + } + case TopAbs_EDGE: { + const TopoDS_Edge& edge = TopoDS::Edge(shape); + BRepAdaptor_Curve curve(edge); + + switch (curve.GetType()) { + case GeomAbs_Line: { + if (ob->getTypeId().isDerivedFrom(Base::Type::fromName("Part::Datum"))) { + return App::MeasureElementType::LINE; + } + return App::MeasureElementType::LINESEGMENT; + } + case GeomAbs_Circle: { return App::MeasureElementType::CIRCLE; } + case GeomAbs_BezierCurve: + case GeomAbs_BSplineCurve: { + return App::MeasureElementType::CURVE; + } + default: { return App::MeasureElementType::INVALID; } + } + } + case TopAbs_FACE: { + const TopoDS_Face& face = TopoDS::Face(shape); + BRepAdaptor_Surface surface(face); + + switch (surface.GetType()) { + case GeomAbs_Cylinder: { return App::MeasureElementType::CYLINDER; } + case GeomAbs_Plane: { return App::MeasureElementType::PLANE; } + default: { return App::MeasureElementType::INVALID; } + } + } + case TopAbs_SOLID: { + return App::MeasureElementType::Volume; + } + default: { + return App::MeasureElementType::INVALID; + } + } +} + + + +bool getShapeFromStrings(TopoDS_Shape &shapeOut, const std::string &doc, const std::string &object, const std::string &sub, Base::Matrix4D *mat) +{ + App::Document *docPointer = App::GetApplication().getDocument(doc.c_str()); + if (!docPointer) { + return false; + } + App::DocumentObject *objectPointer = docPointer->getObject(object.c_str()); + if (!objectPointer) { + return false; + } + shapeOut = Part::Feature::getShape(objectPointer,sub.c_str(),true,mat); + return !shapeOut.IsNull(); +} + + + +Part::VectorAdapter buildAdapter(const App::DocumentObject* ob, std::string* objectName, const std::string* subName) +{ + (void) objectName; + Base::Matrix4D mat; + TopoDS_Shape shape = Part::Feature::getShape(ob, subName->c_str(), true); + if (shape.IsNull()) { + // failure here on loading document with existing measurement. + Base::Console().Message("Part::VectorAdapter did not retrieve shape for %s, %s\n", objectName->c_str(), subName->c_str()); + return Part::VectorAdapter(); + } + + TopAbs_ShapeEnum shapeType = shape.ShapeType(); + + + if (shapeType == TopAbs_EDGE) + { + TopoDS_Shape edgeShape; + if (!getShapeFromStrings(edgeShape, ob->getDocument()->getName(), ob->getNameInDocument(), *subName, &mat)) { + return {}; + } + TopoDS_Edge edge = TopoDS::Edge(edgeShape); + // make edge orientation so that end of edge closest to pick is head of vector. + TopoDS_Vertex firstVertex = TopExp::FirstVertex(edge, Standard_True); + TopoDS_Vertex lastVertex = TopExp::LastVertex(edge, Standard_True); + if (firstVertex.IsNull() || lastVertex.IsNull()) { + return {}; + } + gp_Vec firstPoint = Part::VectorAdapter::convert(firstVertex); + gp_Vec lastPoint = Part::VectorAdapter::convert(lastVertex); + Base::Vector3d v(0.0, 0.0, 0.0); //v(current.x,current.y,current.z); + v = mat*v; + gp_Vec pickPoint(v.x, v.y, v.z); + double firstDistance = (firstPoint - pickPoint).Magnitude(); + double lastDistance = (lastPoint - pickPoint).Magnitude(); + if (lastDistance > firstDistance) + { + if (edge.Orientation() == TopAbs_FORWARD) { + edge.Orientation(TopAbs_REVERSED); + } + else { + edge.Orientation(TopAbs_FORWARD); + } + } + return {edge, pickPoint}; + } + if (shapeType == TopAbs_FACE) + { + TopoDS_Shape faceShape; + if (!getShapeFromStrings(faceShape, ob->getDocument()->getName(), ob->getNameInDocument(), *subName, &mat)) { + return {}; + } + + TopoDS_Face face = TopoDS::Face(faceShape); + Base::Vector3d vTemp(0.0, 0.0, 0.0); //v(current.x, current.y, current.z); + vTemp = mat*vTemp; + gp_Vec pickPoint(vTemp.x, vTemp.y, vTemp.z); + return {face, pickPoint}; + } + + return {}; +} + + +Measure::MeasureLengthInfo MeasureLengthHandler(std::string* objectName, std::string* subName){ + TopoDS_Shape shape = getLocatedShape(*objectName, *subName); + + if (shape.IsNull()) { + // failure here on loading document with existing measurement. + Base::Console().Message("MeasureLengthHandler did not retrieve shape for %s, %s\n", objectName->c_str(), subName->c_str()); + return {false, 0.0, Base::Matrix4D()}; + } + TopAbs_ShapeEnum sType = shape.ShapeType(); + + if (sType != TopAbs_EDGE) { + return {false, 0.0, Base::Matrix4D()}; + } + + // Get Center of mass as the attachment point of the label + GProp_GProps gprops; + BRepGProp::LinearProperties(shape, gprops); + auto origin = gprops.CentreOfMass(); + + // Get rotation of line + auto edge = TopoDS::Edge(shape); + ShapeAnalysis_Edge edgeAnalyzer; + gp_Pnt firstPoint = BRep_Tool::Pnt(edgeAnalyzer.FirstVertex(edge)); + gp_Pnt lastPoint = BRep_Tool::Pnt(edgeAnalyzer.LastVertex(edge)); + auto dir = (lastPoint.XYZ() - firstPoint.XYZ()).Normalized(); + Base::Vector3d elementDirection(dir.X(), dir.Y(), dir.Z()); + Base::Vector3d axisUp(0.0, 0.0, 1.0); + Base::Rotation rot(axisUp, elementDirection); + + Base::Placement placement(Base::Vector3d(origin.X(), origin.Y(), origin.Z()), rot); + return {true, getLength(shape), placement}; +} + +Measure::MeasureRadiusInfo MeasureRadiusHandler(std::string* objectName, std::string* subName){ + Base::Placement placement; // curve center + orientation + Base::Vector3d pointOnCurve; + + TopoDS_Shape shape = getLocatedShape(*objectName, *subName); + + if (shape.IsNull()) { + return { false, 0.0, pointOnCurve, placement}; + } + TopAbs_ShapeEnum sType = shape.ShapeType(); + + if (sType != TopAbs_EDGE) { + return { false, 0.0, pointOnCurve, placement}; + } + + // Get Center of mass as the attachment point of the label + GProp_GProps gprops; + BRepGProp::LinearProperties(shape, gprops); + auto origin = gprops.CentreOfMass(); + + TopoDS_Edge edge = TopoDS::Edge(shape); + gp_Pnt firstPoint = BRep_Tool::Pnt(TopExp::FirstVertex(edge)); + pointOnCurve = Base::Vector3d(firstPoint.X(), firstPoint.Y(), firstPoint.Z()); + // a somewhat arbitrary radius from center -> point on curve + auto dir = (firstPoint.XYZ() - origin.XYZ()).Normalized(); + Base::Vector3d elementDirection(dir.X(), dir.Y(), dir.Z()); + Base::Vector3d axisUp(0.0, 0.0, 1.0); + Base::Rotation rot(axisUp, elementDirection); + + placement = Base::Placement(Base::Vector3d(origin.X(), origin.Y(), origin.Z()), rot); + + return { true, getRadius(shape), pointOnCurve, placement}; +} + + +Measure::MeasureAreaInfo MeasureAreaHandler(std::string* objectName, std::string* subName){ + TopoDS_Shape shape = getLocatedShape(*objectName, *subName); + + if (shape.IsNull()) { + // failure here on loading document with existing measurement. + Base::Console().Message("MeasureLengthHandler did not retrieve shape for %s, %s\n", objectName->c_str(), subName->c_str()); + return {false, 0.0, Base::Matrix4D()}; + } + TopAbs_ShapeEnum sType = shape.ShapeType(); + + if (sType != TopAbs_FACE) { + return {false, 0.0, Base::Matrix4D()}; + } + + // Get Center of mass as the attachment point of the label + GProp_GProps gprops; + BRepGProp::SurfaceProperties(shape, gprops); + auto origin = gprops.CentreOfMass(); + + // TODO: Center of Mass might not lie on the surface, somehow snap to the closest point on the surface? + + Base::Placement placement(Base::Vector3d(origin.X(), origin.Y(), origin.Z()), Base::Rotation()); + return {true, getFaceArea(shape), placement}; +} + + +Base::Vector3d MeasurePositionHandler(std::string* objectName, std::string* subName) { + TopoDS_Shape shape = getLocatedShape(*objectName, *subName); + + if (shape.IsNull()) { + Base::Console().Message("MeasurePositionHandler did not retrieve shape for %s, %s\n", objectName->c_str(), subName->c_str()); + return Base::Vector3d(); + } + TopAbs_ShapeEnum sType = shape.ShapeType(); + + if (sType != TopAbs_VERTEX) { + return Base::Vector3d(); + } + + TopoDS_Vertex vertex = TopoDS::Vertex(shape); + auto point = BRep_Tool::Pnt(vertex); + return Base::Vector3d(point.X(), point.Y(), point.Z()); +} + + +Measure::MeasureAngleInfo MeasureAngleHandler(std::string* objectName, std::string* subName) { + App::DocumentObject* ob = App::GetApplication().getActiveDocument()->getObject(objectName->c_str()); + TopoDS_Shape shape = getLocatedShape(*objectName, *subName); + if (shape.IsNull()) { + // failure here on loading document with existing measurement. + Base::Console().Message("MeasureAngleHandler did not retrieve shape for %s, %s\n", objectName->c_str(), subName->c_str()); + return Measure::MeasureAngleInfo(); + } + + TopAbs_ShapeEnum sType = shape.ShapeType(); + + Part::VectorAdapter vAdapt = buildAdapter(ob, objectName, subName); + + gp_Pnt vec; + Base::Vector3d position; + if (sType == TopAbs_FACE) { + TopoDS_Face face = TopoDS::Face(shape); + + GProp_GProps gprops; + BRepGProp::SurfaceProperties(face, gprops); + vec = gprops.CentreOfMass(); + + } else if (sType == TopAbs_EDGE) { + TopoDS_Edge edge = TopoDS::Edge(shape); + + GProp_GProps gprops; + BRepGProp::LinearProperties(edge, gprops); + vec = gprops.CentreOfMass(); + } + + position.Set(vec.X(), vec.Y(), vec.Z()); + + Measure::MeasureAngleInfo info = {vAdapt.isValid(), (Base::Vector3d)vAdapt, position}; + return info; +} + + +Measure::MeasureDistanceInfo MeasureDistanceHandler(std::string* objectName, std::string* subName) { + TopoDS_Shape shape = getLocatedShape(*objectName, *subName); + + if (shape.IsNull()) { + // failure here on loading document with existing measurement. + Base::Console().Message("MeasureDistanceHandler did not retrieve shape for %s, %s\n", objectName->c_str(), subName->c_str()); + return Measure::MeasureDistanceInfo(); + } + + // Just return the TopoDS_Shape here + return {true, shape}; +} + + +using namespace Measure; + +void Part::Measure::initialize() { + + App::Application& app = App::GetApplication(); + app.addMeasureHandler("Part", PartMeasureTypeCb); + + std::vector proxyList( { "Part", "PartDesign", "Sketcher" } ); + // Extend MeasureLength + MeasureLength::addGeometryHandlers(proxyList, MeasureLengthHandler); + + // Extend MeasurePosition + MeasurePosition::addGeometryHandlers(proxyList, MeasurePositionHandler); + + // Extend MeasureArea + MeasureArea::addGeometryHandlers(proxyList, MeasureAreaHandler); + + // Extend MeasureAngle + MeasureAngle::addGeometryHandlers(proxyList, MeasureAngleHandler); + + // Extend MeasureDistance + MeasureDistance::addGeometryHandlers(proxyList, MeasureDistanceHandler); + + // Extend MeasureRadius + MeasureRadius::addGeometryHandlers(proxyList, MeasureRadiusHandler); +} diff --git a/src/Mod/Part/App/MeasureClient.h b/src/Mod/Part/App/MeasureClient.h new file mode 100644 index 0000000000..80308d0604 --- /dev/null +++ b/src/Mod/Part/App/MeasureClient.h @@ -0,0 +1,52 @@ +/*************************************************************************** + * Copyright (c) 2023 Wandererfan * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + +#ifndef PART_MEASURE_H +#define PART_MEASURE_H + +#include + +#include "MeasureInfo.h" + + + +namespace Part +{ + + +class PartExport MeasureClient +{ +public: + + static void initialize(); + + static CallbackRegistrationList reportLengthCB(); + static CallbackRegistrationList reportPositionCB(); + static CallbackRegistrationList reportAreaCB(); + static CallbackRegistrationList reportAngleCB(); + static CallbackRegistrationList reportDistanceCB(); + static CallbackRegistrationList reportRadiusCB(); +}; + + +} //namespace Part + +#endif diff --git a/src/Mod/Part/App/MeasureInfo.h b/src/Mod/Part/App/MeasureInfo.h new file mode 100644 index 0000000000..6af72a962e --- /dev/null +++ b/src/Mod/Part/App/MeasureInfo.h @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: LGPL-2.0-or-later + +/*************************************************************************** + * Copyright (c) 2024 wandererfan * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + +//! MeasureInfo.h +//! ancestor class and the various flavours of MeasureXXXXXInfo. + +#ifndef PART_MEASUREINFO_H +#define PART_MEASUREINFO_H + +#include + +#include +#include +#include +#include + +#include +#include +#include + +class TopoDS_Shape; + +namespace Part { + +class MeasureInfo; +using MeasureInfoPtr = std::shared_ptr; +class MeasureAngleInfo; +using MeasureAngleInfoPtr = std::shared_ptr; +class MeasureAreaInfo; +using MeasureAreaInfoPtr = std::shared_ptr; +class MeasureDistanceInfo; +using MeasureDistanceInfoPtr = std::shared_ptr; +class MeasureLengthInfo; +using MeasureLengthInfoPtr = std::shared_ptr; +class MeasurePositionInfo; +using MeasurePositionInfoPtr = std::shared_ptr; +class MeasureRadiusInfo; +using MeasureRadiusInfoPtr = std::shared_ptr; + + +class PartExport MeasureInfo { +public: + // making the destructor virtual so MeasureInfo is polymorphic + MeasureInfo() = default; + MeasureInfo(bool val) : valid(val) {} + virtual ~MeasureInfo() = default; + bool valid{false}; +}; + +class PartExport MeasureAngleInfo : public MeasureInfo { +public: + MeasureAngleInfo() = default; + MeasureAngleInfo(bool val, Base::Vector3d orient, Base::Vector3d pos) : + MeasureInfo(val), orientation(orient), position(pos) {} + ~MeasureAngleInfo() override = default; + + Base::Vector3d orientation{0.0, 0.0, 0.0}; + Base::Vector3d position{0.0, 0.0, 0.0}; +}; + +class PartExport MeasureAreaInfo : public MeasureInfo { +public: + MeasureAreaInfo() = default; + MeasureAreaInfo(bool val, double a2, Base::Placement plm) : + MeasureInfo(val), area(a2), placement(plm) {} + ~MeasureAreaInfo() override = default; + + double area{0}; + Base::Placement placement{}; +}; + +// Translate geometry reference into an OCC type +class PartExport MeasureDistanceInfo : public MeasureInfo { +public: + MeasureDistanceInfo() = default; + explicit MeasureDistanceInfo(bool val, const TopoDS_Shape* shp) : + MeasureInfo(val), shape(shp) {} + ~MeasureDistanceInfo() override = default; + + const TopoDS_Shape* getShape() { return shape; } + +private: + const TopoDS_Shape* shape{nullptr}; +}; + +class PartExport MeasureLengthInfo : public MeasureInfo { +public: + MeasureLengthInfo() = default; + MeasureLengthInfo(bool val, double len, Base::Placement plm) : + MeasureInfo(val), length(len), placement(plm) {} + ~MeasureLengthInfo() override = default; + + double length{0}; + Base::Placement placement{}; +}; + +class PartExport MeasurePositionInfo : public MeasureInfo { +public: + MeasurePositionInfo() = default; + MeasurePositionInfo(bool val, Base::Vector3d pos) : + MeasureInfo(val), position(pos) {} + ~MeasurePositionInfo() override = default; + + Base::Vector3d position{0.0, 0.0, 0.0}; +}; + +class PartExport MeasureRadiusInfo : public MeasureInfo { +public: + MeasureRadiusInfo() = default; + MeasureRadiusInfo(bool val, double rad, Base::Vector3d point, Base::Placement plm) : + MeasureInfo(val), radius(rad), pointOnCurve(point), placement(plm) {} + ~MeasureRadiusInfo() override = default; + + double radius{}; + Base::Vector3d pointOnCurve; + Base::Placement placement; // curve center & circle orientation +}; + +//! callback registrations +// TODO: is there more that one place that GeometryHandler is defined? + using GeometryHandler = std::function; + +class PartExport CallbackRegistrationRecord +{ +public: + CallbackRegistrationRecord() = default; + CallbackRegistrationRecord(const std::string& module, const std::string& measureType, GeometryHandler callback) : + m_module(module), m_measureType(measureType), m_callback(callback) + { } + + std::string m_module; + std::string m_measureType; + GeometryHandler m_callback; +}; + +using CallbackRegistrationList = std::vector; + +} //end namespace Part + +#endif From 4f5dd40fa727548f9eac5c30fb0444307b22c8a7 Mon Sep 17 00:00:00 2001 From: hlorus Date: Sun, 14 Jan 2024 21:32:22 +0100 Subject: [PATCH 06/11] [PD] Add handlers for unified measurement facility --- src/Mod/PartDesign/App/AppPartDesign.cpp | 3 ++ src/Mod/PartDesign/App/CMakeLists.txt | 2 ++ src/Mod/PartDesign/App/Measure.cpp | 36 +++++++++++++++++++ src/Mod/PartDesign/App/Measure.h | 44 ++++++++++++++++++++++++ 4 files changed, 85 insertions(+) create mode 100644 src/Mod/PartDesign/App/Measure.cpp create mode 100644 src/Mod/PartDesign/App/Measure.h diff --git a/src/Mod/PartDesign/App/AppPartDesign.cpp b/src/Mod/PartDesign/App/AppPartDesign.cpp index 87ed33c03b..944764ce67 100644 --- a/src/Mod/PartDesign/App/AppPartDesign.cpp +++ b/src/Mod/PartDesign/App/AppPartDesign.cpp @@ -31,6 +31,7 @@ #include "DatumLine.h" #include "DatumPlane.h" #include "DatumPoint.h" +#include "Measure.h" #include "FeatureBase.h" #include "FeatureBoolean.h" #include "FeatureChamfer.h" @@ -153,5 +154,7 @@ PyMOD_INIT_FUNC(_PartDesign) PartDesign::SubtractiveWedge ::init(); PartDesign::FeatureBase ::init(); + PartDesign::Measure ::initialize(); + PyMOD_Return(mod); } diff --git a/src/Mod/PartDesign/App/CMakeLists.txt b/src/Mod/PartDesign/App/CMakeLists.txt index a2ecf82a5a..1a9774a3e1 100644 --- a/src/Mod/PartDesign/App/CMakeLists.txt +++ b/src/Mod/PartDesign/App/CMakeLists.txt @@ -117,6 +117,8 @@ SET(Module_SRCS AppPartDesignPy.cpp PreCompiled.cpp PreCompiled.h + Measure.cpp + Measure.h ) SOURCE_GROUP("Module" FILES ${Module_SRCS}) diff --git a/src/Mod/PartDesign/App/Measure.cpp b/src/Mod/PartDesign/App/Measure.cpp new file mode 100644 index 0000000000..5c4865b94d --- /dev/null +++ b/src/Mod/PartDesign/App/Measure.cpp @@ -0,0 +1,36 @@ +/*************************************************************************** + * Copyright (c) 2023 David Friedli * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + + +#include "PreCompiled.h" + +#include +#include +#include "Base/Console.h" +#include "Measure.h" + + +void PartDesign::Measure::initialize() { + const App::MeasureHandler& handler = App::MeasureManager::getMeasureHandler("Part"); + + App::MeasureManager::addMeasureHandler("PartDesign", handler.typeCb); +} + diff --git a/src/Mod/PartDesign/App/Measure.h b/src/Mod/PartDesign/App/Measure.h new file mode 100644 index 0000000000..23837cafcc --- /dev/null +++ b/src/Mod/PartDesign/App/Measure.h @@ -0,0 +1,44 @@ +/*************************************************************************** + * Copyright (c) 2023 David Friedli * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + +#ifndef PARTDESIGN_MEASURE_H +#define PARTDESIGN_MEASURE_H + +#include + + + +namespace PartDesign +{ + + +class PartDesignExport Measure +{ +public: + + static void initialize(); + +}; + + +} //namespace PartDesign + +#endif From 832a0653fabfdbabc7158bdab76b0d94731cf0fb Mon Sep 17 00:00:00 2001 From: hlorus Date: Sun, 14 Jan 2024 21:32:57 +0100 Subject: [PATCH 07/11] [Sketcher] Add handlers for unified measurement facility --- src/Mod/Sketcher/App/AppSketcher.cpp | 6 +++- src/Mod/Sketcher/App/CMakeLists.txt | 2 ++ src/Mod/Sketcher/App/Measure.cpp | 38 ++++++++++++++++++++++++ src/Mod/Sketcher/App/Measure.h | 43 ++++++++++++++++++++++++++++ 4 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 src/Mod/Sketcher/App/Measure.cpp create mode 100644 src/Mod/Sketcher/App/Measure.h diff --git a/src/Mod/Sketcher/App/AppSketcher.cpp b/src/Mod/Sketcher/App/AppSketcher.cpp index a1190b4c25..eba2a200e6 100644 --- a/src/Mod/Sketcher/App/AppSketcher.cpp +++ b/src/Mod/Sketcher/App/AppSketcher.cpp @@ -41,7 +41,7 @@ #include "SketchObjectSF.h" #include "SketchPy.h" #include "SolverGeometryExtension.h" - +#include "Measure.h" namespace Sketcher { @@ -95,6 +95,10 @@ PyMOD_INIT_FUNC(Sketcher) Sketcher::Constraint ::init(); Sketcher::PropertyConstraintList ::init(); + // connect to unified measurement facility + Sketcher::Measure ::initialize(); + + Base::Console().Log("Loading Sketcher module... done\n"); PyMOD_Return(sketcherModule); diff --git a/src/Mod/Sketcher/App/CMakeLists.txt b/src/Mod/Sketcher/App/CMakeLists.txt index f3ee9500af..f563f04790 100644 --- a/src/Mod/Sketcher/App/CMakeLists.txt +++ b/src/Mod/Sketcher/App/CMakeLists.txt @@ -121,6 +121,8 @@ SET(SketchModule_SRCS AppSketcherPy.cpp PreCompiled.cpp PreCompiled.h + Measure.cpp + Measure.h ) SOURCE_GROUP("Module" FILES ${SketchModule_SRCS}) diff --git a/src/Mod/Sketcher/App/Measure.cpp b/src/Mod/Sketcher/App/Measure.cpp new file mode 100644 index 0000000000..f57e52f8ec --- /dev/null +++ b/src/Mod/Sketcher/App/Measure.cpp @@ -0,0 +1,38 @@ +/*************************************************************************** + * Copyright (c) 2023 Wandererfan * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + +//! a class for establishing our connection with the unified measurement facility +//! we are treating sketches like Part objects for now + +#include "PreCompiled.h" + +#include +#include +#include "Base/Console.h" +#include "Measure.h" + + +void Sketcher::Measure::initialize() +{ + const App::MeasureHandler& handler = App::MeasureManager::getMeasureHandler("Part"); + + App::MeasureManager::addMeasureHandler("Sketcher", handler.typeCb); +} diff --git a/src/Mod/Sketcher/App/Measure.h b/src/Mod/Sketcher/App/Measure.h new file mode 100644 index 0000000000..3058c79fe4 --- /dev/null +++ b/src/Mod/Sketcher/App/Measure.h @@ -0,0 +1,43 @@ +/*************************************************************************** + * Copyright (c) 2023 Wandererfan * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + +//! a class for establishing our connection with the unified measurement facility + +#ifndef SKETCHER_MEASURE_H +#define SKETCHER_MEASURE_H + +#include + + +namespace Sketcher +{ + + +class SketcherExport Measure +{ +public: + static void initialize(); +}; + + +} // namespace Sketcher + +#endif From 36d16b22430e14351147e2666faf4d58130bfe8e Mon Sep 17 00:00:00 2001 From: hlorus Date: Sun, 14 Jan 2024 21:46:50 +0100 Subject: [PATCH 08/11] [Measure] Add features, viewproviders and preferences for unified measurement facility --- .../InitializeFreeCADBuildOptions.cmake | 1 + cMake/FreeCAD_Helpers/PrintFinalReport.cmake | 1 + src/Mod/CMakeLists.txt | 5 +- src/Mod/Measure/App/AppMeasure.cpp | 124 +++- src/Mod/Measure/App/CMakeLists.txt | 41 +- src/Mod/Measure/App/MeasureAngle.cpp | 264 ++++++++ src/Mod/Measure/App/MeasureAngle.h | 93 +++ src/Mod/Measure/App/MeasureArea.cpp | 181 ++++++ src/Mod/Measure/App/MeasureArea.h | 86 +++ src/Mod/Measure/App/MeasureBase.cpp | 211 +++++++ src/Mod/Measure/App/MeasureBase.h | 131 ++++ src/Mod/Measure/App/MeasureBasePy.xml | 20 + src/Mod/Measure/App/MeasureBasePyImp.cpp | 40 ++ src/Mod/Measure/App/MeasureDistance.cpp | 231 +++++++ src/Mod/Measure/App/MeasureDistance.h | 87 +++ src/Mod/Measure/App/MeasureLength.cpp | 184 ++++++ src/Mod/Measure/App/MeasureLength.h | 81 +++ src/Mod/Measure/App/MeasurePosition.cpp | 177 ++++++ src/Mod/Measure/App/MeasurePosition.h | 82 +++ src/Mod/Measure/App/MeasureRadius.cpp | 211 +++++++ src/Mod/Measure/App/MeasureRadius.h | 90 +++ src/Mod/Measure/App/Measurement.cpp | 2 +- src/Mod/Measure/App/MeasurementPy.xml | 1 + src/Mod/Measure/App/Preferences.cpp | 80 +++ src/Mod/Measure/App/Preferences.h | 58 ++ src/Mod/Measure/CMakeLists.txt | 25 +- src/Mod/Measure/Gui/AppMeasureGui.cpp | 99 +++ src/Mod/Measure/Gui/CMakeLists.txt | 90 +++ src/Mod/Measure/Gui/Command.cpp | 33 + .../Gui/DlgPrefsMeasureAppearanceImp.cpp | 75 +++ .../Gui/DlgPrefsMeasureAppearanceImp.h | 55 ++ .../Gui/DlgPrefsMeasureAppearanceImp.ui | 247 ++++++++ src/Mod/Measure/Gui/PreCompiled.cpp | 22 + src/Mod/Measure/Gui/PreCompiled.h | 97 +++ src/Mod/Measure/Gui/Resources/Measure.qrc | 6 + .../Resources/icons/preferences-measure.svg | 317 ++++++++++ .../Gui/Resources/icons/umf-measurement.svg | 317 ++++++++++ .../Gui/Resources/translations/Measure.ts | 42 ++ .../Measure/Gui/ViewProviderMeasureAngle.cpp | 339 ++++++++++ .../Measure/Gui/ViewProviderMeasureAngle.h | 73 +++ .../Measure/Gui/ViewProviderMeasureBase.cpp | 597 ++++++++++++++++++ src/Mod/Measure/Gui/ViewProviderMeasureBase.h | 159 +++++ .../Gui/ViewProviderMeasureDistance.cpp | 222 +++++++ .../Measure/Gui/ViewProviderMeasureDistance.h | 69 ++ src/Mod/Measure/InitGui.py | 43 ++ src/Mod/Measure/MeasureCOM.py | 152 +++++ src/Mod/Measure/MeasureGlobal.h | 13 +- src/Mod/Measure/UtilsMeasure.py | 52 ++ .../App/{Measure.cpp => MeasureClient.cpp} | 268 ++++---- 49 files changed, 5762 insertions(+), 132 deletions(-) create mode 100644 src/Mod/Measure/App/MeasureAngle.cpp create mode 100644 src/Mod/Measure/App/MeasureAngle.h create mode 100644 src/Mod/Measure/App/MeasureArea.cpp create mode 100644 src/Mod/Measure/App/MeasureArea.h create mode 100644 src/Mod/Measure/App/MeasureBase.cpp create mode 100644 src/Mod/Measure/App/MeasureBase.h create mode 100644 src/Mod/Measure/App/MeasureBasePy.xml create mode 100644 src/Mod/Measure/App/MeasureBasePyImp.cpp create mode 100644 src/Mod/Measure/App/MeasureDistance.cpp create mode 100644 src/Mod/Measure/App/MeasureDistance.h create mode 100644 src/Mod/Measure/App/MeasureLength.cpp create mode 100644 src/Mod/Measure/App/MeasureLength.h create mode 100644 src/Mod/Measure/App/MeasurePosition.cpp create mode 100644 src/Mod/Measure/App/MeasurePosition.h create mode 100644 src/Mod/Measure/App/MeasureRadius.cpp create mode 100644 src/Mod/Measure/App/MeasureRadius.h create mode 100644 src/Mod/Measure/App/Preferences.cpp create mode 100644 src/Mod/Measure/App/Preferences.h create mode 100644 src/Mod/Measure/Gui/AppMeasureGui.cpp create mode 100644 src/Mod/Measure/Gui/CMakeLists.txt create mode 100644 src/Mod/Measure/Gui/Command.cpp create mode 100644 src/Mod/Measure/Gui/DlgPrefsMeasureAppearanceImp.cpp create mode 100644 src/Mod/Measure/Gui/DlgPrefsMeasureAppearanceImp.h create mode 100644 src/Mod/Measure/Gui/DlgPrefsMeasureAppearanceImp.ui create mode 100644 src/Mod/Measure/Gui/PreCompiled.cpp create mode 100644 src/Mod/Measure/Gui/PreCompiled.h create mode 100644 src/Mod/Measure/Gui/Resources/Measure.qrc create mode 100644 src/Mod/Measure/Gui/Resources/icons/preferences-measure.svg create mode 100644 src/Mod/Measure/Gui/Resources/icons/umf-measurement.svg create mode 100644 src/Mod/Measure/Gui/Resources/translations/Measure.ts create mode 100644 src/Mod/Measure/Gui/ViewProviderMeasureAngle.cpp create mode 100644 src/Mod/Measure/Gui/ViewProviderMeasureAngle.h create mode 100644 src/Mod/Measure/Gui/ViewProviderMeasureBase.cpp create mode 100644 src/Mod/Measure/Gui/ViewProviderMeasureBase.h create mode 100644 src/Mod/Measure/Gui/ViewProviderMeasureDistance.cpp create mode 100644 src/Mod/Measure/Gui/ViewProviderMeasureDistance.h create mode 100644 src/Mod/Measure/InitGui.py create mode 100644 src/Mod/Measure/MeasureCOM.py create mode 100644 src/Mod/Measure/UtilsMeasure.py rename src/Mod/Part/App/{Measure.cpp => MeasureClient.cpp} (57%) diff --git a/cMake/FreeCAD_Helpers/InitializeFreeCADBuildOptions.cmake b/cMake/FreeCAD_Helpers/InitializeFreeCADBuildOptions.cmake index a08f96438d..ee77487493 100644 --- a/cMake/FreeCAD_Helpers/InitializeFreeCADBuildOptions.cmake +++ b/cMake/FreeCAD_Helpers/InitializeFreeCADBuildOptions.cmake @@ -130,6 +130,7 @@ macro(InitializeFreeCADBuildOptions) option(BUILD_SPREADSHEET "Build the FreeCAD spreadsheet module" ON) option(BUILD_START "Build the FreeCAD start module" ON) option(BUILD_TEST "Build the FreeCAD test module" ON) + option(BUILD_MEASURE "Build the FreeCAD Measure module" ON) option(BUILD_TECHDRAW "Build the FreeCAD Technical Drawing module" ON) option(BUILD_TUX "Build the FreeCAD Tux module" ON) option(BUILD_WEB "Build the FreeCAD Web module" ON) diff --git a/cMake/FreeCAD_Helpers/PrintFinalReport.cmake b/cMake/FreeCAD_Helpers/PrintFinalReport.cmake index 01c26c4c5c..b255abd4cf 100644 --- a/cMake/FreeCAD_Helpers/PrintFinalReport.cmake +++ b/cMake/FreeCAD_Helpers/PrintFinalReport.cmake @@ -118,6 +118,7 @@ macro(PrintFinalReport) value(BUILD_SPREADSHEET) value(BUILD_START) value(BUILD_SURFACE) + value(BUILD_MEASURE) value(BUILD_TECHDRAW) value(BUILD_TEST) value(BUILD_TUX) diff --git a/src/Mod/CMakeLists.txt b/src/Mod/CMakeLists.txt index c7101069f6..f439136157 100644 --- a/src/Mod/CMakeLists.txt +++ b/src/Mod/CMakeLists.txt @@ -122,8 +122,11 @@ if(BUILD_SURFACE) add_subdirectory(Surface) endif(BUILD_SURFACE) -if(BUILD_TECHDRAW) +if(BUILD_MEASURE) add_subdirectory(Measure) +endif(BUILD_MEASURE) + +if(BUILD_TECHDRAW) add_subdirectory(TechDraw) endif(BUILD_TECHDRAW) diff --git a/src/Mod/Measure/App/AppMeasure.cpp b/src/Mod/Measure/App/AppMeasure.cpp index 4be22df894..856c44c875 100644 --- a/src/Mod/Measure/App/AppMeasure.cpp +++ b/src/Mod/Measure/App/AppMeasure.cpp @@ -22,14 +22,38 @@ #include "PreCompiled.h" +#include #include #include +#include +#include + #include "Measurement.h" #include "MeasurementPy.h" +// unified measurement facility +#include "MeasureBase.h" +#include "MeasureBasePy.h" + +#include "MeasureAngle.h" +#include "MeasureDistance.h" +#include "MeasurePosition.h" +#include "MeasureLength.h" +#include "MeasureArea.h" +#include "MeasureRadius.h" namespace Measure { + +// explicit template instantiations +template class MeasureExport MeasureBaseExtendable; +template class MeasureExport MeasureBaseExtendable; +template class MeasureExport MeasureBaseExtendable; +template class MeasureExport MeasureBaseExtendable; +template class MeasureExport MeasureBaseExtendable; +template class MeasureExport MeasureBaseExtendable; + + class Module : public Py::ExtensionModule { public: @@ -48,6 +72,7 @@ PyObject* initModule() } // namespace Measure +using namespace Measure; /* Python entry */ PyMOD_INIT_FUNC(Measure) @@ -62,14 +87,103 @@ PyMOD_INIT_FUNC(Measure) } PyObject* mod = Measure::initModule(); // Add Types to module - Base::Interpreter().addType(&Measure::MeasurementPy ::Type,mod,"Measurement"); - Base::Console().Log("Loading Inspection module... done\n"); - Measure::Measurement ::init(); + Base::Interpreter().addType(&Measure::MeasurementPy::Type, mod, "Measurement"); + Base::Interpreter().addType(&Measure::MeasureBasePy::Type, mod, "MeasureBase"); + + Measure::Measurement ::init(); + + // umf classes + Measure::MeasureBase ::init(); + Measure::MeasurePython ::init(); + Measure::MeasureAngle ::init(); + Measure::MeasureDistance ::init(); + Measure::MeasurePosition ::init(); + Measure::MeasureLength ::init(); + Measure::MeasureArea ::init(); + Measure::MeasureRadius ::init(); + + // Add fundamental umf Measure Types + + App::MeasureManager::addMeasureType("DISTANCE", + "Distance", + "Measure::MeasureDistance", + MeasureDistance::isValidSelection, + MeasureDistance::isPrioritizedSelection + ); + + App::MeasureManager::addMeasureType( + "ANGLE", + "Angle", + "Measure::MeasureAngle", + MeasureAngle::isValidSelection, + MeasureAngle::isPrioritizedSelection + ); + + App::MeasureManager::addMeasureType( + "LENGTH", + "Length", + "Measure::MeasureLength", + MeasureLength::isValidSelection, + nullptr + ); + + App::MeasureManager::addMeasureType( + "POSITION", + "Position", + "Measure::MeasurePosition", + MeasurePosition::isValidSelection, + nullptr + ); + + App::MeasureManager::addMeasureType( + "AREA", + "Area", + "Measure::MeasureArea", + MeasureArea::isValidSelection, + nullptr + ); + + App::MeasureManager::addMeasureType( + "RADIUS", + "Radius", + "Measure::MeasureRadius", + MeasureRadius::isValidSelection, + MeasureRadius::isPrioritizedSelection + ); + + // load measure callbacks from Part module + auto lengthList = Part::MeasureClient::reportLengthCB(); + for (auto& entry : lengthList) { + MeasureBaseExtendable::addGeometryHandler(entry.m_module, entry.m_callback); + } + auto angleList = Part::MeasureClient::reportAngleCB(); + for (auto& entry : angleList) { + MeasureBaseExtendable::addGeometryHandler(entry.m_module, entry.m_callback); + } + auto areaList = Part::MeasureClient::reportAreaCB(); + for (auto& entry : areaList) { + MeasureBaseExtendable::addGeometryHandler(entry.m_module, entry.m_callback); + } + auto distanceList = Part::MeasureClient::reportDistanceCB(); + for (auto& entry : distanceList) { + MeasureBaseExtendable::addGeometryHandler(entry.m_module, entry.m_callback); + } + auto positionList = Part::MeasureClient::reportPositionCB(); + for (auto& entry : positionList) { + MeasureBaseExtendable::addGeometryHandler(entry.m_module, entry.m_callback); + } + auto radiusList = Part::MeasureClient::reportRadiusCB(); + for (auto& entry : radiusList) { + MeasureBaseExtendable::addGeometryHandler(entry.m_module, entry.m_callback); + } + + + Base::Console().Log("Loading Measure module... done\n"); PyMOD_Return(mod); } // debug print for sketchsolv -void debugprint(const std::string& s) +void debugprint(const std::string& text) { - Base::Console().Log("%s", s.c_str()); + Base::Console().Log("%s", text.c_str()); } diff --git a/src/Mod/Measure/App/CMakeLists.txt b/src/Mod/Measure/App/CMakeLists.txt index 2c22cad1b4..0ccf425de3 100644 --- a/src/Mod/Measure/App/CMakeLists.txt +++ b/src/Mod/Measure/App/CMakeLists.txt @@ -17,19 +17,43 @@ set(Measure_LIBS ) generate_from_xml(MeasurementPy) +generate_from_xml(MeasureBasePy) SET(Python_SRCS MeasurementPy.xml MeasurementPyImp.cpp + MeasureBasePy.xml + MeasureBasePyImp.cpp ) SOURCE_GROUP("Python" FILES ${Python_SRCS}) SET(MeasureModule_SRCS - AppMeasure.cpp - Measurement.cpp - Measurement.h PreCompiled.cpp PreCompiled.h + AppMeasure.cpp + +# original service routines + Measurement.cpp + Measurement.h + +# umf + MeasureBase.cpp + MeasureBase.h + MeasureAngle.cpp + MeasureAngle.h + MeasureDistance.cpp + MeasureDistance.h + MeasurePosition.cpp + MeasurePosition.h + MeasureLength.cpp + MeasureLength.h + MeasureArea.cpp + MeasureArea.h + MeasureRadius.cpp + MeasureRadius.h + + Preferences.cpp + Preferences.h ) SOURCE_GROUP("Module" FILES ${MeasureModule_SRCS}) @@ -45,18 +69,9 @@ if(FREECAD_USE_PCH) ADD_MSVC_PRECOMPILED_HEADER(Measure PreCompiled.h PreCompiled.cpp Measure_CPP_SRCS) endif(FREECAD_USE_PCH) -SET(Measure_Scripts - ../Init.py -) - -add_library(Measure SHARED ${Measure_SRCS} ${Measure_Scripts}) +add_library(Measure SHARED ${Measure_SRCS}) target_link_libraries(Measure ${Measure_LIBS}) -fc_target_copy_resource_flat(Measure - ${CMAKE_CURRENT_SOURCE_DIR} - ${CMAKE_BINARY_DIR}/Mod/Measure - ${Measure_Scripts}) - #if(MSVC) # set_target_properties(Measure PROPERTIES SUFFIX ".pyd") # set_target_properties(Measure PROPERTIES DEBUG_OUTPUT_NAME "Measure_d") diff --git a/src/Mod/Measure/App/MeasureAngle.cpp b/src/Mod/Measure/App/MeasureAngle.cpp new file mode 100644 index 0000000000..87b8fbd419 --- /dev/null +++ b/src/Mod/Measure/App/MeasureAngle.cpp @@ -0,0 +1,264 @@ +/*************************************************************************** + * Copyright (c) 2023 David Friedli * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + + +#include "PreCompiled.h" + +#include +#include +#include +#include +#include +#include + +#include "MeasureAngle.h" + +using namespace Measure; + +PROPERTY_SOURCE(Measure::MeasureAngle, Measure::MeasureBase) + + +MeasureAngle::MeasureAngle() +{ + ADD_PROPERTY_TYPE(Element1,(nullptr), "Measurement", App::Prop_None, "First element of the measurement"); + Element1.setScope(App::LinkScope::Global); + Element1.setAllowExternal(true); + + ADD_PROPERTY_TYPE(Element2,(nullptr), "Measurement", App::Prop_None, "Second element of the measurement"); + Element2.setScope(App::LinkScope::Global); + Element2.setAllowExternal(true); + + ADD_PROPERTY_TYPE(Angle,(0.0) ,"Measurement",App::PropertyType(App::Prop_ReadOnly|App::Prop_Output), + "Angle between the two elements"); + Angle.setUnit(Base::Unit::Angle); +} + +MeasureAngle::~MeasureAngle() = default; + + +bool MeasureAngle::isValidSelection(const App::MeasureSelection& selection) +{ + if (selection.size() != 2) { + return false; + } + + for (auto element : selection) { + auto objT = element.object; + + App::DocumentObject* ob = objT.getObject(); + const std::string& subName = objT.getSubName(); + const char* className = objT.getSubObject()->getTypeId().getName(); + std::string mod = Base::Type::getModuleName(className); + + if (!hasGeometryHandler(mod)) { + return false; + } + + App::MeasureHandler handler = App::MeasureManager::getMeasureHandler(mod.c_str()); + App::MeasureElementType type = handler.typeCb(ob, subName.c_str()); + + if (type == App::MeasureElementType::INVALID) { + return false; + } + + if (!(type == App::MeasureElementType::LINE || + type == App::MeasureElementType::PLANE || + type == App::MeasureElementType::LINESEGMENT)) { + return false; + } + } + return true; +} + +bool MeasureAngle::isPrioritizedSelection(const App::MeasureSelection& selection) +{ + if (selection.size() != 2) { + return false; + } + + // Check if the two elements are parallel + auto element1 = selection.at(0); + auto objT1 = element1.object; + App::DocumentObject* ob1 = objT1.getObject(); + std::string sub1 = objT1.getSubName(); + Base::Vector3d vec1; + getVec(*ob1, sub1, vec1); + + auto element2 = selection.at(1); + auto objT2 = element2.object; + App::DocumentObject* ob2 = objT2.getObject(); + std::string sub2 = objT2.getSubName(); + Base::Vector3d vec2; + getVec(*ob2, sub2, vec2); + + + double angle = std::fmod(vec1.GetAngle(vec2), D_PI); + return angle > Base::Precision::Angular(); +} + + +void MeasureAngle::parseSelection(const App::MeasureSelection& selection) { + + assert(selection.size() >= 2); + + auto element1 = selection.at(0); + auto objT1 = element1.object; + App::DocumentObject* ob1 = objT1.getObject(); + const std::vector elems1 = {objT1.getSubName()}; + Element1.setValue(ob1, elems1); + + auto element2 = selection.at(1); + auto objT2 = element2.object; + App::DocumentObject* ob2 = objT2.getObject(); + const std::vector elems2 = {objT2.getSubName()}; + Element2.setValue(ob2, elems2); +} + + +bool MeasureAngle::getVec(App::DocumentObject& ob, std::string& subName, Base::Vector3d& vecOut) { + const char* className = ob.getSubObject(subName.c_str())->getTypeId().getName(); + std::string mod = Base::Type::getModuleName(className); + + if (!hasGeometryHandler(mod)) { + return false; + } + + auto handler = getGeometryHandler(mod); + App::SubObjectT subject{&ob, subName.c_str()}; + auto info = handler(subject); + + auto angleInfo = std::dynamic_pointer_cast(info); + vecOut = angleInfo->orientation; + return true; +} + +Base::Vector3d MeasureAngle::getLoc(App::DocumentObject& ob, std::string& subName) +{ + const char* className = ob.getSubObject(subName.c_str())->getTypeId().getName(); + std::string mod = Base::Type::getModuleName(className); + + if (!hasGeometryHandler(mod)) { + return Base::Vector3d(); + } + + auto handler = getGeometryHandler(mod); + App::SubObjectT subject{&ob, subName.c_str()}; + auto info = handler(subject); + auto angleInfo = std::dynamic_pointer_cast(info); + return angleInfo->position; +} + +gp_Vec MeasureAngle::vector1() { + + App::DocumentObject* ob = Element1.getValue(); + std::vector subs = Element1.getSubValues(); + + if (!ob || !ob->isValid() || subs.empty() ) { + return {}; + } + + Base::Vector3d vec; + getVec(*ob, subs.at(0), vec); + return gp_Vec(vec.x, vec.y, vec.z); +} + +gp_Vec MeasureAngle::vector2() { + App::DocumentObject* ob = Element2.getValue(); + std::vector subs = Element2.getSubValues(); + + if (!ob || !ob->isValid() || subs.empty() ) { + return gp_Vec(); + } + + Base::Vector3d vec; + getVec(*ob, subs.at(0), vec); + return gp_Vec(vec.x, vec.y, vec.z); +} + +gp_Vec MeasureAngle::location1() { + + App::DocumentObject* ob = Element1.getValue(); + std::vector subs = Element1.getSubValues(); + + if (!ob || !ob->isValid() || subs.empty() ) { + return {}; + } + auto temp = getLoc(*ob, subs.at(0)); + return {temp.x, temp.y, temp.z}; +} +gp_Vec MeasureAngle::location2() { + App::DocumentObject* ob = Element2.getValue(); + std::vector subs = Element2.getSubValues(); + + if (!ob || !ob->isValid() || subs.empty() ) { + return {}; + } + + auto temp = getLoc(*ob, subs.at(0)); + return {temp.x, temp.y, temp.z}; +} + +App::DocumentObjectExecReturn *MeasureAngle::execute() +{ + App::DocumentObject* ob1 = Element1.getValue(); + std::vector subs1 = Element1.getSubValues(); + + App::DocumentObject* ob2 = Element2.getValue(); + std::vector subs2 = Element2.getSubValues(); + + if (!ob1 || !ob1->isValid() || !ob2 || !ob2->isValid()) { + return new App::DocumentObjectExecReturn("Submitted object(s) is not valid"); + } + + if (subs1.empty() || subs2.empty()) { + return new App::DocumentObjectExecReturn("No geometry element picked"); + } + + Base::Vector3d vec1; + getVec(*ob1, subs1.at(0), vec1); + + Base::Vector3d vec2; + getVec(*ob2, subs2.at(0), vec2); + + Angle.setValue(Base::toDegrees(vec1.GetAngle(vec2))); + + return DocumentObject::StdReturn; +} + +void MeasureAngle::onChanged(const App::Property* prop) +{ + + if (prop == &Element1 || prop == &Element2) { + if (!isRestoring()) { + App::DocumentObjectExecReturn *ret = recompute(); + delete ret; + } + } + DocumentObject::onChanged(prop); +} + +//! Return the object we are measuring +//! used by the viewprovider in determining visibility +std::vector MeasureAngle::getSubject() const +{ + return {Element1.getValue()}; +} + diff --git a/src/Mod/Measure/App/MeasureAngle.h b/src/Mod/Measure/App/MeasureAngle.h new file mode 100644 index 0000000000..adbb47f62b --- /dev/null +++ b/src/Mod/Measure/App/MeasureAngle.h @@ -0,0 +1,93 @@ +/*************************************************************************** + * Copyright (c) 2023 David Friedli * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + + +#ifndef APP_MEASUREANGLE_H +#define APP_MEASUREANGLE_H + +#include + +#include + +#include +#include +#include +#include + +#include + +#include "MeasureBase.h" + + +namespace Measure +{ + + +class MeasureExport MeasureAngle : public Measure::MeasureBaseExtendable +{ + PROPERTY_HEADER_WITH_OVERRIDE(Measure::MeasureAngle); + +public: + /// Constructor + MeasureAngle(); + ~MeasureAngle() override; + + App::PropertyLinkSub Element1; + App::PropertyLinkSub Element2; + App::PropertyAngle Angle; + + App::DocumentObjectExecReturn *execute() override; + + const char* getViewProviderName() const override { + return "MeasureGui::ViewProviderMeasureAngle"; + } + + static bool isValidSelection(const App::MeasureSelection& selection); + static bool isPrioritizedSelection(const App::MeasureSelection& selection); + void parseSelection(const App::MeasureSelection& selection) override; + + std::vector getInputProps() override {return {"Element1", "Element2"};} + App::Property* getResultProp() override {return &this->Angle;} + + // Return the object we are measuring + std::vector getSubject() const override; + + + static bool getVec(App::DocumentObject& ob, std::string& subName, Base::Vector3d& vecOut); + Base::Vector3d getLoc(App::DocumentObject& ob, std::string& subName); + + // Orientation Vectors + gp_Vec vector1(); + gp_Vec vector2(); + + // Location Vectors + gp_Vec location1(); + gp_Vec location2(); + +private: + + void onChanged(const App::Property* prop) override; +}; + +} //namespace Measure + + +#endif // APP_MEASUREANGLE_H diff --git a/src/Mod/Measure/App/MeasureArea.cpp b/src/Mod/Measure/App/MeasureArea.cpp new file mode 100644 index 0000000000..553a3e6acf --- /dev/null +++ b/src/Mod/Measure/App/MeasureArea.cpp @@ -0,0 +1,181 @@ +/*************************************************************************** + * Copyright (c) 2023 David Friedli * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + + +#include "PreCompiled.h" + +#include +#include +#include + +#include "MeasureArea.h" + + +using namespace Measure; + +PROPERTY_SOURCE(Measure::MeasureArea, Measure::MeasureBase) + + + +MeasureArea::MeasureArea() +{ + ADD_PROPERTY_TYPE(Elements,(nullptr), "Measurement", App::Prop_None, "Element to get the area from"); + Elements.setScope(App::LinkScope::Global); + Elements.setAllowExternal(true); + + ADD_PROPERTY_TYPE(Area,(0.0), "Measurement", App::PropertyType(App::Prop_ReadOnly|App::Prop_Output), + "Area of element"); + +} + +MeasureArea::~MeasureArea() = default; + + +bool MeasureArea::isValidSelection(const App::MeasureSelection& selection){ + + if (selection.empty()) { + return false; + } + + for (auto element : selection) { + auto objT = element.object; + + App::DocumentObject* ob = objT.getObject(); + const std::string& subName = objT.getSubName(); + const char* className = objT.getSubObject()->getTypeId().getName(); + std::string mod = Base::Type::getModuleName(className); + + if (!hasGeometryHandler(mod)) { + return false; + } + + App::MeasureHandler handler = App::MeasureManager::getMeasureHandler(mod.c_str()); + App::MeasureElementType type = handler.typeCb(ob, subName.c_str()); + + if (type == App::MeasureElementType::INVALID) { + return false; + } + + // TODO: Also support Cylinder & Volume? + if ((type != App::MeasureElementType::PLANE && type != App::MeasureElementType::CYLINDER)) { + return false; + } + } + return true; +} + +void MeasureArea::parseSelection(const App::MeasureSelection& selection) { + // Set properties from selection, method is only invoked when isValid Selection returns true + + std::vector objects; + std::vector subElements; + + for (auto element : selection) { + auto objT = element.object; + + objects.push_back(objT.getObject()); + subElements.push_back(objT.getSubName()); + } + + Elements.setValues(objects, subElements); +} + + +App::DocumentObjectExecReturn *MeasureArea::execute() +{ + recalculateArea(); + return DocumentObject::StdReturn; +} + +void MeasureArea::recalculateArea() +{ + const std::vector& objects = Elements.getValues(); + const std::vector& subElements = Elements.getSubValues(); + + double result(0.0); + + // Loop through Elements and call the valid geometry handler + for (std::vector::size_type i=0; igetSubObject(subElement.c_str())->getTypeId().getName(); + const std::string& mod = Base::Type::getModuleName(className); + auto handler = getGeometryHandler(mod); + if (!handler) { + throw Base::RuntimeError("No geometry handler available for submitted element type"); + } + + App::SubObjectT subject{object, subElement.c_str()}; + auto info = handler(subject); + auto areaInfo = std::dynamic_pointer_cast(info); + result += areaInfo->area; + } + + Area.setValue(result); +} + +void MeasureArea::onChanged(const App::Property* prop) +{ + if (isRestoring() || isRemoving()) { + return; + } + + if (prop == &Elements) { + recalculateArea(); + } + + MeasureBase::onChanged(prop); +} + + +Base::Placement MeasureArea::getPlacement() { + const std::vector& objects = Elements.getValues(); + const std::vector& subElements = Elements.getSubValues(); + + if (objects.empty() || subElements.empty()) { + return Base::Placement(); + } + + App::DocumentObject* object = objects.front(); + std::string subElement = subElements.front(); + const char* className = object->getSubObject(subElement.c_str())->getTypeId().getName(); + const std::string& mod = Base::Type::getModuleName(className); + + auto handler = getGeometryHandler(mod); + if (!handler) { + throw Base::RuntimeError("No geometry handler available for submitted element type"); + } + + App::SubObjectT subject{object, subElement.c_str()}; + auto info = handler(subject); + auto areaInfo = std::dynamic_pointer_cast(info); + return areaInfo->placement; +} + + +//! Return the object we are measuring +//! used by the viewprovider in determining visibility +std::vector MeasureArea::getSubject() const +{ + return Elements.getValues(); +} diff --git a/src/Mod/Measure/App/MeasureArea.h b/src/Mod/Measure/App/MeasureArea.h new file mode 100644 index 0000000000..0fef35e382 --- /dev/null +++ b/src/Mod/Measure/App/MeasureArea.h @@ -0,0 +1,86 @@ +/*************************************************************************** + * Copyright (c) 2023 David Friedli * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + + +#ifndef MEASURE_MEASUREAREA_H +#define MEASURE_MEASUREAREA_H + +#include + +#include + +#include +#include +#include + +#include + +#include "MeasureBase.h" + + +namespace Measure +{ + + + + +class MeasureExport MeasureArea : public Measure::MeasureBaseExtendable +{ + PROPERTY_HEADER_WITH_OVERRIDE(Measure::MeasureArea); + +public: + /// Constructor + MeasureArea(); + ~MeasureArea() override; + + App::PropertyLinkSubList Elements; + App::PropertyArea Area; + + App::DocumentObjectExecReturn *execute() override; + void recalculateArea(); + + const char* getViewProviderName() const override { + return "MeasureGui::ViewProviderMeasure"; + } + + static bool isValidSelection(const App::MeasureSelection& selection); + void parseSelection(const App::MeasureSelection& selection) override; + + std::vector getInputProps() override {return {"Elements"};} + App::Property* getResultProp() override {return &this->Area;} + + // Return a placement for the viewprovider, just use the first element for now + Base::Placement getPlacement() override; + + // Return the object we are measuring + std::vector getSubject() const override; + + +private: + void onChanged(const App::Property* prop) override; + +}; + +} //namespace Measure + + +#endif // MEASURE_MEASUREAREA_H + diff --git a/src/Mod/Measure/App/MeasureBase.cpp b/src/Mod/Measure/App/MeasureBase.cpp new file mode 100644 index 0000000000..07883d42bb --- /dev/null +++ b/src/Mod/Measure/App/MeasureBase.cpp @@ -0,0 +1,211 @@ +/*************************************************************************** + * Copyright (c) 2023 David Friedli * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + +#include "PreCompiled.h" + +#include +#include +#include +#include + +#include "MeasureBase.h" +// Generated from MeasureBasePy.xml +#include "MeasureBasePy.h" + +using namespace Measure; + + +PROPERTY_SOURCE(Measure::MeasureBase, App::DocumentObject) + +MeasureBase::MeasureBase() { + ADD_PROPERTY_TYPE(Placement, (Base::Placement()), nullptr, App::PropertyType(App::Prop_ReadOnly|App::Prop_Output|App::Prop_NoRecompute), "Visual placement of the measurement"); + +} + + +PyObject *MeasureBase::getPyObject(void) +{ + if (PythonObject.is(Py::_None())) { + // ref counter is set to 1 + PythonObject = Py::Object(new MeasureBasePy(this), true); + } + return Py::new_reference_to(PythonObject); +} + +Py::Object MeasureBase::getProxyObject() const { + Base::PyGILStateLocker lock; + App::Property* prop = this->getPropertyByName("Proxy"); + if (!prop) { + return Py::None(); + } + return dynamic_cast(prop)->getValue(); +}; + +std::vector MeasureBase::getSubject() const { + Base::PyGILStateLocker lock; + + Py::Object proxy = getProxyObject(); + + // Pass the feture object to the proxy + Py::Tuple args(1); + args.setItem(0, Py::Object(const_cast(this)->getPyObject())); + + Py::Object ret; + try { + ret = proxy.callMemberFunction("getSubject", args); + } catch (Py::Exception&) { + Base::PyException e; + e.ReportException(); + return {}; + } + + Py::Sequence retTuple(ret); + std::vector retVec; + for (Py::Object o : retTuple) { + retVec.push_back(static_cast(o.ptr())->getDocumentObjectPtr()); + } + + return retVec; + }; + + + + +void MeasureBase::parseSelection(const App::MeasureSelection& selection) { + Base::PyGILStateLocker lock; + + Py::Object proxy = getProxyObject(); + + // Convert selection to python list + Py::Tuple selectionPy = App::MeasureManager::getSelectionPy(selection); + + Py::Tuple args(2); + + // Pass the feture object to the proxy + args.setItem(0, Py::Object(const_cast(this)->getPyObject())); + args.setItem(1, selectionPy); + + // Call the parseSelection method of the proxy object + try { + proxy.callMemberFunction("parseSelection", args); + } catch (Py::Exception&) { + Base::PyException e; + e.ReportException(); + } + +} + + +std::vector MeasureBase::getInputProps() { + Base::PyGILStateLocker lock; + Py::Object proxy = getProxyObject(); + + if (proxy.isNone()) { + return {}; + } + + Py::Object ret; + try { + ret = proxy.callMemberFunction("getInputProps"); + } catch (Py::Exception&) { + Base::PyException e; + e.ReportException(); + return {}; + } + Py::Sequence propsPy(ret); + + // Get cpp vector from propsPy + std::vector props; + for (Py::Object o : propsPy) { + props.push_back(o.as_string()); + } + + return props; +} + + + +QString MeasureBase::getResultString() { + Py::Object proxy = getProxyObject(); + Base::PyGILStateLocker lock; + + if (!proxy.isNone()) { + + // Pass the feture object to the proxy + Py::Tuple args(1); + args.setItem(0, Py::Object(const_cast(this)->getPyObject())); + + Py::Object ret; + try { + ret = proxy.callMemberFunction("getResultString", args); + } catch (Py::Exception&) { + Base::PyException e; + e.ReportException(); + return QString(); + } + return QString::fromStdString(ret.as_string()); + } + + App::Property* prop = getResultProp(); + if (prop == nullptr) { + return QString(); + } + + if (prop->isDerivedFrom(App::PropertyQuantity::getClassTypeId())) { + return static_cast(prop)->getQuantityValue().getUserString(); + } + + + return QString(); +} + +void MeasureBase::onDocumentRestored() { + // Force recompute the measurement + recompute(); +} + +Base::Placement MeasureBase::getPlacement() { + return this->Placement.getValue(); +} + + +// Python Drawing feature --------------------------------------------------------- + +namespace App { +/// @cond DOXERR +PROPERTY_SOURCE_TEMPLATE(Measure::MeasurePython, Measure::MeasureBase) +template<> const char* Measure::MeasurePython::getViewProviderName(void) const { + return "MeasureGui::ViewProviderMeasure"; +} +template<> PyObject* Measure::MeasurePython::getPyObject() { + if (PythonObject.is(Py::_None())) { + // ref counter is set to 1 + PythonObject = Py::Object(new FeaturePythonPyT(this),true); + } + return Py::new_reference_to(PythonObject); +} +/// @endcond + +// explicit template instantiation +template class MeasureExport FeaturePythonT; +} + + diff --git a/src/Mod/Measure/App/MeasureBase.h b/src/Mod/Measure/App/MeasureBase.h new file mode 100644 index 0000000000..fe256dc940 --- /dev/null +++ b/src/Mod/Measure/App/MeasureBase.h @@ -0,0 +1,131 @@ +/*************************************************************************** + * Copyright (c) 2023 David Friedli * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + + +#ifndef MEASURE_MEASUREBASE_H +#define MEASURE_MEASUREBASE_H + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include // needed? + + +namespace Measure +{ + +class MeasureExport MeasureBase : public App::DocumentObject +{ + PROPERTY_HEADER_WITH_OVERRIDE(Measure::MeasureBase); + +public: + MeasureBase(); + ~MeasureBase() override = default; + + App::PropertyPlacement Placement; + + // boost::signals2::signal signalGuiInit; + + //return PyObject as MeasureBasePy + PyObject *getPyObject() override; + + // Initialize measurement properties from selection + virtual void parseSelection(const App::MeasureSelection& selection); + + + virtual QString getResultString(); + + virtual std::vector getInputProps(); + virtual App::Property* getResultProp() {return {};}; + virtual Base::Placement getPlacement(); + + // Return the objects that are measured + virtual std::vector getSubject() const; + +private: + Py::Object getProxyObject() const; + +protected: + void onDocumentRestored() override; +}; + +// Create a scriptable object based on MeasureBase +using MeasurePython = App::FeaturePythonT; + +template +class MeasureExport MeasureBaseExtendable : public MeasureBase +{ + + using GeometryHandler = std::function; + using HandlerMap = std::map; + + +public: + + static void addGeometryHandler(const std::string& module, GeometryHandler callback) { + _mGeometryHandlers[module] = callback; + } + + static GeometryHandler getGeometryHandler(const std::string& module) { + + if (!hasGeometryHandler(module)) { + return {}; + } + + return _mGeometryHandlers[module]; + } + + static void addGeometryHandlers(const std::vector& modules, GeometryHandler callback){ + // TODO: this will replace a callback with a later one. Should we check that there isn't already a + // handler defined for this module? + for (auto& mod : modules) { + _mGeometryHandlers[mod] = callback; + } + } + + + static bool hasGeometryHandler(const std::string& module) { + return (_mGeometryHandlers.count(module) > 0); + } + +private: + inline static HandlerMap _mGeometryHandlers = MeasureBaseExtendable::HandlerMap(); +}; + + + +} //namespace Measure + + +#endif // MEASURE_MEASUREBASE_H diff --git a/src/Mod/Measure/App/MeasureBasePy.xml b/src/Mod/Measure/App/MeasureBasePy.xml new file mode 100644 index 0000000000..c56bf805d1 --- /dev/null +++ b/src/Mod/Measure/App/MeasureBasePy.xml @@ -0,0 +1,20 @@ + + + + + + User documentation here + + Developer documentation here + + + diff --git a/src/Mod/Measure/App/MeasureBasePyImp.cpp b/src/Mod/Measure/App/MeasureBasePyImp.cpp new file mode 100644 index 0000000000..ca9af1e6eb --- /dev/null +++ b/src/Mod/Measure/App/MeasureBasePyImp.cpp @@ -0,0 +1,40 @@ +#include "PreCompiled.h" + +#include "MeasureBase.h" + +// Inclusion of the generated files (generated out of MeasureBasePy.xml) +#include "MeasureBasePy.h" +#include "MeasureBasePy.cpp" + + +#include + + +// returns a string which represents the object e.g. when printed in python +std::string MeasureBasePy::representation() const +{ + return ""; +} + +PyObject *MeasureBasePy::PyMake(struct _typeobject *, PyObject *, PyObject *) // Python wrapper +{ + // create a new instance of MeasureBasePy and the Twin object + return new MeasureBasePy(new MeasureBase); +} + + +// constructor method +int MeasureBasePy::PyInit(PyObject* /*args*/, PyObject* /*kwd*/) +{ + return 0; +} + +PyObject *MeasureBasePy::getCustomAttributes(const char* /*attr*/) const +{ + return nullptr; +} + +int MeasureBasePy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/) +{ + return 0; +} diff --git a/src/Mod/Measure/App/MeasureDistance.cpp b/src/Mod/Measure/App/MeasureDistance.cpp new file mode 100644 index 0000000000..9a04b77bcd --- /dev/null +++ b/src/Mod/Measure/App/MeasureDistance.cpp @@ -0,0 +1,231 @@ +/*************************************************************************** + * Copyright (c) 2023 David Friedli * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + + +#include "PreCompiled.h" + +#include +#include +#include +#include +#include +#include + +#include "MeasureDistance.h" + + +using namespace Measure; + +PROPERTY_SOURCE(Measure::MeasureDistance, Measure::MeasureBase) + + +MeasureDistance::MeasureDistance() +{ + ADD_PROPERTY_TYPE(Element1,(nullptr), "Measurement", App::Prop_None, "First element of the measurement"); + Element1.setScope(App::LinkScope::Global); + Element1.setAllowExternal(true); + + ADD_PROPERTY_TYPE(Element2,(nullptr), "Measurement", App::Prop_None, "Second element of the measurement"); + Element2.setScope(App::LinkScope::Global); + Element2.setAllowExternal(true); + + ADD_PROPERTY_TYPE(Distance,(0.0) ,"Measurement",App::PropertyType(App::Prop_ReadOnly|App::Prop_Output), + "Distance between the two elements"); + Distance.setUnit(Base::Unit::Length); + + ADD_PROPERTY_TYPE(Position1,(Base::Vector3d(0.0,0.0,0.0)),"Measurement", App::Prop_Hidden, "Position1"); + ADD_PROPERTY_TYPE(Position2,(Base::Vector3d(0.0,1.0,0.0)),"Measurement", App::Prop_Hidden, "Position2"); + +} + +MeasureDistance::~MeasureDistance() = default; + + +bool MeasureDistance::isValidSelection(const App::MeasureSelection& selection){ + + if (selection.size() != 2) { + return false; + } + + for (auto element : selection) { + + auto objT = element.object; + + App::DocumentObject* ob = objT.getObject(); + const std::string& subName = objT.getSubName(); + const char* className = objT.getSubObject()->getTypeId().getName(); + std::string mod = Base::Type::getModuleName(className); + + if (!hasGeometryHandler(mod)) { + return false; + } + + App::MeasureHandler handler = App::MeasureManager::getMeasureHandler(mod.c_str()); + App::MeasureElementType type = handler.typeCb(ob, subName.c_str()); + + if (type == App::MeasureElementType::INVALID) { + return false; + } + + if ( + type != App::MeasureElementType::POINT && + type != App::MeasureElementType::LINE && + type != App::MeasureElementType::LINESEGMENT && + type != App::MeasureElementType::CIRCLE && + type != App::MeasureElementType::ARC && + type != App::MeasureElementType::CURVE && + type != App::MeasureElementType::PLANE && + type != App::MeasureElementType::CYLINDER + ) { + return false; + } + } + return true; +} + +bool MeasureDistance::isPrioritizedSelection(const App::MeasureSelection& selection) { + + (void)selection; + + // TODO: Check if Elements ar parallel + // if (selection.size() == 2) { + // return true; + // } + + return false; +} + + +void MeasureDistance::parseSelection(const App::MeasureSelection& selection) { + + assert(selection.size() >= 2); + + auto element1 = selection.at(0); + auto objT1 = element1.object; + App::DocumentObject* ob1 = objT1.getObject(); + const std::vector elems1 = {objT1.getSubName()}; + Element1.setValue(ob1, elems1); + + auto element2 = selection.at(1); + auto objT2 = element2.object; + App::DocumentObject* ob2 = objT2.getObject(); + const std::vector elems2 = {objT2.getSubName()}; + Element2.setValue(ob2, elems2); +} + + +bool MeasureDistance::getShape(App::PropertyLinkSub* prop, TopoDS_Shape& rShape) { + + App::DocumentObject* ob = prop->getValue(); + std::vector subs = prop->getSubValues(); + + if (!ob || !ob->isValid() || subs.empty() ) { + return false; + } + + std::string subName = subs.at(0); + const char* className = ob->getSubObject(subName.c_str())->getTypeId().getName(); + std::string mod = Base::Type::getModuleName(className); + + if (!hasGeometryHandler(mod)) { + return false; + } + + auto handler = getGeometryHandler(mod); + std::string obName = static_cast(ob->getNameInDocument()); + App::SubObjectT subject{ob, subName.c_str()}; + auto info = handler(subject); + if (!info->valid) { + return false; + } + auto distanceInfo = std::dynamic_pointer_cast(info); + + rShape = *distanceInfo->getShape(); + return true; +} + + +App::DocumentObjectExecReturn *MeasureDistance::execute() +{ + + App::DocumentObject* ob1 = Element1.getValue(); + std::vector subs1 = Element1.getSubValues(); + + App::DocumentObject* ob2 = Element2.getValue(); + std::vector subs2 = Element2.getSubValues(); + + if (!ob1 || !ob1->isValid() || !ob2 || !ob2->isValid()) { + return new App::DocumentObjectExecReturn("Submitted object(s) is not valid"); + } + + if (subs1.empty() || subs2.empty()) { + return new App::DocumentObjectExecReturn("No geometry element picked"); + } + + // Get both shapes from geometry handler + TopoDS_Shape shape1; + if (!this->getShape(&Element1, shape1)) { + return new App::DocumentObjectExecReturn("Could not get shape"); + } + + TopoDS_Shape shape2; + if (!this->getShape(&Element2, shape2)) { + return new App::DocumentObjectExecReturn("Could not get shape"); + } + + // Calculate the extrema + BRepExtrema_DistShapeShape measure(shape1, shape2); + if (!measure.IsDone() || measure.NbSolution() < 1) { + return new App::DocumentObjectExecReturn("Could not get extrema"); + } + + Distance.setValue(measure.Value()); + + gp_Pnt p1 = measure.PointOnShape1(1); + Position1.setValue(p1.X(), p1.Y(), p1.Z()); + + gp_Pnt p2 = measure.PointOnShape2(1); + Position2.setValue(p2.X(), p2.Y(), p2.Z()); + + + return DocumentObject::StdReturn; +} + +void MeasureDistance::onChanged(const App::Property* prop) +{ + + if (prop == &Element1 || prop == &Element2) { + if (!isRestoring()) { + App::DocumentObjectExecReturn *ret = recompute(); + delete ret; + } + } + DocumentObject::onChanged(prop); +} + + +//! Return the object we are measuring +//! used by the viewprovider in determining visibility +std::vector MeasureDistance::getSubject() const +{ + return {Element1.getValue()}; +} + diff --git a/src/Mod/Measure/App/MeasureDistance.h b/src/Mod/Measure/App/MeasureDistance.h new file mode 100644 index 0000000000..ad6f8984ff --- /dev/null +++ b/src/Mod/Measure/App/MeasureDistance.h @@ -0,0 +1,87 @@ +/*************************************************************************** + * Copyright (c) 2023 David Friedli * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + + +#ifndef MEASUREAPP_MEASUREDISTANCE_H +#define MEASUREAPP_MEASUREDISTANCE_H + +#include + +#include + +#include +#include +#include + +#include + +#include "MeasureBase.h" + +namespace Measure +{ + + + +class MeasureExport MeasureDistance : public Measure::MeasureBaseExtendable +{ + PROPERTY_HEADER_WITH_OVERRIDE(Measure::MeasureDistance); + +public: + /// Constructor + MeasureDistance(); + ~MeasureDistance() override; + + App::PropertyLinkSub Element1; + App::PropertyLinkSub Element2; + App::PropertyDistance Distance; + + // Position properties for the viewprovider + App::PropertyVector Position1; + App::PropertyVector Position2; + + App::DocumentObjectExecReturn *execute() override; + + const char* getViewProviderName() const override { + return "MeasureGui::ViewProviderMeasureDistance"; + } + + static bool isValidSelection(const App::MeasureSelection& selection); + static bool isPrioritizedSelection(const App::MeasureSelection& selection); + void parseSelection(const App::MeasureSelection& selection) override; + + std::vector getInputProps() override {return {"Element1", "Element2"};} + App::Property* getResultProp() override {return &this->Distance;} + + bool getShape(App::PropertyLinkSub* prop, TopoDS_Shape& rShape); + + // Return the object we are measuring + std::vector getSubject() const override; + + +private: + + void onChanged(const App::Property* prop) override; +}; + +} //namespace Measure + + +#endif // MEASUREAPP_MEASUREDISTANCE_H diff --git a/src/Mod/Measure/App/MeasureLength.cpp b/src/Mod/Measure/App/MeasureLength.cpp new file mode 100644 index 0000000000..ba95db39a1 --- /dev/null +++ b/src/Mod/Measure/App/MeasureLength.cpp @@ -0,0 +1,184 @@ +/*************************************************************************** + * Copyright (c) 2023 David Friedli * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + + +#include "PreCompiled.h" + +#include +#include +#include + +#include + +#include "MeasureLength.h" + + +using namespace Measure; + +PROPERTY_SOURCE(Measure::MeasureLength, Measure::MeasureBase) + + + +MeasureLength::MeasureLength() +{ + ADD_PROPERTY_TYPE(Elements,(nullptr), "Measurement", App::Prop_None, "Elements to get the length from"); + Elements.setScope(App::LinkScope::Global); + Elements.setAllowExternal(true); + + ADD_PROPERTY_TYPE(Length,(0.0) ,"Measurement",App::PropertyType(App::Prop_ReadOnly|App::Prop_Output), + "Length of selection"); + +} + +MeasureLength::~MeasureLength() = default; + + +bool MeasureLength::isValidSelection(const App::MeasureSelection& selection){ + + if (selection.empty()) { + return false; + } + + for (auto element : selection) { + auto objT = element.object; + + App::DocumentObject* ob = objT.getObject(); + const std::string& subName = objT.getSubName(); + const char* className = objT.getSubObject()->getTypeId().getName(); + std::string mod = Base::Type::getModuleName(className); + + if (!hasGeometryHandler(mod)) { + return false; + } + + App::MeasureHandler handler = App::MeasureManager::getMeasureHandler(mod.c_str()); + App::MeasureElementType type = handler.typeCb(ob, subName.c_str()); + + if (type == App::MeasureElementType::INVALID) { + return false; + } + + if ((type != App::MeasureElementType::LINESEGMENT && type != App::MeasureElementType::CIRCLE + && type != App::MeasureElementType::ARC && type != App::MeasureElementType::CURVE)) { + return false; + } + } + return true; +} + +void MeasureLength::parseSelection(const App::MeasureSelection& selection) { + // Set properties from selection, method is only invoked when isValid Selection returns true + + std::vector objects; + std::vector subElements; + + for (auto element : selection) { + auto objT = element.object; + + objects.push_back(objT.getObject()); + subElements.push_back(objT.getSubName()); + } + + Elements.setValues(objects, subElements); +} + + +App::DocumentObjectExecReturn *MeasureLength::execute() +{ + recalculateLength(); + return DocumentObject::StdReturn; +} + +void MeasureLength::recalculateLength() +{ + const std::vector& objects = Elements.getValues(); + const std::vector& subElements = Elements.getSubValues(); + + double result(0.0); + + // Loop through Elements and call the valid geometry handler + for (std::vector::size_type i=0; igetSubObject(subElement.c_str())->getTypeId().getName(); + const std::string& mod = Base::Type::getModuleName(className); + auto handler = getGeometryHandler(mod); + if (!handler) { + throw Base::RuntimeError("No geometry handler available for submitted element type"); + } + + App::SubObjectT subject{object, subElement.c_str()}; + auto info = handler(subject); + auto lengthInfo = std::dynamic_pointer_cast(info); + result += lengthInfo->length; + } + + Length.setValue(result); +} + +void MeasureLength::onChanged(const App::Property* prop) +{ + if (isRestoring() || isRemoving()) { + return; + } + + if (prop == &Elements) { + recalculateLength(); + } + + MeasureBase::onChanged(prop); +} + + +Base::Placement MeasureLength::getPlacement() { + const std::vector& objects = Elements.getValues(); + const std::vector& subElements = Elements.getSubValues(); + + if (!objects.size() || !subElements.size()) { + return Base::Placement(); + } + + App::DocumentObject* object = objects.front(); + std::string subElement = subElements.front(); + const char* className = object->getSubObject(subElement.c_str())->getTypeId().getName(); + const std::string& mod = Base::Type::getModuleName(className); + + auto handler = getGeometryHandler(mod); + if (!handler) { + throw Base::RuntimeError("No geometry handler available for submitted element type"); + } + + App::SubObjectT subject{object, subElement.c_str()}; + auto info = handler(subject); + auto lengthInfo = std::dynamic_pointer_cast(info); + return lengthInfo->placement; +} + + +//! Return the object we are measuring +//! used by the viewprovider in determining visibility +std::vector MeasureLength::getSubject() const +{ + return Elements.getValues(); +} + diff --git a/src/Mod/Measure/App/MeasureLength.h b/src/Mod/Measure/App/MeasureLength.h new file mode 100644 index 0000000000..3942925754 --- /dev/null +++ b/src/Mod/Measure/App/MeasureLength.h @@ -0,0 +1,81 @@ +/*************************************************************************** + * Copyright (c) 2023 David Friedli * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + + +#ifndef MEASURE_MEASURELENGTH_H +#define MEASURE_MEASURELENGTH_H + +#include + +#include +#include +#include + +#include +#include + +#include "MeasureBase.h" + +namespace Measure +{ + +class MeasureExport MeasureLength : public Measure::MeasureBaseExtendable +{ + PROPERTY_HEADER_WITH_OVERRIDE(Measure::MeasureLength); + +public: + /// Constructor + MeasureLength(); + ~MeasureLength() override; + + App::PropertyLinkSubList Elements; + App::PropertyDistance Length; + + App::DocumentObjectExecReturn *execute() override; + void recalculateLength(); + + const char* getViewProviderName() const override { + return "MeasureGui::ViewProviderMeasure"; + } + + static bool isValidSelection(const App::MeasureSelection& selection); + void parseSelection(const App::MeasureSelection& selection) override; + + std::vector getInputProps() override {return {"Elements"};} + App::Property* getResultProp() override {return &this->Length;} + + // Return a placement for the viewprovider, just use the first element for now + Base::Placement getPlacement(); + + // Return the object we are measuring + std::vector getSubject() const override; + + +private: + void onChanged(const App::Property* prop) override; + +}; + +} //namespace Measure + + +#endif // MEASURE_MEASURELENGTH_H + diff --git a/src/Mod/Measure/App/MeasurePosition.cpp b/src/Mod/Measure/App/MeasurePosition.cpp new file mode 100644 index 0000000000..c4c00d413d --- /dev/null +++ b/src/Mod/Measure/App/MeasurePosition.cpp @@ -0,0 +1,177 @@ +/*************************************************************************** + * Copyright (c) 2023 David Friedli * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + + +#include "PreCompiled.h" + +#include +#include +#include +#include + +#include "MeasurePosition.h" + + +using namespace Measure; + +PROPERTY_SOURCE(Measure::MeasurePosition, Measure::MeasureBase) + + + +MeasurePosition::MeasurePosition() +{ + ADD_PROPERTY_TYPE(Element,(nullptr), "Measurement", App::Prop_None, "Element to get the position from"); + Element.setScope(App::LinkScope::Global); + Element.setAllowExternal(true); + + + ADD_PROPERTY_TYPE(Position,(0.0, 0.0, 0.0), "Measurement", App::PropertyType(App::Prop_ReadOnly|App::Prop_Output), + "The absolute position"); + +} + +MeasurePosition::~MeasurePosition() = default; + + +bool MeasurePosition::isValidSelection(const App::MeasureSelection& selection){ + + if (selection.empty() || selection.size() > 1) { + return false; + } + + for (auto element : selection) { + auto objT = element.object; + + App::DocumentObject* ob = objT.getObject(); + const std::string& subName = objT.getSubName(); + const char* className = objT.getSubObject()->getTypeId().getName(); + std::string mod = Base::Type::getModuleName(className); + + if (!hasGeometryHandler(mod)) { + return false; + } + + App::MeasureHandler handler = App::MeasureManager::getMeasureHandler(mod.c_str()); + App::MeasureElementType type = handler.typeCb(ob, subName.c_str()); + + if (type == App::MeasureElementType::INVALID) { + return false; + } + + if (type != App::MeasureElementType::POINT) { + return false; + } + } + return true; +} + +void MeasurePosition::parseSelection(const App::MeasureSelection& selection) { + // Set properties from selection, method is only invoked when isValid Selection returns true + + for (auto element : selection) { + auto objT = element.object; + + std::vector subElements {objT.getSubName()}; + Element.setValue(objT.getObject(), subElements); + break; + } +} + + +App::DocumentObjectExecReturn *MeasurePosition::execute() +{ + recalculatePosition(); + return DocumentObject::StdReturn; +} + +void MeasurePosition::recalculatePosition() +{ + const App::DocumentObject* object = Element.getValue(); + const std::vector& subElements = Element.getSubValues(); + + // Get the position of the first point + std::string subElement = subElements.front(); + + // Get the Geometry handler based on the module + const char* className = object->getSubObject(subElement.c_str())->getTypeId().getName(); + const std::string& mod = Base::Type::getModuleName(className); + auto handler = getGeometryHandler(mod); + if (!handler) { + throw Base::RuntimeError("No geometry handler available for submitted element type"); + } + + App::SubObjectT subject{object, subElement.c_str()}; + auto info = handler(subject); + auto positionInfo = std::dynamic_pointer_cast(info); + Position.setValue(positionInfo->position); +} + +void MeasurePosition::onChanged(const App::Property* prop) +{ + if (isRestoring() || isRemoving()) { + return; + } + + if (prop == &Element) { + recalculatePosition(); + } + DocumentObject::onChanged(prop); +} + + +QString MeasurePosition::getResultString() { + App::Property* prop = this->getResultProp(); + if (prop == nullptr) { + return {}; + } + + Base::Vector3d value = Position.getValue(); + QString unit = Position.getUnit().getString(); + int precision = 2; + QString text; + #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) + QTextStream(&text) + << "X: " << QString::number(value.x, 'f', precision) << " " << unit << endl + << "Y: " << QString::number(value.y, 'f', precision) << " " << unit << endl + << "Z: " << QString::number(value.z, 'f', precision) << " " << unit; + #else + QTextStream(&text) + << "X: " << QString::number(value.x, 'f', precision) << " " << unit << Qt::endl + << "Y: " << QString::number(value.y, 'f', precision) << " " << unit << Qt::endl + << "Z: " << QString::number(value.z, 'f', precision) << " " << unit; + #endif + return text; +} + + +Base::Placement MeasurePosition::getPlacement() { + Base::Placement placement; + placement.setPosition(Position.getValue()); + return placement; +} + + +//! Return the object we are measuring +//! used by the viewprovider in determining visibility +std::vector MeasurePosition::getSubject() const +{ + return {Element.getValue()}; +} diff --git a/src/Mod/Measure/App/MeasurePosition.h b/src/Mod/Measure/App/MeasurePosition.h new file mode 100644 index 0000000000..054ff37021 --- /dev/null +++ b/src/Mod/Measure/App/MeasurePosition.h @@ -0,0 +1,82 @@ +/*************************************************************************** + * Copyright (c) 2023 David Friedli * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + + +#ifndef APP_MEASUREPOSITION_H +#define APP_MEASUREPOSITION_H + +#include + +#include + +#include +#include +#include + +#include + +#include "MeasureBase.h" + + +namespace Measure +{ + + +class MeasureExport MeasurePosition : public Measure::MeasureBaseExtendable +{ + PROPERTY_HEADER_WITH_OVERRIDE(Measure::MeasurePosition); + +public: + /// Constructor + MeasurePosition(); + ~MeasurePosition() override; + + App::PropertyLinkSub Element; + App::PropertyPosition Position; + + App::DocumentObjectExecReturn *execute() override; + void recalculatePosition(); + + const char* getViewProviderName() const override { + return "MeasureGui::ViewProviderMeasure"; + } + + static bool isValidSelection(const App::MeasureSelection& selection); + void parseSelection(const App::MeasureSelection& selection) override; + + std::vector getInputProps() override {return {"Element"};} + App::Property* getResultProp() override {return &this->Position;} + QString getResultString() override; + + Base::Placement getPlacement() override; + + // Return the object we are measuring + std::vector getSubject() const override; + +private: + + void onChanged(const App::Property* prop) override; +}; + +} //namespace Measure + + +#endif // APP_MEASUREPOSITION_H diff --git a/src/Mod/Measure/App/MeasureRadius.cpp b/src/Mod/Measure/App/MeasureRadius.cpp new file mode 100644 index 0000000000..c573aff30e --- /dev/null +++ b/src/Mod/Measure/App/MeasureRadius.cpp @@ -0,0 +1,211 @@ +/*************************************************************************** + * Copyright (c) 2023 WandererFan * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + +#include "PreCompiled.h" + + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "MeasureRadius.h" + + +using namespace Measure; + +PROPERTY_SOURCE(Measure::MeasureRadius, Measure::MeasureBase) + + + +MeasureRadius::MeasureRadius() +{ + ADD_PROPERTY_TYPE(Element,(nullptr), "Measurement", App::Prop_None, "Element to get the radius from"); + Element.setScope(App::LinkScope::Global); + Element.setAllowExternal(true); + + ADD_PROPERTY_TYPE(Radius,(0.0) ,"Measurement",App::PropertyType(App::Prop_ReadOnly|App::Prop_Output), + "Radius of selection"); + +} + +MeasureRadius::~MeasureRadius() = default; + +//! validate all the object+subelement pairs in the selection. Must be circle or arc +//! and have a geometry handler available. We only calculate radius if there is a +//! single valid item in the selection +bool MeasureRadius::isValidSelection(const App::MeasureSelection& selection){ + + if (selection.empty() || selection.size() > 1) { + // too few or too many selections + return false; + } + + auto element = selection.front(); + auto objT = element.object; + + App::DocumentObject* ob = objT.getObject(); + const std::string& subName = objT.getSubName(); + const char* className = objT.getSubObject()->getTypeId().getName(); + std::string mod = Base::Type::getModuleName(className); + + if (!hasGeometryHandler(mod)) { + return false; + } + + App::MeasureHandler handler = App::MeasureManager::getMeasureHandler(mod.c_str()); + App::MeasureElementType type = handler.typeCb(ob, subName.c_str()); + + if (type == App::MeasureElementType::INVALID) { + return false; + } + + if (type != App::MeasureElementType::CIRCLE + && type != App::MeasureElementType::ARC) { + return false; + } + + return true; +} + +//! return true if the selection is particularly interesting to MeasureRadius. +//! In this case we claim circles and arcs. +bool MeasureRadius::isPrioritizedSelection(const App::MeasureSelection& selection) { + if (selection.size() != 1) { + return false; + } + + auto element = selection.front(); + auto objT = element.object; + + App::DocumentObject* ob = objT.getObject(); + const std::string& subName = objT.getSubName(); + const char* className = objT.getSubObject()->getTypeId().getName(); + std::string mod = Base::Type::getModuleName(className); + + if (!hasGeometryHandler(mod)) { + return false; + } + + App::MeasureHandler handler = App::MeasureManager::getMeasureHandler(mod.c_str()); + App::MeasureElementType type = handler.typeCb(ob, subName.c_str()); + + if (type == App::MeasureElementType::CIRCLE + || type == App::MeasureElementType::ARC) { + return true; + } + + return false; +} + + +//! Set properties from first item in selection. assumes a valid selection. +void MeasureRadius::parseSelection(const App::MeasureSelection& selection) { + auto element = selection.front(); + auto objT = element.object; + + std::vector subElementList { objT.getSubName() }; + Element.setValue(objT.getObject(), subElementList); +} + + +App::DocumentObjectExecReturn *MeasureRadius::execute() +{ + recalculateRadius(); + return DocumentObject::StdReturn; +} + + +void MeasureRadius::recalculateRadius() +{ + Radius.setValue(getMeasureInfoFirst()->radius); +} + +void MeasureRadius::onChanged(const App::Property* prop) +{ + if (isRestoring() || isRemoving()) { + return; + } + + if (prop == &Element) { + recalculateRadius(); + } + + MeasureBase::onChanged(prop); +} + + +//! return a placement (location + orientation) for the first element +Base::Placement MeasureRadius::getPlacement() { + auto loc = getMeasureInfoFirst()->pointOnCurve; + auto p = Base::Placement(); + p.setPosition(loc); + return p; +} + + +//! return the pointOnCurve element in MeasureRadiusInfo for the first element +Base::Vector3d MeasureRadius::getPointOnCurve() const +{ + return getMeasureInfoFirst()->pointOnCurve; +} + +//! get the handler's result for the first element +Part::MeasureRadiusInfoPtr MeasureRadius::getMeasureInfoFirst() const +{ + const App::DocumentObject* object = Element.getValue(); + const std::vector& subElements = Element.getSubValues(); + + if (!object || subElements.empty()) { +// NOLINTNEXTLINE(modernize-return-braced-init-list) + return std::make_shared(); + } + + std::string subElement = subElements.front(); + const char* className = object->getSubObject(subElement.c_str())->getTypeId().getName(); + const std::string& mod = Base::Type::getModuleName(className); + + auto handler = getGeometryHandler(mod); + if (!handler) { + throw Base::RuntimeError("No geometry handler available for submitted element type"); + } + + std::string obName = object->getNameInDocument(); + App::SubObjectT subject{object, subElement.c_str()}; + auto info = handler(subject); + auto radiusInfo = std::dynamic_pointer_cast(info); + return radiusInfo; +} + +//! Return the object we are measuring +//! used by the viewprovider in determining visibility +std::vector MeasureRadius::getSubject() const +{ + return {Element.getValue()}; +} diff --git a/src/Mod/Measure/App/MeasureRadius.h b/src/Mod/Measure/App/MeasureRadius.h new file mode 100644 index 0000000000..9686e4a392 --- /dev/null +++ b/src/Mod/Measure/App/MeasureRadius.h @@ -0,0 +1,90 @@ +/*************************************************************************** + * Copyright (c) 2023 WandererFan * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + + +#ifndef MEASURE_MEASURERADIUS_H +#define MEASURE_MEASURERADIUS_H + +#include + +#include +#include +#include +#include +#include + +#include + +#include "MeasureBase.h" + + +namespace Measure +{ + + +class MeasureExport MeasureRadius : public Measure::MeasureBaseExtendable +{ + PROPERTY_HEADER_WITH_OVERRIDE(Measure::MeasureRadius); + +public: + /// Constructor + MeasureRadius(); + ~MeasureRadius() override; + + App::PropertyLinkSub Element; + App::PropertyDistance Radius; + + App::DocumentObjectExecReturn *execute() override; + const char* getViewProviderName() const override { + return "MeasureGui::ViewProviderMeasure"; + } + + void recalculateRadius(); + + static bool isValidSelection(const App::MeasureSelection& selection); + static bool isPrioritizedSelection(const App::MeasureSelection& selection); + void parseSelection(const App::MeasureSelection& selection) override; + + std::vector getInputProps() override {return {"Element"};} + App::Property* getResultProp() override {return &this->Radius;} + + // Return a placement for the viewprovider, just use the first element for now + Base::Placement getPlacement() override; + // Return a point on curve for the viewprovider + Base::Vector3d getPointOnCurve() const; + + // Return the object we are measuring + std::vector getSubject() const override; + + +private: + void onChanged(const App::Property* prop) override; + Part::MeasureRadiusInfoPtr getMeasureInfoFirst() const; + + +}; + +} //namespace Measure + + +#endif // MEASURE_MEASURERADIUS_H + + diff --git a/src/Mod/Measure/App/Measurement.cpp b/src/Mod/Measure/App/Measurement.cpp index 3c4c6e20f7..1d8fd954f4 100644 --- a/src/Mod/Measure/App/Measurement.cpp +++ b/src/Mod/Measure/App/Measurement.cpp @@ -202,7 +202,7 @@ TopoDS_Shape Measurement::getShape(App::DocumentObject *obj , const char *subNam try { Part::TopoShape partShape = Part::Feature::getTopoShape(obj); - App::GeoFeature* geoFeat = dynamic_cast(obj); + App::GeoFeature* geoFeat = dynamic_cast(obj); if (geoFeat) { partShape.setPlacement(geoFeat->globalPlacement()); } diff --git a/src/Mod/Measure/App/MeasurementPy.xml b/src/Mod/Measure/App/MeasurementPy.xml index 4fa16fef3b..ae4f37b0f9 100644 --- a/src/Mod/Measure/App/MeasurementPy.xml +++ b/src/Mod/Measure/App/MeasurementPy.xml @@ -1,4 +1,5 @@  + * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + +#include "PreCompiled.h" +#ifndef _PreComp_ +# include +#endif + +#include +#include +#include +#include + +#include "Preferences.h" + + +//getters for parameters used in multiple places. +//ensure this is in sync with parameter names and default values on preference pages + +using namespace Measure; + +//! Returns the Measure preference group +Base::Reference Preferences::getPreferenceGroup(const char* Name) +{ + return App::GetApplication().GetUserParameter().GetGroup("BaseApp/Preferences/Mod/Measure")->GetGroup(Name); +} + +App::Color Preferences::defaultLineColor() +{ + App::Color fcColor; + fcColor.setPackedValue(getPreferenceGroup("Appearance")->GetUnsigned("DefaultLineColor", 0xFFFFFFFF)); + return fcColor; +} + +App::Color Preferences::defaultTextColor() +{ + App::Color fcColor; + fcColor.setPackedValue(getPreferenceGroup("Appearance")->GetUnsigned("DefaultTextColor", 0x00000000)); + return fcColor; +} + +App::Color Preferences::defaultTextBackgroundColor() +{ + App::Color fcColor; + fcColor.setPackedValue(getPreferenceGroup("Appearance")->GetUnsigned("DefaultTextBackgroundColor", 0xFFFFFFFF)); + return fcColor; +} + +double Preferences::defaultDistFactor() +{ + return getPreferenceGroup("Appearance")->GetFloat("DefaultDistFactor", 1.0); +} + +int Preferences::defaultFontSize() +{ + return getPreferenceGroup("Appearance")->GetInt("DefaultFontSize", 18); +} + +bool Preferences::defaultMirror() +{ + return getPreferenceGroup("Appearance")->GetBool("DefaultMirror", false); +} diff --git a/src/Mod/Measure/App/Preferences.h b/src/Mod/Measure/App/Preferences.h new file mode 100644 index 0000000000..44f186c485 --- /dev/null +++ b/src/Mod/Measure/App/Preferences.h @@ -0,0 +1,58 @@ +/*************************************************************************** + * Copyright (c) 2023 WandererFan * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + +#ifndef Preferences_h_ +#define Preferences_h_ + +#include + +#include + +#include +#include + +namespace App +{ +class Color; +} + +namespace Measure +{ + +//getters for parameters used in multiple places. +class MeasureExport Preferences +{ + +public: + static Base::Reference getPreferenceGroup(const char* Name); + + static App::Color defaultLineColor(); + static App::Color defaultTextColor(); + static double defaultDistFactor(); + static int defaultFontSize(); + static bool defaultMirror(); + static App::Color defaultTextBackgroundColor(); +}; + + +}//end namespace Measure +#endif + diff --git a/src/Mod/Measure/CMakeLists.txt b/src/Mod/Measure/CMakeLists.txt index 3ab73a3860..3babe21cd1 100644 --- a/src/Mod/Measure/CMakeLists.txt +++ b/src/Mod/Measure/CMakeLists.txt @@ -1,12 +1,29 @@ add_subdirectory(App) -if(FREECAD_BUILD_GUI) - #add_subdirectory(Gui) -endif(FREECAD_BUILD_GUI) + +set(Measure_Scripts + Init.py + UtilsMeasure.py + MeasureCOM.py +) + +if(BUILD_GUI) + list(APPEND Measure_Scripts InitGui.py) + add_subdirectory(Gui) +endif(BUILD_GUI) + +add_custom_target(MeasureScripts ALL + SOURCES ${Measure_Scripts} +) + +fc_target_copy_resource(MeasureScripts + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_BINARY_DIR}/Mod/Measure + ${Measure_Scripts}) INSTALL( FILES - Init.py + ${Measure_Scripts} DESTINATION Mod/Measure ) diff --git a/src/Mod/Measure/Gui/AppMeasureGui.cpp b/src/Mod/Measure/Gui/AppMeasureGui.cpp new file mode 100644 index 0000000000..41bbb81712 --- /dev/null +++ b/src/Mod/Measure/Gui/AppMeasureGui.cpp @@ -0,0 +1,99 @@ +/*************************************************************************** + * Copyright (c) 2023 David Friedli * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + +#include "PreCompiled.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "DlgPrefsMeasureAppearanceImp.h" +#include "ViewProviderMeasureAngle.h" +#include "ViewProviderMeasureDistance.h" +#include "ViewProviderMeasureBase.h" + + +// use a different name to CreateCommand() +void CreateMeasureCommands(); + + +namespace MeasureGui +{ +class Module: public Py::ExtensionModule +{ +public: + Module() + : Py::ExtensionModule("MeasureGui") + { + initialize("This module is the MeasureGui module.");// register with Python + } + + ~Module() override = default; + +private: +}; + +PyObject* initModule() +{ + return Base::Interpreter().addModule(new Module); +} + +}// namespace MeasureGui + +/* Python entry */ +PyMOD_INIT_FUNC(MeasureGui) +{ + if (!Gui::Application::Instance) { + PyErr_SetString(PyExc_ImportError, "Cannot load Gui module in console application."); + PyMOD_Return(nullptr); + } + + // load dependent module + try { + Base::Interpreter().loadModule("Measure"); + } + catch(const Base::Exception& e) { + PyErr_SetString(PyExc_ImportError, e.what()); + PyMOD_Return(nullptr); + } + + PyObject* mod = MeasureGui::initModule(); + Base::Console().Log("Loading GUI of Measure module... done\n"); + + // instantiating the commands + CreateMeasureCommands(); + + MeasureGui::ViewProviderMeasureBase ::init(); + MeasureGui::ViewProviderMeasure ::init(); + MeasureGui::ViewProviderMeasureAngle ::init(); + MeasureGui::ViewProviderMeasureDistance ::init(); + + // register preferences pages + new Gui::PrefPageProducer(QT_TRANSLATE_NOOP("QObject", "Measure")); + +// Q_INIT_RESOURCE(Measure); + + PyMOD_Return(mod); +} diff --git a/src/Mod/Measure/Gui/CMakeLists.txt b/src/Mod/Measure/Gui/CMakeLists.txt new file mode 100644 index 0000000000..343906e412 --- /dev/null +++ b/src/Mod/Measure/Gui/CMakeLists.txt @@ -0,0 +1,90 @@ +include_directories( + ${CMAKE_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_BINARY_DIR}/src + ${CMAKE_CURRENT_BINARY_DIR} + ${Boost_INCLUDE_DIRS} + ${COIN3D_INCLUDE_DIRS} + ${OCC_INCLUDE_DIR} + ${PYTHON_INCLUDE_DIRS} + ${XercesC_INCLUDE_DIRS} + ${ZLIB_INCLUDE_DIR} +) +link_directories(${OCC_LIBRARY_DIR}) + +link_directories(${OCC_LIBRARY_DIR}) + +set(MeasureGui_LIBS + Measure + FreeCADGui +) + +if(MSVC) + include_directories( + ${CMAKE_SOURCE_DIR}/src/3rdParty/OpenGL/api + ) +endif(MSVC) + + +set (Measure_TR_QRC ${CMAKE_CURRENT_BINARY_DIR}/Resources/Measure_translation.qrc) +qt_find_and_add_translation(QM_SRCS "Resources/translations/*_*.ts" + ${CMAKE_CURRENT_BINARY_DIR}/Resources/translations) +qt_create_resource_file(${Measure_TR_QRC} ${QM_SRCS}) +qt_add_resources(MeasureGui_SRCS Resources/Measure.qrc ${Measure_TR_QRC}) + +SET(MeasureGui_UIC_SRCS + DlgPrefsMeasureAppearanceImp.ui +) + +SET(MeasureGui_SRCS + ${CMAKE_SOURCE_DIR}/src/Mod/Measure/InitGui.py + ${MeasureGui_SRCS} + AppMeasureGui.cpp + Command.cpp + Resources/Measure.qrc + PreCompiled.cpp + PreCompiled.h + ViewProviderMeasureBase.cpp + ViewProviderMeasureBase.h + ViewProviderMeasureAngle.cpp + ViewProviderMeasureAngle.h + ViewProviderMeasureDistance.cpp + ViewProviderMeasureDistance.h + # Workbench.cpp + # Workbench.h + DlgPrefsMeasureAppearanceImp.ui + DlgPrefsMeasureAppearanceImp.cpp + DlgPrefsMeasureAppearanceImp.h + +) + +SET(MeasureGuiTaskDlgs_SRCS + DlgPrefsMeasureAppearanceImp.ui +) + + +#set(MeasureGui_Scripts +# ../InitGui.py +#) + +if(FREECAD_USE_PCH) + add_definitions(-D_PreComp_) + GET_MSVC_PRECOMPILED_SOURCE("PreCompiled.cpp" PCH_SRCS ${MeasureGui_SRCS}) + ADD_MSVC_PRECOMPILED_HEADER(MeasureGui PreCompiled.h PreCompiled.cpp PCH_SRCS) +endif(FREECAD_USE_PCH) + +SET(MeasureGuiIcon_SVG + Resources/icons/preferences-measure.svg +) + +add_library(MeasureGui SHARED ${MeasureGui_SRCS} ${MeasureGuiIcon_SVG}) +target_link_libraries(MeasureGui ${MeasureGui_LIBS}) + +SET_BIN_DIR(MeasureGui MeasureGui /Mod/Measure) +SET_PYTHON_PREFIX_SUFFIX(MeasureGui) + +fc_copy_sources(MeasureGui "${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_DATADIR}/Mod/Measure/" ${MeasureGuiIcon_SVG}) + +INSTALL(TARGETS MeasureGui DESTINATION ${CMAKE_INSTALL_LIBDIR}) +INSTALL(FILES ${MeasureGuiIcon_SVG} DESTINATION "${CMAKE_INSTALL_DATADIR}/Mod/Measure/Resources/icons") + diff --git a/src/Mod/Measure/Gui/Command.cpp b/src/Mod/Measure/Gui/Command.cpp new file mode 100644 index 0000000000..f7cbe5442f --- /dev/null +++ b/src/Mod/Measure/Gui/Command.cpp @@ -0,0 +1,33 @@ +/*************************************************************************** + * Copyright (c) 2023 David Friedli * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + +#include "PreCompiled.h" + +#include +#include + + +using namespace std; + +void CreateMeasureCommands() { + Base::Console().Message("Init MeasureGui\n"); + Gui::CommandManager& rcCmdMgr = Gui::Application::Instance->commandManager(); +} \ No newline at end of file diff --git a/src/Mod/Measure/Gui/DlgPrefsMeasureAppearanceImp.cpp b/src/Mod/Measure/Gui/DlgPrefsMeasureAppearanceImp.cpp new file mode 100644 index 0000000000..fe6aa3ede1 --- /dev/null +++ b/src/Mod/Measure/Gui/DlgPrefsMeasureAppearanceImp.cpp @@ -0,0 +1,75 @@ + /************************************************************************** + * Copyright (c) 2023 Wanderer Fan * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + +#include "PreCompiled.h" + +#include "DlgPrefsMeasureAppearanceImp.h" +#include "ui_DlgPrefsMeasureAppearanceImp.h" + +using namespace MeasureGui; + +DlgPrefsMeasureAppearanceImp::DlgPrefsMeasureAppearanceImp( QWidget* parent ) + : PreferencePage( parent ) + , ui(new Ui_DlgPrefsMeasureAppearanceImp) +{ + ui->setupUi(this); +} + +DlgPrefsMeasureAppearanceImp::~DlgPrefsMeasureAppearanceImp() +{ + // no need to delete child widgets, Qt does it all for us +} + +void DlgPrefsMeasureAppearanceImp::saveSettings() +{ + ui->cbMirror->onSave(); + ui->dsbDistFactor->onSave(); + ui->sbFontSize->onSave(); + ui->cbText->onSave(); + ui->cbLine->onSave(); + ui->cbBackground->onSave(); +} + +void DlgPrefsMeasureAppearanceImp::loadSettings() +{ + ui->sbFontSize->onRestore(); + ui->cbText->onRestore(); + ui->cbBackground->onRestore(); + ui->cbLine->onRestore(); + ui->cbMirror->onRestore(); + ui->dsbDistFactor->onRestore(); +} + +/** + * Sets the strings of the subwidgets using the current language. + */ +void DlgPrefsMeasureAppearanceImp::changeEvent(QEvent *e) +{ + if (e->type() == QEvent::LanguageChange) { + ui->retranslateUi(this); + } + else { + QWidget::changeEvent(e); + } +} + +#include + diff --git a/src/Mod/Measure/Gui/DlgPrefsMeasureAppearanceImp.h b/src/Mod/Measure/Gui/DlgPrefsMeasureAppearanceImp.h new file mode 100644 index 0000000000..b4d1634d23 --- /dev/null +++ b/src/Mod/Measure/Gui/DlgPrefsMeasureAppearanceImp.h @@ -0,0 +1,55 @@ + /************************************************************************** + * Copyright (c) 2023 Wanderer Fan * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + +#ifndef MeasureGui_DlgPrefsAppearanceImp_H +#define MeasureGui_DlgPrefsAppearanceImp_H + +#include + +#include +#include + + +namespace MeasureGui { + +class Ui_DlgPrefsMeasureAppearanceImp; + +class DlgPrefsMeasureAppearanceImp : public Gui::Dialog::PreferencePage +{ + Q_OBJECT + +public: + explicit DlgPrefsMeasureAppearanceImp( QWidget* parent = nullptr ); + ~DlgPrefsMeasureAppearanceImp() override; + +protected: + void saveSettings() override; + void loadSettings() override; + void changeEvent(QEvent *e) override; + +private: + std::unique_ptr ui; +}; + +} // namespace MeasureGui + +#endif // MeasureGui_DlgPrefsAppearanceImp_H + diff --git a/src/Mod/Measure/Gui/DlgPrefsMeasureAppearanceImp.ui b/src/Mod/Measure/Gui/DlgPrefsMeasureAppearanceImp.ui new file mode 100644 index 0000000000..acaf50ff97 --- /dev/null +++ b/src/Mod/Measure/Gui/DlgPrefsMeasureAppearanceImp.ui @@ -0,0 +1,247 @@ + + + MeasureGui::DlgPrefsMeasureAppearanceImp + + + + 0 + 0 + 623 + 287 + + + + + 0 + 0 + + + + Appearance + + + + :/icons/preferences-measure.svg:/icons/preferences-measure.svg + + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777215 + 200 + + + + Default Property Values + + + + + + + + Distance Factor + + + + + + + Text color + + + + + + + + 0 + 0 + 0 + + + + DefaultTextColor + + + Mod/Measure/Appearance + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 1 + + + 18 + + + DefaultFontSize + + + Mod/Measure/Appearance + + + + + + + + 0 + 0 + + + + + 255 + 255 + 255 + + + + DefaultLineColor + + + Mod/Measure/Appearance + + + + + + + Text size + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 1.000000000000000 + + + DefaultDistFactor + + + Mod/Measure/Appearance + + + + + + + Line color + + + + + + + Mirror + + + DefaultMirror + + + Mod/Measure/Appearance + + + + + + + Background color + + + + + + + + 0 + 0 + 0 + + + + DefaultTextBackgroundColor + + + Mod/Measure/Appearance + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Gui::ColorButton + QPushButton +
Gui/Widgets.h
+
+ + Gui::PrefSpinBox + QSpinBox +
Gui/PrefWidgets.h
+
+ + Gui::PrefColorButton + Gui::ColorButton +
Gui/PrefWidgets.h
+
+ + Gui::PrefCheckBox + QCheckBox +
Gui/PrefWidgets.h
+
+ + Gui::PrefDoubleSpinBox + QDoubleSpinBox +
Gui/PrefWidgets.h
+
+
+ + + + +
diff --git a/src/Mod/Measure/Gui/PreCompiled.cpp b/src/Mod/Measure/Gui/PreCompiled.cpp new file mode 100644 index 0000000000..78c1dbe181 --- /dev/null +++ b/src/Mod/Measure/Gui/PreCompiled.cpp @@ -0,0 +1,22 @@ +/*************************************************************************** + * Copyright (c) 2023 David Friedli * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + +#include "PreCompiled.h" diff --git a/src/Mod/Measure/Gui/PreCompiled.h b/src/Mod/Measure/Gui/PreCompiled.h new file mode 100644 index 0000000000..f42331cb55 --- /dev/null +++ b/src/Mod/Measure/Gui/PreCompiled.h @@ -0,0 +1,97 @@ +/*************************************************************************** + * Copyright (c) 2023 David Friedli * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + +#ifndef MEASUREGUI_PRECOMPILED_H +#define MEASUREGUI_PRECOMPILED_H + +#include + +#include + +// point at which warnings of overly long specifiers disabled (needed for VC6) +#ifdef _MSC_VER +# pragma warning( disable : 4251 ) +# pragma warning( disable : 4503 ) +# pragma warning( disable : 4786 ) // specifier longer then 255 chars +# pragma warning( disable : 4273 ) +#endif + +#ifdef FC_OS_WIN32 +# ifndef NOMINMAX +# define NOMINMAX +# endif +# include +#endif + +#ifdef _PreComp_ + +// standard +#include +#include + +// STL +#include +#include +#include +#include +#include + +// OpenCasCade +#include + +// Boost +#include +#include + +// GL +// Include glext before QtAll/InventorAll +#ifdef FC_OS_WIN32 +# include +# include +#else +# ifdef FC_OS_MACOSX +# include +# include +# else +# ifndef GL_GLEXT_PROTOTYPES +# define GL_GLEXT_PROTOTYPES 1 +# endif +# include +# include +# endif //FC_OS_MACOSX +#endif //FC_OS_WIN32 +// Should come after glext.h to avoid warnings +#include + +// Qt Toolkit +#ifndef __QtAll__ +# include +#endif + +// Inventor includes OpenGL +#ifndef __InventorAll__ +# include +#endif + +#endif //_PreComp_ + +#endif + diff --git a/src/Mod/Measure/Gui/Resources/Measure.qrc b/src/Mod/Measure/Gui/Resources/Measure.qrc new file mode 100644 index 0000000000..43a28bbdab --- /dev/null +++ b/src/Mod/Measure/Gui/Resources/Measure.qrc @@ -0,0 +1,6 @@ + + + icons/umf-measurement.svg + icons/preferences-measure.svg + + diff --git a/src/Mod/Measure/Gui/Resources/icons/preferences-measure.svg b/src/Mod/Measure/Gui/Resources/icons/preferences-measure.svg new file mode 100644 index 0000000000..e9971aed73 --- /dev/null +++ b/src/Mod/Measure/Gui/Resources/icons/preferences-measure.svg @@ -0,0 +1,317 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/Measure/Gui/Resources/icons/umf-measurement.svg b/src/Mod/Measure/Gui/Resources/icons/umf-measurement.svg new file mode 100644 index 0000000000..e9971aed73 --- /dev/null +++ b/src/Mod/Measure/Gui/Resources/icons/umf-measurement.svg @@ -0,0 +1,317 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/Measure/Gui/Resources/translations/Measure.ts b/src/Mod/Measure/Gui/Resources/translations/Measure.ts new file mode 100644 index 0000000000..ab1f5953c1 --- /dev/null +++ b/src/Mod/Measure/Gui/Resources/translations/Measure.ts @@ -0,0 +1,42 @@ + + + + + MeasureGui::DlgPrefsMeasureAppearanceImp + + + Appearance + + + + + Default Property Values + + + + + Distance Factor + + + + + Text color + + + + + Text size + + + + + Line color + + + + + Mirror + + + + diff --git a/src/Mod/Measure/Gui/ViewProviderMeasureAngle.cpp b/src/Mod/Measure/Gui/ViewProviderMeasureAngle.cpp new file mode 100644 index 0000000000..392ef01ce4 --- /dev/null +++ b/src/Mod/Measure/Gui/ViewProviderMeasureAngle.cpp @@ -0,0 +1,339 @@ +/*************************************************************************** + * Copyright (c) 2023 David Friedli * + * Copyright (c) 2013 Thomas Anderson * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + +#include "PreCompiled.h" + +#ifndef _PreComp_ +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +#endif + +#include +#include +#include +#include +#include +#include +# include +# include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ViewProviderMeasureAngle.h" + + +using namespace MeasureGui; +using namespace Measure; + +gp_Lin getLine(gp_Vec& vec, gp_Vec& origin) { + gp_Pnt tempOrigin; + tempOrigin.SetXYZ(origin.XYZ()); + return gp_Lin(tempOrigin, gp_Dir(vec)); +} + +SbMatrix ViewProviderMeasureAngle::getMatrix() { + // Code ported from src/Mod/Part/Gui/TaskDimension.cpp + + if (pcObject == nullptr) { + throw Base::RuntimeError("no DocumentObject"); + } + + Measure::MeasureAngle* measurement = static_cast(pcObject); + + if (!measurement->Element1.getValue() || measurement->Element1.getSubValues().empty()){ + return SbMatrix(); + } + if (!measurement->Element2.getValue() || measurement->Element2.getSubValues().empty()){ + return SbMatrix(); + } + + + gp_Vec vector1 = measurement->vector1(); + gp_Vec vector2 = measurement->vector2(); + + gp_Vec loc1 = measurement->location1(); + gp_Vec loc2 = measurement->location2(); + + if (vector1.Magnitude() == 0 || vector2.Magnitude() == 0) { + return SbMatrix(); + } + + gp_Lin lin1 = getLine(vector1, loc1); + gp_Lin lin2 = getLine(vector2, loc2); + + SbMatrix dimSys = SbMatrix(); + double radius; + + if (vector1.IsParallel(vector2, Precision::Angular())) { + //take first point project it onto second vector. + Handle(Geom_Curve) heapLine2 = new Geom_Line(lin2); + gp_Pnt tempPoint(loc1.XYZ()); + + GeomAPI_ProjectPointOnCurve projection(tempPoint, heapLine2); + if (projection.NbPoints() < 1) + { + throw Base::RuntimeError("parallel vectors: couldn't project onto line"); + } + gp_Vec newPoint2; + newPoint2.SetXYZ(projection.Point(1).XYZ()); + + //if points are colinear, projection doesn't work and returns the same point. + //In this case we just use the original point. + if ((newPoint2 - loc1).Magnitude() < Precision::Confusion()) + newPoint2 = loc2; + + //now get midpoint between for dim origin. + gp_Vec point1 = loc1; + gp_Vec midPointProjection = newPoint2 - point1; + double distance = midPointProjection.Magnitude(); + midPointProjection.Normalize(); + midPointProjection *= distance / 2.0; + + gp_Vec origin = point1 + midPointProjection; + + //yaxis should be the same as vector1, but doing this to eliminate any potential slop from + //using precision::angular. If lines are colinear and we have no plane, we can't establish zAxis from crossing. + //we just the absolute axis. + gp_Vec xAxis = (point1 - origin).Normalized(); + gp_Vec zAxis; + if (xAxis.IsParallel(vector1, Precision::Angular())) { + if (!xAxis.IsParallel(gp_Vec(0.0, 0.0, 1.0), Precision::Angular())) + zAxis = gp_Vec(0.0, 0.0, 1.0); + else + zAxis = gp_Vec(0.0, 1.0, 0.0); + } + else + zAxis = xAxis.Crossed(vector1).Normalized(); + gp_Vec yAxis = zAxis.Crossed(xAxis).Normalized(); + zAxis = xAxis.Crossed(yAxis).Normalized(); + + dimSys = SbMatrix + ( + xAxis.X(), yAxis.X(), zAxis.X(), origin.X(), + xAxis.Y(), yAxis.Y(), zAxis.Y(), origin.Y(), + xAxis.Z(), yAxis.Z(), zAxis.Z(), origin.Z(), + 0.0, 0.0, 0.0, 1.0 + ); + dimSys = dimSys.transpose(); + + radius = midPointProjection.Magnitude(); + } else { + Handle(Geom_Curve) heapLine1 = new Geom_Line(lin1); + Handle(Geom_Curve) heapLine2 = new Geom_Line(lin2); + + GeomAPI_ExtremaCurveCurve extrema(heapLine1, heapLine2); + + if (extrema.NbExtrema() < 1) + { + throw Base::RuntimeError("couldn't get extrema"); + } + + gp_Pnt extremaPoint1, extremaPoint2, dimensionOriginPoint; + extrema.Points(1, extremaPoint1, extremaPoint2); + if (extremaPoint1.Distance(extremaPoint2) < Precision::Confusion()) + dimensionOriginPoint = extremaPoint1; + else + { + //find halfway point in between extrema points for dimension origin. + gp_Vec vec1(extremaPoint1.XYZ()); + gp_Vec vec2(extremaPoint2.XYZ()); + gp_Vec connection(vec2-vec1); + Standard_Real distance = connection.Magnitude(); + connection.Normalize(); + connection *= (distance / 2.0); + dimensionOriginPoint.SetXYZ((vec1 + connection).XYZ()); + } + + gp_Vec thirdPoint(loc2); + gp_Vec originVector(dimensionOriginPoint.XYZ()); + gp_Vec extrema2Vector(extremaPoint2.XYZ()); + radius = (loc1 - originVector).Magnitude(); + double legOne = (extrema2Vector - originVector).Magnitude(); + if (legOne > Precision::Confusion()) + { + double legTwo = sqrt(pow(radius, 2) - pow(legOne, 2)); + gp_Vec projectionVector(vector2); + projectionVector.Normalize(); + projectionVector *= legTwo; + thirdPoint = extrema2Vector + projectionVector; + gp_Vec hyp(thirdPoint - originVector); + hyp.Normalize(); + gp_Vec otherSide(loc1 - originVector); + otherSide.Normalize(); + } + + gp_Vec xAxis = (loc1 - originVector).Normalized(); + gp_Vec fakeYAxis = (thirdPoint - originVector).Normalized(); + gp_Vec zAxis = (xAxis.Crossed(fakeYAxis)).Normalized(); + gp_Vec yAxis = zAxis.Crossed(xAxis).Normalized(); + + dimSys = SbMatrix + ( + xAxis.X(), yAxis.X(), zAxis.X(), dimensionOriginPoint.X(), + xAxis.Y(), yAxis.Y(), zAxis.Y(), dimensionOriginPoint.Y(), + xAxis.Z(), yAxis.Z(), zAxis.Z(), dimensionOriginPoint.Z(), + 0.0, 0.0, 0.0, 1.0 + ); + + dimSys = dimSys.transpose(); + } + + return dimSys; +} + + + +PROPERTY_SOURCE(MeasureGui::ViewProviderMeasureAngle, MeasureGui::ViewProviderMeasureBase) + + +ViewProviderMeasureAngle::ViewProviderMeasureAngle() +{ + sPixmap = "umf-measurement"; + + // Primary Arc + Gui::ArcEngine *arcEngine = new Gui::ArcEngine(); + arcEngine->angle.connectFrom(&fieldAngle); + + auto calculatorRadius = new SoCalculator(); + calculatorRadius->A.connectFrom(&pDragger->translation); + calculatorRadius->expression.setValue("oa=length(A)"); + arcEngine->radius.connectFrom(&calculatorRadius->oa); + arcEngine->deviation.setValue(0.1f); + + SoCoordinate3 *coordinates = new SoCoordinate3(); + coordinates->point.connectFrom(&arcEngine->points); + + SoLineSet *lineSet = new SoLineSet(); + lineSet->vertexProperty.setValue(coordinates); + lineSet->numVertices.connectFrom(&arcEngine->pointCount); + lineSet->startIndex.setValue(0); + + pLineSeparator->addChild(lineSet); + + // Secondary Arc, from midpoint to label + auto engineAngle = new SoCalculator(); + engineAngle->A.connectFrom(&arcEngine->midpoint); + engineAngle->B.connectFrom(&pLabelTranslation->translation); + engineAngle->expression.setValue("tA=normalize(A); tB=normalize(B); oa=atan2(tB[1], tB[0])-atan2(tA[1], tA[0])"); + + Gui::ArcEngine *arcEngineSecondary = new Gui::ArcEngine(); + arcEngineSecondary->radius.connectFrom(&calculatorRadius->oa); + arcEngineSecondary->deviation.setValue(0.1f); + arcEngineSecondary->angle.connectFrom(&engineAngle->oa); + + // Rotate arc + auto engineRotMidpoint = new SoComposeRotationFromTo(); // absolute angle to midpoint + engineRotMidpoint->from.setValue(SbVec3f(1.0, 0.0, 0.0)); + engineRotMidpoint->to.connectFrom(&arcEngine->midpoint); + + auto matrixEngine = new SoComposeMatrix(); + matrixEngine->rotation.connectFrom(&engineRotMidpoint->rotation); + auto transformEngine = new SoTransformVec3f(); + transformEngine->matrix.connectFrom(&matrixEngine->matrix); + transformEngine->vector.connectFrom(&arcEngineSecondary->points); + + SoCoordinate3 *coordinatesSecondary = new SoCoordinate3(); + coordinatesSecondary->point.connectFrom(&transformEngine->point); + + SoLineSet *lineSetSecondary = new SoLineSet(); + lineSetSecondary->vertexProperty.setValue(coordinatesSecondary); + lineSetSecondary->numVertices.connectFrom(&arcEngineSecondary->pointCount); + lineSetSecondary->startIndex.setValue(0); + + pLineSeparatorSecondary->addChild(lineSetSecondary); +} + + +void ViewProviderMeasureAngle::redrawAnnotation() +{ + auto obj = dynamic_cast(getMeasureObject()); + double angleDeg = obj->Angle.getValue(); + constexpr double radiansPerDegree = M_PI / 180.0; + this->fieldAngle = angleDeg * radiansPerDegree; + + // Set matrix + try { + SbMatrix matrix = getMatrix(); + pcTransform->setMatrix(matrix); + + } catch (const Base::Exception& e) { + Base::Console().Error("Error in ViewProviderMeasureAngle::redrawAnnotation: %s\n", e.what()); + return; + } + + // Set Label + setLabelValue(static_cast(pcObject)->getResultString()); +} + + +//! return the feature as a MeasureAngle +Measure::MeasureAngle* ViewProviderMeasureAngle::getMeasureAngle() +{ + Measure::MeasureAngle* feature = dynamic_cast(pcObject); + if (!feature) { + throw Base::RuntimeError("Feature not found for ViewProviderMeasureAngle"); + } + return feature; +} + + +void ViewProviderMeasureAngle::positionAnno(const Measure::MeasureBase* measureObject) { + (void)measureObject; + setLabelTranslation(SbVec3f(0, 10, 0)); +} + diff --git a/src/Mod/Measure/Gui/ViewProviderMeasureAngle.h b/src/Mod/Measure/Gui/ViewProviderMeasureAngle.h new file mode 100644 index 0000000000..8182b68c38 --- /dev/null +++ b/src/Mod/Measure/Gui/ViewProviderMeasureAngle.h @@ -0,0 +1,73 @@ +/*************************************************************************** + * Copyright (c) 2023 David Friedli * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + + +#ifndef GUI_VIEWPROVIDERMEASUREANGLE_H +#define GUI_VIEWPROVIDERMEASUREANGLE_H + +#include + +#include + +#include +#include +#include + +#include + +#include "ViewProviderMeasureBase.h" + +//NOLINTBEGIN +class SoText2; +class SoTranslation; +class SoCoordinate3; +class SoIndexedLineSet; +class SoTransform; +//NOLINTEND + +namespace MeasureGui +{ + +class MeasureGuiExport ViewProviderMeasureAngle : public MeasureGui::ViewProviderMeasureBase +{ + PROPERTY_HEADER_WITH_OVERRIDE(MeasureGui::ViewProviderMeasureAngle); + +public: + /// Constructor + ViewProviderMeasureAngle(); + + Measure::MeasureAngle* getMeasureAngle(); + void redrawAnnotation() override; + void positionAnno(const Measure::MeasureBase* measureObject) override; + +private: + // Fields + SoSFFloat fieldAngle; //radians. + + SbMatrix getMatrix(); +}; + + +} //namespace MeasureGui + + + +#endif // GUI_VIEWPROVIDERMEASUREANGLE_H diff --git a/src/Mod/Measure/Gui/ViewProviderMeasureBase.cpp b/src/Mod/Measure/Gui/ViewProviderMeasureBase.cpp new file mode 100644 index 0000000000..a0bf468f27 --- /dev/null +++ b/src/Mod/Measure/Gui/ViewProviderMeasureBase.cpp @@ -0,0 +1,597 @@ +/*************************************************************************** + * Copyright (c) 2023 David Friedli * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + +#include "Gui/Application.h" +#include "Gui/MDIView.h" +#include "PreCompiled.h" + +#ifndef _PreComp_ +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include "ViewProviderMeasureBase.h" + + +using namespace MeasureGui; +using namespace Measure; + +//NOLINTBEGIN +PROPERTY_SOURCE(MeasureGui::ViewProviderMeasureBase, Gui::ViewProviderDocumentObject) +//NOLINTEND + +ViewProviderMeasureBase::ViewProviderMeasureBase() +{ + static const char *agroup = "Appearance"; +//NOLINTBEGIN + ADD_PROPERTY_TYPE(TextColor, (Preferences::defaultTextColor()), agroup, App::Prop_None, "Color for the measurement text"); + ADD_PROPERTY_TYPE(TextBackgroundColor, (Preferences::defaultTextBackgroundColor()), agroup, App::Prop_None, "Color for the measurement text background"); + ADD_PROPERTY_TYPE(LineColor, (Preferences::defaultLineColor()), agroup, App::Prop_None, "Color for the measurement lines"); + ADD_PROPERTY_TYPE(FontSize, (Preferences::defaultFontSize()), agroup, App::Prop_None, "Size of measurement text"); +//NOLINTEND + + // setupAnnoSceneGraph() - sets up the annotation scene graph + pLabel = new Gui::SoFrameLabel(); + pLabel->ref(); + pColor = new SoBaseColor(); + pColor->ref(); + pLabelTranslation = new SoTransform(); + pLabelTranslation->ref(); + + auto ps = getSoPickStyle(); + + // Dragger + SoSeparator* dragSeparator = new SoSeparator(); + pDragger = new SoTranslate2Dragger(); + pDragger->ref(); + pDraggerOrientation = new SoTransform(); + pDraggerOrientation->ref(); + dragSeparator->addChild(pDraggerOrientation); + dragSeparator->addChild(pDragger); + + // Transform drag location by dragger local orientation and connect to labelTranslation + auto matrixEngine = new SoComposeMatrix(); + matrixEngine->rotation.connectFrom(&pDraggerOrientation->rotation); + auto transformEngine = new SoTransformVec3f(); + transformEngine->vector.connectFrom(&pDragger->translation); + transformEngine->matrix.connectFrom(&matrixEngine->matrix); + pLabelTranslation->translation.connectFrom(&transformEngine->point); + + pTextSeparator = new SoSeparator(); + pTextSeparator->ref(); + pTextSeparator->addChild(dragSeparator); + pTextSeparator->addChild(pLabelTranslation); + pTextSeparator->addChild(pLabel); + + // Empty line separator which can be populated by inherited class + pLineSeparator = new SoSeparator(); + pLineSeparator->ref(); + pLineSeparator->addChild(ps); + pLineSeparator->addChild(getSoLineStylePrimary()); + pLineSeparator->addChild(pColor); + + // Secondary line separator + pLineSeparatorSecondary = new SoSeparator(); + pLineSeparatorSecondary->ref(); + pLineSeparatorSecondary->addChild(ps); + pLineSeparatorSecondary->addChild(getSoLineStyleSecondary()); + pLineSeparatorSecondary->addChild(pColor); + + pRootSeparator = new SoAnnotation(); + pRootSeparator->ref(); + pRootSeparator->addChild(pLineSeparator); + pRootSeparator->addChild(pLineSeparatorSecondary); + pRootSeparator->addChild(pTextSeparator); + addDisplayMaskMode(pRootSeparator, "Base"); + + pRootSeparator->touch(); + pTextSeparator->touch(); + pLineSeparator->touch(); + + // Register dragger callback + auto dragger = pDragger; + + dragger->addValueChangedCallback(draggerChangedCallback, this); + + + // Use the label node as the transform handle + SoSearchAction sa; + sa.setInterest(SoSearchAction::FIRST); + sa.setSearchingAll(true); + sa.setNode(pLabel); + sa.apply(pcRoot); + SoPath * labelPath = sa.getPath(); + assert(labelPath); + dragger->setPartAsPath("translator", labelPath); + + // Hide the dragger feedback during translation + dragger->setPart("translatorActive", NULL); + dragger->setPart("xAxisFeedback", NULL); + dragger->setPart("yAxisFeedback", NULL); + // end setupSceneGraph + + // these touches cause onChanged to run which then updates pLabel and pColor with the initial values + TextColor.touch(); + TextBackgroundColor.touch(); + FontSize.touch(); + LineColor.touch(); + +} + +ViewProviderMeasureBase::~ViewProviderMeasureBase() +{ + _mVisibilityChangedConnection.disconnect(); + pLabel->unref(); + pColor->unref(); + pDragger->unref(); + pDraggerOrientation->unref(); + pLabelTranslation->unref(); + pTextSeparator->unref(); + pLineSeparator->unref(); + pRootSeparator->unref(); +} + +std::vector ViewProviderMeasureBase::getDisplayModes() const +{ + // add modes + std::vector StrList; + StrList.emplace_back("Base"); + return StrList; +} + +void ViewProviderMeasureBase::setDisplayMode(const char* ModeName) +{ + if (strcmp(ModeName, "Base") == 0) { + setDisplayMaskMode("Base"); + } + ViewProviderDocumentObject::setDisplayMode(ModeName); +} + + +void ViewProviderMeasureBase::finishRestoring() { + // Force measurement visibility when loading a document + show(); +} + + +void ViewProviderMeasureBase::onChanged(const App::Property* prop) +{ + if (prop == &TextColor) { + const App::Color& color = TextColor.getValue(); + pLabel->textColor.setValue(color.r, color.g, color.b); + } + else if (prop == &TextBackgroundColor) { + const App::Color& color = TextBackgroundColor.getValue(); + pLabel->backgroundColor.setValue(color.r, color.g, color.b); + } + else if (prop == &LineColor) { + const App::Color& color = LineColor.getValue(); + pColor->rgb.setValue(color.r, color.g, color.b); + } + else if (prop == &FontSize) { + pLabel->size = FontSize.getValue(); + } + ViewProviderDocumentObject::onChanged(prop); +} + +void ViewProviderMeasureBase::draggerChangedCallback(void *data, SoDragger *) { + auto me = static_cast(data); + me->onLabelMoved(); +} + +void ViewProviderMeasureBase::setLabelValue(const Base::Quantity& value) { + pLabel->string.setValue(value.getUserString().toUtf8().constData()); +} + +void ViewProviderMeasureBase::setLabelValue(const QString& value) { + auto lines = value.split(QString::fromLatin1("\n")); + + int i = 0; + for (auto& it : lines) { + pLabel->string.set1Value(i, it.toUtf8().constData()); + i++; + } +} + +void ViewProviderMeasureBase::setLabelTranslation(const SbVec3f& position) { + // Set the dragger translation to keep it in sync with pLabelTranslation + pDragger->translation.setValue(position); +} + + +SoPickStyle* ViewProviderMeasureBase::getSoPickStyle() { + auto ps = new SoPickStyle(); + ps->style = SoPickStyle::UNPICKABLE; + return ps; +} + +SoDrawStyle* ViewProviderMeasureBase::getSoLineStylePrimary() { + auto style = new SoDrawStyle(); + style->lineWidth = 2.0f; + return style; +} + +SoDrawStyle* ViewProviderMeasureBase::getSoLineStyleSecondary() { + auto style = new SoDrawStyle(); + style->lineWidth = 1.0f; + return style; +} + +SoSeparator* ViewProviderMeasureBase::getSoSeparatorText() { + return pTextSeparator; +} + + +void ViewProviderMeasureBase::positionAnno(const Measure::MeasureBase* measureObject) { + (void)measureObject; +} + + +void ViewProviderMeasureBase::attach(App::DocumentObject *pcObj) +{ + ViewProviderDocumentObject::attach(pcObj); + positionAnno(static_cast(pcObj)); +} + + +//! handle changes to the feature's properties +void ViewProviderMeasureBase::updateData(const App::Property* prop) +{ + bool doUpdate = false; + + auto obj = getMeasureObject(); + if (!obj) { + return; + } + + // Check if one of the input properties has been changed + auto inputProps = obj->getInputProps(); + if (std::find(inputProps.begin(), inputProps.end(), std::string(prop->getName())) != inputProps.end()) { + doUpdate = true; + + // Add connections to be notified when the measured objects are changed + connectToSubject(obj->getSubject()); + } + + // Check if the result prop has been changed + auto resultProp = obj->getResultProp(); + if (resultProp && prop == resultProp){ + doUpdate = true; + } + + if (doUpdate) { + redrawAnnotation(); + } + + ViewProviderDocumentObject::updateData(prop); +} + + +// TODO: should this be pure virtual? +void ViewProviderMeasureBase::redrawAnnotation() +{ + // Base::Console().Message("VPMB::redrawAnnotation()\n"); +} + +//! connect to the subject to receive visibility updates +void ViewProviderMeasureBase::connectToSubject(App::DocumentObject* subject) +{ + if (!subject) { + return; + } + + // disconnect any existing connection + if (_mVisibilityChangedConnection.connected()) { + _mVisibilityChangedConnection.disconnect(); + } + + //NOLINTBEGIN + auto bndVisibility = std::bind(&ViewProviderMeasureBase::onSubjectVisibilityChanged, this, std::placeholders::_1, std::placeholders::_2); + //NOLINTEND + _mVisibilityChangedConnection = subject->signalChanged.connect(bndVisibility); +} + +//! connect to the subject to receive visibility updates +void ViewProviderMeasureBase::connectToSubject(std::vector subject) +{ + if (subject.empty()) { + return; + } + + // TODO: should we connect to all the subject objects when there is >1? + auto proxy = subject.front(); + connectToSubject(proxy); +} + + +//! retrieve the feature +Measure::MeasureBase* ViewProviderMeasureBase::getMeasureObject() +{ + // Note: Cast to MeasurePropertyBase once we use it to provide the needed values e.g. basePosition textPosition etc. + auto feature = dynamic_cast(pcObject); + if (!feature) { + throw Base::RuntimeError("Feature not found for ViewProviderMeasureBase"); + } + return feature; +} + + +//! calculate a good direction from the elements being measured to the annotation text based on the layout +//! of the elements and relationship with the cardinal axes and the view direction. elementDirection +//! is expected to be a normalized vector. +//! an example of an elementDirection would be the vector from the start of a line to the end. +Base::Vector3d ViewProviderMeasureBase::getTextDirection(Base::Vector3d elementDirection, double tolerance) +{ + // TODO: this can fail if the active view is not a 3d view (spreadsheet, techdraw page) and something causes a measure to try to update + // we need to search through the mdi views for a 3d view and take the direction from it (or decide that if the active view is not 3d, + // assume we are looking from the front). + Base::Vector3d viewDirection; + Base::Vector3d upDirection; + +Gui::View3DInventor* view = nullptr; + try { + view = dynamic_cast(this->getActiveView()); + } catch (const Base::RuntimeError& e) { + Base::Console().Log("ViewProviderMeasureBase::getTextDirection: Could not get active view\n"); + } + + if (view) { + Gui::View3DInventorViewer* viewer = view->getViewer(); + viewDirection = toVector3d(viewer->getViewDirection()).Normalize(); + upDirection = toVector3d(viewer->getUpDirection()).Normalize(); + // Measure doesn't work with this kind of active view. Might be dependency graph, might be TechDraw, or ???? + //throw Base::RuntimeError("Measure doesn't work with this kind of active view."); + } else { + viewDirection = Base::Vector3d(0.0, 1.0, 0.0); + upDirection = Base::Vector3d(0.0, 0.0, 1.0); + } + + Base::Vector3d textDirection = elementDirection.Cross(viewDirection); + if (textDirection.Length() < tolerance) { + // either elementDirection and viewDirection are parallel or one of them is null. + textDirection = elementDirection.Cross(upDirection); + } + + return textDirection.Normalize(); +} + + +//! true if the subject of this measurement is visible. For Measures that have multiple object subject, +//! all of the subjects must be visible. +bool ViewProviderMeasureBase::isSubjectVisible() +{ + Gui::Document* guiDoc = nullptr; + try { + guiDoc = this->getDocument(); + } catch (const Base::RuntimeError& e) { + Base::Console().Log("ViewProviderMeasureBase::isSubjectVisible: Could not get document\n"); + return false; + } + + // we need these things to proceed + if (!getMeasureObject() || + getMeasureObject()->getSubject().empty() || + !guiDoc ) { + return false; + } + + for (auto & obj : getMeasureObject()->getSubject()) { + Gui::ViewProvider* vp = guiDoc->getViewProvider(obj); + if (!vp || !vp->isVisible()) { + return false; + } + } + + // all of the subject objects are visible + return true; +} + + +//! gets called when the subject object issues a signalChanged (ie a property change). We are only interested in the subject's +//! Visibility property +void ViewProviderMeasureBase::onSubjectVisibilityChanged(const App::DocumentObject& docObj, const App::Property& prop) +{ + if (docObj.isRemoving()) { + return; + } + + std::string propName = prop.getName(); + if (propName == "Visibility") { + if (!docObj.Visibility.getValue()) { + // show ourselves only if subject is visible + setVisible(false); + } else { + // here, we don't know if we should be visible or not, so we have to check the whole subject + setVisible(isSubjectVisible()); + } + } +} + + +//NOLINTBEGIN +PROPERTY_SOURCE(MeasureGui::ViewProviderMeasure, MeasureGui::ViewProviderMeasureBase) +//NOLINTEND + +//! the general purpose view provider. handles area, length, etc - any measure without a specialized VP +ViewProviderMeasure::ViewProviderMeasure() +{ + sPixmap = "umf-measurement"; + + + // setupSceneGraph for leader? + const size_t lineCount(3); + + // indexes used to create the edges + // this makes a line from verts[0] to verts[1] + static const int32_t lines[lineCount] = + { + 0,1,-1 + }; + + pCoords = new SoCoordinate3(); + pCoords->ref(); + + // Combine coordinates from baseTranslation and labelTranslation + auto engineCat = new SoConcatenate(SoMFVec3f::getClassTypeId()); + auto origin = new SoSFVec3f(); + origin->setValue(0,0,0); + engineCat->input[0]->connectFrom(origin); + engineCat->input[1]->connectFrom(&pLabelTranslation->translation); + pCoords->point.setNum(engineCat->output->getNumConnections()); + pCoords->point.connectFrom(engineCat->output); + + pLines = new SoIndexedLineSet(); + pLines->ref(); + pLines->coordIndex.setNum(lineCount); + pLines->coordIndex.setValues(0, lineCount, lines); + + auto lineSep = pLineSeparator; + lineSep->addChild(pCoords); + lineSep->addChild(pLines); + auto points = new SoMarkerSet(); + points->markerIndex = Gui::Inventor::MarkerBitmaps::getMarkerIndex("CROSS", + Gui::ViewParams::instance()->getMarkerSize()); + points->numPoints=1; + lineSep->addChild(points); + + // Connect dragger local orientation to view orientation + Gui::View3DInventor* view = nullptr; + try { + view = dynamic_cast(this->getActiveView()); + } catch (const Base::RuntimeError& e) { + Base::Console().Log("ViewProviderMeasure::ViewProviderMeasure: Could not get active view\n"); + } + + if (view) { + Gui::View3DInventorViewer* viewer = view->getViewer(); + auto renderManager = viewer->getSoRenderManager(); + auto cam = renderManager->getCamera(); + pDraggerOrientation->rotation.connectFrom(&cam->orientation); + } + + +} + +ViewProviderMeasure::~ViewProviderMeasure() +{ + pCoords->unref(); + pLines->unref(); +} + +void ViewProviderMeasure::positionAnno(const Measure::MeasureBase* measureObject) { + (void)measureObject; + + // Initialize the text position + Base::Vector3d textPos = getTextPosition(); + auto srcVec = SbVec3f(textPos.x, textPos.y, textPos.z); + + // Translate the position by the local dragger matrix (pDraggerOrientation) + Gui::View3DInventor* view = nullptr; + try { + view = dynamic_cast(this->getActiveView()); + } catch (const Base::RuntimeError& e) { + Base::Console().Log("ViewProviderMeasure::positionAnno: Could not get active view\n"); + } + + if(!view){ + return; + } + + Gui::View3DInventorViewer* viewer = view->getViewer(); + auto gma = SoGetMatrixAction(viewer->getSoRenderManager()->getViewportRegion()); + gma.apply(pDraggerOrientation); + auto mat = gma.getMatrix(); + SbVec3f destVec(0, 0, 0); + mat.multVecMatrix(srcVec, destVec); + + setLabelTranslation(destVec); + updateView(); +} + +void ViewProviderMeasure::onChanged(const App::Property* prop) +{ + if (pcObject == nullptr) { + return; + } + + ViewProviderMeasureBase::onChanged(prop); +} + + +//! repaint the annotation +void ViewProviderMeasure::redrawAnnotation() +{ + // point on element + Base::Vector3d basePos = getBasePosition(); + pcTransform->translation.setValue(SbVec3f(basePos.x, basePos.y, basePos.z)); + + setLabelValue(getMeasureObject()->getResultString()); + + ViewProviderMeasureBase::redrawAnnotation(); + ViewProviderDocumentObject::updateView(); +} + + +Base::Vector3d ViewProviderMeasure::getBasePosition(){ + auto measureObject = getMeasureObject(); + Base::Placement placement = measureObject->getPlacement(); + return placement.getPosition(); +} + + +Base::Vector3d ViewProviderMeasure::getTextPosition(){ + constexpr float DefaultLeaderLength{20.0}; + auto basePoint = getBasePosition(); + Base::Vector3d textDirection(1.0, 1.0, 1.0); + textDirection.Normalize(); + + return basePoint + textDirection * DefaultLeaderLength; +} + +//! called by the system when it is time to display this measure +void ViewProviderMeasureBase::show() +{ + if (isSubjectVisible()) { + // only show the annotation if the subject is visible. + // this avoids disconnected annotations floating in space. + ViewProviderDocumentObject::show(); + } +} diff --git a/src/Mod/Measure/Gui/ViewProviderMeasureBase.h b/src/Mod/Measure/Gui/ViewProviderMeasureBase.h new file mode 100644 index 0000000000..42832ca035 --- /dev/null +++ b/src/Mod/Measure/Gui/ViewProviderMeasureBase.h @@ -0,0 +1,159 @@ +/*************************************************************************** + * Copyright (c) 2023 David Friedli * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + +#ifndef GUI_VIEWPROVIDER_MEASUREMENTBASE_H +#define GUI_VIEWPROVIDER_MEASUREMENTBASE_H + +#include + +#include + +#include +#include +#include +#include +#include + +#include + +//NOLINTBEGIN +class SbVec2s; +class SoFontStyle; +class SoBaseColor; +class SoText2; +class SoTranslation; +class SoPickStyle; +class SoCoordinate3; +class SoIndexedLineSet; +class SoTranslate2Dragger; +//NOLINTEND + + +namespace MeasureGui { + +//NOLINTBEGIN +class MeasureGuiExport ViewProviderMeasureBase :public Gui::ViewProviderDocumentObject +{ + PROPERTY_HEADER_WITH_OVERRIDE(ViewProviderMeasureBase); + +public: + /// constructor. + ViewProviderMeasureBase(); + + /// destructor. + ~ViewProviderMeasureBase() override; + + // Display properties + App::PropertyColor TextColor; + App::PropertyColor TextBackgroundColor; + App::PropertyColor LineColor; + App::PropertyInteger FontSize; +//NOLINTEND + + /** + * Attaches the document object to this view provider. + */ + bool isPartOfPhysicalObject() const override {return false;}; + void attach(App::DocumentObject *pcObj) override; + void updateData(const App::Property* prop) override; + virtual void positionAnno(const Measure::MeasureBase* measureObject); + void finishRestoring() override; + + bool useNewSelectionModel() const override {return true;} + std::vector getDisplayModes() const override; + void setDisplayMode(const char* ModeName) override; + /// Show the annotation in the 3d window + void show() override; + + virtual void redrawAnnotation(); + Measure::MeasureBase* getMeasureObject(); + + virtual bool isSubjectVisible(); + + static Base::Vector3d toVector3d(SbVec3f svec) { return Base::Vector3d(svec[0], svec[1], svec[2]); } + static SbVec3f toSbVec3f(Base::Vector3d vec3) { return SbVec3f(vec3.x, vec3.y, vec3.z); } + + void onSubjectVisibilityChanged(const App::DocumentObject& docObj, const App::Property& prop); + void connectToSubject(App::DocumentObject* subject); + void connectToSubject(std::vector subject); + +protected: + static void draggerChangedCallback(void* data, SoDragger*); + void onChanged(const App::Property* prop) override; + virtual void onLabelMoved() {}; + void setLabelValue(const Base::Quantity& value); + void setLabelValue(const QString& value); + void setLabelTranslation(const SbVec3f& position); + + SoPickStyle* getSoPickStyle(); + SoDrawStyle* getSoLineStylePrimary(); + SoDrawStyle* getSoLineStyleSecondary(); + SoSeparator* getSoSeparatorText(); + + static constexpr double defaultTolerance = 10e-6; + virtual Base::Vector3d getTextDirection(Base::Vector3d elementDirection, double tolerance = defaultTolerance); + + + // TODO: getters & setters and move variables to private? + bool _mShowTree = true; + + Gui::SoFrameLabel * pLabel; + SoTranslate2Dragger* pDragger; + SoTransform* pDraggerOrientation; + SoTransform * pLabelTranslation; + SoBaseColor * pColor; + SoSeparator* pRootSeparator; + SoSeparator* pTextSeparator; + SoSeparator* pLineSeparator; + SoSeparator* pLineSeparatorSecondary; +private: + boost::signals2::connection _mVisibilityChangedConnection; +}; + +//NOLINTBEGIN +class MeasureGuiExport ViewProviderMeasure : public MeasureGui::ViewProviderMeasureBase +{ + PROPERTY_HEADER_WITH_OVERRIDE(MeasureGui::ViewProviderMeasure); +//NOLINTEND + +public: + /// Constructor + ViewProviderMeasure(); + ~ViewProviderMeasure() override; + + void redrawAnnotation() override; + void positionAnno(const Measure::MeasureBase* measureObject) override; + +protected: + void onChanged(const App::Property* prop) override; + + virtual Base::Vector3d getBasePosition(); + virtual Base::Vector3d getTextPosition(); + +private: + SoCoordinate3 * pCoords; + SoIndexedLineSet * pLines; +}; + +} // namespace Gui + +#endif // GUI_VIEWPROVIDER_MEASUREMENTBASE_H + diff --git a/src/Mod/Measure/Gui/ViewProviderMeasureDistance.cpp b/src/Mod/Measure/Gui/ViewProviderMeasureDistance.cpp new file mode 100644 index 0000000000..019932eebf --- /dev/null +++ b/src/Mod/Measure/Gui/ViewProviderMeasureDistance.cpp @@ -0,0 +1,222 @@ +/*************************************************************************** + * Copyright (c) 2023 David Friedli * + * Copyright (c) 2008 Werner Mayer * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + +#include "PreCompiled.h" + +#ifndef _PreComp_ +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +#endif + +#include + +#include +#include +#include +#include +#include "Mod/Measure/App/MeasureDistance.h" +#include + +#include "ViewProviderMeasureDistance.h" +#include "Gui/Application.h" +#include +#include "Gui/Document.h" +#include "Gui/ViewParams.h" + + +using namespace Gui; +using namespace MeasureGui; +using namespace Measure; + +PROPERTY_SOURCE(MeasureGui::ViewProviderMeasureDistance, MeasureGui::ViewProviderMeasureBase) + + +SbMatrix ViewProviderMeasureDistance::getMatrix() { + auto object = dynamic_cast(getObject()); + auto vec1 = object->Position1.getValue(); + auto vec2 = object->Position2.getValue(); + + const double tolerance(10.0e-6); + SbVec3f origin = toSbVec3f((vec2 + vec1) / 2); + Base::Vector3d localXAxis = (vec2 - vec1).Normalize(); + Base::Vector3d localYAxis = getTextDirection(localXAxis, tolerance).Normalize(); + + // X and Y axis have to be 90° to eachother + assert(fabs(localYAxis.Dot(localXAxis)) < tolerance); + Base::Vector3d localZAxis = localYAxis.Cross(localXAxis).Normalize(); + + SbMatrix matrix = SbMatrix( + localXAxis.x, localXAxis.y, localXAxis.z, 0, + localYAxis.x, localYAxis.y, localYAxis.z ,0, + localZAxis.x, localZAxis.y, localZAxis.z, 0, + // 0,0,0,1 + origin[0], origin[1], origin[2], 1 + ); + + return matrix; +} + + +//! calculate a good direction from the elements being measured to the annotation text based on the layout +//! of the elements and its relationship with the cardinal axes and the view direction. elementDirection +//! is expected to be a normalized vector. +//! an example of an elementDirection would be the vector from the start of a line to the end. +Base::Vector3d ViewProviderMeasureDistance::getTextDirection(Base::Vector3d elementDirection, double tolerance) +{ + const Base::Vector3d stdX(1.0, 0.0, 0.0); + const Base::Vector3d stdY(0.0, 1.0, 0.0); + const Base::Vector3d stdZ(0.0, 0.0, 1.0); + + Base::Vector3d textDirection = elementDirection.Cross(stdX); + if (textDirection.Length() < tolerance) { + textDirection = elementDirection.Cross(stdY); + } + if (textDirection.Length() < tolerance) { + textDirection = elementDirection.Cross(stdZ); + } + textDirection.Normalize(); + if (textDirection.Dot(stdZ) < 0.0) { + textDirection = textDirection * -1.0; + } + + return textDirection.Normalize(); +} + + +ViewProviderMeasureDistance::ViewProviderMeasureDistance() +{ + sPixmap = "umf-measurement"; + + // vert indexes used to create the annotation lines + const size_t lineCount(3); + static const int32_t lines[lineCount] = + { + 2,3,-1 // dimension line + }; + + const size_t lineCountSecondary(9); + static const int32_t linesSecondary[lineCountSecondary] = { + 0,2,-1, // extension line 1 + 1,3,-1, // extension line 2 + 2,4,-1 // label helper line + }; + + // Line Coordinates + // 0-1 points on shape (dimension points) + // 2-3 ends of extension lines/dimension line + // 4 label position + pCoords = new SoCoordinate3(); + pCoords->ref(); + + auto engineCoords = new SoCalculator(); + engineCoords->a.connectFrom(&fieldDistance); + engineCoords->A.connectFrom(&pLabelTranslation->translation); + engineCoords->expression.setValue("ta=a/2; tb=A[1]; oA=vec3f(ta, 0, 0); oB=vec3f(-ta, 0, 0); " + "oC=vec3f(ta, tb, 0); oD=vec3f(-ta, tb, 0)"); + + auto engineCat = new SoConcatenate(SoMFVec3f::getClassTypeId()); + engineCat->input[0]->connectFrom(&engineCoords->oA); + engineCat->input[1]->connectFrom(&engineCoords->oB); + engineCat->input[2]->connectFrom(&engineCoords->oC); + engineCat->input[3]->connectFrom(&engineCoords->oD); + engineCat->input[4]->connectFrom(&pLabelTranslation->translation); + + pCoords->point.connectFrom(engineCat->output); + pCoords->point.setNum(engineCat->output->getNumConnections()); + + pLines = new SoIndexedLineSet(); + pLines->ref(); + pLines->coordIndex.setNum(lineCount); + pLines->coordIndex.setValues(0, lineCount, lines); + + pLineSeparator->addChild(pCoords); + pLineSeparator->addChild(pLines); + + + // Secondary Lines + auto lineSetSecondary = new SoIndexedLineSet(); + lineSetSecondary->coordIndex.setNum(lineCountSecondary); + lineSetSecondary->coordIndex.setValues(0, lineCountSecondary, linesSecondary); + + pLineSeparatorSecondary->addChild(pCoords); + pLineSeparatorSecondary->addChild(lineSetSecondary); + + auto points = new SoMarkerSet(); + points->markerIndex = Gui::Inventor::MarkerBitmaps::getMarkerIndex("CROSS", + ViewParams::instance()->getMarkerSize()); + points->numPoints=2; + pLineSeparator->addChild(points); +} + +ViewProviderMeasureDistance::~ViewProviderMeasureDistance() +{ + pCoords->unref(); + pLines->unref(); +} + + +Measure::MeasureDistance* ViewProviderMeasureDistance::getMeasureDistance() +{ + Measure::MeasureDistance* feature = dynamic_cast(pcObject); + if (!feature) { + throw Base::RuntimeError("Feature not found for ViewProviderMeasureDistance"); + } + return feature; +} + +//! repaint the annotation +void ViewProviderMeasureDistance::redrawAnnotation() +{ + auto object = dynamic_cast(getObject()); + auto vec1 = object->Position1.getValue(); + auto vec2 = object->Position2.getValue(); + + // Set the distance + fieldDistance = (vec2 - vec1).Length(); + + setLabelValue(object->Distance.getQuantityValue().getUserString()); + + // Set matrix + SbMatrix matrix = getMatrix(); + pcTransform->setMatrix(matrix); + + ViewProviderMeasureBase::redrawAnnotation(); + updateView(); +} + + +void ViewProviderMeasureDistance::positionAnno(const Measure::MeasureBase* measureObject) { + (void)measureObject; + setLabelTranslation(SbVec3f(0, 10, 0)); +} diff --git a/src/Mod/Measure/Gui/ViewProviderMeasureDistance.h b/src/Mod/Measure/Gui/ViewProviderMeasureDistance.h new file mode 100644 index 0000000000..45551e1231 --- /dev/null +++ b/src/Mod/Measure/Gui/ViewProviderMeasureDistance.h @@ -0,0 +1,69 @@ +/*************************************************************************** + * Copyright (c) 2023 David Friedli * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + + +#ifndef MEASUREGUI_VIEWPROVIDERMEASUREDISTANCE_H +#define MEASUREGUI_VIEWPROVIDERMEASUREDISTANCE_H + +#include + +#include + +#include + +#include "ViewProviderMeasureBase.h" + +class SoCoordinate3; +class SoIndexedLineSet; + +namespace MeasureGui +{ + + +class MeasureGuiExport ViewProviderMeasureDistance : public MeasureGui::ViewProviderMeasureBase +{ + PROPERTY_HEADER_WITH_OVERRIDE(MeasureGui::ViewProviderMeasureDistance); + +public: + /// Constructor + ViewProviderMeasureDistance(); + ~ViewProviderMeasureDistance() override; + + Measure::MeasureDistance* getMeasureDistance(); + void redrawAnnotation() override; + void positionAnno(const Measure::MeasureBase* measureObject) override; + +protected: + Base::Vector3d getTextDirection(Base::Vector3d elementDirection, double tolerance = defaultTolerance) override; + +private: + SoCoordinate3 * pCoords; + SoIndexedLineSet * pLines; + + SoSFFloat fieldDistance; + + SbMatrix getMatrix(); +}; + +} //namespace MeasureGui + + +#endif // MEASUREGUI_VIEWPROVIDERMEASUREDISTANCE_H diff --git a/src/Mod/Measure/InitGui.py b/src/Mod/Measure/InitGui.py new file mode 100644 index 0000000000..22952279b8 --- /dev/null +++ b/src/Mod/Measure/InitGui.py @@ -0,0 +1,43 @@ +# /*************************************************************************** +# * Copyright (c) 2023 David Friedli * +# * * +# * This file is part of FreeCAD. * +# * * +# * FreeCAD is free software: you can redistribute it and/or modify it * +# * under the terms of the GNU Lesser General Public License as * +# * published by the Free Software Foundation, either version 2.1 of the * +# * License, or (at your option) any later version. * +# * * +# * FreeCAD 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 * +# * Lesser General Public License for more details. * +# * * +# * You should have received a copy of the GNU Lesser General Public * +# * License along with FreeCAD. If not, see * +# * . * +# * * +# **************************************************************************/ + + +# Import the measure module here in order to ensure that default measurement types are loaded during startup. +# Note that they won't auto-load in gui-less mode. Ideally they would be loaded from "Init.py", similar +# to how the import/export types are registered. This would require to register measurement types from +# python which could complicate things further. + +import Measure +from MeasureCOM import makeMeasureCOM, MeasureCOM + + +# Expose create functions +Measure.makeMeasureCOM = makeMeasureCOM + + +# Register python measure types +import FreeCAD +FreeCAD.MeasureManager.addMeasureType( + "CENTEROFMASS", + "Center of Mass", + MeasureCOM, +) + diff --git a/src/Mod/Measure/MeasureCOM.py b/src/Mod/Measure/MeasureCOM.py new file mode 100644 index 0000000000..39e73df5cf --- /dev/null +++ b/src/Mod/Measure/MeasureCOM.py @@ -0,0 +1,152 @@ +# /*************************************************************************** +# * Copyright (c) 2023 David Friedli * +# * * +# * This file is part of FreeCAD. * +# * * +# * FreeCAD is free software: you can redistribute it and/or modify it * +# * under the terms of the GNU Lesser General Public License as * +# * published by the Free Software Foundation, either version 2.1 of the * +# * License, or (at your option) any later version. * +# * * +# * FreeCAD 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 * +# * Lesser General Public License for more details. * +# * * +# * You should have received a copy of the GNU Lesser General Public * +# * License along with FreeCAD. If not, see * +# * . * +# * * +# **************************************************************************/ + +import FreeCAD +from FreeCAD import Units, Placement +from UtilsMeasure import MeasureBasePython +from PySide.QtCore import QT_TRANSLATE_NOOP + + + +__title__="Measure Center of Mass Object" +__author__ = "David Friedli" +__url__ = "http://www.freecad.org" + + + +""" + The Measure cpp object defines a result and a placement property. The Python measure type + adds it's own specific properties. Once the object is recomputed the parent properties are updated + based on the specific python properties. + + We'll need some kind of interface for the measure command which exposes "parseSelection", "isValidSelection" etc. + +""" + + + +def makeMeasureCOM(name="CenterOfMass"): + '''makeMeasureCOM(name): make a CenterofMass measurement''' + obj = FreeCAD.ActiveDocument.addObject("Measure::MeasurePython", name) + MeasureCOM(obj) + return obj + + +class MeasureCOM(MeasureBasePython): + "The MeasureCOM object" + + def __init__(self, obj): + obj.Proxy = self + + obj.addProperty("App::PropertyLinkSubGlobal", "Element", "", QT_TRANSLATE_NOOP("App::Property", "Element to measure")) + obj.addProperty("App::PropertyPosition", "Result", "", QT_TRANSLATE_NOOP("App::PropertyVector", "The result location")) + + @classmethod + def isValidSelection(cls, selection): + if not len(selection) == 1: + return False + + element = selection[0] + ob = element["object"] + subName = element["subName"] + + if not ob: + return + + sub = ob.getSubObject(subName) + if not sub: + return + + if not hasattr(sub, "CenterOfMass"): + return + + return True + + @classmethod + def isPrioritySelection(cls, selection): + return False + + @classmethod + def getInputProps(cls): + return ("Element",) + + def getSubject(self, obj): + if not obj: + return () + + element = obj.Element + if not element: + return () + + ob = element[0] + if not ob: + return () + return (ob,) + + + def parseSelection(self, obj, selection): + item = selection[0] + o = item["object"] + obj.Element = (o, item["subName"]) + + + def getResultString(self, obj): + values = [Units.Quantity(v, Units.Length).getUserPreferred()[0] for v in obj.Result] + return "COM\nX: {}\nY: {}\nZ: {}".format(*values) + + def execute(self, obj): + element = obj.Element + if not element: + return + + ob = element[0] + subElements = element[1] + + if subElements: + subName = subElements[0] + sub = ob.getSubObject(subName) + + if not sub or not hasattr(sub, "CenterOfMass"): + return + com = sub.CenterOfMass + + else: + # Get Center of Mass of the object + if not hasattr(ob, "Shape"): + return + + shape = ob.Shape + if not hasattr(shape, "CenterOfMass"): + return + + com = shape.CenterOfMass + + obj.Result = com + placement = Placement() + placement.Base = com + obj.Placement = placement + + + def onChanged(self, obj, prop): + '''Do something when a property has changed''' + + if prop == "Element": + self.execute(obj) diff --git a/src/Mod/Measure/MeasureGlobal.h b/src/Mod/Measure/MeasureGlobal.h index 3df4317b45..5a935a8413 100644 --- a/src/Mod/Measure/MeasureGlobal.h +++ b/src/Mod/Measure/MeasureGlobal.h @@ -29,9 +29,18 @@ // Measure #ifndef MeasureExport #ifdef Measure_EXPORTS -# define MeasureExport FREECAD_DECL_EXPORT +#define MeasureExport FREECAD_DECL_EXPORT #else -# define MeasureExport FREECAD_DECL_IMPORT +#define MeasureExport FREECAD_DECL_IMPORT +#endif +#endif + +// MeasureGui +#ifndef MeasureGuiExport +#ifdef MeasureGui_EXPORTS +# define MeasureGuiExport FREECAD_DECL_EXPORT +#else +# define MeasureGuiExport FREECAD_DECL_IMPORT #endif #endif diff --git a/src/Mod/Measure/UtilsMeasure.py b/src/Mod/Measure/UtilsMeasure.py new file mode 100644 index 0000000000..d013c8d5f0 --- /dev/null +++ b/src/Mod/Measure/UtilsMeasure.py @@ -0,0 +1,52 @@ +# /*************************************************************************** +# * Copyright (c) 2023 David Friedli * +# * * +# * This file is part of FreeCAD. * +# * * +# * FreeCAD is free software: you can redistribute it and/or modify it * +# * under the terms of the GNU Lesser General Public License as * +# * published by the Free Software Foundation, either version 2.1 of the * +# * License, or (at your option) any later version. * +# * * +# * FreeCAD 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 * +# * Lesser General Public License for more details. * +# * * +# * You should have received a copy of the GNU Lesser General Public * +# * License along with FreeCAD. If not, see * +# * . * +# * * +# **************************************************************************/ + +from abc import ABC, abstractmethod, abstractclassmethod +from typing import List, Tuple + +class MeasureBasePython(ABC): + + @abstractclassmethod + def isValidSelection(cls, selection): + """Returns True if the given selection is valid for this measurment""" + pass + + @abstractclassmethod + def isPrioritySelection(cls, selection): + """Returns True if creation of this measurment should be priorized over other measurements for the given selection""" + pass + + @abstractclassmethod + def getInputProps(cls) -> Tuple[str]: + """Returns all properties that the measurement's result depends on""" + return () + + @abstractmethod + def getSubject(self, obj) -> Tuple: + """Returns all objects that are measured, this is used to autohide the measurement if the relevant elements are not visible""" + return [] + + @abstractmethod + def parseSelection(self, obj, selection): + """Sets the measurements properties from the given selection""" + pass + + diff --git a/src/Mod/Part/App/Measure.cpp b/src/Mod/Part/App/MeasureClient.cpp similarity index 57% rename from src/Mod/Part/App/Measure.cpp rename to src/Mod/Part/App/MeasureClient.cpp index e46b01261f..0c099a78a0 100644 --- a/src/Mod/Part/App/Measure.cpp +++ b/src/Mod/Part/App/MeasureClient.cpp @@ -1,24 +1,25 @@ /*************************************************************************** - * Copyright (c) 2023 David Friedli * + * Copyright (c) 2023 Wandererfan * + * Copyright (c) 2023 Joel Meijering (EDG5000) * + * Copyright (c) 2023 David Friedli * * * - * This file is part of the FreeCAD CAx development system. * + * This file is part of FreeCAD. * * * - * 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. * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 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. * + * FreeCAD 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 * + * Lesser 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 * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * * * - ***************************************************************************/ + **************************************************************************/ #include "PreCompiled.h" @@ -39,11 +40,14 @@ #include #include #include -#include +#include +#include #include #include #include +#include +#include #include #include #include @@ -59,7 +63,9 @@ #include "VectorAdapter.h" #include "PartFeature.h" -#include "Measure.h" +#include "MeasureClient.h" + +using namespace Part; // From: https://github.com/Celemation/FreeCAD/blob/joel_selection_summary_demo/src/Gui/SelectionSummary.cpp @@ -90,11 +96,16 @@ static float getRadius(TopoDS_Shape& edge){ return 0.0; } -TopoDS_Shape getLocatedShape(const std::string& objectName, const std::string& subName) +TopoDS_Shape getLocatedShape(const App::SubObjectT& subject) { - App::DocumentObject* obj = App::GetApplication().getActiveDocument()->getObject(objectName.c_str()); - // should this be getTopoShape(obj, subName, true)? + App::DocumentObject* obj = subject.getObject(); + if (!obj) { + return {}; + } Part::TopoShape shape = Part::Feature::getTopoShape(obj); + if (shape.isNull()) { + return {}; + } auto geoFeat = dynamic_cast(obj); if (geoFeat) { shape.setPlacement(geoFeat->globalPlacement()); @@ -105,22 +116,19 @@ TopoDS_Shape getLocatedShape(const std::string& objectName, const std::string& s return shape.getShape(); } - if (subName.empty()) - { + if (!subject.getElementName()) { return shape.getShape(); } - return shape.getSubShape(subName.c_str()); + return shape.getSubShape(subject.getElementName(), true); } -App::MeasureElementType PartMeasureTypeCb(const char* objectName, const char* subName) { - App::DocumentObject* ob = App::GetApplication().getActiveDocument()->getObject(objectName); -// auto sub = ob->getSubObject(subName); - +App::MeasureElementType PartMeasureTypeCb(App::DocumentObject* ob, const char* subName) +{ TopoDS_Shape shape = Part::Feature::getShape(ob, subName, true); if (shape.IsNull()) { // failure here on loading document with existing measurement. - Base::Console().Message("Part::VectorAdapter did not retrieve shape for %s, %s\n", objectName, subName); + Base::Console().Message("Part::PartMeasureTypeCb did not retrieve shape for %s, %s\n", ob->getNameInDocument(), subName); return App::MeasureElementType(); } TopAbs_ShapeEnum shapeType = shape.ShapeType(); @@ -168,31 +176,29 @@ App::MeasureElementType PartMeasureTypeCb(const char* objectName, const char* su } - -bool getShapeFromStrings(TopoDS_Shape &shapeOut, const std::string &doc, const std::string &object, const std::string &sub, Base::Matrix4D *mat) +bool getShapeFromStrings(TopoDS_Shape &shapeOut, const App::SubObjectT& subject, Base::Matrix4D *mat) { - App::Document *docPointer = App::GetApplication().getDocument(doc.c_str()); - if (!docPointer) { - return false; - } - App::DocumentObject *objectPointer = docPointer->getObject(object.c_str()); - if (!objectPointer) { - return false; - } - shapeOut = Part::Feature::getShape(objectPointer,sub.c_str(),true,mat); - return !shapeOut.IsNull(); + App::DocumentObject *obj = subject.getObject(); + if (!obj) { + return {}; + } + shapeOut = Part::Feature::getShape(obj, subject.getElementName(), true, mat); + return !shapeOut.IsNull(); } -Part::VectorAdapter buildAdapter(const App::DocumentObject* ob, std::string* objectName, const std::string* subName) +Part::VectorAdapter buildAdapter(const App::SubObjectT& subject) { - (void) objectName; + if (!subject.getObject()) { + return Part::VectorAdapter(); + } Base::Matrix4D mat; - TopoDS_Shape shape = Part::Feature::getShape(ob, subName->c_str(), true); + TopoDS_Shape shape = Part::Feature::getShape(subject.getObject(), subject.getElementName(), true); if (shape.IsNull()) { // failure here on loading document with existing measurement. - Base::Console().Message("Part::VectorAdapter did not retrieve shape for %s, %s\n", objectName->c_str(), subName->c_str()); + Base::Console().Message("Part::buildAdapter did not retrieve shape for %s, %s\n", + subject.getObjectName(), subject.getElementName()); return Part::VectorAdapter(); } @@ -202,7 +208,7 @@ Part::VectorAdapter buildAdapter(const App::DocumentObject* ob, std::string* obj if (shapeType == TopAbs_EDGE) { TopoDS_Shape edgeShape; - if (!getShapeFromStrings(edgeShape, ob->getDocument()->getName(), ob->getNameInDocument(), *subName, &mat)) { + if (!getShapeFromStrings(edgeShape, subject, &mat)) { return {}; } TopoDS_Edge edge = TopoDS::Edge(edgeShape); @@ -233,7 +239,7 @@ Part::VectorAdapter buildAdapter(const App::DocumentObject* ob, std::string* obj if (shapeType == TopAbs_FACE) { TopoDS_Shape faceShape; - if (!getShapeFromStrings(faceShape, ob->getDocument()->getName(), ob->getNameInDocument(), *subName, &mat)) { + if (!getShapeFromStrings(faceShape, subject, &mat)) { return {}; } @@ -248,18 +254,20 @@ Part::VectorAdapter buildAdapter(const App::DocumentObject* ob, std::string* obj } -Measure::MeasureLengthInfo MeasureLengthHandler(std::string* objectName, std::string* subName){ - TopoDS_Shape shape = getLocatedShape(*objectName, *subName); +MeasureLengthInfoPtr MeasureLengthHandler(const App::SubObjectT& subject) +{ + TopoDS_Shape shape = getLocatedShape(subject); if (shape.IsNull()) { // failure here on loading document with existing measurement. - Base::Console().Message("MeasureLengthHandler did not retrieve shape for %s, %s\n", objectName->c_str(), subName->c_str()); - return {false, 0.0, Base::Matrix4D()}; + Base::Console().Message("MeasureLengthHandler did not retrieve shape for %s, %s\n", + subject.getObjectName(), subject.getElementName()); + return std::make_shared(false, 0.0, Base::Matrix4D()); } TopAbs_ShapeEnum sType = shape.ShapeType(); if (sType != TopAbs_EDGE) { - return {false, 0.0, Base::Matrix4D()}; + return std::make_shared(false, 0.0, Base::Matrix4D()); } // Get Center of mass as the attachment point of the label @@ -278,22 +286,23 @@ Measure::MeasureLengthInfo MeasureLengthHandler(std::string* objectName, std::st Base::Rotation rot(axisUp, elementDirection); Base::Placement placement(Base::Vector3d(origin.X(), origin.Y(), origin.Z()), rot); - return {true, getLength(shape), placement}; + return std::make_shared(true, getLength(shape), placement); } -Measure::MeasureRadiusInfo MeasureRadiusHandler(std::string* objectName, std::string* subName){ +MeasureRadiusInfoPtr MeasureRadiusHandler(const App::SubObjectT& subject) +{ Base::Placement placement; // curve center + orientation Base::Vector3d pointOnCurve; - TopoDS_Shape shape = getLocatedShape(*objectName, *subName); + TopoDS_Shape shape = getLocatedShape(subject); if (shape.IsNull()) { - return { false, 0.0, pointOnCurve, placement}; + return std::make_shared( false, 0.0, pointOnCurve, placement); } TopAbs_ShapeEnum sType = shape.ShapeType(); if (sType != TopAbs_EDGE) { - return { false, 0.0, pointOnCurve, placement}; + return std::make_shared( false, 0.0, pointOnCurve, placement); } // Get Center of mass as the attachment point of the label @@ -312,22 +321,24 @@ Measure::MeasureRadiusInfo MeasureRadiusHandler(std::string* objectName, std::st placement = Base::Placement(Base::Vector3d(origin.X(), origin.Y(), origin.Z()), rot); - return { true, getRadius(shape), pointOnCurve, placement}; + return std::make_shared( true, getRadius(shape), pointOnCurve, placement); } -Measure::MeasureAreaInfo MeasureAreaHandler(std::string* objectName, std::string* subName){ - TopoDS_Shape shape = getLocatedShape(*objectName, *subName); +MeasureAreaInfoPtr MeasureAreaHandler(const App::SubObjectT& subject) +{ + TopoDS_Shape shape = getLocatedShape(subject); if (shape.IsNull()) { // failure here on loading document with existing measurement. - Base::Console().Message("MeasureLengthHandler did not retrieve shape for %s, %s\n", objectName->c_str(), subName->c_str()); - return {false, 0.0, Base::Matrix4D()}; + Base::Console().Message("MeasureLengthHandler did not retrieve shape for %s, %s\n", + subject.getObjectName(), subject.getElementName()); + return std::make_shared(false, 0.0, Base::Matrix4D()); } TopAbs_ShapeEnum sType = shape.ShapeType(); if (sType != TopAbs_FACE) { - return {false, 0.0, Base::Matrix4D()}; + return std::make_shared(false, 0.0, Base::Matrix4D()); } // Get Center of mass as the attachment point of the label @@ -338,41 +349,45 @@ Measure::MeasureAreaInfo MeasureAreaHandler(std::string* objectName, std::string // TODO: Center of Mass might not lie on the surface, somehow snap to the closest point on the surface? Base::Placement placement(Base::Vector3d(origin.X(), origin.Y(), origin.Z()), Base::Rotation()); - return {true, getFaceArea(shape), placement}; + return std::make_shared(true, getFaceArea(shape), placement); } -Base::Vector3d MeasurePositionHandler(std::string* objectName, std::string* subName) { - TopoDS_Shape shape = getLocatedShape(*objectName, *subName); +MeasurePositionInfoPtr MeasurePositionHandler(const App::SubObjectT& subject) +{ + TopoDS_Shape shape = getLocatedShape(subject); if (shape.IsNull()) { - Base::Console().Message("MeasurePositionHandler did not retrieve shape for %s, %s\n", objectName->c_str(), subName->c_str()); - return Base::Vector3d(); + Base::Console().Message("MeasurePositionHandler did not retrieve shape for %s, %s\n", + subject.getObjectName(), subject.getElementName()); + return std::make_shared(false, Base::Vector3d()); } TopAbs_ShapeEnum sType = shape.ShapeType(); if (sType != TopAbs_VERTEX) { - return Base::Vector3d(); + return std::make_shared(false, Base::Vector3d()); } TopoDS_Vertex vertex = TopoDS::Vertex(shape); auto point = BRep_Tool::Pnt(vertex); - return Base::Vector3d(point.X(), point.Y(), point.Z()); + return std::make_shared( true, Base::Vector3d(point.X(), point.Y(), point.Z())); } -Measure::MeasureAngleInfo MeasureAngleHandler(std::string* objectName, std::string* subName) { - App::DocumentObject* ob = App::GetApplication().getActiveDocument()->getObject(objectName->c_str()); - TopoDS_Shape shape = getLocatedShape(*objectName, *subName); +MeasureAngleInfoPtr MeasureAngleHandler(const App::SubObjectT& subject) +{ + TopoDS_Shape shape = getLocatedShape(subject); + if (shape.IsNull()) { // failure here on loading document with existing measurement. - Base::Console().Message("MeasureAngleHandler did not retrieve shape for %s, %s\n", objectName->c_str(), subName->c_str()); - return Measure::MeasureAngleInfo(); + Base::Console().Message("MeasureAngleHandler did not retrieve shape for %s, %s\n", + subject.getObjectName(), subject.getElementName()); + return std::make_shared(); } TopAbs_ShapeEnum sType = shape.ShapeType(); - Part::VectorAdapter vAdapt = buildAdapter(ob, objectName, subName); + Part::VectorAdapter vAdapt = buildAdapter(subject); gp_Pnt vec; Base::Vector3d position; @@ -393,48 +408,89 @@ Measure::MeasureAngleInfo MeasureAngleHandler(std::string* objectName, std::stri position.Set(vec.X(), vec.Y(), vec.Z()); - Measure::MeasureAngleInfo info = {vAdapt.isValid(), (Base::Vector3d)vAdapt, position}; + auto info = std::make_shared(vAdapt.isValid(), (Base::Vector3d)vAdapt, position); return info; } -Measure::MeasureDistanceInfo MeasureDistanceHandler(std::string* objectName, std::string* subName) { - TopoDS_Shape shape = getLocatedShape(*objectName, *subName); +MeasureDistanceInfoPtr MeasureDistanceHandler(const App::SubObjectT& subject) +{ + TopoDS_Shape shape = getLocatedShape(subject); if (shape.IsNull()) { // failure here on loading document with existing measurement. - Base::Console().Message("MeasureDistanceHandler did not retrieve shape for %s, %s\n", objectName->c_str(), subName->c_str()); - return Measure::MeasureDistanceInfo(); + Base::Console().Message("MeasureDistanceHandler did not retrieve shape for %s, %s\n", + subject.getObjectName(), subject.getElementName()); + return std::make_shared(); } - // Just return the TopoDS_Shape here - return {true, shape}; + // return a persistent copy of the TopoDS_Shape here as shape will go out of scope at end + BRepBuilderAPI_Copy copy(shape); + const TopoDS_Shape* newShape = new TopoDS_Shape(copy.Shape()); + return std::make_shared(true, newShape); } -using namespace Measure; +void Part::MeasureClient::initialize() { + App::MeasureManager::addMeasureHandler("Part", PartMeasureTypeCb); -void Part::Measure::initialize() { - - App::Application& app = App::GetApplication(); - app.addMeasureHandler("Part", PartMeasureTypeCb); - - std::vector proxyList( { "Part", "PartDesign", "Sketcher" } ); - // Extend MeasureLength - MeasureLength::addGeometryHandlers(proxyList, MeasureLengthHandler); - - // Extend MeasurePosition - MeasurePosition::addGeometryHandlers(proxyList, MeasurePositionHandler); - - // Extend MeasureArea - MeasureArea::addGeometryHandlers(proxyList, MeasureAreaHandler); - - // Extend MeasureAngle - MeasureAngle::addGeometryHandlers(proxyList, MeasureAngleHandler); - - // Extend MeasureDistance - MeasureDistance::addGeometryHandlers(proxyList, MeasureDistanceHandler); - - // Extend MeasureRadius - MeasureRadius::addGeometryHandlers(proxyList, MeasureRadiusHandler); } + +Part::CallbackRegistrationList Part::MeasureClient::reportLengthCB() +{ + CallbackRegistrationList callbacks; + callbacks.emplace_back("Part", "Length", MeasureLengthHandler); + callbacks.emplace_back("PartDesign", "Length", MeasureLengthHandler); + callbacks.emplace_back("Sketcher", "Length", MeasureLengthHandler); + return callbacks; +} + +Part::CallbackRegistrationList Part::MeasureClient::reportPositionCB() +{ + CallbackRegistrationList callbacks; + callbacks.emplace_back("Part", "Position", MeasurePositionHandler); + callbacks.emplace_back("PartDesign", "Position", MeasurePositionHandler); + callbacks.emplace_back("Sketcher", "Position", MeasurePositionHandler); + return callbacks; +} + +Part::CallbackRegistrationList Part::MeasureClient::reportAreaCB() +{ + CallbackRegistrationList callbacks; + callbacks.emplace_back("Part", "Area", MeasureAreaHandler); + callbacks.emplace_back("PartDesign", "Area", MeasureAreaHandler); + callbacks.emplace_back("Sketcher", "Area", MeasureAreaHandler); + return callbacks; +} + + +Part::CallbackRegistrationList Part::MeasureClient::reportAngleCB() +{ + CallbackRegistrationList callbacks; + callbacks.emplace_back("Part", "Angle", MeasureAngleHandler); + callbacks.emplace_back("PartDesign", "Angle", MeasureAngleHandler); + callbacks.emplace_back("Sketcher", "Angle", MeasureAngleHandler); + return callbacks; +} + + +Part::CallbackRegistrationList Part::MeasureClient::reportDistanceCB() +{ + CallbackRegistrationList callbacks; + callbacks.emplace_back("Part", "Distance", MeasureDistanceHandler); + callbacks.emplace_back("PartDesign", "Distance", MeasureDistanceHandler); + callbacks.emplace_back("Sketcher", "Distance", MeasureDistanceHandler); + return callbacks; +} + + +Part::CallbackRegistrationList Part::MeasureClient::reportRadiusCB() +{ + CallbackRegistrationList callbacks; + callbacks.emplace_back("Part", "Radius", MeasureRadiusHandler); + callbacks.emplace_back("PartDesign", "Radius", MeasureRadiusHandler); + callbacks.emplace_back("Sketcher", "Radius", MeasureRadiusHandler); + return callbacks; +} + + From 47fd87af511cc99cf0b10bb7b97169a727a5ce09 Mon Sep 17 00:00:00 2001 From: hlorus Date: Sun, 14 Jan 2024 21:43:24 +0100 Subject: [PATCH 09/11] [Gui] Add command and task for unified measurement facility --- src/Gui/CMakeLists.txt | 2 + src/Gui/CommandView.cpp | 34 +++ src/Gui/Icons/resource.qrc | 1 + src/Gui/Icons/umf-measurement.svg | 317 ++++++++++++++++++++++++ src/Gui/TaskMeasure.cpp | 388 ++++++++++++++++++++++++++++++ src/Gui/TaskMeasure.h | 87 +++++++ src/Gui/Workbench.cpp | 2 +- 7 files changed, 830 insertions(+), 1 deletion(-) create mode 100644 src/Gui/Icons/umf-measurement.svg create mode 100644 src/Gui/TaskMeasure.cpp create mode 100644 src/Gui/TaskMeasure.h diff --git a/src/Gui/CMakeLists.txt b/src/Gui/CMakeLists.txt index 7a06a512eb..f3da6bc391 100644 --- a/src/Gui/CMakeLists.txt +++ b/src/Gui/CMakeLists.txt @@ -455,6 +455,7 @@ SET(Dialog_CPP_SRCS DownloadManager.cpp DocumentRecovery.cpp TaskElementColors.cpp + TaskMeasure.cpp DlgObjectSelection.cpp DlgAddProperty.cpp VectorListEditor.cpp @@ -494,6 +495,7 @@ SET(Dialog_HPP_SRCS DownloadManager.h DocumentRecovery.h TaskElementColors.h + TaskMeasure.h DlgObjectSelection.h DlgAddProperty.h VectorListEditor.h diff --git a/src/Gui/CommandView.cpp b/src/Gui/CommandView.cpp index bf39b60cef..f4cb50cd8c 100644 --- a/src/Gui/CommandView.cpp +++ b/src/Gui/CommandView.cpp @@ -76,6 +76,7 @@ #include "SelectionObject.h" #include "SoAxisCrossKit.h" #include "SoFCOffscreenRenderer.h" +#include "TaskMeasure.h" #include "TextureMapping.h" #include "Tools.h" #include "Tree.h" @@ -3189,6 +3190,38 @@ bool StdCmdMeasureDistance::isActive() return false; } +//=========================================================================== +// Std_Measure +// this is the Unified Measurement Facility Measure command +//=========================================================================== + + +DEF_STD_CMD_A(StdCmdMeasure) + +StdCmdMeasure::StdCmdMeasure() + :Command("Std_Measure") +{ + sGroup = "Measure"; + sMenuText = QT_TR_NOOP("&Measure"); + sToolTipText = QT_TR_NOOP("Measure a feature"); + sWhatsThis = "Std_Measure"; + sStatusTip = QT_TR_NOOP("Measure a feature"); + sPixmap = "umf-measurement"; +} + +void StdCmdMeasure::activated(int iMsg) +{ + Q_UNUSED(iMsg); + + TaskMeasure *task = new TaskMeasure(); + Gui::Control().showDialog(task); +} + + +bool StdCmdMeasure::isActive(){ + return true; +} + //=========================================================================== // Std_SceneInspector //=========================================================================== @@ -4117,6 +4150,7 @@ void CreateViewStdCommands() rcCmdMgr.addCommand(new StdCmdTreeCollapse()); rcCmdMgr.addCommand(new StdCmdTreeSelectAllInstances()); rcCmdMgr.addCommand(new StdCmdMeasureDistance()); + rcCmdMgr.addCommand(new StdCmdMeasure()); rcCmdMgr.addCommand(new StdCmdSceneInspector()); rcCmdMgr.addCommand(new StdCmdTextureMapping()); rcCmdMgr.addCommand(new StdCmdDemoMode()); diff --git a/src/Gui/Icons/resource.qrc b/src/Gui/Icons/resource.qrc index a5bf4c9b2c..614a660e4b 100644 --- a/src/Gui/Icons/resource.qrc +++ b/src/Gui/Icons/resource.qrc @@ -135,6 +135,7 @@ view-rotate-right.svg view-measurement.svg view-measurement-cross.svg + umf-measurement.svg Tree_Annotation.svg Tree_Dimension.svg Tree_Python.svg diff --git a/src/Gui/Icons/umf-measurement.svg b/src/Gui/Icons/umf-measurement.svg new file mode 100644 index 0000000000..e9971aed73 --- /dev/null +++ b/src/Gui/Icons/umf-measurement.svg @@ -0,0 +1,317 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Gui/TaskMeasure.cpp b/src/Gui/TaskMeasure.cpp new file mode 100644 index 0000000000..7d18b5593a --- /dev/null +++ b/src/Gui/TaskMeasure.cpp @@ -0,0 +1,388 @@ +/*************************************************************************** + * Copyright (c) 2023 David Friedli * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + + +#include "PreCompiled.h" + +#ifndef _PreComp_ +# include +# include +#endif + + +#include "TaskMeasure.h" + +#include "Control.h" +#include "MainWindow.h" +#include "Application.h" +#include "App/Document.h" +#include "App/DocumentObjectGroup.h" +#include + +#include +#include + +using namespace Gui; + + +TaskMeasure::TaskMeasure() +{ + qApp->installEventFilter(this); + + this->setButtonPosition(TaskMeasure::South); + auto taskbox = new Gui::TaskView::TaskBox(Gui::BitmapFactory().pixmap("umf-measurement"), tr("Measurement"), true, nullptr); + + // Create mode dropdown and add all registered measuretypes + modeSwitch = new QComboBox(); + modeSwitch->addItem(QString::fromLatin1("Auto")); + + for (App::MeasureType* mType : App::MeasureManager::getMeasureTypes()){ + modeSwitch->addItem(QString::fromLatin1(mType->label.c_str())); + } + + // Connect dropdown's change signal to our onModeChange slot + connect(modeSwitch, qOverload(&QComboBox::currentIndexChanged), this, &TaskMeasure::onModeChanged); + + // Result widget + valueResult = new QLineEdit(); + valueResult->setReadOnly(true); + + // Main layout + QBoxLayout *layout = taskbox->groupLayout(); + + QFormLayout* formLayout = new QFormLayout(); + formLayout->setHorizontalSpacing(10); + // Note: How can the split between columns be kept in the middle? + // formLayout->setFieldGrowthPolicy(QFormLayout::FieldGrowthPolicy::ExpandingFieldsGrow); + formLayout->setFormAlignment(Qt::AlignCenter); + + formLayout->addRow(QString::fromLatin1("Mode:"), modeSwitch); + formLayout->addRow(QString::fromLatin1("Result:"), valueResult); + layout->addLayout(formLayout); + + Content.emplace_back(taskbox); + + // engage the selectionObserver + attachSelection(); + + // Set selection style + Gui::Selection().setSelectionStyle(Gui::SelectionSingleton::SelectionStyle::GreedySelection); + + if(!App::GetApplication().getActiveTransaction()) + App::GetApplication().setActiveTransaction("Add Measurement"); + + + // Call invoke method delayed, otherwise the dialog might not be fully initialized + QTimer::singleShot(0, this, &TaskMeasure::invoke); +} + +TaskMeasure::~TaskMeasure(){ + Gui::Selection().setSelectionStyle(Gui::SelectionSingleton::SelectionStyle::NormalSelection); + detachSelection(); + qApp->removeEventFilter(this); +} + + +void TaskMeasure::modifyStandardButtons(QDialogButtonBox* box) { + + QPushButton* btn = box->button(QDialogButtonBox::Apply); + btn->setText(tr("Annotate")); + btn->setToolTip(tr("Press the Annotate button to add measurement to the document.")); + connect(btn, &QPushButton::released, this, &TaskMeasure::apply); + + // Disable button by default + btn->setEnabled(false); + btn = box->button(QDialogButtonBox::Abort); + btn->setText(QString::fromLatin1("Close")); + btn->setToolTip(tr("Press the Close button to exit.")); + + // Connect reset button + btn = box->button(QDialogButtonBox::Reset); + connect(btn, &QPushButton::released, this, &TaskMeasure::reset); +} + +bool canAnnotate(Measure::MeasureBase* obj) { + if (obj == nullptr) { + // null object, can't annotate this + return false; + } + + auto vpName = obj->getViewProviderName(); + // if there is not a vp, return false + if ((vpName == nullptr) || (vpName[0] == '\0')){ + return false; + } + + return true; +} + +void TaskMeasure::enableAnnotateButton(bool state) { + // if the task ui is not init yet we don't have a button box. + if (!this->buttonBox) { + return; + } + // Enable/Disable annotate button + auto btn = this->buttonBox->button(QDialogButtonBox::Apply); + btn->setEnabled(state); +} + +void TaskMeasure::setMeasureObject(Measure::MeasureBase* obj) { + _mMeasureObject = obj; +} + + +void TaskMeasure::update() { + + // Reset selection if the selected object is not valid + for(auto sel : Gui::Selection().getSelection()) { + App::DocumentObject* ob = sel.pObject; + App::DocumentObject* sub = ob->getSubObject(sel.SubName); + std::string mod = Base::Type::getModuleName(sub->getTypeId().getName()); + + if (!App::MeasureManager::hasMeasureHandler(mod.c_str())) { + Base::Console().Message("No measure handler available for geometry of module: %s\n", mod); + clearSelection(); + return; + } + } + + valueResult->setText(QString::asprintf("-")); + + // Get valid measure type + App::MeasureType *measureType(nullptr); + + + std::string mode = explicitMode ? modeSwitch->currentText().toStdString() : ""; + + App::MeasureSelection selection; + for (auto s : Gui::Selection().getSelection()) { + App::SubObjectT sub(s.pObject, s.SubName); + + App::MeasureSelectionItem item = { sub, Base::Vector3d(s.x, s.y, s.z) }; + selection.push_back(item); + } + + auto measureTypes = App::MeasureManager::getValidMeasureTypes(selection, mode); + if (measureTypes.size() > 0) { + measureType = measureTypes.front(); + } + + + if (!measureType) { + + // Note: If there's no valid measure type we might just restart the selection, + // however this requires enough coverage of measuretypes that we can access all of them + + // std::tuple sel = selection.back(); + // clearSelection(); + // addElement(measureModule.c_str(), get<0>(sel).c_str(), get<1>(sel).c_str()); + + // Reset measure object + if (!explicitMode) { + setModeSilent(nullptr); + } + removeObject(); + enableAnnotateButton(false); + return; + } + + // Update tool mode display + setModeSilent(measureType); + + if (!_mMeasureObject || measureType->measureObject != _mMeasureObject->getTypeId().getName()) { + // we don't already have a measureobject or it isn't the same type as the new one + removeObject(); + + App::Document *doc = App::GetApplication().getActiveDocument(); + if (measureType->isPython) { + Base::PyGILStateLocker lock; + auto pyMeasureClass = measureType->pythonClass; + + // Create a MeasurePython instance + auto featurePython = doc->addObject("Measure::MeasurePython", measureType->label.c_str()); + setMeasureObject((Measure::MeasureBase*)featurePython); + + // Create an instance of the pyMeasureClass, the classe's initializer sets the object as proxy + Py::Tuple args(1); + args.setItem(0, Py::asObject(featurePython->getPyObject())); + PyObject_CallObject(pyMeasureClass, args.ptr()); + } + else { + // Create measure object + setMeasureObject( + (Measure::MeasureBase*)doc->addObject(measureType->measureObject.c_str(), measureType->label.c_str()) + ); + } + } + + // we have a valid measure object so we can enable the annotate button + enableAnnotateButton(true); + + // Fill measure object's properties from selection + _mMeasureObject->parseSelection(selection); + + // Get result + valueResult->setText(_mMeasureObject->getResultString()); +} + +void TaskMeasure::close(){ + Control().closeDialog(); +} + + +void ensureGroup(Measure::MeasureBase* measurement) { + // Ensure measurement object is part of the measurements group + + const char* measurementGroupName = "Measurements"; + if (measurement == nullptr) { + return; + } + + App::Document* doc = App::GetApplication().getActiveDocument(); + App::DocumentObject* obj = doc->getObject(measurementGroupName); + if (!obj || !obj->isValid()) { + obj = doc->addObject("App::DocumentObjectGroup", measurementGroupName); + } + + auto group = static_cast(obj); + group->addObject(measurement); +} + + +// Runs after the dialog is created +void TaskMeasure::invoke() { + update(); +} + +bool TaskMeasure::apply(){ + ensureGroup(_mMeasureObject); + _mMeasureObject = nullptr; + reset(); + + // Commit transaction + App::GetApplication().closeActiveTransaction(); + App::GetApplication().setActiveTransaction("Add Measurement"); + return false; +} + +bool TaskMeasure::reject(){ + removeObject(); + close(); + + // Abort transaction + App::GetApplication().closeActiveTransaction(true); + return false; +} + +void TaskMeasure::reset() { + // Reset tool state + this->clearSelection(); + + // Should the explicit mode also be reset? + // setModeSilent(nullptr); + // explicitMode = false; + + this->update(); +} + + +void TaskMeasure::removeObject() { + if (_mMeasureObject == nullptr) { + return; + } + if (_mMeasureObject->isRemoving() ) { + return; + } + _mMeasureObject->getDocument()->removeObject (_mMeasureObject->getNameInDocument()); + setMeasureObject(nullptr); +} + +bool TaskMeasure::hasSelection(){ + return !Gui::Selection().getSelection().empty(); +} + +void TaskMeasure::clearSelection(){ + Gui::Selection().clearSelection(); +} + +void TaskMeasure::onSelectionChanged(const Gui::SelectionChanges& msg) +{ + // Skip non-relevant events + if (msg.Type != SelectionChanges::AddSelection && msg.Type != SelectionChanges::RmvSelection + && msg.Type != SelectionChanges::SetSelection && msg.Type != SelectionChanges::ClrSelection) { + + return; + } + + update(); +} + +bool TaskMeasure::eventFilter(QObject* obj, QEvent* event) { + + if (event->type() == QEvent::KeyPress) { + auto keyEvent = static_cast(event); + + if (keyEvent->key() == Qt::Key_Escape) { + + if (this->hasSelection()) { + this->reset(); + } else { + this->reject(); + } + + return true; + } + + if (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) { + this->apply(); + return true; + } + } + + return TaskDialog::eventFilter(obj, event); +} + +void TaskMeasure::onModeChanged(int index) { + explicitMode = (index != 0); + this->update(); +} + +void TaskMeasure::setModeSilent(App::MeasureType* mode) { + modeSwitch->blockSignals(true); + + if (mode == nullptr) { + modeSwitch->setCurrentIndex(0); + } + else { + modeSwitch->setCurrentText(QString::fromLatin1(mode->label.c_str())); + } + modeSwitch->blockSignals(false); +} + +// Get explicitly set measure type from the mode switch +App::MeasureType* TaskMeasure::getMeasureType() { + for (App::MeasureType* mType : App::MeasureManager::getMeasureTypes()) { + if (mType->label.c_str() == modeSwitch->currentText().toLatin1()) { + return mType; + } + } + return nullptr; +} diff --git a/src/Gui/TaskMeasure.h b/src/Gui/TaskMeasure.h new file mode 100644 index 0000000000..ed92e6ab6e --- /dev/null +++ b/src/Gui/TaskMeasure.h @@ -0,0 +1,87 @@ +/*************************************************************************** + * Copyright (c) 2023 David Friedli * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + + +#include +#include +#include +#include + +#include +#include + +#include + +#include "TaskView/TaskDialog.h" +#include "TaskView/TaskView.h" +#include "Selection.h" + +namespace Gui { + +class TaskMeasure : public TaskView::TaskDialog, public Gui::SelectionObserver { + +public: + TaskMeasure(); + ~TaskMeasure() override; + + void modifyStandardButtons(QDialogButtonBox* box) override; + QDialogButtonBox::StandardButtons getStandardButtons() const override { + return QDialogButtonBox::Apply | QDialogButtonBox::Abort | QDialogButtonBox::Reset; + } + + void invoke(); + void update(); + void close(); + bool apply(); + bool reject() override; + void reset(); + + bool hasSelection(); + void clearSelection(); + bool eventFilter(QObject* obj, QEvent* event) override; + void setMeasureObject(Measure::MeasureBase* obj); + +private: + QColumnView* dialog{nullptr}; + + void onSelectionChanged(const Gui::SelectionChanges& msg) override; + + Measure::MeasureBase *_mMeasureObject = nullptr; + + QLineEdit* valueResult{nullptr}; + QLabel* labelResult{nullptr}; + QComboBox* modeSwitch{nullptr}; + + void removeObject(); + void onModeChanged(int index); + void setModeSilent(App::MeasureType* mode); + App::MeasureType* getMeasureType(); + void enableAnnotateButton(bool state); + + // List of measure types + std::vector measureObjects; + + // Stores if the mode is explicitly set by the user or implicitly through the selection + bool explicitMode = false; + +}; + +} // namespace Gui diff --git a/src/Gui/Workbench.cpp b/src/Gui/Workbench.cpp index 09e54d4e4b..e284b42bb7 100644 --- a/src/Gui/Workbench.cpp +++ b/src/Gui/Workbench.cpp @@ -814,7 +814,7 @@ ToolBarItem* StdWorkbench::setupToolBars() const view->setCommand("View"); *view << "Std_ViewFitAll" << "Std_ViewFitSelection" << "Std_ViewGroup" << "Separator" << "Std_DrawStyle" << "Std_TreeViewActions" - << "Separator" << "Std_MeasureDistance"; + << "Separator" << "Std_MeasureDistance" << "Std_Measure"; // Individual views auto individualViews = new ToolBarItem(root, ToolBarItem::DefaultVisibility::Hidden); From e070b61cdc036e14fc51406cd045c5f8f237da72 Mon Sep 17 00:00:00 2001 From: wandererfan Date: Sun, 3 Mar 2024 09:50:42 -0500 Subject: [PATCH 10/11] [Measure] Change icon Co-Authored-By: Max Wilfinger <6246609+maxwxyz@users.noreply.github.com> --- src/Gui/Icons/umf-measurement.svg | 481 +++++++----------- .../Resources/icons/preferences-measure.svg | 481 +++++++----------- .../Gui/Resources/icons/umf-measurement.svg | 481 +++++++----------- 3 files changed, 585 insertions(+), 858 deletions(-) diff --git a/src/Gui/Icons/umf-measurement.svg b/src/Gui/Icons/umf-measurement.svg index e9971aed73..a12b6fb6b3 100644 --- a/src/Gui/Icons/umf-measurement.svg +++ b/src/Gui/Icons/umf-measurement.svg @@ -1,317 +1,226 @@ + + + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + id="defs2871"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="linearGradient10"> + id="stop10" /> + id="stop11" /> - + id="linearGradient5"> + id="stop19" /> + id="stop20" /> + + + + + + + + + + + + + + + + id="metadata2874"> image/svg+xml - + + + [maxwxyz] + + + https://www.freecad.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/ + + + FreeCAD LGPL2+ + + + 2024 - - - - - - - - - - - - - - - + id="layer3" + style="display:inline"> + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/Measure/Gui/Resources/icons/preferences-measure.svg b/src/Mod/Measure/Gui/Resources/icons/preferences-measure.svg index e9971aed73..a12b6fb6b3 100644 --- a/src/Mod/Measure/Gui/Resources/icons/preferences-measure.svg +++ b/src/Mod/Measure/Gui/Resources/icons/preferences-measure.svg @@ -1,317 +1,226 @@ + + + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + id="defs2871"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="linearGradient10"> + id="stop10" /> + id="stop11" /> - + id="linearGradient5"> + id="stop19" /> + id="stop20" /> + + + + + + + + + + + + + + + + id="metadata2874"> image/svg+xml - + + + [maxwxyz] + + + https://www.freecad.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/ + + + FreeCAD LGPL2+ + + + 2024 - - - - - - - - - - - - - - - + id="layer3" + style="display:inline"> + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/Measure/Gui/Resources/icons/umf-measurement.svg b/src/Mod/Measure/Gui/Resources/icons/umf-measurement.svg index e9971aed73..a12b6fb6b3 100644 --- a/src/Mod/Measure/Gui/Resources/icons/umf-measurement.svg +++ b/src/Mod/Measure/Gui/Resources/icons/umf-measurement.svg @@ -1,317 +1,226 @@ + + + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + id="defs2871"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + id="linearGradient10"> + id="stop10" /> + id="stop11" /> - + id="linearGradient5"> + id="stop19" /> + id="stop20" /> + + + + + + + + + + + + + + + + id="metadata2874"> image/svg+xml - + + + [maxwxyz] + + + https://www.freecad.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/ + + + FreeCAD LGPL2+ + + + 2024 - - - - - - - - - - - - - - - + id="layer3" + style="display:inline"> + + + + + + + + + + + + + + + + + + + + + + + + + + + From 7ff4da5a274212ac7bb4b8ae4e02f5d5c13ded8e Mon Sep 17 00:00:00 2001 From: hlorus <64740362+hlorus@users.noreply.github.com> Date: Mon, 22 Apr 2024 08:11:21 +0200 Subject: [PATCH 11/11] [Part/PD] Remove measure tools --- src/Mod/Part/Gui/AppPartGui.cpp | 6 - src/Mod/Part/Gui/CMakeLists.txt | 9 - src/Mod/Part/Gui/Command.cpp | 227 --- src/Mod/Part/Gui/DlgSettingsMeasure.cpp | 91 - src/Mod/Part/Gui/DlgSettingsMeasure.h | 58 - src/Mod/Part/Gui/DlgSettingsMeasure.ui | 279 --- src/Mod/Part/Gui/Resources/Part.qrc | 9 - .../icons/measure/Part_Measure_Angular.svg | 215 -- .../icons/measure/Part_Measure_Linear.svg | 135 -- .../icons/measure/Part_Measure_Refresh.svg | 659 ------ .../measure/Part_Measure_Step_Active.svg | 128 -- .../icons/measure/Part_Measure_Step_Done.svg | 113 -- .../icons/measure/Part_Measure_Toggle_3D.svg | 150 -- .../measure/Part_Measure_Toggle_Delta.svg | 143 -- src/Mod/Part/Gui/TaskDimension.cpp | 1783 ----------------- src/Mod/Part/Gui/TaskDimension.h | 337 ---- src/Mod/Part/Gui/Workbench.cpp | 24 - src/Mod/Part/Gui/WorkbenchManipulator.cpp | 16 - src/Mod/Part/Gui/WorkbenchManipulator.h | 7 - src/Mod/PartDesign/Gui/Workbench.cpp | 27 - 20 files changed, 4416 deletions(-) delete mode 100644 src/Mod/Part/Gui/DlgSettingsMeasure.cpp delete mode 100644 src/Mod/Part/Gui/DlgSettingsMeasure.h delete mode 100644 src/Mod/Part/Gui/DlgSettingsMeasure.ui delete mode 100644 src/Mod/Part/Gui/Resources/icons/measure/Part_Measure_Angular.svg delete mode 100644 src/Mod/Part/Gui/Resources/icons/measure/Part_Measure_Linear.svg delete mode 100644 src/Mod/Part/Gui/Resources/icons/measure/Part_Measure_Refresh.svg delete mode 100644 src/Mod/Part/Gui/Resources/icons/measure/Part_Measure_Step_Active.svg delete mode 100644 src/Mod/Part/Gui/Resources/icons/measure/Part_Measure_Step_Done.svg delete mode 100644 src/Mod/Part/Gui/Resources/icons/measure/Part_Measure_Toggle_3D.svg delete mode 100644 src/Mod/Part/Gui/Resources/icons/measure/Part_Measure_Toggle_Delta.svg delete mode 100644 src/Mod/Part/Gui/TaskDimension.cpp delete mode 100644 src/Mod/Part/Gui/TaskDimension.h diff --git a/src/Mod/Part/Gui/AppPartGui.cpp b/src/Mod/Part/Gui/AppPartGui.cpp index 4aef8a4efe..a1895fe0e9 100644 --- a/src/Mod/Part/Gui/AppPartGui.cpp +++ b/src/Mod/Part/Gui/AppPartGui.cpp @@ -39,9 +39,7 @@ #include "PropertyEnumAttacherItem.h" #include "DlgSettings3DViewPartImp.h" #include "DlgSettingsGeneral.h" -#include "DlgSettingsMeasure.h" #include "DlgSettingsObjectColor.h" -#include "TaskDimension.h" #include "SoBrepEdgeSet.h" #include "SoBrepFaceSet.h" #include "SoBrepPointSet.h" @@ -136,7 +134,6 @@ PyMOD_INIT_FUNC(PartGui) Gui::BitmapFactory().addPath(QString::fromLatin1(":/icons/booleans")); Gui::BitmapFactory().addPath(QString::fromLatin1(":/icons/create")); - Gui::BitmapFactory().addPath(QString::fromLatin1(":/icons/measure")); Gui::BitmapFactory().addPath(QString::fromLatin1(":/icons/parametric")); Gui::BitmapFactory().addPath(QString::fromLatin1(":/icons/tools")); @@ -212,8 +209,6 @@ PyMOD_INIT_FUNC(PartGui) PartGui::ViewProviderRuledSurface ::init(); PartGui::ViewProviderFace ::init(); PartGui::ViewProviderProjectOnSurface ::init(); - PartGui::DimensionLinear ::initClass(); - PartGui::DimensionAngular ::initClass(); PartGui::Workbench ::init(); auto manip = std::make_shared(); @@ -237,7 +232,6 @@ PyMOD_INIT_FUNC(PartGui) (void)new Gui::PrefPageProducer(QT_TRANSLATE_NOOP("QObject", "Part/Part Design")); (void)new Gui::PrefPageProducer(QT_TRANSLATE_NOOP("QObject", "Part/Part Design")); (void)new Gui::PrefPageProducer(QT_TRANSLATE_NOOP("QObject", "Part/Part Design")); - (void)new Gui::PrefPageProducer(QT_TRANSLATE_NOOP("QObject", "Part/Part Design")); (void)new Gui::PrefPageProducer(QT_TRANSLATE_NOOP("QObject", "Import-Export")); (void)new Gui::PrefPageProducer(QT_TRANSLATE_NOOP("QObject", "Import-Export")); Gui::ViewProviderBuilder::add( diff --git a/src/Mod/Part/Gui/CMakeLists.txt b/src/Mod/Part/Gui/CMakeLists.txt index 7cdd529d43..9475aa01fd 100644 --- a/src/Mod/Part/Gui/CMakeLists.txt +++ b/src/Mod/Part/Gui/CMakeLists.txt @@ -57,7 +57,6 @@ set(PartGui_UIC_SRCS DlgRevolution.ui DlgSettings3DViewPart.ui DlgSettingsGeneral.ui - DlgSettingsMeasure.ui DlgSettingsObjectColor.ui DlgProjectionOnSurface.ui SectionCutting.ui @@ -133,15 +132,9 @@ SET(PartGui_SRCS DlgSettingsGeneral.cpp DlgSettingsGeneral.h DlgSettingsGeneral.ui - DlgSettingsMeasure.cpp - DlgSettingsMeasure.h - DlgSettingsMeasure.ui DlgSettingsObjectColor.cpp DlgSettingsObjectColor.h DlgSettingsObjectColor.ui - DlgSettingsMeasure.cpp - DlgSettingsMeasure.h - DlgSettingsMeasure.ui DlgProjectionOnSurface.cpp DlgProjectionOnSurface.h DlgProjectionOnSurface.ui @@ -248,8 +241,6 @@ SET(PartGui_SRCS TaskSweep.ui TaskThickness.cpp TaskThickness.h - TaskDimension.cpp - TaskDimension.h TaskCheckGeometry.cpp TaskCheckGeometry.h TaskAttacher.h diff --git a/src/Mod/Part/Gui/Command.cpp b/src/Mod/Part/Gui/Command.cpp index 0821411a01..422daa40da 100644 --- a/src/Mod/Part/Gui/Command.cpp +++ b/src/Mod/Part/Gui/Command.cpp @@ -63,7 +63,6 @@ #include "Mirroring.h" #include "SectionCutting.h" #include "TaskCheckGeometry.h" -#include "TaskDimension.h" #include "TaskLoft.h" #include "TaskShapeBuilder.h" #include "TaskSweep.h" @@ -2123,225 +2122,6 @@ bool CmdColorPerFace::isActive() return (hasActiveDocument() && !Gui::Control().activeDialog() && objectSelected); } -//=========================================================================== -// Part_Measure_Linear -//=========================================================================== - -DEF_STD_CMD_A(CmdMeasureLinear) - -CmdMeasureLinear::CmdMeasureLinear() - : Command("Part_Measure_Linear") -{ - sAppModule = "Part"; - sGroup = QT_TR_NOOP("Part"); - sMenuText = QT_TR_NOOP("Measure Linear"); - sToolTipText = QT_TR_NOOP("Measure the linear distance between two points;\n" - "if edges or faces are picked, it will measure\n" - "between two vertices of them."); - sWhatsThis = "Part_Measure_Linear"; - sStatusTip = sToolTipText; - sPixmap = "Part_Measure_Linear"; -} - -void CmdMeasureLinear::activated(int iMsg) -{ - Q_UNUSED(iMsg); - PartGui::goDimensionLinearRoot(); -} - -bool CmdMeasureLinear::isActive() -{ - return hasActiveDocument(); -} - -//=========================================================================== -// Part_Measure_Angular -//=========================================================================== - -DEF_STD_CMD_A(CmdMeasureAngular) - -CmdMeasureAngular::CmdMeasureAngular() - : Command("Part_Measure_Angular") -{ - sAppModule = "Part"; - sGroup = QT_TR_NOOP("Part"); - sMenuText = QT_TR_NOOP("Measure Angular"); - sToolTipText = QT_TR_NOOP("Measure the angle between two edges."); - sWhatsThis = "Part_Measure_Angular"; - sStatusTip = sToolTipText; - sPixmap = "Part_Measure_Angular"; -} - -void CmdMeasureAngular::activated(int iMsg) -{ - Q_UNUSED(iMsg); - PartGui::goDimensionAngularRoot(); -} - -bool CmdMeasureAngular::isActive() -{ - return hasActiveDocument(); -} - -//=========================================================================== -// Part_Measure_Refresh -//=========================================================================== - -DEF_STD_CMD_A(CmdMeasureRefresh) - -CmdMeasureRefresh::CmdMeasureRefresh() - : Command("Part_Measure_Refresh") -{ - sAppModule = "Part"; - sGroup = QT_TR_NOOP("Part"); - sMenuText = QT_TR_NOOP("Refresh"); - sToolTipText = QT_TR_NOOP("Recalculate the dimensions\n" - "if the measured points have moved."); - sWhatsThis = "Part_Measure_Refresh"; - sStatusTip = sToolTipText; - sPixmap = "Part_Measure_Refresh"; -} - -void CmdMeasureRefresh::activated(int iMsg) -{ - Q_UNUSED(iMsg); - PartGui::refreshDimensions(); -} - -bool CmdMeasureRefresh::isActive() -{ - return hasActiveDocument(); -} - -//=========================================================================== -// Part_Measure_Clear_All -//=========================================================================== - -DEF_STD_CMD_A(CmdMeasureClearAll) - -CmdMeasureClearAll::CmdMeasureClearAll() - : Command("Part_Measure_Clear_All") -{ - sAppModule = "Part"; - sGroup = QT_TR_NOOP("Part"); - sMenuText = QT_TR_NOOP("Clear All"); - sToolTipText = QT_TR_NOOP("Clear all dimensions from the active 3D view."); - sWhatsThis = "Part_Measure_Clear_All"; - sStatusTip = sToolTipText; - sPixmap = "Part_Measure_Clear_All"; -} - -void CmdMeasureClearAll::activated(int iMsg) -{ - Q_UNUSED(iMsg); - PartGui::eraseAllDimensions(); -} - -bool CmdMeasureClearAll::isActive() -{ - return hasActiveDocument(); -} - -//=========================================================================== -// Part_Measure_Toggle_All -//=========================================================================== - -DEF_STD_CMD_A(CmdMeasureToggleAll) - -CmdMeasureToggleAll::CmdMeasureToggleAll() - : Command("Part_Measure_Toggle_All") -{ - sAppModule = "Part"; - sGroup = QT_TR_NOOP("Part"); - sMenuText = QT_TR_NOOP("Toggle All"); - sToolTipText = QT_TR_NOOP("Toggle on and off " - "all currently visible dimensions,\n" - "direct, orthogonal, and angular."); - sWhatsThis = "Part_Measure_Toggle_All"; - sStatusTip = sToolTipText; - sPixmap = "Part_Measure_Toggle_All"; -} - -void CmdMeasureToggleAll::activated(int iMsg) -{ - Q_UNUSED(iMsg); - ParameterGrp::handle group = App::GetApplication().GetUserParameter(). - GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("View"); - bool visibility = group->GetBool("DimensionsVisible", true); - if (visibility) - group->SetBool("DimensionsVisible", false); - else - group->SetBool("DimensionsVisible", true); -} - -bool CmdMeasureToggleAll::isActive() -{ - return hasActiveDocument(); -} - -//=========================================================================== -// Part_Measure_Toggle_3D -//=========================================================================== - -DEF_STD_CMD_A(CmdMeasureToggle3d) - -CmdMeasureToggle3d::CmdMeasureToggle3d() - : Command("Part_Measure_Toggle_3D") -{ - sAppModule = "Part"; - sGroup = QT_TR_NOOP("Part"); - sMenuText = QT_TR_NOOP("Toggle 3D"); - sToolTipText = QT_TR_NOOP("Toggle on and off " - "all direct dimensions,\n" - "including angular."); - sWhatsThis = "Part_Measure_Toggle_3D"; - sStatusTip = sToolTipText; - sPixmap = "Part_Measure_Toggle_3D"; -} - -void CmdMeasureToggle3d::activated(int iMsg) -{ - Q_UNUSED(iMsg); - PartGui::toggle3d(); -} - -bool CmdMeasureToggle3d::isActive() -{ - return hasActiveDocument(); -} - -//=========================================================================== -// Part_Measure_Toggle_Delta -//=========================================================================== - -DEF_STD_CMD_A(CmdMeasureToggleDelta) - -CmdMeasureToggleDelta::CmdMeasureToggleDelta() - : Command("Part_Measure_Toggle_Delta") -{ - sAppModule = "Part"; - sGroup = QT_TR_NOOP("Part"); - sMenuText = QT_TR_NOOP("Toggle Delta"); - sToolTipText = QT_TR_NOOP("Toggle on and off " - "all orthogonal dimensions,\n" - "meaning that a direct dimension will be decomposed\n" - "into its X, Y, and Z components."); - sWhatsThis = "Part_Measure_Toggle_Delta"; - sStatusTip = sToolTipText; - sPixmap = "Part_Measure_Toggle_Delta"; -} - -void CmdMeasureToggleDelta::activated(int iMsg) -{ - Q_UNUSED(iMsg); - PartGui::toggleDelta(); -} - -bool CmdMeasureToggleDelta::isActive() -{ - return hasActiveDocument(); -} - //=========================================================================== // Part_BoxSelection //=========================================================================== @@ -2486,13 +2266,6 @@ void CreatePartCommands() rcCmdMgr.addCommand(new CmdPartThickness()); rcCmdMgr.addCommand(new CmdCheckGeometry()); rcCmdMgr.addCommand(new CmdColorPerFace()); - rcCmdMgr.addCommand(new CmdMeasureLinear()); - rcCmdMgr.addCommand(new CmdMeasureAngular()); - rcCmdMgr.addCommand(new CmdMeasureRefresh()); - rcCmdMgr.addCommand(new CmdMeasureClearAll()); - rcCmdMgr.addCommand(new CmdMeasureToggleAll()); - rcCmdMgr.addCommand(new CmdMeasureToggle3d()); - rcCmdMgr.addCommand(new CmdMeasureToggleDelta()); rcCmdMgr.addCommand(new CmdBoxSelection()); rcCmdMgr.addCommand(new CmdPartProjectionOnSurface()); rcCmdMgr.addCommand(new CmdPartSectionCut()); diff --git a/src/Mod/Part/Gui/DlgSettingsMeasure.cpp b/src/Mod/Part/Gui/DlgSettingsMeasure.cpp deleted file mode 100644 index 7034473b74..0000000000 --- a/src/Mod/Part/Gui/DlgSettingsMeasure.cpp +++ /dev/null @@ -1,91 +0,0 @@ -/*************************************************************************** - * Copyright (c) 2022 * - * * - * 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" - -#include - -#include "DlgSettingsMeasure.h" -#include "ui_DlgSettingsMeasure.h" - - -using namespace PartGui; - -DlgSettingsMeasure::DlgSettingsMeasure(QWidget* parent) - : PreferencePage(parent) , ui(new Ui_DlgSettingsMeasure) -{ - ui->setupUi(this); - connect(ui->pushButtonRefresh, &QPushButton::clicked, this, &DlgSettingsMeasure::onMeasureRefresh); -} - -/** - * Destroys the object and frees any allocated resources - */ -DlgSettingsMeasure::~DlgSettingsMeasure() = default; - -void DlgSettingsMeasure::saveSettings() -{ - ui->dim3dColorButton->onSave(); - ui->dimDeltaColorButton->onSave(); - ui->dimAngularColorButton->onSave(); - - ui->fontSizeSpinBox->onSave(); - ui->fontNameComboBox->onSave(); - - ui->fontStyleBoldCheckBox->onSave(); - ui->fontStyleItalicCheckBox->onSave(); -} - -void DlgSettingsMeasure::loadSettings() -{ - ui->dim3dColorButton->onRestore(); - ui->dimDeltaColorButton->onRestore(); - ui->dimAngularColorButton->onRestore(); - - ui->fontSizeSpinBox->onRestore(); - ui->fontNameComboBox->onRestore(); - ui->fontNameComboBox->addItems(QStringList({QString::fromUtf8("defaultFont")})); - - ui->fontStyleBoldCheckBox->onRestore(); - ui->fontStyleItalicCheckBox->onRestore(); -} - -/** - * Sets the strings of the subwidgets using the current language. - */ -void DlgSettingsMeasure::changeEvent(QEvent *e) -{ - if (e->type() == QEvent::LanguageChange) { - ui->retranslateUi(this); - } - else { - QWidget::changeEvent(e); - } -} - -void DlgSettingsMeasure::onMeasureRefresh() -{ - DlgSettingsMeasure::saveSettings(); - Gui::Command::runCommand(Gui::Command::Gui, "Gui.runCommand('Part_Measure_Refresh',0)"); -} - -#include "moc_DlgSettingsMeasure.cpp" diff --git a/src/Mod/Part/Gui/DlgSettingsMeasure.h b/src/Mod/Part/Gui/DlgSettingsMeasure.h deleted file mode 100644 index 2f347ef2c2..0000000000 --- a/src/Mod/Part/Gui/DlgSettingsMeasure.h +++ /dev/null @@ -1,58 +0,0 @@ -/*************************************************************************** - * Copyright (c) 2022 * - * * - * 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 PARTGUI_DIALOG_DLGSETTINGSMEASURE_H -#define PARTGUI_DIALOG_DLGSETTINGSMEASURE_H - -#include -#include - -namespace PartGui { -class Ui_DlgSettingsMeasure; - -/** - * The DlgSettingsMeasure class implements a preference page to change color - * and font settings for Measure Dimensions - */ -class DlgSettingsMeasure : public Gui::Dialog::PreferencePage -{ - Q_OBJECT - -public: - explicit DlgSettingsMeasure(QWidget* parent = nullptr); - ~DlgSettingsMeasure() override; - - void saveSettings() override; - void loadSettings() override; - -protected: - void changeEvent(QEvent *e) override; - -private: - std::unique_ptr ui; - void onMeasureRefresh(); -}; - -} // namespace PartGui - -#endif // PARTGUI_DIALOG_DLGSETTINGSMEASURE_H diff --git a/src/Mod/Part/Gui/DlgSettingsMeasure.ui b/src/Mod/Part/Gui/DlgSettingsMeasure.ui deleted file mode 100644 index 27ce62964d..0000000000 --- a/src/Mod/Part/Gui/DlgSettingsMeasure.ui +++ /dev/null @@ -1,279 +0,0 @@ - - - PartGui::DlgSettingsMeasure - - - - 0 - 0 - 400 - 282 - - - - - 0 - 0 - - - - Measure - - - - - - Measurement settings - - - - - - - - - 255 - 0 - 0 - - - - Dimensions3dColor - - - Mod/Part - - - - - - - 3D color - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 255 - 0 - - - - DimensionsDeltaColor - - - Mod/Part - - - - - - - Delta color - - - - - - - - 0 - 0 - 255 - - - - DimensionsAngularColor - - - Mod/Part - - - - - - - Angular color - - - - - - - - - - - - - - - 80 - 28 - - - - 30 - - - DimensionsFontSize - - - Mod/Part - - - - - - - Font size - - - - - - - - 75 - true - - - - Bold - - - DimensionsFontStyleBold - - - Mod/Part - - - - - - - - true - - - - Italic - - - DimensionsFontStyleItalic - - - Mod/Part - - - - - - - defaultFont - - - DimensionsFontName - - - Mod/Part - - - - - - - Font name - - - - - - - - - - - Refresh existing measurements - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - Gui::ColorButton - QPushButton -
Gui/Widgets.h
-
- - Gui::PrefSpinBox - QSpinBox -
Gui/PrefWidgets.h
-
- - Gui::PrefColorButton - Gui::ColorButton -
Gui/PrefWidgets.h
-
- - Gui::PrefCheckBox - QCheckBox -
Gui/PrefWidgets.h
-
- - Gui::PrefFontBox - QFontComboBox -
Gui/PrefWidgets.h
-
-
- - -
diff --git a/src/Mod/Part/Gui/Resources/Part.qrc b/src/Mod/Part/Gui/Resources/Part.qrc index ffde8218b6..e4b86645ea 100644 --- a/src/Mod/Part/Gui/Resources/Part.qrc +++ b/src/Mod/Part/Gui/Resources/Part.qrc @@ -30,15 +30,6 @@ icons/create/Part_Torus.svg icons/create/Part_Tube.svg - - icons/measure/Part_Measure_Angular.svg - icons/measure/Part_Measure_Linear.svg - icons/measure/Part_Measure_Refresh.svg - icons/measure/Part_Measure_Step_Active.svg - icons/measure/Part_Measure_Step_Done.svg - icons/measure/Part_Measure_Toggle_3D.svg - icons/measure/Part_Measure_Toggle_Delta.svg - icons/parametric/Part_Box_Parametric.svg icons/parametric/Part_Circle_Parametric.svg diff --git a/src/Mod/Part/Gui/Resources/icons/measure/Part_Measure_Angular.svg b/src/Mod/Part/Gui/Resources/icons/measure/Part_Measure_Angular.svg deleted file mode 100644 index f0ffba950b..0000000000 --- a/src/Mod/Part/Gui/Resources/icons/measure/Part_Measure_Angular.svg +++ /dev/null @@ -1,215 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - [blobfish] - - - Part_Measure_Angular - 2013-12-17 - https://www.freecad.org/wiki/index.php?title=Artwork - - - FreeCAD - - - FreeCAD/src/Mod/Part/Gui/Resources/icons/Part_Measure_Angular.svg - - - FreeCAD LGPL2+ - - - https://www.gnu.org/copyleft/lesser.html - - - [agryson] Alexander Gryson - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Mod/Part/Gui/Resources/icons/measure/Part_Measure_Linear.svg b/src/Mod/Part/Gui/Resources/icons/measure/Part_Measure_Linear.svg deleted file mode 100644 index bde7662177..0000000000 --- a/src/Mod/Part/Gui/Resources/icons/measure/Part_Measure_Linear.svg +++ /dev/null @@ -1,135 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - [blobfish] - - - Part_Measure_Linear - 2013-12-17 - https://www.freecad.org/wiki/index.php?title=Artwork - - - FreeCAD - - - FreeCAD/src/Mod/Part/Gui/Resources/icons/Part_Measure_Linear.svg - - - FreeCAD LGPL2+ - - - https://www.gnu.org/copyleft/lesser.html - - - [agryson] Alexander Gryson - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Mod/Part/Gui/Resources/icons/measure/Part_Measure_Refresh.svg b/src/Mod/Part/Gui/Resources/icons/measure/Part_Measure_Refresh.svg deleted file mode 100644 index b4b78f6d91..0000000000 --- a/src/Mod/Part/Gui/Resources/icons/measure/Part_Measure_Refresh.svg +++ /dev/null @@ -1,659 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - [blobfish] - - - Part_Measure_Linear - 2013-12-17 - https://www.freecad.org/wiki/index.php?title=Artwork - - - FreeCAD - - - FreeCAD/src/Mod/Part/Gui/Resources/icons/Part_Measure_Linear.svg - - - FreeCAD LGPL2+ - - - https://www.gnu.org/copyleft/lesser.html - - - [agryson] Alexander Gryson - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Mod/Part/Gui/Resources/icons/measure/Part_Measure_Step_Active.svg b/src/Mod/Part/Gui/Resources/icons/measure/Part_Measure_Step_Active.svg deleted file mode 100644 index a52ce279de..0000000000 --- a/src/Mod/Part/Gui/Resources/icons/measure/Part_Measure_Step_Active.svg +++ /dev/null @@ -1,128 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - [blobfish] - - - Part_Measure_Step_Active - 2013-12-17 - https://www.freecad.org/wiki/index.php?title=Artwork - - - FreeCAD - - - FreeCAD/src/Mod/Part/Gui/Resources/icons/Part_Measure_Step_Active.svg - - - FreeCAD LGPL2+ - - - https://www.gnu.org/copyleft/lesser.html - - - [agryson] Alexander Gryson - - - - - - - - - - - - - - - diff --git a/src/Mod/Part/Gui/Resources/icons/measure/Part_Measure_Step_Done.svg b/src/Mod/Part/Gui/Resources/icons/measure/Part_Measure_Step_Done.svg deleted file mode 100644 index e9edc1e744..0000000000 --- a/src/Mod/Part/Gui/Resources/icons/measure/Part_Measure_Step_Done.svg +++ /dev/null @@ -1,113 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - [blobfish] - - - Part_Measure_Step_Done - 2013-12-17 - https://www.freecad.org/wiki/index.php?title=Artwork - - - FreeCAD - - - FreeCAD/src/Mod/Part/Gui/Resources/icons/Part_Measure_Step_Done.svg - - - FreeCAD LGPL2+ - - - https://www.gnu.org/copyleft/lesser.html - - - [agryson] Alexander Gryson - - - - - - - - - - - - - diff --git a/src/Mod/Part/Gui/Resources/icons/measure/Part_Measure_Toggle_3D.svg b/src/Mod/Part/Gui/Resources/icons/measure/Part_Measure_Toggle_3D.svg deleted file mode 100644 index a74d1c11bc..0000000000 --- a/src/Mod/Part/Gui/Resources/icons/measure/Part_Measure_Toggle_3D.svg +++ /dev/null @@ -1,150 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - [blobfish] - - - Part_Measure_Toggle_3d - 2013-12-17 - https://www.freecad.org/wiki/index.php?title=Artwork - - - FreeCAD - - - FreeCAD/src/Mod/Part/Gui/Resources/icons/Part_Measure_Toggle_3d.svg - - - FreeCAD LGPL2+ - - - https://www.gnu.org/copyleft/lesser.html - - - [agryson] Alexander Gryson - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Mod/Part/Gui/Resources/icons/measure/Part_Measure_Toggle_Delta.svg b/src/Mod/Part/Gui/Resources/icons/measure/Part_Measure_Toggle_Delta.svg deleted file mode 100644 index 4d9f791774..0000000000 --- a/src/Mod/Part/Gui/Resources/icons/measure/Part_Measure_Toggle_Delta.svg +++ /dev/null @@ -1,143 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - [blobfish] - - - Part_Measure_Toggle_Delta - 2013-12-17 - https://www.freecad.org/wiki/index.php?title=Artwork - - - FreeCAD - - - FreeCAD/src/Mod/Part/Gui/Resources/icons/Part_Measure_Toggle_Delta.svg - - - FreeCAD LGPL2+ - - - https://www.gnu.org/copyleft/lesser.html - - - [agryson] Alexander Gryson - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Mod/Part/Gui/TaskDimension.cpp b/src/Mod/Part/Gui/TaskDimension.cpp deleted file mode 100644 index aba9cff004..0000000000 --- a/src/Mod/Part/Gui/TaskDimension.cpp +++ /dev/null @@ -1,1783 +0,0 @@ -/*************************************************************************** - * Copyright (c) 2013 Thomas Anderson * - * * - * 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 _PreCpmp_ -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include - -# include -# include -# include -# include -# include - -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "TaskDimension.h" - - -namespace sp = std::placeholders; - -static bool _MeasureInfoInited; - -static void slotDeleteDocument(const App::Document &doc); - -struct MeasureInfo { - PartGui::DimSelections sel1; - PartGui::DimSelections sel2; - bool linear; - MeasureInfo(const PartGui::DimSelections &sel1, const PartGui::DimSelections &sel2, bool linear) - :sel1(sel1),sel2(sel2),linear(linear) - { - if(!_MeasureInfoInited) { - _MeasureInfoInited = true; - //NOLINTBEGIN - App::GetApplication().signalDeleteDocument.connect(std::bind(slotDeleteDocument, sp::_1)); - //NOLINTEND - } - } -}; -static std::map > _Measures; - -static void slotDeleteDocument(const App::Document &doc) { - _Measures.erase(doc.getName()); -} - -bool PartGui::getShapeFromStrings(TopoDS_Shape &shapeOut, const std::string &doc, const std::string &object, const std::string &sub, Base::Matrix4D *mat) -{ - App::Document *docPointer = App::GetApplication().getDocument(doc.c_str()); - if (!docPointer) - return false; - App::DocumentObject *objectPointer = docPointer->getObject(object.c_str()); - if (!objectPointer) - return false; - shapeOut = Part::Feature::getShape(objectPointer,sub.c_str(),true,mat); - if (shapeOut.IsNull()) - return false; - return true; -} - -bool PartGui::evaluateLinearPreSelection(TopoDS_Shape &shape1, TopoDS_Shape &shape2) -{ - std::vector selections = Gui::Selection().getSelection(nullptr, Gui::ResolveMode::NoResolve); - if (selections.size() != 2) - return false; - std::vector::iterator it; - std::vector shapes; - DimSelections sels[2]; - - int i=0; - for (it = selections.begin(); it != selections.end(); ++it) - { - TopoDS_Shape shape = Part::Feature::getShape(it->pObject,it->SubName,true); - if (shape.IsNull()) - break; - shapes.push_back(shape); - sels[i].selections.emplace_back(); - auto &sel = sels[i].selections[0]; - ++i; - sel.documentName = it->DocName; - sel.objectName = it->FeatName; - sel.subObjectName = it->SubName; - } - - if (shapes.size() != 2) - return false; - - shape1 = shapes.front(); - shape2 = shapes.back(); - - auto doc = App::GetApplication().getActiveDocument(); - if(doc) - _Measures[doc->getName()].emplace_back(sels[0],sels[1],true); - return true; -} - -void PartGui::goDimensionLinearRoot() -{ - PartGui::ensureSomeDimensionVisible(); - - TopoDS_Shape shape1, shape2; - if(evaluateLinearPreSelection(shape1, shape2)) - { - goDimensionLinearNoTask(shape1, shape2); - Gui::Selection().clearSelection(); - } - else - { - Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog(); - if (!dlg) - { - Gui::Selection().clearSelection(); - dlg = new PartGui::TaskMeasureLinear(); - } - Gui::Control().showDialog(dlg); - } -} - -void PartGui::goDimensionLinearNoTask(const TopoDS_Shape &shape1, const TopoDS_Shape &shape2) -{ - //Warning: BRepExtrema_DistShapeShape solution array is NOT 0 based. - BRepExtrema_DistShapeShape measure(shape1, shape2); - if (!measure.IsDone() || measure.NbSolution() < 1) - return; - - dumpLinearResults(measure); - addLinearDimensions(measure); - - //if we ever make this a class add viewer to member. - Gui::View3DInventorViewer *viewer = getViewer(); - if (!viewer) - return; -} - -void PartGui::dumpLinearResults(const BRepExtrema_DistShapeShape &measure) -{ - std::ostringstream out; - //switch to initializer list when switch to c++11 - std::vector typeNames; - typeNames.resize(3); - typeNames[0] = "Vertex"; - typeNames[1] = "Edge"; - typeNames[2] = "Face"; - - Base::Quantity quantity(measure.Value(), Base::Unit::Length); - out << std::endl<< std::setprecision(std::numeric_limits::digits10 + 1) << "distance = " << - measure.Value() << "mm unit distance = " << quantity.getUserString().toUtf8().constData() << std::endl << - "solution count: " << measure.NbSolution() << std::endl; - - for (int index = 1; index < measure.NbSolution() + 1; ++index) //not zero based. - { - gp_Pnt point1 = measure.PointOnShape1(index); - gp_Pnt point2 = measure.PointOnShape2(index); - out << " solution " << index << ":" << std::endl << std::setprecision(std::numeric_limits::digits10 + 1) << - " point1 " << point1.X() << " " << point1.Y() << " " << point1.Z() << std::endl << - " point2 " << point2.X() << " " << point2.Y() << " " << point2.Z() << std::endl << - " DeltaX " << fabs(point2.X() - point1.X()) << std::endl << - " DeltaY " << fabs(point2.Y() - point1.Y()) << std::endl << - " DeltaZ " << fabs(point2.Z() - point1.Z()) << std::endl << - " shape type on object1 is: " << typeNames.at(measure.SupportTypeShape1(index)) << std::endl << - " shape type on object2 is: " << typeNames.at(measure.SupportTypeShape2(index)) << std::endl; - } - out << std::endl; - Base::Console().Message(out.str().c_str()); -} - -auto PartGui::getDimensionsFontName() -{ - ParameterGrp::handle group = App::GetApplication().GetUserParameter().GetGroup("BaseApp/Preferences/Mod/Part"); - std::string fontName = group->GetASCII("DimensionsFontName", "defaultFont"); - // if there is only italic, we must output ":Italic", otherwise ":Bold Italic" - if (group->GetBool("DimensionsFontStyleBold")) { - fontName = fontName + " :Bold"; - if (group->GetBool("DimensionsFontStyleItalic")) - fontName = fontName + " Italic"; - } - else { - if (group->GetBool("DimensionsFontStyleItalic")) - fontName = fontName + " :Italic"; - } - return fontName; -} - -auto PartGui::getDimensionsFontSize() -{ - ParameterGrp::handle group = App::GetApplication().GetUserParameter().GetGroup("BaseApp/Preferences/Mod/Part"); - return group->GetInt("DimensionsFontSize", 30); -} - -Gui::View3DInventorViewer * PartGui::getViewer() -{ - Gui::Document *doc = Gui::Application::Instance->activeDocument(); - if (!doc) - return nullptr; - Gui::View3DInventor *view = dynamic_cast(doc->getActiveView()); - if (!view) - return nullptr; - Gui::View3DInventorViewer *viewer = view->getViewer(); - if (!viewer) - return nullptr; - return viewer; -} - -void PartGui::addLinearDimensions(const BRepExtrema_DistShapeShape &measure) -{ - ParameterGrp::handle group = App::GetApplication().GetUserParameter().GetGroup("BaseApp/Preferences/Mod/Part"); - App::Color c((uint32_t) group->GetUnsigned("Dimensions3dColor", 0xFF000000)); - App::Color d((uint32_t) group->GetUnsigned("DimensionsDeltaColor", 0x00FF0000)); - - Gui::View3DInventorViewer *viewer = getViewer(); - if (!viewer) - return; - gp_Pnt point1 = measure.PointOnShape1(1); - gp_Pnt point2 = measure.PointOnShape2(1); - viewer->addDimension3d(createLinearDimension(point1, point2, SbColor(c.r, c.g, c.b))); - - //create deltas. point1 will always be the same. - gp_Pnt temp = point1; - gp_Pnt lastTemp = temp; - temp.SetX(point2.X()); - viewer->addDimensionDelta(createLinearDimension(lastTemp, temp, SbColor(d.r, d.g, d.b))); - lastTemp = temp; - temp.SetY(point2.Y()); - viewer->addDimensionDelta(createLinearDimension(lastTemp, temp, SbColor(d.r, d.g, d.b))); - lastTemp = temp; - temp.SetZ(point2.Z()); - viewer->addDimensionDelta(createLinearDimension(lastTemp, temp, SbColor(d.r, d.g, d.b))); -} - -SoNode* PartGui::createLinearDimension(const gp_Pnt &point1, const gp_Pnt &point2, const SbColor &color) -{ - SbVec3f vec1(point1.X(), point1.Y(), point1.Z()); - SbVec3f vec2(point2.X(), point2.Y(), point2.Z()); - if ((vec2-vec1).length() < FLT_EPSILON) - return new SoSeparator(); //empty object. - PartGui::DimensionLinear *dimension = new PartGui::DimensionLinear(); - dimension->point1.setValue(vec1); - dimension->point2.setValue(vec2); - dimension->setupDimension(); - - Base::Quantity quantity(static_cast((vec2-vec1).length()), Base::Unit::Length); - dimension->text.setValue(quantity.getUserString().toUtf8().constData()); - - dimension->dColor.setValue(color); - return dimension; -} - -void PartGui::eraseAllDimensions() -{ - Gui::Document *doc = Gui::Application::Instance->activeDocument(); - if (!doc) - return; - _Measures.erase(doc->getDocument()->getName()); - Gui::View3DInventor *view = dynamic_cast(doc->getActiveView()); - if (!view) - return; - Gui::View3DInventorViewer *viewer = view->getViewer(); - if (!viewer) - return; - viewer->eraseAllDimensions(); -} - -void PartGui::refreshDimensions() { - auto doc = App::GetApplication().getActiveDocument(); - if(!doc) - return; - auto it = _Measures.find(doc->getName()); - if(it == _Measures.end()) - return; - std::list measures; - measures.swap(it->second); - eraseAllDimensions(); - for(auto &info : measures) { - if(info.linear) - PartGui::TaskMeasureLinear::buildDimension(info.sel1,info.sel2); - else - PartGui::TaskMeasureAngular::buildDimension(info.sel1,info.sel2); - } -} - -void PartGui::toggle3d() -{ - ParameterGrp::handle group = App::GetApplication().GetUserParameter(). - GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("View"); - bool visibility = group->GetBool("Dimensions3dVisible", true); - if (visibility) - group->SetBool("Dimensions3dVisible", false); - else - group->SetBool("Dimensions3dVisible", true); -} - -void PartGui::toggleDelta() -{ - ParameterGrp::handle group = App::GetApplication().GetUserParameter(). - GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("View"); - bool visibility = group->GetBool("DimensionsDeltaVisible", true); - if (visibility) - group->SetBool("DimensionsDeltaVisible", false); - else - group->SetBool("DimensionsDeltaVisible", true); -} - -void PartGui::ensureSomeDimensionVisible() -{ - ParameterGrp::handle group = App::GetApplication().GetUserParameter(). - GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("View"); - bool visibilityAll = group->GetBool("DimensionsVisible", true); - if (!visibilityAll) - group->SetBool("DimensionsVisible", true); - bool visibility3d = group->GetBool("Dimensions3dVisible", true); - bool visibilityDelta = group->GetBool("DimensionsDeltaVisible", true); - - if (!visibility3d && !visibilityDelta) //both turned off. - group->SetBool("Dimensions3dVisible", true); //turn on 3d, so something is visible. -} - -void PartGui::ensure3dDimensionVisible() -{ - ParameterGrp::handle group = App::GetApplication().GetUserParameter(). - GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("View"); - bool visibilityAll = group->GetBool("DimensionsVisible", true); - if (!visibilityAll) - group->SetBool("DimensionsVisible", true); - bool visibility3d = group->GetBool("Dimensions3dVisible", true); - - if (!visibility3d) //both turned off. - group->SetBool("Dimensions3dVisible", true); //turn on 3d, so something is visible. -} - - -SO_KIT_SOURCE(PartGui::DimensionLinear) - -void PartGui::DimensionLinear::initClass() -{ - SO_KIT_INIT_CLASS(DimensionLinear, SoSeparatorKit, "SeparatorKit"); -} - -PartGui::DimensionLinear::DimensionLinear() -{ - SO_KIT_CONSTRUCTOR(PartGui::DimensionLinear); - - SO_KIT_ADD_CATALOG_ENTRY(transformation, SoTransform, true, topSeparator,"" , true); - SO_KIT_ADD_CATALOG_ENTRY(annotate, SoAnnotation, true, topSeparator,"" , true); - SO_KIT_ADD_CATALOG_ENTRY(leftArrow, SoShapeKit, true, topSeparator,"" ,true); - SO_KIT_ADD_CATALOG_ENTRY(rightArrow, SoShapeKit, true, topSeparator,"" ,true); - SO_KIT_ADD_CATALOG_ENTRY(line, SoShapeKit, true, annotate,"" ,true); - SO_KIT_ADD_CATALOG_ENTRY(textSep, SoSeparator, true, annotate,"" ,true); - - SO_KIT_INIT_INSTANCE(); - - SO_NODE_ADD_FIELD(rotate, (1.0, 0.0, 0.0, 0.0));//position orientation of the dimension. - SO_NODE_ADD_FIELD(length, (1.0));//turns into dimension length - SO_NODE_ADD_FIELD(origin, (0.0, 0.0, 0.0));//static - SO_NODE_ADD_FIELD(text, ("test"));//dimension text - SO_NODE_ADD_FIELD(dColor, (1.0, 0.0, 0.0));//dimension color. -} - -PartGui::DimensionLinear::~DimensionLinear() = default; - -SbBool PartGui::DimensionLinear::affectsState() const -{ - return false; -} - -void PartGui::DimensionLinear::setupDimension() -{ - //make unpickable - SoPickStyle* ps = static_cast(getPart("pickStyle", true)); - if (ps) - ps->style = SoPickStyle::UNPICKABLE; - - //transformation - SoTransform *trans = static_cast(getPart("transformation", true)); - trans->translation.connectFrom(&point1); - //build engine for vector subtraction and length. - SoCalculator *hyp = new SoCalculator(); - hyp->A.connectFrom(&point1); - hyp->B.connectFrom(&point2); - hyp->expression.set1Value(0, "oA = B-A"); - hyp->expression.set1Value(1, "oB = normalize(oA)"); - hyp->expression.set1Value(2, "oa = length(oA)"); - length.connectFrom(&hyp->oa); - - //build engine for rotation. - SoComposeRotationFromTo *rotationEngine = new SoComposeRotationFromTo(); - rotationEngine->from.setValue(SbVec3f(1.0, 0.0, 0.0)); - rotationEngine->to.connectFrom(&hyp->oB); - trans->rotation.connectFrom(&rotationEngine->rotation); - - //color - SoMaterial *material = new SoMaterial; - material->diffuseColor.connectFrom(&dColor); - - //dimension arrows - float dimLength = (point2.getValue()-point1.getValue()).length(); - float coneHeight = dimLength * 0.06; - float coneRadius = coneHeight * 0.5; - - SoCone *cone = new SoCone(); - cone->bottomRadius.setValue(coneRadius); - cone->height.setValue(coneHeight); - - char lStr[100]; - char rStr[100]; - snprintf(lStr, sizeof(lStr), "translation %.6f 0.0 0.0", coneHeight * 0.5); - snprintf(rStr, sizeof(rStr), "translation 0.0 -%.6f 0.0", coneHeight * 0.5); - - setPart("leftArrow.shape", cone); - set("leftArrow.transform", "rotation 0.0 0.0 1.0 1.5707963"); - set("leftArrow.transform", lStr); - setPart("rightArrow.shape", cone); - set("rightArrow.transform", "rotation 0.0 0.0 -1.0 1.5707963"); //no constant for PI. - //have use local here to do the offset because the main is wired up to length of dimension. - set("rightArrow.localTransform", rStr); - - SoTransform *transform = static_cast(getPart("rightArrow.transform", false)); - if (!transform) - return;//what to do here? - SoComposeVec3f *vec = new SoComposeVec3f; - vec->x.connectFrom(&length); - vec->y.setValue(0.0); - vec->z.setValue(0.0); - transform->translation.connectFrom(&vec->vector); - - setPart("leftArrow.material", material); - setPart("rightArrow.material", material); - - //line - SoConcatenate *catEngine = new SoConcatenate(SoMFVec3f::getClassTypeId()); - //don't know how to get around having this dummy origin. cat engine wants to connectfrom? - catEngine->input[0]->connectFrom(&origin); - catEngine->input[1]->connectFrom(&vec->vector); - - SoVertexProperty *lineVerts = new SoVertexProperty; - lineVerts->vertex.connectFrom(catEngine->output); - - int lineVertexMap[] = {0, 1}; - int lineVertexMapSize(sizeof(lineVertexMap)/sizeof(int)); - SoIndexedLineSet *line = new SoIndexedLineSet; - line->vertexProperty = lineVerts; - line->coordIndex.setValues(0, lineVertexMapSize, lineVertexMap); - - setPart("line.shape", line); - setPart("line.material", material); - - //text - SoSeparator *textSep = static_cast(getPart("textSep", true)); - if (!textSep) - return; - - textSep->addChild(material); - - SoCalculator *textVecCalc = new SoCalculator(); - textVecCalc->A.connectFrom(&vec->vector); - textVecCalc->B.set1Value(0, 0.0, 0.250, 0.0); - textVecCalc->expression.set1Value(0, "oA = (A / 2) + B"); - - SoTransform *textTransform = new SoTransform(); - textTransform->translation.connectFrom(&textVecCalc->oA); - textSep->addChild(textTransform); - - SoFont *fontNode = new SoFont(); - fontNode->name.setValue(getDimensionsFontName().c_str()); - fontNode->size.setValue(getDimensionsFontSize()); - textSep->addChild(fontNode); - - SoText2 *textNode = new SoText2(); - textNode->justification = SoText2::CENTER; - textNode->string.connectFrom(&text); - textSep->addChild(textNode); - - //this prevents the 2d text from screwing up the bounding box for a viewall - SoResetTransform *rTrans = new SoResetTransform; - rTrans->whatToReset = SoResetTransform::BBOX; - textSep->addChild(rTrans); -} - -PartGui::TaskMeasureLinear::TaskMeasureLinear() - : Gui::SelectionObserver(true, Gui::ResolveMode::NoResolve) - , selections1(), selections2(), buttonSelectedIndex(0) -{ - setUpGui(); -} - -PartGui::TaskMeasureLinear::~TaskMeasureLinear() -{ - try { - Gui::Selection().clearSelection(); - } - catch (const Py::Exception&) { - Base::PyException e; // extract the Python error text - e.ReportException(); - } -} - -void PartGui::TaskMeasureLinear::onSelectionChanged(const Gui::SelectionChanges& msg) -{ - if (msg.pSubName[0] == '\0') - return; // ignore whole objects selected in the model tree, e.g. when toggling the visibility of an object - - if (buttonSelectedIndex == 0) - { - if (msg.Type == Gui::SelectionChanges::AddSelection) - { - DimSelections::DimSelection newSelection; - newSelection.shapeType = DimSelections::None; - newSelection.documentName = msg.pDocName; - newSelection.objectName = msg.pObjectName; - newSelection.subObjectName = msg.pSubName; - newSelection.x = msg.x; - newSelection.y = msg.y; - newSelection.z = msg.z; - selections1.selections.clear();//we only want one item. - selections1.selections.push_back(newSelection); - QTimer::singleShot(0, this, &PartGui::TaskMeasureLinear::selectionClearDelayedSlot); - stepped->getButton(1)->setEnabled(true); - stepped->getButton(1)->setChecked(true); - return; - } - } - if (buttonSelectedIndex == 1) - { - if (msg.Type == Gui::SelectionChanges::AddSelection) - { - DimSelections::DimSelection newSelection; - newSelection.shapeType = DimSelections::None; - newSelection.documentName = msg.pDocName; - newSelection.objectName = msg.pObjectName; - newSelection.subObjectName = msg.pSubName; - newSelection.x = msg.x; - newSelection.y = msg.y; - newSelection.z = msg.z; - selections2.selections.clear();//we only want one item. - selections2.selections.push_back(newSelection); - buildDimension(); - clearSelectionStrings(); - QTimer::singleShot(0, this, &PartGui::TaskMeasureLinear::selectionClearDelayedSlot); - stepped->getButton(0)->setChecked(true); - stepped->getButton(1)->setEnabled(false); - return; - } - } -} - -void PartGui::TaskMeasureLinear::selectionClearDelayedSlot() -{ - //hack. - //clearing selections are not working as I hoped. Apparently the observer callback gets called - //before the actual selection takes place. Resulting in selections being left. this addresses this - //by being called from the event loop. - this->blockSelection(true); - Gui::Selection().clearSelection(); - this->blockSelection(false); -} - -void PartGui::TaskMeasureLinear::buildDimension() { - buildDimension(selections1,selections2); -} - -void PartGui::TaskMeasureLinear::buildDimension(const DimSelections &sel1, const DimSelections &sel2) -{ - if(sel1.selections.size() != 1 || sel2.selections.size() != 1) - return; - - DimSelections::DimSelection current1 = sel1.selections.at(0); - DimSelections::DimSelection current2 = sel2.selections.at(0); - - TopoDS_Shape shape1, shape2; - if (!getShapeFromStrings(shape1, current1.documentName, current1.objectName, current1.subObjectName)) - { - Base::Console().Message("\nFailed to get shape\n\n"); - return; - } - if (!getShapeFromStrings(shape2, current2.documentName, current2.objectName, current2.subObjectName)) - { - Base::Console().Message("\nFailed to get shape\n\n"); - return; - } - auto doc = App::GetApplication().getActiveDocument(); - if(doc) - _Measures[doc->getName()].emplace_back(sel1,sel2,true); - goDimensionLinearNoTask(shape1, shape2); -} - -void PartGui::TaskMeasureLinear::clearSelectionStrings() -{ - selections1.selections.clear(); - selections2.selections.clear(); -} - -void PartGui::TaskMeasureLinear::setUpGui() -{ - QPixmap mainIcon = Gui::BitmapFactory().pixmap("Part_Measure_Linear"); - - Gui::TaskView::TaskBox* selectionTaskBox = new Gui::TaskView::TaskBox - (mainIcon, QObject::tr("Selections"), false, nullptr); - QVBoxLayout *selectionLayout = new QVBoxLayout(); - stepped = new SteppedSelection(2, selectionTaskBox); - selectionLayout->addWidget(stepped); - selectionTaskBox->groupLayout()->addLayout(selectionLayout); - - Gui::TaskView::TaskBox* controlTaskBox = new Gui::TaskView::TaskBox - (mainIcon, QObject::tr("Control"), false, nullptr); - QVBoxLayout *controlLayout = new QVBoxLayout(); - - DimensionControl *control = new DimensionControl(controlTaskBox); - controlLayout->addWidget(control); - controlTaskBox->groupLayout()->addLayout(controlLayout); - QObject::connect(control->resetButton, &QPushButton::clicked, this, &TaskMeasureLinear::resetDialogSlot); - - this->setButtonPosition(TaskDialog::North); - Content.push_back(selectionTaskBox); - Content.push_back(controlTaskBox); - - stepped->getButton(0)->setChecked(true);//before wired up. - stepped->getButton(0)->setEnabled(true); - QObject::connect(stepped->getButton(0), &QPushButton::toggled, this, &TaskMeasureLinear::selection1Slot); - QObject::connect(stepped->getButton(1), &QPushButton::toggled, this, &TaskMeasureLinear::selection2Slot); -} - -void PartGui::TaskMeasureLinear::selection1Slot(bool checked) -{ - if (!checked) - { - if (!selections1.selections.empty()) - stepped->setIconDone(0); - return; - } - buttonSelectedIndex = 0; - - this->blockSelection(true); - Gui::Selection().clearSelection(); - //we should only be working with 1 entity, but oh well do the loop anyway. - std::vector::const_iterator it; - for (it = selections1.selections.begin(); it != selections1.selections.end(); ++it) - Gui::Selection().addSelection(it->documentName.c_str(), it->objectName.c_str(), it->subObjectName.c_str()); - this->blockSelection(false); -} - -void PartGui::TaskMeasureLinear::selection2Slot(bool checked) -{ - if (!checked) - return; - buttonSelectedIndex = 1; - this->blockSelection(true); - Gui::Selection().clearSelection(); - std::vector::const_iterator it; - for (it = selections2.selections.begin(); it != selections2.selections.end(); ++it) - Gui::Selection().addSelection(it->documentName.c_str(), it->objectName.c_str(), it->subObjectName.c_str()); - this->blockSelection(false); -} - -void PartGui::TaskMeasureLinear::resetDialogSlot(bool) -{ - clearSelectionStrings(); - this->blockSelection(true); - Gui::Selection().clearSelection(); - stepped->getButton(0)->setChecked(true); - stepped->getButton(1)->setEnabled(false); - this->blockSelection(false); -} - -void PartGui::TaskMeasureLinear::toggle3dSlot(bool) -{ - PartGui::toggle3d(); -} - -void PartGui::TaskMeasureLinear::toggleDeltaSlot(bool) -{ - PartGui::toggleDelta(); -} - -void PartGui::TaskMeasureLinear::clearAllSlot(bool) -{ - PartGui::eraseAllDimensions(); -} - -gp_Vec PartGui::convert(const TopoDS_Vertex &vertex) -{ - gp_Pnt point = BRep_Tool::Pnt(vertex); - gp_Vec out(point.X(), point.Y(), point.Z()); - return out; -} - -void PartGui::goDimensionAngularRoot() -{ - PartGui::ensure3dDimensionVisible(); - - VectorAdapter adapter1, adapter2; - if(PartGui::evaluateAngularPreSelection(adapter1, adapter2)) - goDimensionAngularNoTask(adapter1, adapter2); - else - { - Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog(); - if (!dlg) - { - Gui::Selection().clearSelection(); - dlg = new PartGui::TaskMeasureAngular(); - } - Gui::Control().showDialog(dlg); - } - Gui::Selection().clearSelection(); -} - -bool PartGui::evaluateAngularPreSelection(VectorAdapter &vector1Out, VectorAdapter &vector2Out) -{ - std::vector selections = Gui::Selection().getSelection(nullptr, Gui::ResolveMode::NoResolve); - if (selections.size() > 4 || selections.size() < 2) - return false; - std::vector::iterator it; - std::vector adapters; - std::vector sels; - TopoDS_Vertex lastVertex; - for (it = selections.begin(); it != selections.end(); ++it) - { - Base::Matrix4D mat; - TopoDS_Shape shape = Part::Feature::getShape(it->pObject,it->SubName,true,&mat); - if (shape.IsNull()) - break; - mat.inverse(); - - if (shape.ShapeType() == TopAbs_VERTEX) - { - if(sels.empty() || - sels.back().selections.back().shapeType!=DimSelections::Vertex || - sels.back().selections.size()==1) - { - sels.emplace_back(); - } - sels.back().selections.emplace_back(); - auto &sel = sels.back().selections.back(); - sel.documentName = it->DocName; - sel.objectName = it->FeatName; - sel.subObjectName = it->SubName; - sel.shapeType = DimSelections::Vertex; - Base::Vector3d v(it->x,it->y,it->z); - v = mat*v; - sel.x = v.x; - sel.y = v.y; - sel.z = v.z; - - TopoDS_Vertex currentVertex = TopoDS::Vertex(shape); - if (!lastVertex.IsNull()) - { - //need something here for 0 length vector. - //create a point half way between to vertices. - adapters.emplace_back(currentVertex, lastVertex); - lastVertex = TopoDS_Vertex(); - } - else - { - lastVertex = currentVertex; - } - continue; - } - //vertices have to be selected in succession. so if we make it here clear the last vertex. - lastVertex = TopoDS_Vertex(); - - gp_Vec pickPoint(it->x, it->y, it->z); - //can't use selections without a pick point. - if (pickPoint.IsEqual(gp_Vec(0.0, 0.0, 0.0), Precision::Confusion(), Precision::Angular())) - { - Base::Console().Message("Can't use selections without a pick point.\n"); - continue; - } - - sels.emplace_back(); - sels.back().selections.emplace_back(); - auto &sel = sels.back().selections.back(); - sel.documentName = it->DocName; - sel.objectName = it->FeatName; - sel.subObjectName = it->SubName; - Base::Vector3d v(it->x,it->y,it->z); - v = mat*v; - sel.x = v.x; - sel.y = v.y; - sel.z = v.z; - - if (shape.ShapeType() == TopAbs_EDGE) - { - sel.shapeType = DimSelections::Edge; - TopoDS_Edge edge = TopoDS::Edge(shape); - // make edge orientation so that end of edge closest to pick is head of vector. - gp_Vec firstPoint = PartGui::convert(TopExp::FirstVertex(edge, Standard_True)); - gp_Vec lastPoint = PartGui::convert(TopExp::LastVertex(edge, Standard_True)); - double firstDistance = (firstPoint - pickPoint).Magnitude(); - double lastDistance = (lastPoint - pickPoint).Magnitude(); - if (lastDistance > firstDistance) - { - if (edge.Orientation() == TopAbs_FORWARD) - edge.Orientation(TopAbs_REVERSED); - else - edge.Orientation(TopAbs_FORWARD); - } - adapters.emplace_back(edge, pickPoint); - continue; - } - - if (shape.ShapeType() == TopAbs_FACE) - { - sel.shapeType = DimSelections::Face; - TopoDS_Face face = TopoDS::Face(shape); - adapters.emplace_back(face, pickPoint); - continue; - } - } - - if (adapters.size() != 2) - return false; - if (!adapters.front().isValid() || !adapters.back().isValid()) - return false; - - vector1Out = adapters.front(); - vector2Out = adapters.back(); - - //making sure pick points are not equal - if ((vector1Out.getPickPoint() - vector2Out.getPickPoint()).Magnitude() < std::numeric_limits::epsilon()) - { - Base::Console().Message("pick points are equal\n"); - return false; - } - - auto doc = App::GetApplication().getActiveDocument(); - if(doc) - _Measures[doc->getName()].emplace_back(sels[0],sels[1],false); - return true; -} - -void PartGui::goDimensionAngularNoTask(const VectorAdapter &vector1Adapter, const VectorAdapter &vector2Adapter) -{ - gp_Vec vector1 = vector1Adapter; - gp_Vec vector2 = vector2Adapter; - double angle = vector1.Angle(vector2); - - std::ostringstream stream; - stream << std::setprecision(std::numeric_limits::digits10 + 1) << std::fixed << std::endl << - "angle in radians is: " << angle << std::endl << - "angle in degrees is: " << 180 * angle / M_PI << std::endl; - if (angle < M_PI / 2.0) - stream << std::setprecision(std::numeric_limits::digits10 + 1) << - "complement in radians is: " << M_PI / 2.0 - angle << std::endl << - "complement in degrees is: " << 90 - 180 * angle / M_PI << std::endl; - //I don't think we get anything over 180, but just in case. - if (angle > M_PI / 2.0 && angle < M_PI) - stream << std::setprecision(std::numeric_limits::digits10 + 1) << - "supplement in radians is: " << M_PI - angle << std::endl << - "supplement in degrees is: " << 180 - 180 * angle / M_PI << std::endl; - Base::Console().Message(stream.str().c_str()); - - SbMatrix dimSys; - double radius; - double displayAngle;//have to fake the angle in the 3d. - - if (vector1.IsParallel(vector2, Precision::Angular())) - { - //take first point project it onto second vector. - Handle(Geom_Curve) heapLine2 = new Geom_Line(vector2Adapter); - gp_Pnt tempPoint(vector1Adapter.getPickPoint().XYZ()); - - GeomAPI_ProjectPointOnCurve projection(tempPoint, heapLine2); - if (projection.NbPoints() < 1) - { - Base::Console().Message("parallel vectors: couldn't project onto line\n"); - return; - } - gp_Vec newPoint2; - newPoint2.SetXYZ(projection.Point(1).XYZ()); - - //if points are colinear, projection doesn't work and returns the same point. - //In this case we just use the original point. - if ((newPoint2 - vector1Adapter.getPickPoint()).Magnitude() < Precision::Confusion()) - newPoint2 = vector2Adapter.getPickPoint(); - - //now get midpoint between for dim origin. - gp_Vec point1 = vector1Adapter.getPickPoint(); - gp_Vec midPointProjection = newPoint2 - point1; - double distance = midPointProjection.Magnitude(); - midPointProjection.Normalize(); - midPointProjection *= distance / 2.0; - - gp_Vec origin = point1 + midPointProjection; - - //yaxis should be the same as vector1, but doing this to eliminate any potential slop from - //using precision::angular. If lines are colinear and we have no plane, we can't establish zAxis from crossing. - //we just the absolute axis. - gp_Vec xAxis = (point1 - origin).Normalized(); - gp_Vec zAxis; - if (xAxis.IsParallel(vector1, Precision::Angular())) - { - if (!xAxis.IsParallel(gp_Vec(0.0, 0.0, 1.0), Precision::Angular())) - zAxis = gp_Vec(0.0, 0.0, 1.0); - else - zAxis = gp_Vec(0.0, 1.0, 0.0); - } - else - zAxis = xAxis.Crossed(vector1).Normalized(); - gp_Vec yAxis = zAxis.Crossed(xAxis).Normalized(); - zAxis = xAxis.Crossed(yAxis).Normalized(); - - dimSys = SbMatrix - ( - xAxis.X(), yAxis.X(), zAxis.X(), origin.X(), - xAxis.Y(), yAxis.Y(), zAxis.Y(), origin.Y(), - xAxis.Z(), yAxis.Z(), zAxis.Z(), origin.Z(), - 0.0, 0.0, 0.0, 1.0 - ); - dimSys = dimSys.transpose(); - - radius = midPointProjection.Magnitude(); - displayAngle = M_PI; - } - else - { - Handle(Geom_Curve) heapLine1 = new Geom_Line(vector1Adapter); - Handle(Geom_Curve) heapLine2 = new Geom_Line(vector2Adapter); - - GeomAPI_ExtremaCurveCurve extrema(heapLine1, heapLine2); - - if (extrema.NbExtrema() < 1) - { - Base::Console().Message("couldn't get extrema\n"); - return; - } - - gp_Pnt extremaPoint1, extremaPoint2, dimensionOriginPoint; - extrema.Points(1, extremaPoint1, extremaPoint2); - if (extremaPoint1.Distance(extremaPoint2) < Precision::Confusion()) - dimensionOriginPoint = extremaPoint1; - else - { - //find halfway point in between extrema points for dimension origin. - gp_Vec vec1(extremaPoint1.XYZ()); - gp_Vec vec2(extremaPoint2.XYZ()); - gp_Vec connection(vec2-vec1); - Standard_Real distance = connection.Magnitude(); - connection.Normalize(); - connection *= (distance / 2.0); - dimensionOriginPoint.SetXYZ((vec1 + connection).XYZ()); - } - - gp_Vec thirdPoint(vector2Adapter.getPickPoint()); - gp_Vec originVector(dimensionOriginPoint.XYZ()); - gp_Vec extrema2Vector(extremaPoint2.XYZ()); - radius = (vector1Adapter.getPickPoint() - originVector).Magnitude(); - double legOne = (extrema2Vector - originVector).Magnitude(); - displayAngle = angle; - if (legOne > Precision::Confusion()) - { - double legTwo = sqrt(pow(radius, 2) - pow(legOne, 2)); - gp_Vec projectionVector(vector2); - projectionVector.Normalize(); - projectionVector *= legTwo; - thirdPoint = extrema2Vector + projectionVector; - gp_Vec hyp(thirdPoint - originVector); - hyp.Normalize(); - gp_Vec otherSide(vector1Adapter.getPickPoint() - originVector); - otherSide.Normalize(); - displayAngle = hyp.Angle(otherSide); - } - - gp_Vec xAxis = (vector1Adapter.getPickPoint() - originVector).Normalized(); - gp_Vec fakeYAxis = (thirdPoint - originVector).Normalized(); - gp_Vec zAxis = (xAxis.Crossed(fakeYAxis)).Normalized(); - gp_Vec yAxis = zAxis.Crossed(xAxis).Normalized(); - - dimSys = SbMatrix - ( - xAxis.X(), yAxis.X(), zAxis.X(), dimensionOriginPoint.X(), - xAxis.Y(), yAxis.Y(), zAxis.Y(), dimensionOriginPoint.Y(), - xAxis.Z(), yAxis.Z(), zAxis.Z(), dimensionOriginPoint.Z(), - 0.0, 0.0, 0.0, 1.0 - ); - - dimSys = dimSys.transpose(); - } - - ParameterGrp::handle group = App::GetApplication().GetUserParameter().GetGroup("BaseApp/Preferences/Mod/Part"); - App::Color c((uint32_t) group->GetUnsigned("DimensionsAngularColor", 0x0000FF00)); - - DimensionAngular *dimension = new DimensionAngular(); - dimension->ref(); - dimension->matrix.setValue(dimSys); - dimension->radius.setValue(radius); - dimension->angle.setValue(static_cast(displayAngle)); - dimension->text.setValue((Base::Quantity(180 * angle / M_PI, Base::Unit::Angle)).getUserString().toUtf8().constData()); - dimension->dColor.setValue(SbColor(c.r, c.g, c.b)); - dimension->setupDimension(); - - Gui::View3DInventorViewer *viewer = getViewer(); - if (viewer) - viewer->addDimension3d(dimension); - dimension->unref(); -} - -SO_KIT_SOURCE(PartGui::DimensionAngular) - -void PartGui::DimensionAngular::initClass() -{ - SO_KIT_INIT_CLASS(DimensionAngular, SoSeparatorKit, "SeparatorKit"); -} - -PartGui::DimensionAngular::DimensionAngular() -{ - SO_KIT_CONSTRUCTOR(PartGui::DimensionAngular); - - SO_KIT_ADD_CATALOG_ENTRY(transformation, SoMatrixTransform, true, topSeparator,"" , true); - SO_KIT_ADD_CATALOG_ENTRY(annotate, SoAnnotation, true, topSeparator,"" , true); - SO_KIT_ADD_CATALOG_ENTRY(arrow1, SoShapeKit, true, topSeparator,"" ,true); - SO_KIT_ADD_CATALOG_ENTRY(arrow2, SoShapeKit, true, topSeparator,"" ,true); - SO_KIT_ADD_CATALOG_ENTRY(arcSep, SoSeparator, true, annotate,"" ,true); - SO_KIT_ADD_CATALOG_ENTRY(textSep, SoSeparator, true, annotate,"" ,true); - - SO_KIT_INIT_INSTANCE(); - - SO_NODE_ADD_FIELD(radius, (10.0)); - SO_NODE_ADD_FIELD(angle, (1.0)); - SO_NODE_ADD_FIELD(text, ("test"));//dimension text - SO_NODE_ADD_FIELD(dColor, (1.0, 0.0, 0.0));//dimension color. - SO_NODE_ADD_FIELD(matrix, (1.0, 0.0, 0.0, 0.0, - 0.0, 1.0, 0.0, 0.0, - 0.0, 0.0, 1.0, 0.0, - 0.0, 0.0, 0.0, 1.0)); -} - -PartGui::DimensionAngular::~DimensionAngular() = default; - -SbBool PartGui::DimensionAngular::affectsState() const -{ - return false; -} - - -void PartGui::DimensionAngular::setupDimension() -{ - //transformation - SoMatrixTransform *trans = static_cast(getPart("transformation", true)); - trans->matrix.connectFrom(&matrix); - - //color - SoMaterial *material = new SoMaterial; - material->ref(); - material->diffuseColor.connectFrom(&dColor); - - // calculate arrow positions - SoTransform *arrow1Transform = new SoTransform(); - SoComposeVec3f *arrow1Compose = new SoComposeVec3f(); - arrow1Compose->x.connectFrom(&radius); - arrow1Compose->y.setValue(0.0f); - arrow1Compose->z.setValue(0.0f); - const float* constFloat = arrow1Compose->x.getValues(0); - auto PositionX1 = *constFloat; - arrow1Transform->translation.connectFrom(&arrow1Compose->vector); - setPart("arrow1.transform", arrow1Transform); - - SoComposeRotation *arrow2Rotation = new SoComposeRotation(); - arrow2Rotation->angle.connectFrom(&angle); - arrow2Rotation->axis.setValue(0.0f, 0.0f, 1.0f); - SoTransform *arrow2Transform = new SoTransform(); - arrow2Transform->rotation.connectFrom(&arrow2Rotation->rotation); - SoCalculator *arrow2LocationCalc = new SoCalculator(); - arrow2LocationCalc->a.connectFrom(&angle); - arrow2LocationCalc->b.connectFrom(&radius); - arrow2LocationCalc->expression.set1Value(0, "oa = cos(a) * b"); //xLocation - arrow2LocationCalc->expression.set1Value(1, "ob = sin(a) * b"); //yLocation - SoComposeVec3f *arrow2Compose = new SoComposeVec3f(); - arrow2Compose->x.connectFrom(&arrow2LocationCalc->oa); - arrow2Compose->y.connectFrom(&arrow2LocationCalc->ob); - arrow2Compose->z.setValue(0.0f); - arrow2Transform->translation.connectFrom(&arrow2Compose->vector); - - // calculate distance between the 2 arrows - constFloat = arrow2Compose->x.getValues(0); - auto PositionX2 = *constFloat; - constFloat = arrow2Compose->y.getValues(0); - auto PositionY2 = *constFloat; - float distance = sqrt((PositionX2 - PositionX1) * (PositionX2 - PositionX1) + PositionY2 * PositionY2); - - // dimension arrows - // the cone size must be scaled with the distance - // we use the same factors as for linear dimensions - float coneHeight = distance * 0.06; - float coneRadius = coneHeight * 0.5; - - SoCone* cone = new SoCone(); - cone->bottomRadius.setValue(coneRadius); - cone->height.setValue(coneHeight); - - // set arrows, their precision and rotation - char str1[100]; - char str2[100]; - snprintf(str1, sizeof(str1), "translation 0.0 %.6f 0.0", coneHeight * 0.5); - snprintf(str2, sizeof(str2), "translation 0.0 -%.6f 0.0", coneHeight * 0.5); - - setPart("arrow1.shape", cone); - set("arrow1.localTransform", "rotation 0.0 0.0 1.0 3.1415927"); - set("arrow1.localTransform", str1); - setPart("arrow2.shape", cone); - set("arrow2.transform", "rotation 0.0 0.0 1.0 0.0"); - set("arrow2.localTransform", str2); - - // now the position - setPart("arrow1.transform", arrow1Transform); - setPart("arrow2.transform", arrow2Transform); - - // finally the material - setPart("arrow1.material", material); - setPart("arrow2.material", material); - - ArcEngine *arcEngine = new ArcEngine(); - arcEngine->angle.connectFrom(&angle); - arcEngine->radius.connectFrom(&radius); - arcEngine->deviation.setValue(0.1f); - - SoCoordinate3 *coordinates = new SoCoordinate3(); - coordinates->point.connectFrom(&arcEngine->points); - - SoLineSet *lineSet = new SoLineSet(); - lineSet->ref(); - lineSet->vertexProperty.setValue(coordinates); - lineSet->numVertices.connectFrom(&arcEngine->pointCount); - lineSet->startIndex.setValue(0); - - SoSeparator *arcSep = static_cast(getPart("arcSep", true)); - if (arcSep) { - arcSep->addChild(material); - arcSep->addChild(lineSet); - } - - //text - SoSeparator *textSep = static_cast(getPart("textSep", true)); - if (!textSep) - return; - - textSep->addChild(material); - - SoCalculator *textVecCalc = new SoCalculator(); - textVecCalc->a.connectFrom(&angle); - textVecCalc->b.connectFrom(&radius); - textVecCalc->expression.set1Value(0, "oa = a / 2.0"); - textVecCalc->expression.set1Value(1, "ob = cos(oa) * b"); //x - textVecCalc->expression.set1Value(2, "oc = sin(oa) * b"); //y - - SoComposeVec3f *textLocation = new SoComposeVec3f(); - textLocation->x.connectFrom(&textVecCalc->ob); - textLocation->y.connectFrom(&textVecCalc->oc); - textLocation->z.setValue(0.0); - - - SoTransform *textTransform = new SoTransform(); - textTransform->translation.connectFrom(&textLocation->vector); - textSep->addChild(textTransform); - - SoFont *fontNode = new SoFont(); - fontNode->name.setValue(getDimensionsFontName().c_str()); - fontNode->size.setValue(getDimensionsFontSize()); - textSep->addChild(fontNode); - - SoText2 *textNode = new SoText2(); - textNode->justification = SoText2::CENTER; - textNode->string.connectFrom(&text); - textSep->addChild(textNode); - - //this prevents the 2d text from screwing up the bounding box for a viewall - SoResetTransform *rTrans = new SoResetTransform; - rTrans->whatToReset = SoResetTransform::BBOX; - textSep->addChild(rTrans); - - lineSet->unref(); - material->unref(); -} - -SO_ENGINE_SOURCE(PartGui::ArcEngine) - -PartGui::ArcEngine::ArcEngine() -{ - SO_ENGINE_CONSTRUCTOR(ArcEngine); - - SO_ENGINE_ADD_INPUT(radius, (10.0)); - SO_ENGINE_ADD_INPUT(angle, (1.0)); - SO_ENGINE_ADD_INPUT(deviation, (0.25)); - - SO_ENGINE_ADD_OUTPUT(points, SoMFVec3f); - SO_ENGINE_ADD_OUTPUT(pointCount, SoSFInt32); -} - -void PartGui::ArcEngine::initClass() -{ - SO_ENGINE_INIT_CLASS(ArcEngine, SoEngine, "Engine"); -} - -void PartGui::ArcEngine::evaluate() -{ - if (radius.getValue() < std::numeric_limits::epsilon() || - angle.getValue() < std::numeric_limits::epsilon() || - deviation.getValue() < std::numeric_limits::epsilon()) - { - defaultValues(); - return; - } - - float deviationAngle(acos((radius.getValue() - deviation.getValue()) / radius.getValue())); - std::vector tempPoints; - int segmentCount; - if (deviationAngle >= angle.getValue()) - segmentCount = 1; - else - { - segmentCount = static_cast(angle.getValue() / deviationAngle) + 1; - if (segmentCount < 2) - { - defaultValues(); - return; - } - } - float angleIncrement = angle.getValue() / static_cast(segmentCount); - for (int index = 0; index < segmentCount + 1; ++index) - { - SbVec3f currentNormal(1.0, 0.0, 0.0); - float currentAngle = index * angleIncrement; - SbRotation rotation(SbVec3f(0.0, 0.0, 1.0), currentAngle); - rotation.multVec(currentNormal, currentNormal); - tempPoints.push_back(currentNormal * radius.getValue()); - } - int tempCount = tempPoints.size(); //for macro. - SO_ENGINE_OUTPUT(points, SoMFVec3f, setNum(tempCount)); - SO_ENGINE_OUTPUT(pointCount, SoSFInt32, setValue(tempCount)); - std::vector::const_iterator it; - for (it = tempPoints.begin(); it != tempPoints.end(); ++it) - { - int currentIndex = it-tempPoints.begin(); //for macro. - SbVec3f temp(*it); //for macro - SO_ENGINE_OUTPUT(points, SoMFVec3f, set1Value(currentIndex, temp)); - } - -} - -void PartGui::ArcEngine::defaultValues() -{ - //just some non-failing info. - SO_ENGINE_OUTPUT(points, SoMFVec3f, setNum(2)); - SbVec3f point1(10.0, 0.0, 0.0); - SO_ENGINE_OUTPUT(points, SoMFVec3f, set1Value(0, point1)); - SbVec3f point2(7.07f, 7.07f, 0.0); - SO_ENGINE_OUTPUT(points, SoMFVec3f, set1Value(1, point2)); - SO_ENGINE_OUTPUT(pointCount, SoSFInt32, setValue(2)); -} - -PartGui::SteppedSelection::SteppedSelection(const uint& buttonCountIn, QWidget* parent) - : QWidget(parent) - , stepActive(nullptr) - , stepDone(nullptr) -{ - if (buttonCountIn < 1) - return; - - QVBoxLayout *mainLayout = new QVBoxLayout(); - this->setLayout(mainLayout); - - QButtonGroup *buttonGroup = new QButtonGroup(); - buttonGroup->setExclusive(true); - - for (uint index = 0; index < buttonCountIn; ++index) - { - ButtonIconPairType tempPair; - QString text = QObject::tr("Selection"); - std::ostringstream stream; - stream << text.toStdString() << " " << ((index < 10) ? "0" : "") << index + 1; - QString buttonText = QString::fromStdString(stream.str()); - QPushButton *button = new QPushButton(buttonText, this); - button->setCheckable(true); - button->setEnabled(false); - buttonGroup->addButton(button); - connect(button, &QPushButton::toggled, this, &SteppedSelection::selectionSlot); - - QLabel *label = new QLabel; - - tempPair.first = button; - tempPair.second = label; - buttons.push_back(tempPair); - - QHBoxLayout *layout = new QHBoxLayout(); - mainLayout->addLayout(layout); - layout->addWidget(button); - layout->addSpacing(10); - layout->addWidget(label); - layout->addStretch(); - } - mainLayout->addStretch(); - - buildPixmaps(); //uses button size -} - -PartGui::SteppedSelection::~SteppedSelection() -{ - if(stepActive) - { - delete stepActive; - stepActive = nullptr; - } - if (stepDone) - { - delete stepDone; - stepDone = nullptr; - } -} - -void PartGui::SteppedSelection::buildPixmaps() -{ - assert(buttons.size() > 0); - int iconHeight(buttons.at(0).first->height()-6); - stepActive = new QPixmap(Gui::BitmapFactory().pixmap("Part_Measure_Step_Active").scaled - (iconHeight, iconHeight, Qt::KeepAspectRatio)); - stepDone = new QPixmap(Gui::BitmapFactory().pixmap("Part_Measure_Step_Done").scaled - (iconHeight, iconHeight, Qt::KeepAspectRatio)); -} - -void PartGui::SteppedSelection::selectionSlot(bool checked) -{ - QPushButton *sender = qobject_cast(QObject::sender()); - assert(sender); - std::vector::iterator it; - for (it = buttons.begin(); it != buttons.end(); ++it) - if (it->first == sender) - break; - assert(it != buttons.end()); - - if (checked) - it->second->setPixmap(*stepActive); - else - it->second->setPixmap(QPixmap()); -} - -QPushButton* PartGui::SteppedSelection::getButton(const uint& index) -{ - return buttons.at(index).first; -} - -void PartGui::SteppedSelection::setIconDone(const uint& index) -{ - buttons.at(index).second->setPixmap(*stepDone); -} - -PartGui::DimensionControl::DimensionControl(QWidget* parent): QWidget(parent) -{ - QVBoxLayout *commandLayout = new QVBoxLayout(); - this->setLayout(commandLayout); - - resetButton = new QPushButton(Gui::BitmapFactory().pixmap("Part_Measure_Linear"), - QObject::tr("Reset selection"), this); - commandLayout->addWidget(resetButton); - - QPushButton *toggle3dButton = new QPushButton(Gui::BitmapFactory().pixmap("Part_Measure_Toggle_3D"), - QObject::tr("Toggle direct dimensions"), this); - QObject::connect(toggle3dButton, &QPushButton::clicked, this, &DimensionControl::toggle3dSlot); - commandLayout->addWidget(toggle3dButton); - - QPushButton *toggleDeltaButton = new QPushButton(Gui::BitmapFactory().pixmap("Part_Measure_Toggle_Delta"), - QObject::tr("Toggle orthogonal dimensions"), this); - QObject::connect(toggleDeltaButton, &QPushButton::clicked, this, &DimensionControl::toggleDeltaSlot); - commandLayout->addWidget(toggleDeltaButton); - - QPushButton *clearAllButton = new QPushButton(Gui::BitmapFactory().pixmap("Part_Measure_Clear_All"), - QObject::tr("Clear all dimensions"), this); - QObject::connect(clearAllButton, &QPushButton::clicked, this, &DimensionControl::clearAllSlot); - commandLayout->addWidget(clearAllButton); -} - -void PartGui::DimensionControl::toggle3dSlot(bool) -{ - PartGui::toggle3d(); -} - -void PartGui::DimensionControl::toggleDeltaSlot(bool) -{ - PartGui::toggleDelta(); -} - -void PartGui::DimensionControl::clearAllSlot(bool) -{ - PartGui::eraseAllDimensions(); -} - -PartGui::TaskMeasureAngular::TaskMeasureAngular() - : Gui::SelectionObserver(true, Gui::ResolveMode::NoResolve) - , selections1(), selections2(), buttonSelectedIndex(0) -{ - setUpGui(); -} - -PartGui::TaskMeasureAngular::~TaskMeasureAngular() -{ - try { - Gui::Selection().clearSelection(); - } - catch (const Py::Exception&) { - Base::PyException e; // extract the Python error text - e.ReportException(); - } -} - -void PartGui::TaskMeasureAngular::onSelectionChanged(const Gui::SelectionChanges& msg) -{ - if (msg.pSubName[0] == '\0') - return; // ignore whole objects selected in the model tree, e.g. when toggling the visibility of an object - - TopoDS_Shape shape; - Base::Matrix4D mat; - if (!getShapeFromStrings(shape, std::string(msg.pDocName), - std::string(msg.pObjectName), std::string(msg.pSubName),&mat)) - return; - mat.inverse(); - DimSelections::DimSelection newSelection; - newSelection.shapeType = DimSelections::None; - newSelection.documentName = msg.pDocName; - newSelection.objectName = msg.pObjectName; - newSelection.subObjectName = msg.pSubName; - gp_Vec pickPoint(msg.x, msg.y, msg.z); - Base::Vector3d v(msg.x,msg.y,msg.z); - v = mat*v; - newSelection.x = v.x; - newSelection.y = v.y; - newSelection.z = v.z; - if (buttonSelectedIndex == 0) - { - if (msg.Type == Gui::SelectionChanges::AddSelection) - { - if (shape.ShapeType() == TopAbs_VERTEX) - { - //if we have previous selection it should be only one vertex. - if (selections1.selections.size() > 1) - selections1.selections.clear(); - else if(selections1.selections.size() == 1) - { - //make sure it is a vertex. - if (selections1.selections.at(0).shapeType != DimSelections::Vertex) - selections1.selections.clear(); - } - - newSelection.shapeType = DimSelections::Vertex; - selections1.selections.push_back(newSelection); - if (selections1.selections.size() == 1) - return; - //here we should have 2 vertices, but will check to make sure. - assert(selections1.selections.size() == 2); - assert(selections1.selections.at(0).shapeType == DimSelections::Vertex); - assert(selections1.selections.at(1).shapeType == DimSelections::Vertex); - - QTimer::singleShot(0, this, &PartGui::TaskMeasureAngular::selectionClearDelayedSlot); - stepped->getButton(1)->setEnabled(true); - stepped->getButton(1)->setChecked(true); - return; - } - - //here there should only be one in the selections container. so just clear it. - selections1.selections.clear(); - - if (shape.ShapeType() == TopAbs_EDGE) - { - newSelection.shapeType = DimSelections::Edge; - selections1.selections. push_back(newSelection); - } - - if (shape.ShapeType() == TopAbs_FACE) - { - newSelection.shapeType = DimSelections::Face; - selections1.selections.push_back(newSelection); - } - - QTimer::singleShot(0, this, &PartGui::TaskMeasureAngular::selectionClearDelayedSlot); - stepped->getButton(1)->setEnabled(true); - stepped->getButton(1)->setChecked(true); - return; - } - } - if (buttonSelectedIndex == 1) - { - if (msg.Type == Gui::SelectionChanges::AddSelection) - { - if (shape.ShapeType() == TopAbs_VERTEX) - { - //if we have previous selection it should be only one vertex. - if (selections2.selections.size() > 1) - selections2.selections.clear(); - else if(selections2.selections.size() == 1) - { - //make sure it is a vertex. - if (selections2.selections.at(0).shapeType != DimSelections::Vertex) - selections2.selections.clear(); - } - - newSelection.shapeType = DimSelections::Vertex; - selections2.selections.push_back(newSelection); - if (selections2.selections.size() == 1) - return; - //here we should have 2 vertices, but will check to make sure. - assert(selections2.selections.size() == 2); - assert(selections2.selections.at(0).shapeType == DimSelections::Vertex); - assert(selections2.selections.at(1).shapeType == DimSelections::Vertex); - - buildDimension(); - clearSelection(); - QTimer::singleShot(0, this, &PartGui::TaskMeasureAngular::selectionClearDelayedSlot); - stepped->getButton(0)->setChecked(true); - stepped->getButton(1)->setEnabled(false); - return; - } - //vertices have to be selected in succession. if we get here,clear temp selection. - selections2.selections.clear(); - - if (shape.ShapeType() == TopAbs_EDGE) - { - newSelection.shapeType = DimSelections::Edge; - selections2.selections. push_back(newSelection); - } - - if (shape.ShapeType() == TopAbs_FACE) - { - newSelection.shapeType = DimSelections::Face; - selections2.selections.push_back(newSelection); - } - - buildDimension(); - clearSelection(); - QTimer::singleShot(0, this, &PartGui::TaskMeasureAngular::selectionClearDelayedSlot); - stepped->getButton(0)->setChecked(true); - stepped->getButton(1)->setEnabled(false); - return; - } - } -} - -void PartGui::TaskMeasureAngular::selectionClearDelayedSlot() -{ - //hack. - //clearing selections are not working as I hoped. Apparently the observer callback gets called - //before the actual selection takes place. Resulting in selections being left. this addresses this - //by being called from the event loop. - this->blockSelection(true); - Gui::Selection().clearSelection(); - this->blockSelection(false); -} - -VectorAdapter PartGui::TaskMeasureAngular::buildAdapter(const PartGui::DimSelections& selection) -{ - Base::Matrix4D mat; - assert(selection.selections.size() > 0 && selection.selections.size() < 3); - if (selection.selections.size() == 1) - { - DimSelections::DimSelection current = selection.selections.at(0); - if (current.shapeType == DimSelections::Edge) - { - TopoDS_Shape edgeShape; - if (!getShapeFromStrings(edgeShape, current.documentName, current.objectName, current.subObjectName,&mat)) - return {}; - TopoDS_Edge edge = TopoDS::Edge(edgeShape); - // make edge orientation so that end of edge closest to pick is head of vector. - TopoDS_Vertex firstVertex = TopExp::FirstVertex(edge, Standard_True); - TopoDS_Vertex lastVertex = TopExp::LastVertex(edge, Standard_True); - if (firstVertex.IsNull() || lastVertex.IsNull()) - return {}; - gp_Vec firstPoint = PartGui::convert(firstVertex); - gp_Vec lastPoint = PartGui::convert(lastVertex); - Base::Vector3d v(current.x,current.y,current.z); - v = mat*v; - gp_Vec pickPoint(v.x, v.y, v.z); - double firstDistance = (firstPoint - pickPoint).Magnitude(); - double lastDistance = (lastPoint - pickPoint).Magnitude(); - if (lastDistance > firstDistance) - { - if (edge.Orientation() == TopAbs_FORWARD) - edge.Orientation(TopAbs_REVERSED); - else - edge.Orientation(TopAbs_FORWARD); - } - return VectorAdapter(edge, pickPoint); - } - if (current.shapeType == DimSelections::Face) - { - TopoDS_Shape faceShape; - if (!getShapeFromStrings(faceShape, current.documentName, current.objectName, current.subObjectName,&mat)) - return {}; - - TopoDS_Face face = TopoDS::Face(faceShape); - Base::Vector3d v(current.x,current.y,current.z); - v = mat*v; - gp_Vec pickPoint(v.x, v.y, v.z); - return VectorAdapter(face, pickPoint); - } - } - //selection size == 2. - DimSelections::DimSelection current1 = selection.selections.at(0); - DimSelections::DimSelection current2 = selection.selections.at(1); - assert(current1.shapeType == DimSelections::Vertex); - assert(current2.shapeType == DimSelections::Vertex); - TopoDS_Shape vertexShape1, vertexShape2; - if(!getShapeFromStrings(vertexShape1, current1.documentName, current1.objectName, current1.subObjectName)) - return {}; - if(!getShapeFromStrings(vertexShape2, current2.documentName, current2.objectName, current2.subObjectName)) - return {}; - - TopoDS_Vertex vertex1 = TopoDS::Vertex(vertexShape1); - TopoDS_Vertex vertex2 = TopoDS::Vertex(vertexShape2); - - //build a temp adapter to make sure it is valid. - return VectorAdapter(PartGui::convert(vertex2), PartGui::convert(vertex1)); -} - -void PartGui::TaskMeasureAngular::buildDimension() { - buildDimension(selections1,selections2); -} - -void PartGui::TaskMeasureAngular::buildDimension(const DimSelections &sel1, const DimSelections &sel2) -{ - //build adapters. - VectorAdapter adapt1 = buildAdapter(sel1); - VectorAdapter adapt2 = buildAdapter(sel2); - - if (!adapt1.isValid() || !adapt2.isValid()) - { - Base::Console().Message("\ncouldn't build adapter\n\n"); - return; - } - auto doc = App::GetApplication().getActiveDocument(); - if(doc) - _Measures[doc->getName()].emplace_back(sel1,sel2,false); - goDimensionAngularNoTask(adapt1, adapt2); -} - -void PartGui::TaskMeasureAngular::clearSelection() -{ - selections1.selections.clear(); - selections2.selections.clear(); -} - -void PartGui::TaskMeasureAngular::setUpGui() -{ - QPixmap mainIcon = Gui::BitmapFactory().pixmap("Part_Measure_Angular"); - - Gui::TaskView::TaskBox* selectionTaskBox = new Gui::TaskView::TaskBox - (mainIcon, QObject::tr("Selections"), false, nullptr); - QVBoxLayout *selectionLayout = new QVBoxLayout(); - stepped = new SteppedSelection(2, selectionTaskBox); - selectionLayout->addWidget(stepped); - selectionTaskBox->groupLayout()->addLayout(selectionLayout); - - Gui::TaskView::TaskBox* controlTaskBox = new Gui::TaskView::TaskBox - (mainIcon, QObject::tr("Control"), false, nullptr); - QVBoxLayout *controlLayout = new QVBoxLayout(); - - DimensionControl *control = new DimensionControl(controlTaskBox); - controlLayout->addWidget(control); - controlTaskBox->groupLayout()->addLayout(controlLayout); - QObject::connect(control->resetButton, &QPushButton::clicked, this, &TaskMeasureAngular::resetDialogSlot); - - this->setButtonPosition(TaskDialog::North); - Content.push_back(selectionTaskBox); - Content.push_back(controlTaskBox); - - stepped->getButton(0)->setChecked(true);//before wired up. - stepped->getButton(0)->setEnabled(true); - QObject::connect(stepped->getButton(0), &QPushButton::toggled, this, &TaskMeasureAngular::selection1Slot); - QObject::connect(stepped->getButton(1), &QPushButton::toggled, this, &TaskMeasureAngular::selection2Slot); -} - -void PartGui::TaskMeasureAngular::selection1Slot(bool checked) -{ - if (checked) - { - buttonSelectedIndex = 0; - this->blockSelection(true); - Gui::Selection().clearSelection(); - std::vector::const_iterator it; - for (it = selections1.selections.begin(); it != selections1.selections.end(); ++it) - Gui::Selection().addSelection(it->documentName.c_str(), it->objectName.c_str(), it->subObjectName.c_str()); - this->blockSelection(false); - } - else - { - if (!selections1.selections.empty()) - stepped->setIconDone(0); - } -} - -void PartGui::TaskMeasureAngular::selection2Slot(bool checked) -{ - if (checked) - buttonSelectedIndex = 1; - this->blockSelection(true); - Gui::Selection().clearSelection(); - std::vector::const_iterator it; - for (it = selections2.selections.begin(); it != selections2.selections.end(); ++it) - Gui::Selection().addSelection(it->documentName.c_str(), it->objectName.c_str(), it->subObjectName.c_str()); - this->blockSelection(false); -} - -void PartGui::TaskMeasureAngular::resetDialogSlot(bool) -{ - clearSelection(); - this->blockSelection(true); - Gui::Selection().clearSelection(); - stepped->getButton(0)->setChecked(true); - stepped->getButton(1)->setEnabled(false); - this->blockSelection(false); -} - -void PartGui::TaskMeasureAngular::toggle3dSlot(bool) -{ - PartGui::toggle3d(); -} - -void PartGui::TaskMeasureAngular::toggleDeltaSlot(bool) -{ - PartGui::toggleDelta(); -} - -void PartGui::TaskMeasureAngular::clearAllSlot(bool) -{ - PartGui::eraseAllDimensions(); -} - -#include "moc_TaskDimension.cpp" diff --git a/src/Mod/Part/Gui/TaskDimension.h b/src/Mod/Part/Gui/TaskDimension.h deleted file mode 100644 index 5f17ed8de3..0000000000 --- a/src/Mod/Part/Gui/TaskDimension.h +++ /dev/null @@ -1,337 +0,0 @@ -/*************************************************************************** - * Copyright (c) 2013 Thomas Anderson * - * * - * 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 TASKDIMENSION_H -#define TASKDIMENSION_H - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - - -class TopoDS_Shape; -class TopoDS_Face; -class TopoDS_Edge; -class TopoDS_Vertex; -class gp_Pnt; -class BRepExtrema_DistShapeShape; - -class QPushButton; -class QPixmap; -class QLabel; - -using namespace Part; - -namespace Gui{class View3dInventorViewer;} - -namespace PartGui -{ - /*!find shape from selection strings - * @param shapeOut search results. - * @param doc document name to search. - * @param object object name to search. - * @param sub sub-object name to search. - * @return signal if the search was successful. - */ - bool getShapeFromStrings(TopoDS_Shape &shapeOut, const std::string &doc, const std::string &object, const std::string &sub, Base::Matrix4D *mat=nullptr); - /*!examine pre selection - * @param shape1 first shape in current selection - * @param shape2 second shape in current selection - * @return signal if preselection is valid. false means shape1 and shape2 are invalid. - */ - bool evaluateLinearPreSelection(TopoDS_Shape &shape1, TopoDS_Shape &shape2); - /*!start of the measure linear command*/ - void goDimensionLinearRoot(); - /*!does the measure and create dimensions without a dialog - * @param shape1 first shape. - * @param shape2 second shape. - * @todo incorporate some form of "adapt to topods_shape". so we can expand to other types outside OCC. - */ - void goDimensionLinearNoTask(const TopoDS_Shape &shape1, const TopoDS_Shape &shape2); - /*!prints results of measuring to console. - * @param measure object containing the measure information - */ - void dumpLinearResults(const BRepExtrema_DistShapeShape &measure); - /*!convenience function to get the viewer*/ - Gui::View3DInventorViewer* getViewer(); - /*!adds 3d and delta dimensions to the viewer - * @param measure object containing the measure information. - */ - void addLinearDimensions(const BRepExtrema_DistShapeShape &measure); - /*!creates one dimension from points with color - * @param point1 first point - * @param point2 second point - * @param color color of dimension - * @return an inventor node to add to a scenegraph - */ - SoNode* createLinearDimension(const gp_Pnt &point1, const gp_Pnt &point2, const SbColor &color); - /*!erases all the dimensions in the viewer.*/ - void eraseAllDimensions(); - /*!refresh all the dimensions in the viewer.*/ - void refreshDimensions(); - /*!toggles the display status of the 3d dimensions*/ - void toggle3d(); - /*!toggles the display status of the delta dimensions*/ - void toggleDelta(); - /*!make sure measure command isn't working with everything invisible. Confusing the user*/ - void ensureSomeDimensionVisible(); - /*!make sure angle measure command isn't working with 3d off. Confusing the user*/ - void ensure3dDimensionVisible(); - /*convert a vertex to vector*/ - gp_Vec convert(const TopoDS_Vertex &vertex); - - auto getDimensionsFontName(); - auto getDimensionsFontSize(); - -class DimensionLinear : public SoSeparatorKit -{ - SO_KIT_HEADER(DimensionLinear); - - SO_KIT_CATALOG_ENTRY_HEADER(transformation); - SO_KIT_CATALOG_ENTRY_HEADER(annotate); - SO_KIT_CATALOG_ENTRY_HEADER(leftArrow); - SO_KIT_CATALOG_ENTRY_HEADER(rightArrow); - SO_KIT_CATALOG_ENTRY_HEADER(line); - SO_KIT_CATALOG_ENTRY_HEADER(textSep); -public: - DimensionLinear(); - static void initClass(); - SbBool affectsState() const override; - void setupDimension(); - - SoSFVec3f point1; - SoSFVec3f point2; - SoSFString text; - SoSFColor dColor; -protected: - SoSFRotation rotate; - SoSFFloat length; - SoSFVec3f origin; - -private: - ~DimensionLinear() override; -}; - -/*kit for anglular dimensions*/ -class DimensionAngular : public SoSeparatorKit -{ - SO_KIT_HEADER(DimensionAngular); - - SO_KIT_CATALOG_ENTRY_HEADER(transformation); - SO_KIT_CATALOG_ENTRY_HEADER(annotate); - SO_KIT_CATALOG_ENTRY_HEADER(arrow1); - SO_KIT_CATALOG_ENTRY_HEADER(arrow2); - SO_KIT_CATALOG_ENTRY_HEADER(arcSep); - SO_KIT_CATALOG_ENTRY_HEADER(textSep); -public: - DimensionAngular(); - static void initClass(); - SbBool affectsState() const override; - - SoSFFloat radius;//radians. - SoSFFloat angle;//radians. - SoSFString text; - SoSFColor dColor; - SoSFMatrix matrix; - void setupDimension(); -private: - ~DimensionAngular() override; -}; - -/*used for generating points for arc display*/ -class ArcEngine : public SoEngine -{ - SO_ENGINE_HEADER(ArcEngine); -public: - ArcEngine(); - static void initClass(); - - SoSFFloat radius; - SoSFFloat angle; - SoSFFloat deviation; - - SoEngineOutput points; - SoEngineOutput pointCount; -protected: - void evaluate() override; -private: - ~ArcEngine() override = default; - void defaultValues(); //some non error values if something goes wrong. -}; - -/*! a widget with buttons and icons for a controlled selection process*/ -class SteppedSelection : public QWidget -{ - Q_OBJECT -public: - explicit SteppedSelection(const uint &buttonCountIn, QWidget *parent = nullptr); - ~SteppedSelection() override; - QPushButton* getButton(const uint &index); - void setIconDone(const uint &index); - -protected: - using ButtonIconPairType = std::pair; - std::vector buttons; - QPixmap *stepActive; - QPixmap *stepDone; - -private Q_SLOTS: - void selectionSlot(bool checked); - void buildPixmaps(); - -}; - -/*! just convenience container*/ -class DimSelections -{ -public: - enum ShapeType{None, Vertex, Edge, Face}; - struct DimSelection - { - std::string documentName; - std::string objectName; - std::string subObjectName; - float x; - float y; - float z; - ShapeType shapeType; - }; - std::vector selections; -}; - -/*!widget for buttons controlling the display of dimensions*/ -class DimensionControl : public QWidget -{ - Q_OBJECT -public: - explicit DimensionControl(QWidget* parent); - QPushButton *resetButton; -public Q_SLOTS: - void toggle3dSlot(bool); - void toggleDeltaSlot(bool); - void clearAllSlot(bool); -}; - -/*!linear dialog*/ -class TaskMeasureLinear : public Gui::TaskView::TaskDialog, public Gui::SelectionObserver -{ - Q_OBJECT -public: - TaskMeasureLinear(); - ~TaskMeasureLinear() override; - - QDialogButtonBox::StandardButtons getStandardButtons() const override - {return QDialogButtonBox::Close;} - bool isAllowedAlterDocument() const override {return false;} - bool needsFullSpace() const override {return false;} -protected: - void onSelectionChanged(const Gui::SelectionChanges& msg) override; - -protected Q_SLOTS: - void selection1Slot(bool checked); - void selection2Slot(bool checked); - void resetDialogSlot(bool); - void toggle3dSlot(bool); - void toggleDeltaSlot(bool); - void clearAllSlot(bool); - void selectionClearDelayedSlot(); - -public: - static void buildDimension(const DimSelections &sel1, const DimSelections &sel2); - -private: - void setUpGui(); - void buildDimension(); - void clearSelectionStrings(); - DimSelections selections1; - DimSelections selections2; - uint buttonSelectedIndex; - SteppedSelection *stepped; - -}; - - -/*!angular dialog class*/ -class TaskMeasureAngular : public Gui::TaskView::TaskDialog, public Gui::SelectionObserver -{ - Q_OBJECT -public: - TaskMeasureAngular(); - ~TaskMeasureAngular() override; - - QDialogButtonBox::StandardButtons getStandardButtons() const override - {return QDialogButtonBox::Close;} - bool isAllowedAlterDocument() const override {return false;} - bool needsFullSpace() const override {return false;} -protected: - void onSelectionChanged(const Gui::SelectionChanges& msg) override; - -protected Q_SLOTS: - void selection1Slot(bool checked); - void selection2Slot(bool checked); - void resetDialogSlot(bool); - void toggle3dSlot(bool); - void toggleDeltaSlot(bool); - void clearAllSlot(bool); - void selectionClearDelayedSlot(); - -public: - static void buildDimension(const DimSelections &sel1, const DimSelections &sel2); - -private: - void buildDimension(); - void setUpGui(); - void clearSelection(); - DimSelections selections1; - DimSelections selections2; - uint buttonSelectedIndex; - SteppedSelection *stepped; - static VectorAdapter buildAdapter(const DimSelections &selection); -}; - -/*!start of the measure angular command*/ -void goDimensionAngularRoot(); -/*!examine angular pre selection - * @param vector1Out first shape in current selection - * @param vector2Out second shape in current selection - * @return signal if preselection is valid. false means vector1Out and vector2Out are invalid. - */ -bool evaluateAngularPreSelection(VectorAdapter &vector1Out, VectorAdapter &vector2Out); -/*!build angular dimension*/ -void goDimensionAngularNoTask(const VectorAdapter &vector1Adapter, const VectorAdapter &vector2Adapter); -} - -#endif // TASKDIMENSION_H diff --git a/src/Mod/Part/Gui/Workbench.cpp b/src/Mod/Part/Gui/Workbench.cpp index ee7d9e8790..e2a1b4453f 100644 --- a/src/Mod/Part/Gui/Workbench.cpp +++ b/src/Mod/Part/Gui/Workbench.cpp @@ -41,7 +41,6 @@ using namespace PartGui; qApp->translate("Workbench", "Split"); qApp->translate("Workbench", "Compound"); qApp->translate("Workbench", "Create a copy"); - qApp->translate("Workbench", "Measure"); #endif /// @namespace PartGui @class Workbench @@ -140,18 +139,6 @@ Gui::MenuItem* Workbench::setupMenuBar() const << "Separator" << "Part_EditAttachment"; - Gui::MenuItem* measure = new Gui::MenuItem; - root->insertItem(item,measure); - measure->setCommand("Measure"); - *measure << "Part_Measure_Linear" - << "Part_Measure_Angular" - << "Separator" - << "Part_Measure_Refresh" - << "Part_Measure_Clear_All" - << "Part_Measure_Toggle_All" - << "Part_Measure_Toggle_3D" - << "Part_Measure_Toggle_Delta"; - Gui::MenuItem* view = root->findItem("&View"); if (view) { Gui::MenuItem* appr = view->findItem("Std_RandomColor"); @@ -210,17 +197,6 @@ Gui::ToolBarItem* Workbench::setupToolBars() const << "Part_CheckGeometry" << "Part_Defeaturing"; - Gui::ToolBarItem* measure = new Gui::ToolBarItem(root); - measure->setCommand("Measure"); - *measure << "Part_Measure_Linear" - << "Part_Measure_Angular" - << "Separator" - << "Part_Measure_Refresh" - << "Part_Measure_Clear_All" - << "Part_Measure_Toggle_All" - << "Part_Measure_Toggle_3D" - << "Part_Measure_Toggle_Delta"; - return root; } diff --git a/src/Mod/Part/Gui/WorkbenchManipulator.cpp b/src/Mod/Part/Gui/WorkbenchManipulator.cpp index d8dc4d6ffc..b6a14976dc 100644 --- a/src/Mod/Part/Gui/WorkbenchManipulator.cpp +++ b/src/Mod/Part/Gui/WorkbenchManipulator.cpp @@ -34,13 +34,6 @@ void WorkbenchManipulator::modifyMenuBar([[maybe_unused]] Gui::MenuItem* menuBar addSectionCut(menuBar); } -void WorkbenchManipulator::modifyContextMenu(const char* recipient, Gui::MenuItem* menuBar) -{ - if (strcmp(recipient, "View") == 0) { - addSelectionFilter(menuBar); - } -} - void WorkbenchManipulator::modifyToolBars(Gui::ToolBarItem* toolBar) { addSelectionFilter(toolBar); @@ -78,12 +71,3 @@ void WorkbenchManipulator::addSelectionFilter(Gui::ToolBarItem* toolBar) } } } - -void WorkbenchManipulator::addSelectionFilter(Gui::MenuItem* menuBar) -{ - if (auto measure = menuBar->findItem("Measure")) { - auto add = new Gui::MenuItem(); // NOLINT - add->setCommand("Part_SelectFilter"); - menuBar->insertItem(measure, add); - } -} diff --git a/src/Mod/Part/Gui/WorkbenchManipulator.h b/src/Mod/Part/Gui/WorkbenchManipulator.h index 745e7556b4..338dd3243d 100644 --- a/src/Mod/Part/Gui/WorkbenchManipulator.h +++ b/src/Mod/Part/Gui/WorkbenchManipulator.h @@ -38,12 +38,6 @@ protected: * The default implementation doesn't change anything.SectionCut */ void modifyMenuBar(Gui::MenuItem* menuBar) override; - /*! - * \brief modifyContextMenu - * Method to manipulate the contextmenu structure of a workbench. - * The default implementation doesn't change anything. - */ - void modifyContextMenu(const char* recipient, Gui::MenuItem* menuBar) override; /*! * \brief modifyToolBars * Method to manipulate the toolbar structure of a workbench @@ -60,7 +54,6 @@ protected: private: static void addSectionCut(Gui::MenuItem* menuBar); static void addSelectionFilter(Gui::ToolBarItem* toolBar); - static void addSelectionFilter(Gui::MenuItem* menuBar); }; } // namespace PartGui diff --git a/src/Mod/PartDesign/Gui/Workbench.cpp b/src/Mod/PartDesign/Gui/Workbench.cpp index 408d5b9172..539f856705 100644 --- a/src/Mod/PartDesign/Gui/Workbench.cpp +++ b/src/Mod/PartDesign/Gui/Workbench.cpp @@ -580,20 +580,6 @@ Gui::MenuItem* Workbench::setupMenuBar() const *part << "Separator" << "PartDesign_WizardShaft"; } - // use Part's measure features also for PartDesign - Gui::MenuItem* measure = new Gui::MenuItem; - root->insertItem(item, measure); - measure->setCommand("Measure"); - - *measure << "Part_Measure_Linear" - << "Part_Measure_Angular" - << "Separator" - << "Part_Measure_Refresh" - << "Part_Measure_Clear_All" - << "Part_Measure_Toggle_All" - << "Part_Measure_Toggle_3D" - << "Part_Measure_Toggle_Delta"; - Gui::MenuItem* view = root->findItem("&View"); if (view) { Gui::MenuItem* appr = view->findItem("Std_RandomColor"); @@ -662,19 +648,6 @@ Gui::ToolBarItem* Workbench::setupToolBars() const << "Separator" << "PartDesign_Boolean"; - // use Part's measure features also for PartDesign - Gui::ToolBarItem* measure = new Gui::ToolBarItem(root); - measure->setCommand("Measure"); - - *measure << "Part_Measure_Linear" - << "Part_Measure_Angular" - << "Separator" - << "Part_Measure_Refresh" - << "Part_Measure_Clear_All" - << "Part_Measure_Toggle_All" - << "Part_Measure_Toggle_3D" - << "Part_Measure_Toggle_Delta"; - return root; }