Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
DLR-AMR
GitHub Repository: DLR-AMR/t8code
Path: blob/main/src/t8_vtk/t8_with_vtk/t8_vtk_reader.cxx
914 views
/*
  This file is part of t8code.
  t8code is a C library to manage a collection (a forest) of multiple
  connected adaptive space-trees of general element classes in parallel.

  Copyright (C) 2025 the developers

  t8code is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  t8code 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 General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with t8code; if not, write to the Free Software Foundation, Inc.,
  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

#include <t8_vtk/t8_with_vtk/t8_vtk_reader.hxx>
#include <t8_vtk/t8_with_vtk/t8_vtk_reader.hxx>
#include <t8_vtk/t8_vtk_types.h>
#include <t8_cmesh/t8_cmesh.hxx>
#include <t8_geometry/t8_geometry_implementations/t8_geometry_linear.hxx>
#include <t8_cmesh/t8_cmesh_vertex_connectivity/t8_cmesh_vertex_connectivity.hxx>

#include <t8_vtk/t8_with_vtk/t8_vtk_polydata.hxx>
#include <t8_vtk/t8_with_vtk/t8_vtk_parallel.hxx>
#include <t8_vtk/t8_with_vtk/t8_vtk_unstructured.hxx>
#include <vtkCellIterator.h>
#include <vtkCellData.h>
#include <vtkCellDataToPointData.h>
#include <vtkCellTypes.h>
#include <vtkUnstructuredGrid.h>
#include <vtkUnstructuredGridReader.h>
#include <vtkXMLUnstructuredGridReader.h>
#include <vtkXMLPUnstructuredGridReader.h>
#include <vtkXMLPPolyDataReader.h>
#include <vtkPolyData.h>
#include <vtkBYUReader.h>
#include <vtkOBJReader.h>
#include <vtkPLYReader.h>
#include <vtkPolyDataReader.h>
#include <vtkSTLReader.h>
#include <vtkXMLPolyDataReader.h>

vtk_read_success_t
t8_file_to_vtkGrid (const char *filename, vtkSmartPointer<vtkDataSet> vtkGrid, const int partition, const int main_proc,
                    sc_MPI_Comm comm, const vtk_file_type_t vtk_file_type)
{
  vtk_read_success_t main_proc_read_successful = read_failure;
  int mpirank;
  int mpisize;
  int mpiret;
  mpiret = sc_MPI_Comm_rank (comm, &mpirank);
  SC_CHECK_MPI (mpiret);
  mpiret = sc_MPI_Comm_size (comm, &mpisize);
  SC_CHECK_MPI (mpiret);
  T8_ASSERT (filename != NULL);
  T8_ASSERT (0 <= main_proc && main_proc < mpisize);
  /* Read the file and set the pointer to the vtkGrid
   * We read the file if:
   * - We do not use a partitioned read, every process reads the vtk-file, or if
   * - We use a partitioned read for a non-parallel filetype and the main-process reaches the code-block, or if
   * - We use a parallel file-type and use a partitioned read, every proc reads its chunk of the files.
   */
  if (!partition || mpirank == main_proc || vtk_file_type & VTK_PARALLEL_FILE) {
    switch (vtk_file_type) {
    case VTK_UNSTRUCTURED_FILE:
      main_proc_read_successful = t8_read_unstructured (filename, vtkGrid);
      break;
    case VTK_POLYDATA_FILE:
      main_proc_read_successful = t8_read_polyData (filename, vtkGrid);
      break;
    case VTK_PARALLEL_UNSTRUCTURED_FILE:
      if (!partition) {
        main_proc_read_successful = t8_read_unstructured (filename, vtkGrid);
      }
      else {
        main_proc_read_successful = t8_read_parallel_unstructured (filename, vtkGrid, comm);
        break;
      }
      break;
    case VTK_PARALLEL_POLYDATA_FILE:
      if (!partition) {
        main_proc_read_successful = t8_read_polyData (filename, vtkGrid);
      }
      else {
        main_proc_read_successful = t8_read_parallel_polyData (filename, vtkGrid, comm);
        break;
      }
      break;
    default:
      vtkGrid = NULL;
      t8_errorf ("Filetype not supported.\n");
      break;
    }
    if (partition && vtk_file_type & VTK_SERIAL_FILE) {
      /* Communicate the success/failure of the reading process. */
      sc_MPI_Bcast (&main_proc_read_successful, 1, sc_MPI_INT, main_proc, comm);
      return main_proc_read_successful;
    }
  }
  if (partition) {
    if (vtk_file_type & VTK_SERIAL_FILE) {
      sc_MPI_Bcast (&main_proc_read_successful, 1, sc_MPI_INT, main_proc, comm);
    }
    else {
      int recv_buf;
      sc_MPI_Allreduce (&main_proc_read_successful, &recv_buf, 1, sc_MPI_INT, sc_MPI_LOR, comm);
      main_proc_read_successful = (vtk_read_success_t) recv_buf;
    }
  }
  return main_proc_read_successful;
}

/**
 * Get the dimension of a vtkDataSet
 *
 * \param[in] vtkGrid The vtkDataSet
 * \return The dimension of \a vtkGrid.
 */
int
t8_vtk_grid_get_dimension (vtkSmartPointer<vtkDataSet> vtkGrid)
{
  /* This array contains the type of each cell */
  vtkSmartPointer<vtkCellTypes> cell_type_of_each_cell = vtkSmartPointer<vtkCellTypes>::New ();
  vtkGrid->GetCellTypes (cell_type_of_each_cell);
  vtkSmartPointer<vtkUnsignedCharArray> cell_types = cell_type_of_each_cell->GetCellTypesArray ();
  int max_cell_type = -1;
  const vtkIdType num_types = cell_types->GetNumberOfTuples ();
  /* Iterate over all types and compare the dimension. They are stored
   * in ascending order. */
  for (vtkIdType cell_types_it = 0; cell_types_it < num_types; cell_types_it++) {
    const vtkIdType type = cell_types->GetValue (cell_types_it);
    if (type > max_cell_type) {
      max_cell_type = type;
    }
  }
  T8_ASSERT (0 <= max_cell_type && max_cell_type < 82);
  const int ieclass = t8_cmesh_vtk_type_to_t8_type[max_cell_type];
  T8_ASSERT (ieclass != T8_ECLASS_INVALID);

  return t8_eclass_to_dimension[ieclass];
}

/**
 * Iterate over all cells of a vtkDataset and construct a cmesh representing
 * the vtkGrid. Each cell in the vtkDataSet becomes a tree in the cmesh. This
 * function constructs a cmesh on a single process.
 *
 * \param[in] vtkGrid       The vtkGrid that gets translated
 * \param[in, out] cmesh    An empty cmesh that is filled with the data.
 * \param[in] first_tree    The global id of the first tree. Will be the global id of the first tree on this proc.
 * \param[in] comm        A communicator.
 * \param[in] package_id  The package id of the cmesh.
 * \param[in] starting_key The starting key of the attributes in the cmesh.
 * \return  The number of elements that have been read by the process.
 */

static void
t8_vtk_iterate_cells (vtkSmartPointer<vtkDataSet> vtkGrid, t8_cmesh_t cmesh, const t8_gloidx_t first_tree,
                      [[maybe_unused]] sc_MPI_Comm comm, const int package_id, const int starting_key)
{
  double **tuples = NULL;
  size_t *data_size = NULL;
  t8_gloidx_t tree_id = first_tree;

  vtkCellIterator *cell_it;
  vtkSmartPointer<vtkPoints> points;
  vtkSmartPointer<vtkCellData> cell_data = vtkGrid->GetCellData ();

  std::array<double, T8_ECLASS_MAX_CORNERS * T8_ECLASS_MAX_DIM> vertices;
  std::array<t8_gloidx_t, T8_ECLASS_MAX_CORNERS> global_vertex_indices;
  /* Get cell iterator */
  cell_it = vtkGrid->NewCellIterator ();
  /* get the number of data-arrays per cell */
  const int num_data_arrays = cell_data->GetNumberOfArrays ();
  T8_ASSERT (num_data_arrays >= 0);

  /* Prepare attributes */
  if (num_data_arrays > 0) {
    size_t tuple_size;
    tuples = T8_ALLOC (double *, num_data_arrays);
    data_size = T8_ALLOC (size_t, num_data_arrays);
    for (int idata = 0; idata < num_data_arrays; idata++) {
      vtkDataArray *data = cell_data->GetArray (idata);
      tuple_size = data->GetNumberOfComponents ();
      data_size[idata] = sizeof (double) * tuple_size;
      /* Allocate memory for a tuple in array i */
      tuples[idata] = T8_ALLOC (double, tuple_size);
    }
  }

  /* Iterate over all cells */
  for (cell_it->InitTraversal (); !cell_it->IsDoneWithTraversal (); cell_it->GoToNextCell ()) {
    /* Set the t8_eclass of the cell */
    const t8_eclass_t cell_type = t8_cmesh_vtk_type_to_t8_type[cell_it->GetCellType ()];
    SC_CHECK_ABORTF (t8_eclass_is_valid (cell_type), "vtk-cell-type %i not supported by t8code\n", cell_type);
    t8_cmesh_set_tree_class (cmesh, tree_id, cell_type);
    /* Get the points of the cell */
    const int num_points = cell_it->GetNumberOfPoints ();
    T8_ASSERT (num_points > 0);
    points = cell_it->GetPoints ();

    for (int ipoint = 0; ipoint < num_points; ipoint++) {
      points->GetPoint (t8_element_shape_t8_to_vtk_corner_number (cell_type, ipoint), &vertices[3 * ipoint]);
    }
    t8_cmesh_set_tree_vertices (cmesh, tree_id, vertices.data (), num_points);
    vtkIdList *id_list = cell_it->GetPointIds ();
    const int num_id = id_list->GetNumberOfIds ();
    T8_ASSERT (num_id == num_points);

    for (int i_vertex = 0; i_vertex < num_id; i_vertex++) {
      global_vertex_indices[i_vertex] = id_list->GetId (t8_element_shape_t8_to_vtk_corner_number (cell_type, i_vertex));
    }
    t8_cmesh_set_global_vertices_of_tree (cmesh, tree_id, global_vertex_indices.data (), num_id);

    /* TODO: Avoid magic numbers in the attribute setting. */
    /* Get and set the data of each cell */
    for (int dtype = 0; dtype < num_data_arrays; dtype++) {
      const t8_gloidx_t cell_id = cell_it->GetCellId ();
      vtkDataArray *data = cell_data->GetArray (dtype);
      data->GetTuple (cell_id, tuples[dtype]);
      t8_cmesh_set_attribute (cmesh, tree_id, package_id, dtype + starting_key, tuples[dtype], data_size[dtype], 0);
    }
    tree_id++;
  }
  /* Clean-up */
  cell_it->Delete ();
  if (num_data_arrays > 0) {
    T8_FREE (data_size);
    for (int idata = num_data_arrays - 1; idata >= 0; idata--) {
      T8_FREE (tuples[idata]);
    }
    T8_FREE (tuples);
  }
  return;
}

/**
 * Set the partition for cmesh coming from a distributed vtkGrid (like pvtu)
 *
 * \param[in, out] cmesh On input a cmesh, on output a cmesh with a partition according to the number of trees read on each proc
 * \param[in] mpirank The mpirank of this proc
 * \param[in] mpisize The size of the mpicommunicator
 * \param[in] num_trees The number of trees read on this proc. Must be >= 0
 * \param[in] dim     The dimension of the cells
 * \param[in] comm    The mpi-communicator to use.
 * \return            the global id of the first tree on this proc.
 */
static t8_gloidx_t
t8_vtk_partition (t8_cmesh_t cmesh, const int mpirank, const int mpisize, t8_gloidx_t num_trees,
                  [[maybe_unused]] int dim, sc_MPI_Comm comm)
{
  t8_gloidx_t first_tree = 0;
  t8_gloidx_t last_tree = 1;
  /* Compute the global id of the first tree on each proc. */
  SC_CHECK_ABORT (t8_shmem_init (comm) > 0, "Error in shared memory setup in vtk output.");
  t8_shmem_set_type (comm, T8_SHMEM_BEST_TYPE);
  t8_shmem_array_t offsets = NULL;
  t8_shmem_array_init (&offsets, sizeof (t8_gloidx_t), mpisize + 1, comm);

  t8_shmem_array_prefix ((void *) &num_trees, offsets, 1, T8_MPI_GLOIDX, sc_MPI_SUM, comm);

  first_tree = t8_shmem_array_get_gloidx (offsets, mpirank);
  /* Set the partition of the cmesh. */
  if (num_trees == 0) {
    last_tree = first_tree - 1;
  }
  else {
    last_tree = first_tree + num_trees - 1;
  }
  const int set_face_knowledge = 3; /* Expect face connection of local and ghost trees. */
  t8_cmesh_set_partition_range (cmesh, set_face_knowledge, first_tree, last_tree);
  t8_shmem_array_destroy (&offsets);
  return first_tree;
}

