icplot 0.2.2__tar.gz → 0.2.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. {icplot-0.2.2/src/icplot.egg-info → icplot-0.2.4}/PKG-INFO +3 -2
  2. {icplot-0.2.2 → icplot-0.2.4}/pyproject.toml +3 -2
  3. icplot-0.2.4/src/icplot/geometry/__init__.py +4 -0
  4. icplot-0.2.4/src/icplot/geometry/base.py +62 -0
  5. icplot-0.2.4/src/icplot/geometry/operations.py +57 -0
  6. icplot-0.2.4/src/icplot/geometry/shapes.py +155 -0
  7. icplot-0.2.4/src/icplot/geometry/transform.py +66 -0
  8. icplot-0.2.4/src/icplot/mesh/__init__.py +6 -0
  9. icplot-0.2.4/src/icplot/mesh/cell.py +74 -0
  10. icplot-0.2.4/src/icplot/mesh/edge.py +31 -0
  11. icplot-0.2.4/src/icplot/mesh/mesh.py +107 -0
  12. icplot-0.2.4/src/icplot/mesh/openfoam/__init__.py +3 -0
  13. icplot-0.2.2/src/icplot/mesh/foam.py → icplot-0.2.4/src/icplot/mesh/openfoam/blockmesh.py +31 -38
  14. icplot-0.2.4/src/icplot/mesh/openfoam/foamfile.py +118 -0
  15. icplot-0.2.4/src/icplot/mesh/openfoam/polymesh.py +315 -0
  16. icplot-0.2.4/src/icplot/mesh/operations.py +120 -0
  17. icplot-0.2.4/src/icplot/mesh/shapes.py +323 -0
  18. icplot-0.2.4/src/icplot/mesh/vertex.py +80 -0
  19. {icplot-0.2.2 → icplot-0.2.4}/src/icplot/mesh/vtk.py +9 -6
  20. {icplot-0.2.2 → icplot-0.2.4}/src/icplot/tex.py +2 -1
  21. icplot-0.2.4/src/icplot/tree.py +104 -0
  22. {icplot-0.2.2 → icplot-0.2.4/src/icplot.egg-info}/PKG-INFO +3 -2
  23. {icplot-0.2.2 → icplot-0.2.4}/src/icplot.egg-info/SOURCES.txt +20 -4
  24. {icplot-0.2.2 → icplot-0.2.4}/src/icplot.egg-info/requires.txt +2 -1
  25. {icplot-0.2.2 → icplot-0.2.4}/test/test_geometry.py +5 -9
  26. icplot-0.2.4/test/test_meshing.py +94 -0
  27. icplot-0.2.4/test/test_openfoam.py +36 -0
  28. icplot-0.2.4/test/test_polymesh.py +124 -0
  29. icplot-0.2.4/test/test_tree.py +83 -0
  30. icplot-0.2.2/src/icplot/geometry.py +0 -209
  31. icplot-0.2.2/src/icplot/mesh/__init__.py +0 -1
  32. icplot-0.2.2/src/icplot/mesh/mesh.py +0 -280
  33. icplot-0.2.2/test/test_mesh.py +0 -36
  34. {icplot-0.2.2 → icplot-0.2.4}/LICENSE +0 -0
  35. {icplot-0.2.2 → icplot-0.2.4}/README.md +0 -0
  36. {icplot-0.2.2 → icplot-0.2.4}/setup.cfg +0 -0
  37. {icplot-0.2.2 → icplot-0.2.4}/src/icplot/__init__.py +0 -0
  38. {icplot-0.2.2 → icplot-0.2.4}/src/icplot/cairo_interface.py +0 -0
  39. {icplot-0.2.2 → icplot-0.2.4}/src/icplot/color.py +0 -0
  40. {icplot-0.2.2 → icplot-0.2.4}/src/icplot/convert.py +0 -0
  41. {icplot-0.2.2 → icplot-0.2.4}/src/icplot/gantt/__init__.py +0 -0
  42. {icplot-0.2.2 → icplot-0.2.4}/src/icplot/gantt/gantt.py +0 -0
  43. {icplot-0.2.2 → icplot-0.2.4}/src/icplot/gantt/gantt_renderer.py +0 -0
  44. {icplot-0.2.2 → icplot-0.2.4}/src/icplot/gantt/pgfgantt.py +0 -0
  45. {icplot-0.2.2 → icplot-0.2.4}/src/icplot/graph/__init__.py +0 -0
  46. {icplot-0.2.2 → icplot-0.2.4}/src/icplot/graph/axis.py +0 -0
  47. {icplot-0.2.2 → icplot-0.2.4}/src/icplot/graph/generator.py +0 -0
  48. {icplot-0.2.2 → icplot-0.2.4}/src/icplot/graph/matplotlib.py +0 -0
  49. {icplot-0.2.2 → icplot-0.2.4}/src/icplot/graph/mpl.py +0 -0
  50. {icplot-0.2.2 → icplot-0.2.4}/src/icplot/graph/plot.py +0 -0
  51. {icplot-0.2.2 → icplot-0.2.4}/src/icplot/graph/plot_group.py +0 -0
  52. {icplot-0.2.2 → icplot-0.2.4}/src/icplot/graph/series.py +0 -0
  53. {icplot-0.2.2 → icplot-0.2.4}/src/icplot/graph/video.py +0 -0
  54. {icplot-0.2.2 → icplot-0.2.4}/src/icplot/graph/vtk.py +0 -0
  55. {icplot-0.2.2 → icplot-0.2.4}/src/icplot/image_utils.py +0 -0
  56. {icplot-0.2.2 → icplot-0.2.4}/src/icplot/main_cli.py +0 -0
  57. {icplot-0.2.2 → icplot-0.2.4}/src/icplot/mermaid.py +0 -0
  58. {icplot-0.2.2 → icplot-0.2.4}/src/icplot/py.typed +0 -0
  59. {icplot-0.2.2 → icplot-0.2.4}/src/icplot/scene.py +0 -0
  60. {icplot-0.2.2 → icplot-0.2.4}/src/icplot/trace/__init__.py +0 -0
  61. {icplot-0.2.2 → icplot-0.2.4}/src/icplot/trace/trace.py +0 -0
  62. {icplot-0.2.2 → icplot-0.2.4}/src/icplot.egg-info/dependency_links.txt +0 -0
  63. {icplot-0.2.2 → icplot-0.2.4}/src/icplot.egg-info/entry_points.txt +0 -0
  64. {icplot-0.2.2 → icplot-0.2.4}/src/icplot.egg-info/top_level.txt +0 -0
  65. {icplot-0.2.2 → icplot-0.2.4}/test/test_cairo_interface.py +0 -0
  66. {icplot-0.2.2 → icplot-0.2.4}/test/test_gantt_chart.py +0 -0
  67. {icplot-0.2.2 → icplot-0.2.4}/test/test_image_utils.py +0 -0
  68. {icplot-0.2.2 → icplot-0.2.4}/test/test_line_plots.py +0 -0
  69. {icplot-0.2.2 → icplot-0.2.4}/test/test_mermaid.py +0 -0
  70. {icplot-0.2.2 → icplot-0.2.4}/test/test_plot_group.py +0 -0
  71. {icplot-0.2.2 → icplot-0.2.4}/test/test_tex.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: icplot
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: Utilities for generating plots and graphics for technical reports.
5
5
  Author-email: "James Grogan, Irish Centre for High End Computing" <james.grogan@ichec.ie>
6
6
  Project-URL: Repository, https://git.ichec.ie/performance/toolshed/icplot
@@ -19,7 +19,8 @@ Requires-Dist: matplotlib
19
19
  Requires-Dist: vtk
20
20
  Requires-Dist: moviepy
21
21
  Requires-Dist: scipy
22
- Requires-Dist: iccore==0.2.2
22
+ Requires-Dist: iccore==0.2.3
23
+ Requires-Dist: graphviz
23
24
  Provides-Extra: test
24
25
  Requires-Dist: pytest; extra == "test"
25
26
  Requires-Dist: pytest-cov; extra == "test"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "icplot"
3
- version = "0.2.2"
3
+ version = "0.2.4"
4
4
  authors = [
5
5
  { name="James Grogan, Irish Centre for High End Computing", email="james.grogan@ichec.ie" },
6
6
  ]
@@ -21,7 +21,8 @@ dependencies = ["wand",
21
21
  "vtk",
22
22
  "moviepy",
23
23
  "scipy",
24
- "iccore==0.2.2"]
24
+ "iccore==0.2.3",
25
+ "graphviz"]
25
26
 
26
27
  [project.urls]
27
28
  Repository = "https://git.ichec.ie/performance/toolshed/icplot"
@@ -0,0 +1,4 @@
1
+ from .base import * # NOQA
2
+ from .operations import * # NOQA
3
+ from .shapes import * # NOQA
4
+ from .transform import * # NOQA
@@ -0,0 +1,62 @@
1
+ from __future__ import annotations
2
+ from dataclasses import dataclass
3
+
4
+ import numpy as np
5
+
6
+
7
+ @dataclass(frozen=True)
8
+ class Vector:
9
+ """
10
+ A 3d spatial vector
11
+ """
12
+
13
+ x: float
14
+ y: float = 0.0
15
+ z: float = 0.0
16
+
17
+ def scale(self, factor: float) -> Vector:
18
+ return Vector(self.x * factor, self.y * factor, self.z * factor)
19
+
20
+ def as_array(self) -> np.ndarray:
21
+ return np.array([self.x, self.y, self.z])
22
+
23
+ def subtract(self, v: Vector) -> Vector:
24
+ return Vector(self.x - v.x, self.y - v.y, self.z - v.z)
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class Point:
29
+ """
30
+ A location in 3d space
31
+ """
32
+
33
+ x: float
34
+ y: float = 0.0
35
+ z: float = 0.0
36
+
37
+ def as_array(self) -> np.ndarray:
38
+ return np.array([self.x, self.y, self.z])
39
+
40
+ def translate(self, v: Vector) -> Point:
41
+ return Point(self.x + v.x, self.y + v.y, self.z + v.z)
42
+
43
+ @staticmethod
44
+ def from_array(arr: np.ndarray) -> Point:
45
+ return Point(arr[0], arr[1], arr[2])
46
+
47
+ def subtract(self, v: Vector) -> Vector:
48
+ return Vector(self.x - v.x, self.y - v.y, self.z - v.z)
49
+
50
+
51
+ @dataclass(frozen=True)
52
+ class Bounds:
53
+ """
54
+ A bounding box
55
+ """
56
+
57
+ xmin: float
58
+ xmax: float
59
+ ymin: float
60
+ ymax: float
61
+ zmin: float = 0.0
62
+ zmax: float = 0.0
@@ -0,0 +1,57 @@
1
+ import numpy as np
2
+ from scipy.spatial.transform import Rotation
3
+
4
+
5
+ def get_rotation_matrix(vec0, vec1):
6
+ """
7
+ Get the matrix required to rotate vec0 to vec1
8
+ """
9
+ vec0 = np.reshape(vec0, (1, -1))
10
+ vec1 = np.reshape(vec1, (1, -1))
11
+ r = Rotation.align_vectors(vec0, vec1)
12
+ return r[0].as_matrix()
13
+
14
+
15
+ def get_three_point_normal(p0, p1, p2):
16
+ """
17
+ Get the normal to the plane given by three points
18
+ """
19
+ direction = np.cross(p1 - p0, p2 - p0)
20
+ return direction / np.linalg.norm(direction)
21
+
22
+
23
+ def segment_contains_point(v0, v1, p, tol: float = 1.0e-4) -> bool:
24
+ """
25
+ True if the line segment defined by v0 and v1 contains point p
26
+ """
27
+
28
+ dv0v1 = np.linalg.norm(v1 - v0)
29
+ dv0p = np.linalg.norm(v0 - p)
30
+ dv1p = np.linalg.norm(v1 - p)
31
+ return np.abs(dv0p + dv1p - dv0v1) < tol
32
+
33
+
34
+ def get_point_distance(p0, p1) -> float:
35
+ """
36
+ Return the distance between two points
37
+ """
38
+ return float(np.linalg.norm(p1 - p0))
39
+
40
+
41
+ def get_segment_distance(v0, v1, p) -> float:
42
+ """
43
+ Return the distance between the line segment v0-v1 and the point p
44
+ """
45
+
46
+ pv0 = p - v0
47
+ v1v0 = v1 - v0
48
+ side0 = np.dot(pv0, v1v0)
49
+ if side0 < 0.0:
50
+ return float(np.linalg.norm(pv0))
51
+ pv1 = p - v1
52
+ side1 = np.dot(pv1, v0 - v1)
53
+ if side1 < 0.0:
54
+ return float(np.linalg.norm(pv1))
55
+ length = np.linalg.norm(v1v0)
56
+ proj = (side1 * v0 + side0 * v1) / (length * length)
57
+ return float(np.linalg.norm(p - proj))
@@ -0,0 +1,155 @@
1
+ """
2
+ Module for primitive geometries. This is used to keep dependencies simple,
3
+ consider a real geometry library like cgal for more complex or performance
4
+ dependent work.
5
+ """
6
+
7
+ from __future__ import annotations
8
+ from dataclasses import dataclass
9
+
10
+ from .base import Point, Vector
11
+ from .transform import Transform
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class Shape:
16
+ """
17
+ A shape in 3d space, defaults to having a normal to Z
18
+ """
19
+
20
+ transform: Transform = Transform()
21
+
22
+
23
+ @dataclass(frozen=True)
24
+ class Circle(Shape):
25
+
26
+ radius: float = 1.0
27
+
28
+
29
+ @dataclass(frozen=True)
30
+ class Annulus(Shape):
31
+
32
+ outer_radius: float = 1.0
33
+ inner_radius: float = 0.5
34
+ angle: float = 360 # degrees
35
+
36
+
37
+ @dataclass(frozen=True)
38
+ class Quad(Shape):
39
+ """
40
+ A regular quadrilateral in 3d space, defaults to be normal to Z
41
+ """
42
+
43
+ width: float = 1.0
44
+ height: float = 1.0
45
+
46
+ @property
47
+ def points(self) -> list[Point]:
48
+
49
+ locs = ((0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0))
50
+
51
+ points = [
52
+ self.transform.get_point_at_offset(
53
+ self.width * loc[0], self.height * loc[1], 0.0
54
+ )
55
+ for loc in locs
56
+ ]
57
+ points = self.transform.centre_points(points)
58
+ return self.transform.align_points_with_normal(points)
59
+
60
+ def translate(self, v: Vector) -> Quad:
61
+ return Quad(self.transform.translate(v), self.width, self.height)
62
+
63
+
64
+ @dataclass(frozen=True)
65
+ class Cuboid(Shape):
66
+ """
67
+ A regular cuboid
68
+ """
69
+
70
+ width: float = 1.0
71
+ height: float = 1.0
72
+ depth: float = 1.0
73
+ top_width_scale: float = 1.0
74
+
75
+ def translate(self, v: Vector) -> Cuboid:
76
+ return Cuboid(self.transform.translate(v), self.width, self.height, self.depth)
77
+
78
+ @property
79
+ def points(self) -> list[Point]:
80
+
81
+ base = Quad(
82
+ self.transform.translate(Vector(0.0, 0.0, -self.depth / 2.0)),
83
+ self.width,
84
+ self.height,
85
+ )
86
+
87
+ top = Quad(
88
+ self.transform.translate(Vector(0.0, 0.0, -self.depth / 2.0)),
89
+ self.width * self.top_width_scale,
90
+ self.height,
91
+ )
92
+ top = top.translate(self.transform.normal.scale(self.depth))
93
+ return base.points + top.points
94
+
95
+
96
+ @dataclass(frozen=True)
97
+ class Cylinder(Shape):
98
+ """
99
+ A cylinder
100
+ """
101
+
102
+ diameter: float = 1.0
103
+ length: float = 1.0
104
+
105
+ @property
106
+ def start(self) -> Point:
107
+ return self.transform.location
108
+
109
+ @property
110
+ def end(self) -> Point:
111
+ return self.transform.get_point_at_normal_offset(self.length)
112
+
113
+
114
+ @dataclass(frozen=True)
115
+ class Revolution(Shape):
116
+ """
117
+ A revolved profile sitting on the plane given by the loc
118
+ and normal and revolved about the normal.
119
+ """
120
+
121
+ diameter: float = 1.0
122
+ length: float = 1.0
123
+ profile: str = "arc"
124
+
125
+
126
+ @dataclass(frozen=True)
127
+ class CuboidGrid:
128
+ """
129
+ A irregular grid composed of cuboids. Can be useful for
130
+ generating topological meshes like OpenFoam's blockMesh.
131
+ """
132
+
133
+ x_locs: list[float]
134
+ y_locs: list[float]
135
+ z_locs: list[float]
136
+
137
+ @property
138
+ def cuboids(self) -> list[Cuboid]:
139
+ ret = []
140
+
141
+ for kdx, z in enumerate(self.z_locs[:-1]):
142
+ for jdx, y in enumerate(self.y_locs[:-1]):
143
+ for idx, x in enumerate(self.x_locs[:-1]):
144
+ width = self.x_locs[idx + 1] - x
145
+ height = self.y_locs[jdx + 1] - y
146
+ depth = self.z_locs[kdx + 1] - z
147
+ ret.append(
148
+ Cuboid(
149
+ Transform(location=Point(x, y, z)),
150
+ width=width,
151
+ height=height,
152
+ depth=depth,
153
+ )
154
+ )
155
+ return ret
@@ -0,0 +1,66 @@
1
+ from __future__ import annotations
2
+ from dataclasses import dataclass
3
+
4
+ import numpy as np
5
+
6
+ from .base import Point, Vector
7
+ from .operations import get_three_point_normal, get_rotation_matrix
8
+
9
+
10
+ _GLOBAL_Z = np.array([0.0, 0.0, 1.0])
11
+
12
+
13
+ def get_normal_from_points(p0: Point, p1: Point, p2: Point):
14
+ return get_three_point_normal(p0.as_array(), p1.as_array(), p2.as_array())
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class Transform:
19
+ location: Point = Point(0.0, 0.0, 0.0)
20
+ normal: Vector = Vector(0.0, 0.0, 1.0)
21
+ scale: Vector = Vector(1.0, 1.0, 1.0)
22
+ angle: float = 0.0 # degrees
23
+
24
+ def translate(self, v: Vector) -> Transform:
25
+ return Transform(
26
+ self.location.translate(v), self.normal, self.scale, self.angle
27
+ )
28
+
29
+ def get_point_at_offset(self, x: float, y: float, z: float = 0.0) -> Point:
30
+ return self.location.translate(Vector(x, y, z))
31
+
32
+ def get_point_at_normal_offset(self, offset: float) -> Point:
33
+ return self.location.translate(self.normal.scale(offset))
34
+
35
+ def get_rotation_matrix(self, relative_to: Vector | None = None):
36
+ if relative_to:
37
+ return get_rotation_matrix(relative_to.as_array(), self.normal.as_array())
38
+ return get_rotation_matrix(_GLOBAL_Z, self.normal.as_array())
39
+
40
+ def centre_points(self, points: list[Point]) -> list[Point]:
41
+ x = sum(p.x for p in points) / len(points)
42
+ y = sum(p.y for p in points) / len(points)
43
+ z = sum(p.z for p in points) / len(points)
44
+ delta = self.location.subtract(Vector(x, y, z))
45
+ return [p.translate(delta) for p in points]
46
+
47
+ def align_points_with_normal(self, points: list[Point]) -> list[Point]:
48
+
49
+ rot = self.get_rotation_matrix()
50
+ rotated_points = [
51
+ Point.from_array(
52
+ rot.dot(p.as_array() - self.location.as_array())
53
+ + self.location.as_array()
54
+ )
55
+ for p in points
56
+ ]
57
+
58
+ plane_normal = get_normal_from_points(
59
+ rotated_points[0],
60
+ rotated_points[1],
61
+ rotated_points[3],
62
+ )
63
+
64
+ if np.dot(plane_normal, self.normal.as_array()) < 0:
65
+ rotated_points.reverse()
66
+ return rotated_points
@@ -0,0 +1,6 @@
1
+ from .cell import * # NOQA
2
+ from .edge import * # NOQA
3
+ from .vertex import * # NOQA
4
+ from .mesh import * # NOQA
5
+ from .operations import * # NOQA
6
+ from .shapes import * # NOQA
@@ -0,0 +1,74 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass(frozen=True)
5
+ class Cell:
6
+ """
7
+ A mesh cell
8
+ """
9
+
10
+ vertices: tuple[int, ...]
11
+ type: str = ""
12
+
13
+ def get_edges(self) -> tuple:
14
+ raise NotImplementedError()
15
+
16
+
17
+ def flip_normals(cell: Cell) -> Cell:
18
+ if cell.type == "quad":
19
+ return Cell(type=cell.type, vertices=tuple(reversed(cell.vertices)))
20
+ else:
21
+ raise RuntimeError("Not implemented for this cell type")
22
+
23
+
24
+ def get_hex_faces(cell: Cell) -> tuple[tuple[int, ...], ...]:
25
+ if cell.type != "hex":
26
+ raise RuntimeError("Can only get hex faces for hex cells")
27
+
28
+ return (
29
+ (cell.vertices[0], cell.vertices[1], cell.vertices[5], cell.vertices[4]),
30
+ (cell.vertices[1], cell.vertices[2], cell.vertices[6], cell.vertices[5]),
31
+ (cell.vertices[2], cell.vertices[3], cell.vertices[7], cell.vertices[6]),
32
+ (cell.vertices[3], cell.vertices[0], cell.vertices[4], cell.vertices[7]),
33
+ (cell.vertices[0], cell.vertices[3], cell.vertices[2], cell.vertices[1]),
34
+ (cell.vertices[4], cell.vertices[5], cell.vertices[6], cell.vertices[7]),
35
+ )
36
+
37
+
38
+ @dataclass(frozen=True)
39
+ class HexCell(Cell):
40
+ """
41
+ A hex mesh element - used in openfoam meshing
42
+ """
43
+
44
+ cell_counts: tuple[int, ...] = (1, 1, 1)
45
+ grading: str = "simpleGrading"
46
+ grading_ratios: tuple[int, ...] = (1, 1, 1)
47
+ type = "hex"
48
+
49
+ def get_top_edges(self) -> tuple:
50
+ return (
51
+ (self.vertices[0], self.vertices[1]),
52
+ (self.vertices[1], self.vertices[2]),
53
+ (self.vertices[2], self.vertices[3]),
54
+ (self.vertices[3], self.vertices[0]),
55
+ )
56
+
57
+ def get_bottom_edges(self) -> tuple:
58
+ return (
59
+ (self.vertices[4], self.vertices[5]),
60
+ (self.vertices[5], self.vertices[6]),
61
+ (self.vertices[6], self.vertices[7]),
62
+ (self.vertices[7], self.vertices[4]),
63
+ )
64
+
65
+ def get_side_edges(self) -> tuple:
66
+ return (
67
+ (self.vertices[0], self.vertices[4]),
68
+ (self.vertices[1], self.vertices[5]),
69
+ (self.vertices[2], self.vertices[6]),
70
+ (self.vertices[3], self.vertices[7]),
71
+ )
72
+
73
+ def get_edges(self) -> tuple:
74
+ return self.get_bottom_edges() + self.get_top_edges() + self.get_side_edges()
@@ -0,0 +1,31 @@
1
+ from dataclasses import dataclass
2
+
3
+ from icplot.geometry import Point, segment_contains_point, get_segment_distance
4
+
5
+ from .vertex import Vertex
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class Edge:
10
+ """
11
+ A mesh edge - consists of two verts
12
+ """
13
+
14
+ vert0: int
15
+ vert1: int
16
+ type: str = "line"
17
+ interp_points: tuple[Point, ...] = ()
18
+
19
+
20
+ def contains_point(v0: Vertex, v1: Vertex, p: Point, tol: float = 1.0e-4) -> bool:
21
+ """
22
+ True if the edge given by v0 and v1 contains point p
23
+ """
24
+ return segment_contains_point(v0.as_array(), v1.as_array(), p.as_array(), tol)
25
+
26
+
27
+ def get_distance(v0: Vertex, v1: Vertex, p: Point) -> float:
28
+ """
29
+ Return the distance between the point and the edge
30
+ """
31
+ return get_segment_distance(v0.as_array(), v1.as_array(), p.as_array())
@@ -0,0 +1,107 @@
1
+ """
2
+ Module for describing mesh elements. Goes for simplicity and low dependencies
3
+ over performance. Consider something else for high performance meshing.
4
+ """
5
+
6
+ from __future__ import annotations
7
+ from dataclasses import dataclass
8
+
9
+ from icplot.geometry import Point, Vector, Transform, Bounds
10
+
11
+ from .vertex import Vertex, find_closest_vertex
12
+ from .cell import Cell, flip_normals
13
+ from .edge import Edge, contains_point, get_distance
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class Patch:
18
+ """
19
+ A collection of mesh faces - used for openfoam boundaries
20
+ """
21
+
22
+ type: str
23
+ name: str
24
+ faces: tuple[tuple[int, ...], ...]
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class Mesh:
29
+
30
+ vertices: tuple[Vertex, ...]
31
+ cells: tuple[Cell, ...]
32
+ scale: float = 1.0
33
+ edges: tuple[Edge, ...] = ()
34
+ patches: tuple[Patch, ...] = ()
35
+
36
+ def get_bounds(self) -> Bounds:
37
+ if len(self.vertices) == 0:
38
+ raise RuntimeError("Can't get bounds on empty mesh")
39
+
40
+ xmin = self.vertices[0].x
41
+ xmax = xmin
42
+ ymin = self.vertices[0].y
43
+ ymax = ymin
44
+ zmin = self.vertices[0].z
45
+ zmax = zmin
46
+ for v in self.vertices:
47
+ xmin = min(xmin, v.x)
48
+ xmax = max(xmax, v.x)
49
+ ymin = min(ymin, v.y)
50
+ ymax = max(ymax, v.y)
51
+ zmin = min(zmin, v.z)
52
+ zmax = min(zmax, v.z)
53
+ return Bounds(xmin=xmin, xmax=xmax, ymin=ymin, ymax=ymax, zmin=zmin, zmax=zmax)
54
+
55
+ def flip_normals(self) -> Mesh:
56
+ return Mesh(
57
+ vertices=self.vertices, cells=tuple(flip_normals(c) for c in self.cells)
58
+ )
59
+
60
+ def get_location(self) -> Vector:
61
+ bounds = self.get_bounds()
62
+ return Vector(bounds.xmin, bounds.ymin, bounds.zmin)
63
+
64
+ def apply_transform(self, t: Transform) -> Mesh:
65
+ delta = t.location.subtract(self.get_location())
66
+ return Mesh(
67
+ vertices=tuple(v.translate(delta) for v in self.vertices), cells=self.cells
68
+ )
69
+
70
+ def move_by(self, x: float, y: float, z: float) -> Mesh:
71
+ delta = Vector(x, y, z)
72
+ return Mesh(
73
+ vertices=tuple(v.translate(delta) for v in self.vertices), cells=self.cells
74
+ )
75
+
76
+ def select_edge(self, p: Point) -> tuple[int, int]:
77
+
78
+ for c in self.cells:
79
+ for e in c.get_edges():
80
+ if contains_point(self.vertices[e[0]], self.vertices[e[1]], p):
81
+ return (e[0], e[1])
82
+ raise RuntimeError("No edge found at selection point")
83
+
84
+ def get_closest_vertex(self, v: Vertex) -> Vertex:
85
+ return find_closest_vertex(self.vertices, v)
86
+
87
+ def get_closest_edge(self, p: Point) -> tuple[int, int]:
88
+
89
+ min_dist = -1.0
90
+ closest_edges: tuple[int, int] = (-1, -1)
91
+ for b in self.cells:
92
+ for e in b.get_edges():
93
+ dist = get_distance(self.vertices[e[0]], self.vertices[e[1]], p)
94
+ if min_dist == -1.0:
95
+ min_dist = dist
96
+ closest_edges = (e[0], e[1])
97
+ continue
98
+ if dist < min_dist:
99
+ min_dist = dist
100
+ closest_edges = (e[0], e[1])
101
+ return closest_edges
102
+
103
+ def copy(self) -> Mesh:
104
+ return Mesh(
105
+ vertices=tuple(Vertex(v.x, v.y, v.z, v.id) for v in self.vertices),
106
+ cells=tuple(Cell(c.vertices, c.type) for c in self.cells),
107
+ )
@@ -0,0 +1,3 @@
1
+ from .foamfile import * # NOQA
2
+ from .blockmesh import * # NOQA
3
+ from .polymesh import * # NOQA