Skip to content

Instantly share code, notes, and snippets.

@langthom
Created June 1, 2023 21:55
Show Gist options
  • Save langthom/7b3be42ba31d3c8356d092f2ddc88e2f to your computer and use it in GitHub Desktop.
Save langthom/7b3be42ba31d3c8356d092f2ddc88e2f to your computer and use it in GitHub Desktop.
Utility functions for in-memory conversion between OpenMesh meshes and vtkPolyData.
# Copyright 2023 Dr. Thomas Lang
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software
# and associated documentation files (the “Software”), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
# LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
cmake_minimum_required(VERSION 3.2)
project(MeshConverter)
# The little test project.
add_executable(MeshConverter
MeshConverter.h
Main.cpp
)
# Requires at least VTK 8.1, tested on VTK 8.1.
# More modern versions should also be supported.
find_package(VTK REQUIRED)
target_include_directories(MeshConverter SYSTEM PUBLIC ${VTK_INCLUDE_DIRS})
target_link_libraries(MeshConverter PUBLIC ${VTK_LIBRARIES})
# Requires OpenMesh.
find_package(OpenMesh REQUIRED)
target_compile_definitions(MeshConverter PRIVATE -D_USE_MATH_DEFINES -DOM_STATIC_BUILD) # Necessary (on Windows) for functioning OpenMesh and I/O
target_include_directories(MeshConverter SYSTEM PUBLIC ${OPENMESH_INCLUDE_DIRS})
target_link_libraries(MeshConverter PUBLIC OpenMeshCore OpenMeshTools)
/*
* Copyright 2023 Dr. Thomas Lang
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
* and associated documentation files (the “Software”), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "MeshConverter.h"
#include <iostream>
#include <OpenMesh/Core/Mesh/TriMesh_ArrayKernelT.hh>
#include <OpenMesh/Core/IO/MeshIO.hh>
#include <vtkPLYWriter.h>
#include <vtkPLYReader.h>
int main(int argc, char** argv) {
if (argc < 2) {
std::cerr << "Usage: " << argv[0] << " <path/to/input/mesh>\n";
return EXIT_FAILURE;
}
std::string pathToInputMesh = argv[1];
// Our mesh is a simple triangular mesh using the popular ArrayKernel.
// Please note that we only support triangular meshes, although the
// implementation could be generalized.
using OpenMeshMesh = OpenMesh::TriMesh_ArrayKernelT<>;
// Test case (1)
// 1. Read triangular mesh from the specified input file.
// 2. Convert the OpenMesh mesh to the vtkPolyData representation.
// 3. Write the converted mesh to disk using vtkPLYWriter.
OpenMeshMesh inputOMMesh;
if (!OpenMesh::IO::read_mesh(inputOMMesh, pathToInputMesh)) {
std::cerr << "Reading of TriMesh from '" << pathToInputMesh << "' failed.\n";
}
auto outputVTKMesh = vtkSmartPointer< vtkPolyData >::New();
om_vtk_conversion::convert_om_to_vtk(outputVTKMesh, inputOMMesh);
auto vtkOutputFname = pathToInputMesh.substr(0, pathToInputMesh.size() - 4) + "_vtk.ply";
auto vtkMeshWriter = vtkSmartPointer< vtkPLYWriter >::New();
vtkMeshWriter->SetFileName(vtkOutputFname.c_str());
vtkMeshWriter->SetInputData(outputVTKMesh);
vtkMeshWriter->SetFileTypeToBinary();
vtkMeshWriter->Write();
// Test case (2)
// - Inverse to case (1), read PLY via VTK, convert it to OpenMesh and write via OpenMesh.
auto vtkMeshReader = vtkSmartPointer< vtkPLYReader >::New();
vtkMeshReader->SetFileName(pathToInputMesh.c_str());
vtkMeshReader->Update();
auto inputVTKMesh = vtkMeshReader->GetOutput();
OpenMeshMesh outputOMMesh;
om_vtk_conversion::convert_vtk_to_om(outputOMMesh, inputVTKMesh);
auto omOutputFname = pathToInputMesh.substr(0, pathToInputMesh.size() - 4) + "_om.ply";
if (!OpenMesh::IO::write_mesh(outputOMMesh, omOutputFname, OpenMesh::IO::Options::Binary)) {
std::cerr << "Writing of TriMesh to '" << omOutputFname << "' failed.\n";
}
return EXIT_SUCCESS;
}
/*
* Copyright 2023 Dr. Thomas Lang
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software
* and associated documentation files (the “Software”), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef MESH_CONVERTER_H
#define MESH_CONVERTER_H
#include <vtkPolyData.h>
#include <OpenMesh/Core/Mesh/TriMesh_ArrayKernelT.hh>
namespace om_vtk_conversion {
/// <summary>
/// Convert an instance of <c>vtkPolyData</c> known to house a triangular mesh into an instance
/// of <c>OpenMesh</c>, specifically to a <c>OpenMesh::TriMesh_ArrayKernelT</c> instantiated with
/// the specified <typeparamref name="OMMeshTraits">OMMeshTraits</typeparamref>. Currently, we only support such triangular meshes.
/// Upon invocation, the passed <paramref name="mesh">mesh</paramref> will be cleared through the passed reference.
/// </summary>
/// <typeparam name="OMMeshTraits">
/// Traits with which the <c>OpenMesh::TriMesh_ArrayKernelT</c> template will be instantiated.
/// Defaults to <c>OpenMesh::DefaultTraits</c>.
/// </typeparam>
/// <param name="omMesh">
/// The mesh that will be first cleared entirely and then populated with vertices and faces.
/// </param>
/// <param name="vtkMesh">
/// The input mesh whose vertices and connectivity will be read.
/// Must contain the necessary three-dimensional points and must have a valid <i>polys</i> array.
/// </param>
template< class OMMeshTraits = OpenMesh::DefaultTraits >
void convert_vtk_to_om(OpenMesh::TriMesh_ArrayKernelT< OMMeshTraits >& omMesh, vtkSmartPointer< vtkPolyData > vtkMesh) {
using OMMesh = OpenMesh::TriMesh_ArrayKernelT< OMMeshTraits >;
static_assert(OMMesh::IsTriMesh, "This function only accepts TriMesh instances.");
static constexpr int verticesPerFace = 3;
// Clear the mesh in any case.
omMesh.clear();
// Step 1: Fill in the points. There, we simply read the points from the
// VTK mesh and insert them into the OpenMesh mesh _in the same order_.
auto points = vtkMesh->GetPoints();
auto nr_points = points->GetNumberOfPoints();
for (vtkIdType pointIndex = 0; pointIndex < nr_points; ++pointIndex) {
double coordinates[3];
points->GetPoint(pointIndex, coordinates);
omMesh.add_vertex(typename OMMesh::Point(coordinates));
}
// Step 2: Add the faces.
// As we already added the vertices to the mesh, we can add the faces
// by iterating over them and asking vtkPolyData, which vertices belong
// to each face. These are only indices into the array of vertices
// within vtkPolyData. Now, since we added the vertices in OpenMesh
// in the very same order, we can simply use the vertex ids in that
// order, ask the OpenMesh mesh which handle it actually is, and
// construct and finally add the face.
// Internal note:
// As we only support triangular meshes, we could fully initialize the following
// vector and work with direct elemental access. However, to make this code, in
// principle, generalizable, we choose the "push_back + clear" solution.
std::vector< typename OMMesh::VertexHandle > vertexHandlesPerFace;
vertexHandlesPerFace.reserve(verticesPerFace);
auto polys = vtkMesh->GetPolys();
auto vertexIds = vtkSmartPointer< vtkIdList >::New();
for (polys->InitTraversal(); polys->GetNextCell(vertexIds);) {
// Iterate over each "cell", where a cell is a polygon here.
vtkIdType nr_vertices = vertexIds->GetNumberOfIds();
for (vtkIdType vi = 0; vi < nr_vertices; ++vi) {
vtkIdType vtkVertexID = vertexIds->GetId(vi); // The vertex id within vtkPolyData.
vertexHandlesPerFace.push_back(omMesh.vertex_handle(vtkVertexID)); // Mapping to OpenMesh handles.
}
omMesh.add_face(vertexHandlesPerFace);
vertexHandlesPerFace.clear();
}
}
/// <summary>
/// Convert an instance of a triangular <c>OpenMesh</c> mesh, specifically a <c>OpenMesh::TriMesh_ArrayKernelT</c> instantiated with
/// the specified <typeparamref name="OMMeshTraits">OMMeshTraits</typeparamref>, into an instance of <c>vtkPolyData</c>.
/// Currently, we only support such triangular meshes.
/// </summary>
/// <typeparam name="OMMeshTraits">
/// Traits with which the <c>OpenMesh::TriMesh_ArrayKernelT</c> template will be instantiated.
/// Defaults to <c>OpenMesh::DefaultTraits</c>.
/// </typeparam>
/// <param name="vtkMesh">
/// The output mesh to be filled with the point and polygon information.
/// </param>
/// <param name="omMesh">
/// The input mesh, must be a valid <c>OpenMesh</c> mesh.
/// </param>
template< class OMMeshTraits = OpenMesh::DefaultTraits >
void convert_om_to_vtk(vtkSmartPointer< vtkPolyData > vtkMesh, OpenMesh::TriMesh_ArrayKernelT< OMMeshTraits >& omMesh) {
using OMMesh = OpenMesh::TriMesh_ArrayKernelT< OMMeshTraits >;
static_assert(OMMesh::IsTriMesh, "This function only accepts TriMesh instances.");
static constexpr int verticesPerFace = 3;
auto nr_vertices = omMesh.n_vertices();
auto nr_faces = omMesh.n_faces();
// Step 1: Add the vertices / points to the VTK mesh.
auto vertices = vtkSmartPointer< vtkPoints >::New();
vertices->Allocate(nr_vertices);
for (int vertexID = 0; vertexID < nr_vertices; ++vertexID) {
auto const& point = omMesh.point(typename OMMesh::VertexHandle(vertexID));
double coordinates[3] = {
static_cast< double >(point[0]),
static_cast< double >(point[1]),
static_cast< double >(point[2])
};
vertices->InsertPoint(vertexID, coordinates);
}
// Step 2: Add the faces, i.e., for each cell in the input mesh, create an appropriate VTK cell.
auto faces = vtkSmartPointer< vtkCellArray >::New();
faces->Allocate(faces->EstimateSize(nr_vertices, verticesPerFace));
// - The connectivity array holding the order of vertex IDs that form the triangle/polygon.
auto connectivity = vtkSmartPointer< vtkIdList >::New();
connectivity->SetNumberOfIds(verticesPerFace);
// - Process each face.
for (auto& faceHandleIter : omMesh.faces()) {
// For each vertex associated to the current face, add it to the connectivity in the order linked in the mesh.
vtkIdType vertexID = 0;
faceHandleIter.vertices().for_each([&](auto& vertexHandle) { connectivity->SetId(vertexID++, vertexHandle.idx()); });
faces->InsertNextCell(connectivity);
}
vtkMesh->SetPoints(vertices);
vtkMesh->SetPolys(faces);
}
} // namespace om_vtk_conversion
#endif // MESH_CONVERTER_H
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment