From c78fb0ce852b3d8b9d1555be2d73373f1d82a56f Mon Sep 17 00:00:00 2001 From: mhoeijm Date: Thu, 10 Oct 2024 09:23:12 +0200 Subject: [PATCH 01/14] maint: fix writing cap meshes --- src/ansys/heart/preprocessor/mesher.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ansys/heart/preprocessor/mesher.py b/src/ansys/heart/preprocessor/mesher.py index 4764339e3..6da463088 100644 --- a/src/ansys/heart/preprocessor/mesher.py +++ b/src/ansys/heart/preprocessor/mesher.py @@ -246,7 +246,7 @@ def mesh_fluid_cavities( for c in caps: filename = os.path.join(work_dir_meshing, c.name.lower() + ".stl") - c.save(filename) + c._mesh.save(filename) add_solid_name_to_stl(filename, c.name.lower(), file_type="binary") session = _get_fluent_meshing_session(work_dir_meshing) @@ -263,15 +263,16 @@ def mesh_fluid_cavities( session.tui.objects.merge("'(*)", "model-fluid") # fix duplicate nodes - session.tui.diagnostics.face_connectivity.fix_free_faces("objects '(*)") + session.tui.diagnostics.face_connectivity.fix_free_faces('objects', "'(*)") # set size field session.tui.size_functions.set_global_controls(1, 1, 1.2) session.tui.scoped_sizing.compute("yes") # remesh all caps + cap_names = " ".join([cap.name for cap in caps]) if remesh_caps: - session.tui.boundary.remesh.remesh_constant_size("(cap_*)", "()", 40, 20, 1, "yes") + session.tui.boundary.remesh.remesh_constant_size(f"'({cap_names})", '()', 40, 20, 1, "yes") # convert to mesh object session.tui.objects.change_object_type("(*)", "mesh", "yes") From e807849086cd74861259cf4db895fcac89c204d6 Mon Sep 17 00:00:00 2001 From: mhoeijm Date: Fri, 11 Oct 2024 11:31:26 +0200 Subject: [PATCH 02/14] feat: add option to mesh volume(s) --- src/ansys/heart/core/models.py | 3 +- src/ansys/heart/preprocessor/mesher.py | 93 +++++++++++++------------- 2 files changed, 49 insertions(+), 47 deletions(-) diff --git a/src/ansys/heart/core/models.py b/src/ansys/heart/core/models.py index 75cbcca4c..f4092816a 100644 --- a/src/ansys/heart/core/models.py +++ b/src/ansys/heart/core/models.py @@ -1371,6 +1371,7 @@ def _assign_cavities_to_parts(self) -> None: # create cap: NOTE, mostly for compatibility. Could simplify further cap_mesh = SurfaceMesh(patch.clean(), name=cap_name, id=ii + idoffset) + cap_mesh.cell_data["_cap_id"] = float(ii + idoffset) # Add cap to main mesh. self.mesh.add_surface(cap_mesh, id=cap_mesh.id, name=cap_name) @@ -1390,7 +1391,7 @@ def _assign_cavities_to_parts(self) -> None: # ! and we can only use this for getting some meta-data such as volume # ! also it is not updated dynamically. # merge patches into cavity surface. - surface.cell_data["_cap_id"] = 0 + surface.cell_data["_cap_id"] = float(0) surface_cavity = SurfaceMesh(pv.merge([surface] + [cap._mesh for cap in part.caps])) surface_cavity.name = surface.name surface_cavity.id = surface.id diff --git a/src/ansys/heart/preprocessor/mesher.py b/src/ansys/heart/preprocessor/mesher.py index 6da463088..fd0038d97 100644 --- a/src/ansys/heart/preprocessor/mesher.py +++ b/src/ansys/heart/preprocessor/mesher.py @@ -201,30 +201,29 @@ def _wrap_part(session: MeshingSession, boundary_names: list, wrapped_part_name: return wrapped_face_zone_names -def mesh_fluid_cavities( - fluid_boundaries: List[SurfaceMesh], - caps: List[SurfaceMesh], - workdir: str, - remesh_caps: bool = True, -) -> FluentMesh: - """Mesh the fluid cavities. +def _mesh_fluid_cavities1( + cavity_boundaries: list[SurfaceMesh], workdir: str, mesh_size: float = 1.0 +) -> pv.UnstructuredGrid: + """Mesh the caps of each fluid cavity with uniformly. Parameters ---------- - fluid_boundaries : List[SurfaceMesh] - List of fluid boundaries used for meshing. - caps : List[SurfaceMesh] - List of caps that close each of the cavities. + cavity_boundaries : List[SurfaceMesh] + List of cavity boundaries used for meshing. workdir : str Working directory - remesh_caps : bool, optional - Flag indicating whether to remesh the caps, by default True + mesh_size : float + Mesh size Returns ------- - Path - Path to the .msh.h5 volume mesh. + pv.UnstructuredGrid + Unstructured grid with fluid mesh. """ + # Use the following tgrid api utility for each cavity: + # Consequently need to associate each "patch" with a cap. E.g. by centroid? + # + # (tgapi-util-fill-holes-in-face-zone-list '(face-zone-list) max-hole-edges) if _uses_container: mounted_volume = pyfluent.EXAMPLES_PATH work_dir_meshing = os.path.join(mounted_volume, "tmp_meshing-fluid") @@ -239,16 +238,11 @@ def mesh_fluid_cavities( os.remove(f) # write all boundaries - for b in fluid_boundaries: + for b in cavity_boundaries: filename = os.path.join(work_dir_meshing, b.name.lower() + ".stl") b.save(filename) add_solid_name_to_stl(filename, b.name.lower(), file_type="binary") - for c in caps: - filename = os.path.join(work_dir_meshing, c.name.lower() + ".stl") - c._mesh.save(filename) - add_solid_name_to_stl(filename, c.name.lower(), file_type="binary") - session = _get_fluent_meshing_session(work_dir_meshing) # import all stls @@ -257,44 +251,51 @@ def mesh_fluid_cavities( # will be in /mnt/pyfluent. So need to use relative paths # or replace dirname by /mnt/pyfluent as prefix work_dir_meshing = "/mnt/pyfluent/meshing" - session.tui.file.import_.cad(f"no {work_dir_meshing} *.stl") - # merge objects - session.tui.objects.merge("'(*)", "model-fluid") - - # fix duplicate nodes - session.tui.diagnostics.face_connectivity.fix_free_faces('objects', "'(*)") + session.tui.file.import_.cad(f"no {work_dir_meshing} *.stl") # set size field - session.tui.size_functions.set_global_controls(1, 1, 1.2) + session.tui.size_functions.set_global_controls(mesh_size, mesh_size, 1.2) session.tui.scoped_sizing.compute("yes") - # remesh all caps - cap_names = " ".join([cap.name for cap in caps]) - if remesh_caps: - session.tui.boundary.remesh.remesh_constant_size(f"'({cap_names})", '()', 40, 20, 1, "yes") - - # convert to mesh object - session.tui.objects.change_object_type("(*)", "mesh", "yes") - - # compute volumetric regions - session.tui.objects.volumetric_regions.compute("model-fluid") - - # mesh volume - session.tui.mesh.auto_mesh("model-fluid") + # create caps + # (tgapi-util-fill-holes-in-face-zone-list '(face-zone-list) max-hole-edges) + # convert all to mesh object + session.tui.objects.change_object_type("'(*)", "mesh", "yes") + for cavity_boundary in cavity_boundaries: + cavity_name = "-".join(cavity_boundary.name.split()).lower() + session.scheme_eval.scheme_eval( + f"(tgapi-util-fill-holes-in-face-zone-list '({cavity_name}) 1000)" + ) + # merge unreferenced with cavity + # (get-unreferenced-face-zones) + patch_ids = session.scheme_eval.scheme_eval("(get-unreferenced-face-zones)") + session.tui.objects.create( + f"{cavity_name}-patches", + "fluid", + 3, + "({0})".format(" ".join([str(patch_id) for patch_id in patch_ids])), + "()", + "mesh", + "yes", + ) + # merge objects + session.tui.objects.merge(f"'({cavity_name}*)") + session.tui.objects.volumetric_regions.compute(cavity_name) + session.tui.mesh.auto_mesh(cavity_name, "yes", "pyr", "tet", "yes") - # clean up session.tui.objects.delete_all_geom() - session.tui.objects.delete_unreferenced_faces_and_edges() - # write file_path_mesh = os.path.join(workdir, "fluid-mesh.msh.h5") session.tui.file.write_mesh(file_path_mesh) + session.exit() + + # # write mesh = FluentMesh(file_path_mesh) - mesh.load_mesh() + mesh.load_mesh(reconstruct_tetrahedrons=True) - return mesh + return mesh._to_vtk(add_cells=True, add_faces=True) def mesh_from_manifold_input_model( From 6cb372111934c454953853569dddbc57c8dee26f Mon Sep 17 00:00:00 2001 From: mhoeijm Date: Fri, 11 Oct 2024 13:36:33 +0200 Subject: [PATCH 03/14] feat: improve volume meshing --- src/ansys/heart/preprocessor/mesher.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/ansys/heart/preprocessor/mesher.py b/src/ansys/heart/preprocessor/mesher.py index fd0038d97..65c5deca7 100644 --- a/src/ansys/heart/preprocessor/mesher.py +++ b/src/ansys/heart/preprocessor/mesher.py @@ -281,8 +281,24 @@ def _mesh_fluid_cavities1( ) # merge objects session.tui.objects.merge(f"'({cavity_name}*)") - session.tui.objects.volumetric_regions.compute(cavity_name) - session.tui.mesh.auto_mesh(cavity_name, "yes", "pyr", "tet", "yes") + + # find overlapping pairs + # (tgapi-util-get-overlapping-face-zones "*patch*" 0.1 0.1 ) + overlapping_pairs = session.scheme_eval.scheme_eval( + '(tgapi-util-get-overlapping-face-zones "*patch*" 0.1 0.1 )' + ) + for pair in overlapping_pairs: + # remove first from pair + session.tui.boundary.manage.delete(f"'({pair[0]})", "yes") + + # merge mesh objects + session.tui.objects.merge("'(*)", "fluid-zones") + + # merge nodes + session.tui.boundary.merge_nodes("'(*)", "'(*)", "no", "no , ,") + # mesh volumes + session.tui.objects.volumetric_regions.compute("fluid-zones", "no") + session.tui.mesh.auto_mesh("fluid-zones", "yes", "pyr", "tet", "yes") session.tui.objects.delete_all_geom() From ace5190fead194554ddd6cc7620f75b9a6cdad32 Mon Sep 17 00:00:00 2001 From: mhoeijm Date: Fri, 11 Oct 2024 13:37:01 +0200 Subject: [PATCH 04/14] feat: rename method --- src/ansys/heart/preprocessor/mesher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/heart/preprocessor/mesher.py b/src/ansys/heart/preprocessor/mesher.py index 65c5deca7..a8137aab4 100644 --- a/src/ansys/heart/preprocessor/mesher.py +++ b/src/ansys/heart/preprocessor/mesher.py @@ -201,7 +201,7 @@ def _wrap_part(session: MeshingSession, boundary_names: list, wrapped_part_name: return wrapped_face_zone_names -def _mesh_fluid_cavities1( +def _mesh_fluid_cavities( cavity_boundaries: list[SurfaceMesh], workdir: str, mesh_size: float = 1.0 ) -> pv.UnstructuredGrid: """Mesh the caps of each fluid cavity with uniformly. From 815fc116c03d1180bdb15aeefd4e4f9decfd3cd8 Mon Sep 17 00:00:00 2001 From: mhoeijm Date: Fri, 11 Oct 2024 13:37:36 +0200 Subject: [PATCH 05/14] feat: move method to end --- src/ansys/heart/preprocessor/mesher.py | 226 ++++++++++++------------- 1 file changed, 113 insertions(+), 113 deletions(-) diff --git a/src/ansys/heart/preprocessor/mesher.py b/src/ansys/heart/preprocessor/mesher.py index a8137aab4..40a91325f 100644 --- a/src/ansys/heart/preprocessor/mesher.py +++ b/src/ansys/heart/preprocessor/mesher.py @@ -201,119 +201,6 @@ def _wrap_part(session: MeshingSession, boundary_names: list, wrapped_part_name: return wrapped_face_zone_names -def _mesh_fluid_cavities( - cavity_boundaries: list[SurfaceMesh], workdir: str, mesh_size: float = 1.0 -) -> pv.UnstructuredGrid: - """Mesh the caps of each fluid cavity with uniformly. - - Parameters - ---------- - cavity_boundaries : List[SurfaceMesh] - List of cavity boundaries used for meshing. - workdir : str - Working directory - mesh_size : float - Mesh size - - Returns - ------- - pv.UnstructuredGrid - Unstructured grid with fluid mesh. - """ - # Use the following tgrid api utility for each cavity: - # Consequently need to associate each "patch" with a cap. E.g. by centroid? - # - # (tgapi-util-fill-holes-in-face-zone-list '(face-zone-list) max-hole-edges) - if _uses_container: - mounted_volume = pyfluent.EXAMPLES_PATH - work_dir_meshing = os.path.join(mounted_volume, "tmp_meshing-fluid") - else: - work_dir_meshing = os.path.join(workdir, "meshing-fluid") - - if not os.path.isdir(work_dir_meshing): - os.makedirs(work_dir_meshing) - else: - files = glob.glob(os.path.join(work_dir_meshing, "*.stl")) - for f in files: - os.remove(f) - - # write all boundaries - for b in cavity_boundaries: - filename = os.path.join(work_dir_meshing, b.name.lower() + ".stl") - b.save(filename) - add_solid_name_to_stl(filename, b.name.lower(), file_type="binary") - - session = _get_fluent_meshing_session(work_dir_meshing) - - # import all stls - if _uses_container: - # NOTE: when using a Fluent container visible files - # will be in /mnt/pyfluent. So need to use relative paths - # or replace dirname by /mnt/pyfluent as prefix - work_dir_meshing = "/mnt/pyfluent/meshing" - - session.tui.file.import_.cad(f"no {work_dir_meshing} *.stl") - - # set size field - session.tui.size_functions.set_global_controls(mesh_size, mesh_size, 1.2) - session.tui.scoped_sizing.compute("yes") - - # create caps - # (tgapi-util-fill-holes-in-face-zone-list '(face-zone-list) max-hole-edges) - # convert all to mesh object - session.tui.objects.change_object_type("'(*)", "mesh", "yes") - for cavity_boundary in cavity_boundaries: - cavity_name = "-".join(cavity_boundary.name.split()).lower() - session.scheme_eval.scheme_eval( - f"(tgapi-util-fill-holes-in-face-zone-list '({cavity_name}) 1000)" - ) - # merge unreferenced with cavity - # (get-unreferenced-face-zones) - patch_ids = session.scheme_eval.scheme_eval("(get-unreferenced-face-zones)") - session.tui.objects.create( - f"{cavity_name}-patches", - "fluid", - 3, - "({0})".format(" ".join([str(patch_id) for patch_id in patch_ids])), - "()", - "mesh", - "yes", - ) - # merge objects - session.tui.objects.merge(f"'({cavity_name}*)") - - # find overlapping pairs - # (tgapi-util-get-overlapping-face-zones "*patch*" 0.1 0.1 ) - overlapping_pairs = session.scheme_eval.scheme_eval( - '(tgapi-util-get-overlapping-face-zones "*patch*" 0.1 0.1 )' - ) - for pair in overlapping_pairs: - # remove first from pair - session.tui.boundary.manage.delete(f"'({pair[0]})", "yes") - - # merge mesh objects - session.tui.objects.merge("'(*)", "fluid-zones") - - # merge nodes - session.tui.boundary.merge_nodes("'(*)", "'(*)", "no", "no , ,") - # mesh volumes - session.tui.objects.volumetric_regions.compute("fluid-zones", "no") - session.tui.mesh.auto_mesh("fluid-zones", "yes", "pyr", "tet", "yes") - - session.tui.objects.delete_all_geom() - - file_path_mesh = os.path.join(workdir, "fluid-mesh.msh.h5") - session.tui.file.write_mesh(file_path_mesh) - - session.exit() - - # # write - mesh = FluentMesh(file_path_mesh) - mesh.load_mesh(reconstruct_tetrahedrons=True) - - return mesh._to_vtk(add_cells=True, add_faces=True) - - def mesh_from_manifold_input_model( model: _InputModel, workdir: Union[str, Path], @@ -752,5 +639,118 @@ def mesh_from_non_manifold_input_model( return new_mesh +def _mesh_fluid_cavities( + cavity_boundaries: list[SurfaceMesh], workdir: str, mesh_size: float = 1.0 +) -> pv.UnstructuredGrid: + """Mesh the caps of each fluid cavity with uniformly. + + Parameters + ---------- + cavity_boundaries : List[SurfaceMesh] + List of cavity boundaries used for meshing. + workdir : str + Working directory + mesh_size : float + Mesh size + + Returns + ------- + pv.UnstructuredGrid + Unstructured grid with fluid mesh. + """ + # Use the following tgrid api utility for each cavity: + # Consequently need to associate each "patch" with a cap. E.g. by centroid? + # + # (tgapi-util-fill-holes-in-face-zone-list '(face-zone-list) max-hole-edges) + if _uses_container: + mounted_volume = pyfluent.EXAMPLES_PATH + work_dir_meshing = os.path.join(mounted_volume, "tmp_meshing-fluid") + else: + work_dir_meshing = os.path.join(workdir, "meshing-fluid") + + if not os.path.isdir(work_dir_meshing): + os.makedirs(work_dir_meshing) + else: + files = glob.glob(os.path.join(work_dir_meshing, "*.stl")) + for f in files: + os.remove(f) + + # write all boundaries + for b in cavity_boundaries: + filename = os.path.join(work_dir_meshing, b.name.lower() + ".stl") + b.save(filename) + add_solid_name_to_stl(filename, b.name.lower(), file_type="binary") + + session = _get_fluent_meshing_session(work_dir_meshing) + + # import all stls + if _uses_container: + # NOTE: when using a Fluent container visible files + # will be in /mnt/pyfluent. So need to use relative paths + # or replace dirname by /mnt/pyfluent as prefix + work_dir_meshing = "/mnt/pyfluent/meshing" + + session.tui.file.import_.cad(f"no {work_dir_meshing} *.stl") + + # set size field + session.tui.size_functions.set_global_controls(mesh_size, mesh_size, 1.2) + session.tui.scoped_sizing.compute("yes") + + # create caps + # (tgapi-util-fill-holes-in-face-zone-list '(face-zone-list) max-hole-edges) + # convert all to mesh object + session.tui.objects.change_object_type("'(*)", "mesh", "yes") + for cavity_boundary in cavity_boundaries: + cavity_name = "-".join(cavity_boundary.name.split()).lower() + session.scheme_eval.scheme_eval( + f"(tgapi-util-fill-holes-in-face-zone-list '({cavity_name}) 1000)" + ) + # merge unreferenced with cavity + # (get-unreferenced-face-zones) + patch_ids = session.scheme_eval.scheme_eval("(get-unreferenced-face-zones)") + session.tui.objects.create( + f"{cavity_name}-patches", + "fluid", + 3, + "({0})".format(" ".join([str(patch_id) for patch_id in patch_ids])), + "()", + "mesh", + "yes", + ) + # merge objects + session.tui.objects.merge(f"'({cavity_name}*)") + + # find overlapping pairs + # (tgapi-util-get-overlapping-face-zones "*patch*" 0.1 0.1 ) + overlapping_pairs = session.scheme_eval.scheme_eval( + '(tgapi-util-get-overlapping-face-zones "*patch*" 0.1 0.1 )' + ) + for pair in overlapping_pairs: + # remove first from pair + session.tui.boundary.manage.delete(f"'({pair[0]})", "yes") + + # merge mesh objects + session.tui.objects.merge("'(*)", "fluid-zones") + + # merge nodes + session.tui.boundary.merge_nodes("'(*)", "'(*)", "no", "no , ,") + # mesh volumes + session.tui.objects.volumetric_regions.compute("fluid-zones", "no") + session.tui.mesh.auto_mesh("fluid-zones", "yes", "pyr", "tet", "yes") + + session.tui.objects.delete_all_geom() + + file_path_mesh = os.path.join(workdir, "fluid-mesh.msh.h5") + session.tui.file.write_mesh(file_path_mesh) + + session.exit() + + # # write + mesh = FluentMesh(file_path_mesh) + mesh.load_mesh(reconstruct_tetrahedrons=True) + + return mesh._to_vtk(add_cells=True, add_faces=True) + + if __name__ == "__main__": LOGGER.info("Protected") From 4e7b62cd1c934b494a0baa38ee6fe2b41a301891 Mon Sep 17 00:00:00 2001 From: mhoeijm Date: Wed, 6 Nov 2024 14:30:59 +0100 Subject: [PATCH 06/14] fix version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f35339405..05cf0430d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "flit_core.buildapi" [project] # Check https://flit.readthedocs.io/en/latest/pyproject_toml.html for all available sections name = "pyansys-heart" -version = "0.6.dev0" +version = "0.6.0" description = "Python framework for heart modeling using ANSYS tools." readme = "README.rst" requires-python = ">=3.10,<4" From 9fb8f715d7858ed6f311a207947c23d191c90ce2 Mon Sep 17 00:00:00 2001 From: wenfengye Date: Fri, 15 Nov 2024 09:57:35 +0100 Subject: [PATCH 07/14] fix fast passive filling --- src/ansys/heart/simulator/settings/defaults/mechanics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ansys/heart/simulator/settings/defaults/mechanics.py b/src/ansys/heart/simulator/settings/defaults/mechanics.py index d94b21dfe..8e6ab83b5 100644 --- a/src/ansys/heart/simulator/settings/defaults/mechanics.py +++ b/src/ansys/heart/simulator/settings/defaults/mechanics.py @@ -167,7 +167,7 @@ rp = 0.97 * pee / co ca = tau / rp ra = 0.03 * rp -rv = 0.2 * ra +rv = ra system_model = { "name": "ConstantPreloadWindkesselAfterload", From d01371402f3995b62e980b204e3d6b640e2cbe94 Mon Sep 17 00:00:00 2001 From: wenfengye Date: Fri, 15 Nov 2024 13:41:07 +0100 Subject: [PATCH 08/14] update convergence settings --- src/ansys/heart/writer/dynawriter.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ansys/heart/writer/dynawriter.py b/src/ansys/heart/writer/dynawriter.py index 12d719fee..140e644ee 100644 --- a/src/ansys/heart/writer/dynawriter.py +++ b/src/ansys/heart/writer/dynawriter.py @@ -1071,14 +1071,14 @@ def _add_solution_controls( self.kw_database.main.append( keywords.ControlImplicitSolution( # maxref=35, - # dctol=0.02, - # ectol=1e6, - # rctol=1e3, - abstol=1e-20, - # dnorm=1, + dctol=0.02, + ectol=1e6, + rctol=1e3, + abstol=-1e-20, + dnorm=1, # diverg=2, - # lstol=-0.9, - # lsmtd=5, + lstol=-0.9, + lsmtd=5, # d3itctl=1, nlprint=3, nlnorm=4, From 29bc499c12eb1b295f93e27e3ce159d2fef799bc Mon Sep 17 00:00:00 2001 From: mhoeijm Date: Wed, 20 Nov 2024 09:47:40 +0100 Subject: [PATCH 09/14] fix: fixes for volume meshing --- src/ansys/heart/core/models.py | 47 ++++++++++++++------------ src/ansys/heart/preprocessor/mesher.py | 15 ++++---- 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/src/ansys/heart/core/models.py b/src/ansys/heart/core/models.py index 78756d6b6..4f0bc9dbc 100644 --- a/src/ansys/heart/core/models.py +++ b/src/ansys/heart/core/models.py @@ -553,38 +553,43 @@ def _mesh_fluid_volume(self, remesh_caps: bool = True): LOGGER.info("Meshing fluid cavities...") + # get list of fluid cavities # mesh the fluid cavities - fluid_mesh = mesher.mesh_fluid_cavities( - boundaries_fluid, caps, self.info.workdir, remesh_caps=remesh_caps - ) + cavity_surfaces = [part.cavity.surface for part in self.parts if part.cavity] + # remove caps. + cavity_surfaces = [ + SurfaceMesh(cs.threshold((0, 0), "_cap_id").extract_surface(), name=cs.name) + for cs in cavity_surfaces + ] + fluid_mesh = mesher._mesh_fluid_cavities(cavity_surfaces, self.info.workdir, mesh_size=1) - LOGGER.info(f"Meshed {len(fluid_mesh.cell_zones)} fluid regions...") + # LOGGER.info(f"Meshed {len(fluid_mesh.cell_zones)} fluid regions...") # add part-ids - cz_ids = np.sort([cz.id for cz in fluid_mesh.cell_zones]) + # cz_ids = np.sort([cz.id for cz in fluid_mesh.cell_zones]) # TODO: this offset is arbitrary. - offset = 10000 - new_ids = np.arange(cz_ids.shape[0]) + offset - czid_to_pid = {cz_id: new_ids[ii] for ii, cz_id in enumerate(cz_ids)} + # offset = 10000 + # new_ids = np.arange(cz_ids.shape[0]) + offset + # czid_to_pid = {cz_id: new_ids[ii] for ii, cz_id in enumerate(cz_ids)} - for cz in fluid_mesh.cell_zones: - cz.id = czid_to_pid[cz.id] + # for cz in fluid_mesh.cell_zones: + # cz.id = czid_to_pid[cz.id] - fluid_mesh._fix_negative_cells() - fluid_mesh_vtk = fluid_mesh._to_vtk(add_cells=True, add_faces=False) + # fluid_mesh._fix_negative_cells() + # fluid_mesh_vtk = fluid_mesh._to_vtk(add_cells=True, add_faces=False) - fluid_mesh_vtk.cell_data["part-id"] = fluid_mesh_vtk.cell_data["cell-zone-ids"] + # fluid_mesh_vtk.cell_data["part-id"] = fluid_mesh_vtk.cell_data["cell-zone-ids"] - boundaries = [ - SurfaceMesh(name=fz.name, triangles=fz.faces, nodes=fluid_mesh.nodes, id=fz.id) - for fz in fluid_mesh.face_zones - if "interior" not in fz.name - ] + # boundaries = [ + # SurfaceMesh(name=fz.name, triangles=fz.faces, nodes=fluid_mesh.nodes, id=fz.id) + # for fz in fluid_mesh.face_zones + # if "interior" not in fz.name + # ] - self.fluid_mesh = Mesh(fluid_mesh_vtk) - for boundary in boundaries: - self.fluid_mesh.add_surface(boundary, boundary.id, boundary.name) + self.fluid_mesh = Mesh(fluid_mesh) + # for boundary in boundaries: + # self.fluid_mesh.add_surface(boundary, boundary.id, boundary.name) return diff --git a/src/ansys/heart/preprocessor/mesher.py b/src/ansys/heart/preprocessor/mesher.py index 0a7c04c0b..877da0abd 100644 --- a/src/ansys/heart/preprocessor/mesher.py +++ b/src/ansys/heart/preprocessor/mesher.py @@ -753,12 +753,15 @@ def _mesh_fluid_cavities( # find overlapping pairs # (tgapi-util-get-overlapping-face-zones "*patch*" 0.1 0.1 ) - overlapping_pairs = session.scheme_eval.scheme_eval( - '(tgapi-util-get-overlapping-face-zones "*patch*" 0.1 0.1 )' - ) - for pair in overlapping_pairs: - # remove first from pair - session.tui.boundary.manage.delete(f"'({pair[0]})", "yes") + # try: + # overlapping_pairs = session.scheme_eval.scheme_eval( + # '(tgapi-util-get-overlapping-face-zones "*patch*" 0.1 0.1 )' + # ) + # for pair in overlapping_pairs: + # # remove first from pair + # session.tui.boundary.manage.delete(f"'({pair[0]})", "yes") + # except: + # LOGGER.debug("No overlapping face zones.") # merge mesh objects session.tui.objects.merge("'(*)", "fluid-zones") From e1f0886b98e576911340c1c2c122440781387c6b Mon Sep 17 00:00:00 2001 From: mhoeijm Date: Wed, 20 Nov 2024 11:50:33 +0100 Subject: [PATCH 10/14] fix: fixes and add flag to _to_vtk --- src/ansys/heart/core/helpers/fluenthdf5.py | 13 ++++++++++--- src/ansys/heart/core/models.py | 10 ++-------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/ansys/heart/core/helpers/fluenthdf5.py b/src/ansys/heart/core/helpers/fluenthdf5.py index 823fb2778..3a79a81ea 100644 --- a/src/ansys/heart/core/helpers/fluenthdf5.py +++ b/src/ansys/heart/core/helpers/fluenthdf5.py @@ -426,7 +426,9 @@ def _convert_interior_faces_to_tetrahedrons2(self) -> Tuple[np.ndarray, np.ndarr return tetrahedrons, cell_ids # NOTE: no typehint due to lazy import of pyvista - def _to_vtk(self, add_cells: bool = True, add_faces: bool = False): + def _to_vtk( + self, add_cells: bool = True, add_faces: bool = False, remove_interior_faces: bool = False + ): """Convert mesh to vtk unstructured grid or polydata. Parameters @@ -471,11 +473,16 @@ def _to_vtk(self, add_cells: bool = True, add_faces: bool = False): if add_faces: # add faces. + face_zones = self.face_zones + + if remove_interior_faces: + face_zones = [fz for fz in face_zones if "interior" not in fz.name] + grid_faces = pv.UnstructuredGrid() grid_faces.nodes = self.nodes - face_zone_ids = np.concatenate([[fz.id] * fz.faces.shape[0] for fz in self.face_zones]) - faces = np.array(np.concatenate([fz.faces for fz in self.face_zones]), dtype=int) + face_zone_ids = np.concatenate([[fz.id] * fz.faces.shape[0] for fz in face_zones]) + faces = np.array(np.concatenate([fz.faces for fz in face_zones]), dtype=int) faces = np.hstack([np.ones((faces.shape[0], 1), dtype=int) * 3, faces]) grid_faces = pv.UnstructuredGrid( diff --git a/src/ansys/heart/core/models.py b/src/ansys/heart/core/models.py index 4f0bc9dbc..541f89d3c 100644 --- a/src/ansys/heart/core/models.py +++ b/src/ansys/heart/core/models.py @@ -518,14 +518,8 @@ def mesh_volume( return - def _mesh_fluid_volume(self, remesh_caps: bool = True): - """Generate a volume mesh of the cavities. - - Parameters - ---------- - remesh_caps : bool, optional - Flag indicating whether to remesh the caps of each cavity, by default True - """ + def _mesh_fluid_volume(self): + """Generate a volume mesh of the cavities.""" # get all relevant boundaries for the fluid cavities: substrings_include = ["endocardium", "valve-plane", "septum"] substrings_include_re = "|".join(substrings_include) From 614af8ec7378934afb1544aee4cec51640a7a71f Mon Sep 17 00:00:00 2001 From: mhoeijm Date: Wed, 20 Nov 2024 11:50:55 +0100 Subject: [PATCH 11/14] fix: for left-ventricular model --- src/ansys/heart/preprocessor/mesher.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/ansys/heart/preprocessor/mesher.py b/src/ansys/heart/preprocessor/mesher.py index 877da0abd..b2662301c 100644 --- a/src/ansys/heart/preprocessor/mesher.py +++ b/src/ansys/heart/preprocessor/mesher.py @@ -764,7 +764,11 @@ def _mesh_fluid_cavities( # LOGGER.debug("No overlapping face zones.") # merge mesh objects - session.tui.objects.merge("'(*)", "fluid-zones") + mesh_object_names = session.scheme_eval.scheme_eval("(get-objects-of-type 'mesh)") + if len(mesh_object_names) > 1: + session.tui.objects.merge("'(*)", "fluid-zones") + else: + session.tui.objects.rename_object(mesh_object_names[0].str, "fluid-zones") # merge nodes session.tui.boundary.merge_nodes("'(*)", "'(*)", "no", "no , ,") @@ -783,7 +787,7 @@ def _mesh_fluid_cavities( mesh = FluentMesh(file_path_mesh) mesh.load_mesh(reconstruct_tetrahedrons=True) - return mesh._to_vtk(add_cells=True, add_faces=True) + return mesh._to_vtk(add_cells=True, add_faces=True, remove_interior_faces=True) if __name__ == "__main__": From 1e64bbfa26e4447a53e89e045bf310a4be159a4f Mon Sep 17 00:00:00 2001 From: wenfengye Date: Wed, 20 Nov 2024 16:28:30 +0100 Subject: [PATCH 12/14] minor --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 05cf0430d..f35339405 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "flit_core.buildapi" [project] # Check https://flit.readthedocs.io/en/latest/pyproject_toml.html for all available sections name = "pyansys-heart" -version = "0.6.0" +version = "0.6.dev0" description = "Python framework for heart modeling using ANSYS tools." readme = "README.rst" requires-python = ">=3.10,<4" From e25817f29fc11cf57bfb4d7c3e11f5a6fb4c848b Mon Sep 17 00:00:00 2001 From: mhoeijm Date: Thu, 28 Nov 2024 10:03:06 +0100 Subject: [PATCH 13/14] fix: get updated cavity geometry --- src/ansys/heart/core/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ansys/heart/core/models.py b/src/ansys/heart/core/models.py index 08bd180d9..ac7ad1a79 100644 --- a/src/ansys/heart/core/models.py +++ b/src/ansys/heart/core/models.py @@ -647,13 +647,13 @@ def _mesh_fluid_volume(self): # get list of fluid cavities # mesh the fluid cavities - cavity_surfaces = [part.cavity.surface for part in self.parts if part.cavity] + cavity_surfaces = [self.mesh.get_surface(part.cavity.surface.id) for part in self.parts if part.cavity] # remove caps. cavity_surfaces = [ SurfaceMesh(cs.threshold((0, 0), "_cap_id").extract_surface(), name=cs.name) for cs in cavity_surfaces ] - fluid_mesh = mesher._mesh_fluid_cavities(cavity_surfaces, self.info.workdir, mesh_size=1) + fluid_mesh = mesher._mesh_fluid_cavities(cavity_surfaces, self.workdir, mesh_size=1) # LOGGER.info(f"Meshed {len(fluid_mesh.cell_zones)} fluid regions...") From f97c7f5540d81b2127771565e16db748f7273c75 Mon Sep 17 00:00:00 2001 From: mhoeijm Date: Thu, 28 Nov 2024 11:12:48 +0100 Subject: [PATCH 14/14] fix: style --- src/ansys/heart/core/models.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ansys/heart/core/models.py b/src/ansys/heart/core/models.py index ac7ad1a79..75d99515e 100644 --- a/src/ansys/heart/core/models.py +++ b/src/ansys/heart/core/models.py @@ -647,7 +647,9 @@ def _mesh_fluid_volume(self): # get list of fluid cavities # mesh the fluid cavities - cavity_surfaces = [self.mesh.get_surface(part.cavity.surface.id) for part in self.parts if part.cavity] + cavity_surfaces = [ + self.mesh.get_surface(part.cavity.surface.id) for part in self.parts if part.cavity + ] # remove caps. cavity_surfaces = [ SurfaceMesh(cs.threshold((0, 0), "_cap_id").extract_surface(), name=cs.name)