From b6f556cf45de93cee601e618642d834a91e5c3f5 Mon Sep 17 00:00:00 2001 From: marioalexis Date: Sun, 14 Sep 2025 00:14:52 -0300 Subject: [PATCH 1/3] Fem: Move FemFrameSourceAlgorithm class to its own source files --- src/Mod/Fem/App/CMakeLists.txt | 8 + src/Mod/Fem/App/FemPostPipeline.cpp | 143 +------------- src/Mod/Fem/App/FemPostPipeline.h | 35 +--- src/Mod/Fem/App/VTKExtensions/CMakeLists.txt | 6 + .../vtkFemFrameSourceAlgorithm.cpp | 180 ++++++++++++++++++ .../vtkFemFrameSourceAlgorithm.h | 66 +++++++ 6 files changed, 265 insertions(+), 173 deletions(-) create mode 100644 src/Mod/Fem/App/VTKExtensions/CMakeLists.txt create mode 100644 src/Mod/Fem/App/VTKExtensions/vtkFemFrameSourceAlgorithm.cpp create mode 100644 src/Mod/Fem/App/VTKExtensions/vtkFemFrameSourceAlgorithm.h diff --git a/src/Mod/Fem/App/CMakeLists.txt b/src/Mod/Fem/App/CMakeLists.txt index 24329f725e..6a8f02fb2e 100644 --- a/src/Mod/Fem/App/CMakeLists.txt +++ b/src/Mod/Fem/App/CMakeLists.txt @@ -82,6 +82,12 @@ if(BUILD_FEM_VTK) FemVTKTools.cpp ) SOURCE_GROUP("PostObjects" FILES ${FemPost_SRCS}) + + SET(FemVTK_SRCS + VTKExtensions/vtkFemFrameSourceAlgorithm.h + VTKExtensions/vtkFemFrameSourceAlgorithm.cpp + ) + SOURCE_GROUP("VTKExtensions" FILES ${FemVTK_SRCS}) endif(BUILD_FEM_VTK) @@ -178,6 +184,7 @@ SET(Fem_SRCS ${FemConstraints_SRCS} ${FemPost_SRCS} ${FemSet_SRCS} + ${FemVTK_SRCS} ${Mod_SRCS} ${Python_SRCS} ) @@ -197,6 +204,7 @@ target_include_directories( ${CMAKE_SOURCE_DIR}/src ${CMAKE_BINARY_DIR}/src ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR} ) target_include_directories( diff --git a/src/Mod/Fem/App/FemPostPipeline.cpp b/src/Mod/Fem/App/FemPostPipeline.cpp index 70697e5bfe..953a2957bf 100644 --- a/src/Mod/Fem/App/FemPostPipeline.cpp +++ b/src/Mod/Fem/App/FemPostPipeline.cpp @@ -40,7 +40,6 @@ #include #include #include -#include #include #include #include @@ -61,145 +60,6 @@ using namespace Fem; using namespace App; -vtkStandardNewMacro(FemFrameSourceAlgorithm); - -FemFrameSourceAlgorithm::FemFrameSourceAlgorithm::FemFrameSourceAlgorithm() -{ - // we are a source - SetNumberOfInputPorts(0); - SetNumberOfOutputPorts(1); -} - - -FemFrameSourceAlgorithm::FemFrameSourceAlgorithm::~FemFrameSourceAlgorithm() -{} - -void FemFrameSourceAlgorithm::setDataObject(vtkSmartPointer data) -{ - m_data = data; - Modified(); - Update(); -} - -bool FemFrameSourceAlgorithm::isValid() -{ - return m_data.GetPointer() != nullptr; -} - -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; i < nblocks; i++) { - - vtkDataObject* block = multiblock->GetBlock(i); - // check if the TimeValue field is available - if (!block->GetFieldData()->HasArray("TimeValue")) { - // a frame with no valid value is a undefined state - return std::vector(); - } - - // store the time value! - vtkDataArray* TimeValue = block->GetFieldData()->GetArray("TimeValue"); - if (!TimeValue->IsA("vtkFloatArray") || TimeValue->GetNumberOfTuples() < 1) { - // a frame with no valid value is a undefined state - return std::vector(); - } - - tFrames[i] = vtkFloatArray::SafeDownCast(TimeValue)->GetValue(0); - } - - return tFrames; -} - -int FemFrameSourceAlgorithm::RequestInformation(vtkInformation* reqInfo, - vtkInformationVector** inVector, - vtkInformationVector* outVector) -{ - - // setup default information - if (!this->Superclass::RequestInformation(reqInfo, inVector, outVector)) { - return 0; - } - - if (!m_data) { - // for the no data case we would return a empty data set in RequestData. - return 1; - } - - std::vector frames = getFrameValues(); - - if (frames.empty()) { - // no frames, default info is sufficient - return 1; - } - - double tRange[2] = {frames.front(), frames.back()}; - - // finally set the time info! - vtkInformation* info = outVector->GetInformationObject(0); - info->Set(vtkStreamingDemandDrivenPipeline::TIME_RANGE(), tRange, 2); - info->Set(vtkStreamingDemandDrivenPipeline::TIME_STEPS(), &frames[0], frames.size()); - info->Set(CAN_HANDLE_PIECE_REQUEST(), 1); - - return 1; -} - -int FemFrameSourceAlgorithm::RequestData(vtkInformation*, - vtkInformationVector**, - vtkInformationVector* outVector) -{ - vtkInformation* outInfo = outVector->GetInformationObject(0); - vtkUnstructuredGrid* output = - vtkUnstructuredGrid::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT())); - - if (!output) { - return 0; - } - - if (!m_data) { - outInfo->Set(vtkDataObject::DATA_OBJECT(), vtkUnstructuredGrid::New()); - return 1; - } - - 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 rounding errors. lets subtract the searched time and - // then use the smallest value - for (auto& frame : frames) { - frame = std::abs(frame - time); - } - - auto it = std::ranges::min_element(frames); - idx = std::distance(frames.begin(), it); - } - - auto block = multiblock->GetBlock(idx); - output->ShallowCopy(block); - return 1; -} - PROPERTY_SOURCE_WITH_EXTENSIONS(Fem::FemPostPipeline, Fem::FemPostObject) FemPostPipeline::FemPostPipeline() @@ -215,7 +75,8 @@ FemPostPipeline::FemPostPipeline() "set via pipeline object)."); // create our source algorithm - m_source_algorithm = vtkSmartPointer::New(); + m_source_algorithm = vtkSmartPointer::New(); + m_transform_filter->SetInputConnection(m_source_algorithm->GetOutputPort(0)); } diff --git a/src/Mod/Fem/App/FemPostPipeline.h b/src/Mod/Fem/App/FemPostPipeline.h index d590915cb4..3ba9108f5e 100644 --- a/src/Mod/Fem/App/FemPostPipeline.h +++ b/src/Mod/Fem/App/FemPostPipeline.h @@ -30,44 +30,14 @@ #include "FemPostFunction.h" #include "FemPostObject.h" #include "FemResultObject.h" +#include "VTKExtensions/vtkFemFrameSourceAlgorithm.h" #include -#include - -class vtkInformation; -class vtkInformationVector; namespace Fem { -// 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 -{ -public: - static FemFrameSourceAlgorithm* New(); - vtkTypeMacro(FemFrameSourceAlgorithm, vtkUnstructuredGridAlgorithm); - - bool isValid(); - 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 Fem::FemPostGroupExtension { PROPERTY_HEADER_WITH_EXTENSIONS(Fem::FemPostPipeline); @@ -136,7 +106,8 @@ protected: private: App::Enumeration m_frameEnum; - vtkSmartPointer m_source_algorithm; + + vtkSmartPointer m_source_algorithm; bool m_block_property = false; bool m_data_updated = false; diff --git a/src/Mod/Fem/App/VTKExtensions/CMakeLists.txt b/src/Mod/Fem/App/VTKExtensions/CMakeLists.txt new file mode 100644 index 0000000000..8e9fbd85f7 --- /dev/null +++ b/src/Mod/Fem/App/VTKExtensions/CMakeLists.txt @@ -0,0 +1,6 @@ +SET(FemVTK_SRCS + vtkFemFrameSourceAlgorithm.h + vtkFemFrameSourceAlgorithm.cpp + ) + +target_sources(Fem PRIVATE ${FemVTK_SRCS}) diff --git a/src/Mod/Fem/App/VTKExtensions/vtkFemFrameSourceAlgorithm.cpp b/src/Mod/Fem/App/VTKExtensions/vtkFemFrameSourceAlgorithm.cpp new file mode 100644 index 0000000000..91574cf9d2 --- /dev/null +++ b/src/Mod/Fem/App/VTKExtensions/vtkFemFrameSourceAlgorithm.cpp @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +/*************************************************************************** + * Copyright (c) 2025 Stefan Tröger * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + +#include "PreCompiled.h" + +#ifndef _PreComp_ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#include "vtkFemFrameSourceAlgorithm.h" + +using namespace Fem; + + +vtkStandardNewMacro(vtkFemFrameSourceAlgorithm); + +vtkFemFrameSourceAlgorithm::vtkFemFrameSourceAlgorithm() +{ + // we are a source + SetNumberOfInputPorts(0); + SetNumberOfOutputPorts(1); +} + +vtkFemFrameSourceAlgorithm::~vtkFemFrameSourceAlgorithm() = default; + +void vtkFemFrameSourceAlgorithm::setDataObject(vtkSmartPointer data) +{ + m_data = data; + Modified(); + Update(); +} + +bool vtkFemFrameSourceAlgorithm::isValid() +{ + return m_data.GetPointer() ? true : false; +} + +std::vector vtkFemFrameSourceAlgorithm::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; i < nblocks; i++) { + + vtkDataObject* block = multiblock->GetBlock(i); + // check if the TimeValue field is available + if (!block->GetFieldData()->HasArray("TimeValue")) { + // a frame with no valid value is a undefined state + return std::vector(); + } + + // store the time value! + vtkDataArray* TimeValue = block->GetFieldData()->GetArray("TimeValue"); + if (!TimeValue->IsA("vtkFloatArray") || TimeValue->GetNumberOfTuples() < 1) { + // a frame with no valid value is a undefined state + return std::vector(); + } + + tFrames[i] = vtkFloatArray::SafeDownCast(TimeValue)->GetValue(0); + } + + return tFrames; +} + +int vtkFemFrameSourceAlgorithm::RequestInformation(vtkInformation* reqInfo, + vtkInformationVector** inVector, + vtkInformationVector* outVector) +{ + + // setup default information + if (!this->Superclass::RequestInformation(reqInfo, inVector, outVector)) { + return 0; + } + + if (!m_data) { + // for the no data case we would return a empty data set in RequestData. + return 1; + } + + std::vector frames = getFrameValues(); + + if (frames.empty()) { + // no frames, default info is sufficient + return 1; + } + + double tRange[2] = {frames.front(), frames.back()}; + + // finally set the time info! + vtkInformation* info = outVector->GetInformationObject(0); + info->Set(vtkStreamingDemandDrivenPipeline::TIME_RANGE(), tRange, 2); + info->Set(vtkStreamingDemandDrivenPipeline::TIME_STEPS(), &frames[0], frames.size()); + info->Set(CAN_HANDLE_PIECE_REQUEST(), 1); + + return 1; +} + +int vtkFemFrameSourceAlgorithm::RequestData(vtkInformation*, + vtkInformationVector**, + vtkInformationVector* outVector) +{ + vtkInformation* outInfo = outVector->GetInformationObject(0); + vtkUnstructuredGrid* output = + vtkUnstructuredGrid::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT())); + + if (!output) { + return 0; + } + + if (!m_data) { + outInfo->Set(vtkDataObject::DATA_OBJECT(), vtkUnstructuredGrid::New()); + return 1; + } + + 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 rounding errors. lets subtract the searched time and + // then use the smallest value + for (auto& frame : frames) { + frame = std::abs(frame - time); + } + + auto it = std::ranges::min_element(frames); + idx = std::distance(frames.begin(), it); + } + + auto block = multiblock->GetBlock(idx); + output->ShallowCopy(block); + return 1; +} diff --git a/src/Mod/Fem/App/VTKExtensions/vtkFemFrameSourceAlgorithm.h b/src/Mod/Fem/App/VTKExtensions/vtkFemFrameSourceAlgorithm.h new file mode 100644 index 0000000000..62b309174a --- /dev/null +++ b/src/Mod/Fem/App/VTKExtensions/vtkFemFrameSourceAlgorithm.h @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +/*************************************************************************** + * Copyright (c) 2025 Stefan Tröger * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + **************************************************************************/ + +#ifndef Fem_VTK_vtkFemFrameSourceAlgorithm_H +#define Fem_VTK_vtkFemFrameSourceAlgorithm_H + +#include +#include + +class vtkInformation; +class vtkInformationVector; + + +namespace Fem +{ + +// 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 vtkFemFrameSourceAlgorithm: public vtkUnstructuredGridAlgorithm +{ +public: + static vtkFemFrameSourceAlgorithm* New(); + vtkTypeMacro(vtkFemFrameSourceAlgorithm, vtkUnstructuredGridAlgorithm); + + bool isValid(); + void setDataObject(vtkSmartPointer data); + std::vector getFrameValues(); + +protected: + vtkFemFrameSourceAlgorithm(); + ~vtkFemFrameSourceAlgorithm() override; + + vtkSmartPointer m_data; + + int RequestInformation(vtkInformation* reqInfo, + vtkInformationVector** inVector, + vtkInformationVector* outVector) override; + int RequestData(vtkInformation* reqInfo, + vtkInformationVector** inVector, + vtkInformationVector* outVector) override; +}; + +} // namespace Fem + + +#endif // Fem_VTK_vtkFemFrameSourceAlgorithm_H From ba31cb584c2e52454e9ded9d7f42db6a150681d4 Mon Sep 17 00:00:00 2001 From: marioalexis Date: Sun, 14 Sep 2025 00:34:06 -0300 Subject: [PATCH 2/3] Fem: Add upstream vtkCleanUnstructuredGrid class source files --- src/Mod/Fem/App/CMakeLists.txt | 8 + src/Mod/Fem/App/PreCompiled.h | 4 + src/Mod/Fem/App/VTKExtensions/CMakeLists.txt | 6 - .../vtkCleanUnstructuredGrid.cxx | 680 ++++++++++++++++++ .../VTKExtensions/vtkCleanUnstructuredGrid.h | 153 ++++ src/Mod/Fem/App/VTKExtensions/vtkSMPTools.h | 604 ++++++++++++++++ 6 files changed, 1449 insertions(+), 6 deletions(-) delete mode 100644 src/Mod/Fem/App/VTKExtensions/CMakeLists.txt create mode 100644 src/Mod/Fem/App/VTKExtensions/vtkCleanUnstructuredGrid.cxx create mode 100644 src/Mod/Fem/App/VTKExtensions/vtkCleanUnstructuredGrid.h create mode 100644 src/Mod/Fem/App/VTKExtensions/vtkSMPTools.h diff --git a/src/Mod/Fem/App/CMakeLists.txt b/src/Mod/Fem/App/CMakeLists.txt index 6a8f02fb2e..2ddc53b4df 100644 --- a/src/Mod/Fem/App/CMakeLists.txt +++ b/src/Mod/Fem/App/CMakeLists.txt @@ -87,6 +87,14 @@ if(BUILD_FEM_VTK) VTKExtensions/vtkFemFrameSourceAlgorithm.h VTKExtensions/vtkFemFrameSourceAlgorithm.cpp ) + SET(VTK_SRCS_0903 + VTKExtensions/vtkCleanUnstructuredGrid.h + VTKExtensions/vtkCleanUnstructuredGrid.cxx + VTKExtensions/vtkSMPTools.h + ) + if (${VTK_MAJOR_VERSION} EQUAL 9 AND ${VTK_MINOR_VERSION} LESS 3) + list(APPEND FemVTK_SRCS ${VTK_SRCS_0903}) + endif() SOURCE_GROUP("VTKExtensions" FILES ${FemVTK_SRCS}) endif(BUILD_FEM_VTK) diff --git a/src/Mod/Fem/App/PreCompiled.h b/src/Mod/Fem/App/PreCompiled.h index 4df4b93e63..e336b2c98d 100644 --- a/src/Mod/Fem/App/PreCompiled.h +++ b/src/Mod/Fem/App/PreCompiled.h @@ -148,10 +148,14 @@ #include // VTK +#include #include #include #include #include +#if VTK_VERSION_NUMBER >= VTK_VERSION_CHECK(9, 3, 0) +#include +#endif #include #include #include diff --git a/src/Mod/Fem/App/VTKExtensions/CMakeLists.txt b/src/Mod/Fem/App/VTKExtensions/CMakeLists.txt deleted file mode 100644 index 8e9fbd85f7..0000000000 --- a/src/Mod/Fem/App/VTKExtensions/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -SET(FemVTK_SRCS - vtkFemFrameSourceAlgorithm.h - vtkFemFrameSourceAlgorithm.cpp - ) - -target_sources(Fem PRIVATE ${FemVTK_SRCS}) diff --git a/src/Mod/Fem/App/VTKExtensions/vtkCleanUnstructuredGrid.cxx b/src/Mod/Fem/App/VTKExtensions/vtkCleanUnstructuredGrid.cxx new file mode 100644 index 0000000000..94118758d7 --- /dev/null +++ b/src/Mod/Fem/App/VTKExtensions/vtkCleanUnstructuredGrid.cxx @@ -0,0 +1,680 @@ +// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen +// SPDX-FileCopyrightText: Copyright (c) Kitware, Inc. +// SPDX-License-Identifier: BSD-3-Clause + + +#include "PreCompiled.h" + +#ifndef _PreComp_ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#endif + +#include "vtkCleanUnstructuredGrid.h" +#include "vtkSMPTools.h" + +namespace +{ + +int GetDimension(unsigned char type) +{ + // For the most common cell types, this is a fast call. If the cell type is + // more exotic, then the cell must be grabbed and queried directly, which is + // slow. + switch (type) { + case VTK_EMPTY_CELL: + case VTK_VERTEX: + case VTK_POLY_VERTEX: + return 0; + case VTK_LINE: + case VTK_POLY_LINE: + case VTK_QUADRATIC_EDGE: + case VTK_CUBIC_LINE: + case VTK_LAGRANGE_CURVE: + case VTK_BEZIER_CURVE: + return 1; + case VTK_TRIANGLE: + case VTK_QUAD: + case VTK_PIXEL: + case VTK_POLYGON: + case VTK_TRIANGLE_STRIP: + case VTK_QUADRATIC_TRIANGLE: + case VTK_QUADRATIC_QUAD: + case VTK_QUADRATIC_POLYGON: + case VTK_BIQUADRATIC_QUAD: + case VTK_BIQUADRATIC_TRIANGLE: + case VTK_LAGRANGE_TRIANGLE: + case VTK_LAGRANGE_QUADRILATERAL: + case VTK_BEZIER_TRIANGLE: + case VTK_BEZIER_QUADRILATERAL: + return 2; + case VTK_TETRA: + case VTK_VOXEL: + case VTK_HEXAHEDRON: + case VTK_WEDGE: + case VTK_PYRAMID: + case VTK_PENTAGONAL_PRISM: + case VTK_HEXAGONAL_PRISM: + case VTK_QUADRATIC_TETRA: + case VTK_QUADRATIC_HEXAHEDRON: + case VTK_QUADRATIC_WEDGE: + case VTK_QUADRATIC_PYRAMID: + case VTK_BIQUADRATIC_QUADRATIC_HEXAHEDRON: + case VTK_BIQUADRATIC_QUADRATIC_WEDGE: + case VTK_TRIQUADRATIC_HEXAHEDRON: + case VTK_TRIQUADRATIC_PYRAMID: + case VTK_LAGRANGE_TETRAHEDRON: + case VTK_LAGRANGE_HEXAHEDRON: + case VTK_LAGRANGE_WEDGE: + case VTK_BEZIER_TETRAHEDRON: + case VTK_BEZIER_HEXAHEDRON: + case VTK_BEZIER_WEDGE: + return 3; + default: + vtkNew cell; + cell->SetCellType(type); + return cell->GetCellDimension(); + } +} + +constexpr unsigned char MAX_CELL_DIM = 3; + +void AllocatePointAttributes(vtkPointData* inPD, vtkPointData* outPD, vtkIdType nPoints) +{ + for (vtkIdType iArr = 0; iArr < inPD->GetNumberOfArrays(); ++iArr) { + auto inArr = inPD->GetAbstractArray(iArr); + auto outArr = outPD->GetAbstractArray(inArr->GetName()); + if (!outArr) { + vtkGenericWarningMacro(<< inArr->GetName() << " output array is nullptr"); + continue; + } + outArr->SetNumberOfComponents(inArr->GetNumberOfComponents()); + outArr->SetNumberOfTuples(nPoints); + } +} + +unsigned char GetTopologicalDimension(vtkDataSet* ds) +{ + vtkNew cTypes; + ds->GetCellTypes(cTypes); + unsigned char topoDim = 0; + for (vtkIdType iC = 0; iC < cTypes->GetNumberOfTypes(); ++iC) { + unsigned char dimC = static_cast(GetDimension(cTypes->GetCellType(iC))); + topoDim = std::max(topoDim, dimC); + if (topoDim >= MAX_CELL_DIM) { + break; + } + } + if (topoDim > MAX_CELL_DIM) { + vtkErrorWithObjectMacro( + nullptr, + "Topological dimension of data set is larger than the maximal cell dimension"); + return MAX_CELL_DIM; + } + return topoDim; +} + +struct WeighingStrategy +{ + virtual ~WeighingStrategy() = default; + virtual vtkSmartPointer ComputeWeights(vtkDataSet* ds, + const std::vector& ptMap) = 0; +}; + +struct FirstPointStrategy: public WeighingStrategy +{ + vtkSmartPointer ComputeWeights(vtkDataSet* ds, + const std::vector& ptMap) override + { + if (ds->GetNumberOfPoints() != static_cast(ptMap.size())) { + vtkGenericWarningMacro( + "Number of points in dataset and number of entries in point map don't line up."); + return nullptr; + } + vtkNew weights; + weights->SetNumberOfComponents(1); + weights->SetNumberOfTuples(ds->GetNumberOfPoints()); + weights->Fill(0.0); + + std::unordered_set firstPoints; + for (vtkIdType iP = 0; iP < ds->GetNumberOfPoints(); ++iP) { + if (firstPoints.find(ptMap[iP]) != firstPoints.end()) { + continue; + } + firstPoints.insert(ptMap[iP]); + weights->SetValue(iP, 1.0); + } + return weights; + } +}; + +struct AveragingStrategy: public WeighingStrategy +{ + vtkSmartPointer ComputeWeights(vtkDataSet* ds, + const std::vector& ptMap) override + { + if (ds->GetNumberOfPoints() != static_cast(ptMap.size())) { + vtkGenericWarningMacro( + "Number of points in dataset and number of entries in point map don't line up."); + return nullptr; + } + std::vector counts(ds->GetNumberOfPoints(), 0.0); + for (vtkIdType iP = 0; iP < ds->GetNumberOfPoints(); ++iP) { + if (ptMap[iP] < 0) { + continue; + } + counts[ptMap[iP]] += 1.0; + } + + vtkNew weights; + weights->SetNumberOfComponents(1); + weights->SetNumberOfTuples(ds->GetNumberOfPoints()); + weights->Fill(0.0); + + auto wRange = vtk::DataArrayValueRange<1>(weights); + for (vtkIdType iP = 0; iP < ds->GetNumberOfPoints(); ++iP) { + if (ptMap[iP] < 0) { + continue; + } + wRange[iP] = (counts[ptMap[iP]] ? 1.0 / counts[ptMap[iP]] : 0.0); + } + + return weights; + } +}; + +struct SpatialDensityStrategy: public WeighingStrategy +{ + vtkSmartPointer ComputeWeights(vtkDataSet* ds, + const std::vector& ptMap) override + { + if (ds->GetNumberOfPoints() != static_cast(ptMap.size())) { + vtkGenericWarningMacro( + "Number of points in dataset and number of entries in point map don't line up."); + return nullptr; + } + // Get topological dimension of data set + auto topoDim = GetTopologicalDimension(ds); + // Calculate cell measures + vtkSmartPointer measures; + { + // this scope is so that any extra memory related to the cell size filter gets released + // when no longer needed + vtkNew cellSizeFilter; + cellSizeFilter->SetInputData(ds); + cellSizeFilter->Update(); + auto output = vtkDataSet::SafeDownCast(cellSizeFilter->GetOutputDataObject(0)); + auto cData = output->GetCellData(); + if (!cData || !cData->HasArray("VertexCount") || !cData->HasArray("Length") + || !cData->HasArray("Area") || !cData->HasArray("Volume")) { + vtkErrorWithObjectMacro( + nullptr, + "Could not find correct cell data in output of cell size filter"); + return nullptr; + } + switch (topoDim) { + case 0: + measures = cData->GetArray("VertexCount"); + break; + case 1: + measures = cData->GetArray("Length"); + break; + case 2: + measures = cData->GetArray("Area"); + break; + case 3: + measures = cData->GetArray("Volume"); + break; + default: + vtkErrorWithObjectMacro(nullptr, + "Topological dimension of data set is higher than 3. " + "Cannot deal with that."); + return nullptr; + } + } + // Distribute spatial density to points + vtkNew density; + density->SetNumberOfComponents(1); + density->SetNumberOfTuples(ds->GetNumberOfPoints()); + density->Fill(0.0); + auto dRange = vtk::DataArrayValueRange<1>(density); + auto mRange = vtk::DataArrayValueRange<1>(measures); + if (ds->GetNumberOfCells() > 0) { + vtkNew pointIdList; + ds->GetCellPoints(0, pointIdList); + } + vtkSMPThreadLocalObject localPointIds; + auto distribute = [&](vtkIdType begin, vtkIdType end) { + for (vtkIdType iC = begin; iC < end; ++iC) { + ds->GetCellPoints(iC, localPointIds.Local()); + double participation = mRange[iC] / localPointIds.Local()->GetNumberOfIds(); + for (vtkIdType iP = 0; iP < localPointIds.Local()->GetNumberOfIds(); ++iP) { + dRange[localPointIds.Local()->GetId(iP)] += participation; + } + } + }; + // For thread safety + if (ds->GetNumberOfCells() > 0) { + vtkNew buffer; + ds->GetCellPoints(0, buffer); + } + distribute(0, ds->GetNumberOfCells()); + // Merits a dedicated struct with a reduce operation + // collisions occuring in the += operation + // vtkSMPTools::For(0, ds->GetNumberOfCells(), distribute); + // Normalize spatial densities with respect to point map + { + std::vector masses(*std::max_element(ptMap.begin(), ptMap.end()) + 1, 0); + auto computeMasses = [&dRange, &masses, &ptMap](vtkIdType begin, vtkIdType end) { + for (vtkIdType iP = begin; iP < end; ++iP) { + if (ptMap[iP] < 0) { + dRange[iP] = 0.0; + continue; + } + masses[ptMap[iP]] += dRange[iP]; + } + }; + computeMasses(0, ds->GetNumberOfPoints()); + // Merits a dedicated struct with a reduce operation + // collisions occuring in the += operation + // vtkSMPTools::For(0, ds->GetNumberOfPoints(), computeMasses); + vtkSMPTools::For(0, + ds->GetNumberOfPoints(), + [&dRange, &masses, &ptMap](vtkIdType begin, vtkIdType end) { + for (vtkIdType iP = begin; iP < end; ++iP) { + if (ptMap[iP] < 0) { + continue; + } + dRange[iP] = + (masses[ptMap[iP]] != 0 ? dRange[iP] / masses[ptMap[iP]] + : 0.0); + } + }); + } + return density; + } +}; + +struct WeighingStrategyFactory +{ + std::shared_ptr operator()(vtkCleanUnstructuredGrid::DataWeighingType dwt) + { + switch (dwt) { + case vtkCleanUnstructuredGrid::FIRST_POINT: + return std::make_shared(); + case vtkCleanUnstructuredGrid::AVERAGING: + return std::make_shared(); + case vtkCleanUnstructuredGrid::SPATIAL_DENSITY: + return std::make_shared(); + default: + vtkGenericWarningMacro("Incorrect weighing strategy type passed to factory. " + "defaulting to FIRST_POINT."); + return std::make_shared(); + } + } +}; + +struct WeighingWorklet +{ + template + void operator()(ArrayTypeIn* inArray, + ArrayTypeOut* outArray, + vtkDoubleArray* weights, + const std::vector& ptMap) + { + outArray->Fill(0); + auto inRange = vtk::DataArrayTupleRange(inArray); + auto outRange = vtk::DataArrayTupleRange(outArray); + auto wRange = vtk::DataArrayValueRange<1>(weights); + auto weighing = [&](vtkIdType begin, vtkIdType end) { + for (vtkIdType iP = begin; iP < end; ++iP) { + if (ptMap[iP] < 0) { + continue; + } + auto inT = inRange[iP]; + auto outT = outRange[ptMap[iP]]; + for (vtkIdType iT = 0; iT < inArray->GetNumberOfComponents(); ++iT) { + outT[iT] += wRange[iP] * inT[iT]; + } + } + }; + weighing(0, inArray->GetNumberOfTuples()); + // Merits a dedicated struct with a reduce operation + // collisions occuring in the += operation + // vtkSMPTools::For(0, inArray->GetNumberOfTuples(), weighing); + } +}; + +template<> +void WeighingWorklet::operator()(vtkBitArray* inArray, + vtkBitArray* outArray, + vtkDoubleArray* vtkNotUsed(weights), + const std::vector& ptMap) +{ + outArray->Fill(0); + for (vtkIdType iP = 0; iP < inArray->GetNumberOfValues(); ++iP) { + if (ptMap[iP] < 0) { + continue; + } + outArray->SetValue(ptMap[iP], inArray->GetValue(iP)); + } +} + +template<> +void WeighingWorklet::operator()(vtkStringArray* inArray, + vtkStringArray* outArray, + vtkDoubleArray* vtkNotUsed(weights), + const std::vector& ptMap) +{ + for (vtkIdType iP = 0; iP < inArray->GetNumberOfValues(); ++iP) { + if (ptMap[iP] < 0) { + continue; + } + outArray->SetValue(ptMap[iP], inArray->GetValue(iP)); + } +} + +template<> +void WeighingWorklet::operator()(vtkAbstractArray* inArray, + vtkAbstractArray* outArray, + vtkDoubleArray* vtkNotUsed(weights), + const std::vector& ptMap) +{ + for (vtkIdType iP = 0; iP < inArray->GetNumberOfTuples(); ++iP) { + if (ptMap[iP] < 0) { + continue; + } + outArray->InsertTuple(ptMap[iP], iP, inArray); + } +} + +void WeightAttributes(vtkPointData* inPD, + vtkPointData* outPD, + vtkDoubleArray* weights, + const std::vector& ptMap) +{ + // better here to use a Dispatch2BySameArrayType, but that doesn't exist + using Dispatcher = vtkArrayDispatch::Dispatch2BySameValueType; + WeighingWorklet worker; + for (vtkIdType iArr = 0; iArr < inPD->GetNumberOfArrays(); ++iArr) { + auto inArr = inPD->GetArray(iArr); + // if not data array check for abstract + if (!inArr) { + auto inAbsArr = inPD->GetAbstractArray(iArr); + if (!inAbsArr) { + vtkGenericWarningMacro("One of the arrays in the point data is nullptr."); + continue; + } + auto outAbsArr = outPD->GetAbstractArray(inAbsArr->GetName()); + if (!outAbsArr) { + vtkGenericWarningMacro("Output array " << inAbsArr->GetName() << " is nullptr."); + continue; + } + // if string array go to dedicated path + auto inStrArr = vtkStringArray::SafeDownCast(inAbsArr); + if (inStrArr) { + auto outStrArr = vtkStringArray::SafeDownCast(outAbsArr); + if (!outStrArr) { + vtkGenericWarningMacro("Output array " + << inStrArr->GetName() + << " is not the same type as input string array."); + continue; + } + worker(inStrArr, outStrArr, weights, ptMap); + continue; + } + worker(inAbsArr, outAbsArr, weights, ptMap); + continue; + } + auto outArr = outPD->GetArray(inArr->GetName()); + if (!outArr) { + vtkGenericWarningMacro("Output array " << inArr->GetName() + << " is nullptr or not a vtkDataArray."); + continue; + } + if (!Dispatcher::Execute(inArr, outArr, worker, weights, ptMap)) { + auto inBitArr = vtkBitArray::SafeDownCast(inArr); + auto outBitArr = vtkBitArray::SafeDownCast(outArr); + if (inBitArr && outBitArr) { + worker(inBitArr, outBitArr, weights, ptMap); + } + worker(inArr, outArr, weights, ptMap); + } + } +} + +} // namespace + +vtkStandardNewMacro(vtkCleanUnstructuredGrid); +vtkCxxSetSmartPointerMacro(vtkCleanUnstructuredGrid, Locator, vtkIncrementalPointLocator); + +//---------------------------------------------------------------------------- +vtkCleanUnstructuredGrid::vtkCleanUnstructuredGrid() = default; + +//---------------------------------------------------------------------------- +vtkCleanUnstructuredGrid::~vtkCleanUnstructuredGrid() = default; + +//---------------------------------------------------------------------------- +void vtkCleanUnstructuredGrid::PrintSelf(ostream& os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); + + if (this->Locator) { + os << indent << "Locator: "; + this->Locator->PrintSelf(os, indent.GetNextIndent()); + } + else { + os << indent << "Locator: none\n"; + } + os << indent << "ToleranceIsAbsolute: " << (this->ToleranceIsAbsolute ? "On\n" : "Off\n"); + os << indent << "Tolerance: " << this->Tolerance << std::endl; + os << indent << "AbsoluteTolerance: " << this->AbsoluteTolerance << std::endl; + os << indent + << "RemovePointsWithoutCells: " << (this->RemovePointsWithoutCells ? "On\n" : "Off\n"); + os << indent << "OutputPointsPrecision: " << this->OutputPointsPrecision << std::endl; +} + +//---------------------------------------------------------------------------- +int vtkCleanUnstructuredGrid::RequestData(vtkInformation* vtkNotUsed(request), + vtkInformationVector** inputVector, + vtkInformationVector* outputVector) +{ + vtkInformation* inInfo = inputVector[0]->GetInformationObject(0); + vtkInformation* outInfo = outputVector->GetInformationObject(0); + + vtkDataSet* input = vtkDataSet::SafeDownCast(inInfo->Get(vtkDataObject::DATA_OBJECT())); + vtkUnstructuredGrid* output = + vtkUnstructuredGrid::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT())); + + if (input->GetNumberOfCells() == 0) { + // set up a ugrid with same data arrays as input, but + // no points, cells or data. + output->Allocate(1); + output->GetPointData()->CopyAllocate(input->GetPointData(), VTK_CELL_SIZE); + output->GetCellData()->CopyAllocate(input->GetCellData(), 1); + vtkNew pts; + output->SetPoints(pts); + return 1; + } + + output->GetPointData()->CopyAllocate(input->GetPointData()); + output->GetCellData()->PassData(input->GetCellData()); + + // First, create a new points array that eliminate duplicate points. + // Also create a mapping from the old point id to the new. + vtkNew newPts; + + // Set the desired precision for the points in the output. + if (this->OutputPointsPrecision == vtkAlgorithm::DEFAULT_PRECISION) { + // The logical behaviour would be to use the data type from the input. + // However, input is a vtkDataSet, which has no point data type; only the + // derived class vtkPointSet has a vtkPoints attribute, so only for that + // the logical practice can be applied, while for others (currently + // vtkImageData and vtkRectilinearGrid) the data type is the default + // for vtkPoints - which is VTK_FLOAT. + vtkPointSet* ps = vtkPointSet::SafeDownCast(input); + if (ps) { + newPts->SetDataType(ps->GetPoints()->GetDataType()); + } + } + else if (this->OutputPointsPrecision == vtkAlgorithm::SINGLE_PRECISION) { + newPts->SetDataType(VTK_FLOAT); + } + else if (this->OutputPointsPrecision == vtkAlgorithm::DOUBLE_PRECISION) { + newPts->SetDataType(VTK_DOUBLE); + } + + vtkIdType num = input->GetNumberOfPoints(); + vtkIdType id; + vtkIdType newId; + std::vector ptMap(num); + double pt[3]; + + this->CreateDefaultLocator(input); + if (this->ToleranceIsAbsolute) { + this->Locator->SetTolerance(this->AbsoluteTolerance); + } + else { + this->Locator->SetTolerance(this->Tolerance * input->GetLength()); + } + double bounds[6]; + input->GetBounds(bounds); + this->Locator->InitPointInsertion(newPts, bounds); + + vtkIdType progressStep = num / 100; + if (progressStep == 0) { + progressStep = 1; + } + + vtkNew pointCells; + for (id = 0; id < num; ++id) { + if (id % progressStep == 0) { + this->UpdateProgress(0.8 * ((float)id / num)); + } + + bool insert = true; + if (this->RemovePointsWithoutCells) { + input->GetPointCells(id, pointCells); + if (pointCells->GetNumberOfIds() == 0) { + insert = false; + } + } + + if (insert) { + input->GetPoint(id, pt); + this->Locator->InsertUniquePoint(pt, newId); + ptMap[id] = newId; + } + else { + // Strictly speaking, this is not needed + // as this is never accessed, but better not let + // an id undefined. + ptMap[id] = -1; + } + } + output->SetPoints(newPts); + + ::WeighingStrategyFactory factory; + auto strategy = factory(static_cast(this->PointDataWeighingStrategy)); + auto weights = strategy->ComputeWeights(input, ptMap); + auto inPD = input->GetPointData(); + auto outPD = output->GetPointData(); + ::AllocatePointAttributes(inPD, outPD, output->GetNumberOfPoints()); + ::WeightAttributes(inPD, outPD, weights, ptMap); + + // Now copy the cells. + vtkNew cellPoints; + num = input->GetNumberOfCells(); + output->Allocate(num); + for (id = 0; id < num; ++id) { + if (id % progressStep == 0) { + this->UpdateProgress(0.8 + 0.2 * (static_cast(id) / num)); + } + // special handling for polyhedron cells + if (vtkUnstructuredGrid::SafeDownCast(input) && input->GetCellType(id) == VTK_POLYHEDRON) { + vtkUnstructuredGrid::SafeDownCast(input)->GetFaceStream(id, cellPoints); + vtkUnstructuredGrid::ConvertFaceStreamPointIds(cellPoints, ptMap.data()); + } + else { + input->GetCellPoints(id, cellPoints); + for (int i = 0; i < cellPoints->GetNumberOfIds(); i++) { + int cellPtId = cellPoints->GetId(i); + newId = ptMap[cellPtId]; + cellPoints->SetId(i, newId); + } + } + output->InsertNextCell(input->GetCellType(id), cellPoints); + } + + output->Squeeze(); + return 1; +} + +//---------------------------------------------------------------------------- +int vtkCleanUnstructuredGrid::FillInputPortInformation(int vtkNotUsed(port), vtkInformation* info) +{ + info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkDataSet"); + return 1; +} + +//------------------------------------------------------------------------------ +vtkIncrementalPointLocator* vtkCleanUnstructuredGrid::GetLocator() +{ + return this->Locator; +} + +//---------------------------------------------------------------------------- +void vtkCleanUnstructuredGrid::CreateDefaultLocator(vtkDataSet* input) +{ + double tol; + if (this->ToleranceIsAbsolute) { + tol = this->AbsoluteTolerance; + } + else { + if (input) { + tol = this->Tolerance * input->GetLength(); + } + else { + tol = this->Tolerance; + } + } + + if (this->Locator.Get() == nullptr) { + if (tol == 0.0) { + this->Locator = vtkSmartPointer::New(); + } + else { + this->Locator = vtkSmartPointer::New(); + } + } + else { + // check that the tolerance wasn't changed from zero to non-zero + if ((tol > 0.0) && (this->GetLocator()->GetTolerance() == 0.0)) { + this->Locator = vtkSmartPointer::New(); + } + } +} diff --git a/src/Mod/Fem/App/VTKExtensions/vtkCleanUnstructuredGrid.h b/src/Mod/Fem/App/VTKExtensions/vtkCleanUnstructuredGrid.h new file mode 100644 index 0000000000..f3db74ce4d --- /dev/null +++ b/src/Mod/Fem/App/VTKExtensions/vtkCleanUnstructuredGrid.h @@ -0,0 +1,153 @@ +// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen +// SPDX-FileCopyrightText: Copyright (c) Kitware, Inc. +// SPDX-License-Identifier: BSD-3-Clause +/** + * @class vtkCleanUnstructuredGrid + * @brief merge duplicate points + * + * + * vtkCleanUnstructuredGrid is a filter that takes unstructured grid data as + * input and generates unstructured grid data as output. vtkCleanUnstructuredGrid can + * merge duplicate points (with coincident coordinates) using the vtkMergePoints object + * to merge points. + * + * @sa + * vtkCleanPolyData + */ + + +#ifndef vtkCleanUnstructuredGrid_h +#define vtkCleanUnstructuredGrid_h + +#include "vtkFiltersGeneralModule.h" // For export macro +#include "vtkSmartPointer.h" +#include "vtkUnstructuredGridAlgorithm.h" + +/*VTK_ABI_NAMESPACE_BEGIN*/ + +class vtkIncrementalPointLocator; +class vtkDataSet; + +class /*VTKFILTERSGENERAL_EXPORT*/ vtkCleanUnstructuredGrid: public vtkUnstructuredGridAlgorithm +{ +public: + static vtkCleanUnstructuredGrid* New(); + vtkTypeMacro(vtkCleanUnstructuredGrid, vtkUnstructuredGridAlgorithm); + void PrintSelf(ostream& os, vtkIndent indent) override; + + ///@{ + /** + * By default ToleranceIsAbsolute is false and Tolerance is + * a fraction of Bounding box diagonal, if true, AbsoluteTolerance is + * used when adding points to locator (merging) + */ + vtkSetMacro(ToleranceIsAbsolute, bool); + vtkBooleanMacro(ToleranceIsAbsolute, bool); + vtkGetMacro(ToleranceIsAbsolute, bool); + ///@} + + ///@{ + /** + * Specify tolerance in terms of fraction of bounding box length. + * Default is 0.0. + */ + vtkSetClampMacro(Tolerance, double, 0.0, 1.0); + vtkGetMacro(Tolerance, double); + ///@} + + ///@{ + /** + * Specify tolerance in absolute terms. Default is 1.0. + */ + vtkSetClampMacro(AbsoluteTolerance, double, 0.0, VTK_DOUBLE_MAX); + vtkGetMacro(AbsoluteTolerance, double); + ///@} + + ///@{ + /** + * Set/Get a spatial locator for speeding the search process. By + * default an instance of vtkMergePoints is used. + */ + virtual void SetLocator(vtkIncrementalPointLocator* locator); + virtual vtkIncrementalPointLocator* GetLocator(); + ///@} + + /** + * Create default locator. Used to create one when none is specified. + */ + void CreateDefaultLocator(vtkDataSet* input = nullptr); + + /** + * Release locator + */ + void ReleaseLocator() + { + this->SetLocator(nullptr); + } + + ///@{ + /** + * Set/get the desired precision for the output types. See the documentation + * for the vtkAlgorithm::DesiredOutputPrecision enum for an explanation of + * the available precision settings. + */ + vtkSetMacro(OutputPointsPrecision, int); + vtkGetMacro(OutputPointsPrecision, int); + ///@} + + ///@{ + /** + * Set/Get whether to remove points that do not + * have any cells associated with it. + * Default is false + */ + vtkSetMacro(RemovePointsWithoutCells, bool); + vtkGetMacro(RemovePointsWithoutCells, bool); + vtkBooleanMacro(RemovePointsWithoutCells, bool); + ///@} + + ///@{ + /** + * Set/Get the strategy used to weigh point data on merging points + * + * Possibilities: + * - FIRST_POINT (int(0), default): the point with the lowest index imposes its data on to the + * merged point + * - AVERAGING (int(1)): a number average is performed on all the duplicate points + * - SPATIAL_DENSITY (int(2)): an average by attached cell volume (i.e. for every cell the point + * is connected to sum cell_volume/number_cell_points) is performed on the point data + */ + vtkGetMacro(PointDataWeighingStrategy, int); + vtkSetClampMacro(PointDataWeighingStrategy, int, FIRST_POINT, NUMBER_OF_WEIGHING_TYPES - 1); + ///@} + + enum DataWeighingType + { + FIRST_POINT = 0, + AVERAGING, + SPATIAL_DENSITY, + NUMBER_OF_WEIGHING_TYPES + }; + +protected: + vtkCleanUnstructuredGrid(); + ~vtkCleanUnstructuredGrid() override; + + bool ToleranceIsAbsolute = false; + double Tolerance = 0.0; + double AbsoluteTolerance = 1.0; + bool RemovePointsWithoutCells = false; + vtkSmartPointer Locator; + int OutputPointsPrecision = vtkAlgorithm::DEFAULT_PRECISION; + int PointDataWeighingStrategy = FIRST_POINT; + + int RequestData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) override; + int FillInputPortInformation(int port, vtkInformation* info) override; + +private: + vtkCleanUnstructuredGrid(const vtkCleanUnstructuredGrid&) = delete; + void operator=(const vtkCleanUnstructuredGrid&) = delete; +}; +/*VTK_ABI_NAMESPACE_END*/ +#endif +// VTK-HeaderTest-Exclude: vtkCleanUnstructuredGrid.h diff --git a/src/Mod/Fem/App/VTKExtensions/vtkSMPTools.h b/src/Mod/Fem/App/VTKExtensions/vtkSMPTools.h new file mode 100644 index 0000000000..611860721b --- /dev/null +++ b/src/Mod/Fem/App/VTKExtensions/vtkSMPTools.h @@ -0,0 +1,604 @@ +/*========================================================================= + + Program: Visualization Toolkit + Module: vtkSMPTools.h + + Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen + All rights reserved. + See Copyright.txt or http://www.kitware.com/Copyright.htm for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notice for more information. + +=========================================================================*/ +/** + * @class vtkSMPTools + * @brief A set of parallel (multi-threaded) utility functions. + * + * vtkSMPTools provides a set of utility functions that can + * be used to parallelize parts of VTK code using multiple threads. + * There are several back-end implementations of parallel functionality + * (currently Sequential, TBB, OpenMP and STDThread) that actual execution is + * delegated to. + * + * @sa + * vtkSMPThreadLocal + * vtkSMPThreadLocalObject + */ + +#ifndef vtkSMPTools_h +#define vtkSMPTools_h + +#include "vtkCommonCoreModule.h" // For export macro +#include "vtkObject.h" + +#include "SMP/Common/vtkSMPToolsAPI.h" +#include "vtkSMPThreadLocal.h" // For Initialized + +#include // For std::function +#include // For std::iterator +#include // For std:::enable_if + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +namespace vtk +{ +namespace detail +{ +namespace smp +{ +template +class vtkSMPTools_Has_Initialize +{ + typedef char (&no_type)[1]; + typedef char (&yes_type)[2]; + template + struct V + { + }; + template + static yes_type check(V*); + template + static no_type check(...); + +public: + static bool const value = sizeof(check(nullptr)) == sizeof(yes_type); +}; + +template +class vtkSMPTools_Has_Initialize_const +{ + typedef char (&no_type)[1]; + typedef char (&yes_type)[2]; + template + struct V + { + }; + template + static yes_type check(V*); + template + static no_type check(...); + +public: + static bool const value = sizeof(check(0)) == sizeof(yes_type); +}; + +template +struct vtkSMPTools_FunctorInternal; + +template +struct vtkSMPTools_FunctorInternal +{ + Functor& F; + vtkSMPTools_FunctorInternal(Functor& f) + : F(f) + {} + void Execute(vtkIdType first, vtkIdType last) + { + this->F(first, last); + } + void For(vtkIdType first, vtkIdType last, vtkIdType grain) + { + auto& SMPToolsAPI = vtkSMPToolsAPI::GetInstance(); + SMPToolsAPI.For(first, last, grain, *this); + } + vtkSMPTools_FunctorInternal& + operator=(const vtkSMPTools_FunctorInternal&); + vtkSMPTools_FunctorInternal(const vtkSMPTools_FunctorInternal&); +}; + +template +struct vtkSMPTools_FunctorInternal +{ + Functor& F; + vtkSMPThreadLocal Initialized; + vtkSMPTools_FunctorInternal(Functor& f) + : F(f) + , Initialized(0) + {} + void Execute(vtkIdType first, vtkIdType last) + { + unsigned char& inited = this->Initialized.Local(); + if (!inited) { + this->F.Initialize(); + inited = 1; + } + this->F(first, last); + } + void For(vtkIdType first, vtkIdType last, vtkIdType grain) + { + auto& SMPToolsAPI = vtkSMPToolsAPI::GetInstance(); + SMPToolsAPI.For(first, last, grain, *this); + this->F.Reduce(); + } + vtkSMPTools_FunctorInternal& + operator=(const vtkSMPTools_FunctorInternal&); + vtkSMPTools_FunctorInternal(const vtkSMPTools_FunctorInternal&); +}; + +template +class vtkSMPTools_Lookup_For +{ + static bool const init = vtkSMPTools_Has_Initialize::value; + +public: + typedef vtkSMPTools_FunctorInternal type; +}; + +template +class vtkSMPTools_Lookup_For +{ + static bool const init = vtkSMPTools_Has_Initialize_const::value; + +public: + typedef vtkSMPTools_FunctorInternal type; +}; + +template +struct vtkSMPTools_RangeFunctor; + +template +struct vtkSMPTools_RangeFunctor +{ + Functor& F; + Iterator& Begin; + vtkSMPTools_RangeFunctor(Iterator& begin, Functor& f) + : F(f) + , Begin(begin) + {} + void operator()(vtkIdType first, vtkIdType last) + { + Iterator itFirst(Begin); + std::advance(itFirst, first); + Iterator itLast(itFirst); + std::advance(itLast, last - first); + this->F(itFirst, itLast); + } +}; + +template +struct vtkSMPTools_RangeFunctor +{ + Functor& F; + Iterator& Begin; + vtkSMPTools_RangeFunctor(Iterator& begin, Functor& f) + : F(f) + , Begin(begin) + {} + void Initialize() + { + this->F.Initialize(); + } + void operator()(vtkIdType first, vtkIdType last) + { + Iterator itFirst(Begin); + std::advance(itFirst, first); + Iterator itLast(itFirst); + std::advance(itLast, last - first); + this->F(itFirst, itLast); + } + void Reduce() + { + this->F.Reduce(); + } +}; + +template +class vtkSMPTools_Lookup_RangeFor +{ + static bool const init = vtkSMPTools_Has_Initialize::value; + +public: + typedef vtkSMPTools_RangeFunctor type; +}; + +template +class vtkSMPTools_Lookup_RangeFor +{ + static bool const init = vtkSMPTools_Has_Initialize_const::value; + +public: + typedef vtkSMPTools_RangeFunctor type; +}; + +template +using resolvedNotInt = typename std::enable_if::value, void>::type; +} // namespace smp +} // namespace detail +} // namespace vtk +#endif // DOXYGEN_SHOULD_SKIP_THIS + +class VTKCOMMONCORE_EXPORT vtkSMPTools +{ +public: + ///@{ + /** + * Execute a for operation in parallel. First and last + * define the range over which to operate (which is defined + * by the operator). The operation executed is defined by + * operator() of the functor object. The grain gives the parallel + * engine a hint about the coarseness over which to parallelize + * the function (as defined by last-first of each execution of + * operator() ). + */ + template + static void For(vtkIdType first, vtkIdType last, vtkIdType grain, Functor& f) + { + typename vtk::detail::smp::vtkSMPTools_Lookup_For::type fi(f); + fi.For(first, last, grain); + } + + template + static void For(vtkIdType first, vtkIdType last, vtkIdType grain, Functor const& f) + { + typename vtk::detail::smp::vtkSMPTools_Lookup_For::type fi(f); + fi.For(first, last, grain); + } + ///@} + + ///@{ + /** + * Execute a for operation in parallel. First and last + * define the range over which to operate (which is defined + * by the operator). The operation executed is defined by + * operator() of the functor object. The grain gives the parallel + * engine a hint about the coarseness over which to parallelize + * the function (as defined by last-first of each execution of + * operator() ). Uses a default value for the grain. + */ + template + static void For(vtkIdType first, vtkIdType last, Functor& f) + { + vtkSMPTools::For(first, last, 0, f); + } + + template + static void For(vtkIdType first, vtkIdType last, Functor const& f) + { + vtkSMPTools::For(first, last, 0, f); + } + ///@} + + ///@{ + /** + * Execute a for operation in parallel. Begin and end iterators + * define the range over which to operate (which is defined + * by the operator). The operation executed is defined by + * operator() of the functor object. The grain gives the parallel + * engine a hint about the coarseness over which to parallelize + * the function (as defined by last-first of each execution of + * operator() ). + * + * Usage example: + * \code + * template + * class ExampleFunctor + * { + * void operator()(IteratorT begin, IteratorT end) + * { + * for (IteratorT it = begin; it != end; ++it) + * { + * // Do stuff + * } + * } + * }; + * ExampleFunctor::iterator> worker; + * vtkSMPTools::For(container.begin(), container.end(), 5, worker); + * \endcode + * + * Lambda are also supported through Functor const& + * function overload: + * \code + * vtkSMPTools::For(container.begin(), container.end(), 5, + * [](std::set::iterator begin, std::set::iterator end) { + * // Do stuff + * }); + * \endcode + */ + template + static vtk::detail::smp::resolvedNotInt + For(Iter begin, Iter end, vtkIdType grain, Functor& f) + { + vtkIdType size = std::distance(begin, end); + typename vtk::detail::smp::vtkSMPTools_Lookup_RangeFor::type fi(begin, f); + vtkSMPTools::For(0, size, grain, fi); + } + + template + static vtk::detail::smp::resolvedNotInt + For(Iter begin, Iter end, vtkIdType grain, Functor const& f) + { + vtkIdType size = std::distance(begin, end); + typename vtk::detail::smp::vtkSMPTools_Lookup_RangeFor::type fi(begin, + f); + vtkSMPTools::For(0, size, grain, fi); + } + ///@} + + ///@{ + /** + * Execute a for operation in parallel. Begin and end iterators + * define the range over which to operate (which is defined + * by the operator). The operation executed is defined by + * operator() of the functor object. Uses a default value + * for the grain. + * + * Usage example: + * \code + * template + * class ExampleFunctor + * { + * void operator()(IteratorT begin, IteratorT end) + * { + * for (IteratorT it = begin; it != end; ++it) + * { + * // Do stuff + * } + * } + * }; + * ExampleFunctor::iterator> worker; + * vtkSMPTools::For(container.begin(), container.end(), worker); + * \endcode + * + * Lambda are also supported through Functor const& + * function overload: + * \code + * vtkSMPTools::For(container.begin(), container.end(), + * [](std::set::iterator begin, std::set::iterator end) { + * // Do stuff + * }); + * \endcode + */ + template + static vtk::detail::smp::resolvedNotInt For(Iter begin, Iter end, Functor& f) + { + vtkSMPTools::For(begin, end, 0, f); + } + + template + static vtk::detail::smp::resolvedNotInt For(Iter begin, Iter end, Functor const& f) + { + vtkSMPTools::For(begin, end, 0, f); + } + ///@} + + /** + * Get the backend in use. + */ + static const char* GetBackend(); + + /** + * /!\ This method is not thread safe. + * Change the backend in use. + * The options can be: "Sequential", "STDThread", "TBB" or "OpenMP" + * + * VTK_SMP_BACKEND_IN_USE env variable can also be used to set the default SMPTools + * backend, in that case SetBackend() doesn't need to be called. + * The backend selected with SetBackend() have the priority over VTK_SMP_BACKEND_IN_USE. + * + * SetBackend() will return true if the backend was found and available. + */ + static bool SetBackend(const char* backend); + + /** + * /!\ This method is not thread safe. + * Initialize the underlying libraries for execution. This is + * not required as it is automatically defined by the libaries. + * However, it can be used to control the maximum number of thread used. + * Make sure to call it before the parallel operation. + * + * If Initialize is called without argument it will reset + * to the maximum number of threads or use the VTK_SMP_MAX_THREADS + * env variable if it is defined. + * + * Note: If VTK_SMP_MAX_THREADS env variable is defined the SMPTools will try + * to use it to set the maximum number of threads. Initialize() doesn't + * need to be called. + */ + static void Initialize(int numThreads = 0); + + /** + * Get the estimated number of threads being used by the backend. + * This should be used as just an estimate since the number of threads may + * vary dynamically and a particular task may not be executed on all the + * available threads. + */ + static int GetEstimatedNumberOfThreads(); + + /** + * /!\ This method is not thread safe. + * If true enable nested parallelism for underlying backends. + * When enabled the comportement is different for each backend: + * - TBB support nested parallelism by default. + * - For OpenMP, we set `omp_set_nested` to true so that it is supported. + * - STDThread support nested parallelism by creating new threads pools. + * - For Sequential nothing changes. + * + * Default to true + */ + static void SetNestedParallelism(bool isNested); + + /** + * Get true if the nested parallelism is enabled. + */ + static bool GetNestedParallelism(); + + /** + * Return true if it is called from a parallel scope. + */ + static bool IsParallelScope(); + + /** + * Structure used to specify configuration for LocalScope() method. + * Several parameters can be configured: + * - MaxNumberOfThreads set the maximum number of threads. + * - Backend set a specific SMPTools backend. + * - NestedParallelism, if true enable nested parallelism. + */ + struct Config + { + int MaxNumberOfThreads = 0; + std::string Backend = vtk::detail::smp::vtkSMPToolsAPI::GetInstance().GetBackend(); + bool NestedParallelism = true; + + Config() + {} + Config(int maxNumberOfThreads) + : MaxNumberOfThreads(maxNumberOfThreads) + {} + Config(std::string backend) + : Backend(backend) + {} + Config(bool nestedParallelism) + : NestedParallelism(nestedParallelism) + {} + Config(int maxNumberOfThreads, std::string backend, bool nestedParallelism) + : MaxNumberOfThreads(maxNumberOfThreads) + , Backend(backend) + , NestedParallelism(nestedParallelism) + {} +#ifndef DOXYGEN_SHOULD_SKIP_THIS + Config(vtk::detail::smp::vtkSMPToolsAPI& API) + : MaxNumberOfThreads(API.GetInternalDesiredNumberOfThread()) + , Backend(API.GetBackend()) + , NestedParallelism(API.GetNestedParallelism()) + {} +#endif // DOXYGEN_SHOULD_SKIP_THIS + }; + + /** + * /!\ This method is not thread safe. + * Change the number of threads locally within this scope and call a functor which + * should contains a vtkSMPTools method. + * + * Usage example: + * \code + * vtkSMPTools::LocalScope( + * vtkSMPTools::Config{ 4, "OpenMP", false }, [&]() { vtkSMPTools::For(0, size, worker); }); + * \endcode + */ + template + static void LocalScope(Config const& config, T&& lambda) + { + auto& SMPToolsAPI = vtk::detail::smp::vtkSMPToolsAPI::GetInstance(); + SMPToolsAPI.LocalScope(config, lambda); + } + + /** + * A convenience method for transforming data. It is a drop in replacement for + * std::transform(), it does a unary operation on the input ranges. The data array must have the + * same length. The performed transformation is defined by operator() of the functor object. + * + * Usage example with vtkDataArray: + * \code + * const auto range0 = vtk::DataArrayValueRange<1>(array0); + * auto range1 = vtk::DataArrayValueRange<1>(array1); + * vtkSMPTools::Transform( + * range0.cbegin(), range0.cend(), range1.begin(), [](double x) { return x - 1; }); + * \endcode + * + * Please visit vtkDataArrayRange.h documentation for more information and optimisation. + */ + template + static void Transform(InputIt inBegin, InputIt inEnd, OutputIt outBegin, Functor transform) + { + auto& SMPToolsAPI = vtk::detail::smp::vtkSMPToolsAPI::GetInstance(); + SMPToolsAPI.Transform(inBegin, inEnd, outBegin, transform); + } + + /** + * A convenience method for transforming data. It is a drop in replacement for + * std::transform(), it does a binary operation on the input ranges. The data array must have + * the same length. The performed transformation is defined by operator() of the functor object. + * + * Usage example with vtkDataArray: + * \code + * const auto range0 = vtk::DataArrayValueRange<1>(array0); + * auto range1 = vtk::DataArrayValueRange<1>(array1); + * vtkSMPTools::Transform( + * range0.cbegin(), range0.cend(), range1.cbegin(), range1.begin(), + * [](double x, double y) { return x * y; }); + * \endcode + * + * Please visit vtkDataArrayRange.h documentation for more information and optimisation. + */ + template + static void Transform(InputIt1 inBegin1, + InputIt1 inEnd, + InputIt2 inBegin2, + OutputIt outBegin, + Functor transform) + { + auto& SMPToolsAPI = vtk::detail::smp::vtkSMPToolsAPI::GetInstance(); + SMPToolsAPI.Transform(inBegin1, inEnd, inBegin2, outBegin, transform); + } + + /** + * A convenience method for filling data. It is a drop in replacement for std::fill(), + * it assign the given value to the element in ranges. + * + * Usage example with vtkDataArray: + * \code + * // Fill range with its first tuple value + * auto range = vtk::DataArrayTupleRange<1>(array); + * const auto value = *range.begin(); + * vtkSMPTools::Fill(range.begin(), range.end(), value); + * \endcode + * + * Please visit vtkDataArrayRange.h documentation for more information and optimisation. + */ + template + static void Fill(Iterator begin, Iterator end, const T& value) + { + auto& SMPToolsAPI = vtk::detail::smp::vtkSMPToolsAPI::GetInstance(); + SMPToolsAPI.Fill(begin, end, value); + } + + /** + * A convenience method for sorting data. It is a drop in replacement for + * std::sort(). Under the hood different methods are used. For example, + * tbb::parallel_sort is used in TBB. + */ + template + static void Sort(RandomAccessIterator begin, RandomAccessIterator end) + { + auto& SMPToolsAPI = vtk::detail::smp::vtkSMPToolsAPI::GetInstance(); + SMPToolsAPI.Sort(begin, end); + } + + /** + * A convenience method for sorting data. It is a drop in replacement for + * std::sort(). Under the hood different methods are used. For example, + * tbb::parallel_sort is used in TBB. This version of Sort() takes a + * comparison class. + */ + template + static void Sort(RandomAccessIterator begin, RandomAccessIterator end, Compare comp) + { + auto& SMPToolsAPI = vtk::detail::smp::vtkSMPToolsAPI::GetInstance(); + SMPToolsAPI.Sort(begin, end, comp); + } +}; + +#endif +// VTK-HeaderTest-Exclude: vtkSMPTools.h From ae1b365586795c1600d3d24cb09824a4c6a5adbf Mon Sep 17 00:00:00 2001 From: marioalexis Date: Wed, 10 Sep 2025 13:38:38 -0300 Subject: [PATCH 3/3] Fem: Remove dependency of the ViewProviderFemPostObject class on user parameters --- src/Mod/Fem/App/FemPostPipeline.cpp | 20 ++++ src/Mod/Fem/App/FemPostPipeline.h | 8 +- src/Mod/Fem/Gui/DlgSettingsFemElmer.ui | 21 +--- src/Mod/Fem/Gui/DlgSettingsFemElmerImp.cpp | 2 - src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp | 98 +------------------ src/Mod/Fem/Gui/ViewProviderFemPostObject.h | 1 - .../Fem/Gui/ViewProviderFemPostPipeline.cpp | 3 + 7 files changed, 32 insertions(+), 121 deletions(-) diff --git a/src/Mod/Fem/App/FemPostPipeline.cpp b/src/Mod/Fem/App/FemPostPipeline.cpp index 953a2957bf..64e3f56c8f 100644 --- a/src/Mod/Fem/App/FemPostPipeline.cpp +++ b/src/Mod/Fem/App/FemPostPipeline.cpp @@ -73,10 +73,17 @@ FemPostPipeline::FemPostPipeline() App::Prop_None, "The frame used to calculate the data in the pipeline processing (read only, " "set via pipeline object)."); + ADD_PROPERTY_TYPE(MergeDuplicate, + (false), + "Pipeline", + App::Prop_None, + "Remove coindent elements."); // create our source algorithm m_source_algorithm = vtkSmartPointer::New(); + m_clean_filter = vtkSmartPointer::New(); + m_clean_filter->SetPointDataWeighingStrategy(vtkCleanUnstructuredGrid::AVERAGING); m_transform_filter->SetInputConnection(m_source_algorithm->GetOutputPort(0)); } @@ -276,6 +283,19 @@ void FemPostPipeline::onChanged(const Property* prop) recomputeChildren(); } + if (prop == &MergeDuplicate) { + if (MergeDuplicate.getValue()) { + m_clean_filter->SetInputConnection(m_source_algorithm->GetOutputPort(0)); + m_transform_filter->SetInputConnection(m_clean_filter->GetOutputPort(0)); + } + else { + m_transform_filter->SetInputConnection(m_source_algorithm->GetOutputPort(0)); + } + m_transform_filter->Update(); + updateData(); + recomputeChildren(); + } + // use the correct data as source if (prop == &Data && !m_block_property) { m_source_algorithm->setDataObject(Data.getValue()); diff --git a/src/Mod/Fem/App/FemPostPipeline.h b/src/Mod/Fem/App/FemPostPipeline.h index 3ba9108f5e..62a5dfdad7 100644 --- a/src/Mod/Fem/App/FemPostPipeline.h +++ b/src/Mod/Fem/App/FemPostPipeline.h @@ -32,6 +32,11 @@ #include "FemResultObject.h" #include "VTKExtensions/vtkFemFrameSourceAlgorithm.h" +#if VTK_VERSION_NUMBER < VTK_VERSION_CHECK(9, 3, 0) +#include "VTKExtensions/vtkCleanUnstructuredGrid.h" +#else +#include +#endif #include @@ -47,7 +52,7 @@ public: FemPostPipeline(); App::PropertyEnumeration Frame; - + App::PropertyBool MergeDuplicate; virtual vtkDataSet* getDataSet() override; Fem::FemPostFunctionProvider* getFunctionProvider(); @@ -108,6 +113,7 @@ private: App::Enumeration m_frameEnum; vtkSmartPointer m_source_algorithm; + vtkSmartPointer m_clean_filter; bool m_block_property = false; bool m_data_updated = false; diff --git a/src/Mod/Fem/Gui/DlgSettingsFemElmer.ui b/src/Mod/Fem/Gui/DlgSettingsFemElmer.ui index 205c3d2e2c..9efc747758 100644 --- a/src/Mod/Fem/Gui/DlgSettingsFemElmer.ui +++ b/src/Mod/Fem/Gui/DlgSettingsFemElmer.ui @@ -15,7 +15,7 @@ - + 0 @@ -169,25 +169,6 @@ - - - - Merge mesh volume regions processed by each CPU core to make boundaries invisible. - - - Filter results - - - true - - - FilterMultiCPUResults - - - Mod/Fem/Elmer - - - diff --git a/src/Mod/Fem/Gui/DlgSettingsFemElmerImp.cpp b/src/Mod/Fem/Gui/DlgSettingsFemElmerImp.cpp index a40fa3c9b7..82183bf115 100644 --- a/src/Mod/Fem/Gui/DlgSettingsFemElmerImp.cpp +++ b/src/Mod/Fem/Gui/DlgSettingsFemElmerImp.cpp @@ -59,7 +59,6 @@ void DlgSettingsFemElmerImp::saveSettings() ui->sb_num_processes->onSave(); - ui->cb_filtering->onSave(); ui->ckb_binary_format->onSave(); ui->ckb_geom_id->onSave(); } @@ -71,7 +70,6 @@ void DlgSettingsFemElmerImp::loadSettings() ui->sb_num_processes->onRestore(); - ui->cb_filtering->onRestore(); ui->ckb_binary_format->onRestore(); ui->ckb_geom_id->onRestore(); } diff --git a/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp b/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp index 9205e2d708..10820b350f 100644 --- a/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp +++ b/src/Mod/Fem/Gui/ViewProviderFemPostObject.cpp @@ -515,7 +515,6 @@ void ViewProviderFemPostObject::updateProperties() void ViewProviderFemPostObject::update3D() { - vtkPolyData* pd = m_currentAlgorithm->GetOutput(); vtkPointData* pntData; @@ -802,79 +801,6 @@ void ViewProviderFemPostObject::updateData(const App::Property* p) } } -void ViewProviderFemPostObject::filterArtifacts(vtkDataSet* dset) -{ - // The problem is that in the surface view the boundary regions of the volumes - // calculated by the different CPU cores is always visible, independent of the - // transparency setting. Elmer is not to blame because this is a property of the - // partial VTK file reader. So this can happen with various inputs - // since FreeCAD can also be used to view VTK files without the need to perform - // an analysis. Therefore it is impossible to know in advance when a filter - // is necessary or not. - // Only for pure CCX analyses we know that no filtering is necessary. However, - // the effort to catch this case is not worth it since the filtering is - // only as time-consuming as enabling the surface filter. In fact, it is like - // performing the surface filter twice. - - // We need to set the filter clipping plane below the z-minimum of the data. - // We can either do this by checking the VTK data or by getting the info from - // the 3D view. We use here the latter because this is much faster. - - // since we will set the filter according to the visible bounding box - // assure the object is visible - bool visibility = this->Visibility.getValue(); - if (!visibility) { - this->Visibility.setValue(true); - } - m_blockPropertyChanges = true; - - Gui::Document* doc = this->getDocument(); - Gui::View3DInventor* view = - qobject_cast(doc->getViewOfViewProvider(this)); - - if (view) { - Gui::View3DInventorViewer* viewer = view->getViewer(); - SbBox3f boundingBox; - boundingBox = viewer->getBoundingBox(); - if (boundingBox.hasVolume()) { - // setup - vtkSmartPointer m_implicit; - auto m_plane = vtkSmartPointer::New(); - m_implicit = m_plane; - m_plane->SetNormal(0., 0., 1.); - auto extractor = vtkSmartPointer::New(); - float dx, dy, dz; - boundingBox.getSize(dx, dy, dz); - // Set plane below the minimum to assure there are - // no boundary cells (touching the function) and for Warp filters - // the user might change the warp factor a lot. Thus set - // 10 times dz to be safe even for unrealistic warp deformations - m_plane->SetOrigin(0., 0., -10 * dz); - extractor->SetClipFunction(m_implicit); - extractor->SetInputData(dset); - extractor->Update(); - auto extractorResult = extractor->GetOutputDataObject(0); - if (extractorResult) { - m_surface->SetInputData(extractorResult); - } - else { - m_surface->SetInputData(dset); - } - } - else { - // for the case that there are only 2D objects - m_surface->SetInputData(dset); - } - } - - m_blockPropertyChanges = false; - - // restore initial vsibility - if (!visibility) { - this->Visibility.setValue(visibility); - } -} - bool ViewProviderFemPostObject::setupPipeline() { if (m_blockPropertyChanges) { @@ -882,9 +808,6 @@ bool ViewProviderFemPostObject::setupPipeline() } auto postObject = getObject(); - - // check all fields if there is a real/imaginary one and if so - // add a field with an absolute value vtkDataSet* dset = postObject->getDataSet(); if (!dset) { return false; @@ -893,26 +816,7 @@ bool ViewProviderFemPostObject::setupPipeline() m_outline->SetInputData(dset); m_points->SetInputData(dset); m_wireframe->SetInputData(dset); - - // Filtering artifacts is necessary for partial VTU files (*.pvtu) independent of the - // current Elmer CPU core settings because the user might load an external file. - // It is only necessary for the surface filter. - // The problem is that when opening an existing FreeCAD file, we get no information how the - // Data of the postObject was once created. The vtkDataObject type does not provide this info. - // Therefore the only way is the hack to filter only if the used Elmer CPU cores are > 1. - auto hGrp = App::GetApplication().GetParameterGroupByPath( - "User parameter:BaseApp/Preferences/Mod/Fem/Elmer"); - bool FilterMultiCPUResults = hGrp->GetBool("FilterMultiCPUResults", true); - int UseNumberOfCores = hGrp->GetInt("UseNumberOfCores", 1); - // filtering is only necessary for pipelines and warp filters - if (FilterMultiCPUResults && (UseNumberOfCores > 1) - && ((postObject->getTypeId() == Base::Type::fromName("Fem::FemPostPipeline")) - || (postObject->getTypeId() == Base::Type::fromName("Fem::FemPostWarpVectorFilter")))) { - filterArtifacts(dset); - } - else { - m_surface->SetInputData(dset); - } + m_surface->SetInputData(dset); return true; } diff --git a/src/Mod/Fem/Gui/ViewProviderFemPostObject.h b/src/Mod/Fem/Gui/ViewProviderFemPostObject.h index 948ad96f13..13f1522ecd 100644 --- a/src/Mod/Fem/Gui/ViewProviderFemPostObject.h +++ b/src/Mod/Fem/Gui/ViewProviderFemPostObject.h @@ -155,7 +155,6 @@ protected: vtkSmartPointer m_points, m_pointsSurface; private: - void filterArtifacts(vtkDataSet* data); void updateProperties(); void update3D(); void WritePointData(vtkPoints* points, vtkDataArray* normals, vtkDataArray* tcoords); diff --git a/src/Mod/Fem/Gui/ViewProviderFemPostPipeline.cpp b/src/Mod/Fem/Gui/ViewProviderFemPostPipeline.cpp index 2a14b7a47c..4a19bf0ea2 100644 --- a/src/Mod/Fem/Gui/ViewProviderFemPostPipeline.cpp +++ b/src/Mod/Fem/Gui/ViewProviderFemPostPipeline.cpp @@ -63,6 +63,9 @@ void ViewProviderFemPostPipeline::updateData(const App::Property* prop) updateFunctionSize(); updateColorBars(); } + else if (prop == &pipeline->MergeDuplicate) { + updateVtk(); + } } void ViewProviderFemPostPipeline::updateFunctionSize()