From 3c94e3d87e0c01aeb9cdcb33769b81140fbe5994 Mon Sep 17 00:00:00 2001 From: Uwe Date: Sat, 11 Feb 2023 06:47:48 +0100 Subject: [PATCH 01/42] [FEM] add new filter to display iso contours - very valuable to display for example electromagnetic field lines --- src/Mod/Fem/App/AppFem.cpp | 1 + src/Mod/Fem/App/FemPostFilter.cpp | 278 +++++++++++++++++- src/Mod/Fem/App/FemPostFilter.h | 41 +++ src/Mod/Fem/Gui/AppFemGui.cpp | 1 + src/Mod/Fem/Gui/CMakeLists.txt | 2 + src/Mod/Fem/Gui/Command.cpp | 133 ++++++--- src/Mod/Fem/Gui/Resources/Fem.qrc | 1 + .../icons/FEM_PostFilterContours.svg | 70 +++++ src/Mod/Fem/Gui/TaskPostBoxes.cpp | 134 +++++++++ src/Mod/Fem/Gui/TaskPostBoxes.h | 25 ++ src/Mod/Fem/Gui/TaskPostContours.ui | 87 ++++++ src/Mod/Fem/Gui/ViewProviderFemPostFilter.cpp | 18 +- src/Mod/Fem/Gui/ViewProviderFemPostFilter.h | 14 + src/Mod/Fem/Gui/Workbench.cpp | 2 + src/Mod/Fem/ObjectsFem.py | 15 + src/Mod/Fem/femtest/app/test_femimport.py | 1 + src/Mod/Fem/femtest/app/test_object.py | 7 +- src/Mod/Fem/femviewprovider/view_mesh_gmsh.py | 26 +- 18 files changed, 788 insertions(+), 68 deletions(-) create mode 100644 src/Mod/Fem/Gui/Resources/icons/FEM_PostFilterContours.svg create mode 100644 src/Mod/Fem/Gui/TaskPostContours.ui diff --git a/src/Mod/Fem/App/AppFem.cpp b/src/Mod/Fem/App/AppFem.cpp index b65f17d3ec..fe004396fc 100644 --- a/src/Mod/Fem/App/AppFem.cpp +++ b/src/Mod/Fem/App/AppFem.cpp @@ -178,6 +178,7 @@ PyMOD_INIT_FUNC(Fem) Fem::FemPostPipeline ::init(); Fem::FemPostFilter ::init(); Fem::FemPostClipFilter ::init(); + Fem::FemPostContoursFilter ::init(); Fem::FemPostCutFilter ::init(); Fem::FemPostDataAlongLineFilter ::init(); Fem::FemPostDataAtPointFilter ::init(); diff --git a/src/Mod/Fem/App/FemPostFilter.cpp b/src/Mod/Fem/App/FemPostFilter.cpp index 633282a1b5..1127295797 100644 --- a/src/Mod/Fem/App/FemPostFilter.cpp +++ b/src/Mod/Fem/App/FemPostFilter.cpp @@ -24,6 +24,7 @@ #ifndef _PreComp_ # include +# include # include #endif @@ -68,23 +69,22 @@ DocumentObjectExecReturn* FemPostFilter::execute() { if (!m_pipelines.empty() && !m_activePipeline.empty()) { FemPostFilter::FilterPipeline& pipe = m_pipelines[m_activePipeline]; - if (m_activePipeline.length() >= 11) { - std::string LineClip = m_activePipeline.substr(0, 13); - std::string PointClip = m_activePipeline.substr(0, 11); - if ((LineClip == "DataAlongLine") || (PointClip == "DataAtPoint")) { + vtkSmartPointer data = getInputData(); + if (!data || !data->IsA("vtkDataSet")) + return StdReturn; + + if ((m_activePipeline == "DataAlongLine") || (m_activePipeline == "DataAtPoint")) { pipe.filterSource->SetSourceData(getInputData()); pipe.filterTarget->Update(); - Data.setValue(pipe.filterTarget->GetOutputDataObject(0)); - } } else { - pipe.source->SetInputDataObject(getInputData()); + pipe.source->SetInputDataObject(data); pipe.target->Update(); Data.setValue(pipe.target->GetOutputDataObject(0)); } - } + return StdReturn; } @@ -129,7 +129,7 @@ FemPostClipFilter::FemPostClipFilter() : FemPostFilter() { (false), "Clip", App::Prop_None, - "Decides if cells are cuttet and interpolated or if the cells are kept as a whole"); + "Decides if cells are cut and interpolated or if the cells are kept as a whole"); FilterPipeline clip; m_clipper = vtkSmartPointer::New(); @@ -187,7 +187,8 @@ short int FemPostClipFilter::mustExecute() const { return 1; } - else return App::DocumentObject::mustExecute(); + else + return App::DocumentObject::mustExecute(); } DocumentObjectExecReturn* FemPostClipFilter::execute() { @@ -711,3 +712,260 @@ DocumentObjectExecReturn* FemPostCutFilter::execute() return Fem::FemPostFilter::execute(); } + +// *************************************************************************** +// contours filter +PROPERTY_SOURCE(Fem::FemPostContoursFilter, Fem::FemPostFilter) + +FemPostContoursFilter::FemPostContoursFilter() + : FemPostFilter() +{ + ADD_PROPERTY_TYPE(NumberOfContours, (10), "Contours", + App::Prop_None, "The number of contours"); + ADD_PROPERTY_TYPE(Field, (long(0)), "Clip", + App::Prop_None, "The field used to clip"); + ADD_PROPERTY_TYPE(VectorMode, ((long)0), "Contours", + App::Prop_None, "Select what vector field"); + ADD_PROPERTY_TYPE(NoColor, (false), "Contours", + App::Prop_None, "Don't color the contours"); + + m_contourConstraints.LowerBound = 1; + m_contourConstraints.UpperBound = 1000; + m_contourConstraints.StepSize = 1; + NumberOfContours.setConstraints(&m_contourConstraints); + + FilterPipeline contours; + m_contours = vtkSmartPointer::New(); + m_contours->ComputeScalarsOn(); + contours.source = m_contours; + contours.target = m_contours; + addFilterPipeline(contours, "contours"); + setActiveFilterPipeline("contours"); +} + +FemPostContoursFilter::~FemPostContoursFilter() +{ +} + +DocumentObjectExecReturn* FemPostContoursFilter::execute() +{ + // update list of available fields and their vectors + if (!m_blockPropertyChanges) { + refreshFields(); + refreshVectors(); + } + + // recalculate the filter + auto returnObject = Fem::FemPostFilter::execute(); + + // delete contour field + vtkSmartPointer data = getInputData(); + vtkDataSet* dset = vtkDataSet::SafeDownCast(data); + vtkPointData* pd = dset->GetPointData(); + dset->GetPointData()->RemoveArray(contourFieldName.c_str()); + // refresh fields to reflect the deletion + if (!m_blockPropertyChanges) + refreshFields(); + + return returnObject; +} + +void FemPostContoursFilter::onChanged(const Property* prop) +{ + if (m_blockPropertyChanges) + return; + + if (prop == &Field && (Field.getValue() >= 0)) + refreshVectors(); + + // note that we need to calculate also in case of a Data change + // otherwise the contours output would be empty and the ViewProviderFemPostObject + // would not get any data + if ((prop == &Field || prop == &VectorMode || prop == &NumberOfContours || prop == &Data) + && (Field.getValue() >= 0)) { + double p[2]; + + // get the field and its data + vtkSmartPointer data = getInputData(); + if (!data || !data->IsA("vtkDataSet")) + return; + vtkDataSet* dset = vtkDataSet::SafeDownCast(data); + vtkDataArray* pdata = dset->GetPointData()->GetArray(Field.getValueAsString()); + + if (!pdata) + return; + if (pdata->GetNumberOfComponents() == 1) { + // if we have a scalar, we can directly use the array + m_contours->SetInputArrayToProcess( + 0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_POINTS, Field.getValueAsString()); + pdata->GetRange(p); + recalculateContours(p[0], p[1]); + } + else { + // The contour filter handles vectors by taking always its first component. + // There is no other solution than to make the desired vectorn component a + // scalar array and append this temporarily to the data. (vtkExtractVectorComponents + // does not work because our data is an unstructured data set.) + int component = -1; + if (VectorMode.getValue() == 1) + component = 0; + else if (VectorMode.getValue() == 2) + component = 1; + else if (VectorMode.getValue() == 3) + component = 2; + // extract the component to a new array + vtkSmartPointer componentArray = vtkSmartPointer::New(); + componentArray->SetNumberOfComponents(1); + vtkIdType numTuples = pdata->GetNumberOfTuples(); + componentArray->SetNumberOfTuples(numTuples); + + if (component >= 0) { + for (vtkIdType tupleIdx = 0; tupleIdx < numTuples; ++tupleIdx) { + componentArray->SetComponent( + tupleIdx, 0, pdata->GetComponent(tupleIdx, component)); + } + } + else { + for (vtkIdType tupleIdx = 0; tupleIdx < numTuples; ++tupleIdx) { + componentArray->SetComponent(tupleIdx, 0, + std::sqrt(pdata->GetComponent(tupleIdx, 0) * + pdata->GetComponent(tupleIdx, 0) + + pdata->GetComponent(tupleIdx, 1) * + pdata->GetComponent(tupleIdx, 1) + + pdata->GetComponent(tupleIdx, 2) * + pdata->GetComponent(tupleIdx, 2) ) ); + } + } + // name the array + contourFieldName = + std::string(Field.getValueAsString()) + "_contour"; + componentArray->SetName(contourFieldName.c_str()); + + // add the array as new field and use it for the contour filter + dset->GetPointData()->AddArray(componentArray); + m_contours->SetInputArrayToProcess(0, 0, 0, + vtkDataObject::FIELD_ASSOCIATION_POINTS, contourFieldName.c_str()); + componentArray->GetRange(p); + recalculateContours(p[0], p[1]); + if (prop == &Data) { + // we must recalculate to pass the new created contours field + // to ViewProviderFemPostObject + m_blockPropertyChanges = true; + execute(); + m_blockPropertyChanges = false; + } + } + } + + Fem::FemPostFilter::onChanged(prop); +} + +short int FemPostContoursFilter::mustExecute() const +{ + if (Field.isTouched() || VectorMode.isTouched() || NumberOfContours.isTouched() + || Data.isTouched()) + return 1; + else + return App::DocumentObject::mustExecute(); +} + +void FemPostContoursFilter::recalculateContours(double min, double max) +{ + // As the min and max contours are not visible, an input of "3" leads + // to 1 visible contour. To not confuse the user, take the visible contours + // for NumberOfContours + int visibleNum = NumberOfContours.getValue() + 2; + m_contours->GenerateValues(visibleNum, min, max); +} + +void FemPostContoursFilter::refreshFields() +{ + m_blockPropertyChanges = true; + + std::string fieldName; + if (Field.getValue() >= 0) + fieldName = Field.getValueAsString(); + + std::vector FieldsArray; + + vtkSmartPointer data = getInputData(); + if (!data || !data->IsA("vtkDataSet")) { + m_blockPropertyChanges = false; + return; + } + vtkDataSet* dset = vtkDataSet::SafeDownCast(data); + vtkPointData* pd = dset->GetPointData(); + + // get all fields + for (int i = 0; i < pd->GetNumberOfArrays(); ++i) { + FieldsArray.emplace_back(pd->GetArrayName(i)); + } + + App::Enumeration empty; + Field.setValue(empty); + m_fields.setEnums(FieldsArray); + Field.setValue(m_fields); + + // search if the current field is in the available ones and set it + std::vector::iterator it = + std::find(FieldsArray.begin(), FieldsArray.end(), fieldName); + if (!fieldName.empty() && it != FieldsArray.end()) { + Field.setValue(fieldName.c_str()); + } + else { + m_blockPropertyChanges = false; + // select the first field + Field.setValue(long(0)); + fieldName = Field.getValueAsString(); + } + + m_blockPropertyChanges = false; +} + +void FemPostContoursFilter::refreshVectors() +{ + // refreshes the list of available vectors for the current Field + m_blockPropertyChanges = true; + + vtkSmartPointer data = getInputData(); + if (!data || !data->IsA("vtkDataSet")) { + m_blockPropertyChanges = false; + return; + } + vtkDataSet* dset = vtkDataSet::SafeDownCast(data); + vtkDataArray* fieldArray = dset->GetPointData()->GetArray(Field.getValueAsString()); + if (!fieldArray) { + m_blockPropertyChanges = false; + return; + } + + // store name if already set + std::string vectorName; + if (VectorMode.hasEnums() && VectorMode.getValue() >= 0) + vectorName = VectorMode.getValueAsString(); + + std::vector vectorArray; + if (fieldArray->GetNumberOfComponents() == 1) + vectorArray.emplace_back("Not a vector"); + else { + vectorArray.emplace_back("Magnitude"); + if (fieldArray->GetNumberOfComponents() >= 2) { + vectorArray.emplace_back("X"); + vectorArray.emplace_back("Y"); + } + if (fieldArray->GetNumberOfComponents() >= 3) { + vectorArray.emplace_back("Z"); + } + } + App::Enumeration empty; + VectorMode.setValue(empty); + m_vectors.setEnums(vectorArray); + VectorMode.setValue(m_vectors); + + // apply stored name + auto it = std::find(vectorArray.begin(), vectorArray.end(), vectorName); + if (!vectorName.empty() && it != vectorArray.end()) + VectorMode.setValue(vectorName.c_str()); + + m_blockPropertyChanges = false; +} diff --git a/src/Mod/Fem/App/FemPostFilter.h b/src/Mod/Fem/App/FemPostFilter.h index 867997b7a9..2ea6bdc38f 100644 --- a/src/Mod/Fem/App/FemPostFilter.h +++ b/src/Mod/Fem/App/FemPostFilter.h @@ -23,9 +23,12 @@ #ifndef Fem_FemPostFilter_H #define Fem_FemPostFilter_H +#include #include #include +#include #include +#include #include #include #include @@ -239,6 +242,44 @@ private: vtkSmartPointer m_cutter; }; +class FemExport FemPostContoursFilter: public FemPostFilter +{ + + PROPERTY_HEADER_WITH_OVERRIDE(Fem::FemPostContoursFilter); + +public: + FemPostContoursFilter(); + ~FemPostContoursFilter() override; + + App::PropertyEnumeration Field; + App::PropertyIntegerConstraint NumberOfContours; + App::PropertyEnumeration VectorMode; + App::PropertyBool NoColor; + + const char* getViewProviderName() const override + { + return "FemGui::ViewProviderFemPostContours"; + } + short int mustExecute() const override; + +protected: + App::DocumentObjectExecReturn* execute() override; + void onChanged(const App::Property* prop) override; + void recalculateContours(double min, double max); + void refreshFields(); + void refreshVectors(); + bool m_blockPropertyChanges = false; + std::string contourFieldName; + +private: + vtkSmartPointer m_contours; + vtkSmartPointer m_extractor; + vtkSmartPointer m_norm; + App::Enumeration m_fields; + App::Enumeration m_vectors; + App::PropertyIntegerConstraint::Constraints m_contourConstraints; +}; + } //namespace Fem diff --git a/src/Mod/Fem/Gui/AppFemGui.cpp b/src/Mod/Fem/Gui/AppFemGui.cpp index 8401da4f02..88d93ab3ba 100644 --- a/src/Mod/Fem/Gui/AppFemGui.cpp +++ b/src/Mod/Fem/Gui/AppFemGui.cpp @@ -152,6 +152,7 @@ PyMOD_INIT_FUNC(FemGui) FemGui::ViewProviderFemPostObject ::init(); FemGui::ViewProviderFemPostPipeline ::init(); FemGui::ViewProviderFemPostClip ::init(); + FemGui::ViewProviderFemPostContours ::init(); FemGui::ViewProviderFemPostCut ::init(); FemGui::ViewProviderFemPostDataAlongLine ::init(); FemGui::ViewProviderFemPostDataAtPoint ::init(); diff --git a/src/Mod/Fem/Gui/CMakeLists.txt b/src/Mod/Fem/Gui/CMakeLists.txt index 61783817c3..a7e84a43b5 100755 --- a/src/Mod/Fem/Gui/CMakeLists.txt +++ b/src/Mod/Fem/Gui/CMakeLists.txt @@ -93,6 +93,7 @@ if(BUILD_FEM_VTK) ${FemGui_UIC_SRCS} TaskPostDisplay.ui TaskPostClip.ui + TaskPostContours.ui TaskPostDataAlongLine.ui TaskPostDataAtPoint.ui TaskPostScalarClip.ui @@ -271,6 +272,7 @@ if(BUILD_FEM_VTK) PlaneWidget.ui SphereWidget.ui TaskPostClip.ui + TaskPostContours.ui TaskPostDataAlongLine.ui TaskPostDataAtPoint.ui TaskPostScalarClip.ui diff --git a/src/Mod/Fem/Gui/Command.cpp b/src/Mod/Fem/Gui/Command.cpp index 15e632524e..45e572ea91 100644 --- a/src/Mod/Fem/Gui/Command.cpp +++ b/src/Mod/Fem/Gui/Command.cpp @@ -1534,13 +1534,14 @@ void setupFilter(Gui::Command* cmd, std::string Name) { // issue error if no post object if (!((selObject->getTypeId() == Base::Type::fromName("Fem::FemPostPipeline")) - || (selObject->getTypeId() == Base::Type::fromName("Fem::FemPostWarpVectorFilter")) - || (selObject->getTypeId() == Base::Type::fromName("Fem::FemPostScalarClipFilter")) - || (selObject->getTypeId() == Base::Type::fromName("Fem::FemPostCutFilter")) - || (selObject->getTypeId() == Base::Type::fromName("Fem::FemPostClipFilter")) - || (selObject->getTypeId() == Base::Type::fromName("Fem::FemPostDataAlongLineFilter")) ) - ) { - QMessageBox::warning(Gui::getMainWindow(), + || (selObject->getTypeId() == Base::Type::fromName("Fem::FemPostClipFilter")) + || (selObject->getTypeId() == Base::Type::fromName("Fem::FemPostContoursFilter")) + || (selObject->getTypeId() == Base::Type::fromName("Fem::FemPostCutFilter")) + || (selObject->getTypeId() == Base::Type::fromName("Fem::FemPostDataAlongLineFilter")) + || (selObject->getTypeId() == Base::Type::fromName("Fem::FemPostScalarClipFilter")) + || (selObject->getTypeId() == Base::Type::fromName("Fem::FemPostWarpVectorFilter")))) { + QMessageBox::warning( + Gui::getMainWindow(), qApp->translate("setupFilter", "Error: no post processing object selected."), qApp->translate("setupFilter", "The filter could not be set up.")); return; @@ -1721,20 +1722,21 @@ bool CmdFemPostClipFilter::isActive() // only allow one object if (getSelection().getSelection().size() > 1) return false; - // only activate if a result is either a post pipeline, scalar, cut or warp filter, - // itself or along line filter + // only activate if a result is either a post pipeline or a possible filter if (getSelection().getObjectsOfType().size() == 1) return true; - else if (getSelection().getObjectsOfType().size() == 1) - return true; - else if (getSelection().getObjectsOfType().size() == 1) - return true; - else if (getSelection().getObjectsOfType().size() == 1) - return true; else if (getSelection().getObjectsOfType().size() == 1) return true; else if (getSelection().getObjectsOfType().size() == 1) return true; + else if (getSelection().getObjectsOfType().size() == 1) + return true; + else if (getSelection().getObjectsOfType().size() == 1) + return true; + else if (getSelection().getObjectsOfType().size() == 1) + return true; + else if (getSelection().getObjectsOfType().size() == 1) + return true; else return false; } @@ -1765,20 +1767,21 @@ bool CmdFemPostCutFilter::isActive() // only allow one object if (getSelection().getSelection().size() > 1) return false; - // only activate if a result is either a post pipeline, scalar, clip or warp filter, - // itself, or along line filter + // only activate if a result is either a post pipeline or a possible filter if (getSelection().getObjectsOfType().size() == 1) return true; - else if (getSelection().getObjectsOfType().size() == 1) - return true; else if (getSelection().getObjectsOfType().size() == 1) return true; - else if (getSelection().getObjectsOfType().size() == 1) + else if (getSelection().getObjectsOfType().size() == 1) return true; else if (getSelection().getObjectsOfType().size() == 1) return true; + else if (getSelection().getObjectsOfType().size() == 1) + return true; else if (getSelection().getObjectsOfType().size() == 1) return true; + else if (getSelection().getObjectsOfType().size() == 1) + return true; else return false; } @@ -1809,14 +1812,16 @@ bool CmdFemPostDataAlongLineFilter::isActive() // only allow one object if (getSelection().getSelection().size() > 1) return false; - // only activate if a result is either a post pipeline, scalar, cut, clip or warp filter + // only activate if a result is either a post pipeline or a possible filter if (getSelection().getObjectsOfType().size() == 1) return true; - else if (getSelection().getObjectsOfType().size() == 1) + else if (getSelection().getObjectsOfType().size() == 1) + return true; + else if (getSelection().getObjectsOfType().size() == 1) return true; else if (getSelection().getObjectsOfType().size() == 1) return true; - else if (getSelection().getObjectsOfType().size() == 1) + else if (getSelection().getObjectsOfType().size() == 1) return true; else if (getSelection().getObjectsOfType().size() == 1) return true; @@ -1852,20 +1857,19 @@ bool CmdFemPostDataAtPointFilter::isActive() // only allow one object if (getSelection().getSelection().size() > 1) return false; - // only activate if a result is either a post pipeline, scalar, cut, clip, - // warp or along line filter + // only activate if a result is either a post pipeline or a possible filter if (getSelection().getObjectsOfType().size() == 1) return true; - else if (getSelection().getObjectsOfType().size() == 1) - return true; - else if (getSelection().getObjectsOfType().size() == 1) - return true; else if (getSelection().getObjectsOfType().size() == 1) return true; - else if (getSelection().getObjectsOfType().size() == 1) + else if (getSelection().getObjectsOfType().size() == 1) return true; else if (getSelection().getObjectsOfType().size() == 1) return true; + else if (getSelection().getObjectsOfType().size() == 1) + return true; + else if (getSelection().getObjectsOfType().size() == 1) + return true; else return false; } @@ -1970,17 +1974,19 @@ bool CmdFemPostScalarClipFilter::isActive() // only allow one object if (getSelection().getSelection().size() > 1) return false; - // only activate if a result is either a post pipeline, clip, cut, warp or along line filter + // only activate if a result is either a post pipeline or a possible other filter if (getSelection().getObjectsOfType().size() == 1) return true; else if (getSelection().getObjectsOfType().size() == 1) return true; + else if (getSelection().getObjectsOfType().size() == 1) + return true; else if (getSelection().getObjectsOfType().size() == 1) return true; - else if (getSelection().getObjectsOfType().size() == 1) - return true; else if (getSelection().getObjectsOfType().size() == 1) return true; + else if (getSelection().getObjectsOfType().size() == 1) + return true; else return false; } @@ -2011,17 +2017,63 @@ bool CmdFemPostWarpVectorFilter::isActive() // only allow one object if (getSelection().getSelection().size() > 1) return false; - // only activate if a result is either a post pipeline, scalar, clip, cut or along line filter + // only activate if a result is either a post pipeline or a possible other filter if (getSelection().getObjectsOfType().size() == 1) - return true; - else if (getSelection().getObjectsOfType().size() == 1) + return true; + else if (getSelection().getObjectsOfType().size() == 1) return true; else if (getSelection().getObjectsOfType().size() == 1) return true; - else if (getSelection().getObjectsOfType().size() == 1) + else if (getSelection().getObjectsOfType().size() == 1) return true; else if (getSelection().getObjectsOfType().size() == 1) return true; + else if (getSelection().getObjectsOfType().size() == 1) + return true; + else + return false; +} + + +//================================================================================================ +DEF_STD_CMD_A(CmdFemPostContoursFilter) + +CmdFemPostContoursFilter::CmdFemPostContoursFilter() + : Command("FEM_PostFilterContours") +{ + sAppModule = "Fem"; + sGroup = QT_TR_NOOP("Fem"); + sMenuText = QT_TR_NOOP("Contours filter"); + sToolTipText = + QT_TR_NOOP("Define/create a contours filter which displays iso contours"); + sWhatsThis = "FEM_PostFilterContours"; + sStatusTip = sToolTipText; + sPixmap = "FEM_PostFilterContours"; +} + +void CmdFemPostContoursFilter::activated(int) +{ + setupFilter(this, "Contours"); +} + +bool CmdFemPostContoursFilter::isActive() +{ + // only allow one object + if (getSelection().getSelection().size() > 1) + return false; + // only activate if a result is either a post pipeline or a possible other filter + if (getSelection().getObjectsOfType().size() == 1) + return true; + else if (getSelection().getObjectsOfType().size() == 1) + return true; + else if (getSelection().getObjectsOfType().size() == 1) + return true; + else if (getSelection().getObjectsOfType().size() == 1) + return true; + else if (getSelection().getObjectsOfType().size() == 1) + return true; + else if (getSelection().getObjectsOfType().size() == 1) + return true; else return false; } @@ -2370,15 +2422,16 @@ void CreateFemCommands() // vtk post processing #ifdef FC_USE_VTK + rcCmdMgr.addCommand(new CmdFemPostApllyChanges); rcCmdMgr.addCommand(new CmdFemPostClipFilter); + rcCmdMgr.addCommand(new CmdFemPostContoursFilter); rcCmdMgr.addCommand(new CmdFemPostCutFilter); rcCmdMgr.addCommand(new CmdFemPostDataAlongLineFilter); rcCmdMgr.addCommand(new CmdFemPostDataAtPointFilter); rcCmdMgr.addCommand(new CmdFemPostLinearizedStressesFilter); - rcCmdMgr.addCommand(new CmdFemPostScalarClipFilter); - rcCmdMgr.addCommand(new CmdFemPostWarpVectorFilter); rcCmdMgr.addCommand(new CmdFemPostFunctions); - rcCmdMgr.addCommand(new CmdFemPostApllyChanges); rcCmdMgr.addCommand(new CmdFemPostPipelineFromResult); + rcCmdMgr.addCommand(new CmdFemPostScalarClipFilter); + rcCmdMgr.addCommand(new CmdFemPostWarpVectorFilter); #endif } diff --git a/src/Mod/Fem/Gui/Resources/Fem.qrc b/src/Mod/Fem/Gui/Resources/Fem.qrc index 7f7fdcc322..25860e232f 100755 --- a/src/Mod/Fem/Gui/Resources/Fem.qrc +++ b/src/Mod/Fem/Gui/Resources/Fem.qrc @@ -70,6 +70,7 @@ icons/FEM_PostFilterClipRegion.svg icons/FEM_PostFilterClipScalar.svg + icons/FEM_PostFilterContours.svg icons/FEM_PostFilterCutFunction.svg icons/FEM_PostFilterDataAlongLine.svg icons/FEM_PostFilterDataAtPoint.svg diff --git a/src/Mod/Fem/Gui/Resources/icons/FEM_PostFilterContours.svg b/src/Mod/Fem/Gui/Resources/icons/FEM_PostFilterContours.svg new file mode 100644 index 0000000000..f438432a70 --- /dev/null +++ b/src/Mod/Fem/Gui/Resources/icons/FEM_PostFilterContours.svg @@ -0,0 +1,70 @@ + + + + + + + + image/svg+xml + + + + [Alexander Gryson] + + + 2017-03-11 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/ + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + [agryson] Alexander Gryson + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/Fem/Gui/TaskPostBoxes.cpp b/src/Mod/Fem/Gui/TaskPostBoxes.cpp index 264baf1664..90ab069f66 100644 --- a/src/Mod/Fem/Gui/TaskPostBoxes.cpp +++ b/src/Mod/Fem/Gui/TaskPostBoxes.cpp @@ -56,6 +56,7 @@ #include "ui_TaskPostDataAtPoint.h" #include "ui_TaskPostDisplay.h" #include "ui_TaskPostScalarClip.h" +#include "ui_TaskPostContours.h" #include "ui_TaskPostWarpVector.h" #include "TaskPostBoxes.h" @@ -1701,4 +1702,137 @@ void TaskPostCut::on_FunctionBox_currentIndexChanged(int idx) { recompute(); } + +// *************************************************************************** +// contours filter +TaskPostContours::TaskPostContours(ViewProviderDocumentObject* view, QWidget* parent) + : TaskPostBox(view, Gui::BitmapFactory().pixmap("FEM_PostFilterContours"), + tr("Contours filter options"), parent), + ui(new Ui_TaskPostContours) +{ + assert(view->isDerivedFrom(ViewProviderFemPostContours::getClassTypeId())); + + // load the views widget + proxy = new QWidget(this); + ui->setupUi(proxy); + QMetaObject::connectSlotsByName(this); + this->groupLayout()->addWidget(proxy); + + // load filter settings + updateEnumerationList(getTypedObject()->Field, ui->fieldsCB); + updateEnumerationList(getTypedObject()->VectorMode, ui->vectorsCB); + // for a new filter, initialize the coloring + auto colorState = static_cast(getObject())->NoColor.getValue(); + if (!colorState && getTypedView()->Field.getValue() == 0) { + getTypedView()->Field.setValue(1); + } + + ui->numberContoursSB->setValue( + static_cast(getObject())->NumberOfContours.getValue()); + ui->noColorCB->setChecked(colorState); + + // connect + connect(ui->fieldsCB, qOverload(&QComboBox::currentIndexChanged), + this, &TaskPostContours::onFieldsChanged); + connect(ui->vectorsCB, qOverload(&QComboBox::currentIndexChanged), + this, &TaskPostContours::onVectorModeChanged); + connect(ui->numberContoursSB, qOverload(&QSpinBox::valueChanged), + this, &TaskPostContours::onNumberOfContoursChanged); + connect(ui->noColorCB, &QCheckBox::toggled, + this, &TaskPostContours::onNoColorChanged); +} + +TaskPostContours::~TaskPostContours() +{} + +void TaskPostContours::applyPythonCode() +{} + +void TaskPostContours::updateFields(int idx) +{ + std::vector fieldArray; + std::vector vec = + getTypedObject()->Field.getEnumVector(); + for (std::vector::iterator it = vec.begin(); it != vec.end(); ++it) { + fieldArray.emplace_back(*it); + } + // update the ViewProvider Field enums + App::Enumeration anEnum; + getTypedView()->Field.setValue(anEnum); + anEnum.setEnums(fieldArray); + getTypedView()->Field.setValue(anEnum); + // set new Field index to ViewProvider Field + // the ViewProvider field starts with an additional entry "None", + // therefore the desired new setting is idx + 1 + if (!static_cast(getObject())->NoColor.getValue()) + getTypedView()->Field.setValue(idx + 1); + else + getTypedView()->Field.setValue(long(0)); +} + +void TaskPostContours::onFieldsChanged(int idx) +{ + static_cast(getObject())->Field.setValue(idx); + + blockVectorUpdate = true; + updateEnumerationList(getTypedObject()->VectorMode, ui->vectorsCB); + blockVectorUpdate = false; + + // In > 99 % of the cases the coloring should be equal to the field, + // thus change the coloring field too. Users can override this be resetting only the coloring + // field afterwards in the properties if really necessary. + updateFields(idx); + + // since a new field can be e.g. no vector while the previous one was, + // we must also update the VectorMode + if (!static_cast(getObject())->NoColor.getValue()) { + auto newMode = getTypedObject()->VectorMode.getValue(); + getTypedView()->VectorMode.setValue(newMode); + } +} + +void TaskPostContours::onVectorModeChanged(int idx) +{ + static_cast(getObject())->VectorMode.setValue(idx); + recompute(); + if (!blockVectorUpdate) { + // we can have the case that the previous field had VectorMode "Z" but + // since it is a 2D field, Z is eompty thus no field is available to color + // when the user noch goes back to e.g. "Y" we must set the Field + // first to get the possible VectorModes of that field + auto currentField = getTypedObject()->Field.getValue(); + updateFields(currentField); + // now we can set the VectorMode + if (!static_cast(getObject())->NoColor.getValue()) + getTypedView()->VectorMode.setValue(idx); + } +} + +void TaskPostContours::onNumberOfContoursChanged(int number) +{ + static_cast(getObject())->NumberOfContours.setValue(number); + recompute(); +} + +void TaskPostContours::onNoColorChanged(bool state) +{ + static_cast(getObject())->NoColor.setValue(state); + if (state) { + // no color + getTypedView()->Field.setValue(long(0)); + } + else { + // set same field + auto currentField = getTypedObject()->Field.getValue(); + // the ViewProvider field starts with an additional entry "None", + // therefore the desired new setting is idx + 1 + getTypedView()->Field.setValue(currentField + 1); + // set the VectorMode too + auto currentMode = getTypedObject()->VectorMode.getValue(); + getTypedView()->VectorMode.setValue(currentMode); + } + recompute(); +} + + #include "moc_TaskPostBoxes.cpp" diff --git a/src/Mod/Fem/Gui/TaskPostBoxes.h b/src/Mod/Fem/Gui/TaskPostBoxes.h index 9d74b2903d..a67977fe6c 100644 --- a/src/Mod/Fem/Gui/TaskPostBoxes.h +++ b/src/Mod/Fem/Gui/TaskPostBoxes.h @@ -34,6 +34,7 @@ class QComboBox; class Ui_TaskPostDisplay; class Ui_TaskPostClip; +class Ui_TaskPostContours; class Ui_TaskPostDataAlongLine; class Ui_TaskPostDataAtPoint; class Ui_TaskPostScalarClip; @@ -415,6 +416,30 @@ private: FunctionWidget* fwidget; }; + +class TaskPostContours: public TaskPostBox +{ + Q_OBJECT + +public: + explicit TaskPostContours(Gui::ViewProviderDocumentObject* view, QWidget* parent = nullptr); + ~TaskPostContours() override; + + void applyPythonCode() override; + +private Q_SLOTS: + void onFieldsChanged(int idx); + void onVectorModeChanged(int idx); + void onNumberOfContoursChanged(int number); + void onNoColorChanged(bool state); + +private: + QWidget* proxy; + std::unique_ptr ui; + bool blockVectorUpdate = false; + void updateFields(int idx); +}; + } //namespace FemGui #endif // GUI_TASKVIEW_TaskPostDisplay_H diff --git a/src/Mod/Fem/Gui/TaskPostContours.ui b/src/Mod/Fem/Gui/TaskPostContours.ui new file mode 100644 index 0000000000..f6fba0688e --- /dev/null +++ b/src/Mod/Fem/Gui/TaskPostContours.ui @@ -0,0 +1,87 @@ + + + TaskPostContours + + + + 0 + 0 + 250 + 115 + + + + Form + + + + + + + + + + + Vector: + + + + + + + + + + Field: + + + + + + + + 0 + 0 + + + + Number of contours: + + + + + + + + 40 + 0 + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 1 + + + 1000 + + + + + + + + + Contour lines will not be colored + + + No color + + + + + + + + diff --git a/src/Mod/Fem/Gui/ViewProviderFemPostFilter.cpp b/src/Mod/Fem/Gui/ViewProviderFemPostFilter.cpp index cc9ce29893..9c954dd222 100644 --- a/src/Mod/Fem/Gui/ViewProviderFemPostFilter.cpp +++ b/src/Mod/Fem/Gui/ViewProviderFemPostFilter.cpp @@ -99,7 +99,6 @@ void ViewProviderFemPostDataAtPoint::setupTaskDialog(TaskDlgPost* dlg) { } - PROPERTY_SOURCE(FemGui::ViewProviderFemPostScalarClip, FemGui::ViewProviderFemPostObject) ViewProviderFemPostScalarClip::ViewProviderFemPostScalarClip() { @@ -140,7 +139,6 @@ void ViewProviderFemPostWarpVector::setupTaskDialog(TaskDlgPost* dlg) { FemGui::ViewProviderFemPostObject::setupTaskDialog(dlg); } - PROPERTY_SOURCE(FemGui::ViewProviderFemPostCut, FemGui::ViewProviderFemPostObject) ViewProviderFemPostCut::ViewProviderFemPostCut() { @@ -161,3 +159,19 @@ void ViewProviderFemPostCut::setupTaskDialog(TaskDlgPost* dlg) { //add the display options FemGui::ViewProviderFemPostObject::setupTaskDialog(dlg); } + +PROPERTY_SOURCE(FemGui::ViewProviderFemPostContours, FemGui::ViewProviderFemPostObject) + +ViewProviderFemPostContours::ViewProviderFemPostContours() +{ + sPixmap = "FEM_PostFilterContours"; +} + +ViewProviderFemPostContours::~ViewProviderFemPostContours() +{} + +void ViewProviderFemPostContours::setupTaskDialog(TaskDlgPost* dlg) +{ + // the filter-specific task panel + dlg->appendBox(new TaskPostContours(dlg->getView())); +} diff --git a/src/Mod/Fem/Gui/ViewProviderFemPostFilter.h b/src/Mod/Fem/Gui/ViewProviderFemPostFilter.h index 7681bded57..bb3b77c17d 100644 --- a/src/Mod/Fem/Gui/ViewProviderFemPostFilter.h +++ b/src/Mod/Fem/Gui/ViewProviderFemPostFilter.h @@ -108,6 +108,20 @@ protected: void setupTaskDialog(TaskDlgPost* dlg) override; }; +class FemGuiExport ViewProviderFemPostContours: public ViewProviderFemPostObject +{ + + PROPERTY_HEADER_WITH_OVERRIDE(FemGui::ViewProviderFemPostContours); + +public: + /// constructor. + ViewProviderFemPostContours(); + ~ViewProviderFemPostContours() override; + +protected: + void setupTaskDialog(TaskDlgPost* dlg) override; +}; + } //namespace FemGui diff --git a/src/Mod/Fem/Gui/Workbench.cpp b/src/Mod/Fem/Gui/Workbench.cpp index 9be3e2e32f..70c14c5a7f 100755 --- a/src/Mod/Fem/Gui/Workbench.cpp +++ b/src/Mod/Fem/Gui/Workbench.cpp @@ -203,6 +203,7 @@ Gui::ToolBarItem* Workbench::setupToolBars() const << "FEM_PostFilterClipScalar" << "FEM_PostFilterCutFunction" << "FEM_PostFilterClipRegion" + << "FEM_PostFilterContours" << "FEM_PostFilterDataAlongLine" << "FEM_PostFilterLinearizedStresses" << "FEM_PostFilterDataAtPoint" @@ -371,6 +372,7 @@ Gui::MenuItem* Workbench::setupMenuBar() const << "FEM_PostFilterClipScalar" << "FEM_PostFilterCutFunction" << "FEM_PostFilterClipRegion" + << "FEM_PostFilterContours" << "FEM_PostFilterDataAlongLine" << "FEM_PostFilterLinearizedStresses" << "FEM_PostFilterDataAtPoint" diff --git a/src/Mod/Fem/ObjectsFem.py b/src/Mod/Fem/ObjectsFem.py index ec7d4f2f10..78947dd17f 100644 --- a/src/Mod/Fem/ObjectsFem.py +++ b/src/Mod/Fem/ObjectsFem.py @@ -720,6 +720,21 @@ def makePostVtkFilterWarp( return obj +def makePostVtkFilterContours( + doc, + base_vtk_result, + name="VtkFilterContours" +): + """makePostVtkFilterContours(document, base_vtk_result, [name]): + creates a FEM post processing contours filter object (vtk based)""" + obj = doc.addObject("Fem::FemPostContoursFilter", name) + tmp_filter_list = base_vtk_result.Filter + tmp_filter_list.append(obj) + base_vtk_result.Filter = tmp_filter_list + del tmp_filter_list + return obj + + def makePostVtkResult( doc, base_result, diff --git a/src/Mod/Fem/femtest/app/test_femimport.py b/src/Mod/Fem/femtest/app/test_femimport.py index f6add3ad73..f5d1208a6c 100644 --- a/src/Mod/Fem/femtest/app/test_femimport.py +++ b/src/Mod/Fem/femtest/app/test_femimport.py @@ -154,6 +154,7 @@ class TestObjectExistance(unittest.TestCase): expected_vtk_obj_types = [ "Fem::FemPostClipFilter", + "Fem::FemPostContoursFilter", "Fem::FemPostCutFilter", "Fem::FemPostDataAlongLineFilter", "Fem::FemPostDataAtPointFilter", diff --git a/src/Mod/Fem/femtest/app/test_object.py b/src/Mod/Fem/femtest/app/test_object.py index 0f150527f1..87011ac19a 100644 --- a/src/Mod/Fem/femtest/app/test_object.py +++ b/src/Mod/Fem/femtest/app/test_object.py @@ -87,11 +87,11 @@ class TestObjectCreate(unittest.TestCase): # solver children: equations --> 8 # gmsh mesh children: group, region, boundary layer --> 3 # resule children: mesh result --> 1 - # post pipeline children: region, scalar, cut, wrap --> 4 + # post pipeline children: region, scalar, cut, wrap --> 5 # analysis itself is not in analysis group --> 1 - # thus: -17 + # thus: -18 - self.assertEqual(len(doc.Analysis.Group), count_defmake - 17) + self.assertEqual(len(doc.Analysis.Group), count_defmake - 18) self.assertEqual(len(doc.Objects), count_defmake) fcc_print("doc objects count: {}, method: {}".format( @@ -1858,6 +1858,7 @@ def create_all_fem_objects_doc( vres = analysis.addObject(ObjectsFem.makePostVtkResult(doc, res))[0] ObjectsFem.makePostVtkFilterClipRegion(doc, vres) ObjectsFem.makePostVtkFilterClipScalar(doc, vres) + ObjectsFem.makePostVtkFilterContours(doc, vres) ObjectsFem.makePostVtkFilterCutFunction(doc, vres) ObjectsFem.makePostVtkFilterWarp(doc, vres) diff --git a/src/Mod/Fem/femviewprovider/view_mesh_gmsh.py b/src/Mod/Fem/femviewprovider/view_mesh_gmsh.py index 81be7da090..c26b452dd5 100644 --- a/src/Mod/Fem/femviewprovider/view_mesh_gmsh.py +++ b/src/Mod/Fem/femviewprovider/view_mesh_gmsh.py @@ -64,21 +64,21 @@ class VPMeshGmsh: def setEdit(self, vobj, mode): # hide all FEM meshes and VTK FemPost* objects - for o in vobj.Object.Document.Objects: + for obj in vobj.Object.Document.Objects: if ( - o.isDerivedFrom("Fem::FemMeshObject") - or o.isDerivedFrom("Fem::FemPostPipeline") - or o.isDerivedFrom("Fem::FemPostClipFilter") - or o.isDerivedFrom("Fem::FemPostScalarClipFilter") - or o.isDerivedFrom("Fem::FemPostWarpVectorFilter") - or o.isDerivedFrom("Fem::FemPostDataAlongLineFilter") - or o.isDerivedFrom("Fem::FemPostDataAtPointFilter") - or o.isDerivedFrom("Fem::FemPostCutFilter") - or o.isDerivedFrom("Fem::FemPostDataAlongLineFilter") - or o.isDerivedFrom("Fem::FemPostPlaneFunction") - or o.isDerivedFrom("Fem::FemPostSphereFunction") + obj.isDerivedFrom("Fem::FemMeshObject") + or obj.isDerivedFrom("Fem::FemPostClipFilter") + or obj.isDerivedFrom("Fem::FemPostContoursFilter") + or obj.isDerivedFrom("Fem::FemPostCutFilter") + or obj.isDerivedFrom("Fem::FemPostDataAlongLineFilter") + or obj.isDerivedFrom("Fem::FemPostDataAtPointFilter") + or obj.isDerivedFrom("Fem::FemPostPipeline") + or obj.isDerivedFrom("Fem::FemPostPlaneFunction") + or obj.isDerivedFrom("Fem::FemPostScalarClipFilter") + or obj.isDerivedFrom("Fem::FemPostSphereFunction") + or obj.isDerivedFrom("Fem::FemPostWarpVectorFilter") ): - o.ViewObject.hide() + obj.ViewObject.hide() # show the mesh we like to edit self.ViewObject.show() # show task panel From eea83ae37e380787914d9a4888a99836913c92c1 Mon Sep 17 00:00:00 2001 From: Uwe Date: Mon, 13 Feb 2023 00:45:56 +0100 Subject: [PATCH 02/42] [FEM] improve messages in mesh generation - no longer output an error on mesh from a BooleanFragments. There are cases when this fails, but other cases where one must use a BoopleanFragments compound and this works well, see https://forum.freecad.org/viewtopic.php?t=71070. Therefore a warning is sufficient. - disable log messages not important for the average users. - avoid code duplication --- src/Mod/Fem/femmesh/gmshtools.py | 46 +++++++++---------- src/Mod/Fem/femtaskpanels/task_mesh_gmsh.py | 29 +++--------- src/Mod/Fem/femviewprovider/view_mesh_gmsh.py | 1 - 3 files changed, 28 insertions(+), 48 deletions(-) diff --git a/src/Mod/Fem/femmesh/gmshtools.py b/src/Mod/Fem/femmesh/gmshtools.py index 6af99050e5..dae0af63f5 100644 --- a/src/Mod/Fem/femmesh/gmshtools.py +++ b/src/Mod/Fem/femmesh/gmshtools.py @@ -394,11 +394,11 @@ class GmshTools(): self.group_elements[ge] = new_group_elements[ge] else: Console.PrintError(" A group with this name exists already.\n") - else: - Console.PrintMessage(" No Group meshing for analysis.\n") + #else: + # Console.PrintMessage(" No Group meshing for analysis.\n") - if self.group_elements: - Console.PrintMessage(" {}\n".format(self.group_elements)) + #if self.group_elements: + # Console.PrintMessage(" {}\n".format(self.group_elements)) def get_gmsh_version(self): self.get_gmsh_command() @@ -447,7 +447,7 @@ class GmshTools(): # print(" No mesh regions.") pass else: - Console.PrintMessage(" Mesh regions, we need to get the elements.\n") + #Console.PrintMessage(" Mesh regions, we need to get the elements.\n") # by the use of MeshRegion object and a BooleanSplitCompound # there could be problems with node numbers see # http://forum.freecadweb.org/viewtopic.php?f=18&t=18780&start=40#p149467 @@ -461,17 +461,7 @@ class GmshTools(): or femutils.is_of_type(part, "FeatureXOR") ) ): - error_message = ( - " The mesh to shape is a boolean split tools Compound " - "and the mesh has mesh region list. " - "Gmsh could return unexpected meshes in such circumstances. " - "It is strongly recommended to extract the shape to mesh " - "from the Compound and use this one." - ) - Console.PrintError(error_message + "\n") - # TODO: no gui popup because FreeCAD will be in a endless output loop - # as long as the pop up is on --> maybe find a better solution for - # either of both --> thus the pop up is in task panel + self.outputCompoundWarning for mr_obj in self.mesh_obj.MeshRegionList: # print(mr_obj.Name) # print(mr_obj.CharacteristicLength) @@ -540,8 +530,8 @@ class GmshTools(): ele_shape = geomtools.get_element(self.part_obj, eleml) ele_vertexes = geomtools.get_vertexes_by_element(self.part_obj.Shape, ele_shape) self.ele_node_map[eleml] = ele_vertexes - Console.PrintMessage(" {}\n".format(self.ele_length_map)) - Console.PrintMessage(" {}\n".format(self.ele_node_map)) + #Console.PrintMessage(" {}\n".format(self.ele_length_map)) + #Console.PrintMessage(" {}\n".format(self.ele_node_map)) def get_boundary_layer_data(self): # mesh boundary layer @@ -553,16 +543,11 @@ class GmshTools(): # print(" No mesh boundary layer setting document object.") pass else: - Console.PrintMessage(" Mesh boundary layers, we need to get the elements.\n") + #Console.PrintMessage(" Mesh boundary layers, we need to get the elements.\n") if self.part_obj.Shape.ShapeType == "Compound": # see http://forum.freecadweb.org/viewtopic.php?f=18&t=18780&start=40#p149467 and # http://forum.freecadweb.org/viewtopic.php?f=18&t=18780&p=149520#p149520 - err = ( - "Gmsh could return unexpected meshes for a boolean split tools Compound. " - "It is strongly recommended to extract the shape to mesh " - "from the Compound and use this one." - ) - Console.PrintError(err + "\n") + self.outputCompoundWarning for mr_obj in self.mesh_obj.MeshBoundaryLayerList: if mr_obj.MinimumThickness and Units.Quantity(mr_obj.MinimumThickness).Value > 0: if mr_obj.References: @@ -960,6 +945,17 @@ class GmshTools(): else: Console.PrintError("No mesh was created.\n") + def outputCompoundWarning(self): + error_message = ( + "The mesh to shape is a Boolean Split Tools compound " + "and the mesh has mesh region list.\n" + "Gmsh could return unexpected meshes in such circumstances.\n" + "If this is the case, use the part workbench and " + "apply a Compound Filter on the compound.\n" + "Use the Compound Filter as input for the mesh." + ) + Console.PrintWarning(error_message + "\n") + ## @} diff --git a/src/Mod/Fem/femtaskpanels/task_mesh_gmsh.py b/src/Mod/Fem/femtaskpanels/task_mesh_gmsh.py index 30aac36737..a7e33f97aa 100644 --- a/src/Mod/Fem/femtaskpanels/task_mesh_gmsh.py +++ b/src/Mod/Fem/femtaskpanels/task_mesh_gmsh.py @@ -199,6 +199,8 @@ class _TaskPanel: ) def run_gmsh(self): + from femmesh import gmshtools + gmsh_mesh = gmshtools.GmshTools(self.mesh_obj, self.analysis) QApplication.setOverrideCursor(Qt.WaitCursor) part = self.mesh_obj.Part if ( @@ -209,43 +211,26 @@ class _TaskPanel: or is_of_type(part, "FeatureXOR") ) ): - error_message = ( - "The shape to mesh is a boolean split tools Compound " - "and the mesh has mesh region list. " - "Gmsh could return unexpected meshes in such circumstances. " - "It is strongly recommended to extract the shape " - "to mesh from the Compound and use this one." - ) - qtbox_title = ( - "Shape to mesh is a BooleanFragmentsCompound " - "and mesh regions are defined" - ) - QtGui.QMessageBox.critical( - None, - qtbox_title, - error_message - ) + gmsh_mesh.outputCompoundWarning() self.Start = time.time() self.form.l_time.setText("Time: {0:4.1f}: ".format(time.time() - self.Start)) self.console_message_gmsh = "" self.gmsh_runs = True self.console_log("We are going to start ...") self.get_active_analysis() - from femmesh import gmshtools - gmsh_mesh = gmshtools.GmshTools(self.mesh_obj, self.analysis) self.console_log("Start Gmsh ...") error = "" try: error = gmsh_mesh.create_mesh() except Exception: error = sys.exc_info()[1] - FreeCAD.Console.PrintMessage( + FreeCAD.Console.PrintError( "Unexpected error when creating mesh: {}\n" .format(error) ) if error: - FreeCAD.Console.PrintMessage("Gmsh had warnings ...\n") - FreeCAD.Console.PrintMessage("{}\n".format(error)) + FreeCAD.Console.PrintWarning("Gmsh had warnings:\n") + FreeCAD.Console.PrintWarning("{}\n".format(error)) self.console_log("Gmsh had warnings ...") self.console_log(error, "#FF0000") else: @@ -265,7 +250,7 @@ class _TaskPanel: else: for m in analysis.Group: if m.Name == self.mesh_obj.Name: - FreeCAD.Console.PrintMessage( + FreeCAD.Console.PrintLog( "Active analysis found: {}\n" .format(analysis.Name) ) diff --git a/src/Mod/Fem/femviewprovider/view_mesh_gmsh.py b/src/Mod/Fem/femviewprovider/view_mesh_gmsh.py index c26b452dd5..c68270163f 100644 --- a/src/Mod/Fem/femviewprovider/view_mesh_gmsh.py +++ b/src/Mod/Fem/femviewprovider/view_mesh_gmsh.py @@ -36,7 +36,6 @@ import FemGui from PySide import QtGui from femtaskpanels import task_mesh_gmsh from femtools.femutils import is_of_type -# from . import view_base_femobject # TODO use VPBaseFemObject from view_base_femobject From 50763df45b14a172b9a0bede1d45e5478c3cfedf Mon Sep 17 00:00:00 2001 From: Uwe Date: Mon, 13 Feb 2023 18:00:03 +0100 Subject: [PATCH 03/42] [FEM] sort code of result filters - sort the functions alphabetically to know where to scroll to - eases the reading at least a bit - some automatic reformatting according to our current clang file - split too long lines --- src/Mod/Fem/App/FemPostFilter.cpp | 717 +++++---- src/Mod/Fem/App/FemPostFilter.h | 229 +-- src/Mod/Fem/Gui/TaskPostBoxes.cpp | 1368 +++++++++-------- src/Mod/Fem/Gui/TaskPostBoxes.h | 309 ++-- src/Mod/Fem/Gui/ViewProviderFemPostFilter.cpp | 241 +-- src/Mod/Fem/Gui/ViewProviderFemPostFilter.h | 115 +- 6 files changed, 1547 insertions(+), 1432 deletions(-) diff --git a/src/Mod/Fem/App/FemPostFilter.cpp b/src/Mod/Fem/App/FemPostFilter.cpp index 1127295797..2096a2c2a3 100644 --- a/src/Mod/Fem/App/FemPostFilter.cpp +++ b/src/Mod/Fem/App/FemPostFilter.cpp @@ -46,27 +46,27 @@ FemPostFilter::FemPostFilter() } FemPostFilter::~FemPostFilter() +{} + +void FemPostFilter::addFilterPipeline(const FemPostFilter::FilterPipeline& p, std::string name) { - -} - -void FemPostFilter::addFilterPipeline(const FemPostFilter::FilterPipeline& p, std::string name) { m_pipelines[name] = p; } -FemPostFilter::FilterPipeline& FemPostFilter::getFilterPipeline(std::string name) { +FemPostFilter::FilterPipeline& FemPostFilter::getFilterPipeline(std::string name) +{ return m_pipelines[name]; } -void FemPostFilter::setActiveFilterPipeline(std::string name) { - +void FemPostFilter::setActiveFilterPipeline(std::string name) +{ if (m_activePipeline != name && isValid()) { m_activePipeline = name; } } -DocumentObjectExecReturn* FemPostFilter::execute() { - +DocumentObjectExecReturn* FemPostFilter::execute() +{ if (!m_pipelines.empty() && !m_activePipeline.empty()) { FemPostFilter::FilterPipeline& pipe = m_pipelines[m_activePipeline]; vtkSmartPointer data = getInputData(); @@ -88,8 +88,8 @@ DocumentObjectExecReturn* FemPostFilter::execute() { return StdReturn; } -vtkDataObject* FemPostFilter::getInputData() { - +vtkDataObject* FemPostFilter::getInputData() +{ if (Input.getValue()) { if (Input.getValue()->getTypeId().isDerivedFrom(Base::Type::fromName("Fem::FemPostObject"))) return Input.getValue()->Data.getValue(); @@ -111,101 +111,18 @@ vtkDataObject* FemPostFilter::getInputData() { return nullptr; } - // *************************************************************************** -// clip filter -PROPERTY_SOURCE(Fem::FemPostClipFilter, Fem::FemPostFilter) - -FemPostClipFilter::FemPostClipFilter() : FemPostFilter() { - - ADD_PROPERTY_TYPE(Function, - (nullptr), - "Clip", - App::Prop_None, - "The function object which defines the clip regions"); - ADD_PROPERTY_TYPE(InsideOut, (false), "Clip", App::Prop_None, "Invert the clip direction"); - ADD_PROPERTY_TYPE( - CutCells, - (false), - "Clip", - App::Prop_None, - "Decides if cells are cut and interpolated or if the cells are kept as a whole"); - - FilterPipeline clip; - m_clipper = vtkSmartPointer::New(); - clip.source = m_clipper; - clip.target = m_clipper; - addFilterPipeline(clip, "clip"); - - FilterPipeline extr; - m_extractor = vtkSmartPointer::New(); - extr.source = m_extractor; - extr.target = m_extractor; - addFilterPipeline(extr, "extract"); - - m_extractor->SetExtractInside(0); - setActiveFilterPipeline("extract"); -} - -FemPostClipFilter::~FemPostClipFilter() { - -} - -void FemPostClipFilter::onChanged(const Property* prop) { - - if (prop == &Function) { - - if (Function.getValue() - && Function.getValue()->isDerivedFrom(FemPostFunction::getClassTypeId())) { - m_clipper->SetClipFunction( - static_cast(Function.getValue())->getImplicitFunction()); - m_extractor->SetImplicitFunction( - static_cast(Function.getValue())->getImplicitFunction()); - } - } - else if (prop == &InsideOut) { - - m_clipper->SetInsideOut(InsideOut.getValue()); - m_extractor->SetExtractInside((InsideOut.getValue()) ? 1 : 0); - } - else if (prop == &CutCells) { - - if (!CutCells.getValue()) - setActiveFilterPipeline("extract"); - else - setActiveFilterPipeline("clip"); - }; - - Fem::FemPostFilter::onChanged(prop); -} - -short int FemPostClipFilter::mustExecute() const { - - if (Function.isTouched() || - InsideOut.isTouched() || - CutCells.isTouched()) { - - return 1; - } - else - return App::DocumentObject::mustExecute(); -} - -DocumentObjectExecReturn* FemPostClipFilter::execute() { - - if (!m_extractor->GetImplicitFunction()) - return StdReturn; - - return Fem::FemPostFilter::execute(); -} +// in the following, the different filters sorted alphabetically +// *************************************************************************** // *************************************************************************** -// data along a line +// data along line filter PROPERTY_SOURCE(Fem::FemPostDataAlongLineFilter, Fem::FemPostFilter) -FemPostDataAlongLineFilter::FemPostDataAlongLineFilter() : FemPostFilter() { - +FemPostDataAlongLineFilter::FemPostDataAlongLineFilter() + : FemPostFilter() +{ ADD_PROPERTY_TYPE(Point1, (Base::Vector3d(0.0, 0.0, 0.0)), "DataAlongLine", @@ -259,12 +176,11 @@ FemPostDataAlongLineFilter::FemPostDataAlongLineFilter() : FemPostFilter() { setActiveFilterPipeline("DataAlongLine"); } -FemPostDataAlongLineFilter::~FemPostDataAlongLineFilter() { - -} - -DocumentObjectExecReturn* FemPostDataAlongLineFilter::execute() { +FemPostDataAlongLineFilter::~FemPostDataAlongLineFilter() +{} +DocumentObjectExecReturn* FemPostDataAlongLineFilter::execute() +{ //recalculate the filter return Fem::FemPostFilter::execute(); } @@ -289,7 +205,8 @@ void FemPostDataAlongLineFilter::handleChangedPropertyType(Base::XMLReader& read } } -void FemPostDataAlongLineFilter::onChanged(const Property* prop) { +void FemPostDataAlongLineFilter::onChanged(const Property* prop) +{ if (prop == &Point1) { const Base::Vector3d& vec1 = Point1.getValue(); m_line->SetPoint1(vec1.x, vec1.y, vec1.z); @@ -307,19 +224,16 @@ void FemPostDataAlongLineFilter::onChanged(const Property* prop) { Fem::FemPostFilter::onChanged(prop); } -short int FemPostDataAlongLineFilter::mustExecute() const { - - if (Point1.isTouched() || - Point2.isTouched() || - Resolution.isTouched()) { - +short int FemPostDataAlongLineFilter::mustExecute() const +{ + if (Point1.isTouched() || Point2.isTouched() || Resolution.isTouched()) return 1; - } - else return App::DocumentObject::mustExecute(); + else + return App::DocumentObject::mustExecute(); } -void FemPostDataAlongLineFilter::GetAxisData() { - +void FemPostDataAlongLineFilter::GetAxisData() +{ std::vector coords; std::vector values; @@ -364,11 +278,12 @@ void FemPostDataAlongLineFilter::GetAxisData() { // *************************************************************************** -// data point filter +// data at point filter PROPERTY_SOURCE(Fem::FemPostDataAtPointFilter, Fem::FemPostFilter) -FemPostDataAtPointFilter::FemPostDataAtPointFilter() : FemPostFilter() { - +FemPostDataAtPointFilter::FemPostDataAtPointFilter() + : FemPostFilter() +{ ADD_PROPERTY_TYPE(Center, (Base::Vector3d(0.0, 0.0, 0.0)), "DataAtPoint", @@ -409,17 +324,17 @@ FemPostDataAtPointFilter::FemPostDataAtPointFilter() : FemPostFilter() { setActiveFilterPipeline("DataAtPoint"); } -FemPostDataAtPointFilter::~FemPostDataAtPointFilter() { - -} - -DocumentObjectExecReturn* FemPostDataAtPointFilter::execute() { +FemPostDataAtPointFilter::~FemPostDataAtPointFilter() +{} +DocumentObjectExecReturn* FemPostDataAtPointFilter::execute() +{ //recalculate the filter return Fem::FemPostFilter::execute(); } -void FemPostDataAtPointFilter::onChanged(const Property* prop) { +void FemPostDataAtPointFilter::onChanged(const Property* prop) +{ if (prop == &Center) { const Base::Vector3d& vec = Center.getValue(); m_point->SetCenter(vec.x, vec.y, vec.z); @@ -428,16 +343,16 @@ void FemPostDataAtPointFilter::onChanged(const Property* prop) { Fem::FemPostFilter::onChanged(prop); } -short int FemPostDataAtPointFilter::mustExecute() const { - +short int FemPostDataAtPointFilter::mustExecute() const +{ if (Center.isTouched()) return 1; else return App::DocumentObject::mustExecute(); } -void FemPostDataAtPointFilter::GetPointData() { - +void FemPostDataAtPointFilter::GetPointData() +{ std::vector values; vtkSmartPointer data = m_probe->GetOutputDataObject(0); @@ -468,246 +383,84 @@ void FemPostDataAtPointFilter::GetPointData() { // *************************************************************************** -// scalar clip filter -PROPERTY_SOURCE(Fem::FemPostScalarClipFilter, Fem::FemPostFilter) +// clip filter +PROPERTY_SOURCE(Fem::FemPostClipFilter, Fem::FemPostFilter) -FemPostScalarClipFilter::FemPostScalarClipFilter() : FemPostFilter() { - - ADD_PROPERTY_TYPE( - Value, (0), "Clip", App::Prop_None, "The scalar value used to clip the selected field"); - ADD_PROPERTY_TYPE(Scalars, (long(0)), "Clip", App::Prop_None, "The field used to clip"); - ADD_PROPERTY_TYPE(InsideOut, (false), "Clip", App::Prop_None, "Invert the clip direction"); - - Value.setConstraints(&m_constraints); - - FilterPipeline clip; - m_clipper = vtkSmartPointer::New(); - clip.source = m_clipper; - clip.target = m_clipper; - addFilterPipeline(clip, "clip"); - setActiveFilterPipeline("clip"); -} - -FemPostScalarClipFilter::~FemPostScalarClipFilter() { - -} - -DocumentObjectExecReturn* FemPostScalarClipFilter::execute() { - - std::string val; - if (Scalars.getValue() >= 0) - val = Scalars.getValueAsString(); - - std::vector ScalarsArray; - - vtkSmartPointer data = getInputData(); - if (!data || !data->IsA("vtkDataSet")) - return StdReturn; - - vtkDataSet* dset = vtkDataSet::SafeDownCast(data); - vtkPointData* pd = dset->GetPointData(); - - // get all scalar fields - for (int i = 0; i < pd->GetNumberOfArrays(); ++i) { - if (pd->GetArray(i)->GetNumberOfComponents() == 1) - ScalarsArray.emplace_back(pd->GetArrayName(i)); - } - - App::Enumeration empty; - Scalars.setValue(empty); - m_scalarFields.setEnums(ScalarsArray); - Scalars.setValue(m_scalarFields); - - // search if the current field is in the available ones and set it - std::vector::iterator it = - std::find(ScalarsArray.begin(), ScalarsArray.end(), val); - if (!val.empty() && it != ScalarsArray.end()) - Scalars.setValue(val.c_str()); - - //recalculate the filter - return Fem::FemPostFilter::execute(); -} - -void FemPostScalarClipFilter::onChanged(const Property* prop) { - - if (prop == &Value) { - m_clipper->SetValue(Value.getValue()); - } - else if (prop == &InsideOut) { - m_clipper->SetInsideOut(InsideOut.getValue()); - } - else if (prop == &Scalars && (Scalars.getValue() >= 0)) { - m_clipper->SetInputArrayToProcess(0, 0, 0, - vtkDataObject::FIELD_ASSOCIATION_POINTS, Scalars.getValueAsString()); - setConstraintForField(); - } - - Fem::FemPostFilter::onChanged(prop); -} - -short int FemPostScalarClipFilter::mustExecute() const { - - if (Value.isTouched() || - InsideOut.isTouched() || - Scalars.isTouched()) - return 1; - else - return App::DocumentObject::mustExecute(); -} - -void FemPostScalarClipFilter::setConstraintForField() { - - vtkSmartPointer data = getInputData(); - if (!data || !data->IsA("vtkDataSet")) - return; - - vtkDataSet* dset = vtkDataSet::SafeDownCast(data); - - vtkDataArray* pdata = dset->GetPointData()->GetArray(Scalars.getValueAsString()); - // VTK cannot deliver data when the filer relies e.g. on a cut clip filter - // whose value is set so that all data are cut - if (!pdata) - return; - double p[2]; - pdata->GetRange(p); - m_constraints.LowerBound = p[0]; - m_constraints.UpperBound = p[1]; - m_constraints.StepSize = (p[1] - p[0]) / 100.; -} - - -// *************************************************************************** -// warp vector filter -PROPERTY_SOURCE(Fem::FemPostWarpVectorFilter, Fem::FemPostFilter) - -FemPostWarpVectorFilter::FemPostWarpVectorFilter() : FemPostFilter() { - - ADD_PROPERTY_TYPE(Factor, - (0), - "Warp", - App::Prop_None, - "The factor by which the vector is added to the node positions"); - ADD_PROPERTY_TYPE( - Vector, (long(0)), "Warp", App::Prop_None, "The field added to the node position"); - - FilterPipeline warp; - m_warp = vtkSmartPointer::New(); - warp.source = m_warp; - warp.target = m_warp; - addFilterPipeline(warp, "warp"); - setActiveFilterPipeline("warp"); -} - -FemPostWarpVectorFilter::~FemPostWarpVectorFilter() { - -} - -DocumentObjectExecReturn* FemPostWarpVectorFilter::execute() { - - std::string val; - if (Vector.getValue() >= 0) - val = Vector.getValueAsString(); - - std::vector VectorArray; - - vtkSmartPointer data = getInputData(); - if (!data || !data->IsA("vtkDataSet")) - return StdReturn; - - vtkDataSet* dset = vtkDataSet::SafeDownCast(data); - vtkPointData* pd = dset->GetPointData(); - - // get all vector fields - for (int i = 0; i < pd->GetNumberOfArrays(); ++i) { - if (pd->GetArray(i)->GetNumberOfComponents() == 3) - VectorArray.emplace_back(pd->GetArrayName(i)); - } - - App::Enumeration empty; - Vector.setValue(empty); - m_vectorFields.setEnums(VectorArray); - Vector.setValue(m_vectorFields); - - // search if the current field is in the available ones and set it - std::vector::iterator it = - std::find(VectorArray.begin(), VectorArray.end(), val); - if (!val.empty() && it != VectorArray.end()) - Vector.setValue(val.c_str()); - - //recalculate the filter - return Fem::FemPostFilter::execute(); -} - -void FemPostWarpVectorFilter::onChanged(const Property* prop) { - - if (prop == &Factor) - // since our mesh is in mm, we must scale the factor - m_warp->SetScaleFactor(1000 * Factor.getValue()); - else if (prop == &Vector && (Vector.getValue() >= 0)) - m_warp->SetInputArrayToProcess(0, 0, 0, - vtkDataObject::FIELD_ASSOCIATION_POINTS, Vector.getValueAsString()); - - Fem::FemPostFilter::onChanged(prop); -} - -short int FemPostWarpVectorFilter::mustExecute() const { - - if (Factor.isTouched() || - Vector.isTouched()) - return 1; - else - return App::DocumentObject::mustExecute(); -} - - -// *************************************************************************** -// cut filter -PROPERTY_SOURCE(Fem::FemPostCutFilter, Fem::FemPostFilter) - -FemPostCutFilter::FemPostCutFilter() +FemPostClipFilter::FemPostClipFilter() : FemPostFilter() { ADD_PROPERTY_TYPE(Function, (nullptr), - "Cut", + "Clip", App::Prop_None, - "The function object which defines the cut function"); + "The function object which defines the clip regions"); + ADD_PROPERTY_TYPE(InsideOut, (false), "Clip", App::Prop_None, "Invert the clip direction"); + ADD_PROPERTY_TYPE( + CutCells, + (false), + "Clip", + App::Prop_None, + "Decides if cells are cut and interpolated or if the cells are kept as a whole"); - FilterPipeline cut; - m_cutter = vtkSmartPointer::New(); - cut.source = m_cutter; - cut.target = m_cutter; - addFilterPipeline(cut, "cut"); - setActiveFilterPipeline("cut"); + FilterPipeline clip; + m_clipper = vtkSmartPointer::New(); + clip.source = m_clipper; + clip.target = m_clipper; + addFilterPipeline(clip, "clip"); + + FilterPipeline extr; + m_extractor = vtkSmartPointer::New(); + extr.source = m_extractor; + extr.target = m_extractor; + addFilterPipeline(extr, "extract"); + + m_extractor->SetExtractInside(0); + setActiveFilterPipeline("extract"); } -FemPostCutFilter::~FemPostCutFilter() +FemPostClipFilter::~FemPostClipFilter() {} -void FemPostCutFilter::onChanged(const Property* prop) +void FemPostClipFilter::onChanged(const Property* prop) { if (prop == &Function) { + if (Function.getValue() && Function.getValue()->isDerivedFrom(FemPostFunction::getClassTypeId())) { - m_cutter->SetCutFunction( + m_clipper->SetClipFunction( + static_cast(Function.getValue())->getImplicitFunction()); + m_extractor->SetImplicitFunction( static_cast(Function.getValue())->getImplicitFunction()); } } + else if (prop == &InsideOut) { + + m_clipper->SetInsideOut(InsideOut.getValue()); + m_extractor->SetExtractInside((InsideOut.getValue()) ? 1 : 0); + } + else if (prop == &CutCells) { + + if (!CutCells.getValue()) + setActiveFilterPipeline("extract"); + else + setActiveFilterPipeline("clip"); + }; Fem::FemPostFilter::onChanged(prop); } -short int FemPostCutFilter::mustExecute() const +short int FemPostClipFilter::mustExecute() const { - if (Function.isTouched()) + if (Function.isTouched() || InsideOut.isTouched() || CutCells.isTouched()) + return 1; else return App::DocumentObject::mustExecute(); } -DocumentObjectExecReturn* FemPostCutFilter::execute() +DocumentObjectExecReturn* FemPostClipFilter::execute() { - if (!m_cutter->GetCutFunction()) + if (!m_extractor->GetImplicitFunction()) return StdReturn; return Fem::FemPostFilter::execute(); @@ -720,14 +473,11 @@ PROPERTY_SOURCE(Fem::FemPostContoursFilter, Fem::FemPostFilter) FemPostContoursFilter::FemPostContoursFilter() : FemPostFilter() { - ADD_PROPERTY_TYPE(NumberOfContours, (10), "Contours", - App::Prop_None, "The number of contours"); - ADD_PROPERTY_TYPE(Field, (long(0)), "Clip", - App::Prop_None, "The field used to clip"); - ADD_PROPERTY_TYPE(VectorMode, ((long)0), "Contours", - App::Prop_None, "Select what vector field"); - ADD_PROPERTY_TYPE(NoColor, (false), "Contours", - App::Prop_None, "Don't color the contours"); + ADD_PROPERTY_TYPE(NumberOfContours, (10), "Contours", App::Prop_None, "The number of contours"); + ADD_PROPERTY_TYPE(Field, (long(0)), "Clip", App::Prop_None, "The field used to clip"); + ADD_PROPERTY_TYPE( + VectorMode, ((long)0), "Contours", App::Prop_None, "Select what vector field"); + ADD_PROPERTY_TYPE(NoColor, (false), "Contours", App::Prop_None, "Don't color the contours"); m_contourConstraints.LowerBound = 1; m_contourConstraints.UpperBound = 1000; @@ -744,8 +494,7 @@ FemPostContoursFilter::FemPostContoursFilter() } FemPostContoursFilter::~FemPostContoursFilter() -{ -} +{} DocumentObjectExecReturn* FemPostContoursFilter::execute() { @@ -827,24 +576,23 @@ void FemPostContoursFilter::onChanged(const Property* prop) } else { for (vtkIdType tupleIdx = 0; tupleIdx < numTuples; ++tupleIdx) { - componentArray->SetComponent(tupleIdx, 0, - std::sqrt(pdata->GetComponent(tupleIdx, 0) * - pdata->GetComponent(tupleIdx, 0) + - pdata->GetComponent(tupleIdx, 1) * - pdata->GetComponent(tupleIdx, 1) + - pdata->GetComponent(tupleIdx, 2) * - pdata->GetComponent(tupleIdx, 2) ) ); + componentArray->SetComponent( + tupleIdx, + 0, + std::sqrt( + pdata->GetComponent(tupleIdx, 0) * pdata->GetComponent(tupleIdx, 0) + + pdata->GetComponent(tupleIdx, 1) * pdata->GetComponent(tupleIdx, 1) + + pdata->GetComponent(tupleIdx, 2) * pdata->GetComponent(tupleIdx, 2))); } } // name the array - contourFieldName = - std::string(Field.getValueAsString()) + "_contour"; + contourFieldName = std::string(Field.getValueAsString()) + "_contour"; componentArray->SetName(contourFieldName.c_str()); // add the array as new field and use it for the contour filter dset->GetPointData()->AddArray(componentArray); - m_contours->SetInputArrayToProcess(0, 0, 0, - vtkDataObject::FIELD_ASSOCIATION_POINTS, contourFieldName.c_str()); + m_contours->SetInputArrayToProcess( + 0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_POINTS, contourFieldName.c_str()); componentArray->GetRange(p); recalculateContours(p[0], p[1]); if (prop == &Data) { @@ -966,6 +714,251 @@ void FemPostContoursFilter::refreshVectors() auto it = std::find(vectorArray.begin(), vectorArray.end(), vectorName); if (!vectorName.empty() && it != vectorArray.end()) VectorMode.setValue(vectorName.c_str()); - + m_blockPropertyChanges = false; } + + +// *************************************************************************** +// cut filter +PROPERTY_SOURCE(Fem::FemPostCutFilter, Fem::FemPostFilter) + +FemPostCutFilter::FemPostCutFilter() + : FemPostFilter() +{ + ADD_PROPERTY_TYPE(Function, + (nullptr), + "Cut", + App::Prop_None, + "The function object which defines the cut function"); + + FilterPipeline cut; + m_cutter = vtkSmartPointer::New(); + cut.source = m_cutter; + cut.target = m_cutter; + addFilterPipeline(cut, "cut"); + setActiveFilterPipeline("cut"); +} + +FemPostCutFilter::~FemPostCutFilter() +{} + +void FemPostCutFilter::onChanged(const Property* prop) +{ + if (prop == &Function) { + if (Function.getValue() + && Function.getValue()->isDerivedFrom(FemPostFunction::getClassTypeId())) { + m_cutter->SetCutFunction( + static_cast(Function.getValue())->getImplicitFunction()); + } + } + + Fem::FemPostFilter::onChanged(prop); +} + +short int FemPostCutFilter::mustExecute() const +{ + if (Function.isTouched()) + return 1; + else + return App::DocumentObject::mustExecute(); +} + +DocumentObjectExecReturn* FemPostCutFilter::execute() +{ + if (!m_cutter->GetCutFunction()) + return StdReturn; + + return Fem::FemPostFilter::execute(); +} + + +// *************************************************************************** +// scalar clip filter +PROPERTY_SOURCE(Fem::FemPostScalarClipFilter, Fem::FemPostFilter) + +FemPostScalarClipFilter::FemPostScalarClipFilter() : FemPostFilter() { + + ADD_PROPERTY_TYPE( + Value, (0), "Clip", App::Prop_None, "The scalar value used to clip the selected field"); + ADD_PROPERTY_TYPE(Scalars, (long(0)), "Clip", App::Prop_None, "The field used to clip"); + ADD_PROPERTY_TYPE(InsideOut, (false), "Clip", App::Prop_None, "Invert the clip direction"); + + Value.setConstraints(&m_constraints); + + FilterPipeline clip; + m_clipper = vtkSmartPointer::New(); + clip.source = m_clipper; + clip.target = m_clipper; + addFilterPipeline(clip, "clip"); + setActiveFilterPipeline("clip"); +} + +FemPostScalarClipFilter::~FemPostScalarClipFilter() { + +} + +DocumentObjectExecReturn* FemPostScalarClipFilter::execute() +{ + std::string val; + if (Scalars.getValue() >= 0) + val = Scalars.getValueAsString(); + + std::vector ScalarsArray; + + vtkSmartPointer data = getInputData(); + if (!data || !data->IsA("vtkDataSet")) + return StdReturn; + + vtkDataSet* dset = vtkDataSet::SafeDownCast(data); + vtkPointData* pd = dset->GetPointData(); + + // get all scalar fields + for (int i = 0; i < pd->GetNumberOfArrays(); ++i) { + if (pd->GetArray(i)->GetNumberOfComponents() == 1) + ScalarsArray.emplace_back(pd->GetArrayName(i)); + } + + App::Enumeration empty; + Scalars.setValue(empty); + m_scalarFields.setEnums(ScalarsArray); + Scalars.setValue(m_scalarFields); + + // search if the current field is in the available ones and set it + std::vector::iterator it = + std::find(ScalarsArray.begin(), ScalarsArray.end(), val); + if (!val.empty() && it != ScalarsArray.end()) + Scalars.setValue(val.c_str()); + + //recalculate the filter + return Fem::FemPostFilter::execute(); +} + +void FemPostScalarClipFilter::onChanged(const Property* prop) +{ + if (prop == &Value) { + m_clipper->SetValue(Value.getValue()); + } + else if (prop == &InsideOut) { + m_clipper->SetInsideOut(InsideOut.getValue()); + } + else if (prop == &Scalars && (Scalars.getValue() >= 0)) { + m_clipper->SetInputArrayToProcess(0, 0, 0, + vtkDataObject::FIELD_ASSOCIATION_POINTS, Scalars.getValueAsString()); + setConstraintForField(); + } + + Fem::FemPostFilter::onChanged(prop); +} + +short int FemPostScalarClipFilter::mustExecute() const +{ + if (Value.isTouched() || + InsideOut.isTouched() || + Scalars.isTouched()) + return 1; + else + return App::DocumentObject::mustExecute(); +} + +void FemPostScalarClipFilter::setConstraintForField() +{ + vtkSmartPointer data = getInputData(); + if (!data || !data->IsA("vtkDataSet")) + return; + + vtkDataSet* dset = vtkDataSet::SafeDownCast(data); + + vtkDataArray* pdata = dset->GetPointData()->GetArray(Scalars.getValueAsString()); + // VTK cannot deliver data when the filer relies e.g. on a cut clip filter + // whose value is set so that all data are cut + if (!pdata) + return; + double p[2]; + pdata->GetRange(p); + m_constraints.LowerBound = p[0]; + m_constraints.UpperBound = p[1]; + m_constraints.StepSize = (p[1] - p[0]) / 100.; +} + + +// *************************************************************************** +// warp vector filter +PROPERTY_SOURCE(Fem::FemPostWarpVectorFilter, Fem::FemPostFilter) + +FemPostWarpVectorFilter::FemPostWarpVectorFilter() : FemPostFilter() +{ + ADD_PROPERTY_TYPE(Factor, + (0), + "Warp", + App::Prop_None, + "The factor by which the vector is added to the node positions"); + ADD_PROPERTY_TYPE( + Vector, (long(0)), "Warp", App::Prop_None, "The field added to the node position"); + + FilterPipeline warp; + m_warp = vtkSmartPointer::New(); + warp.source = m_warp; + warp.target = m_warp; + addFilterPipeline(warp, "warp"); + setActiveFilterPipeline("warp"); +} + +FemPostWarpVectorFilter::~FemPostWarpVectorFilter() +{} + +DocumentObjectExecReturn* FemPostWarpVectorFilter::execute() +{ + std::string val; + if (Vector.getValue() >= 0) + val = Vector.getValueAsString(); + + std::vector VectorArray; + + vtkSmartPointer data = getInputData(); + if (!data || !data->IsA("vtkDataSet")) + return StdReturn; + + vtkDataSet* dset = vtkDataSet::SafeDownCast(data); + vtkPointData* pd = dset->GetPointData(); + + // get all vector fields + for (int i = 0; i < pd->GetNumberOfArrays(); ++i) { + if (pd->GetArray(i)->GetNumberOfComponents() == 3) + VectorArray.emplace_back(pd->GetArrayName(i)); + } + + App::Enumeration empty; + Vector.setValue(empty); + m_vectorFields.setEnums(VectorArray); + Vector.setValue(m_vectorFields); + + // search if the current field is in the available ones and set it + std::vector::iterator it = + std::find(VectorArray.begin(), VectorArray.end(), val); + if (!val.empty() && it != VectorArray.end()) + Vector.setValue(val.c_str()); + + //recalculate the filter + return Fem::FemPostFilter::execute(); +} + +void FemPostWarpVectorFilter::onChanged(const Property* prop) +{ + if (prop == &Factor) + // since our mesh is in mm, we must scale the factor + m_warp->SetScaleFactor(1000 * Factor.getValue()); + else if (prop == &Vector && (Vector.getValue() >= 0)) + m_warp->SetInputArrayToProcess(0, 0, 0, + vtkDataObject::FIELD_ASSOCIATION_POINTS, Vector.getValueAsString()); + + Fem::FemPostFilter::onChanged(prop); +} + +short int FemPostWarpVectorFilter::mustExecute() const +{ + if (Factor.isTouched() || Vector.isTouched()) + return 1; + else + return App::DocumentObject::mustExecute(); +} diff --git a/src/Mod/Fem/App/FemPostFilter.h b/src/Mod/Fem/App/FemPostFilter.h index 2ea6bdc38f..a00edb7d9b 100644 --- a/src/Mod/Fem/App/FemPostFilter.h +++ b/src/Mod/Fem/App/FemPostFilter.h @@ -75,33 +75,15 @@ private: std::string m_activePipeline; }; -class FemExport FemPostClipFilter : public FemPostFilter { +// *************************************************************************** +// in the following, the different filters sorted alphabetically +// *************************************************************************** - PROPERTY_HEADER_WITH_OVERRIDE(Fem::FemPostClipFilter); -public: - FemPostClipFilter(); - ~FemPostClipFilter() override; - - App::PropertyLink Function; - App::PropertyBool InsideOut; - App::PropertyBool CutCells; - - const char* getViewProviderName() const override { - return "FemGui::ViewProviderFemPostClip"; - } - short int mustExecute() const override; - App::DocumentObjectExecReturn* execute() override; - -protected: - void onChanged(const App::Property* prop) override; - -private: - vtkSmartPointer m_clipper; - vtkSmartPointer m_extractor; -}; - -class FemExport FemPostDataAlongLineFilter : public FemPostFilter { +// *************************************************************************** +// data along line filter +class FemExport FemPostDataAlongLineFilter: public FemPostFilter +{ PROPERTY_HEADER_WITH_OVERRIDE(Fem::FemPostDataAlongLineFilter); @@ -116,8 +98,9 @@ public: App::PropertyFloatList YAxisData; App::PropertyString PlotData; - const char* getViewProviderName() const override { - return "FemGui::ViewProviderFemPostDataAlongLine"; + const char* getViewProviderName() const override + { + return "FemGui::ViewProviderFemPostDataAlongLine"; } short int mustExecute() const override; void GetAxisData(); @@ -129,13 +112,15 @@ protected: App::Property* prop) override; private: - - vtkSmartPointer m_line; - vtkSmartPointer m_probe; - + vtkSmartPointer m_line; + vtkSmartPointer m_probe; }; -class FemExport FemPostDataAtPointFilter : public FemPostFilter { + +// *************************************************************************** +// data at point filter +class FemExport FemPostDataAtPointFilter: public FemPostFilter +{ PROPERTY_HEADER_WITH_OVERRIDE(Fem::FemPostDataAtPointFilter); @@ -149,8 +134,9 @@ public: App::PropertyFloatList PointData; App::PropertyString Unit; - const char* getViewProviderName() const override { - return "FemGui::ViewProviderFemPostDataAtPoint"; + const char* getViewProviderName() const override + { + return "FemGui::ViewProviderFemPostDataAtPoint"; } short int mustExecute() const override; @@ -160,77 +146,29 @@ protected: void GetPointData(); private: - - vtkSmartPointer m_point; - vtkSmartPointer m_probe; - + vtkSmartPointer m_point; + vtkSmartPointer m_probe; }; -class FemExport FemPostScalarClipFilter : public FemPostFilter { - PROPERTY_HEADER_WITH_OVERRIDE(Fem::FemPostScalarClipFilter); +// *************************************************************************** +// clip filter +class FemExport FemPostClipFilter: public FemPostFilter +{ + + PROPERTY_HEADER_WITH_OVERRIDE(Fem::FemPostClipFilter); public: - FemPostScalarClipFilter(); - ~FemPostScalarClipFilter() override; + FemPostClipFilter(); + ~FemPostClipFilter() override; - App::PropertyBool InsideOut; - App::PropertyFloatConstraint Value; - App::PropertyEnumeration Scalars; + App::PropertyLink Function; + App::PropertyBool InsideOut; + App::PropertyBool CutCells; - const char* getViewProviderName() const override { - return "FemGui::ViewProviderFemPostScalarClip"; - } - short int mustExecute() const override; - -protected: - App::DocumentObjectExecReturn* execute() override; - void onChanged(const App::Property* prop) override; - void setConstraintForField(); - -private: - vtkSmartPointer m_clipper; - App::Enumeration m_scalarFields; - App::PropertyFloatConstraint::Constraints m_constraints; -}; - -class FemExport FemPostWarpVectorFilter : public FemPostFilter { - - PROPERTY_HEADER_WITH_OVERRIDE(Fem::FemPostWarpVectorFilter); - -public: - FemPostWarpVectorFilter(); - ~FemPostWarpVectorFilter() override; - - App::PropertyFloat Factor; - App::PropertyEnumeration Vector; - - const char* getViewProviderName() const override { - return "FemGui::ViewProviderFemPostWarpVector"; - } - short int mustExecute() const override; - -protected: - App::DocumentObjectExecReturn* execute() override; - void onChanged(const App::Property* prop) override; - -private: - vtkSmartPointer m_warp; - App::Enumeration m_vectorFields; -}; - -class FemExport FemPostCutFilter : public FemPostFilter { - - PROPERTY_HEADER_WITH_OVERRIDE(Fem::FemPostCutFilter); - -public: - FemPostCutFilter(); - ~FemPostCutFilter() override; - - App::PropertyLink Function; - - const char* getViewProviderName() const override { - return "FemGui::ViewProviderFemPostCut"; + const char* getViewProviderName() const override + { + return "FemGui::ViewProviderFemPostClip"; } short int mustExecute() const override; App::DocumentObjectExecReturn* execute() override; @@ -239,9 +177,13 @@ protected: void onChanged(const App::Property* prop) override; private: - vtkSmartPointer m_cutter; + vtkSmartPointer m_clipper; + vtkSmartPointer m_extractor; }; + +// *************************************************************************** +// contours filter class FemExport FemPostContoursFilter: public FemPostFilter { @@ -280,6 +222,95 @@ private: App::PropertyIntegerConstraint::Constraints m_contourConstraints; }; + +// *************************************************************************** +// cut filter +class FemExport FemPostCutFilter: public FemPostFilter +{ + + PROPERTY_HEADER_WITH_OVERRIDE(Fem::FemPostCutFilter); + +public: + FemPostCutFilter(); + ~FemPostCutFilter() override; + + App::PropertyLink Function; + + const char* getViewProviderName() const override + { + return "FemGui::ViewProviderFemPostCut"; + } + short int mustExecute() const override; + App::DocumentObjectExecReturn* execute() override; + +protected: + void onChanged(const App::Property* prop) override; + +private: + vtkSmartPointer m_cutter; +}; + + +// *************************************************************************** +// scalar clip filter +class FemExport FemPostScalarClipFilter: public FemPostFilter +{ + + PROPERTY_HEADER_WITH_OVERRIDE(Fem::FemPostScalarClipFilter); + +public: + FemPostScalarClipFilter(); + ~FemPostScalarClipFilter() override; + + App::PropertyBool InsideOut; + App::PropertyFloatConstraint Value; + App::PropertyEnumeration Scalars; + + const char* getViewProviderName() const override + { + return "FemGui::ViewProviderFemPostScalarClip"; + } + short int mustExecute() const override; + +protected: + App::DocumentObjectExecReturn* execute() override; + void onChanged(const App::Property* prop) override; + void setConstraintForField(); + +private: + vtkSmartPointer m_clipper; + App::Enumeration m_scalarFields; + App::PropertyFloatConstraint::Constraints m_constraints; +}; + + +// *************************************************************************** +// warp vector filter +class FemExport FemPostWarpVectorFilter : public FemPostFilter { + + PROPERTY_HEADER_WITH_OVERRIDE(Fem::FemPostWarpVectorFilter); + +public: + FemPostWarpVectorFilter(); + ~FemPostWarpVectorFilter() override; + + App::PropertyFloat Factor; + App::PropertyEnumeration Vector; + + const char* getViewProviderName() const override { + return "FemGui::ViewProviderFemPostWarpVector"; + } + short int mustExecute() const override; + +protected: + App::DocumentObjectExecReturn* execute() override; + void onChanged(const App::Property* prop) override; + +private: + vtkSmartPointer m_warp; + App::Enumeration m_vectorFields; +}; + } //namespace Fem diff --git a/src/Mod/Fem/Gui/TaskPostBoxes.cpp b/src/Mod/Fem/Gui/TaskPostBoxes.cpp index 90ab069f66..0b9ac4d2c3 100644 --- a/src/Mod/Fem/Gui/TaskPostBoxes.cpp +++ b/src/Mod/Fem/Gui/TaskPostBoxes.cpp @@ -70,7 +70,7 @@ using namespace FemGui; using namespace Gui; // *************************************************************************** - +// point marker PointMarker::PointMarker(Gui::View3DInventorViewer* iv, std::string ObjName) : view(iv), vp(new ViewProviderPointMarker) { @@ -118,7 +118,8 @@ void PointMarker::customEvent(QEvent*) Gui::Command::doCommand(Gui::Command::Doc, ObjectInvisible().c_str()); } -std::string PointMarker::ObjectInvisible() { +std::string PointMarker::ObjectInvisible() +{ return "for amesh in App.activeDocument().Objects:\n\ if \"Mesh\" in amesh.TypeId:\n\ aparttoshow = amesh.Name.replace(\"_Mesh\",\"\")\n\ @@ -146,8 +147,9 @@ ViewProviderPointMarker::~ViewProviderPointMarker() pCoords->unref(); } -// *************************************************************************** +// *************************************************************************** +// data marker DataMarker::DataMarker(Gui::View3DInventorViewer* iv, std::string ObjName) : view(iv), vp(new ViewProviderDataMarker) { @@ -190,7 +192,8 @@ void DataMarker::customEvent(QEvent*) Gui::Command::doCommand(Gui::Command::Doc, ObjectInvisible().c_str()); } -std::string DataMarker::ObjectInvisible() { +std::string DataMarker::ObjectInvisible() +{ return "for amesh in App.activeDocument().Objects:\n\ if \"Mesh\" in amesh.TypeId:\n\ aparttoshow = amesh.Name.replace(\"_Mesh\",\"\")\n\ @@ -228,25 +231,72 @@ ViewProviderDataMarker::~ViewProviderDataMarker() pMarker->unref(); } -// *************************************************************************** -// *************************************************************************** -// TaskDialog -// *************************************************************************** +// *************************************************************************** +// main task dialog +TaskPostBox::TaskPostBox(Gui::ViewProviderDocumentObject* view, const QPixmap& icon, + const QString& title, QWidget* parent) + : TaskBox(icon, title, true, parent), + m_object(view->getObject()), + m_view(view) +{} + +TaskPostBox::~TaskPostBox() +{} + +bool TaskPostBox::autoApply() +{ + return FemSettings().getPostAutoRecompute(); +} + +App::Document* TaskPostBox::getDocument() const +{ + App::DocumentObject* obj = getObject(); + return (obj ? obj->getDocument() : nullptr); +} + +void TaskPostBox::recompute() +{ + if (autoApply()) { + App::Document* doc = getDocument(); + if (doc) + doc->recompute(); + } +} + +void TaskPostBox::updateEnumerationList(App::PropertyEnumeration& prop, QComboBox* box) +{ + QStringList list; + std::vector vec = prop.getEnumVector(); + for (std::vector::iterator it = vec.begin(); it != vec.end(); ++it) { + list.push_back(QString::fromStdString(*it)); + } + + int index = prop.getValue(); + // be aware the QComboxBox might be connected to the Property, + // thus clearing the box will set back the property enumeration index too. + // https://forum.freecadweb.org/viewtopic.php?f=10&t=30944 + box->clear(); + box->insertItems(0, list); + box->setCurrentIndex(index); +} + + +// *************************************************************************** +// simulation dialog for the TaskView TaskDlgPost::TaskDlgPost(Gui::ViewProviderDocumentObject* view) - : TaskDialog() - , m_view(view) + : TaskDialog(), + m_view(view) { assert(view); } TaskDlgPost::~TaskDlgPost() +{} + +QDialogButtonBox::StandardButtons TaskDlgPost::getStandardButtons() const { -} - -QDialogButtonBox::StandardButtons TaskDlgPost::getStandardButtons() const { - //check if we only have gui task boxes bool guionly = true; for (std::vector::const_iterator it = m_boxes.begin(); it != m_boxes.end(); ++it) @@ -284,8 +334,8 @@ void TaskDlgPost::connectSlots() } } -void TaskDlgPost::appendBox(TaskPostBox* box) { - +void TaskDlgPost::appendBox(TaskPostBox* box) +{ m_boxes.push_back(box); Content.push_back(box); } @@ -342,63 +392,13 @@ void TaskDlgPost::modifyStandardButtons(QDialogButtonBox* box) { box->button(QDialogButtonBox::Apply)->setDefault(true); } -// *************************************************************************** -// some task box methods -TaskPostBox::TaskPostBox(Gui::ViewProviderDocumentObject* view, const QPixmap& icon, - const QString& title, QWidget* parent) - : TaskBox(icon, title, true, parent) - , m_object(view->getObject()) - , m_view(view) -{ -} - -TaskPostBox::~TaskPostBox() { - -} - -bool TaskPostBox::autoApply() { - - return FemSettings().getPostAutoRecompute(); -} - -App::Document* TaskPostBox::getDocument() const { - App::DocumentObject* obj = getObject(); - return (obj ? obj->getDocument() : nullptr); -} - -void TaskPostBox::recompute() { - - if (autoApply()) { - App::Document* doc = getDocument(); - if (doc) - doc->recompute(); - } -} - -void TaskPostBox::updateEnumerationList(App::PropertyEnumeration& prop, QComboBox* box) { - - QStringList list; - std::vector vec = prop.getEnumVector(); - for (std::vector::iterator it = vec.begin(); it != vec.end(); ++it) { - list.push_back(QString::fromStdString(*it)); - } - - int index = prop.getValue(); - // be aware the QComboxBox might be connected to the Property, - // thus clearing the box will set back the property enumeration index too. - // https://forum.freecadweb.org/viewtopic.php?f=10&t=30944 - box->clear(); - box->insertItems(0, list); - box->setCurrentIndex(index); -} - // *************************************************************************** -// post pipeline results +// box to set the coloring TaskPostDisplay::TaskPostDisplay(Gui::ViewProviderDocumentObject* view, QWidget* parent) : TaskPostBox(view, Gui::BitmapFactory().pixmap("FEM_ResultShow"), tr("Result display options"), - parent) - , ui(new Ui_TaskPostDisplay) + parent), + ui(new Ui_TaskPostDisplay) { // we need a separate container widget to add all controls to proxy = new QWidget(this); @@ -408,7 +408,8 @@ TaskPostDisplay::TaskPostDisplay(Gui::ViewProviderDocumentObject* view, QWidget* this->groupLayout()->addWidget(proxy); // update all fields - updateEnumerationList(getTypedView()->DisplayMode, ui->Representation); + updateEnumerationList(getTypedView()->DisplayMode, + ui->Representation); updateEnumerationList(getTypedView()->Field, ui->Field); updateEnumerationList(getTypedView()->VectorMode, ui->VectorMode); @@ -421,29 +422,28 @@ TaskPostDisplay::TaskPostDisplay(Gui::ViewProviderDocumentObject* view, QWidget* } TaskPostDisplay::~TaskPostDisplay() -{ -} +{} void TaskPostDisplay::slotAddedFunction() { updateEnumerationList(getTypedView()->Field, ui->Field); } -void TaskPostDisplay::on_Representation_activated(int i) { - +void TaskPostDisplay::on_Representation_activated(int i) +{ getTypedView()->DisplayMode.setValue(i); updateEnumerationList(getTypedView()->Field, ui->Field); updateEnumerationList(getTypedView()->VectorMode, ui->VectorMode); } -void TaskPostDisplay::on_Field_activated(int i) { - +void TaskPostDisplay::on_Field_activated(int i) +{ getTypedView()->Field.setValue(i); updateEnumerationList(getTypedView()->VectorMode, ui->VectorMode); } -void TaskPostDisplay::on_VectorMode_activated(int i) { - +void TaskPostDisplay::on_VectorMode_activated(int i) +{ getTypedView()->VectorMode.setValue(i); } @@ -455,19 +455,16 @@ void TaskPostDisplay::on_Transparency_valueChanged(int i) { QToolTip::showText(QCursor::pos(), QString::number(i) + QString::fromLatin1(" %"), nullptr); } -void TaskPostDisplay::applyPythonCode() { +void TaskPostDisplay::applyPythonCode() +{} -} // *************************************************************************** -// ? -// the icon fem-post-geo-plane might be wrong but I do not know any better since the plane is one -// of the implicit functions +// functions TaskPostFunction::TaskPostFunction(ViewProviderDocumentObject* view, QWidget* parent) : TaskPostBox(view, Gui::BitmapFactory().pixmap("fem-post-geo-plane"), tr("Implicit function"), parent) { - assert(view->isDerivedFrom(ViewProviderFemPostFunction::getClassTypeId())); //we load the views widget @@ -477,173 +474,27 @@ TaskPostFunction::TaskPostFunction(ViewProviderDocumentObject* view, QWidget* pa this->groupLayout()->addWidget(w); } -TaskPostFunction::~TaskPostFunction() { - -} - -void TaskPostFunction::applyPythonCode() { +TaskPostFunction::~TaskPostFunction() +{} +void TaskPostFunction::applyPythonCode() +{ //we apply the views widgets python code } // *************************************************************************** -// region clip filter -TaskPostClip::TaskPostClip(ViewProviderDocumentObject* view, App::PropertyLink* function, - QWidget* parent) - : TaskPostBox(view, Gui::BitmapFactory().pixmap("FEM_PostFilterClipRegion"), - tr("Clip region, choose implicit function"), parent), - ui(new Ui_TaskPostClip) -{ - - assert(view->isDerivedFrom(ViewProviderFemPostClip::getClassTypeId())); - assert(function); - Q_UNUSED(function); - - fwidget = nullptr; - - //we load the views widget - proxy = new QWidget(this); - ui->setupUi(proxy); - QMetaObject::connectSlotsByName(this); - this->groupLayout()->addWidget(proxy); - - //the layout for the container widget - QVBoxLayout* layout = new QVBoxLayout(); - ui->Container->setLayout(layout); - - //fill up the combo box with possible functions - collectImplicitFunctions(); - - //add the function creation command - Gui::CommandManager& rcCmdMgr = Gui::Application::Instance->commandManager(); - Gui::Command* cmd = rcCmdMgr.getCommandByName("FEM_PostCreateFunctions"); - if (cmd && cmd->getAction()) - cmd->getAction()->addTo(ui->CreateButton); - ui->CreateButton->setPopupMode(QToolButton::InstantPopup); - - //load the default values - ui->CutCells->setChecked( - static_cast(getObject())->CutCells.getValue()); - ui->InsideOut->setChecked( - static_cast(getObject())->InsideOut.getValue()); -} - -TaskPostClip::~TaskPostClip() { - -} - -void TaskPostClip::applyPythonCode() { - -} - -void TaskPostClip::collectImplicitFunctions() { - - std::vector pipelines; - pipelines = getDocument()->getObjectsOfType(); - if (!pipelines.empty()) { - Fem::FemPostPipeline* pipeline = pipelines.front(); - if (pipeline->Functions.getValue() && pipeline->Functions.getValue()->getTypeId() - == Fem::FemPostFunctionProvider::getClassTypeId()) { - - ui->FunctionBox->clear(); - QStringList items; - std::size_t currentItem = 0; - App::DocumentObject* currentFunction = - static_cast(getObject())->Function.getValue(); - const std::vector& funcs = - static_cast(pipeline->Functions.getValue()) - ->Functions.getValues(); - for (std::size_t i = 0; i < funcs.size(); ++i) { - items.push_back(QString::fromLatin1(funcs[i]->getNameInDocument())); - if (currentFunction == funcs[i]) - currentItem = i; - } - ui->FunctionBox->addItems(items); - ui->FunctionBox->setCurrentIndex(currentItem); - } - } -} - -void TaskPostClip::on_CreateButton_triggered(QAction*) { - - int numFuncs = ui->FunctionBox->count(); - int currentItem = ui->FunctionBox->currentIndex(); - collectImplicitFunctions(); - - // if a new function was successfully added use it - int indexCount = ui->FunctionBox->count(); - if (indexCount > currentItem + 1) - ui->FunctionBox->setCurrentIndex(indexCount - 1); - - // When the first function ever was added, a signal must be emitted - if (numFuncs == 0) { - Q_EMIT emitAddedFunction(); - } - - recompute(); -} - -void TaskPostClip::on_FunctionBox_currentIndexChanged(int idx) { - - //set the correct property - std::vector pipelines; - pipelines = getDocument()->getObjectsOfType(); - if (!pipelines.empty()) { - Fem::FemPostPipeline* pipeline = pipelines.front(); - if (pipeline->Functions.getValue() && pipeline->Functions.getValue()->getTypeId() - == Fem::FemPostFunctionProvider::getClassTypeId()) { - - const std::vector& funcs = - static_cast(pipeline->Functions.getValue()) - ->Functions.getValues(); - if (idx >= 0) - static_cast(getObject())->Function.setValue(funcs[idx]); - else - static_cast(getObject())->Function.setValue(nullptr); - } - } - - //load the correct view - Fem::FemPostFunction* fobj = static_cast( - static_cast(getObject())->Function.getValue()); - Gui::ViewProvider* view = nullptr; - if (fobj) - view = Gui::Application::Instance->getViewProvider(fobj); - - if (fwidget) - fwidget->deleteLater(); - - if (view) { - fwidget = static_cast(view)->createControlWidget(); - fwidget->setParent(ui->Container); - fwidget->setViewProvider(static_cast(view)); - ui->Container->layout()->addWidget(fwidget); - } - recompute(); -} - -void TaskPostClip::on_CutCells_toggled(bool val) { - - static_cast(getObject())->CutCells.setValue(val); - recompute(); -} - -void TaskPostClip::on_InsideOut_toggled(bool val) { - - static_cast(getObject())->InsideOut.setValue(val); - recompute(); -} +// in the following, the different filters sorted alphabetically +// *************************************************************************** // *************************************************************************** -// data along a line +// data along line filter TaskPostDataAlongLine::TaskPostDataAlongLine(ViewProviderDocumentObject* view, QWidget* parent) : TaskPostBox(view, Gui::BitmapFactory().pixmap("FEM_PostFilterDataAlongLine"), tr("Data along a line options"), parent), ui(new Ui_TaskPostDataAlongLine) { - assert(view->isDerivedFrom(ViewProviderFemPostDataAlongLine::getClassTypeId())); //we load the views widget @@ -695,20 +546,34 @@ TaskPostDataAlongLine::TaskPostDataAlongLine(ViewProviderDocumentObject* view, Q int res = static_cast(getObject())->Resolution.getValue(); ui->resolution->setValue(res); - connect(ui->point1X, qOverload(&Gui::QuantitySpinBox::valueChanged), - this, &TaskPostDataAlongLine::point1Changed); - connect(ui->point1Y, qOverload(&Gui::QuantitySpinBox::valueChanged), - this, &TaskPostDataAlongLine::point1Changed); - connect(ui->point1Z, qOverload(&Gui::QuantitySpinBox::valueChanged), - this, &TaskPostDataAlongLine::point1Changed); - connect(ui->point2X, qOverload(&Gui::QuantitySpinBox::valueChanged), - this, &TaskPostDataAlongLine::point2Changed); - connect(ui->point2Y, qOverload(&Gui::QuantitySpinBox::valueChanged), - this, &TaskPostDataAlongLine::point2Changed); - connect(ui->point2Z, qOverload(&Gui::QuantitySpinBox::valueChanged), - this, &TaskPostDataAlongLine::point2Changed); - connect(ui->resolution, qOverload(&QSpinBox::valueChanged), - this, &TaskPostDataAlongLine::resolutionChanged); + connect(ui->point1X, + qOverload(&Gui::QuantitySpinBox::valueChanged), + this, + &TaskPostDataAlongLine::point1Changed); + connect(ui->point1Y, + qOverload(&Gui::QuantitySpinBox::valueChanged), + this, + &TaskPostDataAlongLine::point1Changed); + connect(ui->point1Z, + qOverload(&Gui::QuantitySpinBox::valueChanged), + this, + &TaskPostDataAlongLine::point1Changed); + connect(ui->point2X, + qOverload(&Gui::QuantitySpinBox::valueChanged), + this, + &TaskPostDataAlongLine::point2Changed); + connect(ui->point2Y, + qOverload(&Gui::QuantitySpinBox::valueChanged), + this, + &TaskPostDataAlongLine::point2Changed); + connect(ui->point2Z, + qOverload(&Gui::QuantitySpinBox::valueChanged), + this, + &TaskPostDataAlongLine::point2Changed); + connect(ui->resolution, + qOverload(&QSpinBox::valueChanged), + this, + &TaskPostDataAlongLine::resolutionChanged); //update all fields updateEnumerationList(getTypedView()->DisplayMode, @@ -717,39 +582,36 @@ TaskPostDataAlongLine::TaskPostDataAlongLine(ViewProviderDocumentObject* view, Q updateEnumerationList(getTypedView()->VectorMode, ui->VectorMode); } -TaskPostDataAlongLine::~TaskPostDataAlongLine() { +TaskPostDataAlongLine::~TaskPostDataAlongLine() +{} -} +void TaskPostDataAlongLine::applyPythonCode() +{} -void TaskPostDataAlongLine::applyPythonCode() { - -} - -static const char* cursor_triangle[] = { -"32 32 3 1", -" c None", -". c #FFFFFF", -"+ c #FF0000", -" . ", -" . ", -" . ", -" . ", -" . ", -" ", -"..... ..... ", -" ", -" . ", -" . ", -" . ++ ", -" . + + ", -" . + ++ + ", -" + ++++ + ", -" + ++ ++ + ", -" + ++++++++ + ", -" ++ ++ ++ ++ " }; - -void TaskPostDataAlongLine::on_SelectPoints_clicked() { +static const char* cursor_triangle[] = {"32 32 3 1", + " c None", + ". c #FFFFFF", + "+ c #FF0000", + " . ", + " . ", + " . ", + " . ", + " . ", + " ", + "..... ..... ", + " ", + " . ", + " . ", + " . ++ ", + " . + + ", + " . + ++ + ", + " + ++++ + ", + " + ++ ++ + ", + " + ++++++++ + ", + " ++ ++ ++ ++ "}; +void TaskPostDataAlongLine::on_SelectPoints_clicked() +{ Gui::Command::doCommand(Gui::Command::Doc, ObjectVisible().c_str()); Gui::Document* doc = Gui::Application::Instance->getDocument(getDocument()); Gui::View3DInventor* view = static_cast(doc->getActiveView()); @@ -764,13 +626,14 @@ void TaskPostDataAlongLine::on_SelectPoints_clicked() { FemGui::PointMarker* marker = new FemGui::PointMarker(viewer, ObjName); viewer->addEventCallback(SoMouseButtonEvent::getClassTypeId(), - FemGui::TaskPostDataAlongLine::pointCallback, marker); - connect(marker, &PointMarker::PointsChanged, - this, &TaskPostDataAlongLine::onChange); + FemGui::TaskPostDataAlongLine::pointCallback, + marker); + connect(marker, &PointMarker::PointsChanged, this, &TaskPostDataAlongLine::onChange); } } -std::string TaskPostDataAlongLine::ObjectVisible() { +std::string TaskPostDataAlongLine::ObjectVisible() +{ return "for amesh in App.activeDocument().Objects:\n\ if \"Mesh\" in amesh.TypeId:\n\ aparttoshow = amesh.Name.replace(\"_Mesh\",\"\")\n\ @@ -779,8 +642,8 @@ std::string TaskPostDataAlongLine::ObjectVisible() { apart.ViewObject.Visibility = True\n"; } -void TaskPostDataAlongLine::on_CreatePlot_clicked() { - +void TaskPostDataAlongLine::on_CreatePlot_clicked() +{ App::DocumentObjectT objT(getObject()); std::string ObjName = objT.getObjectPython(); Gui::doCommandT(Gui::Command::Doc, "x = %s.XAxisData", ObjName); @@ -793,7 +656,6 @@ void TaskPostDataAlongLine::on_CreatePlot_clicked() { void TaskPostDataAlongLine::onChange(double x1, double y1, double z1, double x2, double y2, double z2) { - // call point1Changed only once ui->point1X->blockSignals(true); ui->point1Y->blockSignals(true); @@ -819,11 +681,13 @@ void TaskPostDataAlongLine::onChange(double x1, double y1, double z1, double x2, point2Changed(0.0); } -void TaskPostDataAlongLine::point1Changed(double) { - +void TaskPostDataAlongLine::point1Changed(double) +{ try { std::string ObjName = getObject()->getNameInDocument(); - Gui::cmdAppDocumentArgs(getDocument(), "%s.Point1 = App.Vector(%f, %f, %f)", ObjName, + Gui::cmdAppDocumentArgs(getDocument(), + "%s.Point1 = App.Vector(%f, %f, %f)", + ObjName, ui->point1X->value().getValue(), ui->point1Y->value().getValue(), ui->point1Z->value().getValue()); @@ -841,11 +705,13 @@ void TaskPostDataAlongLine::point1Changed(double) { } } -void TaskPostDataAlongLine::point2Changed(double) { - +void TaskPostDataAlongLine::point2Changed(double) +{ try { std::string ObjName = getObject()->getNameInDocument(); - Gui::cmdAppDocumentArgs(getDocument(), "%s.Point2 = App.Vector(%f, %f, %f)", ObjName, + Gui::cmdAppDocumentArgs(getDocument(), + "%s.Point2 = App.Vector(%f, %f, %f)", + ObjName, ui->point2X->value().getValue(), ui->point2Y->value().getValue(), ui->point2Z->value().getValue()); @@ -863,8 +729,8 @@ void TaskPostDataAlongLine::point2Changed(double) { } } -void TaskPostDataAlongLine::resolutionChanged(int val) { - +void TaskPostDataAlongLine::resolutionChanged(int val) +{ static_cast(getObject())->Resolution.setValue(val); // recompute the feature getObject()->recomputeFeature(); @@ -910,27 +776,28 @@ void TaskPostDataAlongLine::pointCallback(void* ud, SoEventCallback* n) } } -void TaskPostDataAlongLine::on_Representation_activated(int i) { - +void TaskPostDataAlongLine::on_Representation_activated(int i) +{ getTypedView()->DisplayMode.setValue(i); updateEnumerationList(getTypedView()->Field, ui->Field); updateEnumerationList(getTypedView()->VectorMode, ui->VectorMode); } -void TaskPostDataAlongLine::on_Field_activated(int i) { - +void TaskPostDataAlongLine::on_Field_activated(int i) +{ getTypedView()->Field.setValue(i); std::string FieldName = ui->Field->currentText().toStdString(); static_cast(getObject())->PlotData.setValue(FieldName); updateEnumerationList(getTypedView()->VectorMode, ui->VectorMode); } -void TaskPostDataAlongLine::on_VectorMode_activated(int i) { - +void TaskPostDataAlongLine::on_VectorMode_activated(int i) +{ getTypedView()->VectorMode.setValue(i); } -std::string TaskPostDataAlongLine::Plot() { +std::string TaskPostDataAlongLine::Plot() +{ auto xlabel = tr("Length", "X-Axis plot label"); std::ostringstream oss; oss << "import FreeCAD\n\ @@ -940,7 +807,8 @@ from matplotlib import pyplot as plt\n\ plt.ioff()\n\ plt.figure(title)\n\ plt.plot(x, y)\n\ -plt.xlabel(\"" << xlabel.toStdString() << "\")\n\ +plt.xlabel(\"" + << xlabel.toStdString() << "\")\n\ plt.ylabel(title)\n\ plt.title(title)\n\ plt.grid()\n\ @@ -953,13 +821,12 @@ plt.show()\n"; // *************************************************************************** -// data at point +// data at point filter TaskPostDataAtPoint::TaskPostDataAtPoint(ViewProviderDocumentObject* view, QWidget* parent) : TaskPostBox(view, Gui::BitmapFactory().pixmap("FEM_PostFilterDataAtPoint"), tr("Data at point options"), parent), ui(new Ui_TaskPostDataAtPoint) { - assert(view->isDerivedFrom(ViewProviderFemPostDataAtPoint::getClassTypeId())); //we load the views widget @@ -996,57 +863,62 @@ TaskPostDataAtPoint::TaskPostDataAtPoint(ViewProviderDocumentObject* view, QWidg updateEnumerationList(getTypedView()->Field, ui->Field); // read in point value - auto pointValue = static_cast(getObject())->PointData[0]; + auto pointValue = static_cast(getObject())->PointData[0]; showValue(pointValue, static_cast(getObject())->Unit.getValue()); - connect(ui->centerX, qOverload(&Gui::QuantitySpinBox::valueChanged), - this, &TaskPostDataAtPoint::centerChanged); - connect(ui->centerY, qOverload(&Gui::QuantitySpinBox::valueChanged), - this, &TaskPostDataAtPoint::centerChanged); - connect(ui->centerZ, qOverload(&Gui::QuantitySpinBox::valueChanged), - this, &TaskPostDataAtPoint::centerChanged); + connect(ui->centerX, + qOverload(&Gui::QuantitySpinBox::valueChanged), + this, + &TaskPostDataAtPoint::centerChanged); + connect(ui->centerY, + qOverload(&Gui::QuantitySpinBox::valueChanged), + this, + &TaskPostDataAtPoint::centerChanged); + connect(ui->centerZ, + qOverload(&Gui::QuantitySpinBox::valueChanged), + this, + &TaskPostDataAtPoint::centerChanged); // the point filter object needs to be recomputed // to fill all fields with data at the current point getObject()->recomputeFeature(); } -TaskPostDataAtPoint::~TaskPostDataAtPoint() { +TaskPostDataAtPoint::~TaskPostDataAtPoint() +{ App::Document* doc = getDocument(); if (doc) doc->recompute(); } -void TaskPostDataAtPoint::applyPythonCode() { +void TaskPostDataAtPoint::applyPythonCode() +{} -} - -static const char* cursor_star[] = { -"32 17 3 1", -" c None", -". c #FFFFFF", -"+ c #FF0000", -" . ", -" . ", -" . ", -" . ", -" . ", -" ", -"..... ..... ", -" ", -" . ", -" . ", -" . ++ ", -" . + + ", -" . + ++ + ", -" + ++++ + ", -" + ++ ++ + ", -" + ++++++++ + ", -" ++ ++ ++ ++ " }; - -void TaskPostDataAtPoint::on_SelectPoint_clicked() { +static const char* cursor_star[] = {"32 17 3 1", + " c None", + ". c #FFFFFF", + "+ c #FF0000", + " . ", + " . ", + " . ", + " . ", + " . ", + " ", + "..... ..... ", + " ", + " . ", + " . ", + " . ++ ", + " . + + ", + " . + ++ + ", + " + ++++ + ", + " + ++ ++ + ", + " + ++++++++ + ", + " ++ ++ ++ ++ "}; +void TaskPostDataAtPoint::on_SelectPoint_clicked() +{ Gui::Command::doCommand(Gui::Command::Doc, ObjectVisible().c_str()); Gui::Document* doc = Gui::Application::Instance->getDocument(getDocument()); Gui::View3DInventor* view = static_cast(doc->getActiveView()); @@ -1061,14 +933,16 @@ void TaskPostDataAtPoint::on_SelectPoint_clicked() { FemGui::DataMarker* marker = new FemGui::DataMarker(viewer, ObjName); viewer->addEventCallback(SoMouseButtonEvent::getClassTypeId(), - FemGui::TaskPostDataAtPoint::pointCallback, marker); + FemGui::TaskPostDataAtPoint::pointCallback, + marker); connect(marker, &DataMarker::PointsChanged, this, &TaskPostDataAtPoint::onChange); } getTypedView()->DisplayMode.setValue(1); updateEnumerationList(getTypedView()->Field, ui->Field); } -std::string TaskPostDataAtPoint::ObjectVisible() { +std::string TaskPostDataAtPoint::ObjectVisible() +{ return "for amesh in App.activeDocument().Objects:\n\ if \"Mesh\" in amesh.TypeId:\n\ aparttoshow = amesh.Name.replace(\"_Mesh\",\"\")\n\ @@ -1077,7 +951,8 @@ std::string TaskPostDataAtPoint::ObjectVisible() { apart.ViewObject.Visibility = True\n"; } -void TaskPostDataAtPoint::onChange(double x, double y, double z) { +void TaskPostDataAtPoint::onChange(double x, double y, double z) +{ // call centerChanged only once ui->centerX->blockSignals(true); @@ -1092,11 +967,13 @@ void TaskPostDataAtPoint::onChange(double x, double y, double z) { centerChanged(0.0); } -void TaskPostDataAtPoint::centerChanged(double) { - +void TaskPostDataAtPoint::centerChanged(double) +{ try { std::string ObjName = getObject()->getNameInDocument(); - Gui::cmdAppDocumentArgs(getDocument(), "%s.Center = App.Vector(%f, %f, %f)", ObjName, + Gui::cmdAppDocumentArgs(getDocument(), + "%s.Center = App.Vector(%f, %f, %f)", + ObjName, ui->centerX->value().getValue(), ui->centerY->value().getValue(), ui->centerZ->value().getValue()); @@ -1148,8 +1025,8 @@ void TaskPostDataAtPoint::pointCallback(void* ud, SoEventCallback* n) } } -void TaskPostDataAtPoint::on_Field_activated(int i) { - +void TaskPostDataAtPoint::on_Field_activated(int i) +{ getTypedView()->Field.setValue(i); std::string FieldName = ui->Field->currentText().toStdString(); // there is no "None" for the FieldName property, thus return here @@ -1163,23 +1040,22 @@ void TaskPostDataAtPoint::on_Field_activated(int i) { // Set the unit for the different known result types. // CCX names - if ( (FieldName == "von Mises Stress") || (FieldName == "Tresca Stress") - || (FieldName == "Major Principal Stress") - || (FieldName == "Intermediate Principal Stress") - || (FieldName == "Minor Principal Stress") - || (FieldName == "Major Principal Stress Vector") + if ((FieldName == "von Mises Stress") || (FieldName == "Tresca Stress") + || (FieldName == "Major Principal Stress") || (FieldName == "Intermediate Principal Stress") + || (FieldName == "Minor Principal Stress") || (FieldName == "Major Principal Stress Vector") || (FieldName == "Intermediate Principal Stress Vector") - || (FieldName == "Minor Principal Stress Vector") - || (FieldName == "Stress xx component") || (FieldName == "Stress xy component") - || (FieldName == "Stress xz component") || (FieldName == "Stress yy component") - || (FieldName == "Stress yz component") || (FieldName == "Stress zz component") ) { + || (FieldName == "Minor Principal Stress Vector") || (FieldName == "Stress xx component") + || (FieldName == "Stress xy component") || (FieldName == "Stress xz component") + || (FieldName == "Stress yy component") || (FieldName == "Stress yz component") + || (FieldName == "Stress zz component")) { static_cast(getObject())->Unit.setValue("Pa"); } // The Elmer names are different. If there are EigenModes, the names are unique for // every mode. Therefore we only check for the beginning of the name. - else if ( (FieldName.find("tresca", 0) == 0) || (FieldName.find("vonmises", 0) == 0) - || (FieldName.find("stress_", 0) == 0) || (FieldName.find("principal stress", 0) == 0) ) { - static_cast(getObject())->Unit.setValue("Pa"); + else if ((FieldName.find("tresca", 0) == 0) || (FieldName.find("vonmises", 0) == 0) + || (FieldName.find("stress_", 0) == 0) + || (FieldName.find("principal stress", 0) == 0)) { + static_cast(getObject())->Unit.setValue("Pa"); } else if ((FieldName == "current density") || (FieldName == "current density re") || (FieldName == "current density im") || (FieldName == "current density abs")) { @@ -1197,7 +1073,7 @@ void TaskPostDataAtPoint::on_Field_activated(int i) { static_cast(getObject())->Unit.setValue("V/m"); } else if (FieldName == "electric flux") { - static_cast(getObject())->Unit.setValue("A*s/m^2"); + static_cast(getObject())->Unit.setValue("A*s/m^2"); } else if (FieldName == "electric force density") { static_cast(getObject())->Unit.setValue("N/m^2"); @@ -1239,7 +1115,7 @@ void TaskPostDataAtPoint::on_Field_activated(int i) { static_cast(getObject())->Unit.setValue("V"); } else if (FieldName == "potential flux") { - static_cast(getObject())->Unit.setValue("W/m^2"); + static_cast(getObject())->Unit.setValue("W/m^2"); } // potential loads are in Coulomb: https://www.elmerfem.org/forum/viewtopic.php?t=7780 else if (FieldName == "potential loads") { @@ -1253,7 +1129,7 @@ void TaskPostDataAtPoint::on_Field_activated(int i) { static_cast(getObject())->Unit.setValue("K"); } else if (FieldName == "temperature flux") { - static_cast(getObject())->Unit.setValue("W/m^2"); + static_cast(getObject())->Unit.setValue("W/m^2"); } else { static_cast(getObject())->Unit.setValue(""); @@ -1296,294 +1172,20 @@ std::string TaskPostDataAtPoint::toString(double val) const return valueStream.str(); } -// *************************************************************************** -// scalar clip filter -TaskPostScalarClip::TaskPostScalarClip(ViewProviderDocumentObject* view, QWidget* parent) - : TaskPostBox(view, Gui::BitmapFactory().pixmap("FEM_PostFilterClipScalar"), - tr("Scalar clip options"), parent), - ui(new Ui_TaskPostScalarClip) -{ - - assert(view->isDerivedFrom(ViewProviderFemPostScalarClip::getClassTypeId())); - - //we load the views widget - proxy = new QWidget(this); - ui->setupUi(proxy); - QMetaObject::connectSlotsByName(this); - this->groupLayout()->addWidget(proxy); - - //load the default values - updateEnumerationList(getTypedObject()->Scalars, ui->Scalar); - ui->InsideOut->setChecked( - static_cast(getObject())->InsideOut.getValue()); - App::PropertyFloatConstraint& scalar_prop = - static_cast(getObject())->Value; - double scalar_factor = scalar_prop.getValue(); - - // set spinbox scalar_factor, don't forget to sync the slider - ui->Value->blockSignals(true); - ui->Value->setValue(scalar_factor); - ui->Value->blockSignals(false); - - // sync the slider - // slider min = 0%, slider max = 100% - // - // scalar_factor - // slider_value = --------------- x 100 - // max - // - double max = scalar_prop.getConstraints()->UpperBound; - int slider_value = (scalar_factor / max) * 100.; - ui->Slider->blockSignals(true); - ui->Slider->setValue(slider_value); - ui->Slider->blockSignals(false); - Base::Console().Log( - "init: scalar_factor, slider_value: %f, %i: \n", scalar_factor, slider_value); -} - -TaskPostScalarClip::~TaskPostScalarClip() { - -} - -void TaskPostScalarClip::applyPythonCode() { - -} - -void TaskPostScalarClip::on_Scalar_currentIndexChanged(int idx) { - - static_cast(getObject())->Scalars.setValue(idx); - recompute(); - - // update constraints and values - App::PropertyFloatConstraint& scalar_prop = - static_cast(getObject())->Value; - double scalar_factor = scalar_prop.getValue(); - double min = scalar_prop.getConstraints()->LowerBound; - double max = scalar_prop.getConstraints()->UpperBound; - - ui->Maximum->setText(QString::number(min)); - ui->Minimum->setText(QString::number(max)); - - // set scalar_factor, don't forget to sync the slider - ui->Value->blockSignals(true); - ui->Value->setValue(scalar_factor); - ui->Value->blockSignals(false); - - // sync the slider - ui->Slider->blockSignals(true); - int slider_value = (scalar_factor / max) * 100.; - ui->Slider->setValue(slider_value); - ui->Slider->blockSignals(false); -} - -void TaskPostScalarClip::on_Slider_valueChanged(int v) { - - App::PropertyFloatConstraint& value = - static_cast(getObject())->Value; - double val = value.getConstraints()->LowerBound * (1 - double(v) / 100.) - + double(v) / 100. * value.getConstraints()->UpperBound; - - value.setValue(val); - recompute(); - - //don't forget to sync the spinbox - ui->Value->blockSignals(true); - ui->Value->setValue(val); - ui->Value->blockSignals(false); -} - -void TaskPostScalarClip::on_Value_valueChanged(double v) { - - App::PropertyFloatConstraint& value = - static_cast(getObject())->Value; - value.setValue(v); - recompute(); - - //don't forget to sync the slider - ui->Slider->blockSignals(true); - ui->Slider->setValue( - int(((v - value.getConstraints()->LowerBound) - / (value.getConstraints()->UpperBound - value.getConstraints()->LowerBound)) - * 100.)); - ui->Slider->blockSignals(false); -} - -void TaskPostScalarClip::on_InsideOut_toggled(bool val) { - - static_cast(getObject())->InsideOut.setValue(val); - recompute(); -} - // *************************************************************************** -// warp filter -// spinbox min, slider, spinbox max -// spinbox warp factor -TaskPostWarpVector::TaskPostWarpVector(ViewProviderDocumentObject* view, QWidget* parent) - : TaskPostBox(view, Gui::BitmapFactory().pixmap("FEM_PostFilterWarp"), tr("Warp options"), - parent), - ui(new Ui_TaskPostWarpVector) +// clip filter +TaskPostClip::TaskPostClip(ViewProviderDocumentObject* view, App::PropertyLink* function, + QWidget* parent) + : TaskPostBox(view, Gui::BitmapFactory().pixmap("FEM_PostFilterClipRegion"), + tr("Clip region, choose implicit function"), parent), + ui(new Ui_TaskPostClip) { - - assert(view->isDerivedFrom(ViewProviderFemPostWarpVector::getClassTypeId())); - - // we load the views widget - proxy = new QWidget(this); - ui->setupUi(proxy); - QMetaObject::connectSlotsByName(this); - this->groupLayout()->addWidget(proxy); - - // load the default values for warp display - updateEnumerationList(getTypedObject()->Vector, ui->Vector); - double warp_factor = static_cast(getObject()) - ->Factor.getValue();// get the standard warp factor - - // set spinbox warp_factor, don't forget to sync the slider - ui->Value->blockSignals(true); - ui->Value->setValue(warp_factor); - ui->Value->blockSignals(false); - - // set min and max, don't forget to sync the slider - // TODO if warp is set to standard 1.0, find a smarter way for standard min, max and warp_factor - // may be depend on grid boundbox and min max vector values - ui->Max->blockSignals(true); - ui->Max->setValue(warp_factor == 0 ? 1 : warp_factor * 10.); - ui->Max->blockSignals(false); - ui->Min->blockSignals(true); - ui->Min->setValue(warp_factor == 0 ? 0 : warp_factor / 10.); - ui->Min->blockSignals(false); - - // sync slider - ui->Slider->blockSignals(true); - // slider min = 0%, slider max = 100% - // - // ( warp_factor - min ) - // slider_value = ----------------------- x 100 - // ( max - min ) - // - int slider_value = - (warp_factor - ui->Min->value()) / (ui->Max->value() - ui->Min->value()) * 100.; - ui->Slider->setValue(slider_value); - ui->Slider->blockSignals(false); - Base::Console().Log("init: warp_factor, slider_value: %f, %i: \n", warp_factor, slider_value); -} - -TaskPostWarpVector::~TaskPostWarpVector() { - -} - -void TaskPostWarpVector::applyPythonCode() { - -} - -void TaskPostWarpVector::on_Vector_currentIndexChanged(int idx) { - // combobox to choose the result to warp - - static_cast(getObject())->Vector.setValue(idx); - recompute(); -} - -void TaskPostWarpVector::on_Slider_valueChanged(int slider_value) { - // slider changed, change warp factor and sync spinbox - - // - // ( max - min ) - // warp_factor = min + ( slider_value x --------------- ) - // 100 - // - double warp_factor = - ui->Min->value() + ((ui->Max->value() - ui->Min->value()) / 100.) * slider_value; - static_cast(getObject())->Factor.setValue(warp_factor); - recompute(); - - // sync the spinbox - ui->Value->blockSignals(true); - ui->Value->setValue(warp_factor); - ui->Value->blockSignals(false); - Base::Console().Log("Change: warp_factor, slider_value: %f, %i: \n", warp_factor, slider_value); -} - -void TaskPostWarpVector::on_Value_valueChanged(double warp_factor) { - // spinbox changed, change warp factor and sync slider - - // TODO warp factor should not be smaller than min and greater than max, - // but problems on automate change of warp_factor, see on_Max_valueChanged - - static_cast(getObject())->Factor.setValue(warp_factor); - recompute(); - - // sync the slider, see above for formula - ui->Slider->blockSignals(true); - int slider_value = - (warp_factor - ui->Min->value()) / (ui->Max->value() - ui->Min->value()) * 100.; - ui->Slider->setValue(slider_value); - ui->Slider->blockSignals(false); - Base::Console().Log("Change: warp_factor, slider_value: %f, %i: \n", warp_factor, slider_value); -} - -void TaskPostWarpVector::on_Max_valueChanged(double) { - - // TODO max should be greater than min, see a few lines later on problem on input characters - ui->Slider->blockSignals(true); - ui->Slider->setValue((ui->Value->value() - ui->Min->value()) - / (ui->Max->value() - ui->Min->value()) * 100.); - ui->Slider->blockSignals(false); - - /* - * problem, if warp_factor is 2000 one would like to input 4000 as max, one starts to input 4 - * immediately the warp_factor is changed to 4 because 4 < 2000, but one has just input - * one character of their 4000. * I do not know how to solve this, but the code to set slider - * and spinbox is fine thus I leave it ... - * - * mhh it works if "apply changes to pipeline directly" button is deactivated, - * still it really confuses if the button is active. More investigation is needed. - * - // set warp factor to max, if warp factor > max - if (ui->Value->value() > ui->Max->value()) { - double warp_factor = ui->Max->value(); - static_cast(getObject())->Factor.setValue(warp_factor); - recompute(); - - // sync the slider, see above for formula - ui->Slider->blockSignals(true); - int slider_value = (warp_factor - ui->Min->value()) - / (ui->Max->value() - ui->Min->value()) * 100.; - ui->Slider->setValue(slider_value); - ui->Slider->blockSignals(false); - // sync the spinbox, see above for formula - ui->Value->blockSignals(true); - ui->Value->setValue(warp_factor); - ui->Value->blockSignals(false); - Base::Console().Log("Change: warp_factor, slider_value: %f, %i: \n", warp_factor, slider_value); - } - */ -} - -void TaskPostWarpVector::on_Min_valueChanged(double) { - - // TODO min should be smaller than max - // TODO if warp factor is smaller than min, warp factor should be min, don't forget to sync - ui->Slider->blockSignals(true); - ui->Slider->setValue((ui->Value->value() - ui->Min->value()) - / (ui->Max->value() - ui->Min->value()) * 100.); - ui->Slider->blockSignals(false); -} - - -// *************************************************************************** -// function clip filter -TaskPostCut::TaskPostCut(ViewProviderDocumentObject* view, App::PropertyLink* function, - QWidget* parent) - : TaskPostBox(view, Gui::BitmapFactory().pixmap("FEM_PostFilterCutFunction"), - tr("Function cut, choose implicit function"), parent), - ui(new Ui_TaskPostCut) -{ - - assert(view->isDerivedFrom(ViewProviderFemPostCut::getClassTypeId())); + assert(view->isDerivedFrom(ViewProviderFemPostClip::getClassTypeId())); assert(function); - Q_UNUSED(function) + Q_UNUSED(function); - fwidget = nullptr; + fwidget = nullptr; //we load the views widget proxy = new QWidget(this); @@ -1604,24 +1206,27 @@ TaskPostCut::TaskPostCut(ViewProviderDocumentObject* view, App::PropertyLink* fu if (cmd && cmd->getAction()) cmd->getAction()->addTo(ui->CreateButton); ui->CreateButton->setPopupMode(QToolButton::InstantPopup); + + //load the default values + ui->CutCells->setChecked( + static_cast(getObject())->CutCells.getValue()); + ui->InsideOut->setChecked( + static_cast(getObject())->InsideOut.getValue()); } -TaskPostCut::~TaskPostCut() { +TaskPostClip::~TaskPostClip() +{} -} - -void TaskPostCut::applyPythonCode() { - -} - -void TaskPostCut::collectImplicitFunctions() { +void TaskPostClip::applyPythonCode() +{} +void TaskPostClip::collectImplicitFunctions() +{ std::vector pipelines; pipelines = getDocument()->getObjectsOfType(); if (!pipelines.empty()) { Fem::FemPostPipeline* pipeline = pipelines.front(); - if (pipeline->Functions.getValue() - && pipeline->Functions.getValue()->getTypeId() + if (pipeline->Functions.getValue() && pipeline->Functions.getValue()->getTypeId() == Fem::FemPostFunctionProvider::getClassTypeId()) { ui->FunctionBox->clear(); @@ -1643,8 +1248,8 @@ void TaskPostCut::collectImplicitFunctions() { } } -void TaskPostCut::on_CreateButton_triggered(QAction*) { - +void TaskPostClip::on_CreateButton_triggered(QAction*) +{ int numFuncs = ui->FunctionBox->count(); int currentItem = ui->FunctionBox->currentIndex(); collectImplicitFunctions(); @@ -1662,30 +1267,29 @@ void TaskPostCut::on_CreateButton_triggered(QAction*) { recompute(); } -void TaskPostCut::on_FunctionBox_currentIndexChanged(int idx) { - +void TaskPostClip::on_FunctionBox_currentIndexChanged(int idx) +{ //set the correct property std::vector pipelines; pipelines = getDocument()->getObjectsOfType(); if (!pipelines.empty()) { Fem::FemPostPipeline* pipeline = pipelines.front(); - if (pipeline->Functions.getValue() - && pipeline->Functions.getValue()->getTypeId() + if (pipeline->Functions.getValue() && pipeline->Functions.getValue()->getTypeId() == Fem::FemPostFunctionProvider::getClassTypeId()) { const std::vector& funcs = static_cast(pipeline->Functions.getValue()) ->Functions.getValues(); if (idx >= 0) - static_cast(getObject())->Function.setValue(funcs[idx]); + static_cast(getObject())->Function.setValue(funcs[idx]); else - static_cast(getObject())->Function.setValue(nullptr); + static_cast(getObject())->Function.setValue(nullptr); } } //load the correct view Fem::FemPostFunction* fobj = static_cast( - static_cast(getObject())->Function.getValue()); + static_cast(getObject())->Function.getValue()); Gui::ViewProvider* view = nullptr; if (fobj) view = Gui::Application::Instance->getViewProvider(fobj); @@ -1702,6 +1306,18 @@ void TaskPostCut::on_FunctionBox_currentIndexChanged(int idx) { recompute(); } +void TaskPostClip::on_CutCells_toggled(bool val) +{ + static_cast(getObject())->CutCells.setValue(val); + recompute(); +} + +void TaskPostClip::on_InsideOut_toggled(bool val) +{ + static_cast(getObject())->InsideOut.setValue(val); + recompute(); +} + // *************************************************************************** // contours filter @@ -1732,14 +1348,19 @@ TaskPostContours::TaskPostContours(ViewProviderDocumentObject* view, QWidget* pa ui->noColorCB->setChecked(colorState); // connect - connect(ui->fieldsCB, qOverload(&QComboBox::currentIndexChanged), - this, &TaskPostContours::onFieldsChanged); - connect(ui->vectorsCB, qOverload(&QComboBox::currentIndexChanged), - this, &TaskPostContours::onVectorModeChanged); - connect(ui->numberContoursSB, qOverload(&QSpinBox::valueChanged), - this, &TaskPostContours::onNumberOfContoursChanged); - connect(ui->noColorCB, &QCheckBox::toggled, - this, &TaskPostContours::onNoColorChanged); + connect(ui->fieldsCB, + qOverload(&QComboBox::currentIndexChanged), + this, + &TaskPostContours::onFieldsChanged); + connect(ui->vectorsCB, + qOverload(&QComboBox::currentIndexChanged), + this, + &TaskPostContours::onVectorModeChanged); + connect(ui->numberContoursSB, + qOverload(&QSpinBox::valueChanged), + this, + &TaskPostContours::onNumberOfContoursChanged); + connect(ui->noColorCB, &QCheckBox::toggled, this, &TaskPostContours::onNoColorChanged); } TaskPostContours::~TaskPostContours() @@ -1835,4 +1456,403 @@ void TaskPostContours::onNoColorChanged(bool state) } +// *************************************************************************** +// cut filter +TaskPostCut::TaskPostCut(ViewProviderDocumentObject* view, App::PropertyLink* function, + QWidget* parent) + : TaskPostBox(view, Gui::BitmapFactory().pixmap("FEM_PostFilterCutFunction"), + tr("Function cut, choose implicit function"), parent), + ui(new Ui_TaskPostCut) +{ + assert(view->isDerivedFrom(ViewProviderFemPostCut::getClassTypeId())); + assert(function); + Q_UNUSED(function) + + fwidget = nullptr; + + //we load the views widget + proxy = new QWidget(this); + ui->setupUi(proxy); + QMetaObject::connectSlotsByName(this); + this->groupLayout()->addWidget(proxy); + + //the layout for the container widget + QVBoxLayout* layout = new QVBoxLayout(); + ui->Container->setLayout(layout); + + //fill up the combo box with possible functions + collectImplicitFunctions(); + + //add the function creation command + Gui::CommandManager& rcCmdMgr = Gui::Application::Instance->commandManager(); + Gui::Command* cmd = rcCmdMgr.getCommandByName("FEM_PostCreateFunctions"); + if (cmd && cmd->getAction()) + cmd->getAction()->addTo(ui->CreateButton); + ui->CreateButton->setPopupMode(QToolButton::InstantPopup); +} + +TaskPostCut::~TaskPostCut() +{} + +void TaskPostCut::applyPythonCode() +{} + +void TaskPostCut::collectImplicitFunctions() +{ + std::vector pipelines; + pipelines = getDocument()->getObjectsOfType(); + if (!pipelines.empty()) { + Fem::FemPostPipeline* pipeline = pipelines.front(); + if (pipeline->Functions.getValue() + && pipeline->Functions.getValue()->getTypeId() + == Fem::FemPostFunctionProvider::getClassTypeId()) { + + ui->FunctionBox->clear(); + QStringList items; + std::size_t currentItem = 0; + App::DocumentObject* currentFunction = + static_cast(getObject())->Function.getValue(); + const std::vector& funcs = + static_cast(pipeline->Functions.getValue()) + ->Functions.getValues(); + for (std::size_t i = 0; i < funcs.size(); ++i) { + items.push_back(QString::fromLatin1(funcs[i]->getNameInDocument())); + if (currentFunction == funcs[i]) + currentItem = i; + } + ui->FunctionBox->addItems(items); + ui->FunctionBox->setCurrentIndex(currentItem); + } + } +} + +void TaskPostCut::on_CreateButton_triggered(QAction*) +{ + int numFuncs = ui->FunctionBox->count(); + int currentItem = ui->FunctionBox->currentIndex(); + collectImplicitFunctions(); + + // if a new function was successfully added use it + int indexCount = ui->FunctionBox->count(); + if (indexCount > currentItem + 1) + ui->FunctionBox->setCurrentIndex(indexCount - 1); + + // When the first function ever was added, a signal must be emitted + if (numFuncs == 0) { + Q_EMIT emitAddedFunction(); + } + + recompute(); +} + +void TaskPostCut::on_FunctionBox_currentIndexChanged(int idx) +{ + //set the correct property + std::vector pipelines; + pipelines = getDocument()->getObjectsOfType(); + if (!pipelines.empty()) { + Fem::FemPostPipeline* pipeline = pipelines.front(); + if (pipeline->Functions.getValue() + && pipeline->Functions.getValue()->getTypeId() + == Fem::FemPostFunctionProvider::getClassTypeId()) { + + const std::vector& funcs = + static_cast(pipeline->Functions.getValue()) + ->Functions.getValues(); + if (idx >= 0) + static_cast(getObject())->Function.setValue(funcs[idx]); + else + static_cast(getObject())->Function.setValue(nullptr); + } + } + + //load the correct view + Fem::FemPostFunction* fobj = static_cast( + static_cast(getObject())->Function.getValue()); + Gui::ViewProvider* view = nullptr; + if (fobj) + view = Gui::Application::Instance->getViewProvider(fobj); + + if (fwidget) + fwidget->deleteLater(); + + if (view) { + fwidget = static_cast(view)->createControlWidget(); + fwidget->setParent(ui->Container); + fwidget->setViewProvider(static_cast(view)); + ui->Container->layout()->addWidget(fwidget); + } + recompute(); +} + + +// *************************************************************************** +// scalar clip filter +TaskPostScalarClip::TaskPostScalarClip(ViewProviderDocumentObject* view, QWidget* parent) + : TaskPostBox(view, Gui::BitmapFactory().pixmap("FEM_PostFilterClipScalar"), + tr("Scalar clip options"), parent), + ui(new Ui_TaskPostScalarClip) +{ + assert(view->isDerivedFrom(ViewProviderFemPostScalarClip::getClassTypeId())); + + //we load the views widget + proxy = new QWidget(this); + ui->setupUi(proxy); + QMetaObject::connectSlotsByName(this); + this->groupLayout()->addWidget(proxy); + + //load the default values + updateEnumerationList(getTypedObject()->Scalars, ui->Scalar); + ui->InsideOut->setChecked( + static_cast(getObject())->InsideOut.getValue()); + App::PropertyFloatConstraint& scalar_prop = + static_cast(getObject())->Value; + double scalar_factor = scalar_prop.getValue(); + + // set spinbox scalar_factor, don't forget to sync the slider + ui->Value->blockSignals(true); + ui->Value->setValue(scalar_factor); + ui->Value->blockSignals(false); + + // sync the slider + // slider min = 0%, slider max = 100% + // + // scalar_factor + // slider_value = --------------- x 100 + // max + // + double max = scalar_prop.getConstraints()->UpperBound; + int slider_value = (scalar_factor / max) * 100.; + ui->Slider->blockSignals(true); + ui->Slider->setValue(slider_value); + ui->Slider->blockSignals(false); + Base::Console().Log( + "init: scalar_factor, slider_value: %f, %i: \n", scalar_factor, slider_value); +} + +TaskPostScalarClip::~TaskPostScalarClip() +{} + +void TaskPostScalarClip::applyPythonCode() +{} + +void TaskPostScalarClip::on_Scalar_currentIndexChanged(int idx) +{ + static_cast(getObject())->Scalars.setValue(idx); + recompute(); + + // update constraints and values + App::PropertyFloatConstraint& scalar_prop = + static_cast(getObject())->Value; + double scalar_factor = scalar_prop.getValue(); + double min = scalar_prop.getConstraints()->LowerBound; + double max = scalar_prop.getConstraints()->UpperBound; + + ui->Maximum->setText(QString::number(min)); + ui->Minimum->setText(QString::number(max)); + + // set scalar_factor, don't forget to sync the slider + ui->Value->blockSignals(true); + ui->Value->setValue(scalar_factor); + ui->Value->blockSignals(false); + + // sync the slider + ui->Slider->blockSignals(true); + int slider_value = (scalar_factor / max) * 100.; + ui->Slider->setValue(slider_value); + ui->Slider->blockSignals(false); +} + +void TaskPostScalarClip::on_Slider_valueChanged(int v) +{ + App::PropertyFloatConstraint& value = + static_cast(getObject())->Value; + double val = value.getConstraints()->LowerBound * (1 - double(v) / 100.) + + double(v) / 100. * value.getConstraints()->UpperBound; + + value.setValue(val); + recompute(); + + //don't forget to sync the spinbox + ui->Value->blockSignals(true); + ui->Value->setValue(val); + ui->Value->blockSignals(false); +} + +void TaskPostScalarClip::on_Value_valueChanged(double v) +{ + App::PropertyFloatConstraint& value = + static_cast(getObject())->Value; + value.setValue(v); + recompute(); + + //don't forget to sync the slider + ui->Slider->blockSignals(true); + ui->Slider->setValue( + int(((v - value.getConstraints()->LowerBound) + / (value.getConstraints()->UpperBound - value.getConstraints()->LowerBound)) + * 100.)); + ui->Slider->blockSignals(false); +} + +void TaskPostScalarClip::on_InsideOut_toggled(bool val) +{ + static_cast(getObject())->InsideOut.setValue(val); + recompute(); +} + + +// *************************************************************************** +// warp vector filter +TaskPostWarpVector::TaskPostWarpVector(ViewProviderDocumentObject* view, QWidget* parent) + : TaskPostBox(view, Gui::BitmapFactory().pixmap("FEM_PostFilterWarp"), tr("Warp options"), + parent), + ui(new Ui_TaskPostWarpVector) +{ + assert(view->isDerivedFrom(ViewProviderFemPostWarpVector::getClassTypeId())); + + // we load the views widget + proxy = new QWidget(this); + ui->setupUi(proxy); + QMetaObject::connectSlotsByName(this); + this->groupLayout()->addWidget(proxy); + + // load the default values for warp display + updateEnumerationList(getTypedObject()->Vector, ui->Vector); + double warp_factor = static_cast(getObject()) + ->Factor.getValue();// get the standard warp factor + + // set spinbox warp_factor, don't forget to sync the slider + ui->Value->blockSignals(true); + ui->Value->setValue(warp_factor); + ui->Value->blockSignals(false); + + // set min and max, don't forget to sync the slider + // TODO if warp is set to standard 1.0, find a smarter way for standard min, max + // and warp_factor may be depend on grid boundbox and min max vector values + ui->Max->blockSignals(true); + ui->Max->setValue(warp_factor == 0 ? 1 : warp_factor * 10.); + ui->Max->blockSignals(false); + ui->Min->blockSignals(true); + ui->Min->setValue(warp_factor == 0 ? 0 : warp_factor / 10.); + ui->Min->blockSignals(false); + + // sync slider + ui->Slider->blockSignals(true); + // slider min = 0%, slider max = 100% + // + // ( warp_factor - min ) + // slider_value = ----------------------- x 100 + // ( max - min ) + // + int slider_value = + (warp_factor - ui->Min->value()) / (ui->Max->value() - ui->Min->value()) * 100.; + ui->Slider->setValue(slider_value); + ui->Slider->blockSignals(false); + Base::Console().Log("init: warp_factor, slider_value: %f, %i: \n", warp_factor, slider_value); +} + +TaskPostWarpVector::~TaskPostWarpVector() +{} + +void TaskPostWarpVector::applyPythonCode() +{} + +void TaskPostWarpVector::on_Vector_currentIndexChanged(int idx) +{ + // combobox to choose the result to warp + + static_cast(getObject())->Vector.setValue(idx); + recompute(); +} + +void TaskPostWarpVector::on_Slider_valueChanged(int slider_value) +{ + // slider changed, change warp factor and sync spinbox + + // + // ( max - min ) + // warp_factor = min + ( slider_value x --------------- ) + // 100 + // + double warp_factor = + ui->Min->value() + ((ui->Max->value() - ui->Min->value()) / 100.) * slider_value; + static_cast(getObject())->Factor.setValue(warp_factor); + recompute(); + + // sync the spinbox + ui->Value->blockSignals(true); + ui->Value->setValue(warp_factor); + ui->Value->blockSignals(false); + Base::Console().Log("Change: warp_factor, slider_value: %f, %i: \n", warp_factor, slider_value); +} + +void TaskPostWarpVector::on_Value_valueChanged(double warp_factor) +{ + // spinbox changed, change warp factor and sync slider + + // TODO warp factor should not be smaller than min and greater than max, + // but problems on automate change of warp_factor, see on_Max_valueChanged + + static_cast(getObject())->Factor.setValue(warp_factor); + recompute(); + + // sync the slider, see above for formula + ui->Slider->blockSignals(true); + int slider_value = + (warp_factor - ui->Min->value()) / (ui->Max->value() - ui->Min->value()) * 100.; + ui->Slider->setValue(slider_value); + ui->Slider->blockSignals(false); + Base::Console().Log("Change: warp_factor, slider_value: %f, %i: \n", warp_factor, slider_value); +} + +void TaskPostWarpVector::on_Max_valueChanged(double) +{ + // TODO max should be greater than min, see a few lines later on problem on input characters + ui->Slider->blockSignals(true); + ui->Slider->setValue((ui->Value->value() - ui->Min->value()) + / (ui->Max->value() - ui->Min->value()) * 100.); + ui->Slider->blockSignals(false); + + /* + * problem, if warp_factor is 2000 one would like to input 4000 as max, one starts to input 4 + * immediately the warp_factor is changed to 4 because 4 < 2000, but one has just input + * one character of their 4000. * I do not know how to solve this, but the code to set slider + * and spinbox is fine thus I leave it ... + * + * mhh it works if "apply changes to pipeline directly" button is deactivated, + * still it really confuses if the button is active. More investigation is needed. + * + // set warp factor to max, if warp factor > max + if (ui->Value->value() > ui->Max->value()) { + double warp_factor = ui->Max->value(); + static_cast(getObject())->Factor.setValue(warp_factor); + recompute(); + + // sync the slider, see above for formula + ui->Slider->blockSignals(true); + int slider_value = (warp_factor - ui->Min->value()) + / (ui->Max->value() - ui->Min->value()) * 100.; + ui->Slider->setValue(slider_value); + ui->Slider->blockSignals(false); + // sync the spinbox, see above for formula + ui->Value->blockSignals(true); + ui->Value->setValue(warp_factor); + ui->Value->blockSignals(false); + Base::Console().Log("Change: warp_factor, slider_value: %f, %i: \n", warp_factor, slider_value); + } + */ +} + +void TaskPostWarpVector::on_Min_valueChanged(double) +{ + // TODO min should be smaller than max + // TODO if warp factor is smaller than min, warp factor should be min, don't forget to sync + ui->Slider->blockSignals(true); + ui->Slider->setValue((ui->Value->value() - ui->Min->value()) + / (ui->Max->value() - ui->Min->value()) * 100.); + ui->Slider->blockSignals(false); +} + + #include "moc_TaskPostBoxes.cpp" diff --git a/src/Mod/Fem/Gui/TaskPostBoxes.h b/src/Mod/Fem/Gui/TaskPostBoxes.h index a67977fe6c..0a6e5e7803 100644 --- a/src/Mod/Fem/Gui/TaskPostBoxes.h +++ b/src/Mod/Fem/Gui/TaskPostBoxes.h @@ -51,8 +51,11 @@ class SoEventCallback; class SoMarkerSet; -namespace FemGui { +namespace FemGui +{ +// *************************************************************************** +// point marker class ViewProviderPointMarker; class PointMarker : public QObject { @@ -78,7 +81,6 @@ private: std::string ObjectInvisible(); }; - class FemGuiExport ViewProviderPointMarker : public Gui::ViewProviderDocumentObject { PROPERTY_HEADER_WITH_OVERRIDE(FemGui::ViewProviderPointMarker); @@ -93,6 +95,8 @@ protected: }; +// *************************************************************************** +// data marker class ViewProviderDataMarker; class DataMarker : public QObject { @@ -133,8 +137,11 @@ protected: friend class DataMarker; }; -class TaskPostBox : public Gui::TaskView::TaskBox { +// *************************************************************************** +// main task dialog +class TaskPostBox : public Gui::TaskView::TaskBox +{ Q_OBJECT public: @@ -176,7 +183,8 @@ private: }; -/// simulation dialog for the TaskView +// *************************************************************************** +// simulation dialog for the TaskView class TaskDlgPost : public Gui::TaskView::TaskDialog { Q_OBJECT @@ -217,6 +225,8 @@ protected: }; +// *************************************************************************** +// box to set the coloring class TaskPostDisplay : public TaskPostBox { Q_OBJECT @@ -241,8 +251,10 @@ private: }; -class TaskPostFunction : public TaskPostBox { - +// *************************************************************************** +// functions +class TaskPostFunction : public TaskPostBox +{ Q_OBJECT public: @@ -253,8 +265,76 @@ public: }; -class TaskPostClip : public TaskPostBox { +// *************************************************************************** +// in the following, the different filters sorted alphabetically +// *************************************************************************** + +// *************************************************************************** +// data along line filter +class TaskPostDataAlongLine: public TaskPostBox +{ + Q_OBJECT + +public: + explicit TaskPostDataAlongLine(Gui::ViewProviderDocumentObject* view, + QWidget* parent = nullptr); + ~TaskPostDataAlongLine() override; + + void applyPythonCode() override; + static void pointCallback(void* ud, SoEventCallback* n); + +private Q_SLOTS: + void on_SelectPoints_clicked(); + void on_CreatePlot_clicked(); + void on_Representation_activated(int i); + void on_Field_activated(int i); + void on_VectorMode_activated(int i); + void point2Changed(double); + void point1Changed(double); + void resolutionChanged(int val); + void onChange(double x1, double y1, double z1, double x2, double y2, double z2); + +private: + std::string Plot(); + std::string ObjectVisible(); + QWidget* proxy; + std::unique_ptr ui; +}; + + +// *************************************************************************** +// data at point filter +class TaskPostDataAtPoint: public TaskPostBox +{ + Q_OBJECT + +public: + explicit TaskPostDataAtPoint(Gui::ViewProviderDocumentObject* view, QWidget* parent = nullptr); + ~TaskPostDataAtPoint() override; + + void applyPythonCode() override; + static void pointCallback(void* ud, SoEventCallback* n); + +private Q_SLOTS: + void on_SelectPoint_clicked(); + void on_Field_activated(int i); + void centerChanged(double); + void onChange(double x, double y, double z); + +private: + std::string toString(double val) const; + void showValue(double value, const char* unit); + std::string ObjectVisible(); + QWidget* proxy; + std::unique_ptr ui; +}; + + +// *************************************************************************** +// clip filter +class TaskPostClip : public TaskPostBox +{ Q_OBJECT public: @@ -283,140 +363,8 @@ private: }; -class TaskPostDataAlongLine: public TaskPostBox { - - Q_OBJECT - -public: - explicit TaskPostDataAlongLine(Gui::ViewProviderDocumentObject* view, - QWidget* parent = nullptr); - ~TaskPostDataAlongLine() override; - - void applyPythonCode() override; - static void pointCallback(void * ud, SoEventCallback * n); - -private Q_SLOTS: - void on_SelectPoints_clicked(); - void on_CreatePlot_clicked(); - void on_Representation_activated(int i); - void on_Field_activated(int i); - void on_VectorMode_activated(int i); - void point2Changed(double); - void point1Changed(double); - void resolutionChanged(int val); - void onChange(double x1, double y1, double z1, double x2, double y2, double z2); - - -private: - std::string Plot(); - std::string ObjectVisible(); - QWidget* proxy; - std::unique_ptr ui; -}; - - -class TaskPostDataAtPoint: public TaskPostBox { - - Q_OBJECT - -public: - explicit TaskPostDataAtPoint(Gui::ViewProviderDocumentObject* view, QWidget* parent = nullptr); - ~TaskPostDataAtPoint() override; - - void applyPythonCode() override; - static void pointCallback(void * ud, SoEventCallback * n); - -private Q_SLOTS: - void on_SelectPoint_clicked(); - void on_Field_activated(int i); - void centerChanged(double); - void onChange(double x, double y, double z); - -private: - std::string toString(double val) const; - void showValue(double value, const char* unit); - - -private: - std::string ObjectVisible(); - QWidget* proxy; - std::unique_ptr ui; -}; - - -class TaskPostScalarClip : public TaskPostBox { - - Q_OBJECT - -public: - explicit TaskPostScalarClip(Gui::ViewProviderDocumentObject* view, QWidget* parent = nullptr); - ~TaskPostScalarClip() override; - - void applyPythonCode() override; - -private Q_SLOTS: - void on_Slider_valueChanged(int v); - void on_Value_valueChanged(double v); - void on_Scalar_currentIndexChanged(int idx); - void on_InsideOut_toggled(bool val); - -private: - QWidget* proxy; - std::unique_ptr ui; -}; - - -class TaskPostWarpVector : public TaskPostBox { - - Q_OBJECT - -public: - explicit TaskPostWarpVector(Gui::ViewProviderDocumentObject* view, QWidget* parent = nullptr); - ~TaskPostWarpVector() override; - - void applyPythonCode() override; - -private Q_SLOTS: - void on_Slider_valueChanged(int v); - void on_Value_valueChanged(double v); - void on_Max_valueChanged(double); - void on_Min_valueChanged(double); - void on_Vector_currentIndexChanged(int idx); - -private: - QWidget* proxy; - std::unique_ptr ui; -}; - - -class TaskPostCut : public TaskPostBox { - - Q_OBJECT - -public: - TaskPostCut(Gui::ViewProviderDocumentObject* view, App::PropertyLink* function, - QWidget* parent = nullptr); - ~TaskPostCut() override; - - void applyPythonCode() override; - -private Q_SLOTS: - void on_CreateButton_triggered(QAction*); - void on_FunctionBox_currentIndexChanged(int idx); - -Q_SIGNALS: - void emitAddedFunction(); - -private: - void collectImplicitFunctions(); - - //App::PropertyLink* m_functionProperty; - QWidget* proxy; - std::unique_ptr ui; - FunctionWidget* fwidget; -}; - - +// *************************************************************************** +// contours filter class TaskPostContours: public TaskPostBox { Q_OBJECT @@ -440,6 +388,85 @@ private: void updateFields(int idx); }; + +// *************************************************************************** +// cut filter +class TaskPostCut: public TaskPostBox +{ + Q_OBJECT + +public: + TaskPostCut(Gui::ViewProviderDocumentObject* view, App::PropertyLink* function, + QWidget* parent = nullptr); + ~TaskPostCut() override; + + void applyPythonCode() override; + +private Q_SLOTS: + void on_CreateButton_triggered(QAction*); + void on_FunctionBox_currentIndexChanged(int idx); + +Q_SIGNALS: + void emitAddedFunction(); + +private: + void collectImplicitFunctions(); + + //App::PropertyLink* m_functionProperty; + QWidget* proxy; + std::unique_ptr ui; + FunctionWidget* fwidget; +}; + + +// *************************************************************************** +// scalar clip filter +class TaskPostScalarClip : public TaskPostBox +{ + Q_OBJECT + +public: + explicit TaskPostScalarClip(Gui::ViewProviderDocumentObject* view, QWidget* parent = nullptr); + ~TaskPostScalarClip() override; + + void applyPythonCode() override; + +private Q_SLOTS: + void on_Slider_valueChanged(int v); + void on_Value_valueChanged(double v); + void on_Scalar_currentIndexChanged(int idx); + void on_InsideOut_toggled(bool val); + +private: + QWidget* proxy; + std::unique_ptr ui; +}; + + +// *************************************************************************** +// warp vector filter +class TaskPostWarpVector : public TaskPostBox +{ + Q_OBJECT + +public: + explicit TaskPostWarpVector(Gui::ViewProviderDocumentObject* view, QWidget* parent = nullptr); + ~TaskPostWarpVector() override; + + void applyPythonCode() override; + +private Q_SLOTS: + void on_Slider_valueChanged(int v); + void on_Value_valueChanged(double v); + void on_Max_valueChanged(double); + void on_Min_valueChanged(double); + void on_Vector_currentIndexChanged(int idx); + +private: + QWidget* proxy; + std::unique_ptr ui; +}; + } //namespace FemGui #endif // GUI_TASKVIEW_TaskPostDisplay_H diff --git a/src/Mod/Fem/Gui/ViewProviderFemPostFilter.cpp b/src/Mod/Fem/Gui/ViewProviderFemPostFilter.cpp index 9c954dd222..c991c80253 100644 --- a/src/Mod/Fem/Gui/ViewProviderFemPostFilter.cpp +++ b/src/Mod/Fem/Gui/ViewProviderFemPostFilter.cpp @@ -30,6 +30,63 @@ using namespace FemGui; +// *************************************************************************** +// in the following, the different filters sorted alphabetically +// *************************************************************************** + + +// *************************************************************************** +// data along line filter +PROPERTY_SOURCE(FemGui::ViewProviderFemPostDataAlongLine, FemGui::ViewProviderFemPostObject) + +ViewProviderFemPostDataAlongLine::ViewProviderFemPostDataAlongLine() +{ + sPixmap = "FEM_PostFilterDataAlongLine"; +} + +ViewProviderFemPostDataAlongLine::~ViewProviderFemPostDataAlongLine() +{} + +void ViewProviderFemPostDataAlongLine::setupTaskDialog(TaskDlgPost* dlg) +{ + //add the function box + dlg->appendBox(new TaskPostDataAlongLine(dlg->getView())); +} + + +// *************************************************************************** +// data at point filter +PROPERTY_SOURCE(FemGui::ViewProviderFemPostDataAtPoint, FemGui::ViewProviderFemPostObject) + +ViewProviderFemPostDataAtPoint::ViewProviderFemPostDataAtPoint() +{ + sPixmap = "FEM_PostFilterDataAtPoint"; +} + +void ViewProviderFemPostDataAtPoint::show() +{ + Gui::ViewProviderDocumentObject::show(); +} + +void ViewProviderFemPostDataAtPoint::onSelectionChanged(const Gui::SelectionChanges&) +{ + // do not do anything here + // For DataAtPoint the color bar must not be refreshed when it is selected + // because a single point does not make sense with a color range. +} + +ViewProviderFemPostDataAtPoint::~ViewProviderFemPostDataAtPoint() +{} + +void ViewProviderFemPostDataAtPoint::setupTaskDialog(TaskDlgPost* dlg) +{ + //add the function box + dlg->appendBox(new TaskPostDataAtPoint(dlg->getView())); +} + + +// *************************************************************************** +// clip filter PROPERTY_SOURCE(FemGui::ViewProviderFemPostClip, FemGui::ViewProviderFemPostObject) ViewProviderFemPostClip::ViewProviderFemPostClip() { @@ -37,9 +94,8 @@ ViewProviderFemPostClip::ViewProviderFemPostClip() { sPixmap = "FEM_PostFilterClipRegion"; } -ViewProviderFemPostClip::~ViewProviderFemPostClip() { - -} +ViewProviderFemPostClip::~ViewProviderFemPostClip() +{} void ViewProviderFemPostClip::setupTaskDialog(TaskDlgPost* dlg) { @@ -51,115 +107,9 @@ void ViewProviderFemPostClip::setupTaskDialog(TaskDlgPost* dlg) { FemGui::ViewProviderFemPostObject::setupTaskDialog(dlg); } -PROPERTY_SOURCE(FemGui::ViewProviderFemPostDataAlongLine, FemGui::ViewProviderFemPostObject) - -ViewProviderFemPostDataAlongLine::ViewProviderFemPostDataAlongLine() { - - sPixmap = "FEM_PostFilterDataAlongLine"; -} - -ViewProviderFemPostDataAlongLine::~ViewProviderFemPostDataAlongLine() { - -} - -void ViewProviderFemPostDataAlongLine::setupTaskDialog(TaskDlgPost* dlg) { - - //add the function box - dlg->appendBox(new TaskPostDataAlongLine(dlg->getView())); - -} - -PROPERTY_SOURCE(FemGui::ViewProviderFemPostDataAtPoint, FemGui::ViewProviderFemPostObject) - -ViewProviderFemPostDataAtPoint::ViewProviderFemPostDataAtPoint() { - - sPixmap = "FEM_PostFilterDataAtPoint"; -} - -void ViewProviderFemPostDataAtPoint::show() -{ - Gui::ViewProviderDocumentObject::show(); -} - -void ViewProviderFemPostDataAtPoint::onSelectionChanged(const Gui::SelectionChanges &) -{ - // do not do anything here - // For DataAtPoint the color bar must not be refreshed when it is selected - // because a single point does not make sense with a color range. -} - -ViewProviderFemPostDataAtPoint::~ViewProviderFemPostDataAtPoint() { - -} - -void ViewProviderFemPostDataAtPoint::setupTaskDialog(TaskDlgPost* dlg) { - - //add the function box - dlg->appendBox(new TaskPostDataAtPoint(dlg->getView())); - -} - -PROPERTY_SOURCE(FemGui::ViewProviderFemPostScalarClip, FemGui::ViewProviderFemPostObject) - -ViewProviderFemPostScalarClip::ViewProviderFemPostScalarClip() { - - sPixmap = "FEM_PostFilterClipScalar"; -} - -ViewProviderFemPostScalarClip::~ViewProviderFemPostScalarClip() { - -} - -void ViewProviderFemPostScalarClip::setupTaskDialog(TaskDlgPost* dlg) { - - //add the function box - dlg->appendBox(new TaskPostScalarClip(dlg->getView())); - - //add the display options - FemGui::ViewProviderFemPostObject::setupTaskDialog(dlg); -} - -PROPERTY_SOURCE(FemGui::ViewProviderFemPostWarpVector, FemGui::ViewProviderFemPostObject) - -ViewProviderFemPostWarpVector::ViewProviderFemPostWarpVector() { - - sPixmap = "FEM_PostFilterWarp"; -} - -ViewProviderFemPostWarpVector::~ViewProviderFemPostWarpVector() { - -} - -void ViewProviderFemPostWarpVector::setupTaskDialog(TaskDlgPost* dlg) { - - //add the function box - dlg->appendBox(new TaskPostWarpVector(dlg->getView())); - - //add the display options - FemGui::ViewProviderFemPostObject::setupTaskDialog(dlg); -} - -PROPERTY_SOURCE(FemGui::ViewProviderFemPostCut, FemGui::ViewProviderFemPostObject) - -ViewProviderFemPostCut::ViewProviderFemPostCut() { - - sPixmap = "FEM_PostFilterCutFunction"; -} - -ViewProviderFemPostCut::~ViewProviderFemPostCut() { - -} - -void ViewProviderFemPostCut::setupTaskDialog(TaskDlgPost* dlg) { - - //add the function box - dlg->appendBox(new TaskPostCut(dlg->getView(), - &static_cast(dlg->getView()->getObject())->Function)); - - //add the display options - FemGui::ViewProviderFemPostObject::setupTaskDialog(dlg); -} +// *************************************************************************** +// contours filter PROPERTY_SOURCE(FemGui::ViewProviderFemPostContours, FemGui::ViewProviderFemPostObject) ViewProviderFemPostContours::ViewProviderFemPostContours() @@ -175,3 +125,72 @@ void ViewProviderFemPostContours::setupTaskDialog(TaskDlgPost* dlg) // the filter-specific task panel dlg->appendBox(new TaskPostContours(dlg->getView())); } + + +// *************************************************************************** +// cut filter +PROPERTY_SOURCE(FemGui::ViewProviderFemPostCut, FemGui::ViewProviderFemPostObject) + +ViewProviderFemPostCut::ViewProviderFemPostCut() +{ + sPixmap = "FEM_PostFilterCutFunction"; +} + +ViewProviderFemPostCut::~ViewProviderFemPostCut() +{} + +void ViewProviderFemPostCut::setupTaskDialog(TaskDlgPost* dlg) +{ + //add the function box + dlg->appendBox(new TaskPostCut( + dlg->getView(), + &static_cast(dlg->getView()->getObject())->Function)); + + //add the display options + FemGui::ViewProviderFemPostObject::setupTaskDialog(dlg); +} + + +// *************************************************************************** +// scalar clip filter +PROPERTY_SOURCE(FemGui::ViewProviderFemPostScalarClip, FemGui::ViewProviderFemPostObject) + +ViewProviderFemPostScalarClip::ViewProviderFemPostScalarClip() +{ + sPixmap = "FEM_PostFilterClipScalar"; +} + +ViewProviderFemPostScalarClip::~ViewProviderFemPostScalarClip() { + +} + +void ViewProviderFemPostScalarClip::setupTaskDialog(TaskDlgPost* dlg) +{ + //add the function box + dlg->appendBox(new TaskPostScalarClip(dlg->getView())); + + //add the display options + FemGui::ViewProviderFemPostObject::setupTaskDialog(dlg); +} + + +// *************************************************************************** +// warp vector filter +PROPERTY_SOURCE(FemGui::ViewProviderFemPostWarpVector, FemGui::ViewProviderFemPostObject) + +ViewProviderFemPostWarpVector::ViewProviderFemPostWarpVector() +{ + sPixmap = "FEM_PostFilterWarp"; +} + +ViewProviderFemPostWarpVector::~ViewProviderFemPostWarpVector() +{} + +void ViewProviderFemPostWarpVector::setupTaskDialog(TaskDlgPost* dlg) +{ + //add the function box + dlg->appendBox(new TaskPostWarpVector(dlg->getView())); + + //add the display options + FemGui::ViewProviderFemPostObject::setupTaskDialog(dlg); +} diff --git a/src/Mod/Fem/Gui/ViewProviderFemPostFilter.h b/src/Mod/Fem/Gui/ViewProviderFemPostFilter.h index bb3b77c17d..33f326a965 100644 --- a/src/Mod/Fem/Gui/ViewProviderFemPostFilter.h +++ b/src/Mod/Fem/Gui/ViewProviderFemPostFilter.h @@ -20,30 +20,24 @@ * * ***************************************************************************/ - #ifndef FEM_VIEWPROVIDERFEMPOSTFILTER_H #define FEM_VIEWPROVIDERFEMPOSTFILTER_H #include "ViewProviderFemPostObject.h" + namespace FemGui { -class FemGuiExport ViewProviderFemPostClip : public ViewProviderFemPostObject { +// *************************************************************************** +// in the following, the different filters sorted alphabetically +// *************************************************************************** - PROPERTY_HEADER_WITH_OVERRIDE(FemGui::ViewProviderFemPostClip); - -public: - /// constructor. - ViewProviderFemPostClip(); - ~ViewProviderFemPostClip() override; - -protected: - void setupTaskDialog(TaskDlgPost* dlg) override; -}; - -class FemGuiExport ViewProviderFemPostDataAlongLine : public ViewProviderFemPostObject { +// *************************************************************************** +// data along line filter +class FemGuiExport ViewProviderFemPostDataAlongLine : public ViewProviderFemPostObject +{ PROPERTY_HEADER_WITH_OVERRIDE(FemGui::ViewProviderFemPostDataAlongLine); public: @@ -55,8 +49,11 @@ protected: void setupTaskDialog(TaskDlgPost* dlg) override; }; -class FemGuiExport ViewProviderFemPostDataAtPoint: public ViewProviderFemPostObject { +// *************************************************************************** +// data at point filter +class FemGuiExport ViewProviderFemPostDataAtPoint: public ViewProviderFemPostObject +{ PROPERTY_HEADER_WITH_OVERRIDE(FemGui::ViewProviderFemPostDataAtPoint); public: @@ -69,48 +66,28 @@ public: protected: void setupTaskDialog(TaskDlgPost* dlg) override; }; -class FemGuiExport ViewProviderFemPostScalarClip : public ViewProviderFemPostObject { - PROPERTY_HEADER_WITH_OVERRIDE(FemGui::ViewProviderFemPostScalarClip); + +// *************************************************************************** +// clip filter +class FemGuiExport ViewProviderFemPostClip: public ViewProviderFemPostObject +{ + PROPERTY_HEADER_WITH_OVERRIDE(FemGui::ViewProviderFemPostClip); public: /// constructor. - ViewProviderFemPostScalarClip(); - ~ViewProviderFemPostScalarClip() override; + ViewProviderFemPostClip(); + ~ViewProviderFemPostClip() override; protected: void setupTaskDialog(TaskDlgPost* dlg) override; }; -class FemGuiExport ViewProviderFemPostWarpVector : public ViewProviderFemPostObject { - - PROPERTY_HEADER_WITH_OVERRIDE(FemGui::ViewProviderFemPostWarpVector); - -public: - /// constructor. - ViewProviderFemPostWarpVector(); - ~ViewProviderFemPostWarpVector() override; - -protected: - void setupTaskDialog(TaskDlgPost* dlg) override; -}; - -class FemGuiExport ViewProviderFemPostCut : public ViewProviderFemPostObject { - - PROPERTY_HEADER_WITH_OVERRIDE(FemGui::ViewProviderFemPostCut); - -public: - /// constructor. - ViewProviderFemPostCut(); - ~ViewProviderFemPostCut() override; - -protected: - void setupTaskDialog(TaskDlgPost* dlg) override; -}; +// *************************************************************************** +// contours filter class FemGuiExport ViewProviderFemPostContours: public ViewProviderFemPostObject { - PROPERTY_HEADER_WITH_OVERRIDE(FemGui::ViewProviderFemPostContours); public: @@ -122,6 +99,54 @@ protected: void setupTaskDialog(TaskDlgPost* dlg) override; }; + +// *************************************************************************** +// cut filter +class FemGuiExport ViewProviderFemPostCut: public ViewProviderFemPostObject +{ + PROPERTY_HEADER_WITH_OVERRIDE(FemGui::ViewProviderFemPostCut); + +public: + /// constructor. + ViewProviderFemPostCut(); + ~ViewProviderFemPostCut() override; + +protected: + void setupTaskDialog(TaskDlgPost* dlg) override; +}; + + +// *************************************************************************** +// scalar clip filter +class FemGuiExport ViewProviderFemPostScalarClip: public ViewProviderFemPostObject +{ + PROPERTY_HEADER_WITH_OVERRIDE(FemGui::ViewProviderFemPostScalarClip); + +public: + /// constructor. + ViewProviderFemPostScalarClip(); + ~ViewProviderFemPostScalarClip() override; + +protected: + void setupTaskDialog(TaskDlgPost* dlg) override; +}; + + +// *************************************************************************** +// warp vector filter +class FemGuiExport ViewProviderFemPostWarpVector : public ViewProviderFemPostObject +{ + PROPERTY_HEADER_WITH_OVERRIDE(FemGui::ViewProviderFemPostWarpVector); + +public: + /// constructor. + ViewProviderFemPostWarpVector(); + ~ViewProviderFemPostWarpVector() override; + +protected: + void setupTaskDialog(TaskDlgPost* dlg) override; +}; + } //namespace FemGui From 598acfd88edbd599c43f10e2d5e6ef688995e11d Mon Sep 17 00:00:00 2001 From: edi271 Date: Sun, 12 Feb 2023 14:52:41 +0100 Subject: [PATCH 04/42] [TD] Icon file hole/shaft fit --- .../icons/actions/TechDraw_HoleShaftFit.svg | 323 ++++++++++++++++++ 1 file changed, 323 insertions(+) create mode 100644 src/Mod/TechDraw/Gui/Resources/icons/actions/TechDraw_HoleShaftFit.svg diff --git a/src/Mod/TechDraw/Gui/Resources/icons/actions/TechDraw_HoleShaftFit.svg b/src/Mod/TechDraw/Gui/Resources/icons/actions/TechDraw_HoleShaftFit.svg new file mode 100644 index 0000000000..11b6f7ac31 --- /dev/null +++ b/src/Mod/TechDraw/Gui/Resources/icons/actions/TechDraw_HoleShaftFit.svg @@ -0,0 +1,323 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + [WandererFan] + + + TechDraw_Dimension + 2016-04-27 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/TechDraw/Gui/Resources/icons/TechDraw_Dimension.svg + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + [agryson] Alexander Gryson + + + + + arrow + double arrow + diagonal + + + Double arrow at an angle between two diagonal lines + + + + + + + + + + + + + + + From a25233b526c062ac13de8be1b425260140711bd5 Mon Sep 17 00:00:00 2001 From: edi271 Date: Sun, 12 Feb 2023 14:54:34 +0100 Subject: [PATCH 05/42] [TD] ui file hole /shaft fit --- src/Mod/TechDraw/Gui/TaskHoleShaftFit.ui | 164 +++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 src/Mod/TechDraw/Gui/TaskHoleShaftFit.ui diff --git a/src/Mod/TechDraw/Gui/TaskHoleShaftFit.ui b/src/Mod/TechDraw/Gui/TaskHoleShaftFit.ui new file mode 100644 index 0000000000..67d83d6cf6 --- /dev/null +++ b/src/Mod/TechDraw/Gui/TaskHoleShaftFit.ui @@ -0,0 +1,164 @@ + + + TechDrawGui::TaskSurfaceFinishSymbols + + + true + + + + 0 + 0 + 274 + 162 + + + + + 0 + 0 + + + + + 250 + 0 + + + + Hole /Shaft Fit ISO 286 + + + + + + + + shaft fit + + + true + + + + + + + hole fit + + + + + + + + + + + true + + + + c11 + + + + + f7 + + + + + h6 + + + + + h7 + + + + + h9 + + + + + h9 + + + + + h9 + + + + + h6 + + + + + h6 + + + + + h6 + + + + + h6 + + + + + h6 + + + + + k6 + + + + + n6 + + + + + r6 + + + + + s6 + + + + + + + + loose fit + + + + + + + H11/ + + + + + + + + + + + + From 818255f416c7809e758e410efdc4a7a4dcff3a40 Mon Sep 17 00:00:00 2001 From: edi271 Date: Sun, 12 Feb 2023 14:56:06 +0100 Subject: [PATCH 06/42] [TD] Command source hole /shaft fit --- .../TechDrawTools/CommandHoleShaftFit.py | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 src/Mod/TechDraw/TechDrawTools/CommandHoleShaftFit.py diff --git a/src/Mod/TechDraw/TechDrawTools/CommandHoleShaftFit.py b/src/Mod/TechDraw/TechDrawTools/CommandHoleShaftFit.py new file mode 100644 index 0000000000..d4cedaf31b --- /dev/null +++ b/src/Mod/TechDraw/TechDrawTools/CommandHoleShaftFit.py @@ -0,0 +1,72 @@ +# *************************************************************************** +# * Copyright (c) 2023 edi * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program 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 program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +"""Provides the TechDraw HoleShaftFit GuiCommand.""" + +__title__ = "TechDrawTools.CommandHoleShaftFit" +__author__ = "edi" +__url__ = "https://www.freecadweb.org" +__version__ = "00.01" +__date__ = "2023/02/07" + +from PySide.QtCore import QT_TRANSLATE_NOOP + +import FreeCAD as App +import FreeCADGui as Gui + +import TechDrawTools + +class CommandHoleShaftFit: + """Adds a hole or shaft fit to a selected dimension.""" + + def __init__(self): + """Initialize variables for the command that must exist at all times.""" + pass + + def GetResources(self): + """Return a dictionary with data that will be used by the button or menu item.""" + return {'Pixmap': 'actions/TechDraw_HoleShaftFit.svg', + 'Accel': "", + 'MenuText': QT_TRANSLATE_NOOP("TechDraw_HoleShaftFit", "Add hole or shaft fit"), + 'ToolTip': QT_TRANSLATE_NOOP("TechDraw_HoleShaftFit", "Add a hole or shaft fit to a dimension
\ + - select one length dimension or diameter dimension
\ + - click the tool button, a panel openes
\ + - select shaft fit / hole fit
\ + - select the desired ISO 286 fit field using the combo box")} + + def Activated(self): + """Run the following code when the command is activated (button press).""" + sel = Gui.Selection.getSelectionEx() + #if sel and sel[0].Object.TypeId == 'TechDraw::DrawViewDimension': + if sel[0].Object.TypeId == 'TechDraw::DrawViewDimension': + self.ui = TechDrawTools.TaskHoleShaftFit(sel) + Gui.Control.showDialog(self.ui) + + def IsActive(self): + """Return True when the command should be active or False when it should be disabled (greyed).""" + if App.ActiveDocument: + return TechDrawTools.TDToolsUtil.havePage() and TechDrawTools.TDToolsUtil.haveView() + else: + return False + +# +# The command must be "registered" with a unique name by calling its class. +Gui.addCommand('TechDraw_HoleShaftFit', CommandHoleShaftFit()) + From ca910312bf976c78e67e21110166f18c480786cb Mon Sep 17 00:00:00 2001 From: edi271 Date: Sun, 12 Feb 2023 14:57:35 +0100 Subject: [PATCH 07/42] Combo View dialog source hole / shaft fit --- .../TechDrawTools/TaskHoleShaftFit.py | 183 ++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 src/Mod/TechDraw/TechDrawTools/TaskHoleShaftFit.py diff --git a/src/Mod/TechDraw/TechDrawTools/TaskHoleShaftFit.py b/src/Mod/TechDraw/TechDrawTools/TaskHoleShaftFit.py new file mode 100644 index 0000000000..352f2d6dfc --- /dev/null +++ b/src/Mod/TechDraw/TechDrawTools/TaskHoleShaftFit.py @@ -0,0 +1,183 @@ +# *************************************************************************** +# * Copyright (c) 2023 edi * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program 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 program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +"""Provides the TechDraw HoleShaftFit Task Dialog.""" + +__title__ = "TechDrawTools.TaskHoleShaftFit" +__author__ = "edi" +__url__ = "https://www.freecadweb.org" +__version__ = "00.01" +__date__ = "2023/02/07" + +from PySide.QtCore import QT_TRANSLATE_NOOP +from PySide import QtCore +import PySide.QtGui as QtGui + +import FreeCAD as App +import FreeCADGui as Gui + +from functools import partial + +import os + +class TaskHoleShaftFit: + def __init__(self,sel): + + self.isHole = True + self.sel = sel + self.holeValues = [["h9","D10","loose"],["h9","E9","loose"],["h9","F8","Loose"],["h6","G7","loose"], + ["c11","H11","loose"],["f7","H8","loose"],["h6","H7","loose"],["h7","H8","loose"], + ["k6","H7","snug"],["n6","H7","snug"],["r6","H7","press"],["s6","H7","press"], + ["h6","K7","snug"],["h6","N7","snug"],["h6","R7","press"],["h6","S7","press"]] + self.shaftValues = [["H11","c11","loose"],["H8","f7","loose"],["H7","h6","loose"],["H8","h7","loose"], + ["D10","h9","loose"],["E9","h9","loose"],["F8","h9","loose"],["G7","h6","loose"], + ["K7","h6","snug"],["N7","h6","snug"],["R7","h6","press"],["S7","h6","press"], + ["H7","k6","snug"],["H7","n6","snug"],["H7","r6","press"],["H7","s6","press"]] + + self._uiPath = App.getHomePath() + self._uiPath = os.path.join(self._uiPath, "Mod/TechDraw/TechDrawTools/Gui/TaskHoleShaftFit.ui") + self.form = Gui.PySideUic.loadUi(self._uiPath) + + self.form.setWindowTitle(QT_TRANSLATE_NOOP("TechDraw_HoleShaftFit", "Hole / Shaft Fit ISO 286")) + + self.form.rbHoleBase.clicked.connect(partial(self.on_HoleShaftChanged,True)) + self.form.rbShaftBase.clicked.connect(partial(self.on_HoleShaftChanged,False)) + self.form.cbField.currentIndexChanged.connect(self.on_FieldChanged) + + def setHoleFields(self): + '''set hole fields in the combo box''' + for i in range(self.form.cbField.count()): + self.form.cbField.removeItem(0) + for value in self.holeValues: + self.form.cbField.addItem(value[1]) + self.form.lbBaseField.setText(' '+self.holeValues[0][0]+" /") + self.form.lbFitType.setText(self.holeValues[0][2]+" fit") + + def setShaftFields(self): + '''set shaft fields in the combo box''' + for i in range(self.form.cbField.count()): + self.form.cbField.removeItem(0) + for value in self.shaftValues: + self.form.cbField.addItem(value[1]) + self.form.lbBaseField.setText(' '+self.shaftValues[0][0]+" /") + self.form.lbFitType.setText(self.shaftValues[0][2]+" fit") + + def on_HoleShaftChanged(self,isHole): + '''slot: change the used base fit hole/shaft''' + if isHole: + self.isHole = isHole + self.setShaftFields() + else: + self.isHole = isHole + self.setHoleFields() + + def on_FieldChanged(self): + '''slot: change of the desired field''' + currentIndex = self.form.cbField.currentIndex() + if self.isHole: + self.form.lbBaseField.setText(' '+self.shaftValues[currentIndex][0]+" /") + self.form.lbFitType.setText(self.shaftValues[currentIndex][2]+" fit") + else: + self.form.lbBaseField.setText(' '+self.holeValues[currentIndex][0]+" /") + self.form.lbFitType.setText(self.holeValues[currentIndex][2]+" fit") + + def accept(self): + '''slot: OK pressed''' + currentIndex = self.form.cbField.currentIndex() + if self.isHole: + selectedField = self.shaftValues[currentIndex][1] + else: + selectedField = self.holeValues[currentIndex][1] + fieldChar = selectedField[0] + quality = int(selectedField[1:]) + dim = self.sel[0].Object + value = dim.getRawValue() + iso = ISO286() + iso.calculate(value,fieldChar,quality) + rangeValues = iso.getValues() + mainFormat = dim.FormatSpec + dim.FormatSpec = mainFormat+selectedField + dim.EqualTolerance = False + dim.FormatSpecOverTolerance = '(%+.3f)' + dim.OverTolerance = rangeValues[0] + dim.UnderTolerance = rangeValues[1] + Gui.Control.closeDialog() + + def reject(self): + return True + +class ISO286: + '''This class represents a subset of the ISO 286 standard''' + + def getNominalRange(self,measureValue): + '''return index of selected nominal range field, 0 < measureValue < 500 mm''' + measureRanges = [0,3,6,10,14,18,24,30,40,50,65,80,100,120,140,160,180,200,225,250,280,315,355,400,450,500] + index = 1 + while measureValue > measureRanges[index]: + index = index+1 + return index-1 + + def getITValue(self,valueQuality,valueNominalRange): + '''return IT-value (value of quality in micrometers)''' + '''tables IT6 to IT11 from 0 to 500 mm''' + IT6 = [6,8,9,11,11,13,13,16,16,19,19,22,22,25,25,25,29,29,29,32,32,36,36,40,40] + IT7 = [10,12,15,18,18,21,21,25,25,30,30,35,35,40,40,40,46,46,46,52,52,57,57,63,63] + IT8 = [14,18,22,27,27,33,33,39,39,46,46,54,54,63,63,63,72,72,72,81,81,89,89,97,97] + IT9 = [25,30,36,43,43,52,52,62,62,74,74,87,87,100,100,100,115,115,115,130,130,140,140,155,155] + IT10 = [40,48,58,70,70,84,84,100,100,120,120,140,140,160,160,160,185,185,185,210,210,230,230,250,250] + IT11 = [60,75,90,110,110,130,130,160,160,190,190,220,220,250,250,250,290,290,290,320,320,360,360,400,400] + qualityTable = [IT6,IT7,IT8,IT9,IT10,IT11] + return qualityTable[valueQuality-6][valueNominalRange] + + def getFieldValue(self,fieldCharacter,valueNominalRange): + '''return es or ES value of the field in micrometers''' + cField = [-60,-70,-80,-95,-95,-110,-110,-120,-130,-140,-150,-170,-180,-200,-210,-230,-240,-260,-280,-300,-330,-360,-400,-440,-480] + fField = [-6,-10,-13,-16,-16,-20,-20,-25,-25,-30,-30,-36,-36,-43,-43,-43,-50,-50,-50,-56,-56,-62,-62,-68,-68] + gField = [-2,-4,-5,-6,-6,-7,-7,-9,-9,-10,-10,-12,-12,-14,-14,-14,-15,-15,-15,-17,-17,-18,-18,-20,-20] + hField = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] + kField = [6,9,10,12,12,15,15,18,18,21,21,25,25,28,28,28,33,33,33,36,36,40,40,45,45] + nField = [10,16,19,23,23,28,28,33,33,39,39,45,45,52,52,60,60,66,66,73,73,80,80] + rField = [16,23,28,34,34,41,41,50,50,60,62,73,76,88,90,93,106,109,113,126,130,144,150,166,172] + sField = [20,27,32,39,39,48,48,59,59,72,78,93,101,117,125,133,151,159,169,190,202,226,244,272,292] + DField = [60,78,98,120,120,149,149,180,180,220,220,260,260,305,305,305,355,355,355,400,400,440,440,480,480] + EField = [39,50,61,75,75,92,92,112,112,134,134,159,159,185,185,185,215,215,215,240,240,265,265,290,290] + FField = [20,28,35,43,43,53,53,64,64,76,76,90,90,106,106,106,122,122,122,137,137,151,151,165,165] + GField = [12,16,20,24,24,28,28,34,34,40,40,47,47,54,54,54,61,61,61,69,69,75,75,83,83] + HField = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] + KField = [0,3,5,6,6,6,6,7,7,9,9,10,10,12,12,12,13,13,13,16,16,17,17,18,18] + NField = [-4,-4,-4,-5,-5,-7,-7,-8,-8,-9,-9,-10,-10,-12,-12,-12,-14,-14,-14,-14,-14,-16,-16,-17,-17] + RField = [-10,-11,-13,-16,-16,-20,-20,-25,-25,-30,-32,-38,-41,-48,-50,-53,-60,-63,-67,-74,-78,-87,-93,-103,-109] + SField = [-14,-15,-17,-21,-21,-27,-27,-34,-34,-42,-48,-58,-66,-77,-85,-93,-105,-113,-123,-138,-150,-169,-187,-209,-229] + fieldDict = {'c':cField,'f':fField,'g':gField,'h':hField,'k':kField,'n':nField,'r':rField,'s':sField, + 'D':DField,'E':EField,'F':FField,'G':GField,'H':HField,'K':KField,'N':NField,'R':RField,'S':SField} + return fieldDict[fieldCharacter][valueNominalRange] + + def calculate(self,value,fieldChar,quality): + '''calculate upper and lower field values''' + self.nominalRange = self. getNominalRange(value) + self.upperValue = self.getFieldValue(fieldChar,self.nominalRange) + self.lowerValue = self.upperValue-self.getITValue(quality,self.nominalRange) + if fieldChar == 'H': + self.upperValue = -self.lowerValue + self.lowerValue = 0 + + def getValues(self): + '''return range values in mm''' + return (self.upperValue/1000,self.lowerValue/1000) From cfc5d0bb53f165ce1ea33aa4e610d7e51931cdb1 Mon Sep 17 00:00:00 2001 From: edi271 Date: Sun, 12 Feb 2023 14:59:18 +0100 Subject: [PATCH 08/42] Changed files --- src/Mod/TechDraw/CMakeLists.txt | 3 +++ src/Mod/TechDraw/Gui/Resources/TechDraw.qrc | 1 + src/Mod/TechDraw/Gui/Workbench.cpp | 3 +++ src/Mod/TechDraw/TechDrawTools/__init__.py | 2 ++ 4 files changed, 9 insertions(+) diff --git a/src/Mod/TechDraw/CMakeLists.txt b/src/Mod/TechDraw/CMakeLists.txt index 1678c83be8..b4a796218b 100644 --- a/src/Mod/TechDraw/CMakeLists.txt +++ b/src/Mod/TechDraw/CMakeLists.txt @@ -21,14 +21,17 @@ set(TechDraw_ToolsScripts TechDrawTools/CommandMoveView.py TechDrawTools/CommandShareView.py TechDrawTools/CommandAxoLengthDimension.py + TechDrawTools/CommandHoleShaftFit.py TechDrawTools/TaskMoveView.py TechDrawTools/TaskShareView.py + TechDrawTools/TaskHoleShaftFit.py TechDrawTools/TDToolsUtil.py TechDrawTools/TDToolsMovers.py ) set(TechDraw_ToolsGui Gui/TaskMoveView.ui + Gui/TaskHoleShaftFit.ui Gui/DlgPageChooser.ui ) diff --git a/src/Mod/TechDraw/Gui/Resources/TechDraw.qrc b/src/Mod/TechDraw/Gui/Resources/TechDraw.qrc index b142946ba5..aa405f1538 100644 --- a/src/Mod/TechDraw/Gui/Resources/TechDraw.qrc +++ b/src/Mod/TechDraw/Gui/Resources/TechDraw.qrc @@ -29,6 +29,7 @@ icons/actions/TechDraw_Midpoints.svg icons/actions/TechDraw_MoveView.svg icons/actions/TechDraw_AxoLengthDimension.svg + icons/actions/TechDraw_HoleShaftFit.svg icons/actions/TechDraw_Multiview.svg icons/actions/TechDraw_PageDefault.svg icons/actions/TechDraw_PageTemplate.svg diff --git a/src/Mod/TechDraw/Gui/Workbench.cpp b/src/Mod/TechDraw/Gui/Workbench.cpp index 9ba7b30a03..00559b6e0d 100644 --- a/src/Mod/TechDraw/Gui/Workbench.cpp +++ b/src/Mod/TechDraw/Gui/Workbench.cpp @@ -226,6 +226,7 @@ Gui::MenuItem* Workbench::setupMenuBar() const *draw << "TechDraw_ShowAll"; *draw << "TechDraw_WeldSymbol"; *draw << "TechDraw_SurfaceFinishSymbols"; + *draw << "TechDraw_HoleShaftFit"; *draw << "Separator"; *draw << "TechDraw_ProjectShape"; return root; @@ -376,6 +377,7 @@ Gui::ToolBarItem* Workbench::setupToolBars() const *anno << "TechDraw_ShowAll"; *anno << "TechDraw_WeldSymbol"; *anno << "TechDraw_SurfaceFinishSymbols"; + *anno << "TechDraw_HoleShaftFit"; return root; } @@ -525,6 +527,7 @@ Gui::ToolBarItem* Workbench::setupCommandBars() const *anno << "TechDraw_ShowAll"; *anno << "TechDraw_WeldSymbol"; *anno << "TechDraw_SurfaceFinishSymbols"; + *anno << "TechDraw_HoleShaftFit"; return root; } diff --git a/src/Mod/TechDraw/TechDrawTools/__init__.py b/src/Mod/TechDraw/TechDrawTools/__init__.py index 37cabe6849..dd21abe35b 100644 --- a/src/Mod/TechDraw/TechDrawTools/__init__.py +++ b/src/Mod/TechDraw/TechDrawTools/__init__.py @@ -34,5 +34,7 @@ from .TDToolsUtil import * from .CommandShareView import CommandShareView from .CommandMoveView import CommandMoveView from .CommandAxoLengthDimension import CommandAxoLengthDimension +from .CommandHoleShaftFit import CommandHoleShaftFit from .TaskShareView import TaskShareView from .TaskMoveView import TaskMoveView +from .TaskHoleShaftFit import TaskHoleShaftFit From bf108be32147041a61d9ee964187832aa71f86fd Mon Sep 17 00:00:00 2001 From: edi271 Date: Mon, 13 Feb 2023 17:56:30 +0100 Subject: [PATCH 09/42] [TD] solve reported issues --- .../TechDrawTools/CommandHoleShaftFit.py | 7 +----- .../TechDrawTools/TaskHoleShaftFit.py | 23 ++++++++++--------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/Mod/TechDraw/TechDrawTools/CommandHoleShaftFit.py b/src/Mod/TechDraw/TechDrawTools/CommandHoleShaftFit.py index d4cedaf31b..94b027ae12 100644 --- a/src/Mod/TechDraw/TechDrawTools/CommandHoleShaftFit.py +++ b/src/Mod/TechDraw/TechDrawTools/CommandHoleShaftFit.py @@ -36,10 +36,6 @@ import TechDrawTools class CommandHoleShaftFit: """Adds a hole or shaft fit to a selected dimension.""" - def __init__(self): - """Initialize variables for the command that must exist at all times.""" - pass - def GetResources(self): """Return a dictionary with data that will be used by the button or menu item.""" return {'Pixmap': 'actions/TechDraw_HoleShaftFit.svg', @@ -68,5 +64,4 @@ class CommandHoleShaftFit: # # The command must be "registered" with a unique name by calling its class. -Gui.addCommand('TechDraw_HoleShaftFit', CommandHoleShaftFit()) - +Gui.addCommand('TechDraw_HoleShaftFit', CommandHoleShaftFit()) \ No newline at end of file diff --git a/src/Mod/TechDraw/TechDrawTools/TaskHoleShaftFit.py b/src/Mod/TechDraw/TechDrawTools/TaskHoleShaftFit.py index 352f2d6dfc..609956b997 100644 --- a/src/Mod/TechDraw/TechDrawTools/TaskHoleShaftFit.py +++ b/src/Mod/TechDraw/TechDrawTools/TaskHoleShaftFit.py @@ -27,8 +27,6 @@ __version__ = "00.01" __date__ = "2023/02/07" from PySide.QtCore import QT_TRANSLATE_NOOP -from PySide import QtCore -import PySide.QtGui as QtGui import FreeCAD as App import FreeCADGui as Gui @@ -40,16 +38,19 @@ import os class TaskHoleShaftFit: def __init__(self,sel): + loose = QT_TRANSLATE_NOOP("TechDraw_HoleShaftFit", "loose") + snug = QT_TRANSLATE_NOOP("TechDraw_HoleShaftFit", "snug") + press = QT_TRANSLATE_NOOP("TechDraw_HoleShaftFit", "press") self.isHole = True self.sel = sel - self.holeValues = [["h9","D10","loose"],["h9","E9","loose"],["h9","F8","Loose"],["h6","G7","loose"], - ["c11","H11","loose"],["f7","H8","loose"],["h6","H7","loose"],["h7","H8","loose"], - ["k6","H7","snug"],["n6","H7","snug"],["r6","H7","press"],["s6","H7","press"], - ["h6","K7","snug"],["h6","N7","snug"],["h6","R7","press"],["h6","S7","press"]] - self.shaftValues = [["H11","c11","loose"],["H8","f7","loose"],["H7","h6","loose"],["H8","h7","loose"], - ["D10","h9","loose"],["E9","h9","loose"],["F8","h9","loose"],["G7","h6","loose"], - ["K7","h6","snug"],["N7","h6","snug"],["R7","h6","press"],["S7","h6","press"], - ["H7","k6","snug"],["H7","n6","snug"],["H7","r6","press"],["H7","s6","press"]] + self.holeValues = [["h9","D10",loose],["h9","E9",loose],["h9","F8",loose],["h6","G7",loose], + ["c11","H11",loose],["f7","H8",loose],["h6","H7",loose],["h7","H8",loose], + ["k6","H7",snug],["n6","H7",snug],["r6","H7",press],["s6","H7",press], + ["h6","K7",snug],["h6","N7",snug],["h6","R7",press],["h6","S7",press]] + self.shaftValues = [["H11","c11",loose],["H8","f7",loose],["H7","h6",loose],["H8","h7",loose], + ["D10","h9",loose],["E9","h9",loose],["F8","h9",loose],["G7","h6",loose], + ["K7","h6",snug],["N7","h6",snug],["R7","h6",press],["S7","h6",press], + ["H7","k6",snug],["H7","n6",snug],["H7","r6",press],["H7","s6",press]] self._uiPath = App.getHomePath() self._uiPath = os.path.join(self._uiPath, "Mod/TechDraw/TechDrawTools/Gui/TaskHoleShaftFit.ui") @@ -180,4 +181,4 @@ class ISO286: def getValues(self): '''return range values in mm''' - return (self.upperValue/1000,self.lowerValue/1000) + return (self.upperValue/1000,self.lowerValue/1000) \ No newline at end of file From 5e0a8dd4acfb8885a09a88c43275947390d66e8c Mon Sep 17 00:00:00 2001 From: Roy-043 <70520633+Roy-043@users.noreply.github.com> Date: Mon, 13 Feb 2023 21:11:52 +0100 Subject: [PATCH 10/42] [Arch] fix Arch_Stairs recompute issue (#8444) * [Arch] fix Arch_Stairs recompute issue * Fixed the tooltips for the updated properties. * Added warnings to the functions that updated the properties. * Fixed a typo makeCurvedStairsWithLandings -> makeCurvedStairsWithLanding --- src/Mod/Arch/ArchStairs.py | 142 ++++++++++++++++++++++++------------- 1 file changed, 92 insertions(+), 50 deletions(-) diff --git a/src/Mod/Arch/ArchStairs.py b/src/Mod/Arch/ArchStairs.py index 4db3b35b7b..12965187ba 100644 --- a/src/Mod/Arch/ArchStairs.py +++ b/src/Mod/Arch/ArchStairs.py @@ -161,28 +161,18 @@ def makeRailing(stairs): if side == "L": outlineLR = stair.OutlineLeft outlineLRAll = stair.OutlineLeftAll - stairs0RailingLR = "RailingLeft" # stairs0OutlineWireLR = "OutlineWireLeft" - stairRailingLR = "RailingLeft" # stairOutlineWireLR = "OutlineWireLeft" + stairRailingLR = "RailingLeft" elif side == "R": outlineLR = stair.OutlineRight outlineLRAll = stair.OutlineRightAll - stairs0RailingLR = "RailingRight" # stairs0OutlineWireLR = "OutlineWireRight" - stairRailingLR = "RailingRight" # stairOutlineWireLR = "OutlineWireRight" + stairRailingLR = "RailingRight" if outlineLR or outlineLRAll: lrRail = ArchPipe.makePipe(baseobj=None,diameter=0,length=0,placement=None,name="Rail") if outlineLRAll: - #lrRail.Base = lrRailWire # no need to set here as _Stairs will do - setattr(stair, stairRailingLR, lrRail.Name) # setattr(stair, stairOutlineWireLR, lrRailWire.Name) - railList = stairs[0].Additions - railList.append(lrRail) - stairs[0].Additions = railList + setattr(stair, stairRailingLR, lrRail) break elif outlineLR: - #lrRail.Base = lrRailWire # no need to set here as _Stairs will do - setattr(stair, stairRailingLR, lrRail.Name) # setattr(stair, stairOutlineWireLR, lrRailWire.Name) - railList = stair.Additions - railList.append(lrRail) - stair.Additions = railList + setattr(stair, stairRailingLR, lrRail) if stairs is None: sel = FreeCADGui.Selection.getSelection() @@ -342,25 +332,9 @@ class _Stairs(ArchComponent.Component): self.OutlineRailArcRight = [] if not hasattr(obj,"RailingLeft"): - obj.addProperty("App::PropertyString","RailingLeft","Segment and Parts","Name of Railing object (left) created") - # Migration - if hasattr(obj,"OutlineWireLeft"): - outlineWireLeftObject = FreeCAD.ActiveDocument.getObject(obj.OutlineWireLeft) - try: - obj.RailingLeft = outlineWireLeftObject.InList[0].Name - obj.removeProperty("OutlineWireLeft") - except Exception: - pass + obj.addProperty("App::PropertyLinkHidden","RailingLeft","Segment and Parts","Name of Railing object (left) created") if not hasattr(obj,"RailingRight"): - obj.addProperty("App::PropertyString","RailingRight","Segment and Parts","Name of Railing object (right) created") - # Migration - if hasattr(obj,"OutlineWireRight"): - outlineWireRightObject = FreeCAD.ActiveDocument.getObject(obj.OutlineWireRight) - try: - obj.RailingRight = outlineWireRightObject.InList[0].Name - obj.removeProperty("OutlineWireRight") - except Exception: - pass + obj.addProperty("App::PropertyLinkHidden","RailingRight","Segment and Parts","Name of Railing object (right) created") if not hasattr(obj,"OutlineLeftAll"): obj.addProperty("App::PropertyVectorList","OutlineLeftAll","Segment and Parts",QT_TRANSLATE_NOOP("App::Property","The 'left outline' of all segments of stairs")) @@ -426,6 +400,59 @@ class _Stairs(ArchComponent.Component): ArchComponent.Component.onDocumentRestored(self,obj) self.setProperties(obj) + if hasattr(obj,"OutlineWireLeft"): + self.update_properties_0v18_to_0v20(obj) + + if obj.getTypeIdOfProperty("RailingLeft") == "App::PropertyString": + self.update_properties_0v19_to_0v20(obj) + + def update_properties_0v18_to_0v20(self, obj): + doc = FreeCAD.ActiveDocument + outlineWireLeftObject = doc.getObject(obj.OutlineWireLeft) + outlineWireRightObject = doc.getObject(obj.OutlineWireRight) + try: + obj.RailingLeft = outlineWireLeftObject.InList[0] + except Exception: + pass + try: + obj.RailingRight = outlineWireRightObject.InList[0] + except Exception: + pass + obj.removeProperty("OutlineWireLeft") + obj.removeProperty("OutlineWireRight") + self.update_properties_to_0v20(obj) + doc.recompute() + from draftutils.messages import _wrn + _wrn("v0.20.3, " + obj.Label + ", " + + translate("Arch", "removed properties 'OutlineWireLeft' and 'OutlineWireRight', and added properties 'RailingLeft' and 'RailingRight'")) + + def update_properties_0v19_to_0v20(self, obj): + doc = FreeCAD.ActiveDocument + railingLeftObject = doc.getObject(obj.RailingLeft) + railingRightObject = doc.getObject(obj.RailingRight) + obj.removeProperty("RailingLeft") + obj.removeProperty("RailingRight") + self.setProperties(obj) + obj.RailingLeft = railingLeftObject + obj.RailingRight = railingRightObject + self.update_properties_to_0v20(obj) + doc.recompute() + from draftutils.messages import _wrn + _wrn("v0.20.3, " + obj.Label + ", " + + translate("Arch", "changed the type of properties 'RailingLeft' and 'RailingRight'")) + + def update_properties_to_0v20(self, obj): + additions = obj.Additions + if obj.RailingLeft in additions: + additions.remove(obj.RailingLeft) + if obj.RailingRight in additions: + additions.remove(obj.RailingRight) + obj.Additions = additions + if obj.RailingLeft is not None: + obj.RailingLeft.Visibility = True + if obj.RailingRight is not None: + obj.RailingRight.Visibility = True + def execute(self,obj): "constructs the shape of the stairs" @@ -481,7 +508,7 @@ class _Stairs(ArchComponent.Component): else: if obj.Landings == "At center": landings = 1 - self.makeCurvedStairsWithLandings(obj,edge) + self.makeCurvedStairsWithLanding(obj,edge) else: self.makeCurvedStairs(obj,edge) @@ -515,23 +542,22 @@ class _Stairs(ArchComponent.Component): railingLeftObject, railWireL = None, None railingRightObject, railWireR = None, None + doc = FreeCAD.ActiveDocument if obj.RailingLeft: - railingLeftObject = FreeCAD.ActiveDocument.getObject(obj.RailingLeft) - if railingLeftObject: # TODO - need to update if railing is deleted by user? This become None if deleted. + railingLeftObject = obj.RailingLeft if obj.OutlineLeftAll: - railWireL, NU = _Stairs.returnOutlineWireFace(obj.OutlineLeftAll, self.OutlineRailArcLeftAll, mode = "notFaceAlso") #(outlinePoints, pArc, mode="wire or faceAlso") + railWireL, NU = _Stairs.returnOutlineWireFace(obj.OutlineLeftAll, self.OutlineRailArcLeftAll, mode = "notFaceAlso") elif obj.OutlineLeft: railWireL, NU = _Stairs.returnOutlineWireFace(obj.OutlineLeft, self.OutlineRailArcLeft, mode = "notFaceAlso") else: print (" No obj.OutlineLeftAll or obj.OutlineLeft") if railWireL: - # Migration - if Draft.getType(railingLeftObject.Base) != "Part": # None or not "Part" - railingLeftWireObject = FreeCAD.ActiveDocument.addObject("Part::Feature","RailingWire") - if railingLeftObject.Base: # if has railingLeftObject.Base but that != "Part" - railingLeftObject.Document.removeObject(railingLeftObject.Base.Name) # Delete the previous Base object... # Not Using FreeCAD.ActiveDocument... + if Draft.getType(railingLeftObject.Base) != "Part::Feature": # Base can have wrong type or be None. + if railingLeftObject.Base: + doc.removeObject(railingLeftObject.Base.Name) + railingLeftWireObject = doc.addObject("Part::Feature","RailingWire") railingLeftObject.Base = railingLeftWireObject # update the Base object shape railingLeftObject.Base.Shape = railWireL @@ -539,23 +565,19 @@ class _Stairs(ArchComponent.Component): print (" No railWireL created ") if obj.RailingRight: - railingRightObject = FreeCAD.ActiveDocument.getObject(obj.RailingRight) - if railingRightObject: # TODO - need to update if railing is deleted by user? This become None if deleted. + railingRightObject = obj.RailingRight if obj.OutlineRightAll: - print (" DEBUG - has obj.OutlineRightAll ") - railWireR, NU = _Stairs.returnOutlineWireFace(obj.OutlineRightAll, self.OutlineRailArcRightAll, mode = "notFaceAlso") #(outlinePoints, pArc, mode="wire or faceAlso") + railWireR, NU = _Stairs.returnOutlineWireFace(obj.OutlineRightAll, self.OutlineRailArcRightAll, mode = "notFaceAlso") elif obj.OutlineLeft: - print (" DEBUG - has obj.OutlineLeft ") railWireR, NU = _Stairs.returnOutlineWireFace(obj.OutlineLeft, self.OutlineRailArcRight, mode = "notFaceAlso") else: print (" No obj.OutlineRightAll or obj.OutlineLeft") if railWireR: - # Migration - if Draft.getType(railingRightObject.Base) != "Part": - railingRightWireObject = FreeCAD.ActiveDocument.addObject("Part::Feature","RailingWire") + if Draft.getType(railingRightObject.Base) != "Part::Feature": # Base can have wrong type or be None. if railingRightObject.Base: - railingRightObject.Document.removeObject(railingRightObject.Base.Name) + doc.removeObject(railingRightObject.Base.Name) + railingRightWireObject = doc.addObject("Part::Feature","RailingWire") railingRightObject.Base = railingRightWireObject # update the Base object shape railingRightObject.Base.Shape = railWireR @@ -1475,6 +1497,26 @@ class _ViewProviderStairs(ArchComponent.ViewProviderComponent): import Arch_rc return ":/icons/Arch_Stairs_Tree.svg" + def claimChildren(self): + + "Define which objects will appear as children in the tree view" + + if hasattr(self, "Object"): + obj = self.Object + lst = [] + if hasattr(obj, "Base"): + lst.append(obj.Base) + if hasattr(obj, "RailingLeft"): + lst.append(obj.RailingLeft) + if hasattr(obj, "RailingRight"): + lst.append(obj.RailingRight) + if hasattr(obj, "Additions"): + lst.extend(obj.Additions) + if hasattr(obj, "Subtractions"): + lst.extend(obj.Subtractions) + return lst + return [] + if FreeCAD.GuiUp: FreeCADGui.addCommand('Arch_Stairs',_CommandStairs()) From 68da9cd64a48f52b8bdb199c5ca9c65f9ad61a9b Mon Sep 17 00:00:00 2001 From: VM4Dim Date: Sun, 12 Feb 2023 06:12:48 +0300 Subject: [PATCH 11/42] [Gui] Sort Add Property Types --- src/Gui/DlgAddProperty.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Gui/DlgAddProperty.cpp b/src/Gui/DlgAddProperty.cpp index 2fe4d34065..40ab59df9d 100644 --- a/src/Gui/DlgAddProperty.cpp +++ b/src/Gui/DlgAddProperty.cpp @@ -56,6 +56,8 @@ DlgAddProperty::DlgAddProperty(QWidget* parent, std::vector types; Base::Type::getAllDerivedFrom(Base::Type::fromName("App::Property"),types); + std::sort(types.begin(), types.end(), [](Base::Type a, Base::Type b) { return strcmp(a.getName(), b.getName()) < 0; }); + for(const auto& type : types) { ui->comboType->addItem(QString::fromLatin1(type.getName())); if(type == defType) From 2603cd6b16a46d6683858b9dab2a97fa7be7a9ad Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sun, 12 Feb 2023 19:40:50 -0600 Subject: [PATCH 12/42] Test(Metadata): Remove catch from Setup() If an exception occurs while setting up Xerces-C, make the test framework handle it. Also add a copyright header, and try to quiet down the clang-tidy linter. --- tests/src/App/Metadata.cpp | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/tests/src/App/Metadata.cpp b/tests/src/App/Metadata.cpp index 59fe73923d..97389d3cb1 100644 --- a/tests/src/App/Metadata.cpp +++ b/tests/src/App/Metadata.cpp @@ -1,7 +1,32 @@ +/************************************************************************** +* * +* Copyright (c) 2021-2023 FreeCAD Project Association * +* * +* 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 * +* . * +* * +***************************************************************************/ + +// NOLINTNEXTLINE #include "gtest/gtest.h" #include "App/Metadata.h" +// NOLINTBEGIN(readability-named-parameter) + TEST(ContactTest, ContactDefaultConstruction){ auto contact = App::Meta::Contact(); ASSERT_EQ(contact.name,""); @@ -128,6 +153,7 @@ TEST(VersionTest, VersionOperatorComparison){ auto version_2_2_4_delta = App::Meta::Version(2,2,4,"delta"); auto version_2_4_4_delta = App::Meta::Version(2,4,4,"delta"); auto version_2_3_3_delta = App::Meta::Version(2,3,3,"delta"); + // NOLINTNEXTLINE Five is not really a "magic number" in this test auto version_2_3_5_delta = App::Meta::Version(2,3,5,"delta"); auto version_2_3_4_epsilon = App::Meta::Version(2,3,4,"epsilon"); auto version_2_3_4_beta = App::Meta::Version(2,3,4,"beta"); @@ -147,19 +173,14 @@ TEST(VersionTest, VersionOperatorComparison){ class MetadataTest : public ::testing::Test { protected: void SetUp() override { - try { - xercesc_3_2::XMLPlatformUtils::Initialize(); - } - catch (const xercesc_3_2::XMLException& toCatch) { - // Some kind of Google Test error condition? - } + xercesc_3_2::XMLPlatformUtils::Initialize(); } void TearDown() override { xercesc_3_2::XMLPlatformUtils::Terminate(); } std::string GivenSimpleMetadataXMLString() { - std::stringstream stream; + std::ostringstream stream; stream << "\n" << "\n" << " " << _name << "\n" @@ -176,7 +197,7 @@ protected: ASSERT_EQ(testObject.description(), _description); ASSERT_EQ(testObject.version(), App::Meta::Version(_version)); } - +private: std::string _name = "TestAddon"; std::string _description = "A package.xml file for unit testing."; std::string _version = "1.2.3beta"; @@ -188,3 +209,4 @@ TEST_F(MetadataTest, MetadataInMemoryConstruction) { AssertMetadataMatches(testObject); } +// NOLINTEND(readability-named-parameter) From f83abbab4c24cc0b80dc29c8ff4ba23d9b2dcb20 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sat, 11 Feb 2023 13:43:52 -0800 Subject: [PATCH 13/42] Addon Manager: Refactor Macro parser --- .../AddonManagerTest/app/mocks.py | 76 +++- .../AddonManagerTest/app/test_addon.py | 4 +- .../AddonManagerTest/app/test_macro_parser.py | 347 ++++++++++++++++++ .../AddonManagerTest/data/DoNothing.FCMacro | 15 +- src/Mod/AddonManager/CMakeLists.txt | 2 + src/Mod/AddonManager/addonmanager_macro.py | 146 +------- .../AddonManager/addonmanager_macro_parser.py | 249 +++++++++++++ 7 files changed, 683 insertions(+), 156 deletions(-) create mode 100644 src/Mod/AddonManager/AddonManagerTest/app/test_macro_parser.py create mode 100644 src/Mod/AddonManager/addonmanager_macro_parser.py diff --git a/src/Mod/AddonManager/AddonManagerTest/app/mocks.py b/src/Mod/AddonManager/AddonManagerTest/app/mocks.py index f15ee8c81a..49550229ee 100644 --- a/src/Mod/AddonManager/AddonManagerTest/app/mocks.py +++ b/src/Mod/AddonManager/AddonManagerTest/app/mocks.py @@ -22,17 +22,20 @@ """Mock objects for use when testing the addon manager non-GUI code.""" +# pylint: disable=too-few-public-methods,too-many-instance-attributes,missing-function-docstring + import os from typing import Union, List import xml.etree.ElementTree as ElemTree -class GitFailed (RuntimeError): +class GitFailed(RuntimeError): pass class MockConsole: """Mock for the FreeCAD.Console -- does NOT print anything out, just logs it.""" + def __init__(self): self.log = [] self.messages = [] @@ -71,6 +74,8 @@ class MockConsole: class MockMetadata: + """Minimal implementation of a Metadata-like object.""" + def __init__(self): self.Name = "MockMetadata" self.Urls = {"repository": {"location": "file://localhost/", "branch": "main"}} @@ -83,6 +88,8 @@ class MockMetadata: """Don't use the real metadata class, but try to read in the parameters we care about from the given metadata file (or file-like object, as the case probably is). This allows us to test whether the data is being passed around correctly.""" + + # pylint: disable=too-many-branches xml = None root = None try: @@ -120,12 +127,14 @@ class MockMetadata: class MockAddon: """Minimal Addon class""" + # pylint: disable=too-many-instance-attributes + def __init__( - self, - name: str = None, - url: str = None, - status: object = None, - branch: str = "main", + self, + name: str = None, + url: str = None, + status: object = None, + branch: str = "main", ): test_dir = os.path.join(os.path.dirname(__file__), "..", "data") if name: @@ -188,12 +197,13 @@ class MockMacro: def install(self, location: os.PathLike): """Installer function for the mock macro object: creates a file with the src_filename attribute, and optionally an icon, xpm, and other_files. The data contained in these files - is not usable and serves only as a placeholder for the existence of the files.""" + is not usable and serves only as a placeholder for the existence of the files. + """ with open( - os.path.join(location, self.filename), - "w", - encoding="utf-8", + os.path.join(location, self.filename), + "w", + encoding="utf-8", ) as f: f.write("Test file for macro installation unit tests") if self.icon: @@ -201,7 +211,7 @@ class MockMacro: f.write(b"Fake icon data - nothing to see here\n") if self.xpm: with open( - os.path.join(location, "MockMacro_icon.xpm"), "w", encoding="utf-8" + os.path.join(location, "MockMacro_icon.xpm"), "w", encoding="utf-8" ) as f: f.write(self.xpm) for name in self.other_files: @@ -224,6 +234,15 @@ class MockMacro: class SignalCatcher: + """Object to track signals that it has caught. + + Usage: + catcher = SignalCatcher() + my_signal.connect(catcher.catch_signal) + do_things_that_emit_the_signal() + self.assertTrue(catcher.caught) + """ + def __init__(self): self.caught = False self.killed = False @@ -238,6 +257,8 @@ class SignalCatcher: class AddonSignalCatcher: + """Signal catcher specifically designed for catching emitted addons.""" + def __init__(self): self.addons = [] @@ -246,6 +267,10 @@ class AddonSignalCatcher: class CallCatcher: + """Generic call monitor -- use to override functions that are not themselves under + test so that you can detect when the function has been called, and how many times. + """ + def __init__(self): self.called = False self.call_count = 0 @@ -260,7 +285,8 @@ class CallCatcher: class MockGitManager: """A mock git manager: does NOT require a git installation. Takes no actions, only records which functions are called for instrumentation purposes. Can be forced to appear to fail as - needed. Various member variables can be set to emulate necessary return responses.""" + needed. Various member variables can be set to emulate necessary return responses. + """ def __init__(self): self.called_methods = [] @@ -288,7 +314,9 @@ class MockGitManager: self.called_methods.append("clone") self._check_for_failure() - def async_clone(self, _remote, _local_path, _progress_monitor, _args: List[str] = None): + def async_clone( + self, _remote, _local_path, _progress_monitor, _args: List[str] = None + ): self.called_methods.append("async_clone") self._check_for_failure() @@ -380,7 +408,7 @@ class MockSignal: class MockNetworkManager: """Instrumented mock for the NetworkManager. Does no network access, is not asynchronous, and - does not require a running event loop. No submitted requests ever complete.""" + does not require a running event loop. No submitted requests ever complete.""" def __init__(self): self.urls = [] @@ -418,8 +446,28 @@ class MockNetworkManager: class MockByteArray: + """Mock for QByteArray. Only provides the data() access member.""" + def __init__(self, data_to_wrap="data".encode("utf-8")): self.wrapped = data_to_wrap def data(self) -> bytes: return self.wrapped + + +class MockThread: + """Mock for QThread for use when threading is not being used, but interruption + needs to be tested. Set interrupt_after_n_calls to the call number to stop at.""" + + def __init__(self): + self.interrupt_after_n_calls = 0 + self.interrupt_check_counter = 0 + + def isInterruptionRequested(self): + self.interrupt_check_counter += 1 + if ( + self.interrupt_after_n_calls + and self.interrupt_check_counter >= self.interrupt_after_n_calls + ): + return True + return False diff --git a/src/Mod/AddonManager/AddonManagerTest/app/test_addon.py b/src/Mod/AddonManager/AddonManagerTest/app/test_addon.py index 3c53df90a0..0e1e493515 100644 --- a/src/Mod/AddonManager/AddonManagerTest/app/test_addon.py +++ b/src/Mod/AddonManager/AddonManagerTest/app/test_addon.py @@ -220,7 +220,7 @@ class TestAddon(unittest.TestCase): self.assertEqual(addon.repo_type, Addon.Kind.MACRO) self.assertEqual(addon.name, "DoNothing") self.assertEqual( - addon.macro.comment, "Do absolutely nothing. For Addon Manager unit tests." + addon.macro.comment, "Do absolutely nothing. For Addon Manager integration tests." ) self.assertEqual(addon.url, "https://github.com/FreeCAD/FreeCAD") self.assertEqual(addon.macro.version, "1.0") @@ -228,7 +228,7 @@ class TestAddon(unittest.TestCase): self.assertEqual(addon.macro.author, "Chris Hennes") self.assertEqual(addon.macro.date, "2022-02-28") self.assertEqual(addon.macro.icon, "not_real.png") - self.assertEqual(addon.macro.xpm, "") + self.assertNotEqual(addon.macro.xpm, "") def test_cache(self): addon = Addon( diff --git a/src/Mod/AddonManager/AddonManagerTest/app/test_macro_parser.py b/src/Mod/AddonManager/AddonManagerTest/app/test_macro_parser.py new file mode 100644 index 0000000000..f10a0fc347 --- /dev/null +++ b/src/Mod/AddonManager/AddonManagerTest/app/test_macro_parser.py @@ -0,0 +1,347 @@ +# *************************************************************************** +# * * +# * Copyright (c) 2022-2023 FreeCAD Project Association * +# * * +# * 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 * +# * . * +# * * +# *************************************************************************** + +"""Tests for the MacroParser class""" + +import io +import os +import sys +import unittest + +sys.path.append("../../") # So the IDE can find the classes to run with + +from addonmanager_macro_parser import MacroParser +from AddonManagerTest.app.mocks import MockConsole, CallCatcher, MockThread + + +# pylint: disable=protected-access, too-many-public-methods + + +class TestMacroParser(unittest.TestCase): + """Test the MacroParser class""" + + def setUp(self) -> None: + self.test_object = MacroParser("UnitTestMacro") + self.test_object.console = MockConsole() + self.test_object.current_thread = MockThread() + + def tearDown(self) -> None: + pass + + def test_fill_details_from_code_normal(self): + """Test to make sure _process_line gets called as expected""" + catcher = CallCatcher() + self.test_object._process_line = catcher.catch_call + fake_macro_data = self.given_some_lines(20, 10) + self.test_object.fill_details_from_code(fake_macro_data) + self.assertEqual(catcher.call_count, 10) + + def test_fill_details_from_code_too_many_lines(self): + """Test to make sure _process_line gets limited as expected""" + catcher = CallCatcher() + self.test_object._process_line = catcher.catch_call + self.test_object.MAX_LINES_TO_SEARCH = 5 + fake_macro_data = self.given_some_lines(20, 10) + self.test_object.fill_details_from_code(fake_macro_data) + self.assertEqual(catcher.call_count, 5) + + def test_fill_details_from_code_thread_interrupted(self): + """Test to make sure _process_line gets stopped as expected""" + catcher = CallCatcher() + self.test_object._process_line = catcher.catch_call + self.test_object.current_thread.interrupt_after_n_calls = 6 # Stop on the 6th + fake_macro_data = self.given_some_lines(20, 10) + self.test_object.fill_details_from_code(fake_macro_data) + self.assertEqual(catcher.call_count, 5) + + @staticmethod + def given_some_lines(num_lines, num_dunder_lines) -> str: + """Generate fake macro header data with the given number of lines and number of + lines beginning with a double-underscore.""" + result = "" + for i in range(num_lines): + if i < num_dunder_lines: + result += f"__something_{i}__ = 'Test{i}' # A line to be scanned\n" + else: + result += f"# Nothing to see on line {i}\n" + return result + + def test_process_line_known_lines(self): + """Lines starting with keys are processed""" + test_lines = ["__known_key__ = 'Test'", "__another_known_key__ = 'Test'"] + for line in test_lines: + with self.subTest(line=line): + self.test_object.remaining_item_map = { + "__known_key__": "known_key", + "__another_known_key__": "another_known_key", + } + content_lines = io.StringIO(line) + read_in_line = content_lines.readline() + catcher = CallCatcher() + self.test_object._process_key = catcher.catch_call + self.test_object._process_line(read_in_line, content_lines) + self.assertTrue( + catcher.called, "_process_key was not called for a known key" + ) + + def test_process_line_unknown_lines(self): + """Lines starting with non-keys are not processed""" + test_lines = [ + "# Just a line with a comment", + "\n", + "__dont_know_this_one__ = 'Who cares?'", + "# __known_key__ = 'Aha, but it is commented out!'", + ] + for line in test_lines: + with self.subTest(line=line): + self.test_object.remaining_item_map = { + "__known_key__": "known_key", + "__another_known_key__": "another_known_key", + } + content_lines = io.StringIO(line) + read_in_line = content_lines.readline() + catcher = CallCatcher() + self.test_object._process_key = catcher.catch_call + self.test_object._process_line(read_in_line, content_lines) + self.assertFalse( + catcher.called, "_process_key was called for an unknown key" + ) + + def test_process_key_standard(self): + """Normal expected data is processed""" + self.test_object._reset_map() + in_memory_data = '__comment__ = "Test"' + content_lines = io.StringIO(in_memory_data) + line = content_lines.readline() + self.test_object._process_key("__comment__", line, content_lines) + self.assertTrue(self.test_object.parse_results["comment"], "Test") + + def test_process_key_special(self): + """Special handling for version = date is processed""" + self.test_object._reset_map() + self.test_object.parse_results["date"] = "2001-01-01" + in_memory_data = "__version__ = __date__" + content_lines = io.StringIO(in_memory_data) + line = content_lines.readline() + self.test_object._process_key("__version__", line, content_lines) + self.assertTrue(self.test_object.parse_results["version"], "2001-01-01") + + def test_handle_backslash_continuation_no_backslashes(self): + """The backslash handling code doesn't change a line with no backslashes""" + in_memory_data = '"Not a backslash in sight"' + content_lines = io.StringIO(in_memory_data) + line = content_lines.readline() + result = self.test_object._handle_backslash_continuation(line, content_lines) + self.assertEqual(result, in_memory_data) + + def test_handle_backslash_continuation(self): + """Lines ending in a backslash get stripped and concatenated""" + in_memory_data = '"Line1\\\nLine2\\\nLine3\\\nLine4"' + content_lines = io.StringIO(in_memory_data) + line = content_lines.readline() + result = self.test_object._handle_backslash_continuation(line, content_lines) + self.assertEqual(result, '"Line1Line2Line3Line4"') + + def test_handle_triple_quoted_string_no_triple_quotes(self): + """The triple-quote handler leaves alone lines without triple-quotes""" + in_memory_data = '"Line1"' + content_lines = io.StringIO(in_memory_data) + line = content_lines.readline() + result, was_triple_quoted = self.test_object._handle_triple_quoted_string( + line, content_lines + ) + self.assertEqual(result, in_memory_data) + self.assertFalse(was_triple_quoted) + + def test_handle_triple_quoted_string(self): + """Data is extracted across multiple lines for a triple-quoted string""" + in_memory_data = '"""Line1\nLine2\nLine3\nLine4"""\nLine5\n' + content_lines = io.StringIO(in_memory_data) + line = content_lines.readline() + result, was_triple_quoted = self.test_object._handle_triple_quoted_string( + line, content_lines + ) + self.assertEqual(result, '"""Line1\nLine2\nLine3\nLine4"""') + self.assertTrue(was_triple_quoted) + + def test_strip_quotes_single(self): + """Single quotes are stripped from the final string""" + expected = "test" + quoted = f"'{expected}'" + actual = self.test_object._strip_quotes(quoted) + self.assertEqual(actual, expected) + + def test_strip_quotes_double(self): + """Double quotes are stripped from the final string""" + expected = "test" + quoted = f'"{expected}"' + actual = self.test_object._strip_quotes(quoted) + self.assertEqual(actual, expected) + + def test_strip_quotes_triple(self): + """Triple quotes are stripped from the final string""" + expected = "test" + quoted = f'"""{expected}"""' + actual = self.test_object._strip_quotes(quoted) + self.assertEqual(actual, expected) + + def test_strip_quotes_unquoted(self): + """Unquoted data results in None""" + unquoted = "This has no quotation marks of any kind" + actual = self.test_object._strip_quotes(unquoted) + self.assertIsNone(actual) + + def test_standard_extraction_string(self): + """String variables are extracted and stored""" + string_keys = [ + "comment", + "url", + "wiki", + "version", + "author", + "date", + "icon", + "xpm", + ] + for key in string_keys: + with self.subTest(key=key): + self.test_object._standard_extraction(key, "test") + self.assertEqual(self.test_object.parse_results[key], "test") + + def test_standard_extraction_list(self): + """List variable is extracted and stored""" + key = "other_files" + self.test_object._standard_extraction(key, "test1, test2, test3") + self.assertIn("test1", self.test_object.parse_results[key]) + self.assertIn("test2", self.test_object.parse_results[key]) + self.assertIn("test3", self.test_object.parse_results[key]) + + def test_apply_special_handling_version(self): + """If the tag is __version__, apply our special handling""" + self.test_object._reset_map() + self.test_object._apply_special_handling("__version__", 42) + self.assertNotIn("__version__", self.test_object.remaining_item_map) + self.assertEqual(self.test_object.parse_results["version"], "42") + + def test_apply_special_handling_not_version(self): + """If the tag is not __version__, raise an error""" + self.test_object._reset_map() + with self.assertRaises(SyntaxError): + self.test_object._apply_special_handling("__not_version__", 42) + self.assertIn("__version__", self.test_object.remaining_item_map) + + def test_process_noncompliant_version_date(self): + """Detect and allow __date__ for the __version__""" + self.test_object.parse_results["date"] = "1/2/3" + self.test_object._process_noncompliant_version("__date__") + self.assertEqual( + self.test_object.parse_results["version"], + self.test_object.parse_results["date"], + ) + + def test_process_noncompliant_version_float(self): + """Detect and allow floats for the __version__""" + self.test_object._process_noncompliant_version(1.2) + self.assertEqual(self.test_object.parse_results["version"], "1.2") + + def test_process_noncompliant_version_int(self): + """Detect and allow integers for the __version__""" + self.test_object._process_noncompliant_version(42) + self.assertEqual(self.test_object.parse_results["version"], "42") + + def test_detect_illegal_content_prefixed_string(self): + """Detect and raise an error for various kinds of prefixed strings""" + illegal_strings = [ + "f'Some fancy {thing}'", + 'f"Some fancy {thing}"', + "r'Some fancy {thing}'", + 'r"Some fancy {thing}"', + "u'Some fancy {thing}'", + 'u"Some fancy {thing}"', + "fr'Some fancy {thing}'", + 'fr"Some fancy {thing}"', + "rf'Some fancy {thing}'", + 'rf"Some fancy {thing}"', + ] + for test_string in illegal_strings: + with self.subTest(test_string=test_string): + with self.assertRaises(SyntaxError): + MacroParser._detect_illegal_content(test_string) + + def test_detect_illegal_content_not_a_string(self): + """Detect and raise an error for (some) non-strings""" + illegal_strings = [ + "no quotes", + "do_stuff()", + 'print("A function call sporting quotes!")', + "__name__", + "__version__", + "1.2.3", + ] + for test_string in illegal_strings: + with self.subTest(test_string=test_string): + with self.assertRaises(SyntaxError): + MacroParser._detect_illegal_content(test_string) + + def test_detect_illegal_content_no_failure(self): + """Recognize strings of various kinds, plus ints, and floats""" + legal_strings = [ + '"Some legal value in double quotes"', + "'Some legal value in single quotes'", + '"""Some legal value in triple quotes"""', + "__date__", + "42", + "4.2", + ] + for test_string in legal_strings: + with self.subTest(test_string=test_string): + MacroParser._detect_illegal_content(test_string) + + ##################### + # INTEGRATION TESTS # + ##################### + + def test_macro_parser(self): + """INTEGRATION TEST: Given "real" data, ensure the parsing yields the expected results.""" + data_dir = os.path.join(os.path.dirname(__file__), "../data") + macro_file = os.path.join(data_dir, "DoNothing.FCMacro") + with open(macro_file, "r", encoding="utf-8") as f: + code = f.read() + self.test_object.fill_details_from_code(code) + self.assertEqual(len(self.test_object.console.errors), 0) + self.assertEqual(len(self.test_object.console.warnings), 0) + self.assertEqual(self.test_object.parse_results["author"], "Chris Hennes") + self.assertEqual(self.test_object.parse_results["version"], "1.0") + self.assertEqual(self.test_object.parse_results["date"], "2022-02-28") + self.assertEqual( + self.test_object.parse_results["comment"], + "Do absolutely nothing. For Addon Manager integration tests.", + ) + self.assertEqual( + self.test_object.parse_results["url"], "https://github.com/FreeCAD/FreeCAD" + ) + self.assertEqual(self.test_object.parse_results["icon"], "not_real.png") + self.assertListEqual( + self.test_object.parse_results["other_files"], + ["file1.py", "file2.py", "file3.py"], + ) + self.assertNotEqual(self.test_object.parse_results["xpm"], "") diff --git a/src/Mod/AddonManager/AddonManagerTest/data/DoNothing.FCMacro b/src/Mod/AddonManager/AddonManagerTest/data/DoNothing.FCMacro index dafdb02d2b..4ccec1e450 100644 --- a/src/Mod/AddonManager/AddonManagerTest/data/DoNothing.FCMacro +++ b/src/Mod/AddonManager/AddonManagerTest/data/DoNothing.FCMacro @@ -4,7 +4,7 @@ __Title__ = 'Do Nothing' __Author__ = 'Chris Hennes' __Version__ = '1.0' __Date__ = '2022-02-28' -__Comment__ = 'Do absolutely nothing. For Addon Manager unit tests.' +__Comment__ = 'Do absolutely nothing. For Addon Manager integration tests.' __Web__ = 'https://github.com/FreeCAD/FreeCAD' __Wiki__ = '' __Icon__ = 'not_real.png' @@ -13,5 +13,18 @@ __Status__ = 'Very Stable' __Requires__ = '' __Communication__ = 'Shout into the void' __Files__ = 'file1.py, file2.py, file3.py' +__Xpm__ = """/* XPM */ +static char * blarg_xpm[] = { +"16 7 2 1", +"* c #000000", +". c #ffffff", +"**..*...........", +"*.*.*...........", +"**..*..**.**..**", +"*.*.*.*.*.*..*.*", +"**..*..**.*...**", +"...............*", +".............**." +};""" print("Well, not quite *nothing*... it does print this line out.") \ No newline at end of file diff --git a/src/Mod/AddonManager/CMakeLists.txt b/src/Mod/AddonManager/CMakeLists.txt index 8d4f182580..dad3d3d455 100644 --- a/src/Mod/AddonManager/CMakeLists.txt +++ b/src/Mod/AddonManager/CMakeLists.txt @@ -23,6 +23,7 @@ SET(AddonManager_SRCS addonmanager_installer.py addonmanager_installer_gui.py addonmanager_macro.py + addonmanager_macro_parser.py addonmanager_update_all_gui.py addonmanager_uninstaller.py addonmanager_uninstaller_gui.py @@ -85,6 +86,7 @@ SET(AddonManagerTestsApp_SRCS AddonManagerTest/app/test_git.py AddonManagerTest/app/test_installer.py AddonManagerTest/app/test_macro.py + AddonManagerTest/app/test_macro_parser.py AddonManagerTest/app/test_utilities.py AddonManagerTest/app/test_uninstaller.py ) diff --git a/src/Mod/AddonManager/addonmanager_macro.py b/src/Mod/AddonManager/addonmanager_macro.py index e966a05f8d..e24831410f 100644 --- a/src/Mod/AddonManager/addonmanager_macro.py +++ b/src/Mod/AddonManager/addonmanager_macro.py @@ -33,9 +33,8 @@ from typing import Dict, Tuple, List, Union, Optional import FreeCAD import NetworkManager -from PySide import QtCore -from addonmanager_utilities import is_float +from addonmanager_macro_parser import MacroParser translate = FreeCAD.Qt.translate @@ -109,7 +108,8 @@ class Macro: def is_installed(self): """Returns True if this macro is currently installed (that is, if it exists in the user macro directory), or False if it is not. Both the exact filename, as well as - the filename prefixed with "Macro", are considered an installation of this macro.""" + the filename prefixed with "Macro", are considered an installation of this macro. + """ if self.on_git and not self.src_filename: return False return os.path.exists( @@ -125,144 +125,12 @@ class Macro: self.fill_details_from_code(self.code) def fill_details_from_code(self, code: str) -> None: - """Reads in the macro code from the given string and parses it for its metadata.""" - # Number of parsed fields of metadata. Overrides anything set previously (the code is - # considered authoritative). - # For now: - # __Comment__ - # __Web__ - # __Wiki__ - # __Version__ - # __Files__ - # __Author__ - # __Date__ - # __Icon__ - max_lines_to_search = 200 - line_counter = 0 - - string_search_mapping = { - "__comment__": "comment", - "__web__": "url", - "__wiki__": "wiki", - "__version__": "version", - "__files__": "other_files", - "__author__": "author", - "__date__": "date", - "__icon__": "icon", - "__xpm__": "xpm", - } - - string_search_regex = re.compile(r"\s*(['\"])(.*)\1") - f = io.StringIO(code) - while f and line_counter < max_lines_to_search: - line = f.readline() - if not line: - break - if QtCore.QThread.currentThread().isInterruptionRequested(): - return - line_counter += 1 - if not line.startswith("__"): - # Speed things up a bit... this comparison is very cheap - continue - - lowercase_line = line.lower() - for key, value in string_search_mapping.items(): - if lowercase_line.startswith(key): - _, _, after_equals = line.partition("=") - match = re.match(string_search_regex, after_equals) - - # We do NOT support triple-quoted strings, except for the icon XPM data - # Most cases should be caught by this code - if match and '"""' not in after_equals: - self._standard_extraction(value, match.group(2)) - string_search_mapping.pop(key) - break - - # For cases where either there is no match, or we found a triple quote, - # more processing is needed - - # Macro authors are supposed to be providing strings here, but in some - # cases they are not doing so. If this is the "__version__" tag, try - # to apply some special handling to accepts numbers, and "__date__" - if key == "__version__": - self._process_noncompliant_version(after_equals) - string_search_mapping.pop(key) - break - - # Icon data can be actual embedded XPM data, inside a triple-quoted string - if key in ("__icon__", "__xpm__"): - self._process_icon(f, key, after_equals) - string_search_mapping.pop(key) - break - - FreeCAD.Console.PrintError( - translate( - "AddonsInstaller", - "Syntax error while reading {} from macro {}", - ).format(key, self.name) - + "\n" - ) - FreeCAD.Console.PrintError(line + "\n") - - # Do some cleanup of the values: - if self.comment: - self.comment = re.sub("<.*?>", "", self.comment) # Strip any HTML tags - - # Truncate long comments to speed up searches, and clean up display - if len(self.comment) > 512: - self.comment = self.comment[:511] + "…" - - # Make sure the icon is not an absolute path, etc. - self.clean_icon() - + parser = MacroParser(self.name, code) + for key, value in parser.parse_results.items(): + if value: + self.__dict__[key] = value self.parsed = True - def _standard_extraction(self, value: str, match_group): - """For most macro metadata values, this extracts the required data""" - if isinstance(self.__dict__[value], str): - self.__dict__[value] = match_group - elif isinstance(self.__dict__[value], list): - self.__dict__[value] = [of.strip() for of in match_group.split(",")] - else: - FreeCAD.Console.PrintError( - "Internal Error: bad type in addonmanager_macro class.\n" - ) - - def _process_noncompliant_version(self, after_equals): - if "__date__" in after_equals.lower(): - self.version = self.date - elif is_float(after_equals): - self.version = str(after_equals).strip() - else: - FreeCAD.Console.PrintLog( - f"Unrecognized value for __version__ in macro {self.name}" - ) - self.version = "(Unknown)" - - def _process_icon(self, f, key, after_equals): - # If this is an icon, it's possible that the icon was actually directly - # specified in the file as XPM data. This data **must** be between - # triple double quotes in order for the Addon Manager to recognize it. - if '"""' in after_equals: - _, _, xpm_data = after_equals.partition('"""') - while True: - line = f.readline() - if not line: - FreeCAD.Console.PrintError( - translate( - "AddonsInstaller", - "Syntax error while reading {} from macro {}", - ).format(key, self.name) - + "\n" - ) - break - if '"""' in line: - last_line, _, _ = line.partition('"""') - xpm_data += last_line - break - xpm_data += line - self.xpm = xpm_data - def fill_details_from_wiki(self, url): """For a given URL, download its data and attempt to get the macro's metadata out of it. If the macro's code is hosted elsewhere, as specified by a "rawcodeurl" found on diff --git a/src/Mod/AddonManager/addonmanager_macro_parser.py b/src/Mod/AddonManager/addonmanager_macro_parser.py new file mode 100644 index 0000000000..27e45bca22 --- /dev/null +++ b/src/Mod/AddonManager/addonmanager_macro_parser.py @@ -0,0 +1,249 @@ +# *************************************************************************** +# * * +# * Copyright (c) 2023 FreeCAD Project Association * +# * * +# * 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 * +# * . * +# * * +# *************************************************************************** + +"""Contains the parser class for extracting metadata from a FreeCAD macro""" + +# pylint: disable=too-few-public-methods + +import io +import re +from typing import Any, Tuple + +try: + from PySide import QtCore +except ImportError: + QtCore = None + +try: + import FreeCAD +except ImportError: + FreeCAD = None + + +class DummyThread: + @classmethod + def isInterruptionRequested(cls): + return False + + +class MacroParser: + """Extracts metadata information from a FreeCAD macro""" + + MAX_LINES_TO_SEARCH = 200 # To speed up parsing: some files are VERY large + + def __init__(self, name: str, code: str = ""): + """Create a parser for the macro named "name". Note that the name is only + used as the context for error messages, it is not otherwise important.""" + self.name = name + self.parse_results = { + "comment": "", + "url": "", + "wiki": "", + "version": "", + "other_files": [""], + "author": "", + "date": "", + "icon": "", + "xpm": "", + } + self.remaining_item_map = {} + self.console = None if FreeCAD is None else FreeCAD.Console + self.current_thread = ( + DummyThread() if QtCore is None else QtCore.QThread.currentThread() + ) + if code: + self.fill_details_from_code(code) + + def _reset_map(self): + """This map tracks which items we've already read. If the same parser is used + twice, it has to be reset.""" + self.remaining_item_map = { + "__comment__": "comment", + "__web__": "url", + "__wiki__": "wiki", + "__version__": "version", + "__files__": "other_files", + "__author__": "author", + "__date__": "date", + "__icon__": "icon", + "__xpm__": "xpm", + } + + def fill_details_from_code(self, code: str) -> None: + """Reads in the macro code from the given string and parses it for its + metadata.""" + + self._reset_map() + line_counter = 0 + content_lines = io.StringIO(code) + while content_lines and line_counter < self.MAX_LINES_TO_SEARCH: + line = content_lines.readline() + if not line: + break + if self.current_thread.isInterruptionRequested(): + return + line_counter += 1 + if not line.startswith("__"): + # Speed things up a bit... this comparison is very cheap + continue + try: + self._process_line(line, content_lines) + except SyntaxError as e: + err_string = f"Syntax error when parsing macro {self.name}:\n{str(e)}" + if self.console: + self.console.PrintWarning(err_string) + else: + print(err_string) + + def _process_line(self, line: str, content_lines: io.StringIO): + """Given a single line of the macro file, see if it matches one of our items, + and if so, extract the data.""" + + lowercase_line = line.lower() + for key in self.remaining_item_map: + if lowercase_line.startswith(key): + self._process_key(key, line, content_lines) + break + + def _process_key(self, key: str, line: str, content_lines: io.StringIO): + """Given a line that starts with a known key, extract the data for that key, + possibly reading in additional lines (if it contains a line continuation + character, or is a triple-quoted string).""" + + line = self._handle_backslash_continuation(line, content_lines) + line, was_triple_quoted = self._handle_triple_quoted_string(line, content_lines) + + _, _, line = line.partition("=") + if not was_triple_quoted: + line, _, _ = line.partition("#") + self._detect_illegal_content(line) + final_content_line = line.strip() + + stripped_of_quotes = self._strip_quotes(final_content_line) + if stripped_of_quotes is not None: + self._standard_extraction(self.remaining_item_map[key], stripped_of_quotes) + self.remaining_item_map.pop(key) + else: + self._apply_special_handling(key, line) + + @staticmethod + def _handle_backslash_continuation(line, content_lines) -> str: + while line.strip().endswith("\\"): + line = line.strip()[:-1] + concat_line = content_lines.readline() + line += concat_line.strip() + return line + + @staticmethod + def _handle_triple_quoted_string(line, content_lines) -> Tuple[str, bool]: + result = line + was_triple_quoted = False + if '"""' in result: + was_triple_quoted = True + while True: + new_line = content_lines.readline() + if not new_line: + raise SyntaxError("Syntax error while reading macro") + if '"""' in new_line: + last_line, _, _ = new_line.partition('"""') + result += last_line + '"""' + break + result += new_line + return result, was_triple_quoted + + @staticmethod + def _strip_quotes(line) -> str: + line = line.strip() + stripped_of_quotes = None + if line.startswith('"""') and line.endswith('"""'): + stripped_of_quotes = line[3:-3] + elif (line[0] == '"' and line[-1] == '"') or ( + line[0] == "'" and line[-1] == "'" + ): + stripped_of_quotes = line[1:-1] + return stripped_of_quotes + + def _standard_extraction(self, value: str, match_group: str): + """For most macro metadata values, this extracts the required data""" + if isinstance(self.parse_results[value], str): + self.parse_results[value] = match_group + if value == "comment": + self._cleanup_comment() + elif isinstance(self.parse_results[value], list): + self.parse_results[value] = [of.strip() for of in match_group.split(",")] + else: + raise SyntaxError(f"Conflicting data type for {value}") + + def _cleanup_comment(self): + """Remove HTML from the comment line, and truncate it at 512 characters.""" + + self.parse_results["comment"] = re.sub( + "<.*?>", "", self.parse_results["comment"] + ) + if len(self.parse_results["comment"]) > 512: + self.parse_results["comment"] = self.parse_results["comment"][:511] + "…" + + def _apply_special_handling(self, key: str, line: str): + # Macro authors are supposed to be providing strings here, but in some + # cases they are not doing so. If this is the "__version__" tag, try + # to apply some special handling to accept numbers, and "__date__" + if key == "__version__": + self._process_noncompliant_version(line) + self.remaining_item_map.pop(key) + return + + raise SyntaxError(f"Failed to process {key} from {line}") + + def _process_noncompliant_version(self, after_equals): + if is_float(after_equals): + self.parse_results["version"] = str(after_equals).strip() + elif "__date__" in after_equals.lower() and self.parse_results["date"]: + self.parse_results["version"] = self.parse_results["date"] + else: + self.parse_results["version"] = "(Unknown)" + raise SyntaxError(f"Unrecognized version string {after_equals}") + + @staticmethod + def _detect_illegal_content(line: str): + """Raise a syntax error if this line contains something we can't handle""" + + lower_line = line.strip().lower() + if lower_line.startswith("'") and lower_line.endswith("'"): + return + if lower_line.startswith('"') and lower_line.endswith('"'): + return + if is_float(lower_line): + return + if lower_line == "__date__": + return + raise SyntaxError(f"Metadata is expected to be a static string, but got {line}") + + +# Borrowed from Stack Overflow: +# https://stackoverflow.com/questions/736043/checking-if-a-string-can-be-converted-to-float +def is_float(element: Any) -> bool: + """Determine whether a given item can be converted to a floating-point number""" + try: + float(element) + return True + except ValueError: + return False From d54efb42b35c4bc7c34551cbd44a01ed46fe5dfb Mon Sep 17 00:00:00 2001 From: luzpaz Date: Tue, 14 Feb 2023 05:22:04 -0500 Subject: [PATCH 14/42] Arch: fix incorrect indentation in ArchStairs.py (#8475) --- src/Mod/Arch/ArchStairs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Arch/ArchStairs.py b/src/Mod/Arch/ArchStairs.py index 12965187ba..d25bbf5402 100644 --- a/src/Mod/Arch/ArchStairs.py +++ b/src/Mod/Arch/ArchStairs.py @@ -236,7 +236,7 @@ class _CommandStairs: FreeCADGui.addModule("Draft") for obj in stairs: - Draft.autogroup(obj) # seems not working? + Draft.autogroup(obj) # seems not working? FreeCAD.ActiveDocument.commitTransaction() FreeCAD.ActiveDocument.recompute() From e71386d20df36bd89db2ddc38efbb89bd8d1c7e2 Mon Sep 17 00:00:00 2001 From: wmayer Date: Tue, 14 Feb 2023 11:12:19 +0100 Subject: [PATCH 15/42] App: define an enum to be used as the retType argument of DocumentObject.getSubObject() This is to avoid to use magic numbers in client code --- src/App/FreeCADInit.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/App/FreeCADInit.py b/src/App/FreeCADInit.py index 123c7522ca..f085c24718 100644 --- a/src/App/FreeCADInit.py +++ b/src/App/FreeCADInit.py @@ -947,6 +947,17 @@ class PropertyType(IntEnum): App.PropertyType = PropertyType +class ReturnType(IntEnum): + PyObject = 0 + DocObject = 1 + DocAndPyObject = 2 + Placement = 3 + Matrix = 4 + LinkAndPlacement = 5 + LinkAndMatrix = 6 + +App.ReturnType = ReturnType + # clean up namespace del(InitApplications) del(test_ascii) From 559923a8d076ebdcce98cb118c0a23096b51e993 Mon Sep 17 00:00:00 2001 From: Uwe Date: Tue, 14 Feb 2023 05:15:52 +0100 Subject: [PATCH 16/42] [FEM] Contours: fix task panel logic - the ViewProvider sorting of the field can be different from the sorting in the dialog - also hide a Contours property (I forgot this when the contours filter was added) --- src/Mod/Fem/App/FemPostFilter.cpp | 3 ++- src/Mod/Fem/Gui/TaskPostBoxes.cpp | 34 ++++++++++++------------------- src/Mod/Fem/Gui/TaskPostBoxes.h | 2 +- 3 files changed, 16 insertions(+), 23 deletions(-) diff --git a/src/Mod/Fem/App/FemPostFilter.cpp b/src/Mod/Fem/App/FemPostFilter.cpp index 2096a2c2a3..76116180c2 100644 --- a/src/Mod/Fem/App/FemPostFilter.cpp +++ b/src/Mod/Fem/App/FemPostFilter.cpp @@ -477,7 +477,8 @@ FemPostContoursFilter::FemPostContoursFilter() ADD_PROPERTY_TYPE(Field, (long(0)), "Clip", App::Prop_None, "The field used to clip"); ADD_PROPERTY_TYPE( VectorMode, ((long)0), "Contours", App::Prop_None, "Select what vector field"); - ADD_PROPERTY_TYPE(NoColor, (false), "Contours", App::Prop_None, "Don't color the contours"); + ADD_PROPERTY_TYPE(NoColor, (false), "Contours", + PropertyType(Prop_Hidden), "Don't color the contours"); m_contourConstraints.LowerBound = 1; m_contourConstraints.UpperBound = 1000; diff --git a/src/Mod/Fem/Gui/TaskPostBoxes.cpp b/src/Mod/Fem/Gui/TaskPostBoxes.cpp index 0b9ac4d2c3..fe24296f87 100644 --- a/src/Mod/Fem/Gui/TaskPostBoxes.cpp +++ b/src/Mod/Fem/Gui/TaskPostBoxes.cpp @@ -1369,26 +1369,19 @@ TaskPostContours::~TaskPostContours() void TaskPostContours::applyPythonCode() {} -void TaskPostContours::updateFields(int idx) +void TaskPostContours::updateFields() { - std::vector fieldArray; - std::vector vec = - getTypedObject()->Field.getEnumVector(); - for (std::vector::iterator it = vec.begin(); it != vec.end(); ++it) { - fieldArray.emplace_back(*it); + // update the ViewProvider Field + // since the ViewProvider can have another field sorting, we cannot use the same index + if (!static_cast(getObject())->NoColor.getValue()) { + std::string objectField = + getTypedObject()->Field.getValueAsString(); + getTypedView()->Field.setValue(objectField.c_str()); } - // update the ViewProvider Field enums - App::Enumeration anEnum; - getTypedView()->Field.setValue(anEnum); - anEnum.setEnums(fieldArray); - getTypedView()->Field.setValue(anEnum); - // set new Field index to ViewProvider Field - // the ViewProvider field starts with an additional entry "None", - // therefore the desired new setting is idx + 1 - if (!static_cast(getObject())->NoColor.getValue()) - getTypedView()->Field.setValue(idx + 1); - else - getTypedView()->Field.setValue(long(0)); + else { + getTypedView()->Field.setValue("None"); + } + } void TaskPostContours::onFieldsChanged(int idx) @@ -1402,7 +1395,7 @@ void TaskPostContours::onFieldsChanged(int idx) // In > 99 % of the cases the coloring should be equal to the field, // thus change the coloring field too. Users can override this be resetting only the coloring // field afterwards in the properties if really necessary. - updateFields(idx); + updateFields(); // since a new field can be e.g. no vector while the previous one was, // we must also update the VectorMode @@ -1421,8 +1414,7 @@ void TaskPostContours::onVectorModeChanged(int idx) // since it is a 2D field, Z is eompty thus no field is available to color // when the user noch goes back to e.g. "Y" we must set the Field // first to get the possible VectorModes of that field - auto currentField = getTypedObject()->Field.getValue(); - updateFields(currentField); + updateFields(); // now we can set the VectorMode if (!static_cast(getObject())->NoColor.getValue()) getTypedView()->VectorMode.setValue(idx); diff --git a/src/Mod/Fem/Gui/TaskPostBoxes.h b/src/Mod/Fem/Gui/TaskPostBoxes.h index 0a6e5e7803..4f1fa1a23c 100644 --- a/src/Mod/Fem/Gui/TaskPostBoxes.h +++ b/src/Mod/Fem/Gui/TaskPostBoxes.h @@ -385,7 +385,7 @@ private: QWidget* proxy; std::unique_ptr ui; bool blockVectorUpdate = false; - void updateFields(int idx); + void updateFields(); }; From 921ecca691a76e05de694fdca9327dfc40612342 Mon Sep 17 00:00:00 2001 From: Uwe Date: Tue, 14 Feb 2023 13:44:18 +0100 Subject: [PATCH 17/42] [FEM] [skip ci] Countours: remove unused code - also fix a trailing whitespace --- src/Mod/Fem/App/FemPostFilter.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Mod/Fem/App/FemPostFilter.cpp b/src/Mod/Fem/App/FemPostFilter.cpp index 76116180c2..f808520e54 100644 --- a/src/Mod/Fem/App/FemPostFilter.cpp +++ b/src/Mod/Fem/App/FemPostFilter.cpp @@ -511,7 +511,6 @@ DocumentObjectExecReturn* FemPostContoursFilter::execute() // delete contour field vtkSmartPointer data = getInputData(); vtkDataSet* dset = vtkDataSet::SafeDownCast(data); - vtkPointData* pd = dset->GetPointData(); dset->GetPointData()->RemoveArray(contourFieldName.c_str()); // refresh fields to reflect the deletion if (!m_blockPropertyChanges) @@ -715,7 +714,7 @@ void FemPostContoursFilter::refreshVectors() auto it = std::find(vectorArray.begin(), vectorArray.end(), vectorName); if (!vectorName.empty() && it != vectorArray.end()) VectorMode.setValue(vectorName.c_str()); - + m_blockPropertyChanges = false; } From 2431932ae1421f25ffc4e1ee6ea30e75ce8aaf54 Mon Sep 17 00:00:00 2001 From: Syres916 <46537884+Syres916@users.noreply.github.com> Date: Tue, 14 Feb 2023 11:49:31 +0000 Subject: [PATCH 18/42] [Sketcher] Fix cursor coordinates text size... ...to stay in proportion to the constraint text size. Fixes https://github.com/FreeCAD/FreeCAD/issues/7080 --- src/Mod/Sketcher/Gui/EditModeCoinManager.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Mod/Sketcher/Gui/EditModeCoinManager.cpp b/src/Mod/Sketcher/Gui/EditModeCoinManager.cpp index 6e570cede3..147d45ba30 100644 --- a/src/Mod/Sketcher/Gui/EditModeCoinManager.cpp +++ b/src/Mod/Sketcher/Gui/EditModeCoinManager.cpp @@ -709,6 +709,8 @@ void EditModeCoinManager::createEditModeInventorNodes() float transparency; SbColor cursorTextColor(0,0,1); cursorTextColor.setPackedValue((uint32_t)hGrp->GetUnsigned("CursorTextColor", cursorTextColor.getPackedValue()), transparency); + int defaultFontSizePixels = defaultApplicationFontSizePixels(); // returns height in pixels, not points + int cursorFontSize = hGrp->GetInt("EditSketcherFontSize", defaultFontSizePixels); // stuff for the EditMarkers +++++++++++++++++++++++++++++++++++++++ SoSeparator* editMarkersRoot = new SoSeparator; @@ -746,7 +748,7 @@ void EditModeCoinManager::createEditModeInventorNodes() Coordsep->addChild(CoordTextMaterials); SoFont *font = new SoFont(); - font->size.setValue(drawingParameters.coinFontSize); + font->size.setValue(cursorFontSize); Coordsep->addChild(font); From b461e81dc6e9c339c6c109cd3e115629022ef3f7 Mon Sep 17 00:00:00 2001 From: Roy-043 <70520633+Roy-043@users.noreply.github.com> Date: Tue, 14 Feb 2023 15:44:26 +0100 Subject: [PATCH 19/42] [Draft] typo: object name should be capitalized (#8484) --- src/Mod/Draft/draftfunctions/mirror.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Mod/Draft/draftfunctions/mirror.py b/src/Mod/Draft/draftfunctions/mirror.py index ea00f26e8e..206cd5dce2 100644 --- a/src/Mod/Draft/draftfunctions/mirror.py +++ b/src/Mod/Draft/draftfunctions/mirror.py @@ -109,8 +109,8 @@ def mirror(objlist, p1, p2): result = [] for obj in objlist: - mir = App.ActiveDocument.addObject("Part::Mirroring", "mirror") - mir.Label = obj.Label + " (" + translate("draft","mirrored") + ") " + mir = App.ActiveDocument.addObject("Part::Mirroring", "Mirror") + mir.Label = obj.Label + " (" + translate("draft", "mirrored") + ")" mir.Source = obj mir.Base = p1 mir.Normal = pnorm From 454c580457396aab39b3af9b080577248df7b007 Mon Sep 17 00:00:00 2001 From: wmayer Date: Tue, 14 Feb 2023 14:34:39 +0100 Subject: [PATCH 20/42] Part: creating a NURBS of a circle or ellipse doesn't respect any transformation Example: mat = App.Matrix() mat.rotateX(1) mat.rotateY(1) mat.rotateZ(1) circle = Part.Circle() circle.Radius = 5 circle.transform(mat) circle.transform(mat) Part.show(circle.toShape()) nurbs = circle.toNurbs() Part.show(nurbs.toShape()) arc = circle.trim(0, 2) nurbs = arc.toNurbs() Part.show(nurbs.toShape()) spline = circle.toBSpline() Part.show(spline.toShape()) #------------------------- ellipse = Part.Ellipse() ellipse.MajorRadius = 5 ellipse.MinorRadius = 3 ellipse.transform(mat) ellipse.transform(mat) Part.show(ellipse.toShape()) nurbs = ellipse.toNurbs() Part.show(nurbs.toShape()) arc = ellipse.trim(0, 2) nurbs = arc.toNurbs() Part.show(nurbs.toShape()) spline = ellipse.toBSpline() Part.show(spline.toShape()) --- src/Mod/Part/App/Geometry.cpp | 53 +++++++++++++++-------------------- 1 file changed, 22 insertions(+), 31 deletions(-) diff --git a/src/Mod/Part/App/Geometry.cpp b/src/Mod/Part/App/Geometry.cpp index 99b83b5d4c..1663571d95 100644 --- a/src/Mod/Part/App/Geometry.cpp +++ b/src/Mod/Part/App/Geometry.cpp @@ -2244,29 +2244,23 @@ GeomBSplineCurve* GeomCircle::toNurbs(double first, double last) const return GeomConic::toNurbs(first, last); } - double radius = getRadius(); - Handle(Geom_Conic) conic = Handle(Geom_Conic)::DownCast(handle()); - gp_Ax1 axis = conic->Axis(); - //gp_Dir xdir = conic->XAxis().Direction(); - //Standard_Real angle = gp_Dir(1,0,0).Angle(xdir) + first; - Standard_Real angle = first; - const gp_Pnt& loc = axis.Location(); - //Note: If the matching this way doesn't work reliably then we must compute the - //angle so that the point of the curve for 'first' matches the first pole - //gp_Pnt pnt = conic->Value(first); + Handle(Geom_Circle) conic = Handle(Geom_Circle)::DownCast(handle()); + double radius = conic->Radius(); TColgp_Array1OfPnt poles(1, 7); - poles(1) = loc.Translated(gp_Vec(radius, 0, 0)); - poles(2) = loc.Translated(gp_Vec(radius, 2*radius, 0)); - poles(3) = loc.Translated(gp_Vec(-radius, 2*radius, 0)); - poles(4) = loc.Translated(gp_Vec(-radius, 0, 0)); - poles(5) = loc.Translated(gp_Vec(-radius, -2*radius, 0)); - poles(6) = loc.Translated(gp_Vec(radius, -2*radius, 0)); - poles(7) = loc.Translated(gp_Vec(radius, 0, 0)); + poles(1) = gp_Pnt(radius, 0, 0); + poles(2) = gp_Pnt(radius, 2*radius, 0); + poles(3) = gp_Pnt(-radius, 2*radius, 0); + poles(4) = gp_Pnt(-radius, 0, 0); + poles(5) = gp_Pnt(-radius, -2*radius, 0); + poles(6) = gp_Pnt(radius, -2*radius, 0); + poles(7) = gp_Pnt(radius, 0, 0); + gp_Trsf trsf; + trsf.SetTransformation(conic->Position(), gp_Ax3()); TColStd_Array1OfReal weights(1,7); for (int i=1; i<=7; i++) { - poles(i).Rotate(axis, angle); + poles(i).Transform(trsf); weights(i) = 1; } weights(1) = 3; @@ -2285,7 +2279,6 @@ GeomBSplineCurve* GeomCircle::toNurbs(double first, double last) const Handle(Geom_BSplineCurve) spline = new Geom_BSplineCurve(poles, weights,knots, mults, 3, Standard_False, Standard_True); - spline->Segment(0, last-first); return new GeomBSplineCurve(spline); } @@ -2671,25 +2664,23 @@ GeomBSplineCurve* GeomEllipse::toNurbs(double first, double last) const } Handle(Geom_Ellipse) conic = Handle(Geom_Ellipse)::DownCast(handle()); - gp_Ax1 axis = conic->Axis(); Standard_Real majorRadius = conic->MajorRadius(); Standard_Real minorRadius = conic->MinorRadius(); - gp_Dir xdir = conic->XAxis().Direction(); - Standard_Real angle = atan2(xdir.Y(), xdir.X()); - const gp_Pnt& loc = axis.Location(); TColgp_Array1OfPnt poles(1, 7); - poles(1) = loc.Translated(gp_Vec(majorRadius, 0, 0)); - poles(2) = loc.Translated(gp_Vec(majorRadius, 2*minorRadius, 0)); - poles(3) = loc.Translated(gp_Vec(-majorRadius, 2*minorRadius, 0)); - poles(4) = loc.Translated(gp_Vec(-majorRadius, 0, 0)); - poles(5) = loc.Translated(gp_Vec(-majorRadius, -2*minorRadius, 0)); - poles(6) = loc.Translated(gp_Vec(majorRadius, -2*minorRadius, 0)); - poles(7) = loc.Translated(gp_Vec(majorRadius, 0, 0)); + poles(1) = gp_Pnt(majorRadius, 0, 0); + poles(2) = gp_Pnt(majorRadius, 2*minorRadius, 0); + poles(3) = gp_Pnt(-majorRadius, 2*minorRadius, 0); + poles(4) = gp_Pnt(-majorRadius, 0, 0); + poles(5) = gp_Pnt(-majorRadius, -2*minorRadius, 0); + poles(6) = gp_Pnt(majorRadius, -2*minorRadius, 0); + poles(7) = gp_Pnt(majorRadius, 0, 0); + gp_Trsf trsf; + trsf.SetTransformation(conic->Position(), gp_Ax3()); TColStd_Array1OfReal weights(1,7); for (int i=1; i<=7; i++) { - poles(i).Rotate(axis, angle); + poles(i).Transform(trsf); weights(i) = 1; } weights(1) = 3; From 643a71ae6ecff87933b84df096dad82092e1ceca Mon Sep 17 00:00:00 2001 From: wmayer Date: Tue, 14 Feb 2023 14:53:15 +0100 Subject: [PATCH 21/42] Part: add integration tests --- src/Mod/Part/TestPartApp.py | 42 +++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/Mod/Part/TestPartApp.py b/src/Mod/Part/TestPartApp.py index 1c252fa5cc..14ad04c6bc 100644 --- a/src/Mod/Part/TestPartApp.py +++ b/src/Mod/Part/TestPartApp.py @@ -175,6 +175,48 @@ class PartTestBSplineCurve(unittest.TestCase): #closing doc FreeCAD.closeDocument("PartTest") +class PartTestCurveToNurbs(unittest.TestCase): + def testCircleToNurbs(self): + mat = Base.Matrix() + mat.rotateX(1) + mat.rotateY(1) + mat.rotateZ(1) + + circle = Part.Circle() + circle.Radius = 5 + + circle.transform(mat) + nurbs = circle.toNurbs() + self.assertEqual(circle.value(0), nurbs.value(0)) + + arc = circle.trim(0, 2) + nurbs = arc.toNurbs() + self.assertEqual(circle.value(0), nurbs.value(0)) + + spline = circle.toBSpline() + self.assertAlmostEqual(circle.value(0).distanceToPoint(spline.value(0)), 0) + + def testEllipseToNurbs(self): + mat = Base.Matrix() + mat.rotateX(1) + mat.rotateY(1) + mat.rotateZ(1) + + ellipse = Part.Ellipse() + ellipse.MajorRadius = 5 + ellipse.MinorRadius = 3 + + ellipse.transform(mat) + nurbs = ellipse.toNurbs() + self.assertEqual(ellipse.value(0), nurbs.value(0)) + + arc = ellipse.trim(0, 2) + nurbs = arc.toNurbs() + self.assertEqual(ellipse.value(0), nurbs.value(0)) + + spline = ellipse.toBSpline() + self.assertAlmostEqual(ellipse.value(0).distanceToPoint(spline.value(0)), 0) + class PartTestBSplineSurface(unittest.TestCase): def testTorusToSpline(self): to = Part.Toroid() From 7c691dc7f4c2c9c7aacab4afffd4d8288a0ab170 Mon Sep 17 00:00:00 2001 From: Uwe Date: Wed, 15 Feb 2023 00:32:43 +0100 Subject: [PATCH 22/42] [FEM] [skip ci] add missing header for precompiled headers --- src/Mod/Fem/Gui/PreCompiled.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Mod/Fem/Gui/PreCompiled.h b/src/Mod/Fem/Gui/PreCompiled.h index 74c53d91f2..c4ce783c2e 100644 --- a/src/Mod/Fem/Gui/PreCompiled.h +++ b/src/Mod/Fem/Gui/PreCompiled.h @@ -171,10 +171,11 @@ #include // VTK -#include #include #include +#include #include +#include #endif //_PreComp_ From 7a0acd1db629b36b782f14255c3b148a3e76642e Mon Sep 17 00:00:00 2001 From: luzpaz Date: Tue, 14 Feb 2023 19:04:27 -0500 Subject: [PATCH 23/42] FEM: fix trailing whitespace Fix trailing whitespace in `Fem/App/FemPostFilter.cpp` --- src/Mod/Fem/App/FemPostFilter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Fem/App/FemPostFilter.cpp b/src/Mod/Fem/App/FemPostFilter.cpp index f808520e54..3cce6ff056 100644 --- a/src/Mod/Fem/App/FemPostFilter.cpp +++ b/src/Mod/Fem/App/FemPostFilter.cpp @@ -714,7 +714,7 @@ void FemPostContoursFilter::refreshVectors() auto it = std::find(vectorArray.begin(), vectorArray.end(), vectorName); if (!vectorName.empty() && it != vectorArray.end()) VectorMode.setValue(vectorName.c_str()); - + m_blockPropertyChanges = false; } From 05af64ae9e8f97fc29dce1897286c40eb2dbcb79 Mon Sep 17 00:00:00 2001 From: Uwe Date: Wed, 15 Feb 2023 00:34:56 +0100 Subject: [PATCH 24/42] [FEM] improve checks for datasets - check if datasets exists before we access them - speed up the check for the datatype by testing after the downcast --- src/Mod/Fem/App/FemPostFilter.cpp | 34 ++++++++++--------- src/Mod/Fem/App/FemPostObject.cpp | 5 +-- src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp | 2 ++ .../Fem/Gui/ViewProviderFemPostPipeline.cpp | 5 ++- 4 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/Mod/Fem/App/FemPostFilter.cpp b/src/Mod/Fem/App/FemPostFilter.cpp index 3cce6ff056..19b5a6abe1 100644 --- a/src/Mod/Fem/App/FemPostFilter.cpp +++ b/src/Mod/Fem/App/FemPostFilter.cpp @@ -239,6 +239,8 @@ void FemPostDataAlongLineFilter::GetAxisData() vtkSmartPointer data = m_probe->GetOutputDataObject(0); vtkDataSet* dset = vtkDataSet::SafeDownCast(data); + if (!dset) + return; vtkDataArray* pdata = dset->GetPointData()->GetArray(PlotData.getValue()); // VTK cannot deliver data when the filer relies e.g. on a scalar clip filter // whose value is set so that all data are clipped @@ -357,6 +359,8 @@ void FemPostDataAtPointFilter::GetPointData() vtkSmartPointer data = m_probe->GetOutputDataObject(0); vtkDataSet* dset = vtkDataSet::SafeDownCast(data); + if (!dset) + return; vtkDataArray* pdata = dset->GetPointData()->GetArray(FieldName.getValue()); // VTK cannot deliver data when the filer relies e.g. on a scalar clip filter // whose value is set so that all data are clipped @@ -511,6 +515,8 @@ DocumentObjectExecReturn* FemPostContoursFilter::execute() // delete contour field vtkSmartPointer data = getInputData(); vtkDataSet* dset = vtkDataSet::SafeDownCast(data); + if (!dset) + return returnObject; dset->GetPointData()->RemoveArray(contourFieldName.c_str()); // refresh fields to reflect the deletion if (!m_blockPropertyChanges) @@ -536,11 +542,10 @@ void FemPostContoursFilter::onChanged(const Property* prop) // get the field and its data vtkSmartPointer data = getInputData(); - if (!data || !data->IsA("vtkDataSet")) - return; vtkDataSet* dset = vtkDataSet::SafeDownCast(data); + if (!dset) + return; vtkDataArray* pdata = dset->GetPointData()->GetArray(Field.getValueAsString()); - if (!pdata) return; if (pdata->GetNumberOfComponents() == 1) { @@ -637,11 +642,11 @@ void FemPostContoursFilter::refreshFields() std::vector FieldsArray; vtkSmartPointer data = getInputData(); - if (!data || !data->IsA("vtkDataSet")) { + vtkDataSet* dset = vtkDataSet::SafeDownCast(data); + if (!dset) { m_blockPropertyChanges = false; return; } - vtkDataSet* dset = vtkDataSet::SafeDownCast(data); vtkPointData* pd = dset->GetPointData(); // get all fields @@ -676,11 +681,11 @@ void FemPostContoursFilter::refreshVectors() m_blockPropertyChanges = true; vtkSmartPointer data = getInputData(); - if (!data || !data->IsA("vtkDataSet")) { + vtkDataSet* dset = vtkDataSet::SafeDownCast(data); + if (!dset) { m_blockPropertyChanges = false; return; } - vtkDataSet* dset = vtkDataSet::SafeDownCast(data); vtkDataArray* fieldArray = dset->GetPointData()->GetArray(Field.getValueAsString()); if (!fieldArray) { m_blockPropertyChanges = false; @@ -807,10 +812,9 @@ DocumentObjectExecReturn* FemPostScalarClipFilter::execute() std::vector ScalarsArray; vtkSmartPointer data = getInputData(); - if (!data || !data->IsA("vtkDataSet")) - return StdReturn; - vtkDataSet* dset = vtkDataSet::SafeDownCast(data); + if (!dset) + return StdReturn; vtkPointData* pd = dset->GetPointData(); // get all scalar fields @@ -864,10 +868,9 @@ short int FemPostScalarClipFilter::mustExecute() const void FemPostScalarClipFilter::setConstraintForField() { vtkSmartPointer data = getInputData(); - if (!data || !data->IsA("vtkDataSet")) - return; - vtkDataSet* dset = vtkDataSet::SafeDownCast(data); + if (!dset) + return; vtkDataArray* pdata = dset->GetPointData()->GetArray(Scalars.getValueAsString()); // VTK cannot deliver data when the filer relies e.g. on a cut clip filter @@ -916,10 +919,9 @@ DocumentObjectExecReturn* FemPostWarpVectorFilter::execute() std::vector VectorArray; vtkSmartPointer data = getInputData(); - if (!data || !data->IsA("vtkDataSet")) - return StdReturn; - vtkDataSet* dset = vtkDataSet::SafeDownCast(data); + if (!dset) + return StdReturn; vtkPointData* pd = dset->GetPointData(); // get all vector fields diff --git a/src/Mod/Fem/App/FemPostObject.cpp b/src/Mod/Fem/App/FemPostObject.cpp index 17d40d48db..3ad500a685 100644 --- a/src/Mod/Fem/App/FemPostObject.cpp +++ b/src/Mod/Fem/App/FemPostObject.cpp @@ -48,8 +48,9 @@ vtkBoundingBox FemPostObject::getBoundingBox() { vtkBoundingBox box; - if (Data.getValue() && Data.getValue()->IsA("vtkDataSet")) - box.AddBounds(vtkDataSet::SafeDownCast(Data.getValue())->GetBounds()); + vtkDataSet* dset = vtkDataSet::SafeDownCast(Data.getValue()); + if (dset) + box.AddBounds(dset->GetBounds()); // TODO: add calculation of multiblock and Multipiece datasets diff --git a/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp b/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp index 9b9ebfdc1b..9e9a164974 100644 --- a/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp +++ b/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp @@ -634,6 +634,8 @@ bool ViewProviderFemPostObject::setupPipeline() // add a field with an absolute value vtkSmartPointer SPdata = data; vtkDataSet* dset = vtkDataSet::SafeDownCast(SPdata); + if (!dset) + return false; std::string FieldName; auto numFields = dset->GetPointData()->GetNumberOfArrays(); for (int i = 0; i < numFields; ++i) { diff --git a/src/Mod/Fem/Gui/ViewProviderFemPostPipeline.cpp b/src/Mod/Fem/Gui/ViewProviderFemPostPipeline.cpp index b59aa56c42..a3e3c0d9e6 100644 --- a/src/Mod/Fem/Gui/ViewProviderFemPostPipeline.cpp +++ b/src/Mod/Fem/Gui/ViewProviderFemPostPipeline.cpp @@ -155,10 +155,9 @@ void ViewProviderFemPostPipeline::transformField(char *FieldName, double FieldFa Fem::FemPostPipeline *obj = static_cast(getObject()); vtkSmartPointer data = obj->Data.getValue(); - if (!data || !data->IsA("vtkDataSet")) - return; - vtkDataSet *dset = vtkDataSet::SafeDownCast(data); + if (!dset) + return; vtkDataArray *pdata = dset->GetPointData()->GetArray(FieldName); if (!pdata) return; From 5f07b4999de32c8d496e1d7286dbce052f5dfcbe Mon Sep 17 00:00:00 2001 From: luzpaz Date: Wed, 15 Feb 2023 13:13:29 +0000 Subject: [PATCH 25/42] Draft: fix typos --- src/Mod/Draft/draftmake/make_dimension.py | 4 ++-- src/Mod/Draft/draftutils/init_draft_statusbar.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Mod/Draft/draftmake/make_dimension.py b/src/Mod/Draft/draftmake/make_dimension.py index 1bc3d90fda..aecd4b48c5 100644 --- a/src/Mod/Draft/draftmake/make_dimension.py +++ b/src/Mod/Draft/draftmake/make_dimension.py @@ -195,7 +195,7 @@ def make_linear_dimension(p1, p2, dim_line=None): positioned from the measured segment that goes from `p1` to `p2`. If it is `None`, this point will be calculated from the intermediate - distance betwwen `p1` and `p2`. + distance between `p1` and `p2`. Returns ------- @@ -288,7 +288,7 @@ def make_linear_dimension_obj(edge_object, i1=1, i2=2, dim_line=None): positioned from the measured segment in `edge_object`. If it is `None`, this point will be calculated from the intermediate - distance betwwen the vertices defined by `i1` and `i2`. + distance between the vertices defined by `i1` and `i2`. Returns ------- diff --git a/src/Mod/Draft/draftutils/init_draft_statusbar.py b/src/Mod/Draft/draftutils/init_draft_statusbar.py index 5f34ba4329..f2c77a7c44 100644 --- a/src/Mod/Draft/draftutils/init_draft_statusbar.py +++ b/src/Mod/Draft/draftutils/init_draft_statusbar.py @@ -67,7 +67,7 @@ draft_scales_eng_imperial = ["1in=10ft", "1in=20ft", "1in=30ft", def get_scales(unit_system = 0): """ - returns the list of preset scales accordin to unit system. + returns the list of preset scales according to unit system. Parameters: unit_system = 0 : default from user preferences From 92b1e6a6fbb5e871b244799334b25c1b5ae9e466 Mon Sep 17 00:00:00 2001 From: Uwe Date: Tue, 14 Feb 2023 01:05:39 +0100 Subject: [PATCH 26/42] [FEM] improve visualization of pvtu files - To speed up analyses one calculates on several CPU cores. The result is a partial VTU file. Unfortunately when applying a transparency the boundaries of the volumes computed by each CPU core are always visible. This makes it often unusable for visualizations. The solution is to filter the results the same way a clip filter does. --- src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp | 71 +++++++++++++++++-- src/Mod/Fem/Gui/ViewProviderFemPostObject.h | 2 + 2 files changed, 68 insertions(+), 5 deletions(-) diff --git a/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp b/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp index 9e9a164974..ed4c204ef5 100644 --- a/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp +++ b/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp @@ -38,6 +38,7 @@ # include # include # include +# include # include # include @@ -55,6 +56,8 @@ #include #include #include +#include +#include #include #include "ViewProviderFemPostObject.h" @@ -265,8 +268,8 @@ void ViewProviderFemPostObject::attach(App::DocumentObject* pcObj) (void)setupPipeline(); } -SoSeparator* ViewProviderFemPostObject::getFrontRoot() const { - +SoSeparator* ViewProviderFemPostObject::getFrontRoot() const +{ return m_colorRoot; } @@ -623,10 +626,65 @@ void ViewProviderFemPostObject::updateData(const App::Property* p) { } } -bool ViewProviderFemPostObject::setupPipeline() +void ViewProviderFemPostObject::filterArtifacts(vtkDataObject* data) +{ + // The problem is that in the surface view the boundary reagions of the volumess + // calculated by the different CPU cores is always visible, independent of the + // transparency setting. Elmer is not to blame, this is just a property of the + // partial VTK file reader. So this can happen with various input + // since FreeCAD can also be used to view VTK files without the need to perform + // an analysis. Therefore it is impossible to know in advance when to filter + // or not. + // Only for pure CCX analyses we know that no filtering is necessary. + // However, the effort to catch this case is not worth it since the filtering + // is only as time-consuming as enabling the surface filter. In fact, it is like + // performing the surface flter twice. + + // We need to set the filter clipping plane below the z minimum of the data. + // We can either do this by checkting the VTK data or by getting the info from + // the 3D view. We use here the latter because this much faster. + + // since we will set the filter according to the visible bounding box + // assure the object is visible + bool visibility = this->Visibility.getValue(); + this->Visibility.setValue(false); + + Gui::Document* doc = this->getDocument(); + Gui::View3DInventor* view = + qobject_cast(doc->getViewOfViewProvider(this)); + if (view) { + Gui::View3DInventorViewer* viewer = view->getViewer(); + SbBox3f boundingBox; + boundingBox = viewer->getBoundingBox(); + if (boundingBox.hasVolume()) { + // setup + vtkSmartPointer m_implicit; + auto m_plane = vtkSmartPointer::New(); + m_implicit = m_plane; + m_plane->SetNormal(0., 0., 1.); + auto extractor = vtkSmartPointer::New(); + float dx, dy, dz; + boundingBox.getSize(dx, dy, dz); + // set plane slightly below the minimum to assure there are + // no boundary cells (touching the function + m_plane->SetOrigin(0., 0., -1 * dz - 1); + extractor->SetClipFunction(m_implicit); + extractor->SetInputData(data); + extractor->Update(); + m_surface->SetInputData(extractor->GetOutputDataObject(0)); + } + else { + // for e.g. DataAtPoint filter + m_surface->SetInputData(data); + } + } + // restore initial vsibility + this->Visibility.setValue(visibility); +} + + bool ViewProviderFemPostObject::setupPipeline() { vtkDataObject* data = static_cast(getObject())->Data.getValue(); - if (!data) return false; @@ -644,10 +702,13 @@ bool ViewProviderFemPostObject::setupPipeline() } m_outline->SetInputData(data); - m_surface->SetInputData(data); m_wireframe->SetInputData(data); m_points->SetInputData(data); + // filter artifacts + // only necessary for the surface filter + filterArtifacts(data); + return true; } diff --git a/src/Mod/Fem/Gui/ViewProviderFemPostObject.h b/src/Mod/Fem/Gui/ViewProviderFemPostObject.h index fce127a5c5..8a5d7f0bc9 100644 --- a/src/Mod/Fem/Gui/ViewProviderFemPostObject.h +++ b/src/Mod/Fem/Gui/ViewProviderFemPostObject.h @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -150,6 +151,7 @@ protected: vtkSmartPointer m_points, m_pointsSurface; private: + void filterArtifacts(vtkDataObject* data); void updateProperties(); void update3D(); void WritePointData(vtkPoints *points, vtkDataArray *normals, From a0731a0d8647f877de7a77cb41cb1a4e55db68f5 Mon Sep 17 00:00:00 2001 From: Uwe Date: Wed, 15 Feb 2023 14:22:01 +0100 Subject: [PATCH 27/42] [FEM] Contours: disable keyboard tracking - otherwise the contour creating algorithm would be invoked on every keystroke - also improve a code comment --- src/Mod/Fem/Gui/TaskPostContours.ui | 3 +++ src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp | 24 +++++++++---------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/Mod/Fem/Gui/TaskPostContours.ui b/src/Mod/Fem/Gui/TaskPostContours.ui index f6fba0688e..9e62a3466e 100644 --- a/src/Mod/Fem/Gui/TaskPostContours.ui +++ b/src/Mod/Fem/Gui/TaskPostContours.ui @@ -60,6 +60,9 @@ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + false + 1 diff --git a/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp b/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp index ed4c204ef5..7cd58a0cd0 100644 --- a/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp +++ b/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp @@ -628,21 +628,21 @@ void ViewProviderFemPostObject::updateData(const App::Property* p) { void ViewProviderFemPostObject::filterArtifacts(vtkDataObject* data) { - // The problem is that in the surface view the boundary reagions of the volumess + // The problem is that in the surface view the boundary regions of the volumes // calculated by the different CPU cores is always visible, independent of the - // transparency setting. Elmer is not to blame, this is just a property of the - // partial VTK file reader. So this can happen with various input + // transparency setting. Elmer is not to blame because this is a property of the + // partial VTK file reader. So this can happen with various inputs // since FreeCAD can also be used to view VTK files without the need to perform - // an analysis. Therefore it is impossible to know in advance when to filter - // or not. - // Only for pure CCX analyses we know that no filtering is necessary. - // However, the effort to catch this case is not worth it since the filtering - // is only as time-consuming as enabling the surface filter. In fact, it is like - // performing the surface flter twice. + // an analysis. Therefore it is impossible to know in advance when a filter + // is necessary or not. + // Only for pure CCX analyses we know that no filtering is necessary. However, + // the effort to catch this case is not worth it since the filtering is + // only as time-consuming as enabling the surface filter. In fact, it is like + // performing the surface filter twice. - // We need to set the filter clipping plane below the z minimum of the data. - // We can either do this by checkting the VTK data or by getting the info from - // the 3D view. We use here the latter because this much faster. + // We need to set the filter clipping plane below the z-minimum of the data. + // We can either do this by checking the VTK data or by getting the info from + // the 3D view. We use here the latter because this is much faster. // since we will set the filter according to the visible bounding box // assure the object is visible From 61e96bd763c65a9aa941800cd75ff077c29eb8ee Mon Sep 17 00:00:00 2001 From: luzpaz Date: Wed, 15 Feb 2023 13:41:38 +0000 Subject: [PATCH 28/42] FEM: fix trailing newlines --- src/Mod/Fem/Gui/Command.cpp | 4 ++-- src/Mod/Fem/Gui/TaskPostBoxes.cpp | 2 +- src/Mod/Fem/Gui/TaskPostBoxes.h | 2 +- src/Mod/Fem/Gui/ViewProviderFemPostFilter.cpp | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Mod/Fem/Gui/Command.cpp b/src/Mod/Fem/Gui/Command.cpp index 45e572ea91..9a71678678 100644 --- a/src/Mod/Fem/Gui/Command.cpp +++ b/src/Mod/Fem/Gui/Command.cpp @@ -2019,7 +2019,7 @@ bool CmdFemPostWarpVectorFilter::isActive() return false; // only activate if a result is either a post pipeline or a possible other filter if (getSelection().getObjectsOfType().size() == 1) - return true; + return true; else if (getSelection().getObjectsOfType().size() == 1) return true; else if (getSelection().getObjectsOfType().size() == 1) @@ -2432,6 +2432,6 @@ void CreateFemCommands() rcCmdMgr.addCommand(new CmdFemPostFunctions); rcCmdMgr.addCommand(new CmdFemPostPipelineFromResult); rcCmdMgr.addCommand(new CmdFemPostScalarClipFilter); - rcCmdMgr.addCommand(new CmdFemPostWarpVectorFilter); + rcCmdMgr.addCommand(new CmdFemPostWarpVectorFilter); #endif } diff --git a/src/Mod/Fem/Gui/TaskPostBoxes.cpp b/src/Mod/Fem/Gui/TaskPostBoxes.cpp index fe24296f87..621e8b324b 100644 --- a/src/Mod/Fem/Gui/TaskPostBoxes.cpp +++ b/src/Mod/Fem/Gui/TaskPostBoxes.cpp @@ -1381,7 +1381,7 @@ void TaskPostContours::updateFields() else { getTypedView()->Field.setValue("None"); } - + } void TaskPostContours::onFieldsChanged(int idx) diff --git a/src/Mod/Fem/Gui/TaskPostBoxes.h b/src/Mod/Fem/Gui/TaskPostBoxes.h index 4f1fa1a23c..d046975a77 100644 --- a/src/Mod/Fem/Gui/TaskPostBoxes.h +++ b/src/Mod/Fem/Gui/TaskPostBoxes.h @@ -333,7 +333,7 @@ private: // *************************************************************************** // clip filter -class TaskPostClip : public TaskPostBox +class TaskPostClip : public TaskPostBox { Q_OBJECT diff --git a/src/Mod/Fem/Gui/ViewProviderFemPostFilter.cpp b/src/Mod/Fem/Gui/ViewProviderFemPostFilter.cpp index c991c80253..a8803ee909 100644 --- a/src/Mod/Fem/Gui/ViewProviderFemPostFilter.cpp +++ b/src/Mod/Fem/Gui/ViewProviderFemPostFilter.cpp @@ -155,7 +155,7 @@ void ViewProviderFemPostCut::setupTaskDialog(TaskDlgPost* dlg) // scalar clip filter PROPERTY_SOURCE(FemGui::ViewProviderFemPostScalarClip, FemGui::ViewProviderFemPostObject) -ViewProviderFemPostScalarClip::ViewProviderFemPostScalarClip() +ViewProviderFemPostScalarClip::ViewProviderFemPostScalarClip() { sPixmap = "FEM_PostFilterClipScalar"; } From 8f32631d34ebade67813f442da25b01565bc6cae Mon Sep 17 00:00:00 2001 From: wmayer Date: Wed, 15 Feb 2023 14:21:59 +0100 Subject: [PATCH 29/42] Fem: fixes #8485: transparency of pipeline objects is visually lost --- src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp b/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp index 7cd58a0cd0..7f664962ff 100644 --- a/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp +++ b/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp @@ -549,7 +549,8 @@ void ViewProviderFemPostObject::WriteColorData(bool ResetColorBarRange) { if (Field.getEnumVector().empty() || Field.getValue() == 0) { m_material->diffuseColor.setValue(SbColor(0.8, 0.8, 0.8)); - m_material->transparency.setValue(0.); + float trans = float(Transparency.getValue()) / 100.0; + m_material->transparency.setValue(trans); m_materialBinding->value = SoMaterialBinding::OVERALL; m_materialBinding->touch(); // since there is no field, set the range to the default From 194fbf1efe715ef7c9c82593accd96ce3513ae8c Mon Sep 17 00:00:00 2001 From: Abdullah Tahiri Date: Wed, 15 Feb 2023 15:02:18 +0100 Subject: [PATCH 30/42] Sketcher: Correct font sizes and updates ======================================== Fonts: - SoDatumLabel internally uses QFont and thus points. It was being fed pixels, for which the configured size in preferences did not match the actual on screen size. This is corrected. - Font sizes fed directly from parameters to the coin nodes skeleton were not being updated, as the parameter observer is initialised after the nodes are created, and the parameter observer was not configured to update this nodes. This is corrected. Colors: - Some color parameters were not being being updated by the parameter observer. This is corrected. --- src/Mod/Sketcher/Gui/EditModeCoinManager.cpp | 48 +++++++++++-------- src/Mod/Sketcher/Gui/EditModeCoinManager.h | 2 + .../Gui/EditModeCoinManagerParameters.cpp | 1 + .../Gui/EditModeCoinManagerParameters.h | 6 +++ .../Gui/EditModeConstraintCoinManager.cpp | 2 +- ...ditModeInformationOverlayCoinConverter.cpp | 2 +- 6 files changed, 39 insertions(+), 22 deletions(-) diff --git a/src/Mod/Sketcher/Gui/EditModeCoinManager.cpp b/src/Mod/Sketcher/Gui/EditModeCoinManager.cpp index 147d45ba30..9abd818da6 100644 --- a/src/Mod/Sketcher/Gui/EditModeCoinManager.cpp +++ b/src/Mod/Sketcher/Gui/EditModeCoinManager.cpp @@ -148,6 +148,8 @@ void EditModeCoinManager::ParameterObserver::initParameters() [this, drawingParameters = Client.drawingParameters](const std::string & param){updateColor(drawingParameters.PreselectColor, param);}}, {"SelectionColor", [this, drawingParameters = Client.drawingParameters](const std::string & param){updateColor(drawingParameters.SelectColor, param);}}, + {"CursorTextColor", + [this, drawingParameters = Client.drawingParameters](const std::string & param){updateColor(drawingParameters.CursorTextColor, param);}}, }; for( auto & val : str2updatefunction){ @@ -230,12 +232,12 @@ void EditModeCoinManager::ParameterObserver::updateElementSizeParameters(const s // simple scaling factor for hardcoded pixel values in the Sketcher Client.drawingParameters.pixelScalingFactor = viewScalingFactor * dpi / 96; // 96 ppi is the standard pixel density for which pixel quantities were calculated - // Coin documentation indicates the size of a font is: - // SoSFFloat SoFont::size Size of font. Defaults to 10.0. + // About sizes: + // SoDatumLabel takes the size in points, not in pixels. This is because it uses QFont internally. + // Coin, at least our coin at this time, takes pixels, not points. // - // For 2D rendered bitmap fonts (like for SoText2), this value is the height of a character in screen pixels. For 3D text, this value is the world-space coordinates height of a character in the current units setting (see documentation for SoUnits node). - // - // However, with hdpi monitors, the coin font labels do not respect the size passed in pixels: + // DPI considerations: + // With hdpi monitors, the coin font labels do not respect the size passed in pixels: // https://forum.freecadweb.org/viewtopic.php?f=3&t=54347&p=467610#p467610 // https://forum.freecadweb.org/viewtopic.php?f=10&t=49972&start=40#p467471 // @@ -247,7 +249,8 @@ void EditModeCoinManager::ParameterObserver::updateElementSizeParameters(const s // This means that the following correction does not have a documented basis, but appears necessary so that the Sketcher is usable in // HDPI monitors. - Client.drawingParameters.coinFontSize = std::lround(sketcherfontSize * 96.0f / dpi); + Client.drawingParameters.coinFontSize = std::lround(sketcherfontSize * 96.0f / dpi); // this is in pixels + Client.drawingParameters.labelFontSize = std::lround(sketcherfontSize * 72.0f / dpi); // this is in points, as SoDatumLabel uses points Client.drawingParameters.constraintIconSize = std::lround(0.8 * sketcherfontSize); // For marker size the global default is used. @@ -269,6 +272,8 @@ void EditModeCoinManager::ParameterObserver::updateColor(SbColor &sbcolor, const unsigned long color = (unsigned long)(sbcolor.getPackedValue()); color = hGrp->GetUnsigned(parametername.c_str(), color); sbcolor.setPackedValue((uint32_t)color, transparency); + + Client.updateInventorColors(); } void EditModeCoinManager::ParameterObserver::subscribeToParameters() @@ -705,13 +710,6 @@ void EditModeCoinManager::createEditModeInventorNodes() editModeScenegraphNodes.EditCurveSet->setName("EditCurveLineSet"); editCurvesRoot->addChild(editModeScenegraphNodes.EditCurveSet); - ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/View"); - float transparency; - SbColor cursorTextColor(0,0,1); - cursorTextColor.setPackedValue((uint32_t)hGrp->GetUnsigned("CursorTextColor", cursorTextColor.getPackedValue()), transparency); - int defaultFontSizePixels = defaultApplicationFontSizePixels(); // returns height in pixels, not points - int cursorFontSize = hGrp->GetInt("EditSketcherFontSize", defaultFontSizePixels); - // stuff for the EditMarkers +++++++++++++++++++++++++++++++++++++++ SoSeparator* editMarkersRoot = new SoSeparator; editModeScenegraphNodes.EditRoot->addChild(editMarkersRoot); @@ -742,15 +740,16 @@ void EditModeCoinManager::createEditModeInventorNodes() // no caching for frequently-changing data structures Coordsep->renderCaching = SoSeparator::OFF; - SoMaterial *CoordTextMaterials = new SoMaterial; - CoordTextMaterials->setName("CoordTextMaterials"); - CoordTextMaterials->diffuseColor = cursorTextColor; - Coordsep->addChild(CoordTextMaterials); + editModeScenegraphNodes.textMaterial = new SoMaterial; + editModeScenegraphNodes.textMaterial->setName("CoordTextMaterials"); + editModeScenegraphNodes.textMaterial->diffuseColor = drawingParameters.CursorTextColor; + Coordsep->addChild(editModeScenegraphNodes.textMaterial); - SoFont *font = new SoFont(); - font->size.setValue(cursorFontSize); + editModeScenegraphNodes.textFont = new SoFont(); + editModeScenegraphNodes.textFont->name.setValue("Helvetica"); + editModeScenegraphNodes.textFont->size.setValue(drawingParameters.coinFontSize); - Coordsep->addChild(font); + Coordsep->addChild(editModeScenegraphNodes.textFont); editModeScenegraphNodes.textPos = new SoTranslation(); Coordsep->addChild(editModeScenegraphNodes.textPos); @@ -830,9 +829,18 @@ void EditModeCoinManager::updateInventorNodeSizes() editModeScenegraphNodes.ConstraintDrawStyle->lineWidth = 1 * drawingParameters.pixelScalingFactor; editModeScenegraphNodes.InformationDrawStyle->lineWidth = 1 * drawingParameters.pixelScalingFactor; + editModeScenegraphNodes.textFont->size.setValue(drawingParameters.coinFontSize); + pEditModeConstraintCoinManager->rebuildConstraintNodes(); } +void EditModeCoinManager::updateInventorColors() +{ + editModeScenegraphNodes.RootCrossMaterials->diffuseColor.set1Value(0, drawingParameters.CrossColorH); + editModeScenegraphNodes.RootCrossMaterials->diffuseColor.set1Value(1, drawingParameters.CrossColorV); + editModeScenegraphNodes.textMaterial->diffuseColor = drawingParameters.CursorTextColor; +} + /************************ Edit node access ************************/ SoSeparator* EditModeCoinManager::getRootEditNode() diff --git a/src/Mod/Sketcher/Gui/EditModeCoinManager.h b/src/Mod/Sketcher/Gui/EditModeCoinManager.h index a032847ebb..beb5659e49 100644 --- a/src/Mod/Sketcher/Gui/EditModeCoinManager.h +++ b/src/Mod/Sketcher/Gui/EditModeCoinManager.h @@ -262,6 +262,8 @@ private: void updateInventorNodeSizes(); + void updateInventorColors(); + /** @name coin nodes creation*/ void createEditModeInventorNodes(); //@} diff --git a/src/Mod/Sketcher/Gui/EditModeCoinManagerParameters.cpp b/src/Mod/Sketcher/Gui/EditModeCoinManagerParameters.cpp index f6cf090c9e..207274c138 100644 --- a/src/Mod/Sketcher/Gui/EditModeCoinManagerParameters.cpp +++ b/src/Mod/Sketcher/Gui/EditModeCoinManagerParameters.cpp @@ -51,5 +51,6 @@ SbColor DrawingParameters::ConstrIcoColor (1.0f, 0.14 SbColor DrawingParameters::NonDrivingConstrDimColor (0.0f, 0.149f, 1.0f); // #0026FF -> ( 0, 38,255) SbColor DrawingParameters::ExprBasedConstrDimColor (1.0f, 0.5f, 0.149f); // #FF7F26 -> (255, 127,38) SbColor DrawingParameters::DeactivatedConstrDimColor (0.8f, 0.8f, 0.8f); // #CCCCCC -> (204,204,204) +SbColor DrawingParameters::CursorTextColor (0.0f, 0.0f, 1.0f); // #0000FF -> (0,0,255) const MultiFieldId MultiFieldId::Invalid = MultiFieldId(); diff --git a/src/Mod/Sketcher/Gui/EditModeCoinManagerParameters.h b/src/Mod/Sketcher/Gui/EditModeCoinManagerParameters.h index 5ff0a18aee..9e4781a9d5 100644 --- a/src/Mod/Sketcher/Gui/EditModeCoinManagerParameters.h +++ b/src/Mod/Sketcher/Gui/EditModeCoinManagerParameters.h @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -39,6 +40,7 @@ #include #include + #include #include @@ -112,12 +114,14 @@ struct DrawingParameters { static SbColor NonDrivingConstrDimColor; // Color used for non-driving (reference) dimensional constraints static SbColor ExprBasedConstrDimColor; // Color used for expression based dimensional constraints static SbColor DeactivatedConstrDimColor; // Color used for deactivated dimensional constraints + static SbColor CursorTextColor; // Color used by the edit mode cursor //@} /** @name Rendering sizes (also to support HDPI monitors) **/ //@{ double pixelScalingFactor = 1.0; // Scaling factor to be used for pixels int coinFontSize = 17; // Font size to be used by coin + int labelFontSize = 17; // Font size to be used by SoDatumLabel, which uses a QPainter and a QFont internally int constraintIconSize = 15; // Size of constraint icons int markerSize = 7; // Size used for markers //@} @@ -326,6 +330,8 @@ struct EditModeScenegraphNodes { //@{ SoText2 *textX; SoTranslation *textPos; + SoFont *textFont; + SoMaterial *textMaterial; //@} /** @name Constraint nodes*/ diff --git a/src/Mod/Sketcher/Gui/EditModeConstraintCoinManager.cpp b/src/Mod/Sketcher/Gui/EditModeConstraintCoinManager.cpp index eaceec9db7..a56404a6a1 100644 --- a/src/Mod/Sketcher/Gui/EditModeConstraintCoinManager.cpp +++ b/src/Mod/Sketcher/Gui/EditModeConstraintCoinManager.cpp @@ -1440,7 +1440,7 @@ void EditModeConstraintCoinManager::rebuildConstraintNodes(const GeoListFacade & drawingParameters.ConstrDimColor :drawingParameters.NonDrivingConstrDimColor) :drawingParameters.DeactivatedConstrDimColor; - text->size.setValue(drawingParameters.coinFontSize); + text->size.setValue(drawingParameters.labelFontSize); text->lineWidth = 2 * drawingParameters.pixelScalingFactor; text->useAntialiasing = false; SoAnnotation *anno = new SoAnnotation(); diff --git a/src/Mod/Sketcher/Gui/EditModeInformationOverlayCoinConverter.cpp b/src/Mod/Sketcher/Gui/EditModeInformationOverlayCoinConverter.cpp index 52446f6cb1..3fd78714b9 100644 --- a/src/Mod/Sketcher/Gui/EditModeInformationOverlayCoinConverter.cpp +++ b/src/Mod/Sketcher/Gui/EditModeInformationOverlayCoinConverter.cpp @@ -365,9 +365,9 @@ void EditModeInformationOverlayCoinConverter::addNode(const Result & result) { else setText(result.strings[i], text); - sep->addChild(translate); sep->addChild(mat); sep->addChild(font); + sep->addChild(translate); sep->addChild(text); sw->addChild(sep); From ca946aabcb3b7c8c3ccbd9ae5f9b85c461f4cc3a Mon Sep 17 00:00:00 2001 From: wmayer Date: Wed, 15 Feb 2023 18:05:18 +0100 Subject: [PATCH 31/42] Gui: [skip ci] set object name to tabbar of MDI area --- src/Gui/MainWindow.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Gui/MainWindow.cpp b/src/Gui/MainWindow.cpp index 2815d9c2ea..37c1a97832 100644 --- a/src/Gui/MainWindow.cpp +++ b/src/Gui/MainWindow.cpp @@ -280,6 +280,7 @@ MainWindow::MainWindow(QWidget * parent, Qt::WindowFlags f) tab->setTabsClosable(true); // The tabs might be very wide tab->setExpanding(false); + tab->setObjectName(QString::fromLatin1("mdiAreaTabBar")); } d->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); From 498e774e681aff5a7430c5a276ab04a0353c673e Mon Sep 17 00:00:00 2001 From: wmayer Date: Wed, 15 Feb 2023 23:58:13 +0100 Subject: [PATCH 32/42] Gui: fix removing item from recent files list --- src/Gui/Action.cpp | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/Gui/Action.cpp b/src/Gui/Action.cpp index a43253c775..df84585d76 100644 --- a/src/Gui/Action.cpp +++ b/src/Gui/Action.cpp @@ -918,6 +918,22 @@ public: } } + void trySaveUserParameter() + { + // update the XML structure and save the user parameter to disk (#0001989) + bool saveParameter = App::GetApplication().GetParameterGroupByPath + ("User parameter:BaseApp/Preferences/General")->GetBool("SaveUserParameter", true); + if (saveParameter) { + saveUserParameter(); + } + } + + void saveUserParameter() + { + ParameterManager* parmgr = App::GetApplication().GetParameterSet("User parameter"); + parmgr->SaveDocument(App::Application::Config()["UserParameter"].c_str()); + } + public: RecentFilesAction *master; ParameterGrp::handle handle; @@ -954,13 +970,7 @@ void RecentFilesAction::appendFile(const QString& filename) setFiles(files); save(); - // update the XML structure and save the user parameter to disk (#0001989) - bool saveParameter = App::GetApplication().GetParameterGroupByPath - ("User parameter:BaseApp/Preferences/General")->GetBool("SaveUserParameter", true); - if (saveParameter) { - ParameterManager* parmgr = App::GetApplication().GetParameterSet("User parameter"); - parmgr->SaveDocument(App::Application::Config()["UserParameter"].c_str()); - } + _pimpl->trySaveUserParameter(); } /** @@ -1022,6 +1032,7 @@ void RecentFilesAction::activateFile(int id) QMessageBox::critical(getMainWindow(), tr("File not found"), tr("The file '%1' cannot be opened.").arg(filename)); files.removeAll(filename); setFiles(files); + save(); } else { // invokes appendFile() From efdae63ef1147a23d997784fc731da4264254498 Mon Sep 17 00:00:00 2001 From: Roy-043 Date: Thu, 16 Feb 2023 00:06:29 +0100 Subject: [PATCH 33/42] [Arch] children of Component did not rotate with Host --- src/Mod/Arch/ArchComponent.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Mod/Arch/ArchComponent.py b/src/Mod/Arch/ArchComponent.py index 1951d17124..df2ac49100 100644 --- a/src/Mod/Arch/ArchComponent.py +++ b/src/Mod/Arch/ArchComponent.py @@ -314,28 +314,28 @@ class Component(ArchIFC.IfcProduct): if prop == "Placement": if hasattr(self,"oldPlacement"): if self.oldPlacement: - import DraftVecUtils deltap = obj.Placement.Base.sub(self.oldPlacement.Base) if deltap.Length == 0: deltap = None - v = FreeCAD.Vector(0,0,1) - deltar = FreeCAD.Rotation(self.oldPlacement.Rotation.multVec(v),obj.Placement.Rotation.multVec(v)) - #print "Rotation",deltar.Axis,deltar.Angle + deltar = obj.Placement.Rotation * self.oldPlacement.Rotation.inverted() if deltar.Angle < 0.0001: deltar = None for child in self.getMovableChildren(obj): - #print "moving ",child.Label if deltar: - #child.Placement.Rotation = child.Placement.Rotation.multiply(deltar) - not enough, child must also move - # use shape methods to obtain a correct placement - import Part,math - shape = Part.Shape() - shape.Placement = child.Placement - #print("angle before rotation:",shape.Placement.Rotation.Angle) - #print("rotation angle:",math.degrees(deltar.Angle)) - shape.rotate(DraftVecUtils.tup(self.oldPlacement.Base), DraftVecUtils.tup(deltar.Axis), math.degrees(deltar.Angle)) - #print("angle after rotation:",shape.Placement.Rotation.Angle) - child.Placement = shape.Placement + import math + # Code for V1.0: + # child.Placement.rotate(self.oldPlacement.Base, + # deltar.Axis, + # math.degrees(deltar.Angle), + # comp=True) + + # Workaround solution for V0.20.3 backport: + # See: https://forum.freecadweb.org/viewtopic.php?p=613196#p613196 + offset_rotation = FreeCAD.Placement(FreeCAD.Vector(0, 0, 0), + FreeCAD.Rotation(deltar.Axis, math.degrees(deltar.Angle)), + self.oldPlacement.Base) + child.Placement = offset_rotation * child.Placement + # End workaround solution. if deltap: child.Placement.move(deltap) From 6c6e0ae9bbadde1cff7629102a7f28fa0503187c Mon Sep 17 00:00:00 2001 From: Uwe Date: Thu, 16 Feb 2023 01:26:04 +0100 Subject: [PATCH 34/42] [FEM] make pvtu file filtering optional - it is sensible to filter by default, however, it turned out that on some complex models Elmer fails to compute if the mesh volume regions are too small (in most cases) or at a certain mesh region. By disabling the filtering, one gets at least for the latter case a visual feedback where the mesh volume of the different CPU are (by also setting a transparency to the result pipeline). --- src/Mod/Fem/Gui/DlgSettingsFemElmer.ui | 164 +++++++++++------- src/Mod/Fem/Gui/DlgSettingsFemElmerImp.cpp | 2 + src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp | 11 +- 3 files changed, 110 insertions(+), 67 deletions(-) diff --git a/src/Mod/Fem/Gui/DlgSettingsFemElmer.ui b/src/Mod/Fem/Gui/DlgSettingsFemElmer.ui index de8da1844e..598048f7d0 100644 --- a/src/Mod/Fem/Gui/DlgSettingsFemElmer.ui +++ b/src/Mod/Fem/Gui/DlgSettingsFemElmer.ui @@ -6,15 +6,15 @@ 0 0 - 400 - 203 + 350 + 259 Elmer - - + + @@ -39,6 +39,46 @@ + + + + false + + + + 0 + 0 + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 0 + 0 + + + + <html><head/><body><p>Leave blank to use default Elmer elmer binary file</p><p><span style=" font-weight:600;">Note:</span> To use multithreading you must specify here<br> the executable variant with the suffix &quot;_mpi&quot;.</p></body></html> + + + elmerBinaryPath + + + Mod/Fem/Elmer + + + @@ -55,22 +95,6 @@ - - - - false - - - - 100 - 0 - - - - ElmerGrid binary path - - - @@ -134,6 +158,22 @@ + + + + false + + + + 100 + 0 + + + + ElmerGrid binary path + + + @@ -150,54 +190,23 @@ - - - - false - - - - 0 - 0 - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 0 - 0 - - - - <html><head/><body><p>Leave blank to use default Elmer elmer binary file</p><p><span style=" font-weight:600;">Note:</span> To use multithreading you must specify here<br> the executable variant with the suffix &quot;_mpi&quot;.</p></body></html> - - - elmerBinaryPath - - - Mod/Fem/Elmer - - - - - + + + + + + + Options + + + + Multithreading: - + @@ -246,10 +255,37 @@ + + + + Multi-CPU core support: + + + + + + + The mesh volume regions processed by each CPU core +will be merged to make the volume boundaries invisible. + + + Filter results + + + true + + + FilterMultiCPUResults + + + Mod/Fem/Elmer + + + - + Qt::Vertical diff --git a/src/Mod/Fem/Gui/DlgSettingsFemElmerImp.cpp b/src/Mod/Fem/Gui/DlgSettingsFemElmerImp.cpp index 40110f59d7..201bbe3f24 100644 --- a/src/Mod/Fem/Gui/DlgSettingsFemElmerImp.cpp +++ b/src/Mod/Fem/Gui/DlgSettingsFemElmerImp.cpp @@ -70,6 +70,7 @@ void DlgSettingsFemElmerImp::saveSettings() ui->fc_grid_binary_path->onSave(); ui->sb_elmer_num_cores->onSave(); + ui->cb_elmer_filtering->onSave(); } void DlgSettingsFemElmerImp::loadSettings() @@ -81,6 +82,7 @@ void DlgSettingsFemElmerImp::loadSettings() ui->fc_grid_binary_path->onRestore(); ui->sb_elmer_num_cores->onRestore(); + ui->cb_elmer_filtering->onRestore(); } /** diff --git a/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp b/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp index 7f664962ff..7e0ac35dde 100644 --- a/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp +++ b/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp @@ -706,9 +706,14 @@ void ViewProviderFemPostObject::filterArtifacts(vtkDataObject* data) m_wireframe->SetInputData(data); m_points->SetInputData(data); - // filter artifacts - // only necessary for the surface filter - filterArtifacts(data); + // filtering artifacts is only necessary for the surface filter + auto hGrp = App::GetApplication().GetParameterGroupByPath( + "User parameter:BaseApp/Preferences/Mod/Fem/Elmer"); + bool FilterMultiCPUResults = hGrp->GetBool("FilterMultiCPUResults", 1); + if (FilterMultiCPUResults) + filterArtifacts(data); + else + m_surface->SetInputData(data); return true; } From 9aa61bf49aef90359f3f84b902d54c22b2e28ed3 Mon Sep 17 00:00:00 2001 From: Uwe Date: Wed, 15 Feb 2023 00:38:31 +0100 Subject: [PATCH 35/42] [Gui] font setting fixes for NaviCube - it turned out that the current check for default font failed because of wrong check for the hinting - fix bug that when the FontSize is not yet stored in the parameters, the dialog shows a wrong size value --- src/Gui/DlgSettingsNavigation.cpp | 18 ++++++++++++++++-- src/Gui/NaviCube.cpp | 28 +++++++++++----------------- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src/Gui/DlgSettingsNavigation.cpp b/src/Gui/DlgSettingsNavigation.cpp index 29c2b1c5d4..f01380a236 100644 --- a/src/Gui/DlgSettingsNavigation.cpp +++ b/src/Gui/DlgSettingsNavigation.cpp @@ -144,7 +144,7 @@ void DlgSettingsNavigation::loadSettings() ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath ("User parameter:BaseApp/Preferences/View"); - std::string model = hGrp->GetASCII("NavigationStyle",CADNavigationStyle::getClassTypeId().getName()); + std::string model = hGrp->GetASCII("NavigationStyle", CADNavigationStyle::getClassTypeId().getName()); int index = ui->comboNavigationStyle->findData(QByteArray(model.c_str())); if (index > -1) ui->comboNavigationStyle->setCurrentIndex(index); @@ -196,11 +196,25 @@ void DlgSettingsNavigation::loadSettings() QStringList familyNames = QFontDatabase::families(QFontDatabase::Any); #endif ui->naviCubeFontName->addItems(familyNames); + + // if the parameter has not yet been set, do so immediately + // this assures it is set even if the user cancels the dialog + if (hGrp->GetASCII("FontString", "").empty()) + hGrp->SetASCII("FontString", defaultSansserifFont.constData()); int indexFamilyNames = familyNames.indexOf( - QString::fromLatin1(hGrp->GetASCII("FontString", defaultSansserifFont).c_str())); + QString::fromStdString(hGrp->GetASCII("FontString", defaultSansserifFont))); if (indexFamilyNames < 0) indexFamilyNames = 0; ui->naviCubeFontName->setCurrentIndex(indexFamilyNames); + + // if the FontSize parameter does not yet exist, set the default value + // the default is defined in NaviCubeImplementation::getDefaultFontSize() + // but not accessible if there is no cube yet drawn + if (hGrp->GetInt("FontSize", 0) == 0) { + // the "4" is the hardcoded m_OverSample from getDefaultFontSize() + hGrp->SetInt("FontSize", int(0.18 * 4 * ui->prefCubeSize->value())); + ui->naviCubeFontSize->onRestore(); + } } void DlgSettingsNavigation::onMouseButtonClicked() diff --git a/src/Gui/NaviCube.cpp b/src/Gui/NaviCube.cpp index e489e8c6f0..9c384540f9 100644 --- a/src/Gui/NaviCube.cpp +++ b/src/Gui/NaviCube.cpp @@ -296,24 +296,18 @@ void NaviCube::setFontSize(int size) // the Helvetica font is a good start for most OSes QFont NaviCube::getDefaultSansserifFont() { - QFont font(QString::fromLatin1("Helvetica")); - if (font.styleHint() & QFont::SansSerif) + // Windows versions since 2017 have the 'Bahnschrift' font (a condensed + // sans serif font, optimized for readability despite being condensed), + // we first check for that. + QFont font(QString::fromLatin1("Bahnschrift")); + if (font.exactMatch()) return font; - // on Windows 10 or newer there is no Helvetica font - // therefore if we didn't find a Helvetica font check - // for the Bahnschrift font (in all Windows since 2017) - // if this too is unavailable (on Win 7), we check for the - // DejaVu Sans fonts - font.setFamily(QString::fromLatin1("Bahnschrift")); - // on Windows 11 sansserif fonts like Bahnschrift do not have the - // styleHint QFont::SansSerif but QFont::AnyStyle - // however, in future they might have, thus allow both - if (font.styleHint() == QFont::SansSerif || font.styleHint() == QFont::AnyStyle) - return font; - font.setFamily(QString::fromLatin1("DejaVu Sans")); - if (font.styleHint() == QFont::SansSerif || font.styleHint() == QFont::AnyStyle) - return font; - return font; // We failed, but return whatever we have anyway + + // On systems without 'Bahnschrift' we check for 'Helvetica' or its closest match + // as default sans serif font. (For Windows 7 this will e.g. result in 'Arial'.) + font = QString::fromLatin1("Helvetica"); + font.setStyleHint(QFont::SansSerif); + return font; } int NaviCube::getDefaultFontSize() From fb2bfe3a089ea4fb90454afe6b6d0e577e2de9a5 Mon Sep 17 00:00:00 2001 From: berniev Date: Thu, 16 Feb 2023 06:18:05 +1000 Subject: [PATCH 36/42] clang-format enable reflow comments --- .clang-format | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.clang-format b/.clang-format index 6ba545491f..45623c68c9 100644 --- a/.clang-format +++ b/.clang-format @@ -47,7 +47,7 @@ ObjCSpaceAfterProperty: false ObjCSpaceBeforeProtocolList: true PackConstructorInitializers: Never PointerAlignment: Left -ReflowComments: false +ReflowComments: true SpaceAfterCStyleCast: false SpaceAfterLogicalNot: false SpaceAfterTemplateKeyword: false From a1341298681f5e4c2f81d81f9801bf03637baa50 Mon Sep 17 00:00:00 2001 From: luzpaz Date: Thu, 16 Feb 2023 13:52:44 +0000 Subject: [PATCH 37/42] Fix various trailing whitespace issues --- cMake/FindOpenCV.cmake | 86 +++++++++---------- cMake/FindPyCXX.cmake | 2 +- cMake/FindRift.cmake | 6 +- cMake/FreeCAD_Helpers/PrintFinalReport.cmake | 2 +- cMake/FreeCAD_Helpers/SetupQt.cmake | 2 +- .../SetupShibokenAndPyside.cmake | 4 +- cMake/UseLibPackCLbundler.cmake | 2 +- src/Doc/sphinx/conf.py | 6 +- src/MacAppBundle/CMakeLists.txt | 2 +- src/Main/freecad.rc.cmake | 2 +- src/Main/freecadCmd.rc.cmake | 2 +- src/Mod/Start/StartPage/StartPage.py | 2 +- .../TechDrawTools/TaskHoleShaftFit.py | 2 +- 13 files changed, 60 insertions(+), 60 deletions(-) diff --git a/cMake/FindOpenCV.cmake b/cMake/FindOpenCV.cmake index 8b06b13815..e5f2d37ddd 100644 --- a/cMake/FindOpenCV.cmake +++ b/cMake/FindOpenCV.cmake @@ -3,10 +3,10 @@ # # The following variables are optionally searched for defaults # OpenCV_ROOT_DIR: Base directory of OpenCv tree to use. -# OpenCV_FIND_REQUIRED_COMPONENTS : FIND_PACKAGE(OpenCV COMPONENTS ..) +# OpenCV_FIND_REQUIRED_COMPONENTS : FIND_PACKAGE(OpenCV COMPONENTS ..) # compatible interface. typically CV CXCORE CVAUX HIGHGUI CVCAM .. etc. # -# The following are set after configuration is done: +# The following are set after configuration is done: # OpenCV_FOUND # OpenCV_INCLUDE_DIR # OpenCV_LIBRARIES @@ -16,10 +16,10 @@ # OPENCV_* uppercase replaced by case sensitive OpenCV_* # OPENCV_EXE_LINKER_FLAGS # OPENCV_INCLUDE_DIR : replaced by plural *_DIRS -# -# 2004/05 Jan Woetzel, Friso, Daniel Grest +# +# 2004/05 Jan Woetzel, Friso, Daniel Grest # 2006/01 complete rewrite by Jan Woetzel -# 1006/09 2nd rewrite introducing ROOT_DIR and PATH_SUFFIXES +# 1006/09 2nd rewrite introducing ROOT_DIR and PATH_SUFFIXES # to handle multiple installed versions gracefully by Jan Woetzel # # tested with: @@ -42,14 +42,14 @@ IF (NOT OpenCV_FIND_COMPONENTS) SET(OpenCV_FIND_REQUIRED_COMPONENTS CV CXCORE CVAUX HIGHGUI ) IF (WIN32) LIST(APPEND OpenCV_FIND_REQUIRED_COMPONENTS CVCAM ) # WIN32 only actually - ENDIF(WIN32) + ENDIF(WIN32) ENDIF (NOT OpenCV_FIND_COMPONENTS) # typical root dirs of installations, exactly one of them is used SET (OpenCV_POSSIBLE_ROOT_DIRS "${OpenCV_ROOT_DIR}" - "$ENV{OpenCV_ROOT_DIR}" + "$ENV{OpenCV_ROOT_DIR}" "$ENV{OPENCV_DIR}" # only for backward compatibility deprecated by ROOT_DIR "$ENV{OPENCV_HOME}" # only for backward compatibility "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Intel(R) Open Source Computer Vision Library_is1;Inno Setup: App Path]" @@ -59,17 +59,17 @@ SET (OpenCV_POSSIBLE_ROOT_DIRS ) -# MIP Uni Kiel /opt/net network installation +# MIP Uni Kiel /opt/net network installation # get correct prefix for current gcc compiler version for gcc 3.x 4.x IF (${CMAKE_COMPILER_IS_GNUCXX}) IF (NOT OpenCV_FIND_QUIETLY) MESSAGE(STATUS "Checking GNUCXX version 3/4 to determine OpenCV /opt/net/ path") ENDIF (NOT OpenCV_FIND_QUIETLY) - EXEC_PROGRAM(${CMAKE_CXX_COMPILER} ARGS --version OUTPUT_VARIABLE CXX_COMPILER_VERSION) + EXEC_PROGRAM(${CMAKE_CXX_COMPILER} ARGS --version OUTPUT_VARIABLE CXX_COMPILER_VERSION) IF (CXX_COMPILER_VERSION MATCHES ".*3\\.[0-9].*") SET(IS_GNUCXX3 TRUE) LIST(APPEND OpenCV_POSSIBLE_ROOT_DIRS /opt/net/gcc33/OpenCV ) - ENDIF(CXX_COMPILER_VERSION MATCHES ".*3\\.[0-9].*") + ENDIF(CXX_COMPILER_VERSION MATCHES ".*3\\.[0-9].*") IF (CXX_COMPILER_VERSION MATCHES ".*4\\.[0-9].*") SET(IS_GNUCXX4 TRUE) LIST(APPEND OpenCV_POSSIBLE_ROOT_DIRS /opt/net/gcc41/OpenCV ) @@ -79,15 +79,15 @@ ENDIF (${CMAKE_COMPILER_IS_GNUCXX}) #DBG_MSG("DBG (OpenCV_POSSIBLE_ROOT_DIRS=${OpenCV_POSSIBLE_ROOT_DIRS}") # -# select exactly ONE OpenCV base directory/tree +# select exactly ONE OpenCV base directory/tree # to avoid mixing different version headers and libs # -FIND_PATH(OpenCV_ROOT_DIR - NAMES +FIND_PATH(OpenCV_ROOT_DIR + NAMES cv/include/cv.h # windows include/opencv/cv.h # linux /opt/net - include/cv/cv.h - include/cv.h + include/cv/cv.h + include/cv.h PATHS ${OpenCV_POSSIBLE_ROOT_DIRS}) DBG_MSG("OpenCV_ROOT_DIR=${OpenCV_ROOT_DIR}") @@ -106,7 +106,7 @@ SET(OpenCV_INCDIR_SUFFIXES otherlibs/_graphics/include ) -# library linkdir suffixes appended to OpenCV_ROOT_DIR +# library linkdir suffixes appended to OpenCV_ROOT_DIR SET(OpenCV_LIBDIR_SUFFIXES lib OpenCV/lib @@ -119,56 +119,56 @@ SET(OpenCV_LIBDIR_SUFFIXES # find incdir for each lib # FIND_PATH(OpenCV_CV_INCLUDE_DIR - NAMES cv.h - PATHS ${OpenCV_ROOT_DIR} + NAMES cv.h + PATHS ${OpenCV_ROOT_DIR} PATH_SUFFIXES ${OpenCV_INCDIR_SUFFIXES} ) -FIND_PATH(OpenCV_CXCORE_INCLUDE_DIR +FIND_PATH(OpenCV_CXCORE_INCLUDE_DIR NAMES cxcore.h - PATHS ${OpenCV_ROOT_DIR} + PATHS ${OpenCV_ROOT_DIR} PATH_SUFFIXES ${OpenCV_INCDIR_SUFFIXES} ) -FIND_PATH(OpenCV_CVAUX_INCLUDE_DIR +FIND_PATH(OpenCV_CVAUX_INCLUDE_DIR NAMES cvaux.h - PATHS ${OpenCV_ROOT_DIR} + PATHS ${OpenCV_ROOT_DIR} PATH_SUFFIXES ${OpenCV_INCDIR_SUFFIXES} ) -FIND_PATH(OpenCV_HIGHGUI_INCLUDE_DIR - NAMES highgui.h - PATHS ${OpenCV_ROOT_DIR} +FIND_PATH(OpenCV_HIGHGUI_INCLUDE_DIR + NAMES highgui.h + PATHS ${OpenCV_ROOT_DIR} PATH_SUFFIXES ${OpenCV_INCDIR_SUFFIXES} ) -FIND_PATH(OpenCV_CVCAM_INCLUDE_DIR - NAMES cvcam.h - PATHS ${OpenCV_ROOT_DIR} +FIND_PATH(OpenCV_CVCAM_INCLUDE_DIR + NAMES cvcam.h + PATHS ${OpenCV_ROOT_DIR} PATH_SUFFIXES ${OpenCV_INCDIR_SUFFIXES} ) # -# find sbsolute path to all libraries +# find sbsolute path to all libraries # some are optionally, some may not exist on Linux # -FIND_LIBRARY(OpenCV_CV_LIBRARY +FIND_LIBRARY(OpenCV_CV_LIBRARY NAMES cv opencv - PATHS ${OpenCV_ROOT_DIR} + PATHS ${OpenCV_ROOT_DIR} PATH_SUFFIXES ${OpenCV_LIBDIR_SUFFIXES} ) FIND_LIBRARY(OpenCV_CVAUX_LIBRARY NAMES cvaux PATHS ${OpenCV_ROOT_DIR} PATH_SUFFIXES ${OpenCV_LIBDIR_SUFFIXES} ) -FIND_LIBRARY(OpenCV_CVCAM_LIBRARY +FIND_LIBRARY(OpenCV_CVCAM_LIBRARY NAMES cvcam - PATHS ${OpenCV_ROOT_DIR} PATH_SUFFIXES ${OpenCV_LIBDIR_SUFFIXES} ) + PATHS ${OpenCV_ROOT_DIR} PATH_SUFFIXES ${OpenCV_LIBDIR_SUFFIXES} ) FIND_LIBRARY(OpenCV_CVHAARTRAINING_LIBRARY NAMES cvhaartraining - PATHS ${OpenCV_ROOT_DIR} PATH_SUFFIXES ${OpenCV_LIBDIR_SUFFIXES} ) -FIND_LIBRARY(OpenCV_CXCORE_LIBRARY + PATHS ${OpenCV_ROOT_DIR} PATH_SUFFIXES ${OpenCV_LIBDIR_SUFFIXES} ) +FIND_LIBRARY(OpenCV_CXCORE_LIBRARY NAMES cxcore PATHS ${OpenCV_ROOT_DIR} PATH_SUFFIXES ${OpenCV_LIBDIR_SUFFIXES} ) -FIND_LIBRARY(OpenCV_CXTS_LIBRARY +FIND_LIBRARY(OpenCV_CXTS_LIBRARY NAMES cxts PATHS ${OpenCV_ROOT_DIR} PATH_SUFFIXES ${OpenCV_LIBDIR_SUFFIXES} ) -FIND_LIBRARY(OpenCV_HIGHGUI_LIBRARY +FIND_LIBRARY(OpenCV_HIGHGUI_LIBRARY NAMES highgui PATHS ${OpenCV_ROOT_DIR} PATH_SUFFIXES ${OpenCV_LIBDIR_SUFFIXES} ) -FIND_LIBRARY(OpenCV_ML_LIBRARY +FIND_LIBRARY(OpenCV_ML_LIBRARY NAMES ml PATHS ${OpenCV_ROOT_DIR} PATH_SUFFIXES ${OpenCV_LIBDIR_SUFFIXES} ) -FIND_LIBRARY(OpenCV_TRS_LIBRARY +FIND_LIBRARY(OpenCV_TRS_LIBRARY NAMES trs PATHS ${OpenCV_ROOT_DIR} PATH_SUFFIXES ${OpenCV_LIBDIR_SUFFIXES} ) @@ -181,7 +181,7 @@ SET(OpenCV_FOUND ON) DBG_MSG("OpenCV_FIND_REQUIRED_COMPONENTS=${OpenCV_FIND_REQUIRED_COMPONENTS}") FOREACH(NAME ${OpenCV_FIND_REQUIRED_COMPONENTS} ) - # only good if header and library both found + # only good if header and library both found IF (OpenCV_${NAME}_INCLUDE_DIR AND OpenCV_${NAME}_LIBRARY) LIST(APPEND OpenCV_INCLUDE_DIRS ${OpenCV_${NAME}_INCLUDE_DIR} ) LIST(APPEND OpenCV_LIBRARIES ${OpenCV_${NAME}_LIBRARY} ) @@ -192,13 +192,13 @@ FOREACH(NAME ${OpenCV_FIND_REQUIRED_COMPONENTS} ) "\nOpenCV_${NAME}_LIBRARY=${OpenCV_${NAME}_LIBRARY} ") SET(OpenCV_FOUND OFF) ENDIF (OpenCV_${NAME}_INCLUDE_DIR AND OpenCV_${NAME}_LIBRARY) - + ENDFOREACH(NAME) DBG_MSG("OpenCV_INCLUDE_DIRS=${OpenCV_INCLUDE_DIRS}") DBG_MSG("OpenCV_LIBRARIES=${OpenCV_LIBRARIES}") -# get the link directory for rpath to be used with LINK_DIRECTORIES: +# get the link directory for rpath to be used with LINK_DIRECTORIES: IF (OpenCV_CV_LIBRARY) GET_FILENAME_COMPONENT(OpenCV_LINK_DIRECTORIES ${OpenCV_CV_LIBRARY} PATH) ENDIF (OpenCV_CV_LIBRARY) @@ -239,7 +239,7 @@ IF(NOT OpenCV_FOUND) MESSAGE(FATAL_ERROR "OpenCV required but some headers or libs not found. Please specify it's location with OpenCV_ROOT_DIR env. variable.") ELSE(OpenCV_FIND_REQUIRED) - MESSAGE(STATUS + MESSAGE(STATUS "ERROR: OpenCV was not found.") ENDIF(OpenCV_FIND_REQUIRED) ENDIF(NOT OpenCV_FIND_QUIETLY) diff --git a/cMake/FindPyCXX.cmake b/cMake/FindPyCXX.cmake index 1b703e967a..c15c9ec367 100644 --- a/cMake/FindPyCXX.cmake +++ b/cMake/FindPyCXX.cmake @@ -53,7 +53,7 @@ else(PYCXX_INCLUDE_DIR) endif(PyCXX_FIND_REQUIRED) endif(NOT PYCXX_INCLUDE_DIR) endif(PYCXX_INCLUDE_DIR) - + # find the sources directory if(PYCXX_SOURCE_DIR) # source directory specified, they'd better be there diff --git a/cMake/FindRift.cmake b/cMake/FindRift.cmake index 55dd21b519..8082e8da92 100644 --- a/cMake/FindRift.cmake +++ b/cMake/FindRift.cmake @@ -6,7 +6,7 @@ # OCULUS_LIBRARIES # # Copyright (c) 2012 I-maginer -# +# # This program 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 of the License, or (at your option) any later @@ -42,11 +42,11 @@ IF(OCULUS_ROOT) ${OCULUS_INCLUDE_SEARCH_DIRS} ${OCULUS_ROOT}/include ) - SET(OCULUS_LIBRARY_SEARCH_RELEASE_DIRS + SET(OCULUS_LIBRARY_SEARCH_RELEASE_DIRS ${OCULUS_LIBRARY_SEARCH_DIRS} ${OCULUS_ROOT}/Lib/x64/VS2012 ) - SET(OCULUS_LIBRARY_SEARCH_DEBUG_DIRS + SET(OCULUS_LIBRARY_SEARCH_DEBUG_DIRS ${OCULUS_LIBRARY_SEARCH_DIRS} ${OCULUS_ROOT}/Lib/x64/VS2012 ) diff --git a/cMake/FreeCAD_Helpers/PrintFinalReport.cmake b/cMake/FreeCAD_Helpers/PrintFinalReport.cmake index 845eae98a1..cb59d7e15f 100644 --- a/cMake/FreeCAD_Helpers/PrintFinalReport.cmake +++ b/cMake/FreeCAD_Helpers/PrintFinalReport.cmake @@ -99,7 +99,7 @@ macro(PrintFinalReport) value(FREECAD_LIBPACK_USE) section_end() - + ################ Libraries ################## section_begin(Libraries) diff --git a/cMake/FreeCAD_Helpers/SetupQt.cmake b/cMake/FreeCAD_Helpers/SetupQt.cmake index 2f3d71b223..34c454adba 100644 --- a/cMake/FreeCAD_Helpers/SetupQt.cmake +++ b/cMake/FreeCAD_Helpers/SetupQt.cmake @@ -15,7 +15,7 @@ if(BUILD_GUI) if (BUILD_WEB) list (APPEND FREECAD_QT_COMPONENTS WebEngineWidgets) endif() - if(BUILD_DESIGNER_PLUGIN) + if(BUILD_DESIGNER_PLUGIN) list (APPEND FREECAD_QT_COMPONENTS Designer) endif() endif() diff --git a/cMake/FreeCAD_Helpers/SetupShibokenAndPyside.cmake b/cMake/FreeCAD_Helpers/SetupShibokenAndPyside.cmake index b7a1c73b12..43e650fe85 100644 --- a/cMake/FreeCAD_Helpers/SetupShibokenAndPyside.cmake +++ b/cMake/FreeCAD_Helpers/SetupShibokenAndPyside.cmake @@ -190,7 +190,7 @@ macro(find_pip_package PACKAGE) STRING(SUBSTRING "${LINE}" 9 -1 PIP_PACKAGE_LOCATION) endif() endforeach() - file(TO_NATIVE_PATH "${PIP_PACKAGE_LOCATION}/${PIP_PACKAGE_NAME}/include" INCLUDE_DIR) + file(TO_NATIVE_PATH "${PIP_PACKAGE_LOCATION}/${PIP_PACKAGE_NAME}/include" INCLUDE_DIR) file(TO_NATIVE_PATH "${PIP_PACKAGE_LOCATION}/${PIP_PACKAGE_NAME}/lib" LIBRARY) set(${PACKAGE}_INCLUDE_DIRS ${INCLUDE_DIR} PARENT_SCOPE) set(${PACKAGE}_LIBRARIES ${LIBRARY} PARENT_SCOPE) @@ -200,7 +200,7 @@ macro(find_pip_package PACKAGE) endmacro() -# Macros similar to FindQt4.cmake's WRAP_UI and WRAP_RC, for the automatic generation of Python +# Macros similar to FindQt4.cmake's WRAP_UI and WRAP_RC, for the automatic generation of Python # code from Qt4's user interface ('.ui') and resource ('.qrc') files. These macros are called: # - PYSIDE_WRAP_UI # - PYSIDE_WRAP_RC diff --git a/cMake/UseLibPackCLbundler.cmake b/cMake/UseLibPackCLbundler.cmake index 082a27f8b5..22177651c5 100644 --- a/cMake/UseLibPackCLbundler.cmake +++ b/cMake/UseLibPackCLbundler.cmake @@ -44,7 +44,7 @@ set (Qt5XmlPatterns_DIR ${Qt5_ROOT_DIR}/lib/cmake/Qt5XmlPatterns CACHE PATH "") find_library(XercesC_LIBRARY_RELEASE xerces-c_3 "${FREECAD_LIBPACK_DIR}/lib") find_library(XercesC_LIBRARY_DEBUG xerces-c_3D "${FREECAD_LIBPACK_DIR}/lib") set (XercesC_LIBRARIES debug ${XercesC_LIBRARY_DEBUG} optimized ${XercesC_LIBRARY_RELEASE}) -set(XercesC_FOUND TRUE) +set(XercesC_FOUND TRUE) find_library(COIN3D_LIBRARY_RELEASE coin4 "${FREECAD_LIBPACK_DIR}/lib") find_library(COIN3D_LIBRARY_DEBUG coin4d "${FREECAD_LIBPACK_DIR}/lib") diff --git a/src/Doc/sphinx/conf.py b/src/Doc/sphinx/conf.py index 6146780a80..b94c1953f6 100644 --- a/src/Doc/sphinx/conf.py +++ b/src/Doc/sphinx/conf.py @@ -2,8 +2,8 @@ #*************************************************************************** #* * -#* Copyright (c) 2012 * -#* Yorik van Havre * +#* Copyright (c) 2012 * +#* Yorik van Havre * #* * #* This program is free software; you can redistribute it and/or modify * #* it under the terms of the GNU Lesser General Public License (LGPL) * @@ -52,7 +52,7 @@ if os.path.exists(os.path.abspath(os.path.join(os.environ["HOME"],"FreeCAD/lib") elif commands.getstatusoutput("locate FreeCAD/lib")[0] == 0: path = commands.getstatusoutput("locate FreeCAD/lib")[1].split()[0] sys.path.append(path) - + # locate TemplatePyMod if commands.getstatusoutput("locate TemplatePyMod")[0] == 0: path = commands.getstatusoutput("locate TemplatePyMod")[1].split()[0] diff --git a/src/MacAppBundle/CMakeLists.txt b/src/MacAppBundle/CMakeLists.txt index a6c72dcecd..a5fac6d82c 100644 --- a/src/MacAppBundle/CMakeLists.txt +++ b/src/MacAppBundle/CMakeLists.txt @@ -146,7 +146,7 @@ file(GLOB CONFIG_GCC "${HOMEBREW_PREFIX}/opt/gcc/lib/gcc/current") execute_process( COMMAND find -L /usr/local/Cellar/nglib -name MacOS OUTPUT_VARIABLE CONFIG_NGLIB) - + install(CODE "message(STATUS \"Making bundle relocatable...\") # The top-level CMakeLists.txt should prevent multiple package manager diff --git a/src/Main/freecad.rc.cmake b/src/Main/freecad.rc.cmake index c319021bb6..3c2a09094d 100644 --- a/src/Main/freecad.rc.cmake +++ b/src/Main/freecad.rc.cmake @@ -12,7 +12,7 @@ IDI_ICON1 ICON DISCARDABLE "icon.ico" // File info for the FreeCAD.exe // -1 VERSIONINFO +1 VERSIONINFO FILEVERSION ${PACKAGE_VERSION_MAJOR},${PACKAGE_VERSION_MINOR},${PACKAGE_VERSION_PATCH},${PACKAGE_BUILD_VERSION} BEGIN BLOCK "StringFileInfo" diff --git a/src/Main/freecadCmd.rc.cmake b/src/Main/freecadCmd.rc.cmake index 0d701a1039..9dc76dc9d1 100644 --- a/src/Main/freecadCmd.rc.cmake +++ b/src/Main/freecadCmd.rc.cmake @@ -12,7 +12,7 @@ IDI_ICON1 ICON DISCARDABLE "icon.ico" // File info for the FreeCADCmd.exe // -1 VERSIONINFO +1 VERSIONINFO FILEVERSION ${PACKAGE_VERSION_MAJOR},${PACKAGE_VERSION_MINOR},${PACKAGE_VERSION_PATCH},${PACKAGE_BUILD_VERSION} BEGIN BLOCK "StringFileInfo" diff --git a/src/Mod/Start/StartPage/StartPage.py b/src/Mod/Start/StartPage/StartPage.py index 1e38609f87..972c9b5490 100644 --- a/src/Mod/Start/StartPage/StartPage.py +++ b/src/Mod/Start/StartPage/StartPage.py @@ -253,7 +253,7 @@ def getDefaultIcon(): def buildCard(filename,method,arg=None): - """builds an html
  • element representing a file. + """builds an html
  • element representing a file. method is a script + a keyword, for ex. url.py?key=""" result = "" diff --git a/src/Mod/TechDraw/TechDrawTools/TaskHoleShaftFit.py b/src/Mod/TechDraw/TechDrawTools/TaskHoleShaftFit.py index 609956b997..fcd39194fe 100644 --- a/src/Mod/TechDraw/TechDrawTools/TaskHoleShaftFit.py +++ b/src/Mod/TechDraw/TechDrawTools/TaskHoleShaftFit.py @@ -115,7 +115,7 @@ class TaskHoleShaftFit: rangeValues = iso.getValues() mainFormat = dim.FormatSpec dim.FormatSpec = mainFormat+selectedField - dim.EqualTolerance = False + dim.EqualTolerance = False dim.FormatSpecOverTolerance = '(%+.3f)' dim.OverTolerance = rangeValues[0] dim.UnderTolerance = rangeValues[1] From 14da2ac92bf12d98b55310fd48ba00e25cbeba38 Mon Sep 17 00:00:00 2001 From: Roy-043 <70520633+Roy-043@users.noreply.github.com> Date: Thu, 16 Feb 2023 20:56:22 +0100 Subject: [PATCH 38/42] [Arch] Allow Arch_stairs with 2 steps (#8515) - Fixes #7050 --- src/Mod/Arch/ArchStairs.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/Mod/Arch/ArchStairs.py b/src/Mod/Arch/ArchStairs.py index d25bbf5402..7c1e8f725d 100644 --- a/src/Mod/Arch/ArchStairs.py +++ b/src/Mod/Arch/ArchStairs.py @@ -1330,24 +1330,25 @@ class _Stairs(ArchComponent.Component): "builds a straight staircase with/without a landing in the middle" - if obj.NumberOfSteps < 3: + if obj.NumberOfSteps < 2: + print("Fewer than 2 steps, unable to create/update stairs") return v = DraftGeomUtils.vec(edge) landing = 0 if obj.TreadDepthEnforce == 0: - if obj.Landings == "At center": + if obj.Landings == "At center" and obj.NumberOfSteps > 3: if obj.LandingDepth: reslength = edge.Length - obj.LandingDepth.Value else: reslength = edge.Length - obj.Width.Value - treadDepth = float(reslength)/(obj.NumberOfSteps-2) # why needs 'float'? + treadDepth = reslength/(obj.NumberOfSteps-2) obj.TreadDepth = treadDepth vLength = DraftVecUtils.scaleTo(v,treadDepth) else: reslength = edge.Length - treadDepth = float(reslength)/(obj.NumberOfSteps-1) # why needs 'float'? + treadDepth = reslength/(obj.NumberOfSteps-1) obj.TreadDepth = treadDepth vLength = DraftVecUtils.scaleTo(v,treadDepth) else: @@ -1370,7 +1371,7 @@ class _Stairs(ArchComponent.Component): h = obj.RiserHeightEnforce.Value * (obj.NumberOfSteps) hstep = obj.RiserHeightEnforce.Value obj.RiserHeight = hstep - if obj.Landings == "At center": + if obj.Landings == "At center" and obj.NumberOfSteps > 3: landing = int(obj.NumberOfSteps/2) else: landing = obj.NumberOfSteps @@ -1382,7 +1383,7 @@ class _Stairs(ArchComponent.Component): obj.AbsTop = p1.add(Vector(0,0,h)) p2 = p1.add(DraftVecUtils.scale(vLength,landing-1).add(Vector(0,0,landing*hstep))) - if obj.Landings == "At center": + if obj.Landings == "At center" and obj.NumberOfSteps > 3: if obj.LandingDepth: p3 = p2.add(DraftVecUtils.scaleTo(vLength,obj.LandingDepth.Value)) else: @@ -1414,13 +1415,16 @@ class _Stairs(ArchComponent.Component): self.makeStraightStairs(obj,Part.LineSegment(p1,p2).toShape(),obj.DownSlabThickness.Value,obj.RiserHeight.Value,landing,None,'toSlabThickness') else: + if obj.Landings == "At center": + print("Fewer than 4 steps, unable to create landing") self.makeStraightStairs(obj,Part.LineSegment(p1,p2).toShape(),obj.DownSlabThickness.Value,obj.UpSlabThickness.Value,landing,None,None) print (p1, p2) - if obj.Landings == "At center" and obj.Flight not in ["HalfTurnLeft", "HalfTurnRight"]: - print (p3, p4) - elif obj.Landings == "At center" and obj.Flight in ["HalfTurnLeft", "HalfTurnRight"]: - print (p3r, p4r) + if obj.Landings == "At center" and obj.NumberOfSteps > 3: + if obj.Flight not in ["HalfTurnLeft", "HalfTurnRight"]: + print (p3, p4) + elif obj.Flight in ["HalfTurnLeft", "HalfTurnRight"]: + print (p3r, p4r) edge = Part.LineSegment(p1,p2).toShape() From 2c191f74c73c9c6572f002393d2b53263c970211 Mon Sep 17 00:00:00 2001 From: Roy-043 <70520633+Roy-043@users.noreply.github.com> Date: Thu, 16 Feb 2023 21:09:06 +0100 Subject: [PATCH 39/42] [Arch] Arch_Stairs fix for #8444 (#8518) - Recompute should not be called from onDocumentRestored. It causes issues when updating complex stairs. --- src/Mod/Arch/ArchStairs.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Mod/Arch/ArchStairs.py b/src/Mod/Arch/ArchStairs.py index 7c1e8f725d..5d2aadc4e0 100644 --- a/src/Mod/Arch/ArchStairs.py +++ b/src/Mod/Arch/ArchStairs.py @@ -421,7 +421,6 @@ class _Stairs(ArchComponent.Component): obj.removeProperty("OutlineWireLeft") obj.removeProperty("OutlineWireRight") self.update_properties_to_0v20(obj) - doc.recompute() from draftutils.messages import _wrn _wrn("v0.20.3, " + obj.Label + ", " + translate("Arch", "removed properties 'OutlineWireLeft' and 'OutlineWireRight', and added properties 'RailingLeft' and 'RailingRight'")) @@ -436,7 +435,6 @@ class _Stairs(ArchComponent.Component): obj.RailingLeft = railingLeftObject obj.RailingRight = railingRightObject self.update_properties_to_0v20(obj) - doc.recompute() from draftutils.messages import _wrn _wrn("v0.20.3, " + obj.Label + ", " + translate("Arch", "changed the type of properties 'RailingLeft' and 'RailingRight'")) From 824f2cdc9aad00a5f369061455a82508c78f3e74 Mon Sep 17 00:00:00 2001 From: Uwe Date: Fri, 17 Feb 2023 01:48:35 +0100 Subject: [PATCH 40/42] [FEM] fix possible crash on changing the VectorMode - if pvtu file filtering is on, there is by design not on every call of WriteColorData() data (on other occasions of GetArray(array) the result is checked, for two calls is was forgotten) - also some automatic code formatting according to our current clang file --- src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp | 47 +++++++++++-------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp b/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp index 7e0ac35dde..d5bce46f92 100644 --- a/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp +++ b/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp @@ -362,8 +362,9 @@ void ViewProviderFemPostObject::updateProperties() { colorArrays.emplace_back("Not a vector"); else { int array = Field.getValue() - 1; //0 is none - vtkPolyData* pd = m_currentAlgorithm->GetOutput(); - vtkDataArray* data = pd->GetPointData()->GetArray(array); + vtkDataArray* data = point->GetArray(array); + if (!data) + return; if (data->GetNumberOfComponents() == 1) colorArrays.emplace_back("Not a vector"); @@ -494,7 +495,6 @@ void ViewProviderFemPostObject::update3D() { void ViewProviderFemPostObject::WritePointData(vtkPoints* points, vtkDataArray* normals, vtkDataArray* tcoords) { - Q_UNUSED(tcoords); if (!points) @@ -542,8 +542,8 @@ void ViewProviderFemPostObject::updateMaterial() WriteColorData(true); } -void ViewProviderFemPostObject::WriteColorData(bool ResetColorBarRange) { - +void ViewProviderFemPostObject::WriteColorData(bool ResetColorBarRange) +{ if (!setupPipeline()) return; @@ -562,6 +562,8 @@ void ViewProviderFemPostObject::WriteColorData(bool ResetColorBarRange) { int array = Field.getValue() - 1; // 0 is none vtkPolyData* pd = m_currentAlgorithm->GetOutput(); vtkDataArray* data = pd->GetPointData()->GetArray(array); + if (!data) + return; int component = VectorMode.getValue() - 1; // 0 is either "Not a vector" or magnitude, // for -1 is correct for magnitude. @@ -610,8 +612,8 @@ void ViewProviderFemPostObject::WriteColorData(bool ResetColorBarRange) { m_triangleStrips->touch(); } -void ViewProviderFemPostObject::WriteTransparency() { - +void ViewProviderFemPostObject::WriteTransparency() +{ float trans = float(Transparency.getValue()) / 100.0; m_material->transparency.setValue(trans); @@ -620,8 +622,8 @@ void ViewProviderFemPostObject::WriteTransparency() { m_triangleStrips->touch(); } -void ViewProviderFemPostObject::updateData(const App::Property* p) { - +void ViewProviderFemPostObject::updateData(const App::Property* p) +{ if (strcmp(p->getName(), "Data") == 0) { updateVtk(); } @@ -653,6 +655,7 @@ void ViewProviderFemPostObject::filterArtifacts(vtkDataObject* data) Gui::Document* doc = this->getDocument(); Gui::View3DInventor* view = qobject_cast(doc->getViewOfViewProvider(this)); + if (view) { Gui::View3DInventorViewer* viewer = view->getViewer(); SbBox3f boundingBox; @@ -683,7 +686,7 @@ void ViewProviderFemPostObject::filterArtifacts(vtkDataObject* data) this->Visibility.setValue(visibility); } - bool ViewProviderFemPostObject::setupPipeline() +bool ViewProviderFemPostObject::setupPipeline() { vtkDataObject* data = static_cast(getObject())->Data.getValue(); if (!data) @@ -718,8 +721,8 @@ void ViewProviderFemPostObject::filterArtifacts(vtkDataObject* data) return true; } -void ViewProviderFemPostObject::onChanged(const App::Property* prop) { - +void ViewProviderFemPostObject::onChanged(const App::Property* prop) +{ if (m_blockPropertyChanges) return; @@ -748,7 +751,8 @@ void ViewProviderFemPostObject::onChanged(const App::Property* prop) { ViewProviderDocumentObject::onChanged(prop); } -bool ViewProviderFemPostObject::doubleClicked() { +bool ViewProviderFemPostObject::doubleClicked() +{ // work around for a problem in VTK implementation: // https://forum.freecadweb.org/viewtopic.php?t=10587&start=130#p125688 // check if backlight is enabled @@ -764,8 +768,8 @@ bool ViewProviderFemPostObject::doubleClicked() { return true; } -bool ViewProviderFemPostObject::setEdit(int ModNum) { - +bool ViewProviderFemPostObject::setEdit(int ModNum) +{ if (ModNum == ViewProvider::Default || ModNum == 1) { Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog(); @@ -802,8 +806,8 @@ bool ViewProviderFemPostObject::setEdit(int ModNum) { } } -void ViewProviderFemPostObject::setupTaskDialog(TaskDlgPost* dlg) { - +void ViewProviderFemPostObject::setupTaskDialog(TaskDlgPost* dlg) +{ dlg->appendBox(new TaskPostDisplay(this)); } @@ -821,7 +825,8 @@ void ViewProviderFemPostObject::unsetEdit(int ModNum) { } } -void ViewProviderFemPostObject::hide() { +void ViewProviderFemPostObject::hide() +{ Gui::ViewProviderDocumentObject::hide(); m_colorStyle->style = SoDrawStyle::INVISIBLE; // The object is now hidden but the color bar is wrong @@ -856,7 +861,8 @@ void ViewProviderFemPostObject::hide() { } } -void ViewProviderFemPostObject::show() { +void ViewProviderFemPostObject::show() +{ Gui::ViewProviderDocumentObject::show(); m_colorStyle->style = SoDrawStyle::FILLED; // we must update the color bar except for data point filters @@ -864,7 +870,8 @@ void ViewProviderFemPostObject::show() { WriteColorData(true); } -void ViewProviderFemPostObject::OnChange(Base::Subject< int >& /*rCaller*/, int /*rcReason*/) { +void ViewProviderFemPostObject::OnChange(Base::Subject< int >& /*rCaller*/, int /*rcReason*/) +{ bool ResetColorBarRange = false; WriteColorData(ResetColorBarRange); } From 7d200fe120c030ae6be47d5a13dd330eb86c53e7 Mon Sep 17 00:00:00 2001 From: Roy-043 <70520633+Roy-043@users.noreply.github.com> Date: Fri, 17 Feb 2023 10:09:53 +0100 Subject: [PATCH 41/42] [Arch] Arch_Site did not handle movable children (#8510) --- src/Mod/Arch/ArchSite.py | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/src/Mod/Arch/ArchSite.py b/src/Mod/Arch/ArchSite.py index c4b16cb9df..3ef39c8aa9 100644 --- a/src/Mod/Arch/ArchSite.py +++ b/src/Mod/Arch/ArchSite.py @@ -24,7 +24,8 @@ containers for Arch objects, and also define a terrain surface. """ -import FreeCAD,Draft,ArchCommands,math,re,datetime,ArchIFC +import FreeCAD,Draft,ArchCommands,ArchComponent,math,re,datetime,ArchIFC + if FreeCAD.GuiUp: import FreeCADGui from PySide import QtGui,QtCore @@ -718,24 +719,16 @@ class _Site(ArchIFC.IfcProduct): if vobj.Proxy is not None: vobj.Proxy.updateDisplaymodeTerrainSwitches(vobj) - def onChanged(self,obj,prop): - """Method called when the object has a property changed. + def onBeforeChange(self, obj, prop): + ArchComponent.Component.onBeforeChange(self, obj, prop) - If Terrain has changed, hide the base object terrain. + def onChanged(self, obj, prop): + ArchComponent.Component.onChanged(self, obj, prop) + if prop == "Terrain" and obj.Terrain and FreeCAD.GuiUp: + obj.Terrain.ViewObject.hide() - Also call ArchIFC.IfcProduct.onChanged(). - - Parameters - ---------- - prop: string - The name of the property that has changed. - """ - - ArchIFC.IfcProduct.onChanged(self, obj, prop) - if prop == "Terrain": - if obj.Terrain: - if FreeCAD.GuiUp: - obj.Terrain.ViewObject.hide() + def getMovableChildren(self, obj): + return obj.Additions + obj.Subtractions def computeAreas(self,obj): """Compute the area, perimeter length, and volume of the terrain shape. From b5c9ba93f48f44a7edd173cbc395bdab11d68049 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Fri, 17 Feb 2023 10:51:58 +0100 Subject: [PATCH 42/42] Arch: IFC export, fix because one malformed geometry can break the whole export --- src/Mod/Arch/exportIFC.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Mod/Arch/exportIFC.py b/src/Mod/Arch/exportIFC.py index f71330a256..7ad2bbb44d 100644 --- a/src/Mod/Arch/exportIFC.py +++ b/src/Mod/Arch/exportIFC.py @@ -2237,6 +2237,11 @@ def getRepresentation( loops = [] verts = [v.Point for v in fcface.OuterWire.OrderedVertexes] c = fcface.CenterOfMass + if len(verts) < 1: + print("Warning: OuterWire returned no ordered Vertexes in ", obj.Label) + # Part.show(fcface) + # Part.show(fcsolid) + continue v1 = verts[0].sub(c) v2 = verts[1].sub(c) try: