From 9667a2d68a8247536d5756de332b75dee69ae908 Mon Sep 17 00:00:00 2001 From: Tetsuo Koyama Date: Thu, 2 May 2024 00:22:44 +0900 Subject: [PATCH 1/2] Move `delaunay_3d` function to method of `Cube` --- src/skgmsh/__init__.py | 255 +++++++++++++++++++---------------------- tests/test_skgmsh.py | 5 +- 2 files changed, 119 insertions(+), 141 deletions(-) diff --git a/src/skgmsh/__init__.py b/src/skgmsh/__init__.py index 0877194e..7afe2482 100644 --- a/src/skgmsh/__init__.py +++ b/src/skgmsh/__init__.py @@ -26,142 +26,6 @@ __version__ = ".".join(map(str, version_info)) -def delaunay_3d( - edge_source: pv.PolyData, - *, - target_sizes: float | Sequence[float] | None = None, -) -> pv.UnstructuredGrid | None: - """ - Delaunay 3D mesh algorithm. - - Parameters - ---------- - edge_source : pyvista.PolyData - Specify the source object used to specify constrained - edges and loops. If set, and lines/polygons are defined, a - constrained triangulation is created. The lines/polygons - are assumed to reference points in the input point set - (i.e. point ids are identical in the input and - source). - - target_sizes : float | Sequence[float], optional - Target mesh size close to the points. - Default max size of edge_source in each direction. - - Returns - ------- - pyvista.UnstructuredGrid - Mesh from the 3D delaunay generation. - - Examples - -------- - >>> import skgmsh as sg - - >>> edge_source = sg.Cube() - >>> mesh = sg.delaunay_3d(edge_source, target_sizes=0.2) - - >>> plotter = sg.Plotter(off_screen=True) - >>> _ = plotter.add_mesh(mesh, show_edges=True, line_width=4, color="white", lighting=True, edge_color=[153, 153, 153]) - >>> _ = plotter.add_mesh(edge_source.extract_all_edges(), line_width=4, color=[214, 39, 40]) - >>> _ = plotter.add_points(edge_source.points, style="points", point_size=20, color=[214, 39, 40]) - >>> plotter.enable_parallel_projection() - >>> _ = plotter.add_axes( - ... box=True, - ... box_args={ - ... "opacity": 0.5, - ... "color_box": True, - ... "x_face_color": "white", - ... "y_face_color": "white", - ... "z_face_color": "white", - ... }, - ... ) - >>> plotter.show(screenshot="docs/_static/delaunay_3d_01.png") - - >>> clipped = mesh.clip(origin = (0.0, 0.0, 0.0), normal = (0.0, 0.0, 1.0), crinkle=True) - >>> plotter = sg.Plotter(off_screen=True) - >>> _ = plotter.add_mesh( - ... clipped, - ... show_edges=True, - ... line_width=4, - ... color="white", - ... lighting=True, - ... edge_color=[153, 153, 153], - ... ) - >>> _ = plotter.add_mesh(edge_source.extract_all_edges(), line_width=4, color=[214, 39, 40]) - >>> _ = plotter.add_points( - ... edge_source.points, style="points", point_size=20, color=[214, 39, 40] - ... ) - >>> plotter.enable_parallel_projection() - >>> _ = plotter.add_axes( - ... box=True, - ... box_args={ - ... "opacity": 0.5, - ... "color_box": True, - ... "x_face_color": "white", - ... "y_face_color": "white", - ... "z_face_color": "white", - ... }, - ... ) - >>> plotter.show(screenshot="docs/_static/delaunay_3d_02.png") - - """ - points = edge_source.points - faces = edge_source.regular_faces - bounds = edge_source.bounds - - gmsh.initialize() - gmsh.option.set_number("Mesh.Algorithm3D", DELAUNAY_3D) - - if target_sizes is None: - target_sizes = np.max( - [ - np.abs(bounds[1] - bounds[0]), - np.abs(bounds[3] - bounds[2]), - np.abs(bounds[5] - bounds[4]), - ] - ) - - if isinstance(target_sizes, float): - target_sizes = [target_sizes] * edge_source.number_of_points - - for i, (point, target_size) in enumerate(zip(points, target_sizes)): - id_ = i + 1 - gmsh.model.geo.add_point(point[0], point[1], point[2], target_size, id_) - - surface_loop = [] - for i, face in enumerate(faces): - gmsh.model.geo.add_line(face[0] + 1, face[1] + 1, i * 4 + 0) - gmsh.model.geo.add_line(face[1] + 1, face[2] + 1, i * 4 + 1) - gmsh.model.geo.add_line(face[2] + 1, face[3] + 1, i * 4 + 2) - gmsh.model.geo.add_line(face[3] + 1, face[0] + 1, i * 4 + 3) - gmsh.model.geo.add_curve_loop( - [i * 4 + 0, i * 4 + 1, i * 4 + 2, i * 4 + 3], i + 1 - ) - gmsh.model.geo.add_plane_surface([i + 1], i + 1) - gmsh.model.geo.remove_all_duplicates() - gmsh.model.geo.synchronize() - surface_loop.append(i + 1) - - gmsh.model.geo.add_surface_loop(surface_loop, 1) - gmsh.model.geo.add_volume([1], 1) - - gmsh.model.geo.synchronize() - gmsh.model.mesh.generate(3) - - mesh = pv.wrap(extract_to_meshio()) - gmsh.clear() - gmsh.finalize() - - ind = [] - for i, cell in enumerate(mesh.cell): - if cell.type != pv.CellType.TETRA: - ind.append(i) - mesh = mesh.remove_cells(ind) - mesh.clear_data() - - return mesh - - class Report(scooby.Report): # type: ignore[misc] """ Generate an environment package and hardware report. @@ -274,9 +138,124 @@ class Plotter(PlotterBase, pv.Plotter): # type: ignore[misc] """Plotting object to display vtk meshes or numpy arrays.""" -def Cube(*args, **kwargs): # type: ignore[no-untyped-def] # noqa: ANN002, ANN003, ANN201, N802 +class Cube: """Create a cube.""" - return pv.Cube(*args, **kwargs) + + def __init__(self: Cube) -> None: + """ + Create a cube. + + Examples + -------- + Create a cube. + + >>> import skgmsh as sg + >>> cube = sg.Cube() + + """ + self.cube_data: pv.Cube = pv.Cube() + + def delaunay_3d( + self: Cube, + edge_source: Cube, + *, + target_sizes: float | Sequence[float] | None = None, + ) -> pv.UnstructuredGrid | None: + """ + Delaunay 3D mesh algorithm. + + Parameters + ---------- + edge_source : pyvista.PolyData + Specify the source object used to specify constrained + edges and loops. If set, and lines/polygons are defined, a + constrained triangulation is created. The lines/polygons + are assumed to reference points in the input point set + (i.e. point ids are identical in the input and + source). + + target_sizes : float | Sequence[float], optional + Target mesh size close to the points. + + Returns + ------- + pyvista.UnstructuredGrid + Mesh from the 3D delaunay generation. + + """ + points = edge_source.cube_data.points + faces = edge_source.cube_data.regular_faces + bounds = edge_source.cube_data.bounds + + gmsh.initialize() + gmsh.option.set_number("Mesh.Algorithm3D", DELAUNAY_3D) + + if target_sizes is None: + target_sizes = np.max( + [ + np.abs(bounds[1] - bounds[0]), + np.abs(bounds[3] - bounds[2]), + np.abs(bounds[5] - bounds[4]), + ] + ) + + if isinstance(target_sizes, float): + target_sizes = [target_sizes] * edge_source.number_of_points + + for i, (point, target_size) in enumerate(zip(points, target_sizes)): + id_ = i + 1 + gmsh.model.geo.add_point(point[0], point[1], point[2], target_size, id_) + + surface_loop = [] + for i, face in enumerate(faces): + gmsh.model.geo.add_line(face[0] + 1, face[1] + 1, i * 4 + 0) + gmsh.model.geo.add_line(face[1] + 1, face[2] + 1, i * 4 + 1) + gmsh.model.geo.add_line(face[2] + 1, face[3] + 1, i * 4 + 2) + gmsh.model.geo.add_line(face[3] + 1, face[0] + 1, i * 4 + 3) + gmsh.model.geo.add_curve_loop( + [i * 4 + 0, i * 4 + 1, i * 4 + 2, i * 4 + 3], i + 1 + ) + gmsh.model.geo.add_plane_surface([i + 1], i + 1) + gmsh.model.geo.remove_all_duplicates() + gmsh.model.geo.synchronize() + surface_loop.append(i + 1) + + gmsh.model.geo.add_surface_loop(surface_loop, 1) + gmsh.model.geo.add_volume([1], 1) + + gmsh.model.geo.synchronize() + gmsh.model.mesh.generate(3) + + mesh = pv.wrap(extract_to_meshio()) + gmsh.clear() + gmsh.finalize() + + ind = [] + for i, cell in enumerate(mesh.cell): + if cell.type != pv.CellType.TETRA: + ind.append(i) + mesh = mesh.remove_cells(ind) + mesh.clear_data() + + return mesh + + @property + def number_of_points(self: Cube) -> int: + """Return the number of points in the cube.""" + number_of_points: int = self.cube_data.number_of_points + return number_of_points + + @property + def number_of_cells(self: Cube) -> int: + """Return the number of cells in the cube.""" + number_of_cells: int = self.cube_data.number_of_cells + return number_of_cells + + @property + def volume(self: Cube) -> float: + """Return the volume of the cube.""" + volume: float = self.cube_data.volume + return volume class Polygon: diff --git a/tests/test_skgmsh.py b/tests/test_skgmsh.py index 429d301f..649727e7 100644 --- a/tests/test_skgmsh.py +++ b/tests/test_skgmsh.py @@ -9,7 +9,6 @@ import numpy as np import pytest -import pyvista as pv import skgmsh as sg @@ -37,8 +36,8 @@ def test_frontal_delaunay_2d( ) def test_delaunay_3d(target_sizes: float | Sequence[float] | None) -> None: """Delaunay 3D mesh algorithm test code.""" - edge_source = pv.Cube() - mesh = sg.delaunay_3d(edge_source, target_sizes=target_sizes) + edge_source = sg.Cube() + mesh = edge_source.delaunay_3d(edge_source, target_sizes=target_sizes) assert mesh.number_of_points > edge_source.number_of_points assert mesh.number_of_cells > edge_source.number_of_cells assert np.allclose(mesh.volume, edge_source.volume) From 43ab5b0f3f8888d435804166578928ee808c33c0 Mon Sep 17 00:00:00 2001 From: Tetsuo Koyama Date: Thu, 2 May 2024 00:25:57 +0900 Subject: [PATCH 2/2] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bec9203d..6fc48f7a 100644 --- a/README.md +++ b/README.md @@ -97,8 +97,8 @@ plotter.show(cpos="xy") We can also generate a 3D mesh. ```python -edge_source = sg.Cube() -mesh = sg.delaunay_3d(edge_source, target_sizes=0.2) +source = sg.Cube() +source = sg.delaunay_3d(edge_source=source, target_sizes=0.2) ``` ```python