t8_cmesh_t
t8_vtkGrid_to_cmesh (vtkSmartPointer<vtkDataSet> vtkGrid, const int partition, const int main_proc,
                     const int distributed_grid, sc_MPI_Comm comm, const int package_id, const int starting_key)
{
  T8_ASSERT (package_id != t8_get_package_id ());
  T8_ASSERT (package_id != sc_get_package_id ());
  T8_ASSERT (sc_package_is_registered (package_id));
  t8_cmesh_t cmesh;
  int mpisize;
  int mpirank;
  int mpiret;
  t8_cmesh_init (&cmesh);
  /* Get the size of the communicator and the rank of the process. */
  mpiret = sc_MPI_Comm_size (comm, &mpisize);
  SC_CHECK_MPI (mpiret);
  mpiret = sc_MPI_Comm_rank (comm, &mpirank);
  SC_CHECK_MPI (mpiret);

  /* Ensure that the main-proc is a valid proc. */
  T8_ASSERT (0 <= main_proc && main_proc < mpisize);

  /* Already declared here, because we might use them during communication */
  t8_gloidx_t num_trees = 0;
  if (!partition || mpirank == main_proc || distributed_grid) {
    num_trees = vtkGrid->GetNumberOfCells ();
  }

  /* Set the dimension on all procs (even empty procs). */
  int dim = num_trees > 0 ? t8_vtk_grid_get_dimension (vtkGrid) : 0;
  int dim_buf = dim;
  mpiret = sc_MPI_Allreduce ((void *) &dim, &dim_buf, 1, sc_MPI_INT, sc_MPI_MAX, comm);
  SC_CHECK_MPI (mpiret);
  t8_cmesh_set_dimension (cmesh, dim_buf);

  /* Set the geometry. */
  t8_cmesh_register_geometry<t8_geometry_linear> (cmesh);

  /* Global-id of the first local tree */
  t8_gloidx_t first_tree = 0;

  /* Set the partition first, so we know the global id of the first tree on all procs. */
  if (partition) {
    first_tree = t8_vtk_partition (cmesh, mpirank, mpisize, num_trees, dim, comm);
  }
  /* Translation of vtkGrid to cmesh
   * We translate the file if:
   * - We do not use a partitioned read, every process has read the vtk-file and shall now translate it, or if
   * - We use a partitioned read for a non-parallel filetype and the main-process reaches the code-block, or if
   * - We use a parallel file-type and use a partitioned read, every proc translates its chunk of the grid.
   */
  if (!partition || mpirank == main_proc || distributed_grid) {
    t8_vtk_iterate_cells (vtkGrid, cmesh, first_tree, comm, package_id, starting_key);
  }

  if (cmesh != NULL) {
    t8_cmesh_commit (cmesh, comm);
  }
  return cmesh;
}

vtkSmartPointer<vtkPointSet>
t8_vtkGrid_to_vtkPointSet (vtkSmartPointer<vtkDataSet> vtkGrid)
{
  if (vtkGrid == NULL) {
    return NULL;
  }
  /* Set points */
  vtkSmartPointer<vtkPoints> points = vtkSmartPointer<vtkPoints>::New ();
  const vtkIdType num_points = vtkGrid->GetNumberOfPoints ();
  points->SetDataType (VTK_DOUBLE);
  points->SetNumberOfPoints (num_points);

  for (vtkIdType ipoint = 0; ipoint < num_points; ipoint++) {
    double vp[3];
    vtkGrid->GetPoint (ipoint, vp);
    points->SetPoint (ipoint, vp);
  }
  points->Modified ();
  T8_ASSERT (points->GetNumberOfPoints () == num_points);
  vtkSmartPointer<vtkPointSet> cloud = vtkSmartPointer<vtkPointSet>::New ();
  cloud->SetPoints (points);

  /* Map cell data to point data */
  vtkSmartPointer<vtkCellDataToPointData> c2p = vtkCellDataToPointData::New ();
  c2p->PassCellDataOff ();
  c2p->SetInputData (vtkGrid);
  c2p->Update ();
  cloud->DeepCopy (c2p->GetOutput ());
  c2p->Delete ();
  //cloud->DeepCopy (vtkPointSet::SafeDownCast (c2p->GetOutput ()));

  return cloud;
}

