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