From 20c730188e3309f673505e2e664a00ed323ea801 Mon Sep 17 00:00:00 2001
From: Spiros Tsalikis <spiros.tsalikis@kitware.com>
Date: Fri, 14 Feb 2025 11:58:03 -0500
Subject: [PATCH] vtkLANLX3DReader: Import from ParaView

---
 IO/LANLX3D/CMakeLists.txt       |  17 +
 IO/LANLX3D/LICENSE              |  39 +++
 IO/LANLX3D/X3D.hxx              | 128 +++++++
 IO/LANLX3D/X3D_reader.cxx       | 408 ++++++++++++++++++++++
 IO/LANLX3D/X3D_reader.hxx       | 140 ++++++++
 IO/LANLX3D/X3D_tokens.cxx       |  83 +++++
 IO/LANLX3D/X3D_tokens.hxx       | 510 ++++++++++++++++++++++++++++
 IO/LANLX3D/vtk.module           |  22 ++
 IO/LANLX3D/vtkLANLX3DReader.cxx | 577 ++++++++++++++++++++++++++++++++
 IO/LANLX3D/vtkLANLX3DReader.h   |  58 ++++
 10 files changed, 1982 insertions(+)
 create mode 100644 IO/LANLX3D/CMakeLists.txt
 create mode 100644 IO/LANLX3D/LICENSE
 create mode 100644 IO/LANLX3D/X3D.hxx
 create mode 100644 IO/LANLX3D/X3D_reader.cxx
 create mode 100644 IO/LANLX3D/X3D_reader.hxx
 create mode 100644 IO/LANLX3D/X3D_tokens.cxx
 create mode 100644 IO/LANLX3D/X3D_tokens.hxx
 create mode 100644 IO/LANLX3D/vtk.module
 create mode 100644 IO/LANLX3D/vtkLANLX3DReader.cxx
 create mode 100644 IO/LANLX3D/vtkLANLX3DReader.h