vtkSmartPointer<vtkDataSet>
t8_vtk_reader (const char *filename, const int partition, const int main_proc, sc_MPI_Comm comm,
               const vtk_file_type_t vtk_file_type)
{
  int mpisize;
  int mpirank;
  int mpiret;
  /* Get the size of the communicator and the rank of the process. */
  mpiret = sc_MPI_Comm_size (comm, &mpisize);
  SC_CHECK_MPI (mpiret);
  mpiret = sc_MPI_Comm_rank (comm, &mpirank);
  SC_CHECK_MPI (mpiret);
  /* Ensure that the main-proc is a valid proc. */
  T8_ASSERT (0 <= main_proc && main_proc < mpisize);
  T8_ASSERT (filename != NULL);
  vtk_read_success_t main_proc_read_successful = read_failure;

  vtkSmartPointer<vtkDataSet> vtkGrid = NULL;
  switch (vtk_file_type) {
  case VTK_UNSTRUCTURED_FILE:
    vtkGrid = vtkSmartPointer<vtkUnstructuredGrid>::New ();
    break;
  case VTK_POLYDATA_FILE:
    vtkGrid = vtkSmartPointer<vtkPolyData>::New ();
    break;
  case VTK_PARALLEL_UNSTRUCTURED_FILE:
    vtkGrid = vtkSmartPointer<vtkUnstructuredGrid>::New ();
    break;
  case VTK_PARALLEL_POLYDATA_FILE:
    vtkGrid = vtkSmartPointer<vtkPolyData>::New ();
    break;
  default:
    t8_errorf ("Filetype is not supported.\n");
    break;
  }
  T8_ASSERT (partition == 0 || (main_proc >= 0 && main_proc < mpisize));
  /* Read the file and set the pointer. */
  main_proc_read_successful = t8_file_to_vtkGrid (filename, vtkGrid, partition, main_proc, comm, vtk_file_type);

  if (!main_proc_read_successful) {
    t8_global_errorf ("Reading process (Rank %i) did not read the file successfully.\n", main_proc);
    return NULL;
  }
  else {
    return vtkGrid;
  }
}

vtkSmartPointer<vtkPointSet>
t8_vtk_reader_pointSet ([[maybe_unused]] const char *filename, [[maybe_unused]] const int partition,
                        [[maybe_unused]] const int main_proc, [[maybe_unused]] sc_MPI_Comm comm,
                        [[maybe_unused]] const vtk_file_type_t vtk_file_type)
{
  vtkSmartPointer<vtkDataSet> vtkGrid = t8_vtk_reader (filename, partition, main_proc, comm, vtk_file_type);
  return t8_vtkGrid_to_vtkPointSet (vtkGrid);
}
t8_cmesh_t
t8_vtk_reader_cmesh ([[maybe_unused]] const char *filename, [[maybe_unused]] const int partition,
                     [[maybe_unused]] const int main_proc, [[maybe_unused]] sc_MPI_Comm comm,
                     [[maybe_unused]] const vtk_file_type_t vtk_file_type, [[maybe_unused]] const int package_id,
                     [[maybe_unused]] const int starting_key)
{
  vtkSmartPointer<vtkDataSet> vtkGrid = t8_vtk_reader (filename, partition, main_proc, comm, vtk_file_type);
  if (vtkGrid != NULL) {
    const int distributed_grid = (vtk_file_type & VTK_PARALLEL_FILE) && partition;
    t8_cmesh_t cmesh
      = t8_vtkGrid_to_cmesh (vtkGrid, partition, main_proc, distributed_grid, comm, package_id, starting_key);
    T8_ASSERT (cmesh != NULL);
    return cmesh;
  }
  else {
    t8_global_errorf ("Error translating file \n");
    return NULL;
  }
}