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
This commit is contained in:
Stefan Tröger
2024-11-24 16:11:53 +01:00
committed by Benjamin Nauck
parent 2119f9dfb4
commit 1cff507a7f
28 changed files with 1752 additions and 249 deletions

View File

@@ -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

View File

@@ -0,0 +1,274 @@
/***************************************************************************
* Copyright (c) 2015 Stefan Tröger <stefantroeger@gmx.net> *
* *
* 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 <Python.h>
#include <vtkDataSetReader.h>
#include <vtkImageData.h>
#include <vtkRectilinearGrid.h>
#include <vtkStructuredGrid.h>
#include <vtkUnstructuredGrid.h>
#include <vtkXMLImageDataReader.h>
#include <vtkXMLPUnstructuredGridReader.h>
#include <vtkXMLPolyDataReader.h>
#include <vtkXMLRectilinearGridReader.h>
#include <vtkXMLStructuredGridReader.h>
#include <vtkXMLUnstructuredGridReader.h>
#endif
#include <Base/Console.h>
#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<vtkAppendFilter>::New();
m_passthrough = vtkSmartPointer<vtkPassThrough>::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<App::DocumentObject*> objs = Group.getValues();
if (objs.empty()) {
return;
}
// prepare output filter: we make all connections new!
m_append->RemoveAllInputConnections(0);
FemPostFilter* filter = NULL;
std::vector<App::DocumentObject*>::iterator it = objs.begin();
for (; it != objs.end(); ++it) {
// prepare the filter: make all connections new
FemPostFilter* nextFilter = static_cast<FemPostFilter*>(*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<Fem::FemPostFilter*>(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<App::DocumentObject*> objs = Group.getValues();
if (objs.empty()) {
return;
}
bool started = false;
std::vector<App::DocumentObject*>::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<Fem::FemPostPipeline*>(group);
pipe->filterChanged(this);
}
else if (group->isDerivedFrom(Fem::FemPostBranch::getClassTypeId())) {
auto branch = dynamic_cast<Fem::FemPostBranch*>(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<FemPostObject*>(Group.getValues().back());
}
bool FemPostBranch::holdsPostObject(FemPostObject* obj)
{
std::vector<App::DocumentObject*>::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);
}

View File

@@ -0,0 +1,101 @@
/***************************************************************************
* Copyright (c) 2024 Stefan Tröger <stefantroeger@gmx.net> *
* *
* 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 <vtkSmartPointer.h>
#include <vtkAppendFilter.h>
#include <vtkPassThrough.h>
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<vtkAppendFilter> m_append;
vtkSmartPointer<vtkPassThrough> m_passthrough;
template<class TReader>
void readXMLFile(std::string file)
{
vtkSmartPointer<TReader> reader = vtkSmartPointer<TReader>::New();
reader->SetFileName(file.c_str());
reader->Update();
Data.setValue(reader->GetOutput());
}
};
} // namespace Fem
#endif // Fem_FemPostBranch_H

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<GenerateModel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="generateMetaModel_Module.xsd">
<PythonExport
Father="FemPostObjectPy"
Name="FemPostBranchPy"
Twin="FemPostBranch"
TwinPointer="FemPostBranch"
Include="Mod/Fem/App/FemPostBranch.h"
Namespace="Fem"
FatherInclude="Mod/Fem/App/FemPostObjectPy.h"
FatherNamespace="Fem">
<Documentation>
<Author Licence="LGPL" Name="Werner Mayer" EMail="wmayer@users.sourceforge.net" />
<UserDocu>The FemPostBranch class.</UserDocu>
</Documentation>
<Methode Name="recomputeChildren">
<Documentation>
<UserDocu>Recomputes all children of the pipeline</UserDocu>
</Documentation>
</Methode>
<Methode Name="getLastPostObject">
<Documentation>
<UserDocu>Get the last post-processing object</UserDocu>
</Documentation>
</Methode>
<Methode Name="holdsPostObject">
<Documentation>
<UserDocu>Check if this pipeline holds a given post-processing object</UserDocu>
</Documentation>
</Methode>
</PythonExport>
</GenerateModel>

View File

@@ -0,0 +1,94 @@
/***************************************************************************
* Copyright (c) 2017 Werner Mayer <wmayer[at]users.sourceforge.net> *
* *
* 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 <Python.h>
#endif
#include <Base/FileInfo.h>
// 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 {"<FemPostBranch object>"};
}
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<App::DocumentObjectPy*>(py)->getDocumentObjectPtr();
if (!obj->isDerivedFrom<FemPostObject>()) {
PyErr_SetString(PyExc_TypeError, "object is not a post-processing object");
return nullptr;
}
bool ok = getFemPostBranchPtr()->holdsPostObject(static_cast<FemPostObject*>(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;
}

View File

@@ -26,12 +26,16 @@
#include <Python.h>
#include <vtkDoubleArray.h>
#include <vtkPointData.h>
#include <vtkAlgorithm.h>
#include <vtkAlgorithmOutput.h>
#endif
#include <App/Document.h>
#include <Base/Console.h>
#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<Fem::FemPostPipeline*>(group);
pipe->pipelineChanged(this);
}
else if (group->isDerivedFrom(Fem::FemPostBranch::getClassTypeId())) {
auto branch = dynamic_cast<Fem::FemPostBranch*>(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<Fem::FemPostPipeline*>(group);
pipe->filterChanged(this);
}
else if (group->isDerivedFrom(Fem::FemPostBranch::getClassTypeId())) {
auto branch = dynamic_cast<Fem::FemPostBranch*>(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<vtkDataObject> 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<Fem::FemPostObject>()) {
return Input.getValue<FemPostObject*>()->Data.getValue();
}
else {
throw std::runtime_error(
"The filter's Input object is not a 'Fem::FemPostObject' object!");
}
vtkSmartPointer<vtkDataSet> FemPostFilter::getInputData() {
if (getActiveFilterPipeline().source->GetNumberOfInputConnections(0) == 0) {
return nullptr;
}
else {
// get the pipeline and use the pipelinedata
std::vector<App::DocumentObject*> objs =
getDocument()->getObjectsOfType(FemPostPipeline::getClassTypeId());
for (auto it : objs) {
if (static_cast<FemPostPipeline*>(it)->holdsPostObject(this)) {
return static_cast<FemPostObject*>(it)->Data.getValue();
}
vtkAlgorithmOutput* output = getActiveFilterPipeline().source->GetInputConnection(0,0);
vtkAlgorithm* algo = output->GetProducer();
algo->Update();
return vtkDataSet::SafeDownCast(algo->GetOutputDataObject(0));
}
std::vector<std::string> FemPostFilter::getInputVectorFields()
{
vtkDataSet* dset = getInputData();
if (!dset) {
return std::vector<std::string>();
}
vtkPointData* pd = dset->GetPointData();
// get all vector fields
std::vector<std::string> 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<std::string> FemPostFilter::getInputScalarFields()
{
vtkDataSet* dset = getInputData();
if (!dset) {
return std::vector<std::string>();
}
vtkPointData* pd = dset->GetPointData();
// get all scalar fields
std::vector<std::string> 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<vtkPassThrough>::New();
m_probe = vtkSmartPointer<vtkProbeFilter>::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<vtkPassThrough>::New();
m_probe = vtkSmartPointer<vtkProbeFilter>::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<vtkDataObject> 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<vtkDataObject> data = getInputData();
vtkDataSet* dset = vtkDataSet::SafeDownCast(data);
vtkDataSet* dset = getInputData();
if (!dset) {
return;
}
@@ -807,8 +891,7 @@ void FemPostContoursFilter::refreshFields()
std::vector<std::string> FieldsArray;
vtkSmartPointer<vtkDataObject> 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<vtkDataObject> 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<std::string> ScalarsArray;
vtkSmartPointer<vtkDataObject> 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<std::string> ScalarsArray = getInputScalarFields();
App::Enumeration empty;
Scalars.setValue(empty);
@@ -1044,8 +1113,7 @@ short int FemPostScalarClipFilter::mustExecute() const
void FemPostScalarClipFilter::setConstraintForField()
{
vtkSmartPointer<vtkDataObject> 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<std::string> VectorArray;
vtkSmartPointer<vtkDataObject> 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<std::string> VectorArray = getInputVectorFields();
App::Enumeration empty;
Vector.setValue(empty);

View File

@@ -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<vtkDataSet> getInputData();
std::vector<std::string> getInputVectorFields();
std::vector<std::string> getInputScalarFields();
// pipeline handling for derived filter
struct FilterPipeline
{
vtkSmartPointer<vtkAlgorithm> source, target;
vtkSmartPointer<vtkProbeFilter> filterSource, filterTarget;
std::vector<vtkSmartPointer<vtkAlgorithm>> 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<std::string, FilterPipeline> m_pipelines;

View File

@@ -25,6 +25,7 @@
#ifndef _PreComp_
#include <vtkDataSet.h>
#include <vtkXMLDataSetWriter.h>
#include <vtkXMLMultiBlockDataWriter.h>
#endif
#include <Base/Exception.h>
@@ -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<typename T>
void femVTKWriter(const char* filename, const vtkSmartPointer<vtkDataObject>& 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<vtkDataObject>& 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<vtkXMLDataSetWriter>(name.c_str(), data);
if (extension == "vtm") {
femVTKWriter<vtkXMLMultiBlockDataWriter>(name.c_str(), data);
} else {
femVTKWriter<vtkXMLDataSetWriter>(name.c_str(), data);
}
}

View File

@@ -27,6 +27,7 @@
#include "PropertyPostDataObject.h"
#include <App/GeoFeature.h>
#include <vtkBoundingBox.h>
#include <vtkDataSet.h>
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();

View File

@@ -36,9 +36,17 @@
#include <vtkXMLRectilinearGridReader.h>
#include <vtkXMLStructuredGridReader.h>
#include <vtkXMLUnstructuredGridReader.h>
#include <vtkXMLMultiBlockDataReader.h>
#include <vtkMultiBlockDataSet.h>
#include <vtkStreamingDemandDrivenPipeline.h>
#include <vtkPointData.h>
#include <vtkFloatArray.h>
#include <vtkStringArray.h>
#endif
#include <Base/Console.h>
#include <cmath>
#include <QString>
#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<vtkDataObject> data ) {
m_data = data;
Update();
}
std::vector<double> FemFrameSourceAlgorithm::getFrameValues()
{
// check if we have frame data
if (!m_data || !m_data->IsA("vtkMultiBlockDataSet")) {
return std::vector<double>();
}
// we have multiple frames! let's check the amount and times
vtkSmartPointer<vtkMultiBlockDataSet> multiblock = vtkMultiBlockDataSet::SafeDownCast(m_data);
unsigned long nblocks = multiblock->GetNumberOfBlocks();
std::vector<double> tFrames(nblocks);
for (unsigned long i=0; i<nblocks; i++) {
vtkDataObject* block = multiblock->GetBlock(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<double>();
}
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<double> 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<vtkMultiBlockDataSet> 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<FemFrameSourceAlgorithm>::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<vtkDataSetReader>(File.filePath());
}
else if (File.hasExtension("vtm")) {
readXMLFile<vtkXMLMultiBlockDataReader>(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<double> frames = m_source_algorithm->getFrameValues();
std::vector<std::string> 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<std::string>::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<App::DocumentObject*> objs = Filter.getValues();
std::vector<App::DocumentObject*> objs = Group.getValues();
if (objs.empty()) {
return;
}
FemPostFilter* filter = NULL;
std::vector<App::DocumentObject*>::iterator it = objs.begin();
FemPostFilter* filter = static_cast<FemPostFilter*>(*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<FemPostFilter*>(*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<FemPostFilter*>(*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<App::DocumentObject*> objs = Group.getValues();
if (objs.empty()) {
return;
}
bool started = false;
std::vector<App::DocumentObject*>::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<Fem::FemPostFilter*>(obj)->Frame.setValue(frame);
}
}
}
FemPostObject* FemPostPipeline::getLastPostObject()
{
if (Filter.getValues().empty()) {
if (Group.getValues().empty()) {
return this;
}
return static_cast<FemPostObject*>(Filter.getValues().back());
return static_cast<FemPostObject*>(Group.getValues().back());
}
bool FemPostPipeline::holdsPostObject(FemPostObject* obj)
{
std::vector<App::DocumentObject*>::const_iterator it = Filter.getValues().begin();
for (; it != Filter.getValues().end(); ++it) {
std::vector<App::DocumentObject*>::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<vtkDataObject> 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<vtkMultiBlockDataSet> 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<vtkDataObject> 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<vtkMultiBlockDataSet> 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<double> 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<FemResultObject*> res, std::vector<double> 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<vtkMultiBlockDataSet>::New();
for (ulong i=0; i<res.size(); i++) {
if (!res[i]->Mesh.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<FemMeshObject*>(res[i]->Mesh.getValue())->FemMesh.getValue();
vtkSmartPointer<vtkUnstructuredGrid> grid = vtkSmartPointer<vtkUnstructuredGrid>::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())) {

View File

@@ -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 <vtkSmartPointer.h>
#include <vtkUnstructuredGridAlgorithm.h>
#include <vtkInformation.h>
#include <vtkInformationVector.h>
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<vtkDataObject> data);
std::vector<double> getFrameValues();
protected:
FemFrameSourceAlgorithm();
~FemFrameSourceAlgorithm() override;
vtkSmartPointer<vtkDataObject> 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<FemResultObject*> res, std::vector<double> 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<double> getFrameValues();
protected:
void onChanged(const App::Property* prop) override;
private:
static const char* ModeEnums[];
App::Enumeration m_frameEnum;
vtkSmartPointer<FemFrameSourceAlgorithm> m_source_algorithm;
template<class TReader>
void readXMLFile(std::string file)

View File

@@ -25,7 +25,7 @@
</Methode>
<Methode Name="load">
<Documentation>
<UserDocu>Load a result object</UserDocu>
<UserDocu>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</UserDocu>
</Documentation>
</Methode>
<Methode Name="recomputeChildren">

View File

@@ -26,6 +26,7 @@
#endif
#include <Base/FileInfo.h>
#include <Base/UnitPy.h>
// 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<App::DocumentObjectPy*>(py)->getDocumentObjectPtr();
if (!obj->isDerivedFrom<FemResultObject>()) {
PyErr_SetString(PyExc_TypeError, "object is not a result object");
return nullptr;
}
getFemPostPipelinePtr()->load(static_cast<FemResultObject*>(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<FemResultObject*> 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<DocumentObjectPy*>(*item)->getDocumentObjectPtr();
if (!obj->isDerivedFrom<FemResultObject>()) {
throw Base::TypeError("object is not a result object");
}
results[i] = static_cast<FemResultObject*>(obj);
}
//extract the values
Py::Sequence values_list(list);
size = values_list.size();
std::vector<double> 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<Base::UnitPy*>(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<App::DocumentObjectPy*>(py)->getDocumentObjectPtr();
if (!obj->isDerivedFrom<FemResultObject>()) {
PyErr_SetString(PyExc_TypeError, "object is not a result object");
return nullptr;
}
getFemPostPipelinePtr()->load(static_cast<FemResultObject*>(obj));
Py_Return;
}

View File

@@ -33,6 +33,8 @@
#include <vtkUniformGrid.h>
#include <vtkUnstructuredGrid.h>
#include <vtkXMLDataSetWriter.h>
#include <vtkXMLMultiBlockDataWriter.h>
#include <vtkXMLMultiBlockDataReader.h>
#include <vtkXMLImageDataReader.h>
#include <vtkXMLPolyDataReader.h>
#include <vtkXMLRectilinearGridReader.h>
@@ -49,6 +51,12 @@
#include <Base/Writer.h>
#include <CXX/Objects.hxx>
#ifdef _MSC_VER
#include <zipios++/zipios-config.h>
#endif
#include <zipios++/zipoutputstream.h>
#include "PropertyPostDataObject.h"
@@ -246,11 +254,11 @@ void PropertyPostDataObject::getPaths(std::vector<App::ObjectIdentifier>& /*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<vtkXMLDataSetWriter> xmlWriter = vtkSmartPointer<vtkXMLDataSetWriter>::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<vtkXMLMultiBlockDataWriter>::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<vtkXMLDataSetWriter>::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<vtkXMLImageDataReader>::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<vtkXMLMultiBlockDataReader>::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();
}
}

View File

@@ -95,6 +95,7 @@ if(BUILD_FEM_VTK)
TaskPostDisplay.ui
TaskPostScalarClip.ui
TaskPostWarpVector.ui
TaskPostFrames.ui
)
endif(BUILD_FEM_VTK)

View File

@@ -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<Fem::FemPostFilter*>(objFilter);
if (!selectionIsPipeline) {
femFilter->Input.setValue(selObject);
}
femFilter->Data.setValue(static_cast<Fem::FemPostObject*>(selObject)->Data.getValue());
auto selObjectView = static_cast<FemGui::ViewProviderFemPostObject*>(
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<Fem::FemPostDataAlongLineFilter>()

View File

@@ -80,6 +80,7 @@
<file>icons/FEM_PostFilterDataAtPoint.svg</file>
<file>icons/FEM_PostFilterLinearizedStresses.svg</file>
<file>icons/FEM_PostFilterWarp.svg</file>
<file>icons/FEM_PostFrames.svg</file>
<file>icons/FEM_PostPipelineFromResult.svg</file>
<file>icons/FEM_ResultShow.svg</file>
<file>icons/FEM_ResultsPurge.svg</file>

View File

@@ -0,0 +1,108 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="64"
height="64"
id="svg2"
version="1.1"
sodipodi:docname="FEM_PostSteps.svg"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="10.650796"
inkscape:cx="26.24217"
inkscape:cy="37.743658"
inkscape:window-width="3772"
inkscape:window-height="2132"
inkscape:window-x="68"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs4">
<linearGradient
id="linearGradient3802">
<stop
style="stop-color:#4e9a06;stop-opacity:1"
offset="0"
id="stop3804" />
<stop
style="stop-color:#73d216;stop-opacity:1"
offset="1"
id="stop3806" />
</linearGradient>
<linearGradient
xlink:href="#linearGradient3802"
id="linearGradient3808"
x1="49"
y1="58"
x2="47"
y2="42"
gradientUnits="userSpaceOnUse" />
</defs>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:creator>
<cc:Agent>
<dc:title>[Alexander Gryson]</dc:title>
</cc:Agent>
</dc:creator>
<dc:date>2017-03-11</dc:date>
<dc:relation>https://www.freecad.org/wiki/index.php?title=Artwork</dc:relation>
<dc:publisher>
<cc:Agent>
<dc:title>FreeCAD</dc:title>
</cc:Agent>
</dc:publisher>
<dc:identifier>FreeCAD/src/Mod/</dc:identifier>
<dc:rights>
<cc:Agent>
<dc:title>FreeCAD LGPL2+</dc:title>
</cc:Agent>
</dc:rights>
<cc:license>https://www.gnu.org/copyleft/lesser.html</cc:license>
<dc:contributor>
<cc:Agent>
<dc:title>[agryson] Alexander Gryson</dc:title>
</cc:Agent>
</dc:contributor>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
transform="translate(0,-988.36218)">
<path
style="fill:none;stroke:#172a04;stroke-width:7.88137;stroke-linecap:butt;stroke-linejoin:bevel;stroke-dasharray:none;stroke-opacity:1"
d="m 4.3166604,1048.6979 c 10.0528756,-6.3495 11.0960996,-30.5752 16.6915666,-33.7987 5.595471,-3.2236 6.828368,10.3544 14.225768,14.3596 3.888376,1.9536 12.80319,-6.5449 15.838022,-11.8199 3.03483,-5.275 9.389006,-25.69077 9.389006,-25.69077 v 0"
id="path1"
sodipodi:nodetypes="cccccc" />
<path
style="fill:none;stroke:#8ae234;stroke-width:5.1317;stroke-opacity:1"
d="m 4.3878505,1048.9004 c 10.0583295,-6.3607 11.1021185,-30.6296 16.7006225,-33.859 5.598506,-3.2291 6.832073,10.3729 14.233486,14.3854 3.890486,1.9571 12.810136,-6.5567 15.846614,-11.841 3.036476,-5.2844 9.394101,-25.73679 9.394101,-25.73679 v 0"
id="path1-6"
sodipodi:nodetypes="cccccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -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<Fem::FemPostPipeline*>(getObject());
ui->Type->setText(QString::fromStdString(pipeline->getFrameType()));
auto unit = pipeline->getFrameUnit();
auto steps = pipeline->getFrameValues();
for (unsigned long i=0; i<steps.size(); i++) {
QTableWidgetItem *idx = new QTableWidgetItem(QString::number(i));
QTableWidgetItem *value = new QTableWidgetItem(Base::Quantity(steps[i], unit).getUserString());
int rowIdx = ui->FrameTable->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<Fem::FemPostPipeline*>(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
// ***************************************************************************

View File

@@ -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_TaskPostFrames> ui;
};
// ***************************************************************************
// in the following, the different filters sorted alphabetically

View File

@@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TaskPostFrames</class>
<widget class="QWidget" name="TaskPostFrames">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>232</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Type of frames:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="Type">
<property name="text">
<string>Ressonance Frequencies</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTableWidget" name="FrameTable">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="columnCount">
<number>2</number>
</property>
<attribute name="horizontalHeaderCascadingSectionResizes">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string>Frame</string>
</property>
</column>
<column>
<property name="text">
<string>Value</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -850,15 +850,9 @@ bool ViewProviderFemPostObject::setupPipeline()
auto postObject = getObject<Fem::FemPostObject>();
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<vtkDataObject> SPdata = data;
vtkDataSet* dset = vtkDataSet::SafeDownCast(SPdata);
vtkDataSet* dset = postObject->getDataSet();
if (!dset) {
return false;
}

View File

@@ -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<App::DocumentObject*> ViewProviderFemPostPipeline::claimChildren() c
{
Fem::FemPostPipeline* pipeline = getObject<Fem::FemPostPipeline>();
std::vector<App::DocumentObject*> children;
std::vector<App::DocumentObject*> 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<App::DocumentObject*> ViewProviderFemPostPipeline::claimChildren3D() const
{
return claimChildren();
}
void ViewProviderFemPostPipeline::updateData(const App::Property* prop)
{
FemGui::ViewProviderFemPostObject::updateData(prop);
<<<<<<< HEAD
Fem::FemPostPipeline* pipeline = getObject<Fem::FemPostPipeline>();
=======
Fem::FemPostPipeline* pipeline = static_cast<Fem::FemPostPipeline*>(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<Fem::FemPostPipeline>();
vtkSmartPointer<vtkDataObject> 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) {

View File

@@ -26,16 +26,17 @@
#include <Gui/ViewProviderFeaturePython.h>
#include <Mod/Fem/FemGlobal.h>
#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

View File

@@ -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"
)

View File

@@ -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

View File

@@ -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"

View File

@@ -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)