diff --git a/IO/LANLX3D/CMakeLists.txt b/IO/LANLX3D/CMakeLists.txt
new file mode 100644
index 00000000000..95f36daa9fb
--- /dev/null
+++ b/IO/LANLX3D/CMakeLists.txt
@@ -0,0 +1,17 @@
+set(classes
+  vtkLANLX3DReader)
+
+set(sources
+  X3D_reader.cxx
+  X3D_tokens.cxx)
+
+set(private_headers
+  X3D.hxx
+  X3D_reader.hxx
+  X3D_tokens.hxx)
+
+vtk_module_add_module(
+  VTK::IOLANLX3D
+  CLASSES ${classes}
+  SOURCES ${sources}
+  PRIVATE_HEADERS ${private_headers})
diff --git a/IO/LANLX3D/LICENSE b/IO/LANLX3D/LICENSE
new file mode 100644
index 00000000000..6f205d9672d
--- /dev/null
+++ b/IO/LANLX3D/LICENSE
@@ -0,0 +1,39 @@
+BSD 3-Clause License
+
+Copyright (c) 2021, Los Alamos National Laboratory. All rights reserved.
+Copyright © 2021. Triad National Security, LLC. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+   contributors may be used to endorse or promote products derived from
+   this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+This program was produced under U.S. Government contract 89233218CNA000001 for
+Los Alamos National Laboratory (LANL), which is operated by Triad National
+Security, LLC for the U.S. Department of Energy/National Nuclear Security
+Administration.  All rights in the program are reserved by Triad National
+Security, LLC, and the U.S. Department of Energy/National Nuclear Security
+Administration.  The Government is granted for itself and others acting on its
+behalf a nonexclusive, paid-up, irrevocable worldwide license in this material
+to reproduce, prepare derivative works, distribute copies to the public,
+perform publicly and display publicly, and to permit others to do so.
diff --git a/IO/LANLX3D/X3D.hxx b/IO/LANLX3D/X3D.hxx
new file mode 100644
index 00000000000..b6be91b01fa
--- /dev/null
+++ b/IO/LANLX3D/X3D.hxx
@@ -0,0 +1,128 @@
+// SPDX-FileCopyrightText: Copyright (c) Kitware Inc.
+// SPDX-FileCopyrightText: Copyright (c) 2021, Los Alamos National Laboratory
+// SPDX-FileCopyrightText: Copyright (c) 2021. Triad National Security, LLC
+// SPDX-License-Identifier: LicenseRef-BSD-3-Clause-LANL-Triad-USGov
+/**
+   \file X3D.hxx
+
+   Constants, typedefs, and utility functions for Reader and
+   Writer classes.
+
+   \author Mark G. Gray <gray@lanl.gov>
+*/
+
+#ifndef X3D_HXX
+#define X3D_HXX
+
+#include "vtkABINamespace.h"
+
+#include <array>
+#include <map>
+#include <ostream>
+#include <string>
+#include <vector>
+
+VTK_ABI_NAMESPACE_BEGIN
+template <typename T, std::size_t N>
+std::ostream& operator<<(std::ostream& os, const std::array<T, N>& vec)
+{
+  os << "{";
+  for (auto elem : vec)
+  {
+    os << elem << " ";
+  }
+  os << "}";
+  return os;
+}
+
+template <typename T>
+std::ostream& operator<<(std::ostream& os, const std::vector<T>& vec)
+{
+  for (auto elem : vec)
+  {
+    os << elem << " ";
+  }
+  return os;
+}
+
+namespace X3D
+{
+// Supported X3D versions
+enum class Version
+{
+  v1_0,
+  v1_3
+};
+
+// Magic string which must be at beginning of an X3D file
+const std::string MAGIC_STRING = "x3dtoflag ascii";
+
+// Top level section headings in X3D file in order
+const std::vector<std::string> TOP_BLOCK = { "header", "matnames", "mateos", "matopc", "nodes",
+  "faces", "cells", "slaved_nodes", "ghost_nodes", "cell_data", "node_data" };
+
+// Keys in an X3D file header block in order
+const std::vector<std::string> HEADER_KEYS = { "process", "numdim", "materials", "nodes", "faces",
+  "elements", "ghost_nodes", "slaved_nodes", "nodes_per_slave", "nodes_per_face", "faces_per_cell",
+  "node_data_fields", "cell_data_fields" };
+
+// X3D block representation in STL data structures
+// Use PascalCase for types
+typedef std::map<std::string, int> Header;  // Header Data Block
+typedef std::vector<std::string> Materials; // Material Data Blocks
+typedef std::array<double, 3> Node;
+typedef std::vector<Node> Nodes; // Coordinate Data Blck
+struct Face
+{
+  std::vector<int> node_id;
+  int face_id; // X3D local face ID
+  int neighbor_process_id;
+  int neighbor_face_id;
+};
+typedef std::vector<Face> Faces;             // Faces Data Block
+typedef std::vector<std::vector<int>> Cells; // Cell Data Block
+struct ConstrainedNode
+{
+  int vertex_id;
+  std::vector<int> master;
+};
+typedef std::vector<ConstrainedNode> ConstrainedNodes; // Constrained Node Block
+typedef std::array<int, 4> SharedNode;
+typedef std::vector<SharedNode> SharedNodes; // Shared Nodes (on Parallel Boundary) Block
+struct CellData                              // Cell Data Block
+{
+  std::vector<std::string> names;
+  std::vector<int> matid;
+  std::vector<int> partelm;
+  std::map<std::string, std::vector<double>> fields;
+};
+struct NodeData // Point-centered Physical Data Block
+{
+  std::vector<std::string> names;
+  std::map<std::string, std::vector<Node>> fields;
+};
+
+/**
+   \function error_message
+
+   Format error message for ReadError, WriteError
+*/
+inline std::string error_message(
+  const std::string& expect, const std::string& found, const std::string& where)
+{
+  return "Expect: \"" + expect + "\"; found: \"" + found + "\" in " + where;
+}
+
+/**
+   \function error_message
+
+   Format error message for ReadError, WriteError
+*/
+inline std::string error_message(int expect, int found, const std::string& where)
+{
+  return error_message(std::to_string(expect), std::to_string(found), where);
+}
+
+}
+VTK_ABI_NAMESPACE_END
+#endif
diff --git a/IO/LANLX3D/X3D_reader.cxx b/IO/LANLX3D/X3D_reader.cxx
new file mode 100644
index 00000000000..1b025250797
--- /dev/null
+++ b/IO/LANLX3D/X3D_reader.cxx
@@ -0,0 +1,408 @@
+// SPDX-FileCopyrightText: Copyright (c) Kitware Inc.
+// SPDX-FileCopyrightText: Copyright (c) 2021, Los Alamos National Laboratory
+// SPDX-FileCopyrightText: Copyright (c) 2021. Triad National Security, LLC
+// SPDX-License-Identifier: LicenseRef-BSD-3-Clause-LANL-Triad-USGov
+#include "X3D_reader.hxx"
+#include "X3D_tokens.hxx"
+
+#include <iostream>
+
+using namespace std;
+
+namespace X3D
+{
+VTK_ABI_NAMESPACE_BEGIN
+// Does the start of s1 match s2?  Coming in C++20!
+inline bool starts_with(const string& s1, const string& s2)
+{
+  return 0 == s1.compare(0, s2.size(), s2);
+}
+
+// Get next line from file and match against s or throw ReadError.
+string Reader::expect_starts_with(const string& s)
+{
+  string line;
+
+  getline(file, line);
+  if (starts_with(line, s))
+  {
+    return line;
+  }
+  else // unexpected block begin/end
+  {
+    throw ReadError(s, line, filename + ": " + to_string(file.tellg()));
+  }
+}
+
+// Return offset of block header in file using just-in-time search.
+// A valid block header in valid X3D file must have been either
+// previously cached, or in remainder of file not yet searched
+// for headers.  In the latter case cache more headers until it is
+// found.
+streampos Reader::offset_of(const string& block)
+{
+  if (offset.find(block) == offset.end())
+  { // block not cached
+    size_t num_cached_blocks = offset.size();
+    if (num_cached_blocks)
+    { // some blocks cached; start after them...
+      string last_block = TOP_BLOCK[num_cached_blocks - 1];
+      if (file.tellg() < offset.at(last_block)) // ...unless beyond them
+        file.seekg(offset.at(last_block));      // move to last cached block
+    }
+    for (size_t i = num_cached_blocks; i < TOP_BLOCK.size(); i++)
+    {
+      string next_block = TOP_BLOCK[i]; // look for next uncached block
+      streampos position;
+      while ((position = file.tellg()) > 0)
+      {
+        string line;
+        getline(file, line);
+        if (starts_with(line, next_block))
+        {                                // found next block header
+          offset[next_block] = position; // add to cache
+          if (next_block == block)
+          {
+            return position; // found what we're looking for
+          }
+          else
+          {
+            break; // look for next block
+          }
+        }
+      }
+    }
+
+    // read all headers or EOF w/o finding block
+    throw ReadError(block, "EOF", filename);
+  }
+  return offset.at(block); // block cached
+}
+
+// Construct X3D Reader from data on filename
+Reader::Reader(const string& filename_, const Version version_)
+  : filename(filename_)
+  , version(version_)
+  , faces_read(false)
+{
+  // Open file and read magic string
+  file.exceptions(ifstream::badbit); // Exception on filesystem errors
+  file.open(filename);
+  if (!file.is_open())
+    throw ReadError("Error opening file: " + filename);
+
+  expect_starts_with(MAGIC_STRING); // match X3D header line
+
+  // Read Header Block
+  string block = TOP_BLOCK[0];
+  file.seekg(offset_of(block));
+  expect_starts_with(block);
+
+  Xformat x3(3);
+  Aformat a23(23);
+  Iformat i10(10);
+  for (auto key : HEADER_KEYS)
+  {
+    file >> x3 >> a23 >> i10 >> eat_endl; // (3X, A23, I10)
+    if (key != a23())                     // unexpected key
+      throw ReadError(key, a23(), block + ": " + to_string(file.tellg()));
+    size[key] = i10();
+  }
+  expect_starts_with("end_" + block);
+}
+
+// Read Materials Data Blocks: "matnames", "mateos", or "matopc"
+Materials Reader::materials(const string& block)
+{
+  Materials m;
+  Xformat x3(3);
+  Iformat i10(10);
+  Aformat a;
+
+  file.seekg(offset_of(block));
+  expect_starts_with(block);
+  int num_materials = size.at("materials");
+  for (int i = 0; i < num_materials; i++)
+  {
+    file >> x3 >> i10 >> x3 >> a >> eat_endl; // (3X, I10, 3X, A)
+    if (i + 1 != i10())                       // unexpected material id
+      throw ReadError(i + 1, i10(), block + ": " + to_string(file.tellg()));
+    m.push_back(a());
+  }
+  expect_starts_with("end_" + block);
+  return m;
+}
+
+// Read Nodes Block, a.k.a. coordinate data
+Nodes Reader::nodes()
+{
+  Nodes n;
+  string block("nodes");
+  Iformat i10(10);
+  Xformat x1(1);
+  PEformat pe22_14(22, 14);
+
+  file.seekg(offset_of(block));
+  expect_starts_with(block);
+  int num_nodes = size.at(block);
+  for (int i = 0; i < num_nodes; i++)
+  {                     // (i10, 3(1PE22.14))
+    file >> i10;        // node id
+    if (i + 1 != i10()) // unexpected node id
+      throw ReadError(i + 1, i10(), block + ": " + to_string(file.tellg()));
+    Node vec;
+    for (unsigned int j = 0; j < vec.size(); j++)
+    { // node coordinates
+      file >> x1 >> pe22_14;
+      vec[j] = pe22_14();
+    }
+    file >> eat_endl;
+    n.push_back(vec);
+  }
+  expect_starts_with("end_" + block);
+  return n;
+}
+
+// Read Faces Data Block
+Faces Reader::faces()
+{
+  if (faces_read)
+    return all_faces;
+
+  faces_read = true;
+  string block("faces");
+  Iformat i10(10);
+  Rformat rn(version == Version::v1_3 ? 13 : 0);
+
+  file.seekg(offset_of(block));
+  expect_starts_with(block);
+  int num_faces = size.at(block);
+  int this_process_id = size.at("process");
+  for (int i = 0; i < num_faces; i++)
+  { // ((2+num_nodes)I10)
+    rn.reset();
+    Face fl;
+    file >> i10 >> rn;  // face id
+    if (i + 1 != i10()) // unexpected face id
+      throw ReadError(i + 1, i10(), block + ": " + to_string(file.tellg()));
+    fl.face_id = i + 1;
+    file >> i10 >> rn; // number of face nodes
+    int num_nodes = i10();
+    for (int j = 0; j < num_nodes; j++)
+    { // node ids
+      file >> i10 >> rn;
+      fl.node_id.push_back(i10());
+    }
+    file >> i10 >> rn;
+    if (this_process_id != i10()) // unexpected process id
+      throw ReadError(this_process_id, i10(), block + ": " + to_string(file.tellg()));
+    file >> i10 >> rn;
+    fl.neighbor_process_id = i10();
+    file >> i10 >> rn;
+    fl.neighbor_face_id = i10();
+    for (int j = 0; j < 5; j++) // discard five ones of "no significance"
+      file >> i10 >> rn;
+    if (rn())
+      file >> eat_endl; // eat newline
+    all_faces.push_back(fl);
+  }
+  expect_starts_with("end_" + block);
+  return all_faces;
+}
+
+int Reader::number_of_cells() const
+{
+  return size.at("elements");
+}
+
+// Read Cells Block
+Cells Reader::cells()
+{
+  Cells c;
+  string block("cells");
+  Iformat i10(10);
+
+  file.seekg(offset_of(block));
+  expect_starts_with(block);
+  // N.B. X3D inconsistency: block="cells", num_cells = size["elements"]
+  int num_cells = size.at("elements");
+  for (int i = 0; i < num_cells; i++)
+  { // ((2+num_faces)(I10))
+    file >> i10;
+    if (i + 1 != i10()) // unexpected element id
+      throw ReadError(i + 1, i10(), block + ": " + to_string(file.tellg()));
+    file >> i10;
+    int num_faces = i10();
+    vector<int> cl;
+    for (int j = 0; j < num_faces; j++)
+    {
+      file >> i10;
+      cl.push_back(i10());
+    }
+    file >> eat_endl;
+    c.push_back(cl);
+  }
+  expect_starts_with("end_" + block);
+  return c;
+}
+
+// Read Constrained Nodes Block
+ConstrainedNodes Reader::constrained_nodes()
+{
+  ConstrainedNodes s;
+  string block("slaved_nodes");
+  Aformat a12(12);
+  Iformat i10(10);
+
+  file.seekg(offset_of(block));
+  file >> a12 >> i10 >> eat_endl;
+  if (a12() != block)
+    throw ReadError(block, a12(), filename + ": " + to_string(file.tellg()));
+  int num_lines = size.at(block);
+  if (i10() != num_lines)
+    throw ReadError(num_lines, i10(), block);
+  for (int i = 0; i < num_lines; i++)
+  { // ((3+num_masters)I10)
+    ConstrainedNode sl;
+    file >> i10;
+    if (i + 1 != i10()) // unexpected constrained node id
+      throw ReadError(i + 1, i10(), block + ": " + to_string(file.tellg()));
+    file >> i10;
+    sl.vertex_id = i10();
+    file >> i10;
+    int num_masters = i10();
+    for (int j = 0; j < num_masters; j++)
+    {
+      file >> i10;
+      sl.master.push_back(i10());
+    }
+    file >> eat_endl;
+    s.push_back(sl);
+  }
+  expect_starts_with("end_" + block);
+  return s;
+}
+
+// Read Parallel Shared Nodes Block
+SharedNodes Reader::shared_nodes()
+{
+  SharedNodes s;
+  string block("ghost_nodes");
+  Aformat a12(12);
+  Iformat i10(10);
+
+  auto offs = offset_of(block);
+  file.seekg(offs);
+  file >> a12 >> i10 >> eat_endl;
+  if (a12() != block)
+    throw ReadError(block, a12(), filename + ": " + to_string(file.tellg()));
+  int num_lines = size.at(block);
+  if (i10() != num_lines)
+    throw ReadError(num_lines, i10(), block);
+  for (int i = 0; i < num_lines; i++)
+  { // (4I10)
+    SharedNode sl;
+    for (unsigned int j = 0; j < sl.size(); j++)
+    {
+      file >> i10;
+      sl[j] = i10();
+    }
+    file >> eat_endl;
+    s.push_back(sl);
+  }
+  expect_starts_with("end_" + block);
+  return s;
+}
+
+// Read Cell Data Block
+CellData Reader::cell_data()
+{
+  CellData cd;
+  string block("cell_data");
+  Aformat a;
+  Iformat i10(10); // I10
+  Rformat r10(10); // 10 per line
+  PEformat pe20_12(20, 12);
+  int num_fields = size.at(block + "_fields");
+  int num_elements = size.at("elements");
+
+  file.seekg(offset_of(block));
+  expect_starts_with(block);
+  for (int i = 0; i < num_fields; i++)
+  {
+    file >> a >> eat_endl; // get field name
+    string field_name(a());
+    if (field_name == "matid" || field_name == "partelm")
+    { // mandatory field
+      r10.reset();
+      vector<int> f;
+      for (int j = 0; j < num_elements; j++)
+      {                     // (10I10)
+        file >> i10 >> r10; // read integer from line
+        f.push_back(i10()); // and add to field
+      }
+      if (r10())
+        file >> eat_endl; // eat EOL from partial last line
+      if (field_name == "matid")
+        cd.matid = f; // add matid to struct
+      else
+        cd.partelm = f; // add partelm to struct
+    }
+    else
+    { // get optional zone-centered field
+      vector<double> f;
+      for (int j = 0; j < num_elements; j++)
+      {                              //  (1PE20.12)
+        file >> pe20_12 >> eat_endl; // read double from line
+        f.push_back(pe20_12());      // and add to field
+      }
+      cd.fields[field_name] = f; // add field to map
+    }
+    expect_starts_with("end_" + field_name);
+    cd.names.push_back(field_name); // add to names vector
+  }
+  expect_starts_with("end_" + block);
+  return cd;
+}
+
+// Read Node Data Block, a.k.a. Point-centered Physical Data Block
+NodeData Reader::node_data()
+{
+  NodeData nd;
+  string block("node_data");
+  Aformat a;
+  PEformat pe20_12(20, 12); // 1PE20.12
+  int num_fields = size.at(block + "_fields");
+  int num_nodes = size.at("nodes");
+
+  file.seekg(offset_of(block));
+  expect_starts_with(block);
+
+  for (int i = 0; i < num_fields; i++)
+  {
+    // get field block
+    file >> a >> eat_endl;
+    string field_block(a());
+    nd.names.push_back(field_block);
+    // read vector field in f
+    vector<Node> field;
+    for (int j = 0; j < num_nodes; j++)
+    {
+      Node v;
+      // read vector from line
+      for (unsigned int k = 0; k < v.size(); k++)
+      { // (3(1PE20.12))
+        file >> pe20_12;
+        v[k] = pe20_12();
+      }
+      file >> eat_endl;
+      field.push_back(v); // add vector to field
+    }
+    expect_starts_with("end_" + field_block);
+    nd.fields[field_block] = field; // add field to map
+  }
+  expect_starts_with("end_" + block);
+  return nd;
+}
+VTK_ABI_NAMESPACE_END
+}
diff --git a/IO/LANLX3D/X3D_reader.hxx b/IO/LANLX3D/X3D_reader.hxx
new file mode 100644
index 00000000000..f6ed02b99be
--- /dev/null
+++ b/IO/LANLX3D/X3D_reader.hxx
@@ -0,0 +1,140 @@
+// SPDX-FileCopyrightText: Copyright (c) Kitware Inc.
+// SPDX-FileCopyrightText: Copyright (c) 2021, Los Alamos National Laboratory
+// SPDX-FileCopyrightText: Copyright (c) 2021. Triad National Security, LLC
+// SPDX-License-Identifier: LicenseRef-BSD-3-Clause-LANL-Triad-USGov
+/**
+   \file X3D_reader.hxx
+
+   Read X3D file by block and return data in standard library types.
+
+   An X3D file consists of a fixed sequence of blocks.  An X3D block
+   consists of a sequence of fixed, parameterized lines, or a sequence
+   of blocks which consist of a fixed sequence of lines.  An X3D line
+   consists of a fixed sequence of tokens.  An X3D token is specified
+   by a FORTRAN data or control descriptor.
+
+   See: G. A. Hansen, "Summary of the FLAG X3D Format", V 1.0,
+        LA-UR-04-9033, 2005-1-14
+        Brian Jean, "Summary of the FLAG X3D Format", V 1.3,
+        2008-2-11
+
+   \author Mark G. Gray <gray@lanl.gov>
+*/
+
+#ifndef X3D_READER_HXX
+#define X3D_READER_HXX
+
+#include "X3D.hxx"
+#include "vtkABINamespace.h"
+
+#include <fstream>
+#include <stdexcept>
+
+namespace X3D
+{
+VTK_ABI_NAMESPACE_BEGIN
+
+/**
+   \class ReadError
+
+   Exception thrown by Reader.
+
+   When Reader encounters an inconsistency in its input stream,
+   it throws this exception with a message containing what it
+   expected, what it found, and where (file or block name: file byte
+   offset) it found the discrepancy.
+*/
+class ReadError : public std::runtime_error
+{
+public:
+  explicit ReadError(const std::string& m)
+    : std::runtime_error(m.c_str())
+  {
+  }
+  explicit ReadError(const std::string& expect, const std::string& found, const std::string& where)
+    : std::runtime_error(error_message(expect, found, where).c_str())
+  {
+  }
+  explicit ReadError(int expect, int found, const std::string& where)
+    : std::runtime_error(error_message(expect, found, where).c_str())
+  {
+  }
+};
+
+/**
+   \class Reader
+
+   Provide C++ STL representation of X3D file.
+
+   Member functions named after X3D top level blocks seek that block
+   in file, read its contents, and return STL based container with
+   the block's data.  Block data may be accessed in any order.
+*/
+class Reader
+{
+public:
+  /**
+      Initialize Reader from named file.
+
+      Open the named X3D file, index the location of its top level
+      blocks, and read and store its header block.
+
+      Supports both version 1.0 X3D files with "All the columns for
+      face data must appear on a single line.", and version 1.3 X3D
+      files with "...the maximum number of columns per physical line
+      is 13."
+
+      \param filename name of X3D file
+      \param version number
+  */
+  explicit Reader(const std::string& filename, const Version version = Version::v1_3);
+
+  // Header Data Block
+  Header header() const { return size; }
+
+  // Material Data Blocks
+  Materials matnames() { return materials("matnames"); }
+  Materials mateos() { return materials("mateos"); }
+  Materials matopc() { return materials("matopc"); }
+
+  // Coordinate Data Block
+  Nodes nodes();
+
+  // Faces Data Block
+  Faces faces();
+
+  // Cell Block
+  Cells cells();
+  int number_of_cells() const;
+
+  // Slaved Node Block
+  ConstrainedNodes constrained_nodes();
+
+  // Shared Nodes Block
+  SharedNodes shared_nodes();
+
+  // Cell Data Block
+  CellData cell_data();
+
+  // Point-centered Physical Data Block
+  NodeData node_data();
+
+  static const char* const pythonName;
+
+private:
+  std::string expect_starts_with(const std::string& s);
+  std::streampos offset_of(const std::string& block);
+  Materials materials(const std::string& s);
+  typedef std::map<std::string, std::streampos> Offset;
+
+  std::string filename; // name of X3D file to read
+  Version version;      // X3D format version to process
+  std::ifstream file;   // stream to read from
+  Offset offset;        // file offsets to top blocks
+  Header size;          // Header Block sizes
+  Faces all_faces;
+  bool faces_read;
+};
+VTK_ABI_NAMESPACE_END
+}
+#endif
diff --git a/IO/LANLX3D/X3D_tokens.cxx b/IO/LANLX3D/X3D_tokens.cxx
new file mode 100644
index 00000000000..b109ec5dc3b
--- /dev/null
+++ b/IO/LANLX3D/X3D_tokens.cxx
@@ -0,0 +1,83 @@
+// SPDX-FileCopyrightText: Copyright (c) Kitware Inc.
+// SPDX-FileCopyrightText: Copyright (c) 2021, Los Alamos National Laboratory
+// SPDX-FileCopyrightText: Copyright (c) 2021. Triad National Security, LLC
+// SPDX-License-Identifier: LicenseRef-BSD-3-Clause-LANL-Triad-USGov
+#include "X3D_tokens.hxx"
+
+#include <iomanip>
+#include <ios>
+#include <sstream>
+
+using namespace std;
+
+namespace X3D
+{
+VTK_ABI_NAMESPACE_BEGIN
+
+// Get w characters from is and return trimmed string.
+string fixed_get(istream& is, unsigned int width)
+{
+  const char ws[] = " \t\n\r\f\v"; // whitespace characters
+
+  if (width)
+  { // read next width characters
+    string result;
+    char c;
+    for (unsigned int i = 0; i < width; i++)
+      if (is.get(c) && c != '\n')
+        result.push_back(c);
+      else
+        throw ScanError("Unexpected EOL following \"" + result + "\" at character: ", is.tellg());
+    result.erase(0, result.find_first_not_of(ws)); // trim leading ws
+    result.erase(result.find_last_not_of(ws) + 1); // trim trailing ws
+    return result;
+  }
+  else
+  { // read next ws terminated string
+    string result;
+    is >> result;
+    return result;
+  }
+}
+
+// Return string containing s formatted as Aw
+string Aformat::operator()(string s)
+{
+  ostringstream f;
+
+  value = s;
+  f << left << std::setw(width) << s;
+  if (width && f.str().size() > width) // width overflow;
+    return f.str().substr(0, width);   // truncate string
+  else
+    return f.str();
+}
+
+// Return string containing i formatted as Iw.
+string Iformat::operator()(int i)
+{
+  ostringstream f;
+
+  value = i;
+  f << std::setw(width) << i;
+  if (width && f.str().size() > width) // width overflow;
+    return string(width, '*');         // return width '*'s
+  else
+    return f.str();
+}
+
+// Return string containing d formatted as 1PEw.d.
+string PEformat::operator()(double x)
+{
+  ostringstream f;
+
+  value = x;
+  f << scientific << showpoint << uppercase << std::setprecision(precision) << std::setw(width)
+    << x;
+  if (width && f.str().size() > width) // width overflow;
+    return string(width, '*');         // return width '*'s
+  else
+    return f.str();
+}
+VTK_ABI_NAMESPACE_END
+}
diff --git a/IO/LANLX3D/X3D_tokens.hxx b/IO/LANLX3D/X3D_tokens.hxx
new file mode 100644
index 00000000000..3b78d882414
--- /dev/null
+++ b/IO/LANLX3D/X3D_tokens.hxx
@@ -0,0 +1,510 @@
+// SPDX-FileCopyrightText: Copyright (c) Kitware Inc.
+// SPDX-FileCopyrightText: Copyright (c) 2021, Los Alamos National Laboratory
+// SPDX-FileCopyrightText: Copyright © 2021. Triad National Security, LLC
+// SPDX-License-Identifier: LicenseRef-BSD-3-Clause-LANL-Triad-USGov
+/**
+   \file X3D_tokens.hxx
+
+   Implement FORTRAN data and control descriptors as C++ classes
+   suitable for use with C++ streams.
+
+   The five parameterized descriptors correspond to the tokens
+   (terminal symbols) of the FLAG X3D file format; use of these
+   descriptors according to the formats specified in "Summary of the
+   FLAG X3D Format" (LA-UR-04-9033 V. 1.2) constitutes a scanner for
+   the X3D format.
+
+   \author Mark G. Gray <gray@lanl.gov>
+*/
+
+#ifndef X3D_TOKENS_HXX
+#define X3D_TOKENS_HXX
+
+#include "vtkABINamespace.h"
+
+#include <istream>
+#include <ostream>
+#include <stdexcept>
+#include <string>
+
+namespace X3D
+{
+VTK_ABI_NAMESPACE_BEGIN
+/**
+   \class ScanError
+
+   Exception thrown by Aformat, Iformat, PEformat, Rformat, and Xformat.
+
+   When Aformat, Iformat, PEformat, Rformat, or Xformat encounters
+   an unexpected token in the stream being read, it throws this
+   exception with a message containing the token and character
+   position in the stream where found.
+*/
+class ScanError : public std::runtime_error
+{
+public:
+  explicit ScanError(const std::string& m)
+    : std::runtime_error(m.c_str())
+  {
+  }
+  explicit ScanError(std::string unexpect, std::streamoff where)
+    : std::runtime_error((unexpect + std::to_string(where)).c_str())
+  {
+  }
+};
+
+/**
+    Read and ignore rest of line including newline.
+
+    If the last line does not terminate with a newline,
+    this will read through the end of file, setting stream
+    internal state flags eofbit and failbit.
+
+    \param is lhs of >>
+    \return stream reference
+    \exception ScanError if EOF reached before next newline.
+*/
+inline std::istream& eat_endl(std::istream& is)
+{
+  char c;
+  while (is.get(c) && c != '\n')
+    ;
+  if (is.eof())
+    throw ScanError("Unexpected EOF at character: ", is.tellg());
+  return is;
+}
+
+/**
+   Get width characters or until newline from istream,
+   return in whitespace trimmed string.
+
+   If width=0, return next whitespace terminated string.
+   End get at newline.
+
+   \param is stream to read from
+   \param width number of characters to read
+   \return string whitespace trimmed string containing read characters
+   \exception ScanError if newline reached before width characters
+*/
+extern std::string fixed_get(std::istream& is, unsigned int width);
+
+/**
+   \class Aformat
+
+   Fortran CHARACTER data descriptor A.
+*/
+class Aformat
+{
+public:
+  /**
+     Construct format descriptor
+
+     Default width skips whitespace and
+     reads next whitespace delimited string.
+
+     \param w field width
+  */
+  explicit Aformat(unsigned int w = 0)
+    : width(w)
+    , value()
+  {
+  }
+
+  /**
+     Set format width.
+
+     Return reference to *this for chaining.
+     Use width=0 to print as many digits as needed.
+
+     \param w field width
+     \return Aformat reference
+  */
+  Aformat& setw(unsigned int w)
+  {
+    width = w;
+    return *this;
+  }
+
+  /**
+     Set value and return string in Aw formatted string.
+
+     \param s string to assign
+     \return string containing formatted s
+  **/
+  std::string operator()(std::string s);
+
+  /**
+     Extract characters from Aw read of stream.
+
+     End extract at newline.
+
+     \param is lhs of >>
+     \param a rhs of >>
+     \return istream reference
+     \exception ScanError if characters on line < width.
+  **/
+  friend std::istream& operator>>(std::istream& is, Aformat& a);
+
+  /**
+     Return value from last get.
+
+     \return value
+  */
+  std::string operator()() const { return value; }
+
+private:
+  unsigned int width; // of field
+  std::string value;
+};
+
+/**
+   Extract trimmed string from Aw read of stream.
+
+   End extract at newline.
+
+   \param is lhs of >>
+   \param a rhs of <<
+   \return istream reference
+*/
+inline std::istream& operator>>(std::istream& is, Aformat& a)
+{
+  a.value = fixed_get(is, a.width);
+  return is;
+}
+
+/**
+   \class Iformat
+
+   Fortran INTEGER data descriptor I.
+**/
+class Iformat
+{
+public:
+  /**
+     Construct format descriptor
+
+     Default width skips whitespace and
+     reads next non-digit terminated integer.
+
+     \param w field width
+  */
+  explicit Iformat(unsigned int w = 0)
+    : width(w)
+    , value(0)
+  {
+  }
+
+  /**
+     Set format width.
+
+     Return reference to *this for chaining.
+     Use width=0 to print as many digits as needed.
+
+     \param w field width
+     \return Iformat reference
+  */
+  Iformat& setw(unsigned int w)
+  {
+    width = w;
+    return *this;
+  }
+
+  /**
+      Set value and return integer in Iw formatted string.
+
+      \param i value to format
+      \return string containing formatted value
+  **/
+  std::string operator()(int i);
+
+  friend std::istream& operator>>(std::istream& is, Iformat& i);
+
+  /**
+     Return value from last get.
+
+     \return value
+  */
+  int operator()() const { return value; }
+
+private:
+  unsigned int width; // of field
+  int value;
+};
+
+/**
+   Extract integer from Iw read of stream.
+
+   End extract at newline.
+
+   \param is lhs of >>
+   \param i rhs of >>
+   \return istream reference
+   \exception ScanError if characters on line < width or
+                              cannot convert token to integer.
+**/
+inline std::istream& operator>>(std::istream& is, Iformat& i)
+{
+  std::string s(fixed_get(is, i.width));
+  try
+  {
+    i.value = std::stoi(s);
+  }
+  catch (const std::invalid_argument&)
+  {
+    throw ScanError("Cannot convert \"" + s + "\" to int before: ", is.tellg());
+  }
+  catch (const std::out_of_range&)
+  {
+    throw ScanError("Token \"" + s + "\" out of int range before: ", is.tellg());
+  }
+  return is;
+}
+
+/**
+   \class PEformat
+
+   Fortran REAL data descriptor 1PE.
+*/
+class PEformat
+{
+public:
+  /**
+     Construct format descriptor.
+
+     Default width and precision matches C++ default for doubles.
+
+     \param w field width
+     \param p field precision
+  */
+  explicit PEformat(unsigned int w = 0, unsigned int d = 6)
+    : width(w)
+    , precision(d)
+    , value(0.0)
+  {
+  }
+
+  /**
+     Set format width.
+
+     Return reference to *this for chaining.
+     Use width=0 to print as many digits as needed.
+
+     \param w field width
+     \return PEformat reference
+  */
+  PEformat& setw(unsigned int w)
+  {
+    width = w;
+    return *this;
+  }
+
+  /**
+     Set format precision.
+
+     Return reference to *this for chaining.
+
+     \param p field precision
+     \return PEformat reference
+  */
+  PEformat& setprecision(unsigned int d)
+  {
+    precision = d;
+    return *this;
+  }
+
+  friend std::istream& operator>>(std::istream& is, PEformat& pe);
+
+  /**
+     Return value from last get.
+
+     \return double
+  */
+  double operator()() const { return value; }
+
+  /**
+     Set value and return double in 1PEw.d formatted string.
+
+     \param x value to format
+     \return string containing formatted value
+  */
+  std::string operator()(double x);
+
+private:
+  unsigned int width;     // of field
+  unsigned int precision; // of value
+  double value;
+};
+
+/**
+   Extract double from 1PEw.d read of stream.
+
+   End extract at newline.
+
+   \param is lhs of >>
+   \param pe rhs of >>
+   \return istream reference
+   \exception ScanError if characters on line < width or
+                              cannot convert token to double.
+*/
+inline std::istream& operator>>(std::istream& is, PEformat& pe)
+{
+  std::string s(fixed_get(is, pe.width));
+  try
+  {
+    pe.value = std::stod(s);
+  }
+  catch (const std::invalid_argument&)
+  {
+    throw ScanError("Cannot convert \"" + s + "\" to double before: ", is.tellg());
+  }
+  catch (const std::out_of_range&)
+  {
+    throw ScanError("Token \"" + s + "\" overflows double before: ", is.tellg());
+  }
+  return is;
+}
+
+/**
+   \class Rformat
+
+   Non-Fortran control descriptor for periodic end of line.
+*/
+class Rformat
+{
+public:
+  /**
+   Construct descriptor.
+
+   Count use and put endl or get eat_endl with frequency n.
+   If n=0 (the default), never put endl or get eat_endl.
+  */
+  Rformat(int n = 0)
+    : count(n)
+    , counter_(0)
+  {
+  }
+  int operator()() const { return counter_; }
+  void reset() { counter_ = 0; }
+  friend std::istream& operator>>(std::istream& is, Rformat& r);
+  friend std::ostream& operator<<(std::ostream& os, Rformat& r);
+
+private:
+  int count;
+  int counter_;
+};
+
+/**
+   \function Zmod
+
+   Return representation of i in Z/n.  N.B. Z/0 == Z.
+
+   \param n quotient group divisor
+   \param i member of Z
+   \return member of Z/n representing i.
+*/
+inline int Zmod(int n, int i)
+{
+  return n ? i % n : i;
+}
+
+inline std::istream& operator>>(std::istream& is, Rformat& r)
+{
+  r.counter_ = Zmod(r.count, r.counter_ + 1);
+  if (r.counter_ != 0)
+  {
+    return is;
+  }
+  else
+  {
+    return is >> eat_endl;
+  }
+}
+
+inline std::ostream& operator<<(std::ostream& os, Rformat& r)
+{
+  r.counter_ = Zmod(r.count, r.counter_ + 1);
+  if (r.counter_ != 0)
+  {
+    return os;
+  }
+  else
+  {
+    return os << std::endl;
+  }
+}
+
+/**
+   \class Xformat
+
+   Fortran control descriptor X.
+*/
+class Xformat
+{
+public:
+  /**
+     Construct format descriptor
+
+     Default width discards next character.
+
+     \param w field width
+  */
+  explicit Xformat(unsigned int w = 1)
+    : width(w)
+  {
+  }
+
+  /**
+     Set format width.
+
+     Return reference to *this for chaining.
+
+     \param w field width
+     \return Xformat reference
+  */
+  Xformat& setw(unsigned int w)
+  {
+    width = w;
+    return *this;
+  }
+
+  /**
+     Set width and return blanks in wX formatted string.
+
+     \param w field width
+     \return string of width blanks
+  */
+  std::string operator()(unsigned int w)
+  {
+    width = w;
+    return std::string(width, ' ');
+  }
+
+  /**
+     Return blanks in wX formatted string.
+
+     \return string of width blanks
+  */
+  std::string operator()() const { return std::string(width, ' '); }
+
+  friend std::istream& operator>>(std::istream& is, Xformat& x);
+
+private:
+  unsigned int width; // of field
+};
+/**
+   Extract and ignore characters from wX read of stream.
+
+   End extract at newline.
+
+   \param is lhs of >>
+   \param x rhs of >>
+   \return istream reference
+*/
+inline std::istream& operator>>(std::istream& is, Xformat& x)
+{
+  fixed_get(is, x.width);
+  return is;
+}
+VTK_ABI_NAMESPACE_END
+}
+#endif
diff --git a/IO/LANLX3D/vtk.module b/IO/LANLX3D/vtk.module
new file mode 100644
index 00000000000..2ff7ee2e9e4
--- /dev/null
+++ b/IO/LANLX3D/vtk.module
@@ -0,0 +1,22 @@
+NAME
+  VTK::IOLANLX3D
+LIBRARY_NAME
+  vtkIOLANLX3D
+GROUPS
+  StandAlone
+SPDX_LICENSE_IDENTIFIER
+  LicenseRef-BSD-3-Clause-LANL-Triad-USGov
+SPDX_COPYRIGHT_TEXT
+  Copyright (c) Kitware Inc.
+  Copyright (c) 2021, Los Alamos National Laboratory
+  Copyright (c) 2021. Triad National Security, LLC. All rights reserved.
+SPDX_CUSTOM_LICENSE_FILE
+  LICENSE
+SPDX_CUSTOM_LICENSE_NAME
+  BSD-3-Clause-LANL-Triad-USGov
+LICENSE_FILES
+  LICENSE
+DEPENDS
+  VTK::FiltersCore
+PRIVATE_DEPENDS
+  VTK::CommonCore
diff --git a/IO/LANLX3D/vtkLANLX3DReader.cxx b/IO/LANLX3D/vtkLANLX3DReader.cxx
new file mode 100644
index 00000000000..fd2f596db35
--- /dev/null
+++ b/IO/LANLX3D/vtkLANLX3DReader.cxx
@@ -0,0 +1,577 @@
+// SPDX-FileCopyrightText: Copyright (c) Kitware Inc.
+// SPDX-FileCopyrightText: Copyright (c) 2021, Los Alamos National Laboratory
+// SPDX-FileCopyrightText: Copyright (c) 2021. Triad National Security, LLC
+// SPDX-License-Identifier: LicenseRef-BSD-3-Clause-LANL-Triad-USGov
+#include "vtkLANLX3DReader.h"
+
+// typical VTK boilerplate
+#include "vtkInformation.h"
+#include "vtkInformationVector.h"
+#include "vtkObjectFactory.h"
+#include "vtkStreamingDemandDrivenPipeline.h"
+
+// specific to this class
+#include "vtkCellArray.h"
+#include "vtkCellData.h"
+#include "vtkDoubleArray.h"
+#include "vtkIdTypeArray.h"
+#include "vtkIntArray.h"
+#include "vtkMultiBlockDataSet.h"
+#include "vtkMultiPieceDataSet.h"
+#include "vtkPointData.h"
+#include "vtkUnsignedCharArray.h"
+#include "vtkUnstructuredGrid.h"
+
+// common X3D reader code
+#include "X3D_reader.hxx"
+#include "X3D_tokens.hxx"
+
+// for file detection
+#include <sstream>
+#include <sys/stat.h>
+
+VTK_ABI_NAMESPACE_BEGIN
+//----------------------------------------------------------------------------
+vtkStandardNewMacro(vtkLANLX3DReader);
+
+//----------------------------------------------------------------------------
+vtkLANLX3DReader::vtkLANLX3DReader()
+{
+  this->SetNumberOfInputPorts(0);
+}
+
+//----------------------------------------------------------------------------
+vtkLANLX3DReader::~vtkLANLX3DReader()
+{
+  this->SetFileName(nullptr);
+}
+
+//----------------------------------------------------------------------------
+void vtkLANLX3DReader::PrintSelf(ostream& os, vtkIndent indent)
+{
+  this->Superclass::PrintSelf(os, indent);
+}
+
+//----------------------------------------------------------------------------
+int vtkLANLX3DReader::RequestInformation(
+  vtkInformation*, vtkInformationVector**, vtkInformationVector* outputVector)
+{
+  // we can handle pieces
+  vtkInformation* out_info = outputVector->GetInformationObject(0);
+  out_info->Set(vtkAlgorithm::CAN_HANDLE_PIECE_REQUEST(), 1);
+
+  return 1;
+}
+
+//----------------------------------------------------------------------------
+int vtkLANLX3DReader::RequestData(
+  vtkInformation*, vtkInformationVector**, vtkInformationVector* outputVector)
+{
+  //
+  // VTK stuff
+  //
+  vtkInformation* out_info = outputVector->GetInformationObject(0);
+  // multiblock data set is required, because there are no multipiece data
+  // set algorithms or filters
+  vtkMultiBlockDataSet* output =
+    vtkMultiBlockDataSet::SafeDownCast(out_info->Get(vtkDataObject::DATA_OBJECT()));
+  output->SetNumberOfBlocks(1);
+
+  // accepted pattern for VTK distributed data now is (per processor)
+  // where n is number of files, and p is number of processors
+  // 1 multiblock -> 1 multipiece -> 0 or 1 or (n/p) data sets
+  //
+  // pieces = processor and number of pieces = number of processors
+  int piece = out_info->Get(vtkStreamingDemandDrivenPipeline::UPDATE_PIECE_NUMBER());
+  int n_pieces = out_info->Get(vtkStreamingDemandDrivenPipeline::UPDATE_NUMBER_OF_PIECES());
+  // this is the "real" data set result, as we have multiple file pieces
+  vtkMultiPieceDataSet* mpds = vtkMultiPieceDataSet::New();
+  output->SetBlock(0, mpds);
+
+  // put other data into scope (on the stack) for goto
+  X3D::Reader* x3d = nullptr;
+  int return_code = 1;
+  int first_file_piece, end_file_piece, global_first_file;
+  bool has_numbered_files;
+  std::string fn(this->FileName);
+
+  if (this->FileName == nullptr)
+  {
+    vtkErrorMacro(<< "Fatal error. FileName is not set.");
+    return_code = 0;
+    goto X3D_EXIT_POINT; // yes, goto -- early return/break/guard vars is an
+                         // annoying/bad code pattern for cleaning up in
+                         // structured code and prone to error -- use goto
+                         // to put all clean up in ONE place, rather than
+                         // replication or putting silly guard variables
+                         //
+                         // Dijkstra was wrong
+  }
+
+  //
+  // read the number of pieces on disk
+  //
+
+  // check that it ends with x3d or not
+  // NOTE not unicode compliant! (but I don't think any of VTK is)
+  if (fn.size() > 4 && (fn[fn.size() - 1] == 'd' || fn[fn.size() - 1] == 'D') &&
+    (fn[fn.size() - 2] == '3') && (fn[fn.size() - 3] == 'x' || fn[fn.size() - 3] == 'X') &&
+    (fn[fn.size() - 4] == '.'))
+  {
+    has_numbered_files = false;
+    first_file_piece = 1;
+    end_file_piece = 2;
+  }
+  // check that it ends with numbers and x3d
+  // NOTE not unicode compliant! (but I don't think any of VTK is)
+  else if (fn.size() > 10 && (fn[fn.size() - 1] > 47 && fn[fn.size() - 1] < 58) &&
+    (fn[fn.size() - 2] > 47 && fn[fn.size() - 2] < 58) &&
+    (fn[fn.size() - 3] > 47 && fn[fn.size() - 3] < 58) &&
+    (fn[fn.size() - 4] > 47 && fn[fn.size() - 4] < 58) &&
+    (fn[fn.size() - 5] > 47 && fn[fn.size() - 5] < 58) && (fn[fn.size() - 6] == '.') &&
+    (fn[fn.size() - 7] == 'd' || fn[fn.size() - 7] == 'D') && (fn[fn.size() - 8] == '3') &&
+    (fn[fn.size() - 9] == 'x' || fn[fn.size() - 9] == 'X') && (fn[fn.size() - 10] == '.'))
+  {
+    has_numbered_files = true;
+
+    std::string base = fn.substr(0, fn.size() - 5);
+    first_file_piece = std::stoi(fn.substr(fn.size() - 5));
+    end_file_piece = first_file_piece + 1;
+    fn = base;
+
+    // if we want to read all of them
+    if (this->ReadAllPieces)
+    {
+      // count upwards until we run out of files
+      struct stat buffer;
+      std::stringstream construct;
+      construct << base << std::setw(5) << std::setfill('0') << end_file_piece;
+
+      while (stat(construct.str().c_str(), &buffer) == 0)
+      {
+        end_file_piece = end_file_piece + 1;
+        construct.str("");
+        construct.clear();
+        construct << base << std::setw(5) << std::setfill('0') << end_file_piece;
+      }
+    }
+  }
+  // incorrectly formatted filename
+  else
+  {
+    vtkErrorMacro(<< "Fatal error. X3D file name is not formatted correctly: Needs to end in "
+                     "'.x3d' or '.x3d.NNNNN'.");
+    return_code = 0;
+    goto X3D_EXIT_POINT;
+  }
+
+  // now we know how many file pieces we have
+  // vtkMultiPiece data set allows us to represent each file with
+  // an indepedent unstructured grid
+  //
+  // it is set to the number of file pieces we have in total
+  mpds->SetNumberOfPieces(end_file_piece - first_file_piece);
+
+  // return early if we have more VTK pieces than *actual* X3D file pieces
+  // e.g., generate empty data on more processors than what we have on disk
+  if (piece >= (end_file_piece - first_file_piece))
+  {
+    return_code = 1;
+    goto X3D_EXIT_POINT;
+  }
+
+  // different cases for assigning files to pieces (processors)
+  global_first_file = first_file_piece;
+  if (has_numbered_files && n_pieces > 1)
+  {
+    // fewer processors than files -- assign them to processors
+    // (the reason for the need for multipiece data set)
+    if (n_pieces < (end_file_piece - first_file_piece))
+    {
+      double scale = (double)(end_file_piece - first_file_piece) / (double)n_pieces;
+      int bias = first_file_piece;
+      first_file_piece = (int)(piece * scale) + bias;
+      // make sure we account for numerical error and get *all* files
+      // i.e., if it's the end, leave end_file_piece as the last actual file
+      // (the last processor sucks up any numerical error, off by 1-ish)
+      if (piece + 1 < n_pieces)
+      {
+        end_file_piece = (int)((piece + 1) * scale) + bias;
+      }
+    }
+    // equal (or more) number of pieces and processors -- but we
+    // exited early if there were more
+    else
+    {
+      first_file_piece = first_file_piece + piece;
+      end_file_piece = first_file_piece + 1;
+    }
+  }
+
+  //
+  // read the data, looping over files assigned to this piece
+  //
+  try
+  {
+    for (int f = first_file_piece; f < end_file_piece; f++)
+    {
+      // if we have numbered files, construct the filename
+      if (has_numbered_files)
+      {
+        std::stringstream construct;
+        construct << fn << std::setw(5) << std::setfill('0') << f;
+        x3d = new X3D::Reader(construct.str());
+      }
+      else
+      {
+        x3d = new X3D::Reader(fn);
+      }
+      X3D::Header header = x3d->header();
+
+      // check if X3D processor matches file piece number
+      int processor = header["process"];
+      if (processor != f)
+      {
+        vtkErrorMacro(
+          << "Warning in X3D header: 'process' does not match file number. Visualization may be "
+             "wrong. Further fatal errors may occur in the X3D parser.");
+      }
+
+      // only support dim 2 or 3
+      int dimension = header["numdim"];
+      if (dimension != 2 && dimension != 3)
+      {
+        vtkErrorMacro(<< "Fatal error in X3D header: No VTK reader support for 'numdim' = "
+                      << dimension);
+        return_code = 0;
+        goto X3D_EXIT_POINT;
+      }
+
+      // read the rest
+      X3D::Materials matnames = x3d->matnames();
+      X3D::Materials mateos = x3d->mateos();
+      X3D::Materials matopc = x3d->matopc();
+      X3D::Nodes nodes = x3d->nodes();
+      X3D::Faces faces = x3d->faces();
+      X3D::Cells cells = x3d->cells();
+      X3D::ConstrainedNodes slaved_nodes = x3d->constrained_nodes();
+      X3D::SharedNodes ghost_nodes = x3d->shared_nodes();
+      X3D::CellData cell_data = x3d->cell_data();
+      X3D::NodeData node_data = x3d->node_data();
+      delete x3d;
+      x3d = nullptr; // delete the reader
+
+      //
+      // translate x3d file mesh into VTK UG
+      //
+      vtkUnstructuredGrid* ug = vtkUnstructuredGrid::New();
+      // set our file piece/multipiece number
+      mpds->SetPiece(f - global_first_file, ug);
+
+      //
+      // translate points
+      //
+      size_t n_points = nodes.size();
+
+      {
+        vtkPoints* points = vtkPoints::New();
+        points->SetNumberOfPoints(n_points);
+        vtkIdTypeArray* pid = vtkIdTypeArray::New();
+        pid->SetNumberOfValues(n_points);
+
+        for (size_t i = 0; i < n_points; i++)
+        {
+          points->SetPoint(i, nodes[i][0], nodes[i][1], nodes[i][2]);
+          pid->SetValue(i, header["process"]);
+        }
+
+        // set points
+        ug->SetPoints(points);
+        points->Delete();
+
+        // set pid data
+        pid->SetName("partition_number");
+        ug->GetPointData()->AddArray(pid);
+        pid->Delete();
+      }
+
+      //
+      // translate cells
+      //
+      size_t n_cells = cells.size();
+
+      // both 2D and 3D cells are boundary represented
+      // 1D edges for 2D faces and 2D faces for 3D cells
+      if (dimension == 2)
+      {
+        vtkCellArray* cell_list = vtkCellArray::New();
+        vtkIdTypeArray* id_array = vtkIdTypeArray::New();
+        vtkIdTypeArray* n_neighbors = vtkIdTypeArray::New();
+        n_neighbors->SetNumberOfValues(n_cells);
+
+        for (size_t i = 0; i < n_cells; i++)
+        {
+          size_t n_points_cell = cells[i].size();
+          id_array->InsertNextValue(n_points_cell);
+
+          size_t neighbors = 0;
+          for (size_t j = 0; j < n_points_cell; j++)
+          {
+            size_t face_id = cells[i][j] - 1;
+            // same winding direction as VTK, CCW -- take the first vertex
+            // of a 1D edge
+            id_array->InsertNextValue(faces[face_id].node_id[0] - 1);
+
+            // count number of neighbor faces
+            if (faces[face_id].neighbor_process_id != 0)
+            {
+              neighbors = neighbors + 1;
+            }
+          }
+          n_neighbors->SetValue(i, neighbors);
+        }
+
+        // set cells
+        cell_list->SetCells(n_cells, id_array);
+        ug->SetCells(VTK_POLYGON, cell_list);
+        id_array->Delete();
+        cell_list->Delete();
+
+        // set neighbors
+        n_neighbors->SetName("number_of_neighbors");
+        ug->GetCellData()->AddArray(n_neighbors);
+        n_neighbors->Delete();
+      }
+      // dimension is 3
+      else
+      {
+        vtkCellArray* cell_list = vtkCellArray::New();
+        vtkIdTypeArray* id_array = vtkIdTypeArray::New();
+        vtkIdTypeArray* n_neighbors = vtkIdTypeArray::New();
+        n_neighbors->SetNumberOfValues(n_cells);
+
+        for (size_t i = 0; i < n_cells; i++)
+        {
+          size_t n_faces = cells[i].size();
+          size_t length_index = id_array->GetNumberOfValues();
+          id_array->InsertNextValue(0); // unknown length, have to fixup later
+          id_array->InsertNextValue(n_faces);
+
+          size_t neighbors = 0;
+          size_t length = 1;
+          for (size_t j = 0; j < n_faces; j++)
+          {
+            size_t face_id = cells[i][j] - 1;
+
+            size_t n_points_face = faces[face_id].node_id.size();
+            id_array->InsertNextValue(n_points_face);
+            length = length + 1 + n_points_face;
+
+            for (size_t k = 0; k < n_points_face; k++)
+            {
+              // VTK's polyhedron is a boundary representation cell, too
+              // just insert all of the 2D polygon faces
+              id_array->InsertNextValue(faces[face_id].node_id[k] - 1);
+            }
+
+            // count number of owned faces and neighbor faces
+            if (faces[face_id].neighbor_process_id != 0)
+            {
+              neighbors = neighbors + 1;
+            }
+          }
+          n_neighbors->SetValue(i, neighbors);
+
+          // go back and fixup length
+          id_array->SetValue(length_index, length);
+        }
+
+        // set cells
+        cell_list->SetCells(n_cells, id_array);
+        ug->SetCells(VTK_POLYHEDRON, cell_list);
+        id_array->Delete();
+        cell_list->Delete();
+
+        // set neighbors
+        n_neighbors->SetName("number_of_neighbors");
+        ug->GetCellData()->AddArray(n_neighbors);
+        n_neighbors->Delete();
+      }
+
+      //
+      // translate slave node attribute data
+      //
+      if (!slaved_nodes.empty())
+      {
+        vtkIdTypeArray* n_masters = vtkIdTypeArray::New();
+        n_masters->SetNumberOfValues(n_points);
+        n_masters->Fill(0);
+        vtkIdTypeArray* n_slaves = vtkIdTypeArray::New();
+        n_slaves->SetNumberOfValues(n_points);
+        n_slaves->Fill(0);
+
+        for (size_t i = 0; i < slaved_nodes.size(); i++)
+        {
+          int vertex = slaved_nodes[i].vertex_id - 1;
+          size_t nm = slaved_nodes[i].master.size();
+          n_masters->SetValue(vertex, nm);
+          for (size_t j = 0; j < nm; j++)
+          {
+            size_t master = slaved_nodes[i].master[j] - 1;
+            n_slaves->SetValue(master, n_slaves->GetValue(master) + 1);
+          }
+        }
+
+        // set point data
+        n_masters->SetName("number_of_masters");
+        n_slaves->SetName("number_of_slaves");
+        ug->GetPointData()->AddArray(n_masters);
+        ug->GetPointData()->AddArray(n_slaves);
+        n_masters->Delete();
+        n_slaves->Delete();
+      }
+
+      //
+      // translate ghost node data
+      //
+      if (!ghost_nodes.empty())
+      {
+        ug->AllocatePointGhostArray();
+        vtkUnsignedCharArray* ghosts = ug->GetPointGhostArray();
+        vtkIdTypeArray* owner = vtkIdTypeArray::New();
+        owner->SetNumberOfValues(n_points);
+        owner->Fill(0);
+
+        for (size_t i = 0; i < ghost_nodes.size(); i++)
+        {
+          int point_id = ghost_nodes[i][0] - 1;
+
+          // if the current piece does not match the owner, it's a VTK ghost
+          if (ghost_nodes[i][1] != processor)
+          {
+            ghosts->SetValue(
+              point_id, ghosts->GetValue(point_id) | vtkDataSetAttributes::DUPLICATEPOINT);
+            owner->SetValue(point_id, ghost_nodes[i][1]);
+          }
+        }
+
+        // set owner data
+        owner->SetName("owning_partition");
+        ug->GetPointData()->AddArray(owner);
+        owner->Delete();
+      }
+
+      //
+      // translate cell attribute data
+      //
+
+      // matid
+      {
+        vtkIntArray* matid = vtkIntArray::New();
+        matid->SetNumberOfValues(n_cells);
+        for (size_t i = 0; i < n_cells; i++)
+        {
+          matid->SetValue(i, cell_data.matid[i]);
+        }
+
+        // add matid
+        matid->SetName("matid");
+        ug->GetCellData()->AddArray(matid);
+        matid->Delete();
+      }
+
+      // partelm
+      {
+        vtkIntArray* partelm = vtkIntArray::New();
+        partelm->SetNumberOfValues(n_cells);
+        for (size_t i = 0; i < n_cells; i++)
+        {
+          partelm->SetValue(i, cell_data.partelm[i]);
+        }
+
+        // add partelm
+        partelm->SetName("partelm");
+        ug->GetCellData()->AddArray(partelm);
+        partelm->Delete();
+      }
+
+      // all others
+      for (std::vector<std::string>::iterator iter = cell_data.names.begin();
+           iter != cell_data.names.end(); iter++)
+      {
+        // skip matid and partelm
+        if (*iter == "matid" || *iter == "partelm")
+        {
+          continue;
+        }
+
+        // attr
+        vtkDoubleArray* attr = vtkDoubleArray::New();
+        attr->SetNumberOfValues(n_cells);
+        std::vector<double>& data = cell_data.fields[*iter];
+        for (size_t i = 0; i < n_cells; i++)
+        {
+          attr->SetValue(i, data[i]);
+        }
+
+        // add attr
+        attr->SetName(iter->c_str());
+        ug->GetCellData()->AddArray(attr);
+        attr->Delete();
+      }
+
+      //
+      // translate point attribute data
+      //
+
+      // node attribute data are 3-vectors
+      for (std::vector<std::string>::iterator iter = node_data.names.begin();
+           iter != node_data.names.end(); iter++)
+      {
+        // attr
+        vtkDoubleArray* attr = vtkDoubleArray::New();
+        attr->SetNumberOfComponents(3);
+        attr->SetNumberOfTuples(n_points);
+        std::vector<X3D::Node>& data = node_data.fields[*iter];
+        for (size_t i = 0; i < n_points; i++)
+        {
+          attr->SetTuple3(i, data[i][0], data[i][1], data[i][2]);
+        }
+
+        // add attr
+        attr->SetName(iter->c_str());
+        ug->GetPointData()->AddArray(attr);
+        attr->Delete();
+      }
+
+      // all done with this file
+      ug->Delete();
+    }
+  } // end of try { for {
+
+  // if parser fails
+  catch (const X3D::ReadError& e)
+  {
+    vtkErrorMacro(<< "Fatal error in X3D parsing: " << e.what());
+    return_code = 0;
+  }
+  catch (const X3D::ScanError& e)
+  {
+    vtkErrorMacro(<< "Fatal error in X3D parsing: " << e.what());
+    return_code = 0;
+  }
+  catch (...)
+  {
+    vtkErrorMacro(<< "Fatal error. Caught unknown exception in X3D parser.");
+    return_code = 0;
+  }
+
+  // done -- one exit point because return/breaking out/guard booleans is/are
+  // an annoying code pattern -- as well as try/catch, which should be
+  // replaced with typed maybe/either/none
+X3D_EXIT_POINT:
+  delete x3d;
+  mpds->Delete();
+  return return_code;
+}
+VTK_ABI_NAMESPACE_END
diff --git a/IO/LANLX3D/vtkLANLX3DReader.h b/IO/LANLX3D/vtkLANLX3DReader.h
new file mode 100644
index 00000000000..591c3ea9a6a
--- /dev/null
+++ b/IO/LANLX3D/vtkLANLX3DReader.h
@@ -0,0 +1,58 @@
+// SPDX-FileCopyrightText: Copyright (c) Kitware Inc.
+// SPDX-FileCopyrightText: Copyright (c) 2021, Los Alamos National Laboratory
+// SPDX-FileCopyrightText: Copyright (c) 2021. Triad National Security, LLC
+// SPDX-License-Identifier: LicenseRef-BSD-3-Clause-LANL-Triad-USGov
+/**
+ *
+ * @class vtkLANLX3DReader
+ * @brief class for reading LANL X3D format files
+ *
+ * @section caveats Caveats
+ * The LANL X3D file format is not to be confused with the X3D file format that
+ * is the successor to VRML. The LANL X3D format is designed to store geometry
+ * for LANL physics codes.
+ *
+ * @par Thanks:
+ * Developed by Jonathan Woodering at Los Alamos National Laboratory
+ */
+
+#ifndef vtkLANLX3DReader_h
+#define vtkLANLX3DReader_h
+
+#include "vtkIOLANLX3DModule.h" // for export macro
+#include "vtkMultiBlockDataSetAlgorithm.h"
+
+class vtkMultiBlockDataSet;
+
+VTK_ABI_NAMESPACE_BEGIN
+class VTKIOLANLX3D_EXPORT vtkLANLX3DReader : public vtkMultiBlockDataSetAlgorithm
+{
+public:
+  static vtkLANLX3DReader* New();
+  vtkTypeMacro(vtkLANLX3DReader, vtkMultiBlockDataSetAlgorithm);
+  void PrintSelf(ostream& os, vtkIndent indent) override;
+
+  vtkSetStringMacro(FileName);
+  vtkGetStringMacro(FileName);
+
+  vtkSetMacro(ReadAllPieces, bool);
+  vtkGetMacro(ReadAllPieces, bool);
+
+protected:
+  vtkLANLX3DReader();
+  ~vtkLANLX3DReader() override;
+
+  char* FileName = nullptr;
+  bool ReadAllPieces = true;
+
+  int RequestInformation(vtkInformation*, vtkInformationVector**, vtkInformationVector*) override;
+
+  int RequestData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) override;
+
+private:
+  vtkLANLX3DReader(const vtkLANLX3DReader&) = delete;
+  void operator=(const vtkLANLX3DReader&) = delete;
+};
+VTK_ABI_NAMESPACE_END
+
+#endif
-- 
GitLab