Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions examples/xml/LoadDataArrays.scn
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Verification scene for unit tests (tests/fixtures/test_mesh.vtu).

Geometry: 22 points, 10 cells in three spatial zones
Zone A z=0 cells 0-1 (quads) + 2-3 (triangles)
Zone B z=1..2 cells 4-5 (hexahedra)
Zone C z=2..3 cells 6-9 (tetrahedra)

Cell data loaded:
pressure float32 centroid z [0.0, 0.0, 0.0, 0.0, 1.5, 1.5, 2.25, ...]
material_id int32 zone id [1, 1, 1, 1, 2, 2, 3, 3, 3, 3]
fiber_direction float64 unit centroid vector (3 components)
int8_data int8 cell index [1 .. 10]
int32_data int32 index * 100 [100 .. 1000]
int64_data int64 index * 10000 [10000 .. 100000]

Point data loaded:
temperature float64 x + y + z P0=0.0, P1=1.0, P5=3.0, P19=5.0, P21=4.0
velocity float64 (x, y, z) P0=(0,0,0), P5=(2,1,0), P21=(0.5,0.5,3)
-->
<Node name="root" dt="0.005" gravity="0 0 -9.81">

<Node name="plugins">
<RequiredPlugin name="SOFA.VTK"/>
<RequiredPlugin name="Sofa.GL.Component.Rendering3D"/>
</Node>

<DefaultAnimationLoop/>

<UnstructuredGridVTKLoader name="loader"
filename="../../tests/fixtures/test_mesh.vtu"
printLog="true"
createSubelements="true"
cellDataNames="pressure material_id fiber_direction int8_data int32_data int64_data"
pointDataNames="temperature velocity"/>

<OglModel name="visual" src="@loader" color="0.6 0.8 1.0 0.85"/>

</Node>
181 changes: 179 additions & 2 deletions src/sofa/vtk/BaseVTKLoader.cpp
Original file line number Diff line number Diff line change
@@ -1,25 +1,182 @@
#include <sofa/vtk/BaseVTKLoader.h>
#include <sofa/config.h>
#include <sofa/vtk/VTKtoSOFA.h>
#include <vtkArrayDispatch.h>
#include <vtkCellData.h>
#include <vtkDataArray.h>
#include <vtkPointData.h>
#include <vtkDataArrayRange.h>
#include <vtkPolyData.h>
#include <vtkSmartPointer.h>

