Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
52dbb3d
Implement mesh deformation: add new file and adjust related code
LenaRadmer Nov 10, 2025
afee13d
implement sc_options for command line
LenaRadmer Nov 28, 2025
04959e2
fix doxygen warnings
LenaRadmer Mar 2, 2026
820f1e7
fix doxygen warnings again
LenaRadmer Mar 2, 2026
bd3f549
add doxygen file header for mesh deformation
LenaRadmer Mar 2, 2026
2d3af8f
Merge branch 'main' into feature-mesh_deformation
sandro-elsweijer Mar 3, 2026
fa8e214
add missing documentation for cad manager and iterators
LenaRadmer Mar 5, 2026
59a421e
Merge branch 'main' into feature-mesh_deformation
LenaRadmer Mar 8, 2026
f0b2182
add T8_ENABLE_OCC guards to t8_cad class
LenaRadmer Mar 9, 2026
f13f6fb
chore: restart CI tests
LenaRadmer Mar 10, 2026
6a8cafa
compile mesh deformation only if OCC is enabled
LenaRadmer Mar 11, 2026
1749a48
merge main and resolve conflicts
LenaRadmer Mar 11, 2026
7214e89
fix doxygen param name in t8_geometry_cad
LenaRadmer Mar 12, 2026
8105450
Merge branch 'main' into feature-mesh_deformation
LenaRadmer Mar 12, 2026
8b535bb
Merge branch 'main' into feature-mesh_deformation
LenaRadmer Mar 16, 2026
c11a6cc
move deformation logic to member functions
LenaRadmer Mar 16, 2026
3f4fe39
fix gp_Pnt.hxx error
LenaRadmer Mar 16, 2026
b20b36a
fix gp_Pnt.hxx error for example
LenaRadmer Mar 16, 2026
53a09de
fix MPI error for example
LenaRadmer Mar 16, 2026
c99f5b5
Merge branch 'main' into feature-mesh_deformation
LenaRadmer Mar 23, 2026
b82fa7e
address review comments for mesh deformation and clean up
LenaRadmer Mar 23, 2026
504e60d
clean up mesh deformation example
LenaRadmer Mar 26, 2026
78fd6b2
wrap t8_mesm_mesh_deformation.hxx include with OCC guards in the emsh…
LenaRadmer Mar 26, 2026
14a07c6
address review and add comments
LenaRadmer Mar 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions example/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ add_t8_example( NAME t8_cmesh_set_join_by_vertices SOURCES cmesh/t8_cmesh_s
add_t8_example( NAME t8_cmesh_geometry_examples SOURCES cmesh/t8_cmesh_geometry_examples.cxx )
add_t8_example( NAME t8_cmesh_create_partitioned SOURCES cmesh/t8_cmesh_create_partitioned.cxx )
add_t8_example( NAME t8_cmesh_hypercube_pad SOURCES cmesh/t8_cmesh_hypercube_pad.cxx )
add_t8_example( NAME t8_cmesh_mesh_deformation SOURCES cmesh/t8_cmesh_mesh_deformation.cxx )

add_t8_example( NAME t8_test_ghost SOURCES forest/t8_test_ghost.cxx )
add_t8_example( NAME t8_test_face_iterate SOURCES forest/t8_test_face_iterate.cxx )
Expand Down
156 changes: 156 additions & 0 deletions example/cmesh/t8_cmesh_mesh_deformation.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*
This file is part of t8code.
t8code is a C library to manage a collection (a forest) of multiple
connected adaptive space-trees of general element classes in parallel.

Copyright (C) 2026 the developers

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

t8code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

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

/** \file t8_cmesh_mesh_deformation.cxx
* This file implements an example for CAD-based mesh deformation.
*/

#include <t8_cmesh/t8_cmesh.hxx>
#include <t8_cmesh/t8_cmesh_vertex_connectivity/t8_cmesh_vertex_connectivity.hxx>
#include <t8_cmesh/t8_cmesh_io/t8_cmesh_readmshfile.h>
#include <t8_schemes/t8_default/t8_default.hxx>
#if T8CODE_ENABLE_OCC
#include <t8_cad/t8_cad_handle.hxx>
#include <t8_cmesh/t8_cmesh_mesh_deformation/t8_cmesh_mesh_deformation.hxx>
#endif /* T8CODE_ENABLE_OCC */
#include <t8_vtk/t8_vtk_writer.h>
#include <sc_options.h>

#include <iostream>
#include <string>
#include <vector>
#include <array>

int
main ([[maybe_unused]] int argc, [[maybe_unused]] char **argv)
{
#if T8CODE_ENABLE_OCC

char usage[BUFSIZ];
/* Brief help message. */
int sreturnA = snprintf (usage, BUFSIZ, "Usage:\t%s <OPTIONS>\n\t%s -h\t for a brief overview of all options.",
basename (argv[0]), basename (argv[0]));

char help[BUFSIZ];
/* Long help message. */
int sreturnB = snprintf (
help, BUFSIZ,
"Deform a mesh based on a msh file with the new CAD geometry.\n"
"Required arguments are the input mesh file, the deformation geometry file, and the mesh dimension.\n\n%s\n",
usage);

if (sreturnA > BUFSIZ || sreturnB > BUFSIZ) {
/* The usage string or help message was truncated */
/* Note: gcc >= 7.1 prints a warning if we
* do not check the return value of snprintf. */
t8_debugf ("WARNING: Truncated usage string and help message to '%s' and '%s'\n", usage, help);
}

/*
* Initialization.
*/

/* Initialize MPI. This has to happen before we initialize sc or t8code. */
int mpiret = sc_MPI_Init (&argc, &argv);

/* Error check the MPI return value. */
SC_CHECK_MPI (mpiret);

/* Initialize the sc library, has to happen before we initialize t8code. */
sc_init (sc_MPI_COMM_WORLD, 1, 1, NULL, SC_LP_ESSENTIAL);

/* Initialize t8code with log level SC_LP_ESSENTIAL. See sc.h for more info on the log levels. */
t8_init (SC_LP_ESSENTIAL);

int helpme = 0;
const char *msh_file = NULL;
const char *brep_file = NULL;
int dim, level;

/* Initialize command line argument parser. */
sc_options_t *opt = sc_options_new (argv[0]);
sc_options_add_switch (opt, 'h', "help", &helpme, "Display a short help message.");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should mention, that this feature only works if t8code is linked against occ

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

sc_options_add_string (opt, 'm', "mshfile", &msh_file, NULL, "File prefix of the input mesh file (without .msh)");
sc_options_add_string (opt, 'b', "brepfile", &brep_file, NULL,
"File prefix of the deformation geometry file (without .brep)");
sc_options_add_int (opt, 'd', "dimension", &dim, 0, "Dimension of the mesh (1, 2 or 3)");
sc_options_add_int (opt, 'l', "level", &level, 2, "Uniform refinement level for the input mesh. Default: 2");

int parsed = sc_options_parse (t8_get_package_id (), SC_LP_ERROR, opt, argc, argv);

if (helpme) {
t8_global_productionf ("%s\n", help);
sc_options_print_usage (t8_get_package_id (), SC_LP_ERROR, opt, NULL);
}
else if (msh_file == NULL || brep_file == NULL || dim == 0) {
t8_global_errorf ("ERROR: Missing required arguments: -m, -b, and -d are mandatory.\n\n");
sc_options_print_usage (t8_get_package_id (), SC_LP_ERROR, opt, NULL);
}
else if (dim < 1 || dim > 3) {
t8_global_errorf ("ERROR: Invalid mesh dimension: dim=%d. Dimension must be 1, 2 or 3.\n\n", dim);
sc_options_print_usage (t8_get_package_id (), SC_LP_ERROR, opt, NULL);
}
else if (parsed >= 0) {

/* We will use MPI_COMM_WORLD as a communicator. */
sc_MPI_Comm comm = sc_MPI_COMM_WORLD;

/* Create cmesh from msh. */
t8_cmesh_t cmesh = t8_cmesh_from_msh_file (msh_file, 0, comm, dim, 0, 1);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check beforehand if occ is linked, throw an error otherwise

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

t8_forest_t forest = t8_forest_new_uniform (cmesh, t8_scheme_new_default (), level, 0, comm);

/* Load CAD geometry from .brep file. */
auto cad = std::make_shared<t8_cad_handle> (brep_file);

/* Initialize the deformation object for the given mesh. */
t8_cmesh_mesh_deformation deformation (cmesh);

/* Calculate displacements. */
auto displacements = deformation.calculate_displacement_surface_vertices (cad.get ());

/* Write output. */
t8_forest_vtk_write_file (forest, "input_forest", 1, 1, 1, 1, 0, 0, NULL);

/* Apply displacements. */
deformation.apply_vertex_displacements (displacements, cad);

/* Write output. */
t8_forest_vtk_write_file (forest, "deformed_forest", 1, 1, 1, 1, 0, 0, NULL);

/* Cleanup. */
t8_forest_unref (&forest);

t8_global_productionf ("Mesh deformation completed.");
}

sc_options_destroy (opt);

sc_finalize ();
mpiret = sc_MPI_Finalize ();
SC_CHECK_MPI (mpiret);

#else /* T8CODE_ENABLE_OCC */
t8_global_errorf ("ERROR: This example requires OpenCASCADE support to be enabled in t8code.\n");
#endif /* T8CODE_ENABLE_OCC */

return 0;
}
5 changes: 5 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ if( T8CODE_ENABLE_OCC )
target_sources(T8 PRIVATE
t8_geometry/t8_geometry_implementations/t8_geometry_cad.cxx
t8_cad/t8_cad_handle.cxx
t8_cmesh/t8_cmesh_mesh_deformation/t8_cmesh_mesh_deformation.cxx
)
install( FILES
t8_geometry/t8_geometry_implementations/t8_geometry_cad.hxx
Expand All @@ -107,6 +108,10 @@ if( T8CODE_ENABLE_OCC )
t8_cad/t8_cad_handle.hxx
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/t8_cad
)
install( FILES
t8_cmesh/t8_cmesh_mesh_deformation/t8_cmesh_mesh_deformation.hxx
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/t8_cmesh/t8_cmesh_mesh_deformation/
)
endif()

if( T8CODE_BUILD_PEDANTIC )
Expand Down
1 change: 1 addition & 0 deletions src/t8_cad/t8_cad_handle.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class t8_cad_handle {
* \param [in] fileprefix Prefix of a .brep file from which to extract cad geometry.
*/
t8_cad_handle (const std::string_view fileprefix);

/**
* Constructor of the cad shape.
* The shape is initialized directly from an existing TopoDS_Shape.
Expand Down
190 changes: 190 additions & 0 deletions src/t8_cmesh/t8_cmesh_mesh_deformation/t8_cmesh_mesh_deformation.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/*
This file is part of t8code.
t8code is a C library to manage a collection (a forest) of multiple
connected adaptive space-trees of general element classes in parallel.

Copyright (C) 2026 the developers

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

t8code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

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

/** \file t8_cmesh_mesh_deformation.cxx
* This file implements the routines for CAD-based mesh deformation.
*/

#include <unordered_map>
#include <t8_cmesh/t8_cmesh_vertex_connectivity/t8_cmesh_vertex_connectivity.hxx>
#include <t8_cad/t8_cad_handle.hxx>
#include <t8_cmesh/t8_cmesh.h>
#include <t8_cmesh/t8_cmesh.hxx>
#include <t8_cmesh/t8_cmesh_mesh_deformation/t8_cmesh_mesh_deformation.hxx>
#include <t8_geometry/t8_geometry_handler.hxx>
#include <t8_geometry/t8_geometry_implementations/t8_geometry_cad.hxx>

std::unordered_map<t8_gloidx_t, t8_3D_vec>
t8_cmesh_mesh_deformation::calculate_displacement_surface_vertices (const t8_cad_handle *cad)
{
T8_ASSERT (t8_cmesh_is_committed (associated_cmesh));

const int mesh_dimension = t8_cmesh_get_dimension (associated_cmesh);

/* Map from global vertex id -> displacement vector. */
std::unordered_map<t8_gloidx_t, t8_3D_vec> displacements;

for (const auto &global_vertex : *(associated_cmesh->vertex_connectivity)) {

/* Get the list of all trees associated with the vertex. */
const auto &tree_list = global_vertex.second;
const t8_gloidx_t global_vertex_id = global_vertex.first;

/* Get the first tree and the local corner index of the vertex in the tree. */
const auto &first_tree = tree_list.front ();
const t8_locidx_t first_tree_id = first_tree.first;
const int local_corner_index = first_tree.second;

/* Get the first tree as a reference. */
const int *first_tree_geom_attribute = static_cast<const int *> (t8_cmesh_get_attribute (
associated_cmesh, t8_get_package_id (), T8_CMESH_NODE_GEOMETRY_ATTRIBUTE_KEY, first_tree_id));

/* Check if the geometry attribute is available for this tree. */
if (first_tree_geom_attribute == nullptr) {
t8_errorf ("Error: Geometry attribute missing for tree %d\n.", first_tree_id);
SC_ABORTF ("Geometry attribute is missing.");
}

const int first_tree_entity_dim = first_tree_geom_attribute[2 * tree_list[0].second];
const int first_tree_entity_tag = first_tree_geom_attribute[2 * tree_list[0].second + 1];

/* Check if all trees sharing this vertex have consistent geometry attributes. */
#if T8_ENABLE_DEBUG
/* Iterate over all trees and compare to the reference tree. */
for (const auto &[tree_id, local_corner_index] : tree_list) {

const int *geom_attribute = static_cast<const int *> (
t8_cmesh_get_attribute (associated_cmesh, t8_get_package_id (), T8_CMESH_NODE_GEOMETRY_ATTRIBUTE_KEY, tree_id));

const int entity_dim = geom_attribute[2 * local_corner_index];
const int entity_tag = geom_attribute[2 * local_corner_index + 1];

/* Check if the attribute of the vertex is the same in all trees. */
if (!(entity_dim == first_tree_entity_dim && entity_tag == first_tree_entity_tag)) {
t8_errorf (
"Error: Inconsistent entity info for global vertex %li: tree %d: dim=%d tag=%d, expected dim=%d tag=%d\n",
global_vertex_id, tree_id, entity_dim, entity_tag, first_tree_entity_dim, first_tree_entity_tag);
SC_ABORTF ("Inconsistency in vertex info.\n");
}
}
#endif /* T8_ENABLE_DEBUG */

/* Check if this vertex is a boundary node. */
if (first_tree_entity_dim < mesh_dimension && first_tree_entity_dim >= 0) {

/* Get the pointer to the array of (u,v)-parameters for the CAD geometry. */
const double *uv_attribute = (const double *) t8_cmesh_get_attribute (
associated_cmesh, t8_get_package_id (), T8_CMESH_NODE_PARAMETERS_ATTRIBUTE_KEY, first_tree_id);

/* Check if the (u,v)-parameters are available. */
if (uv_attribute == nullptr) {
t8_errorf ("Error: (u,v)-parameters are missing for tree %d\n.", first_tree_id);
SC_ABORT ("(u,v)-parameters are missing.");
}
/* Get the (u,v)-parameter of the vertex. */
const double *uv_parameter = &uv_attribute[2 * local_corner_index];

/* Get the pointer to the coordinate array as it was before the deformation. */
const double *old_coords = (const double *) t8_cmesh_get_attribute (
associated_cmesh, t8_get_package_id (), T8_CMESH_VERTICES_ATTRIBUTE_KEY, first_tree_id);

/* Check if the coordinates are available. */
if (old_coords == nullptr) {
t8_errorf ("Error: Coordinates attribute missing for tree %d\n.", first_tree_id);
SC_ABORTF ("Vertex coordinates are missing.");
}

gp_Pnt new_coords;

/* Find the new coordinates of the vertex in the cad file, based on the geometry its lying on. */
switch (first_tree_entity_dim) {
case 0: {
new_coords = cad->get_cad_point (first_tree_entity_tag);
break;
}
case 1: {
Handle_Geom_Curve curve = cad->get_cad_curve (first_tree_entity_tag);
curve->D0 (uv_parameter[0], new_coords);
break;
}
case 2: {
Handle_Geom_Surface surface = cad->get_cad_surface (first_tree_entity_tag);
surface->D0 (uv_parameter[0], uv_parameter[1], new_coords);
break;
}
default:
SC_ABORT_NOT_REACHED ();
}

/* Get the old coordinates before the deformation. */
const double old_x = old_coords[3 * local_corner_index + 0];
const double old_y = old_coords[3 * local_corner_index + 1];
const double old_z = old_coords[3 * local_corner_index + 2];

/* Calculate the displacement of the vertex which should be then done in the deformation. */
displacements[global_vertex_id] = { new_coords.X () - old_x, new_coords.Y () - old_y, new_coords.Z () - old_z };
}
}
return displacements;
}

void
t8_cmesh_mesh_deformation::apply_vertex_displacements (const std::unordered_map<t8_gloidx_t, t8_3D_vec> &displacements,
std::shared_ptr<t8_cad_handle> cad)
{
T8_ASSERT (t8_cmesh_is_committed (associated_cmesh));

/* Iterate over all vertices in the displacement map. */
for (const auto &[global_vertex, displacement] : displacements) {

/* Get the list of trees where this vertex exists. */
const auto &tree_list = associated_cmesh->vertex_connectivity->get_tree_list_of_vertex (global_vertex);

/* Update the vertex coordinates in each tree. */
for (const auto &[tree_id, local_vertex_index] : tree_list) {

/* Get the vertex coordinates of the current tree. */
double *tree_vertex_coords = (double *) t8_cmesh_get_attribute (associated_cmesh, t8_get_package_id (),
T8_CMESH_VERTICES_ATTRIBUTE_KEY, tree_id);

/* Check if the coordinates are available. */
if (tree_vertex_coords != nullptr) {
/* Update the coordinates of the vertex. */
for (int coord_index = 0; coord_index < 3; ++coord_index) {
tree_vertex_coords[3 * local_vertex_index + coord_index] += displacement[coord_index];
}
}
}
}

/* Update the cad geometry. */
t8_geometry_handler *geometry_handler = associated_cmesh->geometry_handler;
T8_ASSERT (geometry_handler != nullptr);

for (auto geom = geometry_handler->begin (); geom != geometry_handler->end (); ++geom) {
if (geom->second->t8_geom_get_type () == T8_GEOMETRY_TYPE_CAD) {
t8_geometry_cad *cad_geom = static_cast<t8_geometry_cad *> (geom->second.get ());
cad_geom->update_cad_handle (cad);
break;
}
}
}
Loading
Loading