From 1cff507a7f141b0210ae32537ef78b46ccebb574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Tr=C3=B6ger?= Date: Sun, 24 Nov 2024 16:11:53 +0100 Subject: [PATCH] FEM: Multiframe adoptions - To support timedata and the relevant filters the pipeline needs to be fully setup, hence not only working on data - Multiblock source algorithm is needed to supply the time data for the algorithms --- src/Mod/Fem/App/CMakeLists.txt | 5 + src/Mod/Fem/App/FemPostBranch.cpp | 274 ++++++++++ src/Mod/Fem/App/FemPostBranch.h | 101 ++++ src/Mod/Fem/App/FemPostBranchPy.xml | 32 ++ src/Mod/Fem/App/FemPostBranchPyImp.cpp | 94 ++++ src/Mod/Fem/App/FemPostFilter.cpp | 200 +++++--- src/Mod/Fem/App/FemPostFilter.h | 26 +- src/Mod/Fem/App/FemPostObject.cpp | 30 +- src/Mod/Fem/App/FemPostObject.h | 7 + src/Mod/Fem/App/FemPostPipeline.cpp | 473 ++++++++++++++++-- src/Mod/Fem/App/FemPostPipeline.h | 55 +- src/Mod/Fem/App/FemPostPipelinePy.xml | 2 +- src/Mod/Fem/App/FemPostPipelinePyImp.cpp | 90 +++- src/Mod/Fem/App/PropertyPostDataObject.cpp | 132 ++++- src/Mod/Fem/Gui/CMakeLists.txt | 1 + src/Mod/Fem/Gui/Command.cpp | 18 +- src/Mod/Fem/Gui/Resources/Fem.qrc | 1 + .../Gui/Resources/icons/FEM_PostFrames.svg | 108 ++++ src/Mod/Fem/Gui/TaskPostBoxes.cpp | 62 +++ src/Mod/Fem/Gui/TaskPostBoxes.h | 21 + src/Mod/Fem/Gui/TaskPostFrames.ui | 98 ++++ src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp | 8 +- .../Fem/Gui/ViewProviderFemPostPipeline.cpp | 32 +- src/Mod/Fem/Gui/ViewProviderFemPostPipeline.h | 10 +- src/Mod/Fem/Init.py | 4 +- src/Mod/Fem/ObjectsFem.py | 39 +- src/Mod/Fem/feminout/importCcxFrdResults.py | 76 ++- src/Mod/Fem/femtest/app/test_object.py | 2 +- 28 files changed, 1752 insertions(+), 249 deletions(-) create mode 100644 src/Mod/Fem/App/FemPostBranch.cpp create mode 100644 src/Mod/Fem/App/FemPostBranch.h create mode 100644 src/Mod/Fem/App/FemPostBranchPy.xml create mode 100644 src/Mod/Fem/App/FemPostBranchPyImp.cpp create mode 100644 src/Mod/Fem/Gui/Resources/icons/FEM_PostFrames.svg create mode 100644 src/Mod/Fem/Gui/TaskPostFrames.ui diff --git a/src/Mod/Fem/App/CMakeLists.txt b/src/Mod/Fem/App/CMakeLists.txt index ad10535cb9..dff8238603 100644 --- a/src/Mod/Fem/App/CMakeLists.txt +++ b/src/Mod/Fem/App/CMakeLists.txt @@ -67,9 +67,12 @@ if(BUILD_FEM_VTK) FemPostObjectPyImp.cpp FemPostPipelinePy.xml FemPostPipelinePyImp.cpp + FemPostBranchPy.xml + FemPostBranchPyImp.cpp ) generate_from_xml(FemPostObjectPy) generate_from_xml(FemPostPipelinePy) + generate_from_xml(FemPostBranchPy) endif(BUILD_FEM_VTK) SOURCE_GROUP("Python" FILES ${Python_SRCS}) @@ -82,6 +85,8 @@ if(BUILD_FEM_VTK) FemPostObject.cpp FemPostPipeline.h FemPostPipeline.cpp + FemPostBranch.h + FemPostBranch.cpp FemPostFilter.h FemPostFilter.cpp FemPostFunction.h diff --git a/src/Mod/Fem/App/FemPostBranch.cpp b/src/Mod/Fem/App/FemPostBranch.cpp new file mode 100644 index 0000000000..39f486eddc --- /dev/null +++ b/src/Mod/Fem/App/FemPostBranch.cpp @@ -0,0 +1,274 @@ +/*************************************************************************** + * Copyright (c) 2015 Stefan Tröger * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + +#include "PreCompiled.h" + +#ifndef _PreComp_ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#include + +#include "FemMesh.h" +#include "FemMeshObject.h" +#include "FemPostPipeline.h" +#include "FemPostBranch.h" +#include "FemPostBranchPy.h" +#include "FemVTKTools.h" + + +using namespace Fem; +using namespace App; + +PROPERTY_SOURCE(Fem::FemPostBranch, Fem::FemPostFilter) +const char* FemPostBranch::ModeEnums[] = {"Serial", "Parallel", nullptr}; +const char* FemPostBranch::OutputEnums[] = {"Passthrough", "Append", nullptr}; + +FemPostBranch::FemPostBranch() : Fem::FemPostFilter(), App::GroupExtension() +{ + GroupExtension::initExtension(this); + + ADD_PROPERTY_TYPE(Mode, + (long(0)), + "Branch", + App::Prop_None, + "Selects which input the child filters of the branch receive\n" + "In serial the first filter receives the branch input, and the concecitive ones get the prior filter output.\n" + "In parallel, every filter receives the branch input."); + ADD_PROPERTY_TYPE(Output, + (long(0)), + "Branch", + App::Prop_None, + "Selects what the output of the branch itself is\n" + "In passthrough the branchs output is equal its imput.\n" + "In append, all filters outputs gets appended as the branches output"); + + Mode.setEnums(ModeEnums); + Output.setEnums(OutputEnums); + + /* We always have a passthrough filter. This allows to connect our children + * dependend on the Mode setting, without worrying about the connection to our input + * filter. We do not care if the input filter changes, as this is affecting only the passthrough + * input and does not affect our child connections. + * Dependent on our output mode, the passthrough is also used as output, but potentially + * the append filter is used. in this case our children need to be connected into the append filter. + * Here the same holds as before: Append filter output can be connected to arbitrary other filters + * in the pipeline, not affecting our internal connections to our children. + */ + + m_append = vtkSmartPointer::New(); + m_passthrough = vtkSmartPointer::New(); + + FilterPipeline passthrough; + passthrough.source = m_passthrough; + passthrough.target = m_passthrough; + addFilterPipeline(passthrough, "passthrough"); + + FilterPipeline append; + append.source = m_passthrough; + append.target = m_append; + addFilterPipeline(append, "append"); + + setActiveFilterPipeline("passthrough"); +} + +FemPostBranch::~FemPostBranch() = default; + +short FemPostBranch::mustExecute() const +{ + if (Mode.isTouched()) { + return 1; + } + + return FemPostFilter::mustExecute(); +} + + +void FemPostBranch::onChanged(const Property* prop) +{ + /* onChanged handles the Pipeline setup: we connect the inputs and outputs + * of our child filters correctly according to the new settings + */ + + if (prop == &Group || prop == &Mode) { + + + // we check if all connections are right and add new ones if needed + std::vector objs = Group.getValues(); + + if (objs.empty()) { + return; + } + + // prepare output filter: we make all connections new! + m_append->RemoveAllInputConnections(0); + + FemPostFilter* filter = NULL; + std::vector::iterator it = objs.begin(); + for (; it != objs.end(); ++it) { + + // prepare the filter: make all connections new + FemPostFilter* nextFilter = static_cast(*it); + nextFilter->getActiveFilterPipeline().source->RemoveAllInputConnections(0); + + // handle input modes + if (Mode.getValue() == 0) { + // serial: the next filter gets the previous output, the first one gets our input + if (filter == NULL) { + nextFilter->getActiveFilterPipeline().source->SetInputConnection(m_passthrough->GetOutputPort()); + } else { + nextFilter->getActiveFilterPipeline().source->SetInputConnection(filter->getActiveFilterPipeline().target->GetOutputPort()); + } + + } + else if (Mode.getValue() == 1) { + // parallel: all filters get out input + nextFilter->getActiveFilterPipeline().source->SetInputConnection(m_passthrough->GetOutputPort()); + } + + // handle append filter + m_append->AddInputConnection(0, nextFilter->getActiveFilterPipeline().target->GetOutputPort()); + + filter = nextFilter; + }; + } + + if (prop == &Frame) { + //Update all children with the new step + for (const auto& obj : Group.getValues()) { + if (obj->isDerivedFrom(FemPostFilter::getClassTypeId())) { + static_cast(obj)->Frame.setValue(Frame.getValue()); + } + } + } + + if (prop == &Output) { + if (Output.getValue() == 0) { + setActiveFilterPipeline("passthrough"); + } + else { + setActiveFilterPipeline("append"); + } + } + + FemPostFilter::onChanged(prop); +} + +void FemPostBranch::filterChanged(FemPostFilter* filter) +{ + //we only need to update the following children if we are in serial mode + if (Mode.getValue() == 0) { + + std::vector objs = Group.getValues(); + + if (objs.empty()) { + return; + } + bool started = false; + std::vector::iterator it = objs.begin(); + for (; it != objs.end(); ++it) { + + if (started) { + (*it)->touch(); + } + + if (*it == filter) { + started = true; + } + } + } + + // if we append as output, we need to inform the parent object that we are isTouched + if (Output.getValue() == 1) { + //make sure we inform our parent object that we changed, it then can inform others if needed + App::DocumentObject* group = App::GroupExtension::getGroupOfObject(this); + if (!group) { + return; + } + + if (group->isDerivedFrom(Fem::FemPostPipeline::getClassTypeId())) { + auto pipe = dynamic_cast(group); + pipe->filterChanged(this); + } + else if (group->isDerivedFrom(Fem::FemPostBranch::getClassTypeId())) { + auto branch = dynamic_cast(group); + branch->filterChanged(this); + } + } +} + +void FemPostBranch::pipelineChanged(FemPostFilter* filter) { + // one of our filters has changed its active pipeline. We need to reconnect it properly. + // As we are cheap we just reconnect everything + // TODO: Do more efficiently + onChanged(&Group); +} + +void FemPostBranch::recomputeChildren() +{ + for (const auto& obj : Group.getValues()) { + obj->touch(); + } +} + +FemPostObject* FemPostBranch::getLastPostObject() +{ + + if (Group.getValues().empty()) { + return this; + } + + return static_cast(Group.getValues().back()); +} + +bool FemPostBranch::holdsPostObject(FemPostObject* obj) +{ + + std::vector::const_iterator it = Group.getValues().begin(); + for (; it != Group.getValues().end(); ++it) { + + if (*it == obj) { + return true; + } + } + return false; +} + +PyObject* FemPostBranch::getPyObject() +{ + if (PythonObject.is(Py::_None())) { + // ref counter is set to 1 + PythonObject = Py::Object(new FemPostBranchPy(this), true); + } + return Py::new_reference_to(PythonObject); +} diff --git a/src/Mod/Fem/App/FemPostBranch.h b/src/Mod/Fem/App/FemPostBranch.h new file mode 100644 index 0000000000..a9ff238018 --- /dev/null +++ b/src/Mod/Fem/App/FemPostBranch.h @@ -0,0 +1,101 @@ +/*************************************************************************** + * Copyright (c) 2024 Stefan Tröger * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + +#ifndef Fem_FemPostBranch_H +#define Fem_FemPostBranch_H + +#include "App/GroupExtension.h" + +#include "FemPostFilter.h" +#include "FemPostFunction.h" +#include "FemPostObject.h" +#include "FemResultObject.h" + +#include +#include +#include + + +namespace Fem +{ + +class FemExport FemPostBranch: public Fem::FemPostFilter, public App::GroupExtension +{ + PROPERTY_HEADER_WITH_EXTENSIONS(Fem::FemPostBranch); + +public: + /// Constructor + FemPostBranch(); + ~FemPostBranch() override; + + App::PropertyEnumeration Mode; + App::PropertyEnumeration Output; + + + short mustExecute() const override; + PyObject* getPyObject() override; + + const char* getViewProviderName() const override + { + return "FemGui::ViewProviderFemPostBranch"; + } + + // load data from files + static bool canRead(Base::FileInfo file); + void read(Base::FileInfo file); + void scale(double s); + + // load from results + void load(FemResultObject* res); + + // Branch handling + void filterChanged(FemPostFilter* filter); + void pipelineChanged(FemPostFilter* filter); + void recomputeChildren(); + FemPostObject* getLastPostObject(); + bool holdsPostObject(FemPostObject* obj); + +protected: + void onChanged(const App::Property* prop) override; + +private: + static const char* ModeEnums[]; + static const char* OutputEnums[]; + + vtkSmartPointer m_append; + vtkSmartPointer m_passthrough; + + template + void readXMLFile(std::string file) + { + + vtkSmartPointer reader = vtkSmartPointer::New(); + reader->SetFileName(file.c_str()); + reader->Update(); + Data.setValue(reader->GetOutput()); + } +}; + +} // namespace Fem + + +#endif // Fem_FemPostBranch_H diff --git a/src/Mod/Fem/App/FemPostBranchPy.xml b/src/Mod/Fem/App/FemPostBranchPy.xml new file mode 100644 index 0000000000..73f7325548 --- /dev/null +++ b/src/Mod/Fem/App/FemPostBranchPy.xml @@ -0,0 +1,32 @@ + + + + + + The FemPostBranch class. + + + + Recomputes all children of the pipeline + + + + + Get the last post-processing object + + + + + Check if this pipeline holds a given post-processing object + + + + diff --git a/src/Mod/Fem/App/FemPostBranchPyImp.cpp b/src/Mod/Fem/App/FemPostBranchPyImp.cpp new file mode 100644 index 0000000000..cab55fc171 --- /dev/null +++ b/src/Mod/Fem/App/FemPostBranchPyImp.cpp @@ -0,0 +1,94 @@ +/*************************************************************************** + * Copyright (c) 2017 Werner Mayer * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + +#include "PreCompiled.h" +#ifndef _PreComp_ +#include +#endif + +#include + +// clang-format off +#include "FemPostBranch.h" +#include "FemPostBranchPy.h" +#include "FemPostBranchPy.cpp" +// clang-format on + + +using namespace Fem; + +// returns a string which represents the object e.g. when printed in python +std::string FemPostBranchPy::representation() const +{ + return {""}; +} + + +PyObject* FemPostBranchPy::recomputeChildren(PyObject* args) +{ + if (!PyArg_ParseTuple(args, "")) { + return nullptr; + } + + getFemPostBranchPtr()->recomputeChildren(); + Py_Return; +} + +PyObject* FemPostBranchPy::getLastPostObject(PyObject* args) +{ + if (!PyArg_ParseTuple(args, "")) { + return nullptr; + } + + App::DocumentObject* obj = getFemPostBranchPtr()->getLastPostObject(); + if (obj) { + return obj->getPyObject(); + } + Py_Return; +} + +PyObject* FemPostBranchPy::holdsPostObject(PyObject* args) +{ + PyObject* py; + if (!PyArg_ParseTuple(args, "O!", &(App::DocumentObjectPy::Type), &py)) { + return nullptr; + } + + App::DocumentObject* obj = static_cast(py)->getDocumentObjectPtr(); + if (!obj->isDerivedFrom()) { + PyErr_SetString(PyExc_TypeError, "object is not a post-processing object"); + return nullptr; + } + + bool ok = getFemPostBranchPtr()->holdsPostObject(static_cast(obj)); + return Py_BuildValue("O", (ok ? Py_True : Py_False)); +} + +PyObject* FemPostBranchPy::getCustomAttributes(const char* /*attr*/) const +{ + return nullptr; +} + +int FemPostBranchPy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/) +{ + return 0; +} diff --git a/src/Mod/Fem/App/FemPostFilter.cpp b/src/Mod/Fem/App/FemPostFilter.cpp index 1e2bc4f4ac..ccd3327608 100644 --- a/src/Mod/Fem/App/FemPostFilter.cpp +++ b/src/Mod/Fem/App/FemPostFilter.cpp @@ -26,12 +26,16 @@ #include #include #include +#include +#include #endif #include +#include #include "FemPostFilter.h" #include "FemPostPipeline.h" +#include "FemPostBranch.h" using namespace Fem; @@ -42,7 +46,12 @@ PROPERTY_SOURCE(Fem::FemPostFilter, Fem::FemPostObject) FemPostFilter::FemPostFilter() { - ADD_PROPERTY(Input, (nullptr)); + ADD_PROPERTY_TYPE(Frame, + ((long)0), + "Data", + App::Prop_ReadOnly, + "The step used to calculate the data"); + } FemPostFilter::~FemPostFilter() = default; @@ -60,57 +69,130 @@ FemPostFilter::FilterPipeline& FemPostFilter::getFilterPipeline(std::string name void FemPostFilter::setActiveFilterPipeline(std::string name) { if (m_activePipeline != name && isValid()) { + + // disable all inputs of current pipeline + if (m_activePipeline != "" and m_pipelines.find( m_activePipeline ) != m_pipelines.end()) { + m_pipelines[m_activePipeline].source->RemoveAllInputConnections(0); + } + + //set the new pipeline active m_activePipeline = name; + + //inform our parent, that we need to be connected a new + App::DocumentObject* group = App::GroupExtension::getGroupOfObject(this); + if (!group) { + return; + } + + if (group->isDerivedFrom(Fem::FemPostPipeline::getClassTypeId())) { + auto pipe = dynamic_cast(group); + pipe->pipelineChanged(this); + } + else if (group->isDerivedFrom(Fem::FemPostBranch::getClassTypeId())) { + auto branch = dynamic_cast(group); + branch->pipelineChanged(this); + } } } +FemPostFilter::FilterPipeline& FemPostFilter::getActiveFilterPipeline() +{ + return m_pipelines[m_activePipeline]; +} + +void FemPostFilter::onChanged(const App::Property* prop) +{ + //make sure we inform our parent object that we changed, it then can inform others if needed + App::DocumentObject* group = App::GroupExtension::getGroupOfObject(this); + if (!group) { + return FemPostObject::onChanged(prop); + } + + if (group->isDerivedFrom(Fem::FemPostPipeline::getClassTypeId())) { + auto pipe = dynamic_cast(group); + pipe->filterChanged(this); + } + else if (group->isDerivedFrom(Fem::FemPostBranch::getClassTypeId())) { + auto branch = dynamic_cast(group); + branch->filterChanged(this); + } + + return FemPostObject::onChanged(prop); +} + + DocumentObjectExecReturn* FemPostFilter::execute() { + + // the pipelines are setup correctly, all we need to do is to update and take out the data. if (!m_pipelines.empty() && !m_activePipeline.empty()) { FemPostFilter::FilterPipeline& pipe = m_pipelines[m_activePipeline]; - vtkSmartPointer data = getInputData(); - if (!data || !data->IsA("vtkDataSet")) { + + if (pipe.source->GetNumberOfInputConnections(0) == 0) { return StdReturn; } - if ((m_activePipeline == "DataAlongLine") || (m_activePipeline == "DataAtPoint")) { - pipe.filterSource->SetSourceData(getInputData()); - pipe.filterTarget->Update(); - Data.setValue(pipe.filterTarget->GetOutputDataObject(0)); + if (Frame.getValue()>0) { + pipe.target->UpdateTimeStep(Frame.getValue()); } else { - pipe.source->SetInputDataObject(data); pipe.target->Update(); - Data.setValue(pipe.target->GetOutputDataObject(0)); } + Data.setValue(pipe.target->GetOutputDataObject(0)); } return StdReturn; } -vtkDataObject* FemPostFilter::getInputData() -{ - if (Input.getValue()) { - if (Input.getValue()->isDerivedFrom()) { - return Input.getValue()->Data.getValue(); - } - else { - throw std::runtime_error( - "The filter's Input object is not a 'Fem::FemPostObject' object!"); - } +vtkSmartPointer FemPostFilter::getInputData() { + + if (getActiveFilterPipeline().source->GetNumberOfInputConnections(0) == 0) { + return nullptr; } - else { - // get the pipeline and use the pipelinedata - std::vector objs = - getDocument()->getObjectsOfType(FemPostPipeline::getClassTypeId()); - for (auto it : objs) { - if (static_cast(it)->holdsPostObject(this)) { - return static_cast(it)->Data.getValue(); - } + + vtkAlgorithmOutput* output = getActiveFilterPipeline().source->GetInputConnection(0,0); + vtkAlgorithm* algo = output->GetProducer(); + algo->Update(); + + return vtkDataSet::SafeDownCast(algo->GetOutputDataObject(0)); +} + +std::vector FemPostFilter::getInputVectorFields() +{ + vtkDataSet* dset = getInputData(); + if (!dset) { + return std::vector(); + } + vtkPointData* pd = dset->GetPointData(); + + // get all vector fields + std::vector VectorArray; + for (int i = 0; i < pd->GetNumberOfArrays(); ++i) { + if (pd->GetArray(i)->GetNumberOfComponents() == 3) { + VectorArray.emplace_back(pd->GetArrayName(i)); } } - return nullptr; + return VectorArray; +} + +std::vector FemPostFilter::getInputScalarFields() +{ + vtkDataSet* dset = getInputData(); + if (!dset) { + return std::vector(); + } + vtkPointData* pd = dset->GetPointData(); + + // get all scalar fields + std::vector ScalarArray; + for (int i = 0; i < pd->GetNumberOfArrays(); ++i) { + if (pd->GetArray(i)->GetNumberOfComponents() == 1) { + ScalarArray.emplace_back(pd->GetArrayName(i)); + } + } + + return ScalarArray; } // *************************************************************************** @@ -172,7 +254,9 @@ FemPostDataAlongLineFilter::FemPostDataAlongLineFilter() m_line->SetResolution(Resolution.getValue()); + auto passthrough = vtkSmartPointer::New(); m_probe = vtkSmartPointer::New(); + m_probe->SetSourceConnection(passthrough->GetOutputPort(0)); m_probe->SetInputConnection(m_line->GetOutputPort()); m_probe->SetValidPointMaskArrayName("ValidPointArray"); m_probe->SetPassPointArrays(1); @@ -183,8 +267,8 @@ FemPostDataAlongLineFilter::FemPostDataAlongLineFilter() m_probe->SetTolerance(0.01); #endif - clip.filterSource = m_probe; - clip.filterTarget = m_probe; + clip.source = passthrough; + clip.target = m_probe; addFilterPipeline(clip, "DataAlongLine"); setActiveFilterPipeline("DataAlongLine"); @@ -343,7 +427,9 @@ FemPostDataAtPointFilter::FemPostDataAtPointFilter() m_point->SetCenter(vec.x, vec.y, vec.z); m_point->SetRadius(0); + auto passthrough = vtkSmartPointer::New(); m_probe = vtkSmartPointer::New(); + m_probe->SetSourceConnection(passthrough->GetOutputPort(0)); m_probe->SetInputConnection(m_point->GetOutputPort()); m_probe->SetValidPointMaskArrayName("ValidPointArray"); m_probe->SetPassPointArrays(1); @@ -354,8 +440,8 @@ FemPostDataAtPointFilter::FemPostDataAtPointFilter() m_probe->SetTolerance(0.01); #endif - clip.filterSource = m_probe; - clip.filterTarget = m_probe; + clip.source = passthrough; + clip.target = m_probe; addFilterPipeline(clip, "DataAtPoint"); setActiveFilterPipeline("DataAtPoint"); @@ -660,8 +746,7 @@ DocumentObjectExecReturn* FemPostContoursFilter::execute() auto returnObject = Fem::FemPostFilter::execute(); // delete contour field - vtkSmartPointer data = getInputData(); - vtkDataSet* dset = vtkDataSet::SafeDownCast(data); + vtkDataSet* dset = getInputData(); if (!dset) { return returnObject; } @@ -692,8 +777,7 @@ void FemPostContoursFilter::onChanged(const Property* prop) double p[2]; // get the field and its data - vtkSmartPointer data = getInputData(); - vtkDataSet* dset = vtkDataSet::SafeDownCast(data); + vtkDataSet* dset = getInputData(); if (!dset) { return; } @@ -807,8 +891,7 @@ void FemPostContoursFilter::refreshFields() std::vector FieldsArray; - vtkSmartPointer data = getInputData(); - vtkDataSet* dset = vtkDataSet::SafeDownCast(data); + vtkDataSet* dset = getInputData(); if (!dset) { m_blockPropertyChanges = false; return; @@ -838,6 +921,7 @@ void FemPostContoursFilter::refreshFields() } m_blockPropertyChanges = false; + } void FemPostContoursFilter::refreshVectors() @@ -845,8 +929,7 @@ void FemPostContoursFilter::refreshVectors() // refreshes the list of available vectors for the current Field m_blockPropertyChanges = true; - vtkSmartPointer data = getInputData(); - vtkDataSet* dset = vtkDataSet::SafeDownCast(data); + vtkDataSet* dset = getInputData(); if (!dset) { m_blockPropertyChanges = false; return; @@ -981,21 +1064,7 @@ DocumentObjectExecReturn* FemPostScalarClipFilter::execute() val = Scalars.getValueAsString(); } - std::vector ScalarsArray; - - vtkSmartPointer data = getInputData(); - vtkDataSet* dset = vtkDataSet::SafeDownCast(data); - if (!dset) { - return StdReturn; - } - 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)); - } - } + std::vector ScalarsArray = getInputScalarFields(); App::Enumeration empty; Scalars.setValue(empty); @@ -1044,8 +1113,7 @@ short int FemPostScalarClipFilter::mustExecute() const void FemPostScalarClipFilter::setConstraintForField() { - vtkSmartPointer data = getInputData(); - vtkDataSet* dset = vtkDataSet::SafeDownCast(data); + vtkDataSet* dset = getInputData(); if (!dset) { return; } @@ -1094,26 +1162,14 @@ FemPostWarpVectorFilter::~FemPostWarpVectorFilter() = default; DocumentObjectExecReturn* FemPostWarpVectorFilter::execute() { + Base::Console().Message("Warp Execute\n"); + std::string val; if (Vector.getValue() >= 0) { val = Vector.getValueAsString(); } - std::vector VectorArray; - - vtkSmartPointer data = getInputData(); - vtkDataSet* dset = vtkDataSet::SafeDownCast(data); - if (!dset) { - return StdReturn; - } - 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)); - } - } + std::vector VectorArray = getInputVectorFields(); App::Enumeration empty; Vector.setValue(empty); diff --git a/src/Mod/Fem/App/FemPostFilter.h b/src/Mod/Fem/App/FemPostFilter.h index 8c7dd4420c..73d9b5e5bf 100644 --- a/src/Mod/Fem/App/FemPostFilter.h +++ b/src/Mod/Fem/App/FemPostFilter.h @@ -49,23 +49,15 @@ class FemExport FemPostFilter: public Fem::FemPostObject { PROPERTY_HEADER_WITH_OVERRIDE(Fem::FemPostFilter); -public: - /// Constructor - FemPostFilter(); - ~FemPostFilter() override; - - App::PropertyLink Input; - - App::DocumentObjectExecReturn* execute() override; - protected: - vtkDataObject* getInputData(); + vtkSmartPointer getInputData(); + std::vector getInputVectorFields(); + std::vector getInputScalarFields(); // pipeline handling for derived filter struct FilterPipeline { vtkSmartPointer source, target; - vtkSmartPointer filterSource, filterTarget; std::vector> algorithmStorage; }; @@ -73,6 +65,18 @@ protected: void setActiveFilterPipeline(std::string name); FilterPipeline& getFilterPipeline(std::string name); +public: + /// Constructor + FemPostFilter(); + ~FemPostFilter() override; + + App::PropertyFloat Frame; + + void onChanged(const App::Property* prop) override; + App::DocumentObjectExecReturn* execute() override; + + FilterPipeline& getActiveFilterPipeline(); + private: // handling of multiple pipelines which can be the filter std::map m_pipelines; diff --git a/src/Mod/Fem/App/FemPostObject.cpp b/src/Mod/Fem/App/FemPostObject.cpp index fc1bafa39f..6f4c717361 100644 --- a/src/Mod/Fem/App/FemPostObject.cpp +++ b/src/Mod/Fem/App/FemPostObject.cpp @@ -25,6 +25,7 @@ #ifndef _PreComp_ #include #include +#include #endif #include @@ -46,12 +47,26 @@ FemPostObject::FemPostObject() FemPostObject::~FemPostObject() = default; +vtkDataSet* FemPostObject::getDataSet() { + + if (!Data.getValue()) { + return nullptr; + } + + if (Data.getValue()->IsA("vtkDataSet")) { + return vtkDataSet::SafeDownCast(Data.getValue()); + } + + // we could be a composite dataset... hope that our subclasses handle this, + // as this should only be possible for input data (So FemPostPipeline) + return nullptr; +} + vtkBoundingBox FemPostObject::getBoundingBox() { vtkBoundingBox box; - - vtkDataSet* dset = vtkDataSet::SafeDownCast(Data.getValue()); + vtkDataSet* dset = getDataSet(); if (dset) { box.AddBounds(dset->GetBounds()); } @@ -77,7 +92,7 @@ namespace template void femVTKWriter(const char* filename, const vtkSmartPointer& dataObject) { - if (vtkDataSet::SafeDownCast(dataObject)->GetNumberOfPoints() <= 0) { + if (dataObject->IsA("vtkDataSet") && vtkDataSet::SafeDownCast(dataObject)->GetNumberOfPoints() <= 0) { throw Base::ValueError("Empty data object"); } @@ -107,6 +122,9 @@ std::string vtkWriterExtension(const vtkSmartPointer& dataObject) case VTK_UNIFORM_GRID: extension = "vti"; break; + case VTK_MULTIBLOCK_DATA_SET: + extension = "vtm"; + break; default: break; } @@ -135,5 +153,9 @@ void FemPostObject::writeVTK(const char* filename) const name = name.append(".").append(extension); } - femVTKWriter(name.c_str(), data); + if (extension == "vtm") { + femVTKWriter(name.c_str(), data); + } else { + femVTKWriter(name.c_str(), data); + } } diff --git a/src/Mod/Fem/App/FemPostObject.h b/src/Mod/Fem/App/FemPostObject.h index 5f70614a7c..25c9fbfe6c 100644 --- a/src/Mod/Fem/App/FemPostObject.h +++ b/src/Mod/Fem/App/FemPostObject.h @@ -27,6 +27,7 @@ #include "PropertyPostDataObject.h" #include #include +#include namespace Fem @@ -45,6 +46,12 @@ public: Fem::PropertyPostDataObject Data; + // returns the DataSet from the data property. Better use this + // instead of casting Data.getValue(), as data does not need to be a dataset, + // but could for example also be a composite data structure. + // Could return NULL if no dataset is available + virtual vtkDataSet* getDataSet(); + PyObject* getPyObject() override; vtkBoundingBox getBoundingBox(); diff --git a/src/Mod/Fem/App/FemPostPipeline.cpp b/src/Mod/Fem/App/FemPostPipeline.cpp index 16b49c289f..a675f86a09 100644 --- a/src/Mod/Fem/App/FemPostPipeline.cpp +++ b/src/Mod/Fem/App/FemPostPipeline.cpp @@ -36,9 +36,17 @@ #include #include #include +#include +#include +#include +#include +#include +#include #endif #include +#include +#include #include "FemMesh.h" #include "FemMeshObject.h" @@ -51,54 +59,201 @@ using namespace Fem; using namespace App; -PROPERTY_SOURCE(Fem::FemPostPipeline, Fem::FemPostObject) -const char* FemPostPipeline::ModeEnums[] = {"Serial", "Parallel", "Custom", nullptr}; -FemPostPipeline::FemPostPipeline() +vtkStandardNewMacro(FemFrameSourceAlgorithm); + +FemFrameSourceAlgorithm::FemFrameSourceAlgorithm::FemFrameSourceAlgorithm() { - ADD_PROPERTY_TYPE(Filter, - (nullptr), - "Pipeline", - App::Prop_None, - "The filter used in this pipeline"); + // we are a source + SetNumberOfInputPorts(0); + SetNumberOfOutputPorts(1); +} + + +FemFrameSourceAlgorithm::FemFrameSourceAlgorithm::~FemFrameSourceAlgorithm() +{ +} + +void FemFrameSourceAlgorithm::setDataObject(vtkSmartPointer data ) { + m_data = data; + Update(); +} + + +std::vector FemFrameSourceAlgorithm::getFrameValues() +{ + + // check if we have frame data + if (!m_data || !m_data->IsA("vtkMultiBlockDataSet")) { + return std::vector(); + } + + // we have multiple frames! let's check the amount and times + vtkSmartPointer multiblock = vtkMultiBlockDataSet::SafeDownCast(m_data); + + unsigned long nblocks = multiblock->GetNumberOfBlocks(); + std::vector tFrames(nblocks); + + for (unsigned long i=0; iGetBlock(i); + // check if the TimeValue field is available + if (!block->GetFieldData()->HasArray("TimeValue")) { + break; + } + + //store the time value! + vtkDataArray* TimeValue = block->GetFieldData()->GetArray("TimeValue"); + if (!TimeValue->IsA("vtkFloatArray") || + TimeValue->GetNumberOfTuples() < 1) { + break; + } + + tFrames[i] = vtkFloatArray::SafeDownCast(TimeValue)->GetValue(0); + } + + if (tFrames.size() != nblocks) { + // not every block had time data + return std::vector(); + } + + return tFrames; +} + +int FemFrameSourceAlgorithm::RequestInformation(vtkInformation*reqInfo, vtkInformationVector **inVector, vtkInformationVector* outVector) +{ + + if (!this->Superclass::RequestInformation(reqInfo, inVector, outVector)) + { + return 0; + } + + + std::stringstream strm; + outVector->Print(strm); + + std::vector frames = getFrameValues(); + + if (frames.empty()) { + return 1; + } + + double tRange[2] = {frames.front(), frames.back()}; + double tFrames[frames.size()]; + std::copy(frames.begin(), frames.end(), tFrames); + + // finally set the time info! + vtkInformation* info = outVector->GetInformationObject(0); + info->Set(vtkStreamingDemandDrivenPipeline::TIME_RANGE(), tRange, 2); + info->Set(vtkStreamingDemandDrivenPipeline::TIME_STEPS(), tFrames, frames.size()); + info->Set(CAN_HANDLE_PIECE_REQUEST(), 1); + + return 1; +} + +int FemFrameSourceAlgorithm::RequestData(vtkInformation* reqInfo, vtkInformationVector** inVector, vtkInformationVector* outVector) +{ + + std::stringstream strstm; + outVector->Print(strstm); + + vtkInformation* outInfo = outVector->GetInformationObject(0); + vtkUnstructuredGrid* output = vtkUnstructuredGrid::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT())); + + if (!output || !m_data) { + return 0; + } + + if (!m_data->IsA("vtkMultiBlockDataSet")) { + // no multi frame data, return directly + outInfo->Set(vtkDataObject::DATA_OBJECT(), m_data); + return 1; + } + + vtkSmartPointer multiblock = vtkMultiBlockDataSet::SafeDownCast(m_data); + // find the block asked for (lazy implementation) + unsigned long idx = 0; + if (outInfo->Has(vtkStreamingDemandDrivenPipeline::UPDATE_TIME_STEP())) + { + auto time = outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_TIME_STEP()); + auto frames = getFrameValues(); + + // we have float values, so be aware of roundign erros. lets subtract the searched time and then use the smalles value + for(auto& frame : frames) + frame = std::abs(frame-time); + + auto it = std::min_element(std::begin(frames), std::end(frames)); + idx = std::distance(std::begin(frames), it); + } + + auto block = multiblock->GetBlock(idx); + output->ShallowCopy(block); + return 1; +} + + + +PROPERTY_SOURCE(Fem::FemPostPipeline, Fem::FemPostObject) +const char* FemPostPipeline::ModeEnums[] = {"Serial", "Parallel", nullptr}; + +FemPostPipeline::FemPostPipeline() : Fem::FemPostObject(), App::GroupExtension() +{ + GroupExtension::initExtension(this); + ADD_PROPERTY_TYPE(Functions, (nullptr), "Pipeline", App::Prop_Hidden, "The function provider which groups all pipeline functions"); ADD_PROPERTY_TYPE(Mode, - (long(2)), + (long(0)), "Pipeline", App::Prop_None, "Selects the pipeline data transition mode.\n" "In serial, every filter gets the output of the previous one as input.\n" - "In parallel, every filter gets the pipeline source as input.\n" - "In custom, every filter keeps its input set by the user."); + "In parallel, every filter gets the pipeline source as input.\n"); + ADD_PROPERTY_TYPE(Frame, + (long(0)), + "Pipeline", + App::Prop_None, + "The frame used to calculate the data in the pipeline processing (read only, set via pipeline object)."); + Mode.setEnums(ModeEnums); + + // create our source algorithm + m_source_algorithm = vtkSmartPointer::New(); } FemPostPipeline::~FemPostPipeline() = default; short FemPostPipeline::mustExecute() const { - if (Mode.isTouched()) { + if (Mode.isTouched() ) { return 1; } return FemPostObject::mustExecute(); } -DocumentObjectExecReturn* FemPostPipeline::execute() -{ - return Fem::FemPostObject::execute(); -} +vtkDataSet* FemPostPipeline::getDataSet() { + vtkDataObject* data = m_source_algorithm->GetOutputDataObject(0); + if (!data) { + return nullptr; + } + + if (data->IsA("vtkDataSet")) { + return vtkDataSet::SafeDownCast(data); + } + + return nullptr; +} bool FemPostPipeline::canRead(Base::FileInfo File) { // from FemResult only unstructural mesh is supported in femvtktoools.cpp - return File.hasExtension({"vtk", "vtp", "vts", "vtr", "vti", "vtu", "pvtu"}); + return File.hasExtension({"vtk", "vtp", "vts", "vtr", "vti", "vtu", "pvtu", "vtm"}); } void FemPostPipeline::read(Base::FileInfo File) @@ -130,6 +285,9 @@ void FemPostPipeline::read(Base::FileInfo File) else if (File.hasExtension("vtk")) { readXMLFile(File.filePath()); } + else if (File.hasExtension("vtm")) { + readXMLFile(File.filePath()); + } else { throw Base::FileException("Unknown extension"); } @@ -138,79 +296,180 @@ void FemPostPipeline::read(Base::FileInfo File) void FemPostPipeline::scale(double s) { Data.scale(s); + onChanged(&Data); } void FemPostPipeline::onChanged(const Property* prop) { - if (prop == &Filter || prop == &Mode) { + /* onChanged handles the Pipeline setup: we connect the inputs and outputs + * of our child filters correctly according to the new settings + */ - // if we are in custom mode the user is free to set the input - // thus nothing needs to be done here - if (Mode.getValue() == 2) { // custom - return; + + // use the correct data as source + if (prop == &Data) { + m_source_algorithm->setDataObject(Data.getValue()); + + // change the frame enum to correct values + std::string val; + if (Frame.hasEnums() && Frame.getValue() >= 0) { + val = Frame.getValueAsString(); } + std::vector frames = m_source_algorithm->getFrameValues(); + std::vector frame_values; + if (frames.empty()) { + frame_values.push_back("No frames available"); + } + else { + auto unit = getFrameUnit(); + for (const double& frame : frames) { + auto quantity = Base::Quantity(frame, unit); + frame_values.push_back(quantity.getUserString().toStdString()); + } + } + + App::Enumeration empty; + Frame.setValue(empty); + m_frameEnum.setEnums(frame_values); + Frame.setValue(m_frameEnum); + + std::vector::iterator it = std::find(frame_values.begin(), frame_values.end(), val); + if (!val.empty() && it != frame_values.end()) { + Frame.setValue(val.c_str()); + } + + Frame.purgeTouched(); + recomputeChildren(); + } + + if (prop == &Frame) { + + // update the algorithm for the visulization + auto frames = getFrameValues(); + if (!frames.empty() && + Frame.getValue() < long(frames.size())) { + + double time = frames[Frame.getValue()]; + m_source_algorithm->UpdateTimeStep(time); + } + + // inform the downstream pipeline + recomputeChildren(); + } + + + // connect all filters correctly to the source + if (prop == &Group || prop == &Mode) { + // we check if all connections are right and add new ones if needed - std::vector objs = Filter.getValues(); + std::vector objs = Group.getValues(); if (objs.empty()) { return; } + FemPostFilter* filter = NULL; std::vector::iterator it = objs.begin(); - FemPostFilter* filter = static_cast(*it); - - // the first filter must always grab the data - if (filter->Input.getValue()) { - filter->Input.setValue(nullptr); - } - - // all the others need to be connected to the previous filter or grab the data, - // dependent on mode - ++it; for (; it != objs.end(); ++it) { - auto* nextFilter = static_cast(*it); - if (Mode.getValue() == 0) { // serial mode - if (nextFilter->Input.getValue() != filter) { - nextFilter->Input.setValue(filter); + // prepare the filter: make all connections new + FemPostFilter* nextFilter = static_cast(*it); + nextFilter->getActiveFilterPipeline().source->RemoveAllInputConnections(0); + + // handle input modes + if (Mode.getValue() == 0) { + // serial: the next filter gets the previous output, the first one gets our input + if (filter == NULL) { + nextFilter->getActiveFilterPipeline().source->SetInputConnection(m_source_algorithm->GetOutputPort(0)); + } else { + nextFilter->getActiveFilterPipeline().source->SetInputConnection(filter->getActiveFilterPipeline().target->GetOutputPort()); } + } - else { // Parallel mode - if (nextFilter->Input.getValue()) { - nextFilter->Input.setValue(nullptr); - } + else if (Mode.getValue() == 1) { + // parallel: all filters get out input + nextFilter->getActiveFilterPipeline().source->SetInputConnection(m_source_algorithm->GetOutputPort(0)); + } + else { + throw Base::ValueError("Unknown Mode set for Pipeline"); } filter = nextFilter; }; } - App::GeoFeature::onChanged(prop); + FemPostObject::onChanged(prop); + +} + +void FemPostPipeline::filterChanged(FemPostFilter* filter) +{ + //we only need to update the following children if we are in serial mode + if (Mode.getValue() == 0) { + + std::vector objs = Group.getValues(); + + if (objs.empty()) { + return; + } + bool started = false; + std::vector::iterator it = objs.begin(); + for (; it != objs.end(); ++it) { + + if (started) { + (*it)->touch(); + } + + if (*it == filter) { + started = true; + } + } + } +} + +void FemPostPipeline::pipelineChanged(FemPostFilter* filter) { + // one of our filters has changed its active pipeline. We need to reconnect it properly. + // As we are cheap we just reconnect everything + // TODO: Do more efficiently + onChanged(&Group); } void FemPostPipeline::recomputeChildren() { - for (const auto& obj : Filter.getValues()) { + // get the frame we use + double frame = 0; + auto frames = getFrameValues(); + if (!frames.empty() && + Frame.getValue() < frames.size()) { + + frame = frames[Frame.getValue()]; + } + + for (const auto& obj : Group.getValues()) { obj->touch(); + + if (obj->isDerivedFrom(FemPostFilter::getClassTypeId())) { + static_cast(obj)->Frame.setValue(frame); + } } } FemPostObject* FemPostPipeline::getLastPostObject() { - if (Filter.getValues().empty()) { + if (Group.getValues().empty()) { return this; } - return static_cast(Filter.getValues().back()); + return static_cast(Group.getValues().back()); } bool FemPostPipeline::holdsPostObject(FemPostObject* obj) { - std::vector::const_iterator it = Filter.getValues().begin(); - for (; it != Filter.getValues().end(); ++it) { + std::vector::const_iterator it = Group.getValues().begin(); + for (; it != Group.getValues().end(); ++it) { if (*it == obj) { return true; @@ -219,6 +478,79 @@ bool FemPostPipeline::holdsPostObject(FemPostObject* obj) return false; } + + +bool FemPostPipeline::hasFrames() +{ + // lazy implementation + return !m_source_algorithm->getFrameValues().empty(); +} + +std::string FemPostPipeline::getFrameType() +{ + vtkSmartPointer data = Data.getValue(); + + // check if we have frame data + if (!data || !data->IsA("vtkMultiBlockDataSet")) { + return std::string("no frames"); + } + + // we have multiple frames! let's check the amount and times + vtkSmartPointer multiblock = vtkMultiBlockDataSet::SafeDownCast(data); + if (!multiblock->GetFieldData()->HasArray("TimeInfo")) { + return std::string("unknown"); + } + + vtkAbstractArray* TimeInfo = multiblock->GetFieldData()->GetAbstractArray("TimeInfo"); + if (!TimeInfo || + !TimeInfo->IsA("vtkStringArray") || + TimeInfo->GetNumberOfTuples() < 2) { + + return std::string("unknown"); + } + + return vtkStringArray::SafeDownCast(TimeInfo)->GetValue(0); +} + +Base::Unit FemPostPipeline::getFrameUnit() +{ + vtkSmartPointer data = Data.getValue(); + + // check if we have frame data + if (!data || !data->IsA("vtkMultiBlockDataSet")) { + // units cannot be undefined, so use time + return Base::Unit::TimeSpan; + } + + // we have multiple frames! let's check the amount and times + vtkSmartPointer multiblock = vtkMultiBlockDataSet::SafeDownCast(data); + if (!multiblock->GetFieldData()->HasArray("TimeInfo")) { + // units cannot be undefined, so use time + return Base::Unit::TimeSpan; + } + + vtkAbstractArray* TimeInfo = multiblock->GetFieldData()->GetAbstractArray("TimeInfo"); + if (!TimeInfo->IsA("vtkStringArray") || + TimeInfo->GetNumberOfTuples() < 2) { + + // units cannot be undefined, so use time + return Base::Unit::TimeSpan; + } + + return Base::Unit(QString::fromStdString(vtkStringArray::SafeDownCast(TimeInfo)->GetValue(1))); +} + +std::vector FemPostPipeline::getFrameValues() +{ + return m_source_algorithm->getFrameValues(); +} + +unsigned int FemPostPipeline::getFrameNumber() +{ + // lazy implementation + return getFrameValues().size(); +} + void FemPostPipeline::load(FemResultObject* res) { if (!res->Mesh.getValue()) { @@ -243,6 +575,53 @@ void FemPostPipeline::load(FemResultObject* res) Data.setValue(grid); } +// set multiple result objects as frames for one pipeline +// Notes: +// 1. values vector must contain growing value, smallest first +void FemPostPipeline::load(std::vector res, std::vector values, Base::Unit unit, std::string frame_type) { + + if (res.size() != values.size() ) { + Base::Console().Error("Result values and frame values have different length.\n"); + return; + } + + // setup the time information for the multiblock + vtkStringArray* TimeInfo = vtkStringArray::New(); + TimeInfo->SetName("TimeInfo"); + TimeInfo->InsertNextValue(frame_type); + TimeInfo->InsertNextValue(unit.getString().toStdString()); + + auto multiblock = vtkSmartPointer::New(); + for (ulong i=0; iMesh.getValue()->isDerivedFrom(Fem::FemMeshObject::getClassTypeId())) { + Base::Console().Error("Result mesh object is not derived from Fem::FemMeshObject.\n"); + return; + } + + // first copy the mesh over + const FemMesh& mesh = static_cast(res[i]->Mesh.getValue())->FemMesh.getValue(); + vtkSmartPointer grid = vtkSmartPointer::New(); + FemVTKTools::exportVTKMesh(&mesh, grid); + + // Now copy the point data over + FemVTKTools::exportFreeCADResult(res[i], grid); + + // add time information + vtkFloatArray* TimeValue = vtkFloatArray::New(); + TimeValue->SetNumberOfComponents(1); + TimeValue->SetName("TimeValue"); + TimeValue->InsertNextValue(values[i]); + grid->GetFieldData()->AddArray(TimeValue); + grid->GetFieldData()->AddArray(TimeInfo); + + multiblock->SetBlock(i, grid); + } + + multiblock->GetFieldData()->AddArray(TimeInfo); + Data.setValue(multiblock); +} + PyObject* FemPostPipeline::getPyObject() { if (PythonObject.is(Py::_None())) { diff --git a/src/Mod/Fem/App/FemPostPipeline.h b/src/Mod/Fem/App/FemPostPipeline.h index 2f5cb260da..19f6c3628f 100644 --- a/src/Mod/Fem/App/FemPostPipeline.h +++ b/src/Mod/Fem/App/FemPostPipeline.h @@ -23,31 +23,66 @@ #ifndef Fem_FemPostPipeline_H #define Fem_FemPostPipeline_H +<<<<<<< HEAD + +======= +#include "Base/Unit.h" +>>>>>>> 782848c556 (FEM: Make multistep work for eigenmodes) +#include "App/GroupExtension.h" + +#include "FemPostFilter.h" #include "FemPostFunction.h" #include "FemPostObject.h" #include "FemResultObject.h" #include +#include +#include +#include namespace Fem { -class FemExport FemPostPipeline: public Fem::FemPostObject +// algorithm that allows multi frame handling: if data is stored in MultiBlock dataset +// this source enables the downstream filters to query the blocks as different time frames +class FemFrameSourceAlgorithm : public vtkUnstructuredGridAlgorithm { - PROPERTY_HEADER_WITH_OVERRIDE(Fem::FemPostPipeline); +public: + static FemFrameSourceAlgorithm* New(); + vtkTypeMacro(FemFrameSourceAlgorithm, vtkUnstructuredGridAlgorithm); + + void setDataObject(vtkSmartPointer data); + std::vector getFrameValues(); + +protected: + FemFrameSourceAlgorithm(); + ~FemFrameSourceAlgorithm() override; + + vtkSmartPointer m_data; + + int RequestInformation(vtkInformation* reqInfo, vtkInformationVector** inVector, vtkInformationVector* outVector) override; + int RequestData(vtkInformation* reqInfo, vtkInformationVector** inVector, vtkInformationVector* outVector) override; +}; + + +class FemExport FemPostPipeline: public Fem::FemPostObject, public App::GroupExtension +{ + PROPERTY_HEADER_WITH_EXTENSIONS(Fem::FemPostPipeline); public: /// Constructor FemPostPipeline(); ~FemPostPipeline() override; - App::PropertyLinkList Filter; App::PropertyLink Functions; App::PropertyEnumeration Mode; + App::PropertyEnumeration Frame; + + + virtual vtkDataSet* getDataSet() override; short mustExecute() const override; - App::DocumentObjectExecReturn* execute() override; PyObject* getPyObject() override; const char* getViewProviderName() const override @@ -62,17 +97,29 @@ public: // load from results void load(FemResultObject* res); + void load(std::vector res, std::vector values, Base::Unit unit, std::string frame_type); // Pipeline handling + void filterChanged(FemPostFilter* filter); + void pipelineChanged(FemPostFilter* filter); void recomputeChildren(); FemPostObject* getLastPostObject(); bool holdsPostObject(FemPostObject* obj); + // frame handling + bool hasFrames(); + std::string getFrameType(); + Base::Unit getFrameUnit(); + unsigned int getFrameNumber(); + std::vector getFrameValues(); + protected: void onChanged(const App::Property* prop) override; private: static const char* ModeEnums[]; + App::Enumeration m_frameEnum; + vtkSmartPointer m_source_algorithm; template void readXMLFile(std::string file) diff --git a/src/Mod/Fem/App/FemPostPipelinePy.xml b/src/Mod/Fem/App/FemPostPipelinePy.xml index 3b84e84d75..47b1710f5d 100644 --- a/src/Mod/Fem/App/FemPostPipelinePy.xml +++ b/src/Mod/Fem/App/FemPostPipelinePy.xml @@ -25,7 +25,7 @@ - Load a result object + Load a single result object or create a multiframe result by loading multiple result frames. If multistep is wanted, 4 argumenhts are needed: 1. List of result object each being one frame, 2. List of values valid for each frame (e.g. [s] if time data), 3. the unit of the value, 4. the Description of the frames diff --git a/src/Mod/Fem/App/FemPostPipelinePyImp.cpp b/src/Mod/Fem/App/FemPostPipelinePyImp.cpp index 69e650e72e..fd26f3f60c 100644 --- a/src/Mod/Fem/App/FemPostPipelinePyImp.cpp +++ b/src/Mod/Fem/App/FemPostPipelinePyImp.cpp @@ -26,6 +26,7 @@ #endif #include +#include // clang-format off #include "FemPostPipeline.h" @@ -65,18 +66,87 @@ PyObject* FemPostPipelinePy::scale(PyObject* args) PyObject* FemPostPipelinePy::load(PyObject* args) { - PyObject* py; - if (!PyArg_ParseTuple(args, "O!", &(App::DocumentObjectPy::Type), &py)) { - return nullptr; + PyObject *py; + PyObject *list = nullptr; + PyObject *unitobj = nullptr; + const char* value_type; + + if (PyArg_ParseTuple(args, "O|OO!s", &py, &list, &(Base::UnitPy::Type), &unitobj, &value_type)) { + + if (list == nullptr) { + + // single argument version! + + if (!PyObject_TypeCheck(py, &(App::DocumentObjectPy::Type))) { + PyErr_SetString(PyExc_TypeError, "object is not a result object"); + return nullptr; + } + App::DocumentObject* obj = static_cast(py)->getDocumentObjectPtr(); + if (!obj->isDerivedFrom()) { + PyErr_SetString(PyExc_TypeError, "object is not a result object"); + return nullptr; + } + + getFemPostPipelinePtr()->load(static_cast(obj)); + Py_Return; + } + else if (list != nullptr && unitobj != nullptr) { + + //multistep version! + + if ( !(PyTuple_Check(py) || PyList_Check(py)) || + !(PyTuple_Check(list) || PyList_Check(list)) ) { + + std::string error = std::string("Result and value must be list of ResultObjet and number respectively."); + throw Base::TypeError(error); + } + + // extract the result objects + Py::Sequence result_list(py); + Py::Sequence::size_type size = result_list.size(); + std::vector results; + results.resize(size); + + for (Py::Sequence::size_type i = 0; i < size; i++) { + Py::Object item = result_list[i]; + if (!PyObject_TypeCheck(*item, &(DocumentObjectPy::Type))) { + std::string error = std::string("type in result list must be 'ResultObject', not "); + throw Base::TypeError(error); + } + auto obj = static_cast(*item)->getDocumentObjectPtr(); + if (!obj->isDerivedFrom()) { + throw Base::TypeError("object is not a result object"); + } + results[i] = static_cast(obj); + } + + //extract the values + Py::Sequence values_list(list); + size = values_list.size(); + std::vector values; + values.resize(size); + + for (Py::Sequence::size_type i = 0; i < size; i++) { + Py::Object item = values_list[i]; + if (!PyFloat_Check(*item)) { + std::string error = std::string("Values must be float"); + throw Base::TypeError(error); + } + values[i] = PyFloat_AsDouble(*item); + } + + // extract the unit + Base::Unit unit = *(static_cast(unitobj)->getUnitPtr()); + + // extract the value type + std::string step_type = std::string(value_type); + + // Finally call the c++ function! + getFemPostPipelinePtr()->load(results, values, unit, step_type); + Py_Return; + } } - App::DocumentObject* obj = static_cast(py)->getDocumentObjectPtr(); - if (!obj->isDerivedFrom()) { - PyErr_SetString(PyExc_TypeError, "object is not a result object"); - return nullptr; - } - - getFemPostPipelinePtr()->load(static_cast(obj)); Py_Return; } diff --git a/src/Mod/Fem/App/PropertyPostDataObject.cpp b/src/Mod/Fem/App/PropertyPostDataObject.cpp index 41e5146569..4fbe6e1c18 100644 --- a/src/Mod/Fem/App/PropertyPostDataObject.cpp +++ b/src/Mod/Fem/App/PropertyPostDataObject.cpp @@ -33,6 +33,8 @@ #include #include #include +#include +#include #include #include #include @@ -49,6 +51,12 @@ #include #include + +#ifdef _MSC_VER +#include +#endif +#include + #include "PropertyPostDataObject.h" @@ -246,11 +254,11 @@ void PropertyPostDataObject::getPaths(std::vector& /*path void PropertyPostDataObject::Save(Base::Writer& writer) const { - std::string extension; if (!m_dataObject) { return; } + std::string extension; switch (m_dataObject->GetDataObjectType()) { case VTK_POLY_DATA: @@ -268,16 +276,9 @@ void PropertyPostDataObject::Save(Base::Writer& writer) const case VTK_UNIFORM_GRID: extension = "vti"; // image data break; - // TODO:multi-datasets use multiple files, this needs to be implemented specially - // case VTK_COMPOSITE_DATA_SET: - // prop->m_dataObject = vtkCompositeDataSet::New(); - // break; - // case VTK_MULTIBLOCK_DATA_SET: - // prop->m_dataObject = vtkMultiBlockDataSet::New(); - // break; - // case VTK_MULTIPIECE_DATA_SET: - // prop->m_dataObject = vtkMultiPieceDataSet::New(); - // break; + case VTK_MULTIBLOCK_DATA_SET: + extension = "zip"; + break; default: break; }; @@ -297,13 +298,29 @@ void PropertyPostDataObject::Restore(Base::XMLReader& reader) } std::string file(reader.getAttribute("file")); - if (!file.empty()) { // initiate a file read reader.addFile(file.c_str(), this); } } +void add_to_zip(Base::FileInfo path, int zip_path_idx, zipios::ZipOutputStream& ZipWriter) { + + if (path.isDir()) { + for(auto file : path.getDirectoryContent()) { + add_to_zip(file, zip_path_idx, ZipWriter); + } + } + else { + ZipWriter.putNextEntry(path.filePath().substr(zip_path_idx)); + Base::ifstream file(path, std::ios::in | std::ios::binary); + if (file) { + std::streambuf* buf = file.rdbuf(); + ZipWriter << buf; + } + } +} + void PropertyPostDataObject::SaveDocFile(Base::Writer& writer) const { // If the shape is empty we simply store nothing. The file size will be 0 which @@ -315,12 +332,41 @@ void PropertyPostDataObject::SaveDocFile(Base::Writer& writer) const // create a temporary file and copy the content to the zip stream // once the tmp. filename is known use always the same because otherwise // we may run into some problems on the Linux platform - static Base::FileInfo fi(App::Application::getTempFileName()); + static Base::FileInfo fi = Base::FileInfo(App::Application::getTempFileName()); + bool success = false; - vtkSmartPointer xmlWriter = vtkSmartPointer::New(); - xmlWriter->SetInputDataObject(m_dataObject); - xmlWriter->SetFileName(fi.filePath().c_str()); - xmlWriter->SetDataModeToBinary(); + + if (m_dataObject->IsA("vtkMultiBlockDataSet")) { + + // create a tmp directory to write in + auto datafolder = Base::FileInfo(App::Application::getTempPath() + "vtk_datadir"); + datafolder.createDirectories(); + auto datafile = Base::FileInfo(datafolder.filePath() + "/datafile.vtm"); + + //create the data: vtm file and subfolder with the subsequent data files + auto xmlWriter = vtkSmartPointer::New(); + xmlWriter->SetInputDataObject(m_dataObject); + xmlWriter->SetFileName(datafile.filePath().c_str()); + xmlWriter->SetDataModeToBinary(); + success = xmlWriter->Write() == 1; + + if (success) { + // ZIP file we store all data in + zipios::ZipOutputStream ZipWriter(fi.filePath()); + ZipWriter.putNextEntry("dummy"); //need to add a dummy first, as the read stream always omits the first entry for unknown reasons + add_to_zip(datafolder, datafolder.filePath().length(), ZipWriter); + ZipWriter.close(); + datafolder.deleteDirectoryRecursive(); + } + + } + else { + auto xmlWriter = vtkSmartPointer::New(); + xmlWriter->SetInputDataObject(m_dataObject); + xmlWriter->SetFileName(fi.filePath().c_str()); + xmlWriter->SetDataModeToBinary(); + success = xmlWriter->Write() == 1; + } #ifdef VTK_CELL_ARRAY_V2 // Looks like an invalid data object that causes a crash with vtk9 @@ -331,7 +377,7 @@ void PropertyPostDataObject::SaveDocFile(Base::Writer& writer) const } #endif - if (xmlWriter->Write() != 1) { + if (!success) { // Note: Do NOT throw an exception here because if the tmp. file could // not be created we should not abort. // We only print an error message but continue writing the next files to the @@ -352,6 +398,7 @@ void PropertyPostDataObject::SaveDocFile(Base::Writer& writer) const writer.addError(ss.str()); } + Base::ifstream file(fi, std::ios::in | std::ios::binary); if (file) { std::streambuf* buf = file.rdbuf(); @@ -368,6 +415,7 @@ void PropertyPostDataObject::RestoreDocFile(Base::Reader& reader) Base::FileInfo xml(reader.getFileName()); // create a temporary file and copy the content from the zip stream Base::FileInfo fi(App::Application::getTempFileName()); + Base::FileInfo fo; // read in the ASCII file and write back to the file stream Base::ofstream file(fi, std::ios::out | std::ios::binary); @@ -402,11 +450,50 @@ void PropertyPostDataObject::RestoreDocFile(Base::Reader& reader) else if (extension == "vti") { xmlReader = vtkSmartPointer::New(); } + else if (extension == "zip") { + + // first unzip the file into a datafolder + zipios::ZipInputStream ZipReader(fi.filePath()); + fo = Base::FileInfo(App::Application::getTempPath() + "vtk_extract_datadir"); + fo.createDirectories(); + + try { + zipios::ConstEntryPointer entry = ZipReader.getNextEntry(); + while(entry->isValid()) { + Base::FileInfo entry_path(fo.filePath() + entry->getName()); + if (entry->isDirectory()) { + // seems not to be called + entry_path.createDirectories(); + } + else { + auto entry_dir = Base::FileInfo(entry_path.dirPath()); + if(!entry_dir.exists()) { + entry_dir.createDirectories(); + } + + Base::ofstream file(entry_path, std::ios::out | std::ios::binary); + std::streambuf* buf = file.rdbuf(); + ZipReader >> buf; + file.flush(); + file.close(); + } + entry = ZipReader.getNextEntry(); + } + } + catch (const std::exception&) { + // there is no further entry + } + + // create the reader, and change the file for it to read. Also delete zip file, not needed anymore + fi.deleteFile(); + fi = Base::FileInfo(fo.filePath() + "/datafile.vtm"); + xmlReader = vtkSmartPointer::New(); + } xmlReader->SetFileName(fi.filePath().c_str()); xmlReader->Update(); - if (!xmlReader->GetOutputAsDataSet()) { + if (!xmlReader->GetOutputDataObject(0)) { // Note: Do NOT throw an exception here because if the tmp. created file could // not be read it's NOT an indication for an invalid input stream 'reader'. // We only print an error message but continue reading the next files from the @@ -425,12 +512,15 @@ void PropertyPostDataObject::RestoreDocFile(Base::Reader& reader) } else { aboutToSetValue(); - createDataObjectByExternalType(xmlReader->GetOutputAsDataSet()); - m_dataObject->DeepCopy(xmlReader->GetOutputAsDataSet()); + createDataObjectByExternalType(xmlReader->GetOutputDataObject(0)); + m_dataObject->DeepCopy(xmlReader->GetOutputDataObject(0)); hasSetValue(); } } // delete the temp file fi.deleteFile(); + if (xml.extension() == "zip") { + fo.deleteDirectoryRecursive(); + } } diff --git a/src/Mod/Fem/Gui/CMakeLists.txt b/src/Mod/Fem/Gui/CMakeLists.txt index fbeac0d06a..c9fc4f5bfc 100755 --- a/src/Mod/Fem/Gui/CMakeLists.txt +++ b/src/Mod/Fem/Gui/CMakeLists.txt @@ -95,6 +95,7 @@ if(BUILD_FEM_VTK) TaskPostDisplay.ui TaskPostScalarClip.ui TaskPostWarpVector.ui + TaskPostFrames.ui ) endif(BUILD_FEM_VTK) diff --git a/src/Mod/Fem/Gui/Command.cpp b/src/Mod/Fem/Gui/Command.cpp index 80807034a5..b4cddba94c 100644 --- a/src/Mod/Fem/Gui/Command.cpp +++ b/src/Mod/Fem/Gui/Command.cpp @@ -1871,13 +1871,8 @@ void setupFilter(Gui::Command* cmd, std::string Name) FeatName.c_str()); // add it as subobject to the pipeline cmd->doCommand(Gui::Command::Doc, - "__list__ = App.ActiveDocument.%s.Filter", - pipeline->getNameInDocument()); - cmd->doCommand(Gui::Command::Doc, "__list__.append(App.ActiveDocument.%s)", FeatName.c_str()); - cmd->doCommand(Gui::Command::Doc, - "App.ActiveDocument.%s.Filter = __list__", - pipeline->getNameInDocument()); - cmd->doCommand(Gui::Command::Doc, "del __list__"); + "App.ActiveDocument.%s.addObject(App.ActiveDocument.%s)", + pipeline->getNameInDocument(), FeatName.c_str()); // set display to assure the user sees the new object cmd->doCommand(Gui::Command::Doc, @@ -1888,23 +1883,20 @@ void setupFilter(Gui::Command* cmd, std::string Name) cmd->doCommand(Gui::Command::Doc, "App.activeDocument().ActiveObject.ViewObject.SelectionStyle = \"BoundBox\""); - // in case selObject is no pipeline we must set it as input object auto objFilter = App::GetApplication().getActiveDocument()->getActiveObject(); auto femFilter = static_cast(objFilter); - if (!selectionIsPipeline) { - femFilter->Input.setValue(selObject); - } - femFilter->Data.setValue(static_cast(selObject)->Data.getValue()); auto selObjectView = static_cast( Gui::Application::Instance->getViewProvider(selObject)); - cmd->doCommand(Gui::Command::Doc, + //TODO: FIX + /*cmd->doCommand(Gui::Command::Doc, "App.activeDocument().ActiveObject.ViewObject.Field = \"%s\"", selObjectView->Field.getValueAsString()); cmd->doCommand(Gui::Command::Doc, "App.activeDocument().ActiveObject.ViewObject.VectorMode = \"%s\"", selObjectView->VectorMode.getValueAsString()); + */ // hide selected filter if (!femFilter->isDerivedFrom() diff --git a/src/Mod/Fem/Gui/Resources/Fem.qrc b/src/Mod/Fem/Gui/Resources/Fem.qrc index 6342a0f9e5..d92c070cba 100755 --- a/src/Mod/Fem/Gui/Resources/Fem.qrc +++ b/src/Mod/Fem/Gui/Resources/Fem.qrc @@ -80,6 +80,7 @@ icons/FEM_PostFilterDataAtPoint.svg icons/FEM_PostFilterLinearizedStresses.svg icons/FEM_PostFilterWarp.svg + icons/FEM_PostFrames.svg icons/FEM_PostPipelineFromResult.svg icons/FEM_ResultShow.svg icons/FEM_ResultsPurge.svg diff --git a/src/Mod/Fem/Gui/Resources/icons/FEM_PostFrames.svg b/src/Mod/Fem/Gui/Resources/icons/FEM_PostFrames.svg new file mode 100644 index 0000000000..7bb690a987 --- /dev/null +++ b/src/Mod/Fem/Gui/Resources/icons/FEM_PostFrames.svg @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + image/svg+xml + + + + [Alexander Gryson] + + + 2017-03-11 + https://www.freecad.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 d7b235fd5c..e1d74124f5 100644 --- a/src/Mod/Fem/Gui/TaskPostBoxes.cpp +++ b/src/Mod/Fem/Gui/TaskPostBoxes.cpp @@ -59,6 +59,8 @@ #include "ui_TaskPostDisplay.h" #include "ui_TaskPostScalarClip.h" #include "ui_TaskPostWarpVector.h" +#include "ui_TaskPostFrames.h" + #include "FemSettings.h" #include "TaskPostBoxes.h" @@ -473,6 +475,66 @@ void TaskPostFunction::applyPythonCode() } +// *************************************************************************** +// Frames +TaskPostFrames::TaskPostFrames(ViewProviderFemPostObject* view, QWidget* parent) + : TaskPostBox(view, + Gui::BitmapFactory().pixmap("FEM_PostFrames"), + tr("Result Frames"), + parent), ui(new Ui_TaskPostFrames) +{ + // we load the views widget + proxy = new QWidget(this); + ui->setupUi(proxy); + this->groupLayout()->addWidget(proxy); + setupConnections(); + + // populate the data + auto pipeline = static_cast(getObject()); + ui->Type->setText(QString::fromStdString(pipeline->getFrameType())); + + auto unit = pipeline->getFrameUnit(); + auto steps = pipeline->getFrameValues(); + for (unsigned long i=0; iFrameTable->rowCount(); + ui->FrameTable->insertRow (rowIdx); + ui->FrameTable->setItem(rowIdx, 0, idx); + ui->FrameTable->setItem(rowIdx, 1, value); + } + ui->FrameTable->selectRow(pipeline->Frame.getValue()); +} + +TaskPostFrames::~TaskPostFrames() = default; + +void TaskPostFrames::setupConnections() +{ + connect(ui->FrameTable, + qOverload<>(&QTableWidget::itemSelectionChanged), + this, + &TaskPostFrames::onSelectionChanged); +} + +void TaskPostFrames::onSelectionChanged() +{ + auto selection = ui->FrameTable->selectedItems(); + if (selection.count() > 0) { + static_cast(getObject())->Frame.setValue(selection.front()->row()); + recompute(); + } +} + + + +void TaskPostFrames::applyPythonCode() +{ + // we apply the views widgets python code +} + + + // *************************************************************************** // in the following, the different filters sorted alphabetically // *************************************************************************** diff --git a/src/Mod/Fem/Gui/TaskPostBoxes.h b/src/Mod/Fem/Gui/TaskPostBoxes.h index 45f70f3f81..85c0b350fc 100644 --- a/src/Mod/Fem/Gui/TaskPostBoxes.h +++ b/src/Mod/Fem/Gui/TaskPostBoxes.h @@ -40,6 +40,7 @@ class Ui_TaskPostDataAtPoint; class Ui_TaskPostScalarClip; class Ui_TaskPostWarpVector; class Ui_TaskPostCut; +class Ui_TaskPostFrames; class SoFontStyle; class SoText2; @@ -276,6 +277,26 @@ public: void applyPythonCode() override; }; +// *************************************************************************** +// steps +class TaskPostFrames: public TaskPostBox +{ + Q_OBJECT + +public: + explicit TaskPostFrames(ViewProviderFemPostObject* view, QWidget* parent = nullptr); + ~TaskPostFrames() override; + + void applyPythonCode() override; + +private: + void setupConnections(); + void onSelectionChanged(); + + QWidget* proxy; + std::unique_ptr ui; +}; + // *************************************************************************** // in the following, the different filters sorted alphabetically diff --git a/src/Mod/Fem/Gui/TaskPostFrames.ui b/src/Mod/Fem/Gui/TaskPostFrames.ui new file mode 100644 index 0000000000..a799044291 --- /dev/null +++ b/src/Mod/Fem/Gui/TaskPostFrames.ui @@ -0,0 +1,98 @@ + + + TaskPostFrames + + + + 0 + 0 + 400 + 232 + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Form + + + + + + + + Type of frames: + + + + + + + Ressonance Frequencies + + + + + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + 2 + + + false + + + true + + + false + + + + Frame + + + + + Value + + + + + + + + + diff --git a/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp b/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp index de0fd1bdd0..63d2c24966 100644 --- a/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp +++ b/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp @@ -850,15 +850,9 @@ bool ViewProviderFemPostObject::setupPipeline() auto postObject = getObject(); - vtkDataObject* data = postObject->Data.getValue(); - if (!data) { - return false; - } - // check all fields if there is a real/imaginary one and if so // add a field with an absolute value - vtkSmartPointer SPdata = data; - vtkDataSet* dset = vtkDataSet::SafeDownCast(SPdata); + vtkDataSet* dset = postObject->getDataSet(); if (!dset) { return false; } diff --git a/src/Mod/Fem/Gui/ViewProviderFemPostPipeline.cpp b/src/Mod/Fem/Gui/ViewProviderFemPostPipeline.cpp index 88791e2d9a..83ee439429 100644 --- a/src/Mod/Fem/Gui/ViewProviderFemPostPipeline.cpp +++ b/src/Mod/Fem/Gui/ViewProviderFemPostPipeline.cpp @@ -37,6 +37,7 @@ #include "ViewProviderFemPostFunction.h" #include "ViewProviderFemPostPipeline.h" #include "ViewProviderFemPostPipelinePy.h" +#include "TaskPostBoxes.h" using namespace FemGui; @@ -45,6 +46,7 @@ PROPERTY_SOURCE(FemGui::ViewProviderFemPostPipeline, FemGui::ViewProviderFemPost ViewProviderFemPostPipeline::ViewProviderFemPostPipeline() { + ViewProviderGroupExtension::initExtension(this); sPixmap = "FEM_PostPipelineFromResult"; } @@ -54,31 +56,37 @@ std::vector ViewProviderFemPostPipeline::claimChildren() c { Fem::FemPostPipeline* pipeline = getObject(); - std::vector children; + std::vector children = FemGui::ViewProviderFemPostObject::claimChildren(); if (pipeline->Functions.getValue()) { - children.push_back(pipeline->Functions.getValue()); + children.insert(children.begin(), pipeline->Functions.getValue()); } - children.insert(children.end(), - pipeline->Filter.getValues().begin(), - pipeline->Filter.getValues().end()); return children; } std::vector ViewProviderFemPostPipeline::claimChildren3D() const { - return claimChildren(); } void ViewProviderFemPostPipeline::updateData(const App::Property* prop) { FemGui::ViewProviderFemPostObject::updateData(prop); +<<<<<<< HEAD Fem::FemPostPipeline* pipeline = getObject(); +======= + Fem::FemPostPipeline* pipeline = static_cast(getObject()); + +>>>>>>> 782848c556 (FEM: Make multistep work for eigenmodes) if (prop == &pipeline->Functions) { updateFunctionSize(); } + + if (prop == &pipeline->Frame) { + // Frame is pipeline property, not post object, parent updateData does not catch it for update + updateVtk(); + } } void ViewProviderFemPostPipeline::updateFunctionSize() @@ -180,8 +188,7 @@ void ViewProviderFemPostPipeline::transformField(char* FieldName, double FieldFa { Fem::FemPostPipeline* obj = getObject(); - vtkSmartPointer data = obj->Data.getValue(); - vtkDataSet* dset = vtkDataSet::SafeDownCast(data); + vtkDataSet* dset = obj->getDataSet(); if (!dset) { return; } @@ -238,6 +245,15 @@ void ViewProviderFemPostPipeline::scaleField(vtkDataSet* dset, } } +void ViewProviderFemPostPipeline::setupTaskDialog(TaskDlgPost* dlg) +{ + // add the function box + assert(dlg->getView() == this); + ViewProviderFemPostObject::setupTaskDialog(dlg); + dlg->appendBox(new TaskPostFrames(this)); +} + + PyObject* ViewProviderFemPostPipeline::getPyObject() { if (!pyViewObject) { diff --git a/src/Mod/Fem/Gui/ViewProviderFemPostPipeline.h b/src/Mod/Fem/Gui/ViewProviderFemPostPipeline.h index 511c2d8228..2ca65b2a8d 100644 --- a/src/Mod/Fem/Gui/ViewProviderFemPostPipeline.h +++ b/src/Mod/Fem/Gui/ViewProviderFemPostPipeline.h @@ -26,16 +26,17 @@ #include #include +#include "Gui/ViewProviderGroupExtension.h" #include "ViewProviderFemPostObject.h" namespace FemGui { -class FemGuiExport ViewProviderFemPostPipeline: public ViewProviderFemPostObject +class FemGuiExport ViewProviderFemPostPipeline: public ViewProviderFemPostObject, public Gui::ViewProviderGroupExtension { - PROPERTY_HEADER_WITH_OVERRIDE(FemGui::ViewProviderFemPostPipeline); + PROPERTY_HEADER_WITH_EXTENSIONS(FemGui::ViewProviderFemPostPipeline); public: /// constructor. @@ -52,8 +53,13 @@ public: void scaleField(vtkDataSet* dset, vtkDataArray* pdata, double FieldFactor); PyObject* getPyObject() override; + // override, to not show/hide children as the parent is shown/hidden like normal groups + void extensionHide() override {}; + void extensionShow() override {}; + protected: void updateFunctionSize(); + virtual void setupTaskDialog(TaskDlgPost* dlg) override; }; } // namespace FemGui diff --git a/src/Mod/Fem/Init.py b/src/Mod/Fem/Init.py index a24adad59c..7aca64a3b7 100644 --- a/src/Mod/Fem/Init.py +++ b/src/Mod/Fem/Init.py @@ -93,9 +93,9 @@ FreeCAD.addImportType( if "BUILD_FEM_VTK" in FreeCAD.__cmake__: FreeCAD.addImportType( - "FEM result VTK (*.vtk *.VTK *.vtu *.VTU *.pvtu *.PVTU)", + "FEM result VTK (*.vtk *.VTK *.vtu *.VTU *.pvtu *.PVTU *.vtm .VTM)", "feminout.importVTKResults", ) FreeCAD.addExportType( - "FEM result VTK (*.vtu *.vtp *.vts *.vtr *.vti)", "feminout.importVTKResults" + "FEM result VTK (*.vtu *.vtp *.vts *.vtr *.vti *.vtm)", "feminout.importVTKResults" ) diff --git a/src/Mod/Fem/ObjectsFem.py b/src/Mod/Fem/ObjectsFem.py index d5bc8838f5..5038f6493d 100644 --- a/src/Mod/Fem/ObjectsFem.py +++ b/src/Mod/Fem/ObjectsFem.py @@ -603,10 +603,7 @@ def makePostVtkFilterClipRegion(doc, base_vtk_result, name="VtkFilterClipRegion" """makePostVtkFilterClipRegion(document, base_vtk_result, [name]): creates a FEM post processing region clip filter object (vtk based)""" obj = doc.addObject("Fem::FemPostClipFilter", name) - tmp_filter_list = base_vtk_result.Filter - tmp_filter_list.append(obj) - base_vtk_result.Filter = tmp_filter_list - del tmp_filter_list + base_vtk_result.addObject(obj) return obj @@ -614,10 +611,7 @@ def makePostVtkFilterClipScalar(doc, base_vtk_result, name="VtkFilterClipScalar" """makePostVtkFilterClipScalar(document, base_vtk_result, [name]): creates a FEM post processing scalar clip filter object (vtk based)""" obj = doc.addObject("Fem::FemPostScalarClipFilter", name) - tmp_filter_list = base_vtk_result.Filter - tmp_filter_list.append(obj) - base_vtk_result.Filter = tmp_filter_list - del tmp_filter_list + base_vtk_result.addObject(obj) return obj @@ -625,10 +619,7 @@ def makePostVtkFilterCutFunction(doc, base_vtk_result, name="VtkFilterCutFunctio """makePostVtkFilterCutFunction(document, base_vtk_result, [name]): creates a FEM post processing cut function filter object (vtk based)""" obj = doc.addObject("Fem::FemPostClipFilter", name) - tmp_filter_list = base_vtk_result.Filter - tmp_filter_list.append(obj) - base_vtk_result.Filter = tmp_filter_list - del tmp_filter_list + base_vtk_result.addObject(obj) return obj @@ -636,10 +627,7 @@ def makePostVtkFilterWarp(doc, base_vtk_result, name="VtkFilterWarp"): """makePostVtkFilterWarp(document, base_vtk_result, [name]): creates a FEM post processing warp filter object (vtk based)""" obj = doc.addObject("Fem::FemPostWarpVectorFilter", name) - tmp_filter_list = base_vtk_result.Filter - tmp_filter_list.append(obj) - base_vtk_result.Filter = tmp_filter_list - del tmp_filter_list + base_vtk_result.addObject(obj) return obj @@ -647,19 +635,24 @@ 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 + base_vtk_result.addObject(obj) return obj -def makePostVtkResult(doc, base_result, name="VtkResult"): +def makePostVtkResult(doc, result_data, name="VtkResult"): """makePostVtkResult(document, base_result, [name]): - creates a FEM post processing result object (vtk based) to hold FEM results""" + creates a FEM post processing result data (vtk based) to hold FEM results + Note: Result data get expanded, it can either be single result [result] or everything + needed for a multistep result: [results_list, value_list, unit, description] + """ + + print(result_data) + Pipeline_Name = "Pipeline_" + name obj = doc.addObject("Fem::FemPostPipeline", Pipeline_Name) - obj.load(base_result) + print("load") + obj.load(*result_data) + print("load done") if FreeCAD.GuiUp: obj.ViewObject.SelectionStyle = "BoundBox" # to assure the user sees something, set the default to Surface diff --git a/src/Mod/Fem/feminout/importCcxFrdResults.py b/src/Mod/Fem/feminout/importCcxFrdResults.py index 8f9378c3da..a76e04ecd5 100644 --- a/src/Mod/Fem/feminout/importCcxFrdResults.py +++ b/src/Mod/Fem/feminout/importCcxFrdResults.py @@ -58,6 +58,44 @@ def insert(filename, docname): # ********* module specific methods ********* +def setupPipeline(doc, analysis, results_name, result_data): + import ObjectsFem + from . import importToolsFem + + + # create a results pipeline if not already existing + pipeline_name = "Pipeline_" + results_name + pipeline_obj = doc.getObject(pipeline_name) + if pipeline_obj is None: + + pipeline_obj = ObjectsFem.makePostVtkResult(doc, result_data, results_name) + pipeline_visibility = True + if analysis: + analysis.addObject(pipeline_obj) + else: + if FreeCAD.GuiUp: + # store pipeline visibility because pipeline_obj.load makes the + # pipeline always visible + pipeline_visibility = pipeline_obj.ViewObject.Visibility + + pipeline_obj.load(*result_data) + + # update the pipeline + pipeline_obj.recomputeChildren() + pipeline_obj.recompute() + if FreeCAD.GuiUp: + pipeline_obj.ViewObject.updateColorBars() + # make results mesh invisible, will be made visible + # later in task_solver_ccxtools.py + if len(result_data) == 1: + result_data[0].Mesh.ViewObject.Visibility = False + else: + for res in result_data[0]: + res.Mesh.ViewObject.Visibility = False + # restore pipeline visibility + pipeline_obj.ViewObject.Visibility = pipeline_visibility + + def importFrd(filename, analysis=None, result_name_prefix="", result_analysis_type=""): import ObjectsFem from . import importToolsFem @@ -87,6 +125,8 @@ def importFrd(filename, analysis=None, result_name_prefix="", result_analysis_ty res_obj.Mesh = result_mesh_object return res_obj + multistep_result = [] + multistep_value = [] if len(m["Results"]) > 0: for result_set in m["Results"]: if "number" in result_set: @@ -109,6 +149,7 @@ def importFrd(filename, analysis=None, result_name_prefix="", result_analysis_ty else: results_name = f"{result_name_prefix}Results" + multistep_value.append(step_time) res_obj = make_result_mesh(results_name) res_obj = importToolsFem.fill_femresult_mechanical(res_obj, result_set) if analysis: @@ -176,30 +217,19 @@ def importFrd(filename, analysis=None, result_name_prefix="", result_analysis_ty # fill Stats res_obj = resulttools.fill_femresult_stats(res_obj) - # create a results pipeline if not already existing - pipeline_name = "Pipeline_" + results_name - pipeline_obj = doc.getObject(pipeline_name) - if pipeline_obj is None: - pipeline_obj = ObjectsFem.makePostVtkResult(doc, res_obj, results_name) - pipeline_visibility = True - if analysis: - analysis.addObject(pipeline_obj) + # if we have multiple results we delay the pipeline creation + if len(m["Results"]) == 1: + setupPipeline(doc, analysis, results_name, [res_obj]) else: - if FreeCAD.GuiUp: - # store pipeline visibility because pipeline_obj.load makes the - # pipeline always visible - pipeline_visibility = pipeline_obj.ViewObject.Visibility - pipeline_obj.load(res_obj) - # update the pipeline - pipeline_obj.recomputeChildren() - pipeline_obj.recompute() - if FreeCAD.GuiUp: - pipeline_obj.ViewObject.updateColorBars() - # make results mesh invisible, will be made visible - # later in task_solver_ccxtools.py - res_obj.Mesh.ViewObject.Visibility = False - # restore pipeline visibility - pipeline_obj.ViewObject.Visibility = pipeline_visibility + multistep_result.append(res_obj) + + + # we have collected all result objects, lets create the multistep result pipeline + if len(m["Results"]) > 1: + unit = FreeCAD.Units.Frequency + description = "Eigenmodes" + setupPipeline(doc, analysis, results_name, [multistep_result, multistep_value, unit, description]) + elif result_analysis_type == "check": results_name = f"{result_name_prefix}Check" diff --git a/src/Mod/Fem/femtest/app/test_object.py b/src/Mod/Fem/femtest/app/test_object.py index 44faa5125c..4b8ff71452 100644 --- a/src/Mod/Fem/femtest/app/test_object.py +++ b/src/Mod/Fem/femtest/app/test_object.py @@ -1120,7 +1120,7 @@ def create_all_fem_objects_doc(doc): res = analysis.addObject(ObjectsFem.makeResultMechanical(doc))[0] res.Mesh = rm if "BUILD_FEM_VTK" in FreeCAD.__cmake__: - vres = analysis.addObject(ObjectsFem.makePostVtkResult(doc, res))[0] + vres = analysis.addObject(ObjectsFem.makePostVtkResult(doc, [res])[0] ObjectsFem.makePostVtkFilterClipRegion(doc, vres) ObjectsFem.makePostVtkFilterClipScalar(doc, vres) ObjectsFem.makePostVtkFilterContours(doc, vres)