28 const std::string &root_path,
29 const bool non_conforming)
34 if (j_mesh[
"extract"].get<std::string>() !=
"volume")
42 if (j_mesh[
"advanced"][
"normalize_mesh"])
48 mesh->bounding_box(bbox[0], bbox[1]);
50 const std::string unit = j_mesh[
"unit"];
51 double unit_scale = 1;
60 j_mesh[
"transformation"],
61 (bbox[1] - bbox[0]).cwiseAbs().transpose(),
63 mesh->apply_affine_transformation(A, b);
66 mesh->bounding_box(bbox[0], bbox[1]);
69 std::vector<std::shared_ptr<Selection>> surface_selections =
74 const int n_refs = j_mesh[
"n_refs"];
75 const double refinement_location = j_mesh[
"advanced"][
"refinement_location"];
86 if (mesh->has_boundary_ids() && surface_selections.empty())
87 log_and_throw_error(
"Unable to refine a mesh with stored surface selections; provide an explicit surface_selection to recompute them after refinement.");
90 assert(mesh->n_elements() > 0);
91 const int uniform_value = mesh->get_body_id(0);
92 for (
int i = 1; i < mesh->n_elements(); ++i)
93 if (mesh->get_body_id(i) != uniform_value)
94 log_and_throw_error(
"Unable to apply stored nonuniform volume_selection because n_refs={} > 0!", n_refs);
96 logger().info(
"Performing global h-refinement with {} refinements", n_refs);
97 mesh->refine(n_refs, refinement_location);
98 mesh->set_body_ids(std::vector<int>(mesh->n_elements(), uniform_value));
103 if (j_mesh[
"advanced"][
"min_component"].get<int>() != -1)
135 if (j_mesh[
"advanced"][
"force_linear_geometry"].get<bool>())
149 const std::vector<std::shared_ptr<Selection>> node_selections =
152 if (!node_selections.empty())
154 bool boundary_only =
true;
155 for (
const auto &selection : node_selections)
157 if (!selection->boundary_only())
159 boundary_only =
false;
164 mesh->compute_node_ids([&](
const size_t n_id,
const RowVectorNd &p,
bool is_boundary) {
165 if (boundary_only && !is_boundary)
168 const std::vector<int> tmp = {int(n_id)};
169 for (
const auto &selection : node_selections)
171 if (selection->boundary_only() && !is_boundary)
174 if (selection->inside(n_id, tmp, p))
175 return selection->id(n_id, tmp, p);
177 return std::numeric_limits<int>::max();
181 if (!j_mesh[
"curve_selection"].is_null())
186 if (!surface_selections.empty())
188 bool boundary_only =
true;
189 for (
const auto &selection : surface_selections)
191 if (!selection->boundary_only())
193 boundary_only =
false;
198 mesh->compute_boundary_ids([&](
const size_t p_id,
const std::vector<int> &vs,
const RowVectorNd &p,
bool is_boundary) {
199 if (boundary_only && !is_boundary)
202 for (
const auto &selection : surface_selections)
204 if (selection->boundary_only() && !is_boundary)
207 if (selection->inside(p_id, vs, p))
208 return selection->id(p_id, vs, p);
210 return std::numeric_limits<int>::max();
217 const json volume_selection = j_mesh[
"volume_selection"];
218 if (volume_selection.is_object()
219 && volume_selection.size() == 1
220 && volume_selection.contains(
"id_offset"))
222 const int id_offset = volume_selection[
"id_offset"].get<
int>();
225 const int n_body_ids = mesh->n_elements();
226 std::vector<int> body_ids(n_body_ids);
227 for (
int i = 0; i < n_body_ids; ++i)
228 body_ids[i] = mesh->get_body_id(i) + id_offset;
229 mesh->set_body_ids(body_ids);
235 std::vector<std::shared_ptr<Selection>> volume_selections =
239 if (mesh->has_body_ids())
240 volume_selections.push_back(std::make_shared<SpecifiedSelection>(mesh->get_body_ids()));
242 mesh->compute_body_ids([&](
const size_t cell_id,
const std::vector<int> &vs,
const RowVectorNd &p) ->
int {
243 for (
const auto &selection : volume_selections)
246 if (selection->inside(cell_id, vs, p))
247 return selection->id(cell_id, vs, p);
262 const json &geometry,
263 const std::string &root_path,
264 const std::vector<std::string> &_names,
265 const std::vector<Eigen::MatrixXd> &_vertices,
266 const std::vector<Eigen::MatrixXi> &_cells,
267 const bool non_conforming)
287 assert(_names.empty());
288 assert(_vertices.empty());
289 assert(_cells.empty());
293 if (geometry.empty())
300 std::unique_ptr<Mesh> mesh =
nullptr;
302 for (
const json &geometry : geometries)
304 if (!geometry[
"enabled"].get<bool>() || geometry[
"is_obstacle"].get<
bool>())
307 if (geometry[
"type"] !=
"mesh" && geometry[
"type"] !=
"mesh_array")
310 const std::unique_ptr<Mesh> tmp_mesh =
read_fem_mesh(units, geometry, root_path, non_conforming);
313 mesh = tmp_mesh->copy();
315 mesh->append(tmp_mesh);
317 if (geometry[
"type"] ==
"mesh_array")
320 tmp_mesh->bounding_box(bbox[0], bbox[1]);
322 const long dim = tmp_mesh->dimension();
323 const bool is_offset_relative = geometry[
"array"][
"relative"];
324 const double offset = geometry[
"array"][
"offset"];
325 const VectorNd dimensions = (bbox[1] - bbox[0]);
326 const VectorNi size = geometry[
"array"][
"size"];
328 for (
int i = 0; i < size[0]; ++i)
330 for (
int j = 0; j < size[1]; ++j)
332 for (
int k = 0; k < (size.size() > 2 ? size[2] : 1); ++k)
334 if (i == 0 && j == 0 && k == 0)
337 RowVectorNd translation = offset * Eigen::RowVector3d(i, j, k).head(dim);
338 if (is_offset_relative)
339 translation.array() *= dimensions.array();
341 const std::unique_ptr<Mesh> copy_mesh = tmp_mesh->copy();
342 copy_mesh->apply_affine_transformation(MatrixNd::Identity(dim, dim), translation);
343 mesh->append(copy_mesh);
360 const std::string &root_path,
362 Eigen::MatrixXd &vertices,
363 Eigen::VectorXi &codim_vertices,
364 Eigen::MatrixXi &codim_edges,
365 Eigen::MatrixXi &
faces)
370 const std::string mesh_path =
resolve_path(j_mesh[
"mesh"], root_path);
373 mesh_path, vertices, codim_vertices, codim_edges,
faces);
377 throw std::runtime_error(fmt::format(
"Unable to read mesh: {}", mesh_path));
379 const int prev_dim = vertices.cols();
380 vertices.conservativeResize(vertices.rows(), dim);
382 vertices.rightCols(dim - prev_dim).setZero();
387 const std::string unit = j_mesh[
"unit"];
388 double unit_scale = 1;
392 const VectorNd mesh_dimensions = (vertices.colwise().maxCoeff() - vertices.colwise().minCoeff()).cwiseAbs();
396 vertices = vertices * A.transpose();
397 vertices.rowwise() += b.transpose();
400 std::string extract = j_mesh[
"extract"];
402 if (extract ==
"volume")
405 if (extract ==
"points")
408 codim_edges.resize(0, 0);
410 codim_vertices.resize(vertices.rows());
411 for (
int i = 0; i < codim_vertices.size(); ++i)
412 codim_vertices[i] = i;
414 else if (extract ==
"edges" &&
faces.size() != 0)
417 Eigen::MatrixXi edges;
418 igl::edges(
faces, edges);
420 codim_edges.conservativeResize(codim_edges.rows() + edges.rows(), 2);
421 codim_edges.bottomRows(edges.rows()) = edges;
423 else if (extract ==
"surface" && dim == 2 &&
faces.size() != 0)
426 Eigen::MatrixXi boundary_edges;
427 igl::boundary_facets(
faces, boundary_edges);
428 codim_edges.conservativeResize(codim_edges.rows() + boundary_edges.rows(), 2);
429 codim_edges.bottomRows(boundary_edges.rows()) = boundary_edges;
434 else if (extract ==
"volume")
440 if (j_mesh[
"n_refs"].get<int>() != 0)
442 if (
faces.size() != 0)
445 const int n_refs = j_mesh[
"n_refs"];
446 const double refinement_location = j_mesh[
"advanced"][
"refinement_location"];
447 for (
int i = 0; i < n_refs; i++)
449 const size_t n_vertices = vertices.rows();
450 const size_t n_edges = codim_edges.rows();
451 vertices.conservativeResize(n_vertices + n_edges, vertices.cols());
452 codim_edges.conservativeResize(2 * n_edges, codim_edges.cols());
453 for (
size_t ei = 0; ei < n_edges; ei++)
455 const int v0i = codim_edges(ei, 0);
456 const int v1i = codim_edges(ei, 1);
457 const int v2i = n_vertices + ei;
458 vertices.row(v2i) = (vertices.row(v1i) - vertices.row(v0i)) * refinement_location + vertices.row(v0i);
459 codim_edges.row(ei) << v0i, v2i;
460 codim_edges.row(n_edges + ei) << v2i, v1i;
470 const json &geometry,
471 const std::vector<json> &displacements,
472 const std::vector<json> &dirichlets,
473 const std::string &root_path,
475 const std::vector<std::string> &_names,
476 const std::vector<Eigen::MatrixXd> &_vertices,
477 const std::vector<Eigen::MatrixXi> &_cells,
478 const bool non_conforming)
498 assert(_names.empty());
499 assert(_vertices.empty());
500 assert(_cells.empty());
504 if (geometry.empty())
509 for (
const json &geometry : geometries)
512 if (!geometry[
"is_obstacle"].get<bool>())
515 if (!geometry[
"enabled"].get<bool>())
518 if (geometry[
"type"] ==
"mesh" || geometry[
"type"] ==
"mesh_array")
520 Eigen::MatrixXd vertices;
521 Eigen::VectorXi codim_vertices;
522 Eigen::MatrixXi codim_edges;
523 Eigen::MatrixXi
faces;
525 geometry, root_path, dim, vertices, codim_vertices,
528 if (geometry[
"type"] ==
"mesh_array")
530 const Selection::BBox bbox{{vertices.colwise().minCoeff(), vertices.colwise().maxCoeff()}};
532 const bool is_offset_relative = geometry[
"array"][
"relative"];
533 const double offset = geometry[
"array"][
"offset"];
534 const VectorNd dimensions = (bbox[1] - bbox[0]);
535 const VectorNi size = geometry[
"array"][
"size"];
537 const int N = size.head(dim).prod();
538 const int nV = vertices.rows(), nCV = codim_vertices.rows(), nCE = codim_edges.rows(), nF =
faces.rows();
540 vertices.conservativeResize(N * nV, Eigen::NoChange);
541 codim_vertices.conservativeResize(N * nCV, Eigen::NoChange);
542 codim_edges.conservativeResize(N * nCE, Eigen::NoChange);
543 faces.conservativeResize(N * nF, Eigen::NoChange);
545 for (
int i = 0; i < size[0]; ++i)
547 for (
int j = 0; j < size[1]; ++j)
549 for (
int k = 0; k < (size.size() > 2 ? size[2] : 1); ++k)
551 RowVectorNd translation = offset * Eigen::RowVector3d(i, j, k).head(vertices.cols());
552 if (is_offset_relative)
553 translation.array() *= dimensions.array();
555 int n = i * size[1] + j;
561 vertices.middleRows(n * nV, nV) = vertices.topRows(nV).rowwise() + translation;
563 codim_vertices.segment(n * nV, nV) = codim_vertices.head(nV).array() + n * nV;
565 codim_edges.middleRows(n * nCE, nCE) = codim_edges.topRows(nCE).array() + n * nV;
567 faces.middleRows(n * nF, nF) =
faces.topRows(nF).array() + n * nV;
573 json displacement =
"{\"value\":[0, 0, 0]}"_json;
576 if (!geometry[
"surface_selection"].is_number())
579 const int id = geometry[
"surface_selection"];
580 for (
const json &disp : dirichlets)
582 if ((disp[
"id"].is_string() && disp[
"id"].get<std::string>() ==
"all")
583 || (disp[
"id"].is_number_integer() && disp[
"id"].get<
int>() ==
id))
588 else if (disp[
"id"].is_array())
590 for (
const json &disp_id : disp[
"id"])
592 assert(disp_id.is_number_integer());
593 if (disp_id.get<
int>() ==
id)
601 for (
const json &disp : displacements)
603 if ((disp[
"id"].is_string() && disp[
"id"].get<std::string>() ==
"all")
604 || (disp[
"id"].is_number_integer() && disp[
"id"].get<
int>() ==
id))
609 else if (disp[
"id"].is_array())
611 for (
const json &disp_id : disp[
"id"])
613 assert(disp_id.is_number_integer());
614 if (disp_id.get<
int>() ==
id)
625 vertices, codim_vertices, codim_edges,
faces, displacement, root_path);
627 else if (geometry[
"type"] ==
"plane")
629 obstacle.
append_plane(geometry[
"point"], geometry[
"normal"]);
631 else if (geometry[
"type"] ==
"ground")
633 VectorNd gravity = VectorNd::Zero(dim);
635 const double height = geometry[
"height"];
636 assert(gravity.norm() != 0);
637 const VectorNd normal = -gravity.normalized();
638 const VectorNd point = height * normal;
641 else if (geometry[
"type"] ==
"mesh_sequence")
643 namespace fs = std::filesystem;
644 std::vector<fs::path> mesh_files;
645 if (geometry[
"mesh_sequence"].is_array())
647 mesh_files = geometry[
"mesh_sequence"].get<std::vector<fs::path>>();
651 assert(geometry[
"mesh_sequence"].is_string());
652 const fs::path meshes(
resolve_path(geometry[
"mesh_sequence"], root_path));
654 if (fs::is_directory(meshes))
656 for (
const auto &entry : std::filesystem::directory_iterator(meshes))
658 if (entry.is_regular_file())
659 mesh_files.push_back(entry.path());
664 mesh_files = glob::rglob(meshes.string());
667 std::sort(mesh_files.begin(), mesh_files.end(), [](
const fs::path &p1,
const fs::path &p2) {
668 return strnatcmp(p1.string().c_str(), p2.string().c_str()) < 0;
672 std::vector<Eigen::MatrixXd> vertices(mesh_files.size());
673 Eigen::VectorXi codim_vertices;
674 Eigen::MatrixXi codim_edges;
675 Eigen::MatrixXi
faces;
677 for (
int i = 0; i < mesh_files.size(); ++i)
679 json jmesh = geometry;
680 jmesh[
"mesh"] = mesh_files[i];
683 Eigen::VectorXi tmp_codim_vertices;
684 Eigen::MatrixXi tmp_codim_edges;
685 Eigen::MatrixXi tmp_faces;
687 jmesh, root_path, dim, vertices[i],
688 tmp_codim_vertices, tmp_codim_edges, tmp_faces);
691 codim_vertices = tmp_codim_vertices;
692 codim_edges = tmp_codim_edges;
697 assert((codim_vertices.array() == tmp_codim_vertices.array()).all());
698 assert((codim_edges.array() == tmp_codim_edges.array()).all());
699 assert((
faces.array() == tmp_faces.array()).all());
704 vertices, codim_vertices, codim_edges,
faces, geometry[
"fps"]);
719 const double unit_scale,
720 const json &transform,
725 const int dim = mesh_dimensions.size();
732 if (transform[
"dimensions"].is_array())
735 (mesh_dimensions.array() == 0).select(1, mesh_dimensions);
737 scale = transform[
"dimensions"];
738 const int scale_size = scale.size();
739 scale.conservativeResize(dim);
740 if (scale_size < dim)
741 scale.tail(dim - scale_size).setZero();
743 scale.array() /= modified_dimensions.array();
745 else if (transform[
"scale"].is_number())
747 scale.setConstant(dim, transform[
"scale"].get<double>());
751 assert(transform[
"scale"].is_array());
752 scale = transform[
"scale"];
753 const int scale_size = scale.size();
754 scale.conservativeResize(dim);
755 if (scale_size < dim)
756 scale.tail(dim - scale_size).setZero();
762 A = (unit_scale * scale).asDiagonal();
770 MatrixNd R = MatrixNd::Identity(dim, dim);
771 if (!transform[
"rotation"].is_null())
775 if (transform[
"rotation"].is_number())
776 R = Eigen::Rotation2Dd(
deg2rad(transform[
"rotation"].get<double>()))
778 else if (!transform[
"rotation"].is_array() || !transform[
"rotation"].empty())
793 b = transform[
"translation"];
794 const int translation_size = b.size();
795 b.conservativeResize(dim);
796 if (translation_size < dim)
797 b.tail(dim - translation_size).setZero();