namespace
{

template<class Reader>
vtkSmartPointer<vtkDataSet> getDataSet(std::string fileName)
vtkSmartPointer<vtkDataSet> getDataSet(const std::string& fileName)
{
vtkNew<Reader> reader;
reader->SetFileName(fileName.c_str());
reader->Update();
return reader->GetOutput();
}

// Map VTK array value types to SOFA-registered Data types.
// float/double → SReal; small signed integers → int; unsigned integers → sofa::Index;
// long long / unsigned long long are kept as-is (both registered).
// long/unsigned long are platform-dependent and folded into the appropriate fixed-width type.
template<typename T> struct SofaType { using type = T; };
template<> struct SofaType<float> { using type = SReal; };
template<> struct SofaType<double> { using type = SReal; };
template<> struct SofaType<signed char> { using type = int; };
template<> struct SofaType<short> { using type = int; };
template<> struct SofaType<unsigned char> { using type = sofa::Index; };
template<> struct SofaType<unsigned short> { using type = sofa::Index; };
template<> struct SofaType<unsigned int> { using type = sofa::Index; };
template<> struct SofaType<long> { using type = std::conditional_t<sizeof(long) == 4, int, long long>; };
template<> struct SofaType<unsigned long> { using type = std::conditional_t<sizeof(unsigned long) == 4, sofa::Index, unsigned long long>; };

template<typename T>
using SofaType_t = typename SofaType<T>::type;

struct ScalarDataWorker
{
sofavtk::BaseVTKLoader& loader;
const std::string& arrayName;
std::unique_ptr<sofa::core::objectmodel::BaseData> result;

template<typename ArrayT>
void operator()(ArrayT* array)
{
using T = SofaType_t<vtk::GetAPIType<ArrayT>>;

auto dataPtr = std::make_unique<sofa::core::objectmodel::Data<sofa::type::vector<T>>>();
dataPtr->setName(arrayName);
dataPtr->setHelp("Data array loaded from VTK file");

{
auto accessor = sofa::helper::getWriteOnlyAccessor(*dataPtr);
auto& vec = accessor.wref();
vec.resize(array->GetNumberOfTuples());
auto values = vtk::DataArrayValueRange<1>(array);
const vtkIdType n = static_cast<vtkIdType>(array->GetNumberOfTuples());
for (vtkIdType i = 0; i < n; ++i)
vec[i] = values[i];
}

loader.addData(dataPtr.get(), arrayName);
result = std::move(dataPtr);
}
};

struct MultiComponentDataWorker
{
sofavtk::BaseVTKLoader& loader;
const std::string& arrayName;
int numComponents;
std::unique_ptr<sofa::core::objectmodel::BaseData> result;

template<typename ArrayT>
void operator()(ArrayT* array)
{
using T = SofaType_t<vtk::GetAPIType<ArrayT>>;
dispatchN<T, 2>(array);
}

private:
// Recursively dispatch fill function specialization based on the number of components until a
// match is found or the max supported is reached.
template<typename T, int N, typename ArrayT>
void dispatchN(ArrayT* array)
{
if (numComponents == N)
fill<T, N>(array);
else if constexpr (N < 9)
dispatchN<T, N + 1>(array);
}

template<typename T, int N, typename ArrayT>
void fill(ArrayT* array)
{
auto dataPtr = std::make_unique<sofa::core::objectmodel::Data<sofa::type::vector<sofa::type::Vec<N, T>>>>();
dataPtr->setName(arrayName);
dataPtr->setHelp("Data array loaded from VTK file");

{
auto accessor = sofa::helper::getWriteOnlyAccessor(*dataPtr);
auto& vec = accessor.wref();
vec.resize(array->GetNumberOfTuples());
auto tuples = vtk::DataArrayTupleRange<N>(array);
const vtkIdType n = static_cast<vtkIdType>(array->GetNumberOfTuples());
for (vtkIdType i = 0; i < n; ++i)
{
for (int c = 0; c < N; ++c)
vec[i][c] = tuples[i][c];
}
}

loader.addData(dataPtr.get(), arrayName);
result = std::move(dataPtr);
}
};

}

namespace sofavtk
{

void BaseVTKLoader::loadDataArrayByName(vtkFieldData* fieldData, const std::string& arrayName,
std::map<std::string, std::unique_ptr<sofa::core::objectmodel::BaseData>>& storage)
{
vtkDataArray* array = fieldData->GetArray(arrayName.c_str());
if (!array)
{
msg_warning() << fieldData->GetClassName() << " array '" << arrayName << "' not found in VTK file";
return;
}

const int numComponents = array->GetNumberOfComponents();

// Explicitly supported value types. Each maps to a fixed-size C++ type through vtkArrayDispatch
// long and unsigned long are included and remapped via CanonicalLong_t to int/long long based
// on their size on the current platform.
using SupportedTypes = vtkTypeList::Create<
float, double,
signed char, unsigned char,
int, unsigned int,
long, unsigned long,
long long, unsigned long long>;
using Dispatcher = vtkArrayDispatch::DispatchByValueType<SupportedTypes>;

auto dispatch = [&](auto& worker) {
if (!Dispatcher::Execute(array, worker))
worker(array);
if (worker.result)
{
msg_info() << "Loaded " << fieldData->GetClassName() << " '" << arrayName
<< "' with " << array->GetNumberOfTuples() << " entries";
storage[arrayName] = std::move(worker.result);
}
};

if (numComponents == 1)
{
ScalarDataWorker worker{*this, arrayName};
dispatch(worker);
return;
}

if (numComponents > 9)
{
msg_warning() << fieldData->GetClassName() << " array '" << arrayName
<< "' has " << numComponents << " components (max supported: 9)";
return;
}

MultiComponentDataWorker worker{*this, arrayName, numComponents};
dispatch(worker);
}

bool BaseVTKLoader::doLoad()
{
const auto& fileName = d_filename.getFullPath();
Expand All @@ -36,13 +193,33 @@ bool BaseVTKLoader::doLoad()

sofavtk::extractCells(*this, dataSet);

auto* cellData = dataSet->GetCellData();
auto* pointData = dataSet->GetPointData();

for (const auto& arrayName : d_cellDataNames.getValue())
loadDataArrayByName(cellData, arrayName, m_cellData);

for (const auto& arrayName : d_pointDataNames.getValue())
loadDataArrayByName(pointData, arrayName, m_pointData);

return true;
}

return false;
}

