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