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.
- {icplot-0.2.2/src/icplot.egg-info → icplot-0.2.4}/PKG-INFO +3 -2
- {icplot-0.2.2 → icplot-0.2.4}/pyproject.toml +3 -2
- icplot-0.2.4/src/icplot/geometry/__init__.py +4 -0
- icplot-0.2.4/src/icplot/geometry/base.py +62 -0
- icplot-0.2.4/src/icplot/geometry/operations.py +57 -0
- icplot-0.2.4/src/icplot/geometry/shapes.py +155 -0
- icplot-0.2.4/src/icplot/geometry/transform.py +66 -0
- icplot-0.2.4/src/icplot/mesh/__init__.py +6 -0
- icplot-0.2.4/src/icplot/mesh/cell.py +74 -0
- icplot-0.2.4/src/icplot/mesh/edge.py +31 -0
- icplot-0.2.4/src/icplot/mesh/mesh.py +107 -0
- icplot-0.2.4/src/icplot/mesh/openfoam/__init__.py +3 -0
- icplot-0.2.2/src/icplot/mesh/foam.py → icplot-0.2.4/src/icplot/mesh/openfoam/blockmesh.py +31 -38
- icplot-0.2.4/src/icplot/mesh/openfoam/foamfile.py +118 -0
- icplot-0.2.4/src/icplot/mesh/openfoam/polymesh.py +315 -0
- icplot-0.2.4/src/icplot/mesh/operations.py +120 -0
- icplot-0.2.4/src/icplot/mesh/shapes.py +323 -0
- icplot-0.2.4/src/icplot/mesh/vertex.py +80 -0
- {icplot-0.2.2 → icplot-0.2.4}/src/icplot/mesh/vtk.py +9 -6
- {icplot-0.2.2 → icplot-0.2.4}/src/icplot/tex.py +2 -1
- icplot-0.2.4/src/icplot/tree.py +104 -0
- {icplot-0.2.2 → icplot-0.2.4/src/icplot.egg-info}/PKG-INFO +3 -2
- {icplot-0.2.2 → icplot-0.2.4}/src/icplot.egg-info/SOURCES.txt +20 -4
- {icplot-0.2.2 → icplot-0.2.4}/src/icplot.egg-info/requires.txt +2 -1
- {icplot-0.2.2 → icplot-0.2.4}/test/test_geometry.py +5 -9
- icplot-0.2.4/test/test_meshing.py +94 -0
- icplot-0.2.4/test/test_openfoam.py +36 -0
- icplot-0.2.4/test/test_polymesh.py +124 -0
- icplot-0.2.4/test/test_tree.py +83 -0
- icplot-0.2.2/src/icplot/geometry.py +0 -209
- icplot-0.2.2/src/icplot/mesh/__init__.py +0 -1
- icplot-0.2.2/src/icplot/mesh/mesh.py +0 -280
- icplot-0.2.2/test/test_mesh.py +0 -36
- {icplot-0.2.2 → icplot-0.2.4}/LICENSE +0 -0
- {icplot-0.2.2 → icplot-0.2.4}/README.md +0 -0
- {icplot-0.2.2 → icplot-0.2.4}/setup.cfg +0 -0
- {icplot-0.2.2 → icplot-0.2.4}/src/icplot/__init__.py +0 -0
- {icplot-0.2.2 → icplot-0.2.4}/src/icplot/cairo_interface.py +0 -0
- {icplot-0.2.2 → icplot-0.2.4}/src/icplot/color.py +0 -0
- {icplot-0.2.2 → icplot-0.2.4}/src/icplot/convert.py +0 -0
- {icplot-0.2.2 → icplot-0.2.4}/src/icplot/gantt/__init__.py +0 -0
- {icplot-0.2.2 → icplot-0.2.4}/src/icplot/gantt/gantt.py +0 -0
- {icplot-0.2.2 → icplot-0.2.4}/src/icplot/gantt/gantt_renderer.py +0 -0
- {icplot-0.2.2 → icplot-0.2.4}/src/icplot/gantt/pgfgantt.py +0 -0
- {icplot-0.2.2 → icplot-0.2.4}/src/icplot/graph/__init__.py +0 -0
- {icplot-0.2.2 → icplot-0.2.4}/src/icplot/graph/axis.py +0 -0
- {icplot-0.2.2 → icplot-0.2.4}/src/icplot/graph/generator.py +0 -0
- {icplot-0.2.2 → icplot-0.2.4}/src/icplot/graph/matplotlib.py +0 -0
- {icplot-0.2.2 → icplot-0.2.4}/src/icplot/graph/mpl.py +0 -0
- {icplot-0.2.2 → icplot-0.2.4}/src/icplot/graph/plot.py +0 -0
- {icplot-0.2.2 → icplot-0.2.4}/src/icplot/graph/plot_group.py +0 -0
- {icplot-0.2.2 → icplot-0.2.4}/src/icplot/graph/series.py +0 -0
- {icplot-0.2.2 → icplot-0.2.4}/src/icplot/graph/video.py +0 -0
- {icplot-0.2.2 → icplot-0.2.4}/src/icplot/graph/vtk.py +0 -0
- {icplot-0.2.2 → icplot-0.2.4}/src/icplot/image_utils.py +0 -0
- {icplot-0.2.2 → icplot-0.2.4}/src/icplot/main_cli.py +0 -0
- {icplot-0.2.2 → icplot-0.2.4}/src/icplot/mermaid.py +0 -0
- {icplot-0.2.2 → icplot-0.2.4}/src/icplot/py.typed +0 -0
- {icplot-0.2.2 → icplot-0.2.4}/src/icplot/scene.py +0 -0
- {icplot-0.2.2 → icplot-0.2.4}/src/icplot/trace/__init__.py +0 -0
- {icplot-0.2.2 → icplot-0.2.4}/src/icplot/trace/trace.py +0 -0
- {icplot-0.2.2 → icplot-0.2.4}/src/icplot.egg-info/dependency_links.txt +0 -0
- {icplot-0.2.2 → icplot-0.2.4}/src/icplot.egg-info/entry_points.txt +0 -0
- {icplot-0.2.2 → icplot-0.2.4}/src/icplot.egg-info/top_level.txt +0 -0
- {icplot-0.2.2 → icplot-0.2.4}/test/test_cairo_interface.py +0 -0
- {icplot-0.2.2 → icplot-0.2.4}/test/test_gantt_chart.py +0 -0
- {icplot-0.2.2 → icplot-0.2.4}/test/test_image_utils.py +0 -0
- {icplot-0.2.2 → icplot-0.2.4}/test/test_line_plots.py +0 -0
- {icplot-0.2.2 → icplot-0.2.4}/test/test_mermaid.py +0 -0
- {icplot-0.2.2 → icplot-0.2.4}/test/test_plot_group.py +0 -0
- {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.
|
|
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.
|
|
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.
|
|
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.
|
|
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,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,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
|
+
)
|