PolyFEM
Loading...
Searching...
No Matches
MeshLoader.cpp
Go to the documentation of this file.
1#include "MeshLoader.hpp"
2
5
6#include <algorithm>
7
8namespace polyfem::mesh
9{
10 namespace
11 {
12 std::string child_path(const std::string &group, const std::string &name)
13 {
14 return (std::filesystem::path(group) / name).lexically_normal().generic_string();
15 }
16
17 std::vector<std::vector<int>> unpack_connectivity(
18 const std::vector<int> &values,
19 const std::vector<long> &offsets,
20 const io::ResourceIO &resources,
21 const std::string &path)
22 {
23 if (offsets.empty() || offsets.front() != 0 || offsets.back() != long(values.size()))
24 log_and_throw_error("Invalid ragged offsets in {}.", resources.describe(path));
25 std::vector<std::vector<int>> result(offsets.size() - 1);
26 for (size_t i = 0; i + 1 < offsets.size(); ++i)
27 {
28 if (offsets[i] > offsets[i + 1])
29 log_and_throw_error("Offsets in {} are not monotone.", resources.describe(path));
30 result[i].assign(values.begin() + offsets[i], values.begin() + offsets[i + 1]);
31 }
32 return result;
33 }
34
35 std::vector<std::vector<double>> unpack_weights(
36 const std::vector<double> &values,
37 const std::vector<long> &offsets,
38 const io::ResourceIO &resources,
39 const std::string &path)
40 {
41 if (offsets.empty() || offsets.front() != 0 || offsets.back() != long(values.size()))
42 log_and_throw_error("Invalid weight offsets in {}.", resources.describe(path));
43 std::vector<std::vector<double>> result(offsets.size() - 1);
44 for (size_t i = 0; i + 1 < offsets.size(); ++i)
45 {
46 if (offsets[i] > offsets[i + 1])
47 log_and_throw_error("Offsets in {} are not monotone.", resources.describe(path));
48 result[i].assign(values.begin() + offsets[i], values.begin() + offsets[i + 1]);
49 }
50 return result;
51 }
52 } // namespace
53
54 void MeshLoader::validate_group(const std::string &group, const std::string &expected_type) const
55 {
56 if (resources_.exists(child_path(group, "v")) || resources_.exists(child_path(group, "c")))
58 "Historical mesh datasets 'v'/'c' are unsupported in {}; use 'vertices'/'cells'.",
59 resources_.describe(group));
60 if (!resources_.has_attribute(group, "schema_version"))
61 log_and_throw_error("Mesh group {} is missing schema_version metadata.", resources_.describe(group));
62 const long version = resources_.read_integer_attribute(group, "schema_version");
63 if (version != MESH_SCHEMA_VERSION)
65 "Unsupported mesh schema version {} in {}; expected {}.",
66 version, resources_.describe(group), MESH_SCHEMA_VERSION);
67 if (!resources_.has_attribute(group, "mesh_type") || resources_.read_string_attribute(group, "mesh_type") != expected_type)
69 "Mesh group {} does not declare mesh_type='{}'.",
70 resources_.describe(group), expected_type);
71 if (!resources_.has_attribute(group, "dimension"))
72 log_and_throw_error("Mesh group {} is missing dimension metadata.", resources_.describe(group));
73 }
74
75 std::unique_ptr<Mesh> MeshLoader::load_fem(const std::string &path, const bool non_conforming) const
76 {
77 if (!resources_.exists(path))
78 log_and_throw_error("Mesh resource {} does not exist.", resources_.describe(path));
79
80 MeshData data{Eigen::MatrixXd(), Eigen::MatrixXi()};
81 if (!resources_.is_group(path))
82 {
83 std::string extension = std::filesystem::path(path).extension().string();
84 std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
85 if (extension == ".msh")
87 else if (extension == ".hybrid")
88 {
89 auto input = resources_.open(path, false);
90 data = MeshReader::read_hybrid(*input, resources_.describe(path));
91 }
92 else
94 return Mesh::create(std::move(data), non_conforming);
95 }
96
97 validate_group(path, "fem");
98 const std::string vertices_path = child_path(path, "vertices");
99 const std::string cells_path = child_path(path, "cells");
100 if (!resources_.exists(vertices_path) || !resources_.exists(cells_path))
101 log_and_throw_error("FEM mesh group {} requires vertices and cells.", resources_.describe(path));
102 Eigen::MatrixXd vertices = resources_.read_matrix(vertices_path);
103 Eigen::MatrixXi cells = resources_.read_int_matrix(cells_path);
104 const long dimension = resources_.read_integer_attribute(path, "dimension");
105 if ((dimension != 2 && dimension != 3) || vertices.cols() != dimension)
106 log_and_throw_error("Invalid dimension metadata in {}.", resources_.describe(path));
107 if (vertices.rows() == 0 || cells.rows() == 0 || cells.cols() < 3)
108 log_and_throw_error("Empty or invalid connectivity in {}.", resources_.describe(path));
109 if (cells.minCoeff() < -1 || cells.maxCoeff() >= vertices.rows())
110 log_and_throw_error("Connectivity in {} references an invalid vertex.", resources_.describe(path));
111
112 data = MeshData(std::move(vertices), std::move(cells));
113 if (resources_.has_attribute(path, "elements_are_ordered"))
114 {
115 const long ordered = resources_.read_integer_attribute(path, "elements_are_ordered");
116 if (ordered != 0 && ordered != 1)
117 log_and_throw_error("Mesh group {} has invalid elements_are_ordered metadata.", resources_.describe(path));
118 data.elements_are_ordered = ordered != 0;
119 }
120
121 const std::string body_ids_path = child_path(path, "body_ids");
122 if (resources_.exists(body_ids_path))
123 data.body_ids = resources_.read_int_vector(body_ids_path);
124 const std::string geometry_ids_path = child_path(path, "geometry_ids");
125 if (resources_.exists(geometry_ids_path))
126 data.geometry_ids = resources_.read_int_vector(geometry_ids_path);
127 const std::string node_ids_path = child_path(path, "node_ids");
128 if (resources_.exists(node_ids_path))
129 data.node_ids = resources_.read_int_vector(node_ids_path);
130 const std::string boundary_ids_path = child_path(path, "boundary_ids");
131 const std::string boundary_elements_path = child_path(path, "boundary_elements");
132 const bool any_boundary = resources_.exists(boundary_ids_path) || resources_.exists(boundary_elements_path);
133 if (any_boundary)
134 {
135 if (!resources_.exists(boundary_ids_path) || !resources_.exists(boundary_elements_path))
136 log_and_throw_error("Boundary data in {} requires both boundary_elements and boundary_ids.", resources_.describe(path));
137 const Eigen::MatrixXi elements = resources_.read_int_matrix(boundary_elements_path);
138 data.boundary_ids = resources_.read_int_vector(boundary_ids_path);
139 data.boundary_elements.resize(elements.rows());
140 for (int i = 0; i < elements.rows(); ++i)
141 {
142 bool found_padding = false;
143 for (int j = 0; j < elements.cols(); ++j)
144 {
145 const int vertex = elements(i, j);
146 if (vertex == -1)
147 {
148 found_padding = true;
149 continue;
150 }
151 if (vertex < 0 || found_padding)
152 log_and_throw_error("Boundary connectivity in {} has invalid padding.", resources_.describe(path));
153 data.boundary_elements[i].push_back(vertex);
154 }
155 }
156 }
157
158 const std::string higher_nodes_path = child_path(path, "higher_order_nodes");
159 const std::string higher_connectivity_path = child_path(path, "higher_order_connectivity");
160 const std::string higher_offsets_path = child_path(path, "higher_order_offsets");
161 const bool any_higher = resources_.exists(higher_nodes_path)
162 || resources_.exists(higher_connectivity_path) || resources_.exists(higher_offsets_path);
163 if (any_higher)
164 {
165 if (!resources_.exists(higher_nodes_path) || !resources_.exists(higher_connectivity_path) || !resources_.exists(higher_offsets_path))
166 log_and_throw_error("Higher-order data in {} is incomplete.", resources_.describe(path));
167 data.higher_order_connectivity = unpack_connectivity(
168 resources_.read_int_vector(higher_connectivity_path), resources_.read_long_vector(higher_offsets_path),
169 resources_, higher_offsets_path);
170 data.higher_order_nodes = resources_.read_matrix(higher_nodes_path);
171 }
172
173 const std::string weights_path = child_path(path, "higher_order_weights");
174 const std::string weight_offsets_path = child_path(path, "higher_order_weight_offsets");
175 const bool any_weights = resources_.exists(weights_path) || resources_.exists(weight_offsets_path);
176 if (any_weights)
177 {
178 if (!resources_.exists(weights_path) || !resources_.exists(weight_offsets_path))
179 log_and_throw_error("Higher-order weights in {} require values and offsets.", resources_.describe(path));
180 data.higher_order_weights = unpack_weights(
181 resources_.read_double_vector(weights_path), resources_.read_long_vector(weight_offsets_path),
182 resources_, weight_offsets_path);
183 }
184
185 const std::string faces_path = child_path(path, "faces");
186 const std::string face_offsets_path = child_path(path, "face_offsets");
187 const std::string cell_faces_path = child_path(path, "cell_faces");
188 const std::string cell_face_offsets_path = child_path(path, "cell_face_offsets");
189 const bool any_polyhedral = resources_.exists(faces_path) || resources_.exists(face_offsets_path)
190 || resources_.exists(cell_faces_path) || resources_.exists(cell_face_offsets_path)
191 || resources_.exists(child_path(path, "cell_face_orientations"))
192 || resources_.exists(child_path(path, "cell_is_hex"))
193 || resources_.exists(child_path(path, "cell_kernel_points"));
194 if (any_polyhedral)
195 {
196 for (const std::string &required : {
197 faces_path, face_offsets_path, cell_faces_path, cell_face_offsets_path,
198 child_path(path, "cell_face_orientations"), child_path(path, "cell_is_hex"),
199 child_path(path, "cell_kernel_points")})
200 if (!resources_.exists(required))
201 log_and_throw_error("Polyhedral mesh group {} is missing {}.", resources_.describe(path), required);
202 data.faces = unpack_connectivity(
203 resources_.read_int_vector(faces_path), resources_.read_long_vector(face_offsets_path),
204 resources_, face_offsets_path);
205 const std::vector<long> cell_face_offsets = resources_.read_long_vector(cell_face_offsets_path);
206 data.cell_faces = unpack_connectivity(
207 resources_.read_int_vector(cell_faces_path), cell_face_offsets,
208 resources_, cell_face_offsets_path);
209 data.cell_face_orientations = unpack_connectivity(
210 resources_.read_int_vector(child_path(path, "cell_face_orientations")), cell_face_offsets,
211 resources_, cell_face_offsets_path);
212 const std::vector<int> cell_is_hex = resources_.read_int_vector(child_path(path, "cell_is_hex"));
213 if (std::any_of(cell_is_hex.begin(), cell_is_hex.end(), [](const int value) { return value != 0 && value != 1; }))
214 log_and_throw_error("Polyhedral mesh group {} has invalid cell_is_hex values.", resources_.describe(path));
215 data.cell_is_hex.assign(cell_is_hex.begin(), cell_is_hex.end());
216 data.cell_kernel_points = resources_.read_matrix(child_path(path, "cell_kernel_points"));
217 }
218 return Mesh::create(std::move(data), non_conforming);
219 }
220
221 SurfaceMesh MeshLoader::load_surface(const std::string &path) const
222 {
223 if (!resources_.exists(path))
224 log_and_throw_error("Surface resource {} does not exist.", resources_.describe(path));
225 SurfaceMesh result;
226 if (!resources_.is_group(path))
227 {
228 const std::filesystem::path materialized = resources_.materialize(path);
229 std::string extension = std::filesystem::path(path).extension().string();
230 std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
231 if (extension == ".msh")
232 result = MeshReader::read_msh_surface(materialized);
233 else if (extension == ".obj")
234 result = MeshReader::read_obj_surface(materialized);
235 else if (!MeshReader::read_triangle_surface(materialized, result))
236 result = MeshReader::read_geogram_surface(materialized);
237 return result;
238 }
239
240 validate_group(path, "surface");
241 const std::string vertices_path = child_path(path, "vertices");
242 if (!resources_.exists(vertices_path))
243 log_and_throw_error("Surface mesh group {} requires vertices.", resources_.describe(path));
244 result.vertices = resources_.read_matrix(vertices_path);
245 if (result.vertices.cols() != resources_.read_integer_attribute(path, "dimension"))
246 log_and_throw_error("Invalid surface dimension metadata in {}.", resources_.describe(path));
247 const std::string points_path = child_path(path, "points");
248 if (resources_.exists(points_path))
249 {
250 const std::vector<int> values = resources_.read_int_vector(points_path);
251 result.points = Eigen::Map<const Eigen::VectorXi>(values.data(), values.size());
252 }
253 const std::string edges_path = child_path(path, "edges");
254 if (resources_.exists(edges_path))
255 result.edges = resources_.read_int_matrix(edges_path);
256 const std::string faces_path = child_path(path, "faces");
257 if (resources_.exists(faces_path))
258 result.faces = resources_.read_int_matrix(faces_path);
259 const auto valid = [&](const Eigen::MatrixXi &elements) {
260 return elements.size() == 0 || (elements.minCoeff() >= 0 && elements.maxCoeff() < result.vertices.rows());
261 };
262 if ((result.points.size() && (result.points.minCoeff() < 0 || result.points.maxCoeff() >= result.vertices.rows()))
263 || !valid(result.edges) || !valid(result.faces))
264 log_and_throw_error("Surface connectivity in {} references an invalid vertex.", resources_.describe(path));
265 return result;
266 }
267} // namespace polyfem::mesh
virtual std::vector< int > read_int_vector(const std::string &path) const =0
virtual Eigen::MatrixXi read_int_matrix(const std::string &path) const =0
virtual std::string read_string_attribute(const std::string &path, const std::string &name) const =0
virtual long read_integer_attribute(const std::string &path, const std::string &name) const =0
virtual std::filesystem::path materialize(const std::string &path) const =0
virtual bool has_attribute(const std::string &path, const std::string &name) const =0
virtual std::string describe(const std::string &path) const =0
virtual std::vector< double > read_double_vector(const std::string &path) const =0
virtual bool exists(const std::string &path) const =0
virtual std::unique_ptr< std::istream > open(const std::string &path, bool binary) const =0
virtual Eigen::MatrixXd read_matrix(const std::string &path) const =0
virtual std::vector< long > read_long_vector(const std::string &path) const =0
virtual bool is_group(const std::string &path) const =0
Format-independent input used to construct a runtime Mesh.
Definition MeshData.hpp:12
static std::unique_ptr< Mesh > create(MeshData data, bool non_conforming=false)
Construct a runtime mesh from format-independent decoded data.
Definition Mesh.cpp:80
const io::ResourceIO & resources_
std::unique_ptr< Mesh > load_fem(const std::string &path, bool non_conforming=false) const
SurfaceMesh load_surface(const std::string &path) const
void validate_group(const std::string &group, const std::string &expected_type) const
static MeshData read_hybrid(std::istream &input, const std::string &description)
static MeshData read_msh(const std::filesystem::path &path)
static bool read_triangle_surface(const std::filesystem::path &path, SurfaceMeshData &result)
static SurfaceMeshData read_geogram_surface(const std::filesystem::path &path)
static SurfaceMeshData read_obj_surface(const std::filesystem::path &path)
static SurfaceMeshData read_msh_surface(const std::filesystem::path &path)
static MeshData read_geogram(const std::filesystem::path &path)
constexpr long MESH_SCHEMA_VERSION
The canonical typed mesh schema shared by HDF5 bundles and checkpoints.
void log_and_throw_error(const std::string &msg)
Definition Logger.cpp:73