void BaseVTKLoader::doClearBuffers() {}
void BaseVTKLoader::doClearBuffers()
{
auto clearMap = [&](auto& map) {
for (const auto& [name, data] : map)
this->removeData(data.get());
map.clear();
};

clearMap(m_cellData);
clearMap(m_pointData);
}


} // namespace sofavtk

Expand Down
15 changes: 15 additions & 0 deletions src/sofa/vtk/BaseVTKLoader.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include <sofa/vtk/config.h>
#include <sofa/core/loader/MeshLoader.h>
#include <vtkDataSet.h>
#include <vtkFieldData.h>
#include <vtkSmartPointer.h>

namespace sofavtk
Expand All @@ -11,11 +12,25 @@ struct SOFA_VTK_API BaseVTKLoader : sofa::core::loader::MeshLoader
{
SOFA_ABSTRACT_CLASS(BaseVTKLoader, sofa::core::loader::MeshLoader);

sofa::core::objectmodel::Data<sofa::type::vector<std::string>> d_cellDataNames{
initData(&d_cellDataNames, "cellDataNames",
"Names of cell data arrays to load from the VTK file")};

sofa::core::objectmodel::Data<sofa::type::vector<std::string>> d_pointDataNames{
initData(&d_pointDataNames, "pointDataNames",
"Names of point data arrays to load from the VTK file")};

private:

bool doLoad() final;
void doClearBuffers() final;

void loadDataArrayByName(vtkFieldData* fieldData, const std::string& arrayName,
std::map<std::string, std::unique_ptr<sofa::core::objectmodel::BaseData>>& storage);

std::map<std::string, std::unique_ptr<sofa::core::objectmodel::BaseData>> m_cellData;
std::map<std::string, std::unique_ptr<sofa::core::objectmodel::BaseData>> m_pointData;

protected:

virtual vtkSmartPointer<vtkDataSet> getDataSet(const sofa::core::objectmodel::DataFileName& fileName) = 0;
Expand Down
3 changes: 0 additions & 3 deletions src/sofa/vtk/UnstructuredGridVTKLoader.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
#include <sofa/vtk/UnstructuredGridVTKLoader.h>
#include <sofa/core/ObjectFactory.h>

#include <sofa/vtk/VTKtoSOFA.h>
#include <sofa/vtk/CellTypeName.h>

#include <vtkUnstructuredGrid.h>
#include <vtkUnstructuredGridReader.h>
#include <vtkXMLUnstructuredGridReader.h>
Expand Down
9 changes: 8 additions & 1 deletion tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
cmake_minimum_required(VERSION 3.12)
project(SOFAVTK_test VERSION 1.0)
find_package(Sofa.Testing REQUIRED)
find_package(VTK COMPONENTS CommonCore CommonDataModel REQUIRED)

set(SOURCE_FILES
test.cpp
TestCellPointData.cpp
)

add_executable(${PROJECT_NAME} ${SOURCE_FILES})

target_link_libraries(${PROJECT_NAME} Sofa.Testing SOFA.VTK)
target_link_libraries(${PROJECT_NAME} Sofa.Testing SOFA.VTK ${VTK_LIBRARIES})

vtk_module_autoinit(
TARGETS ${PROJECT_NAME}
MODULES ${VTK_LIBRARIES}
)

add_test(NAME ${PROJECT_NAME} COMMAND ${PROJECT_NAME})
Loading
Loading