diff --git a/src/Mod/Fem/App/AppFem.cpp b/src/Mod/Fem/App/AppFem.cpp index b8a8cb6d17..df13a6cf96 100644 --- a/src/Mod/Fem/App/AppFem.cpp +++ b/src/Mod/Fem/App/AppFem.cpp @@ -189,6 +189,7 @@ PyMOD_INIT_FUNC(Fem) Fem::FemPostPipeline ::init(); Fem::FemPostFilter ::init(); Fem::FemPostBranchFilter ::init(); + Fem::FemPostCalculatorFilter ::init(); Fem::FemPostClipFilter ::init(); Fem::FemPostContoursFilter ::init(); Fem::FemPostCutFilter ::init(); diff --git a/src/Mod/Fem/App/FemPostFilter.cpp b/src/Mod/Fem/App/FemPostFilter.cpp index dc46efa3e8..ddf41382d1 100644 --- a/src/Mod/Fem/App/FemPostFilter.cpp +++ b/src/Mod/Fem/App/FemPostFilter.cpp @@ -1297,3 +1297,132 @@ short int FemPostWarpVectorFilter::mustExecute() const return App::DocumentObject::mustExecute(); } } + + +// *************************************************************************** +// calculator filter +PROPERTY_SOURCE(Fem::FemPostCalculatorFilter, Fem::FemPostFilter) + +FemPostCalculatorFilter::FemPostCalculatorFilter() + : FemPostFilter() +{ + ADD_PROPERTY_TYPE(FieldName, + ("Calculator"), + "Calculator", + App::Prop_None, + "Name of the calculated field"); + ADD_PROPERTY_TYPE(Function, + (""), + "Calculator", + App::Prop_None, + "Expression of the unction to evaluate"); + ADD_PROPERTY_TYPE(ReplacementValue, + (0.0f), + "Calculator", + App::Prop_None, + "Value used to replace invalid operations"); + ADD_PROPERTY_TYPE(ReplaceInvalid, + (false), + "Calculator", + App::Prop_None, + "Replace invalid values"); + + FilterPipeline calculator; + m_calculator = vtkSmartPointer::New(); + m_calculator->SetResultArrayName(FieldName.getValue()); + calculator.source = m_calculator; + calculator.target = m_calculator; + addFilterPipeline(calculator, "calculator"); + setActiveFilterPipeline("calculator"); +} + +FemPostCalculatorFilter::~FemPostCalculatorFilter() = default; + +DocumentObjectExecReturn* FemPostCalculatorFilter::execute() +{ + updateAvailableFields(); + + return FemPostFilter::execute(); +} + +void FemPostCalculatorFilter::onChanged(const Property* prop) +{ + if (prop == &Function) { + m_calculator->SetFunction(Function.getValue()); + } + else if (prop == &FieldName) { + m_calculator->SetResultArrayName(FieldName.getValue()); + } + else if (prop == &ReplaceInvalid) { + m_calculator->SetReplaceInvalidValues(ReplaceInvalid.getValue()); + } + else if (prop == &ReplacementValue) { + m_calculator->SetReplacementValue(ReplacementValue.getValue()); + } + else if (prop == &Data) { + updateAvailableFields(); + } + Fem::FemPostFilter::onChanged(prop); +} + +short int FemPostCalculatorFilter::mustExecute() const +{ + if (Function.isTouched() || FieldName.isTouched()) { + return 1; + } + else { + return FemPostFilter::mustExecute(); + } +} + +void FemPostCalculatorFilter::updateAvailableFields() +{ + // clear all variables + m_calculator->RemoveAllVariables(); + m_calculator->AddCoordinateScalarVariable("coordsX", 0); + m_calculator->AddCoordinateScalarVariable("coordsY", 1); + m_calculator->AddCoordinateScalarVariable("coordsZ", 2); + m_calculator->AddCoordinateVectorVariable("coords"); + + std::vector scalars; + std::vector vectors; + // std::vector tensors; + + vtkSmartPointer data = getInputData(); + vtkDataSet* dset = vtkDataSet::SafeDownCast(data); + if (!dset) { + return; + } + vtkPointData* pd = dset->GetPointData(); + + // get all vector fields + for (int i = 0; i < pd->GetNumberOfArrays(); ++i) { + std::string name1 = pd->GetArrayName(i); + std::string name2 = name1; + std::replace(name2.begin(), name2.end(), ' ', '_'); + if (pd->GetArray(i)->GetNumberOfComponents() == 3) { + m_calculator->AddVectorVariable(name2.c_str(), name1.c_str()); + // add components as scalar variable + m_calculator->AddScalarVariable((name2 + "_X").c_str(), name1.c_str(), 0); + m_calculator->AddScalarVariable((name2 + "_Y").c_str(), name1.c_str(), 1); + m_calculator->AddScalarVariable((name2 + "_Z").c_str(), name1.c_str(), 2); + } + else if (pd->GetArray(i)->GetNumberOfComponents() == 1) { + m_calculator->AddScalarVariable(name2.c_str(), name1.c_str()); + } + } +} + +const std::vector FemPostCalculatorFilter::getScalarVariables() +{ + std::vector scalars = m_calculator->GetScalarVariableNames(); + scalars.insert(scalars.begin(), {"coordsX", "coordsY", "coordsZ"}); + return scalars; +} + +const std::vector FemPostCalculatorFilter::getVectorVariables() +{ + std::vector vectors = m_calculator->GetVectorVariableNames(); + vectors.insert(vectors.begin(), "coords"); + return vectors; +} diff --git a/src/Mod/Fem/App/FemPostFilter.h b/src/Mod/Fem/App/FemPostFilter.h index f748a4d914..d137b68bcf 100644 --- a/src/Mod/Fem/App/FemPostFilter.h +++ b/src/Mod/Fem/App/FemPostFilter.h @@ -23,6 +23,7 @@ #ifndef Fem_FemPostFilter_H #define Fem_FemPostFilter_H +#include #include #include #include @@ -372,6 +373,41 @@ private: App::Enumeration m_vectorFields; }; +// *************************************************************************** +// calculator filter +class FemExport FemPostCalculatorFilter: public FemPostFilter +{ + + PROPERTY_HEADER_WITH_OVERRIDE(Fem::FemPostCalculatorFilter); + +public: + FemPostCalculatorFilter(); + ~FemPostCalculatorFilter() override; + + App::PropertyString FieldName; + App::PropertyString Function; + App::PropertyFloat ReplacementValue; + App::PropertyBool ReplaceInvalid; + + const char* getViewProviderName() const override + { + return "FemGui::ViewProviderFemPostCalculator"; + } + short int mustExecute() const override; + + const std::vector getScalarVariables(); + const std::vector getVectorVariables(); + +protected: + App::DocumentObjectExecReturn* execute() override; + void onChanged(const App::Property* prop) override; + + void updateAvailableFields(); + +private: + vtkSmartPointer m_calculator; +}; + } // namespace Fem diff --git a/src/Mod/Fem/App/PreCompiled.h b/src/Mod/Fem/App/PreCompiled.h index b68e1b1bcf..dc915b7df7 100644 --- a/src/Mod/Fem/App/PreCompiled.h +++ b/src/Mod/Fem/App/PreCompiled.h @@ -155,6 +155,7 @@ // VTK #include #include +#include #include #include #include diff --git a/src/Mod/Fem/Gui/AppFemGui.cpp b/src/Mod/Fem/Gui/AppFemGui.cpp index cc0aa8dc7f..65364059eb 100644 --- a/src/Mod/Fem/Gui/AppFemGui.cpp +++ b/src/Mod/Fem/Gui/AppFemGui.cpp @@ -162,6 +162,7 @@ PyMOD_INIT_FUNC(FemGui) FemGui::ViewProviderFemPostObject ::init(); FemGui::ViewProviderFemPostPipeline ::init(); FemGui::ViewProviderFemPostBranchFilter ::init(); + FemGui::ViewProviderFemPostCalculator ::init(); FemGui::ViewProviderFemPostClip ::init(); FemGui::ViewProviderFemPostContours ::init(); FemGui::ViewProviderFemPostCut ::init(); diff --git a/src/Mod/Fem/Gui/CMakeLists.txt b/src/Mod/Fem/Gui/CMakeLists.txt index 6fb0572ecd..6e1686e383 100755 --- a/src/Mod/Fem/Gui/CMakeLists.txt +++ b/src/Mod/Fem/Gui/CMakeLists.txt @@ -87,6 +87,7 @@ if(BUILD_FEM_VTK) CylinderWidget.ui PlaneWidget.ui SphereWidget.ui + TaskPostCalculator.ui TaskPostClip.ui TaskPostContours.ui TaskPostCut.ui @@ -281,6 +282,7 @@ if(BUILD_FEM_VTK) SphereWidget.ui TaskPostBoxes.h TaskPostBoxes.cpp + TaskPostCalculator.ui TaskPostClip.ui TaskPostContours.ui TaskPostCut.ui diff --git a/src/Mod/Fem/Gui/Command.cpp b/src/Mod/Fem/Gui/Command.cpp index e71185cacd..3736c5bb51 100644 --- a/src/Mod/Fem/Gui/Command.cpp +++ b/src/Mod/Fem/Gui/Command.cpp @@ -2338,6 +2338,42 @@ bool CmdFemPostContoursFilter::isActive() } +//================================================================================================ +DEF_STD_CMD_A(CmdFemPostCalculatorFilter) + +CmdFemPostCalculatorFilter::CmdFemPostCalculatorFilter() + : Command("FEM_PostFilterCalculator") +{ + sAppModule = "Fem"; + sGroup = QT_TR_NOOP("Fem"); + sMenuText = QT_TR_NOOP("Calculator filter"); + sToolTipText = QT_TR_NOOP("Create new fields from current data"); + sWhatsThis = "FEM_PostFilterCalculator"; + sStatusTip = sToolTipText; + sPixmap = "FEM_PostFilterCalculator"; +} + +void CmdFemPostCalculatorFilter::activated(int) +{ + setupFilter(this, "Calculator"); +} + +bool CmdFemPostCalculatorFilter::isActive() +{ + // only allow one object + auto selection = getSelection().getSelection(); + if (selection.size() > 1) { + return false; + } + for (auto obj : selection) { + if (obj.pObject->isDerivedFrom()) { + return true; + } + } + return false; +} + + //================================================================================================ DEF_STD_CMD_ACL(CmdFemPostFunctions) @@ -2783,6 +2819,7 @@ void CreateFemCommands() // vtk post processing #ifdef FC_USE_VTK rcCmdMgr.addCommand(new CmdFemPostApllyChanges); + rcCmdMgr.addCommand(new CmdFemPostCalculatorFilter); rcCmdMgr.addCommand(new CmdFemPostClipFilter); rcCmdMgr.addCommand(new CmdFemPostContoursFilter); rcCmdMgr.addCommand(new CmdFemPostCutFilter); diff --git a/src/Mod/Fem/Gui/PreCompiled.h b/src/Mod/Fem/Gui/PreCompiled.h index ef1398cef0..41128a9151 100644 --- a/src/Mod/Fem/Gui/PreCompiled.h +++ b/src/Mod/Fem/Gui/PreCompiled.h @@ -81,6 +81,7 @@ #include #include #include +#include #include #include #include diff --git a/src/Mod/Fem/Gui/Resources/Fem.qrc b/src/Mod/Fem/Gui/Resources/Fem.qrc index dd132e54fc..e2508777a3 100755 --- a/src/Mod/Fem/Gui/Resources/Fem.qrc +++ b/src/Mod/Fem/Gui/Resources/Fem.qrc @@ -72,6 +72,7 @@ icons/FEM_MaterialSolid.svg + icons/FEM_PostFilterCalculator.svg icons/FEM_PostFilterClipRegion.svg icons/FEM_PostFilterClipScalar.svg icons/FEM_PostFilterContours.svg diff --git a/src/Mod/Fem/Gui/Resources/icons/FEM_PostFilterCalculator.svg b/src/Mod/Fem/Gui/Resources/icons/FEM_PostFilterCalculator.svg new file mode 100644 index 0000000000..ec2fa44b67 --- /dev/null +++ b/src/Mod/Fem/Gui/Resources/icons/FEM_PostFilterCalculator.svg @@ -0,0 +1,324 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + Jakub Steiner + + + + + + + + + + + calc + calculator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + f(x) + diff --git a/src/Mod/Fem/Gui/TaskPostBoxes.cpp b/src/Mod/Fem/Gui/TaskPostBoxes.cpp index e9e6f3c610..dbd9dacbc4 100644 --- a/src/Mod/Fem/Gui/TaskPostBoxes.cpp +++ b/src/Mod/Fem/Gui/TaskPostBoxes.cpp @@ -52,6 +52,7 @@ #include #include +#include "ui_TaskPostCalculator.h" #include "ui_TaskPostClip.h" #include "ui_TaskPostContours.h" #include "ui_TaskPostCut.h" @@ -323,6 +324,9 @@ void TaskDlgPost::open() void TaskDlgPost::clicked(int button) { if (button == QDialogButtonBox::Apply) { + for (auto box : m_boxes) { + box->apply(); + } recompute(); } } @@ -2123,4 +2127,130 @@ void TaskPostWarpVector::onMinValueChanged(double) } +// *************************************************************************** +// calculator filter +static const std::vector calculatorOperators = { + "+", "-", "*", "/", "-", "^", "abs", "cos", "sin", "tan", "exp", + "log", "pow", "sqrt", "iHat", "jHat", "kHat", "cross", "dot", "mag", "norm"}; + +TaskPostCalculator::TaskPostCalculator(ViewProviderFemPostCalculator* view, QWidget* parent) + : TaskPostBox(view, + Gui::BitmapFactory().pixmap("FEM_PostFilterCalculator"), + tr("Calculator options"), + parent) + , ui(new Ui_TaskPostCalculator) +{ + // we load the views widget + proxy = new QWidget(this); + ui->setupUi(proxy); + setupConnections(); + this->groupLayout()->addWidget(proxy); + + // load the default values + auto obj = getObject(); + ui->let_field_name->blockSignals(true); + ui->let_field_name->setText(QString::fromUtf8(obj->FieldName.getValue())); + ui->let_field_name->blockSignals(false); + + ui->let_function->blockSignals(true); + ui->let_function->setText(QString::fromUtf8(obj->Function.getValue())); + ui->let_function->blockSignals(false); + + ui->ckb_replace_invalid->setChecked(obj->ReplaceInvalid.getValue()); + ui->dsb_replacement_value->setEnabled(obj->ReplaceInvalid.getValue()); + ui->dsb_replacement_value->setValue(obj->ReplacementValue.getValue()); + ui->dsb_replacement_value->setMaximum(std::numeric_limits::max()); + ui->dsb_replacement_value->setMinimum(std::numeric_limits::lowest()); + + // fill available fields + for (const auto& f : obj->getScalarVariables()) { + ui->cb_scalars->addItem(QString::fromStdString(f)); + } + for (const auto& f : obj->getVectorVariables()) { + ui->cb_vectors->addItem(QString::fromStdString(f)); + } + + QStringList qOperators; + for (const auto& o : calculatorOperators) { + qOperators << QString::fromStdString(o); + } + ui->cb_operators->addItems(qOperators); + + ui->cb_scalars->setCurrentIndex(-1); + ui->cb_vectors->setCurrentIndex(-1); + ui->cb_operators->setCurrentIndex(-1); +} + +TaskPostCalculator::~TaskPostCalculator() = default; + +void TaskPostCalculator::setupConnections() +{ + connect(ui->dsb_replacement_value, + qOverload(&QDoubleSpinBox::valueChanged), + this, + &TaskPostCalculator::onReplacementValueChanged); + connect(ui->ckb_replace_invalid, + &QCheckBox::toggled, + this, + &TaskPostCalculator::onReplaceInvalidChanged); + connect(ui->cb_scalars, + qOverload(&QComboBox::activated), + this, + &TaskPostCalculator::onScalarsActivated); + connect(ui->cb_vectors, + qOverload(&QComboBox::activated), + this, + &TaskPostCalculator::onVectorsActivated); + connect(ui->cb_operators, + qOverload(&QComboBox::activated), + this, + &TaskPostCalculator::onOperatorsActivated); +} + +void TaskPostCalculator::onReplaceInvalidChanged(bool state) +{ + auto obj = static_cast(getObject()); + obj->ReplaceInvalid.setValue(state); + ui->dsb_replacement_value->setEnabled(state); + recompute(); +} + +void TaskPostCalculator::onReplacementValueChanged(double value) +{ + auto obj = static_cast(getObject()); + obj->ReplacementValue.setValue(value); + recompute(); +} + +void TaskPostCalculator::onScalarsActivated(int index) +{ + QString item = ui->cb_scalars->itemText(index); + ui->let_function->insert(item); +} + +void TaskPostCalculator::onVectorsActivated(int index) +{ + QString item = ui->cb_vectors->itemText(index); + ui->let_function->insert(item); +} + +void TaskPostCalculator::onOperatorsActivated(int index) +{ + QString item = ui->cb_operators->itemText(index); + ui->let_function->insert(item); +} + +void TaskPostCalculator::apply() +{ + auto obj = getObject(); + std::string function = ui->let_function->text().toStdString(); + std::string name = ui->let_field_name->text().toStdString(); + obj->Function.setValue(function); + obj->FieldName.setValue(name); + recompute(); + + auto view = getTypedView(); + view->Field.setValue(obj->FieldName.getValue()); +} + #include "moc_TaskPostBoxes.cpp" diff --git a/src/Mod/Fem/Gui/TaskPostBoxes.h b/src/Mod/Fem/Gui/TaskPostBoxes.h index b33729e03b..74842dcc5e 100644 --- a/src/Mod/Fem/Gui/TaskPostBoxes.h +++ b/src/Mod/Fem/Gui/TaskPostBoxes.h @@ -32,6 +32,7 @@ class QComboBox; class Ui_TaskPostDisplay; +class Ui_TaskPostCalculator; class Ui_TaskPostClip; class Ui_TaskPostContours; class Ui_TaskPostDataAlongLine; @@ -141,12 +142,15 @@ public: QWidget* parent = nullptr); ~TaskPostBox() override; - virtual void applyPythonCode() = 0; + virtual void applyPythonCode() {}; virtual bool isGuiTaskOnly() { return false; } // return true if only gui properties are manipulated + // executed when the apply button is pressed in the task dialog + virtual void apply() {}; + protected: App::DocumentObject* getObject() const { @@ -555,6 +559,35 @@ private: std::unique_ptr ui; }; + +// *************************************************************************** +// calculator filter +class ViewProviderFemPostCalculator; + +class TaskPostCalculator: public TaskPostBox +{ + Q_OBJECT + +public: + explicit TaskPostCalculator(ViewProviderFemPostCalculator* view, QWidget* parent = nullptr); + ~TaskPostCalculator() override; + +protected: + void apply() override; + +private: + void setupConnections(); + void onReplaceInvalidChanged(bool state); + void onReplacementValueChanged(double value); + void onScalarsActivated(int index); + void onVectorsActivated(int index); + void onOperatorsActivated(int index); + +private: + QWidget* proxy; + std::unique_ptr ui; +}; + } // namespace FemGui #endif // GUI_TASKVIEW_TaskPostDisplay_H diff --git a/src/Mod/Fem/Gui/TaskPostCalculator.ui b/src/Mod/Fem/Gui/TaskPostCalculator.ui new file mode 100644 index 0000000000..3d90b1eea0 --- /dev/null +++ b/src/Mod/Fem/Gui/TaskPostCalculator.ui @@ -0,0 +1,127 @@ + + + TaskPostCalculator + + + + 0 + 0 + 250 + 115 + + + + Form + + + + + + + + + + + + + + Field Name: + + + + + + + + + + + + + Mathematical expression + + + + + + + + + + + + + Available fields + + + + + + + + Scalars: + + + + + + + + + + Vectors: + + + + + + + + + + Operators: + + + + + + + + + + + + + + + + + + + + Replace invalid data: + + + Replacement value for invalid operations + + + + + + + + + + + + + + + + + Gui::DoubleSpinBox + QWidget +
Gui/SpinBox.h
+
+
+
diff --git a/src/Mod/Fem/Gui/ViewProviderFemPostFilter.cpp b/src/Mod/Fem/Gui/ViewProviderFemPostFilter.cpp index 22ce73853d..fe0ad21fcf 100644 --- a/src/Mod/Fem/Gui/ViewProviderFemPostFilter.cpp +++ b/src/Mod/Fem/Gui/ViewProviderFemPostFilter.cpp @@ -215,3 +215,38 @@ void ViewProviderFemPostWarpVector::setupTaskDialog(TaskDlgPost* dlg) // add the display options FemGui::ViewProviderFemPostObject::setupTaskDialog(dlg); } + + +// *************************************************************************** +// calculator filter +PROPERTY_SOURCE(FemGui::ViewProviderFemPostCalculator, FemGui::ViewProviderFemPostObject) + +ViewProviderFemPostCalculator::ViewProviderFemPostCalculator() +{ + sPixmap = "FEM_PostFilterCalculator"; +} + +ViewProviderFemPostCalculator::~ViewProviderFemPostCalculator() = default; + +void ViewProviderFemPostCalculator::updateData(const App::Property* prop) +{ + auto obj = getObject(); + if (prop == &obj->Data) { + // update color bar + ViewProviderFemPostObject::updateData(prop); + updateMaterial(); + } + else { + return ViewProviderFemPostObject::updateData(prop); + } +} + +void ViewProviderFemPostCalculator::setupTaskDialog(TaskDlgPost* dlg) +{ + // add the function box + assert(dlg->getView() == this); + dlg->appendBox(new TaskPostCalculator(this)); + + // 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 52194b87f1..e728e5fcd0 100644 --- a/src/Mod/Fem/Gui/ViewProviderFemPostFilter.h +++ b/src/Mod/Fem/Gui/ViewProviderFemPostFilter.h @@ -154,6 +154,24 @@ protected: void setupTaskDialog(TaskDlgPost* dlg) override; }; + +// *************************************************************************** +// calculator filter +class FemGuiExport ViewProviderFemPostCalculator: public ViewProviderFemPostObject +{ + PROPERTY_HEADER_WITH_OVERRIDE(FemGui::ViewProviderFemPostCalculator); + +public: + /// constructor. + ViewProviderFemPostCalculator(); + ~ViewProviderFemPostCalculator() override; + + void updateData(const App::Property* prop) 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 345632754e..04a08828df 100644 --- a/src/Mod/Fem/Gui/Workbench.cpp +++ b/src/Mod/Fem/Gui/Workbench.cpp @@ -209,6 +209,7 @@ Gui::ToolBarItem* Workbench::setupToolBars() const << "FEM_PostFilterDataAlongLine" << "FEM_PostFilterLinearizedStresses" << "FEM_PostFilterDataAtPoint" + << "FEM_PostFilterCalculator" << "Separator" << "FEM_PostCreateFunctions"; #endif @@ -356,6 +357,7 @@ Gui::MenuItem* Workbench::setupMenuBar() const << "FEM_PostFilterDataAlongLine" << "FEM_PostFilterLinearizedStresses" << "FEM_PostFilterDataAtPoint" + << "FEM_PostFilterCalculator" << "Separator" << "FEM_PostCreateFunctions"; #endif