From 47cccf6445207120a1936773e17e62d431c9efd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20A=20Le=C3=B3n=20Baldelli?= Date: Fri, 22 Mar 2024 11:44:56 +0100 Subject: [PATCH] testing meshing --- src/irrevolutions/meshes/test_fuse.py | 49 +++--- src/irrevolutions/meshes/test_occ_2.py | 3 +- src/irrevolutions/meshes/test_occ_crack.py | 71 ++++---- src/irrevolutions/meshes/test_occ_pacman.py | 39 +++-- src/irrevolutions/meshes/test_occ_t16.py | 179 +++++++++++--------- 5 files changed, 190 insertions(+), 151 deletions(-) diff --git a/src/irrevolutions/meshes/test_fuse.py b/src/irrevolutions/meshes/test_fuse.py index 56377dac..775d6e88 100644 --- a/src/irrevolutions/meshes/test_fuse.py +++ b/src/irrevolutions/meshes/test_fuse.py @@ -1,28 +1,33 @@ +import os import gmsh +import pytest -# initialise gmsh engine -gmsh.initialize() +# Define the test parameters +TEST_CASES = [ + {"name": "minimal_2d", "units": 1, "expected_mesh_size": 2} + # Add more test cases if needed +] -# assign name to geomtry -gmsh.model.add("simple_2d") +@pytest.mark.parametrize("test_case", TEST_CASES) +def test_generate_2d_mesh(test_case): + # Run the script + gmsh.initialize() + gmsh.model.add("simple_2d") + units = test_case["units"] + rectangle_1 = gmsh.model.occ.addRectangle(0.0, 0.0, 0.0, units, 0.5*units, tag=1) + rectangle_2 = gmsh.model.occ.addRectangle(0.0, 0.5*units, 0.0, units, 0.5*units, tag=2) + rectangles = gmsh.model.occ.fuse([(2,1)], [(2,2)], tag=3, removeObject=True, removeTool=True) + gmsh.model.occ.synchronize() + gmsh.model.addPhysicalGroup(2, [3], 1) + gmsh.model.setPhysicalName(2, 1, "rectangles") + gmsh.model.mesh.generate(1) + gmsh.model.mesh.generate(2) -units = 1 -rectangle_1 = gmsh.model.occ.addRectangle(0.0, 0.0, 0.0, units, 0.5*units, tag=1) -rectangle_2 = gmsh.model.occ.addRectangle(0.0, 0.5*units, 0.0, units, 0.5*units, tag=2) -# link both domains and remove old rectangles -rectangles = gmsh.model.occ.fuse([(2,1)], [(2,2)], tag=3, removeObject=True, removeTool=True) + # Verify the generated mesh + mesh_file_path = os.path.join(os.path.dirname(__file__), "output", f"{test_case['name']}_mesh.msh") + gmsh.write(mesh_file_path) -# sycrhonise geometry with gmsh -gmsh.model.occ.synchronize() + assert os.path.isfile(mesh_file_path) -# create groups -gmsh.model.addPhysicalGroup(2, [3], 1) -gmsh.model.setPhysicalName(2, 1, "rectangles") - -# gnerate 2D mesh, write mesh and convert to xdmf -gmsh.model.mesh.generate(1) -gmsh.model.mesh.generate(2) -gmsh.write("mesh_2d_minimal.msh") - -# finalize gmsh engine -gmsh.finalize() \ No newline at end of file + # Clean up + gmsh.finalize() diff --git a/src/irrevolutions/meshes/test_occ_2.py b/src/irrevolutions/meshes/test_occ_2.py index 40f7d3fe..6d361f2e 100644 --- a/src/irrevolutions/meshes/test_occ_2.py +++ b/src/irrevolutions/meshes/test_occ_2.py @@ -1,4 +1,5 @@ import gmsh +import os gmsh.initialize() @@ -33,7 +34,7 @@ gmsh.model.mesh.generate(2) # Write the mesh file -gmsh.write("mesh.msh") +gmsh.write(os.path.join(os.path.dirname(__file__), "mesh.msh")) # Finalize Gmsh gmsh.finalize() diff --git a/src/irrevolutions/meshes/test_occ_crack.py b/src/irrevolutions/meshes/test_occ_crack.py index 0159f3e4..4395b4ae 100644 --- a/src/irrevolutions/meshes/test_occ_crack.py +++ b/src/irrevolutions/meshes/test_occ_crack.py @@ -1,43 +1,56 @@ import gmsh import sys +import os +import pytest -gmsh.initialize(sys.argv) +def generate_square_with_cracks(): -gmsh.model.add("square with cracks") + gmsh.initialize(sys.argv) -surf1 = 1 -gmsh.model.occ.addRectangle(0, 0, 0, 1, 1, surf1) + gmsh.model.add("square with cracks") -pt1 = gmsh.model.occ.addPoint(0.2, 0.2, 0) -pt2 = gmsh.model.occ.addPoint(0.4, 0.4, 0) -line1 = gmsh.model.occ.addLine(pt1, pt2) -pt3 = gmsh.model.occ.addPoint(0.4, 0.4, 0) -pt4 = gmsh.model.occ.addPoint(0.4, 0.9, 0) -line2 = gmsh.model.occ.addLine(pt3, pt4) + surf1 = 1 + gmsh.model.occ.addRectangle(0, 0, 0, 1, 1, surf1) -o, m = gmsh.model.occ.fragment([(2, surf1)], [(1, line1), (1, line2)]) -gmsh.model.occ.synchronize() + pt1 = gmsh.model.occ.addPoint(0.2, 0.2, 0) + pt2 = gmsh.model.occ.addPoint(0.4, 0.4, 0) + line1 = gmsh.model.occ.addLine(pt1, pt2) + pt3 = gmsh.model.occ.addPoint(0.4, 0.4, 0) + pt4 = gmsh.model.occ.addPoint(0.4, 0.9, 0) + line2 = gmsh.model.occ.addLine(pt3, pt4) -# m contains, for each input entity (surf1, line1 and line2), the child entities -# (if any) after the fragmentation, as lists of tuples. To apply the crack -# plugin we group all the intersecting lines in a physical group + o, m = gmsh.model.occ.fragment([(2, surf1)], [(1, line1), (1, line2)]) + gmsh.model.occ.synchronize() -new_surf = m[0][0][1] -new_lines = [item[1] for sublist in m[1:] for item in sublist] + # m contains, for each input entity (surf1, line1 and line2), the child entities + # (if any) after the fragmentation, as lists of tuples. To apply the crack + # plugin we group all the intersecting lines in a physical group -gmsh.model.addPhysicalGroup(2, [new_surf], 100) -gmsh.model.addPhysicalGroup(1, new_lines, 101) + new_surf = m[0][0][1] + new_lines = [item[1] for sublist in m[1:] for item in sublist] -gmsh.model.mesh.generate(2) + gmsh.model.addPhysicalGroup(2, [new_surf], 100) + gmsh.model.addPhysicalGroup(1, new_lines, 101) -gmsh.plugin.setNumber("Crack", "Dimension", 1) -gmsh.plugin.setNumber("Crack", "PhysicalGroup", 101) -gmsh.plugin.setNumber("Crack", "DebugView", 1) -gmsh.plugin.run("Crack") + gmsh.model.mesh.generate(2) -# save all the elements in the mesh (even those that do not belong to any -# physical group): -gmsh.option.setNumber("Mesh.SaveAll", 1) -gmsh.write("crack.msh") + gmsh.plugin.setNumber("Crack", "Dimension", 1) + gmsh.plugin.setNumber("Crack", "PhysicalGroup", 101) + gmsh.plugin.setNumber("Crack", "DebugView", 1) + gmsh.plugin.run("Crack") -gmsh.finalize() + # save all the elements in the mesh (even those that do not belong to any + # physical group): + gmsh.option.setNumber("Mesh.SaveAll", 1) + output_file = os.path.join(os.path.dirname(__file__), "output", "crack.msh") + + gmsh.finalize() + + return output_file + +def test_generate_square_with_cracks(): + output_file = generate_square_with_cracks() + assert os.path.isfile(output_file) + +if __name__ == "__main__": + pytest.main(args=[__file__]) \ No newline at end of file diff --git a/src/irrevolutions/meshes/test_occ_pacman.py b/src/irrevolutions/meshes/test_occ_pacman.py index 055666f5..41594fe6 100644 --- a/src/irrevolutions/meshes/test_occ_pacman.py +++ b/src/irrevolutions/meshes/test_occ_pacman.py @@ -1,9 +1,13 @@ import gmsh import numpy as np - +import os +import pytest +import sys def create_triangle_with_angle(opening_deg, rotation = 0): + gmsh.initialize(sys.argv) + # Create a new model gmsh.model.add("triangle_with_angle") @@ -35,6 +39,12 @@ def create_triangle_with_angle(opening_deg, rotation = 0): domain = gmsh.model.occ.rotate([(2, triangle_surface)], *p0, *_rotation_axis, angle_radians) + gmsh.model.mesh.generate(2) + output_file = os.path.join(os.path.dirname(__file__), "output", "triangle_with_angle.msh") + gmsh.write(output_file) + + gmsh.finalize() + return output_file # gmsh.model.occ.rotate([(2, triangle_surface)], base_point1, 0, 0, 1, angle_radians) # Generate mesh @@ -64,7 +74,6 @@ def create_pacman(opening_deg, rotation = 0): base_point2 = gmsh.model.occ.addPoint(adjacent_length, 0, 0) base_line = gmsh.model.occ.addLine(base_point1, base_point2) - # Define the vertex of the triangle vertex_point = gmsh.model.occ.addPoint(adjacent_length * np.cos(angle_radians), adjacent_length * np.sin(angle_radians), 0) @@ -116,20 +125,20 @@ def create_pacman(opening_deg, rotation = 0): occ.synchronize() gmsh.model.mesh.generate(2) - # Write the mesh file - gmsh.write("test_occ.msh") + output_file = os.path.join(os.path.dirname(__file__), "output", "pacman.msh") + gmsh.write(output_file) + + gmsh.finalize() + return output_file - # Finalize Gmsh - return True -# Example: Create a triangle with a 45-degree angle +def test_create_triangle_with_angle(): + output_file = create_triangle_with_angle(opening_deg=30, rotation=0) + assert os.path.isfile(output_file) + +def test_create_pacman(): + output_file = create_pacman(opening_deg=30, rotation=180-30/2) + assert os.path.isfile(output_file) if __name__ == "__main__": - gmsh.initialize() - - # domain = create_triangle_with_angle(opening_deg = 30, rotation = 0) - domain = create_pacman(opening_deg = 30, rotation = 180-30/2) - - gmsh.finalize() - - pass \ No newline at end of file + pytest.main(args=[__file__]) \ No newline at end of file diff --git a/src/irrevolutions/meshes/test_occ_t16.py b/src/irrevolutions/meshes/test_occ_t16.py index 86369c51..31fd342c 100644 --- a/src/irrevolutions/meshes/test_occ_t16.py +++ b/src/irrevolutions/meshes/test_occ_t16.py @@ -13,111 +13,122 @@ import gmsh import math import sys +import os -gmsh.initialize() +def generate_3d_mesh(): + + gmsh.initialize() -gmsh.model.add("t16") - -# Let's build the same model as in `t5.py', but using constructive solid -# geometry. + gmsh.model.add("t16") + + # Let's build the same model as in `t5.py', but using constructive solid + # geometry. -# We can log all messages for further processing with: -gmsh.logger.start() + # We can log all messages for further processing with: + gmsh.logger.start() -# We first create two cubes: -gmsh.model.occ.addBox(0, 0, 0, 1, 1, 1, 1) -gmsh.model.occ.addBox(0, 0, 0, 0.5, 0.5, 0.5, 2) + # We first create two cubes: + gmsh.model.occ.addBox(0, 0, 0, 1, 1, 1, 1) + gmsh.model.occ.addBox(0, 0, 0, 0.5, 0.5, 0.5, 2) -# We apply a boolean difference to create the "cube minus one eigth" shape: -gmsh.model.occ.cut([(3, 1)], [(3, 2)], 3) + # We apply a boolean difference to create the "cube minus one eigth" shape: + gmsh.model.occ.cut([(3, 1)], [(3, 2)], 3) -# Boolean operations with OpenCASCADE always create new entities. By default the -# extra arguments `removeObject' and `removeTool' in `cut()' are set to `True', -# which will delete the original entities. + # Boolean operations with OpenCASCADE always create new entities. By default the + # extra arguments `removeObject' and `removeTool' in `cut()' are set to `True', + # which will delete the original entities. -# We then create the five spheres: -x = 0 -y = 0.75 -z = 0 -r = 0.09 -holes = [] -for t in range(1, 6): - x += 0.166 - z += 0.166 - gmsh.model.occ.addSphere(x, y, z, r, 3 + t) - holes.append((3, 3 + t)) + # We then create the five spheres: + x = 0 + y = 0.75 + z = 0 + r = 0.09 + holes = [] + for t in range(1, 6): + x += 0.166 + z += 0.166 + gmsh.model.occ.addSphere(x, y, z, r, 3 + t) + holes.append((3, 3 + t)) -# If we had wanted five empty holes we would have used `cut()' again. Here we -# want five spherical inclusions, whose mesh should be conformal with the mesh -# of the cube: we thus use `fragment()', which intersects all volumes in a -# conformal manner (without creating duplicate interfaces): -ov, ovv = gmsh.model.occ.fragment([(3, 3)], holes) + # If we had wanted five empty holes we would have used `cut()' again. Here we + # want five spherical inclusions, whose mesh should be conformal with the mesh + # of the cube: we thus use `fragment()', which intersects all volumes in a + # conformal manner (without creating duplicate interfaces): + ov, ovv = gmsh.model.occ.fragment([(3, 3)], holes) -# ov contains all the generated entities of the same dimension as the input -# entities: -print("fragment produced volumes:") -for e in ov: - print(e) + # ov contains all the generated entities of the same dimension as the input + # entities: + print("fragment produced volumes:") + for e in ov: + print(e) -# ovv contains the parent-child relationships for all the input entities: -print("before/after fragment relations:") -for e in zip([(3, 3)] + holes, ovv): - print("parent " + str(e[0]) + " -> child " + str(e[1])) + # ovv contains the parent-child relationships for all the input entities: + print("before/after fragment relations:") + for e in zip([(3, 3)] + holes, ovv): + print("parent " + str(e[0]) + " -> child " + str(e[1])) -gmsh.model.occ.synchronize() + gmsh.model.occ.synchronize() -# When the boolean operation leads to simple modifications of entities, and if -# one deletes the original entities, Gmsh tries to assign the same tag to the -# new entities. (This behavior is governed by the -# `Geometry.OCCBooleanPreserveNumbering' option.) + # When the boolean operation leads to simple modifications of entities, and if + # one deletes the original entities, Gmsh tries to assign the same tag to the + # new entities. (This behavior is governed by the + # `Geometry.OCCBooleanPreserveNumbering' option.) -# Here the `Physical Volume' definitions can thus be made for the 5 spheres -# directly, as the five spheres (volumes 4, 5, 6, 7 and 8), which will be -# deleted by the fragment operations, will be recreated identically (albeit with -# new surfaces) with the same tags: -for i in range(1, 6): - gmsh.model.addPhysicalGroup(3, [3 + i], i) + # Here the `Physical Volume' definitions can thus be made for the 5 spheres + # directly, as the five spheres (volumes 4, 5, 6, 7 and 8), which will be + # deleted by the fragment operations, will be recreated identically (albeit with + # new surfaces) with the same tags: + for i in range(1, 6): + gmsh.model.addPhysicalGroup(3, [3 + i], i) -# The tag of the cube will change though, so we need to access it -# programmatically: -gmsh.model.addPhysicalGroup(3, [ov[-1][1]], 10) + # The tag of the cube will change though, so we need to access it + # programmatically: + gmsh.model.addPhysicalGroup(3, [ov[-1][1]], 10) -# Creating entities using constructive solid geometry is very powerful, but can -# lead to practical issues for e.g. setting mesh sizes at points, or identifying -# boundaries. + # Creating entities using constructive solid geometry is very powerful, but can + # lead to practical issues for e.g. setting mesh sizes at points, or identifying + # boundaries. -# To identify points or other bounding entities you can take advantage of the -# `getEntities()', `getBoundary()' and `getEntitiesInBoundingBox()' functions: + # To identify points or other bounding entities you can take advantage of the + # `getEntities()', `getBoundary()' and `getEntitiesInBoundingBox()' functions: -lcar1 = .1 -lcar2 = .0005 -lcar3 = .055 + lcar1 = .1 + lcar2 = .0005 + lcar3 = .055 -# Assign a mesh size to all the points: -gmsh.model.mesh.setSize(gmsh.model.getEntities(0), lcar1) + # Assign a mesh size to all the points: + gmsh.model.mesh.setSize(gmsh.model.getEntities(0), lcar1) -# Override this constraint on the points of the five spheres: -gmsh.model.mesh.setSize(gmsh.model.getBoundary(holes, False, False, True), - lcar3) + # Override this constraint on the points of the five spheres: + gmsh.model.mesh.setSize(gmsh.model.getBoundary(holes, False, False, True), + lcar3) -# Select the corner point by searching for it geometrically: -eps = 1e-3 -ov = gmsh.model.getEntitiesInBoundingBox(0.5 - eps, 0.5 - eps, 0.5 - eps, - 0.5 + eps, 0.5 + eps, 0.5 + eps, 0) -gmsh.model.mesh.setSize(ov, lcar2) + # Select the corner point by searching for it geometrically: + eps = 1e-3 + ov = gmsh.model.getEntitiesInBoundingBox(0.5 - eps, 0.5 - eps, 0.5 - eps, + 0.5 + eps, 0.5 + eps, 0.5 + eps, 0) + gmsh.model.mesh.setSize(ov, lcar2) -gmsh.model.mesh.generate(3) + gmsh.model.mesh.generate(3) + + mesh_file_path = os.path.join(os.path.dirname(__file__), "output", "complex_3d_mesh.msh") -gmsh.write("t16.msh") + gmsh.write(mesh_file_path) -# Additional examples created with the OpenCASCADE geometry kernel are available -# in `t18.py', `t19.py' and `t20.py', as well as in the `examples/api' -# directory. + # Additional examples created with the OpenCASCADE geometry kernel are available + # in `t18.py', `t19.py' and `t20.py', as well as in the `examples/api' + # directory. -# Inspect the log: -log = gmsh.logger.get() -print("Logger has recorded " + str(len(log)) + " lines") -gmsh.logger.stop() - - -gmsh.finalize() + # Inspect the log: + log = gmsh.logger.get() + print("Logger has recorded " + str(len(log)) + " lines") + gmsh.logger.stop() + + + gmsh.finalize() + + return mesh_file_path + +def test_generate_3d_mesh(): + mesh_file_path = generate_3d_mesh() + assert os.path.isfile(mesh_file_path) \ No newline at end of file