icplot 0.1.6__tar.gz → 0.1.7__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.1.6/src/icplot.egg-info → icplot-0.1.7}/PKG-INFO +2 -1
- {icplot-0.1.6 → icplot-0.1.7}/pyproject.toml +4 -3
- {icplot-0.1.6 → icplot-0.1.7}/src/icplot/geometry.py +69 -11
- {icplot-0.1.6 → icplot-0.1.7}/src/icplot/graph/vtk.py +21 -2
- {icplot-0.1.6 → icplot-0.1.7}/src/icplot/image_utils.py +35 -4
- icplot-0.1.7/src/icplot/mesh/foam.py +140 -0
- icplot-0.1.7/src/icplot/mesh/mesh.py +274 -0
- icplot-0.1.7/src/icplot/mesh/vtk.py +44 -0
- {icplot-0.1.6 → icplot-0.1.7/src/icplot.egg-info}/PKG-INFO +2 -1
- {icplot-0.1.6 → icplot-0.1.7}/src/icplot.egg-info/SOURCES.txt +2 -0
- {icplot-0.1.6 → icplot-0.1.7}/src/icplot.egg-info/requires.txt +1 -0
- icplot-0.1.7/test/test_geometry.py +69 -0
- icplot-0.1.7/test/test_mesh.py +36 -0
- icplot-0.1.6/src/icplot/mesh/foam.py +0 -56
- icplot-0.1.6/src/icplot/mesh/mesh.py +0 -79
- icplot-0.1.6/test/test_mesh.py +0 -13
- {icplot-0.1.6 → icplot-0.1.7}/LICENSE +0 -0
- {icplot-0.1.6 → icplot-0.1.7}/README.md +0 -0
- {icplot-0.1.6 → icplot-0.1.7}/setup.cfg +0 -0
- {icplot-0.1.6 → icplot-0.1.7}/src/icplot/__init__.py +0 -0
- {icplot-0.1.6 → icplot-0.1.7}/src/icplot/cairo_interface.py +0 -0
- {icplot-0.1.6 → icplot-0.1.7}/src/icplot/color.py +0 -0
- {icplot-0.1.6 → icplot-0.1.7}/src/icplot/convert.py +0 -0
- {icplot-0.1.6 → icplot-0.1.7}/src/icplot/gantt/__init__.py +0 -0
- {icplot-0.1.6 → icplot-0.1.7}/src/icplot/gantt/gantt.py +0 -0
- {icplot-0.1.6 → icplot-0.1.7}/src/icplot/gantt/gantt_renderer.py +0 -0
- {icplot-0.1.6 → icplot-0.1.7}/src/icplot/gantt/pgfgantt.py +0 -0
- {icplot-0.1.6 → icplot-0.1.7}/src/icplot/graph/__init__.py +0 -0
- {icplot-0.1.6 → icplot-0.1.7}/src/icplot/graph/axis.py +0 -0
- {icplot-0.1.6 → icplot-0.1.7}/src/icplot/graph/generator.py +0 -0
- {icplot-0.1.6 → icplot-0.1.7}/src/icplot/graph/matplotlib.py +0 -0
- {icplot-0.1.6 → icplot-0.1.7}/src/icplot/graph/mpl.py +0 -0
- {icplot-0.1.6 → icplot-0.1.7}/src/icplot/graph/plot.py +0 -0
- {icplot-0.1.6 → icplot-0.1.7}/src/icplot/graph/plot_group.py +0 -0
- {icplot-0.1.6 → icplot-0.1.7}/src/icplot/graph/series.py +0 -0
- {icplot-0.1.6 → icplot-0.1.7}/src/icplot/graph/video.py +0 -0
- {icplot-0.1.6 → icplot-0.1.7}/src/icplot/main_cli.py +0 -0
- {icplot-0.1.6 → icplot-0.1.7}/src/icplot/mermaid.py +0 -0
- {icplot-0.1.6 → icplot-0.1.7}/src/icplot/mesh/__init__.py +0 -0
- {icplot-0.1.6 → icplot-0.1.7}/src/icplot/py.typed +0 -0
- {icplot-0.1.6 → icplot-0.1.7}/src/icplot/scene.py +0 -0
- {icplot-0.1.6 → icplot-0.1.7}/src/icplot/tex.py +0 -0
- {icplot-0.1.6 → icplot-0.1.7}/src/icplot/trace/__init__.py +0 -0
- {icplot-0.1.6 → icplot-0.1.7}/src/icplot/trace/trace.py +0 -0
- {icplot-0.1.6 → icplot-0.1.7}/src/icplot.egg-info/dependency_links.txt +0 -0
- {icplot-0.1.6 → icplot-0.1.7}/src/icplot.egg-info/entry_points.txt +0 -0
- {icplot-0.1.6 → icplot-0.1.7}/src/icplot.egg-info/top_level.txt +0 -0
- {icplot-0.1.6 → icplot-0.1.7}/test/test_cairo_interface.py +0 -0
- {icplot-0.1.6 → icplot-0.1.7}/test/test_gantt_chart.py +0 -0
- {icplot-0.1.6 → icplot-0.1.7}/test/test_image_utils.py +0 -0
- {icplot-0.1.6 → icplot-0.1.7}/test/test_line_plots.py +0 -0
- {icplot-0.1.6 → icplot-0.1.7}/test/test_mermaid.py +0 -0
- {icplot-0.1.6 → icplot-0.1.7}/test/test_tex.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: icplot
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.7
|
|
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
|
|
@@ -18,6 +18,7 @@ Requires-Dist: wand
|
|
|
18
18
|
Requires-Dist: matplotlib
|
|
19
19
|
Requires-Dist: vtk
|
|
20
20
|
Requires-Dist: moviepy
|
|
21
|
+
Requires-Dist: scipy
|
|
21
22
|
Requires-Dist: iccore==0.1.13
|
|
22
23
|
Provides-Extra: test
|
|
23
24
|
Requires-Dist: pytest; extra == "test"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "icplot"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.7"
|
|
4
4
|
authors = [
|
|
5
5
|
{ name="James Grogan, Irish Centre for High End Computing", email="james.grogan@ichec.ie" },
|
|
6
6
|
]
|
|
@@ -20,6 +20,7 @@ dependencies = ["wand",
|
|
|
20
20
|
"matplotlib",
|
|
21
21
|
"vtk",
|
|
22
22
|
"moviepy",
|
|
23
|
+
"scipy",
|
|
23
24
|
"iccore==0.1.13"]
|
|
24
25
|
|
|
25
26
|
[project.urls]
|
|
@@ -50,7 +51,7 @@ build-backend = "setuptools.build_meta"
|
|
|
50
51
|
icplot = "icplot.main_cli:main_cli"
|
|
51
52
|
|
|
52
53
|
[tool.setuptools.package-data]
|
|
53
|
-
"icplot" = ["py.typed"]
|
|
54
|
+
"icplot" = ["py.typed", "controlDict"]
|
|
54
55
|
|
|
55
56
|
[tool.setuptools.packages.find]
|
|
56
57
|
where = ["src"]
|
|
@@ -61,7 +62,7 @@ ignore_missing_imports = true
|
|
|
61
62
|
[tool.pylint.main]
|
|
62
63
|
|
|
63
64
|
source-roots = ["src"]
|
|
64
|
-
fail-under = 9.
|
|
65
|
+
fail-under = 9.2
|
|
65
66
|
|
|
66
67
|
[tool.pylint.basic]
|
|
67
68
|
|
|
@@ -5,9 +5,11 @@ dependent work.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
|
-
import copy
|
|
9
8
|
from dataclasses import dataclass
|
|
10
9
|
|
|
10
|
+
import numpy as np
|
|
11
|
+
from scipy.spatial.transform import Rotation
|
|
12
|
+
|
|
11
13
|
|
|
12
14
|
@dataclass(frozen=True)
|
|
13
15
|
class Vector:
|
|
@@ -22,6 +24,9 @@ class Vector:
|
|
|
22
24
|
def scale(self, factor: float) -> Vector:
|
|
23
25
|
return Vector(self.x * factor, self.y * factor, self.z * factor)
|
|
24
26
|
|
|
27
|
+
def as_array(self) -> np.ndarray:
|
|
28
|
+
return np.array([self.x, self.y, self.z])
|
|
29
|
+
|
|
25
30
|
|
|
26
31
|
@dataclass(frozen=True)
|
|
27
32
|
class Point:
|
|
@@ -33,9 +38,16 @@ class Point:
|
|
|
33
38
|
y: float = 0.0
|
|
34
39
|
z: float = 0.0
|
|
35
40
|
|
|
41
|
+
def as_array(self) -> np.ndarray:
|
|
42
|
+
return np.array([self.x, self.y, self.z])
|
|
43
|
+
|
|
36
44
|
def translate(self, v: Vector) -> Point:
|
|
37
45
|
return Point(self.x + v.x, self.y + v.y, self.z + v.z)
|
|
38
46
|
|
|
47
|
+
@staticmethod
|
|
48
|
+
def from_array(arr: np.ndarray) -> Point:
|
|
49
|
+
return Point(arr[0], arr[1], arr[2])
|
|
50
|
+
|
|
39
51
|
|
|
40
52
|
@dataclass(frozen=True)
|
|
41
53
|
class Shape:
|
|
@@ -43,10 +55,26 @@ class Shape:
|
|
|
43
55
|
A shape in 3d space, defaults to having a normal to Z
|
|
44
56
|
"""
|
|
45
57
|
|
|
46
|
-
loc: Point
|
|
58
|
+
loc: Point = Point(0.0, 0.0, 0.0)
|
|
47
59
|
normal: Vector = Vector(0.0, 0.0, 1.0)
|
|
48
60
|
|
|
49
61
|
|
|
62
|
+
def get_rotation_matrix(vec0, vec1):
|
|
63
|
+
"""get rotation matrix between two vectors using scipy"""
|
|
64
|
+
vec0 = np.reshape(vec0, (1, -1))
|
|
65
|
+
vec1 = np.reshape(vec1, (1, -1))
|
|
66
|
+
r = Rotation.align_vectors(vec0, vec1)
|
|
67
|
+
return r[0].as_matrix()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def get_normal(p0, p1, p2):
|
|
71
|
+
direction = np.cross(p1 - p0, p2 - p0)
|
|
72
|
+
return direction / np.linalg.norm(direction)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
_GLOBAL_Z = np.array([0.0, 0.0, 1.0])
|
|
76
|
+
|
|
77
|
+
|
|
50
78
|
@dataclass(frozen=True)
|
|
51
79
|
class Quad(Shape):
|
|
52
80
|
"""
|
|
@@ -58,14 +86,32 @@ class Quad(Shape):
|
|
|
58
86
|
|
|
59
87
|
@property
|
|
60
88
|
def points(self) -> list[Point]:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
89
|
+
|
|
90
|
+
points = [
|
|
91
|
+
self.loc.translate(Vector(-self.width / 2.0, -self.height / 2.0)),
|
|
92
|
+
self.loc.translate(Vector(self.width / 2.0, -self.height / 2.0)),
|
|
93
|
+
self.loc.translate(Vector(self.width / 2.0, self.height / 2.0)),
|
|
94
|
+
self.loc.translate(Vector(-self.width / 2.0, self.height / 2.0)),
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
rot = get_rotation_matrix(_GLOBAL_Z, self.normal.as_array())
|
|
98
|
+
rotated_points = [
|
|
99
|
+
Point.from_array(
|
|
100
|
+
rot.dot(p.as_array() - self.loc.as_array()) + self.loc.as_array()
|
|
101
|
+
)
|
|
102
|
+
for p in points
|
|
67
103
|
]
|
|
68
104
|
|
|
105
|
+
plane_normal = get_normal(
|
|
106
|
+
rotated_points[0].as_array(),
|
|
107
|
+
rotated_points[1].as_array(),
|
|
108
|
+
rotated_points[3].as_array(),
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
if np.dot(plane_normal, self.normal.as_array()) < 0:
|
|
112
|
+
rotated_points.reverse()
|
|
113
|
+
return rotated_points
|
|
114
|
+
|
|
69
115
|
def translate(self, v: Vector) -> Quad:
|
|
70
116
|
return Quad(self.loc.translate(v), self.normal, self.width, self.height)
|
|
71
117
|
|
|
@@ -76,10 +122,10 @@ class Cuboid(Shape):
|
|
|
76
122
|
A regular cuboid
|
|
77
123
|
"""
|
|
78
124
|
|
|
79
|
-
loc: Point
|
|
80
125
|
width: float = 1.0
|
|
81
126
|
height: float = 1.0
|
|
82
127
|
depth: float = 1.0
|
|
128
|
+
top_width_scale: float = 1.0
|
|
83
129
|
|
|
84
130
|
def translate(self, v: Vector) -> Cuboid:
|
|
85
131
|
return Cuboid(
|
|
@@ -89,8 +135,20 @@ class Cuboid(Shape):
|
|
|
89
135
|
@property
|
|
90
136
|
def points(self) -> list[Point]:
|
|
91
137
|
|
|
92
|
-
base = Quad(
|
|
93
|
-
|
|
138
|
+
base = Quad(
|
|
139
|
+
self.loc.translate(Vector(0.0, 0.0, -self.depth / 2.0)),
|
|
140
|
+
self.normal,
|
|
141
|
+
self.width,
|
|
142
|
+
self.height,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
top = Quad(
|
|
146
|
+
self.loc.translate(Vector(0.0, 0.0, -self.depth / 2.0)),
|
|
147
|
+
self.normal,
|
|
148
|
+
self.width * self.top_width_scale,
|
|
149
|
+
self.height,
|
|
150
|
+
)
|
|
151
|
+
top = top.translate(self.normal.scale(self.depth))
|
|
94
152
|
return base.points + top.points
|
|
95
153
|
|
|
96
154
|
|
|
@@ -1,17 +1,33 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
|
+
import logging
|
|
2
3
|
|
|
3
4
|
import numpy as np
|
|
4
|
-
import vtk
|
|
5
|
-
from vtk.util import numpy_support
|
|
6
5
|
|
|
7
6
|
from iccore.data.units import DateRange
|
|
8
7
|
from iccore.data import Series
|
|
9
8
|
|
|
10
9
|
from .plot_group import PlotGroup
|
|
11
10
|
|
|
11
|
+
_HAS_VTK = True
|
|
12
|
+
try:
|
|
13
|
+
import vtk
|
|
14
|
+
from vtk.util import numpy_support
|
|
15
|
+
except ImportError as e:
|
|
16
|
+
logging.getLogger(__name__).warning(
|
|
17
|
+
"Disabling VTK stupport. Failed to load with: %s", e
|
|
18
|
+
)
|
|
19
|
+
_HAS_VTK = False
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def has_vtk() -> bool:
|
|
23
|
+
return _HAS_VTK
|
|
24
|
+
|
|
12
25
|
|
|
13
26
|
def to_grid(series: Series, dates: DateRange | None):
|
|
14
27
|
|
|
28
|
+
if not has_vtk():
|
|
29
|
+
raise RuntimeError("VTK support failed to load")
|
|
30
|
+
|
|
15
31
|
if not series.x:
|
|
16
32
|
raise ValueError("Attempting to plot a series with no data")
|
|
17
33
|
|
|
@@ -45,6 +61,9 @@ def to_grid(series: Series, dates: DateRange | None):
|
|
|
45
61
|
|
|
46
62
|
def save(path: Path, config: PlotGroup, series: Series, filename: str = "series"):
|
|
47
63
|
|
|
64
|
+
if not has_vtk():
|
|
65
|
+
raise RuntimeError("VTK support failed to load")
|
|
66
|
+
|
|
48
67
|
grid = to_grid(series, config.date_range)
|
|
49
68
|
|
|
50
69
|
writer = vtk.vtkStructuredGridWriter()
|
|
@@ -7,14 +7,36 @@ import logging
|
|
|
7
7
|
import os
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
import
|
|
13
|
-
|
|
10
|
+
_HAS_WAND = True
|
|
11
|
+
try:
|
|
12
|
+
from wand.image import Image
|
|
13
|
+
from wand.color import Color
|
|
14
|
+
except ImportError as e:
|
|
15
|
+
logging.getLogger(__name__).warning(
|
|
16
|
+
"Disabling Wand stupport. Failed to load with: %s", e
|
|
17
|
+
)
|
|
18
|
+
_HAS_WAND = False
|
|
19
|
+
|
|
20
|
+
_HAS_CAIRO_SVG = True
|
|
21
|
+
try:
|
|
22
|
+
import cairosvg
|
|
23
|
+
except ImportError as e:
|
|
24
|
+
logging.getLogger(__name__).warning(
|
|
25
|
+
"Disabling CairoSVG stupport. Failed to load with: %s", e
|
|
26
|
+
)
|
|
27
|
+
_HAS_CAIRO_SVG = False
|
|
14
28
|
|
|
15
29
|
logger = logging.getLogger(__name__)
|
|
16
30
|
|
|
17
31
|
|
|
32
|
+
def has_wand() -> bool:
|
|
33
|
+
return _HAS_WAND
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def has_cairo_svg() -> bool:
|
|
37
|
+
return _HAS_CAIRO_SVG
|
|
38
|
+
|
|
39
|
+
|
|
18
40
|
def _get_out_filename(source: Path, target: Path | None, extension: str) -> Path:
|
|
19
41
|
if target:
|
|
20
42
|
return target
|
|
@@ -26,6 +48,9 @@ def pdf_to_png(source: Path, target: Path | None = None, resolution: int = 300):
|
|
|
26
48
|
Convert a pdf to png with white background
|
|
27
49
|
"""
|
|
28
50
|
|
|
51
|
+
if not has_wand():
|
|
52
|
+
raise RuntimeError("Loading Wand failed - no pdf to png support")
|
|
53
|
+
|
|
29
54
|
outfile = _get_out_filename(source, target, "png")
|
|
30
55
|
os.makedirs(outfile.parent, exist_ok=True)
|
|
31
56
|
|
|
@@ -41,6 +66,9 @@ def svg_to_png(source: Path, target: Path | None = None):
|
|
|
41
66
|
Convert an svg to png
|
|
42
67
|
"""
|
|
43
68
|
|
|
69
|
+
if not has_cairo_svg():
|
|
70
|
+
raise RuntimeError("Loading Cairosvg failed - no svg to png support")
|
|
71
|
+
|
|
44
72
|
outfile = _get_out_filename(source, target, "png")
|
|
45
73
|
os.makedirs(outfile.parent, exist_ok=True)
|
|
46
74
|
cairosvg.svg2png(url=str(source), write_to=str(outfile))
|
|
@@ -51,6 +79,9 @@ def svg_to_pdf(source: Path, target: Path | None = None):
|
|
|
51
79
|
Convert an svg to pdf
|
|
52
80
|
"""
|
|
53
81
|
|
|
82
|
+
if not has_cairo_svg():
|
|
83
|
+
raise RuntimeError("Loading Cairosvg failed - no svg to pdf support")
|
|
84
|
+
|
|
54
85
|
outfile = _get_out_filename(source, target, "pdf")
|
|
55
86
|
os.makedirs(outfile.parent, exist_ok=True)
|
|
56
87
|
cairosvg.svg2pdf(url=str(source), write_to=str(outfile))
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Meshing utilities for use in openfoam
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import shutil
|
|
6
|
+
import subprocess
|
|
7
|
+
import os
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
from .mesh import Mesh, Block, Edge, Patch
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class FoamFile:
|
|
16
|
+
|
|
17
|
+
version: str = "2.0"
|
|
18
|
+
format: str = "ascii"
|
|
19
|
+
arch: str = "LSB;label=32;scalar=64"
|
|
20
|
+
foam_class: str = "dictionary"
|
|
21
|
+
location: str = "system"
|
|
22
|
+
object: str = "blockMeshDict"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _get_foam_header(spec: FoamFile) -> str:
|
|
26
|
+
|
|
27
|
+
output = "FoamFile\n{"
|
|
28
|
+
output += f"\tversion\t{spec.version};\n"
|
|
29
|
+
output += f"\tformat\t{spec.format};\n"
|
|
30
|
+
output += f'\tarch\t"{spec.arch}";\n'
|
|
31
|
+
output += f"\tclass\t{spec.foam_class};\n"
|
|
32
|
+
output += f'\tlocation\t"{spec.location}";\n'
|
|
33
|
+
output += f"\tobject\t{spec.object};\n"
|
|
34
|
+
output += "}\n\n"
|
|
35
|
+
return output
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def has_openfoam() -> bool:
|
|
39
|
+
return bool(shutil.which("openfoam")) and bool(shutil.which("blockMesh"))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _mesh_blocks(blocks: tuple[Block, ...]) -> str:
|
|
43
|
+
|
|
44
|
+
output = "blocks\n(\n"
|
|
45
|
+
for b in blocks:
|
|
46
|
+
verts = " ".join(str(v) for v in b.vertices)
|
|
47
|
+
output += f"\thex ({verts})\n"
|
|
48
|
+
|
|
49
|
+
counts = " ".join(str(c) for c in b.cell_counts)
|
|
50
|
+
output += f"\t({counts})\n"
|
|
51
|
+
|
|
52
|
+
grades = " ".join(str(g) for g in b.grading_ratios)
|
|
53
|
+
output += f"\t{b.grading} ({grades})\n"
|
|
54
|
+
output += ");\n\n"
|
|
55
|
+
return output
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _mesh_edges(edges: tuple[Edge, ...]) -> str:
|
|
59
|
+
|
|
60
|
+
if not edges:
|
|
61
|
+
return ""
|
|
62
|
+
|
|
63
|
+
output = "edges\n(\n"
|
|
64
|
+
for e in edges:
|
|
65
|
+
if e.type == "line":
|
|
66
|
+
output += f"\tline {e.vert0} {e.vert1}\n"
|
|
67
|
+
else:
|
|
68
|
+
interps = ""
|
|
69
|
+
for p in e.interp_points:
|
|
70
|
+
interps += f"({p.x} {p.y} {p.z}) "
|
|
71
|
+
output += f"\t {e.type} {e.vert0} {e.vert1} {interps}\n"
|
|
72
|
+
output += ");\n\n"
|
|
73
|
+
return output
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _mesh_boundaries(patches: tuple[Patch, ...]) -> str:
|
|
77
|
+
|
|
78
|
+
if not patches:
|
|
79
|
+
return ""
|
|
80
|
+
|
|
81
|
+
output = "boundary\n(\n"
|
|
82
|
+
for patch in patches:
|
|
83
|
+
output += f"\t{patch.name}\n"
|
|
84
|
+
output += "\t{\n"
|
|
85
|
+
output += f"\t\ttype {patch.type};\n"
|
|
86
|
+
output += "\t\tfaces\n\t\t(\n"
|
|
87
|
+
for f in patch.faces:
|
|
88
|
+
faces = " ".join(str(idx) for idx in f)
|
|
89
|
+
output += f"\t\t\t({faces})\n"
|
|
90
|
+
output += "\t\t);\n"
|
|
91
|
+
output += "\t}\n"
|
|
92
|
+
output += ");\n\n"
|
|
93
|
+
return output
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def mesh_to_foam(mesh: Mesh, spec: FoamFile = FoamFile()) -> str:
|
|
97
|
+
"""
|
|
98
|
+
Given a mesh, write it in openfoam blockMesh format
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
output = _get_foam_header(spec)
|
|
102
|
+
|
|
103
|
+
output += f"scale\t{mesh.scale};\n"
|
|
104
|
+
|
|
105
|
+
output += "vertices\n(\n"
|
|
106
|
+
for idx, v in enumerate(mesh.vertices):
|
|
107
|
+
output += f"\t( {v.x} {v.y} {v.z} ) // # {idx} \n"
|
|
108
|
+
output += ");\n\n"
|
|
109
|
+
|
|
110
|
+
output += _mesh_edges(mesh.edges)
|
|
111
|
+
|
|
112
|
+
output += _mesh_blocks(mesh.blocks)
|
|
113
|
+
|
|
114
|
+
output += _mesh_boundaries(mesh.patches)
|
|
115
|
+
|
|
116
|
+
return output
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def generate_mesh(mesh: Mesh, case_dir: Path):
|
|
120
|
+
|
|
121
|
+
if not has_openfoam():
|
|
122
|
+
raise RuntimeError("Openfoam not found, can't generate mesh")
|
|
123
|
+
|
|
124
|
+
system_dir = case_dir / "system"
|
|
125
|
+
os.makedirs(system_dir, exist_ok=True)
|
|
126
|
+
|
|
127
|
+
mesh_str = mesh_to_foam(mesh)
|
|
128
|
+
mesh_file = system_dir / "blockMeshDict"
|
|
129
|
+
with open(mesh_file, "w", encoding="utf-8") as f:
|
|
130
|
+
f.write(mesh_str)
|
|
131
|
+
|
|
132
|
+
control_dict = system_dir / "controlDict"
|
|
133
|
+
if not control_dict.exists():
|
|
134
|
+
shutil.copy(Path(__file__).parent / "controlDict", control_dict)
|
|
135
|
+
|
|
136
|
+
cmd = f"blockMesh -case {case_dir}"
|
|
137
|
+
subprocess.run(cmd, cwd=case_dir, shell=True, check=True)
|
|
138
|
+
|
|
139
|
+
cmd = f"blockMesh -case {case_dir} -write-vtk"
|
|
140
|
+
subprocess.run(cmd, cwd=case_dir, shell=True, check=True)
|
|
@@ -0,0 +1,274 @@
|
|
|
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
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from icplot.geometry import Point, Vector, Cuboid, Cylinder
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class Vertex:
|
|
16
|
+
"""
|
|
17
|
+
A mesh vertext
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
x: float
|
|
21
|
+
y: float
|
|
22
|
+
z: float = 0.0
|
|
23
|
+
id: int = -1
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def from_point(p: Point, id: int = -1) -> Vertex:
|
|
27
|
+
return Vertex(p.x, p.y, p.z, id)
|
|
28
|
+
|
|
29
|
+
def as_array(self) -> np.ndarray:
|
|
30
|
+
return np.array([self.x, self.y, self.z])
|
|
31
|
+
|
|
32
|
+
def get_distance(self, p: Point) -> float:
|
|
33
|
+
return float(np.linalg.norm(p.as_array() - self.as_array()))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(frozen=True)
|
|
37
|
+
class Edge:
|
|
38
|
+
"""
|
|
39
|
+
A mesh edge - consists of two verts
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
vert0: int
|
|
43
|
+
vert1: int
|
|
44
|
+
type: str = "line"
|
|
45
|
+
interp_points: tuple[Point, ...] = ()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True)
|
|
49
|
+
class Block:
|
|
50
|
+
"""
|
|
51
|
+
A hex mesh element - used in openfoam meshing
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
vertices: tuple[int, ...]
|
|
55
|
+
cell_counts: tuple[int, ...] = (1, 1, 1)
|
|
56
|
+
grading: str = "simpleGrading"
|
|
57
|
+
grading_ratios: tuple[int, ...] = (1, 1, 1)
|
|
58
|
+
|
|
59
|
+
def get_top_edges(self) -> tuple:
|
|
60
|
+
return (
|
|
61
|
+
(self.vertices[0], self.vertices[1]),
|
|
62
|
+
(self.vertices[1], self.vertices[2]),
|
|
63
|
+
(self.vertices[2], self.vertices[3]),
|
|
64
|
+
(self.vertices[3], self.vertices[0]),
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def get_bottom_edges(self) -> tuple:
|
|
68
|
+
return (
|
|
69
|
+
(self.vertices[4], self.vertices[5]),
|
|
70
|
+
(self.vertices[5], self.vertices[6]),
|
|
71
|
+
(self.vertices[6], self.vertices[7]),
|
|
72
|
+
(self.vertices[7], self.vertices[4]),
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
def get_side_edges(self) -> tuple:
|
|
76
|
+
return (
|
|
77
|
+
(self.vertices[0], self.vertices[4]),
|
|
78
|
+
(self.vertices[1], self.vertices[5]),
|
|
79
|
+
(self.vertices[2], self.vertices[6]),
|
|
80
|
+
(self.vertices[3], self.vertices[7]),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
def get_edges(self) -> tuple:
|
|
84
|
+
return self.get_bottom_edges() + self.get_top_edges() + self.get_side_edges()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass(frozen=True)
|
|
88
|
+
class Patch:
|
|
89
|
+
"""
|
|
90
|
+
A collection of mesh faces - used for openfoam boundaries
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
type: str
|
|
94
|
+
name: str
|
|
95
|
+
faces: tuple[tuple[int, ...], ...]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def is_between(v0: Vertex, v1: Vertex, p: Point, tol: float = 1.0e-4) -> bool:
|
|
99
|
+
|
|
100
|
+
dv0v1 = np.linalg.norm(v1.as_array() - v0.as_array())
|
|
101
|
+
dv0p = np.linalg.norm(v0.as_array() - p.as_array())
|
|
102
|
+
dv1p = np.linalg.norm(v1.as_array() - p.as_array())
|
|
103
|
+
return np.abs(dv0p + dv1p - dv0v1) < tol
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def line_distance(v0: Vertex, v1: Vertex, p: Point) -> float:
|
|
107
|
+
|
|
108
|
+
pv0 = p.as_array() - v0.as_array()
|
|
109
|
+
v1v0 = v1.as_array() - v0.as_array()
|
|
110
|
+
side0 = np.dot(pv0, v1v0)
|
|
111
|
+
if side0 < 0.0:
|
|
112
|
+
return float(np.linalg.norm(pv0))
|
|
113
|
+
pv1 = p.as_array() - v1.as_array()
|
|
114
|
+
side1 = np.dot(pv1, v0.as_array() - v1.as_array())
|
|
115
|
+
if side1 < 0.0:
|
|
116
|
+
return float(np.linalg.norm(pv1))
|
|
117
|
+
length = np.linalg.norm(v1v0)
|
|
118
|
+
proj = (side1 * v0.as_array() + side0 * v1.as_array()) / (length * length)
|
|
119
|
+
return float(np.linalg.norm(p.as_array() - proj))
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@dataclass(frozen=True)
|
|
123
|
+
class Mesh:
|
|
124
|
+
"""
|
|
125
|
+
A mesh tailored for use in openfoam
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
vertices: tuple[Vertex, ...]
|
|
129
|
+
blocks: tuple[Block, ...]
|
|
130
|
+
edges: tuple[Edge, ...] = ()
|
|
131
|
+
patches: tuple[Patch, ...] = ()
|
|
132
|
+
scale: float = 1.0
|
|
133
|
+
|
|
134
|
+
def select_edge(self, p: Point) -> tuple[int, int]:
|
|
135
|
+
|
|
136
|
+
for b in self.blocks:
|
|
137
|
+
for e in b.get_edges():
|
|
138
|
+
if is_between(self.vertices[e[0]], self.vertices[e[1]], p):
|
|
139
|
+
return (e[0], e[1])
|
|
140
|
+
raise RuntimeError("No edge found at selection point")
|
|
141
|
+
|
|
142
|
+
def get_closest_edge(self, p: Point) -> tuple[int, int]:
|
|
143
|
+
|
|
144
|
+
min_dist = -1.0
|
|
145
|
+
closest_edges: tuple[int, int] = (-1, -1)
|
|
146
|
+
for b in self.blocks:
|
|
147
|
+
for e in b.get_edges():
|
|
148
|
+
dist = line_distance(self.vertices[e[0]], self.vertices[e[1]], p)
|
|
149
|
+
if min_dist == -1.0:
|
|
150
|
+
min_dist = dist
|
|
151
|
+
closest_edges = (e[0], e[1])
|
|
152
|
+
continue
|
|
153
|
+
if dist < min_dist:
|
|
154
|
+
min_dist = dist
|
|
155
|
+
closest_edges = (e[0], e[1])
|
|
156
|
+
return closest_edges
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def from_cuboid(cuboid: Cuboid, elements_per_dim: int = 5) -> Mesh:
|
|
160
|
+
|
|
161
|
+
verts = tuple(Vertex.from_point(p) for p in cuboid.points)
|
|
162
|
+
|
|
163
|
+
cell_counts = (elements_per_dim, elements_per_dim, elements_per_dim)
|
|
164
|
+
blocks = (Block(tuple(range(len(verts))), cell_counts),)
|
|
165
|
+
return Mesh(verts, blocks)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def find_closest_vert(verts: list[Vertex], point: Point) -> int:
|
|
169
|
+
|
|
170
|
+
if not verts:
|
|
171
|
+
raise RuntimeError("Can't find nearest vert in empty list")
|
|
172
|
+
|
|
173
|
+
min_id = -1
|
|
174
|
+
min_distance = 0.0
|
|
175
|
+
for idx, v in enumerate(verts):
|
|
176
|
+
|
|
177
|
+
distance = v.get_distance(point)
|
|
178
|
+
|
|
179
|
+
if idx == 0:
|
|
180
|
+
min_id = idx
|
|
181
|
+
min_distance = distance
|
|
182
|
+
continue
|
|
183
|
+
|
|
184
|
+
if distance < min_distance:
|
|
185
|
+
min_id = idx
|
|
186
|
+
min_distance = distance
|
|
187
|
+
|
|
188
|
+
return min_id
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def from_cuboids(
|
|
192
|
+
cuboids: list[Cuboid], elements_per_dim: int = 5, merge_tolerance: float = 1.0e-4
|
|
193
|
+
) -> Mesh:
|
|
194
|
+
|
|
195
|
+
verts: list = []
|
|
196
|
+
block_vert_ids: list = []
|
|
197
|
+
for cuboid in cuboids:
|
|
198
|
+
vert_ids = []
|
|
199
|
+
for p in cuboid.points:
|
|
200
|
+
|
|
201
|
+
if not verts:
|
|
202
|
+
verts.append(Vertex.from_point(p, 0))
|
|
203
|
+
vert_ids.append(0)
|
|
204
|
+
continue
|
|
205
|
+
|
|
206
|
+
nearest_id = find_closest_vert(verts, p)
|
|
207
|
+
if verts[nearest_id].get_distance(p) <= merge_tolerance:
|
|
208
|
+
vert_ids.append(nearest_id)
|
|
209
|
+
else:
|
|
210
|
+
end_id = len(verts)
|
|
211
|
+
verts.append(Vertex.from_point(p, end_id))
|
|
212
|
+
vert_ids.append(end_id)
|
|
213
|
+
block_vert_ids.append(vert_ids)
|
|
214
|
+
|
|
215
|
+
cell_counts = (elements_per_dim, elements_per_dim, elements_per_dim)
|
|
216
|
+
blocks = tuple(Block(tuple(b), cell_counts) for b in block_vert_ids)
|
|
217
|
+
return Mesh(tuple(verts), blocks)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def from_cylinder(cylinder: Cylinder, boundary_frac: float = 0.66) -> Mesh:
|
|
221
|
+
|
|
222
|
+
depth = (cylinder.diameter - boundary_frac * cylinder.diameter) / 2.0
|
|
223
|
+
cuboids = [
|
|
224
|
+
Cuboid(
|
|
225
|
+
Point(0.0, 0.0, depth / 2.0),
|
|
226
|
+
width=cylinder.diameter,
|
|
227
|
+
height=cylinder.length,
|
|
228
|
+
depth=depth,
|
|
229
|
+
top_width_scale=boundary_frac,
|
|
230
|
+
),
|
|
231
|
+
Cuboid(
|
|
232
|
+
Point(0.0, 0.0, cylinder.diameter + depth / 2.0),
|
|
233
|
+
width=cylinder.diameter,
|
|
234
|
+
height=cylinder.length,
|
|
235
|
+
depth=depth,
|
|
236
|
+
normal=Vector(0.0, 0.0, -1.0),
|
|
237
|
+
top_width_scale=boundary_frac,
|
|
238
|
+
),
|
|
239
|
+
Cuboid(
|
|
240
|
+
Point(-cylinder.diameter / 2.0, 0.0, cylinder.diameter / 2.0 + depth / 2.0),
|
|
241
|
+
width=cylinder.diameter,
|
|
242
|
+
height=cylinder.length,
|
|
243
|
+
depth=depth,
|
|
244
|
+
normal=Vector(1.0, 0.0, 0.0),
|
|
245
|
+
top_width_scale=boundary_frac,
|
|
246
|
+
),
|
|
247
|
+
Cuboid(
|
|
248
|
+
Point(cylinder.diameter / 2.0, 0.0, cylinder.diameter / 2.0 + depth / 2.0),
|
|
249
|
+
width=cylinder.diameter,
|
|
250
|
+
height=cylinder.length,
|
|
251
|
+
depth=depth,
|
|
252
|
+
normal=Vector(-1.0, 0.0, 0.0),
|
|
253
|
+
top_width_scale=boundary_frac,
|
|
254
|
+
),
|
|
255
|
+
Cuboid(
|
|
256
|
+
Point(0.0, 0.0, cylinder.diameter * boundary_frac / 2.0 + depth),
|
|
257
|
+
width=cylinder.diameter * boundary_frac,
|
|
258
|
+
height=cylinder.length,
|
|
259
|
+
depth=boundary_frac * cylinder.diameter,
|
|
260
|
+
),
|
|
261
|
+
]
|
|
262
|
+
|
|
263
|
+
mesh = from_cuboids(cuboids)
|
|
264
|
+
|
|
265
|
+
top_front_edge = mesh.get_closest_edge(
|
|
266
|
+
Point(0.0, -cylinder.length / 2.0, cylinder.diameter + depth / 2.0)
|
|
267
|
+
)
|
|
268
|
+
top_front_arc = Point(
|
|
269
|
+
0.0, -cylinder.length / 2.0, cylinder.diameter + depth / 2.0 + 0.15
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
edges = (Edge(top_front_edge[0], top_front_edge[1], "arc", (top_front_arc,)),)
|
|
273
|
+
|
|
274
|
+
return Mesh(mesh.vertices, mesh.blocks, edges=edges)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import os
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from .mesh import Mesh
|
|
6
|
+
|
|
7
|
+
_HAS_VTK = True
|
|
8
|
+
try:
|
|
9
|
+
import vtk
|
|
10
|
+
except ImportError as e:
|
|
11
|
+
logging.getLogger(__name__).warning(
|
|
12
|
+
"Disabling VTK stupport. Failed to load with: %s", e
|
|
13
|
+
)
|
|
14
|
+
_HAS_VTK = False
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def has_vtk() -> bool:
|
|
18
|
+
return _HAS_VTK
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def write_mesh(mesh: Mesh, path: Path):
|
|
22
|
+
|
|
23
|
+
if not has_vtk():
|
|
24
|
+
raise RuntimeError("VTK support failed to load")
|
|
25
|
+
|
|
26
|
+
os.makedirs(path.parent, exist_ok=True)
|
|
27
|
+
|
|
28
|
+
points = vtk.vtkPoints()
|
|
29
|
+
for v in mesh.vertices:
|
|
30
|
+
points.InsertNextPoint(v.x, v.y, v.z)
|
|
31
|
+
|
|
32
|
+
grid = vtk.vtkUnstructuredGrid()
|
|
33
|
+
grid.SetPoints(points)
|
|
34
|
+
for block in mesh.blocks:
|
|
35
|
+
cell = vtk.vtkHexahedron()
|
|
36
|
+
for idx, v_id in enumerate(block.vertices):
|
|
37
|
+
cell.GetPointIds().SetId(idx, v_id)
|
|
38
|
+
grid.InsertNextCell(cell.GetCellType(), cell.GetPointIds())
|
|
39
|
+
|
|
40
|
+
writer = vtk.vtkUnstructuredGridWriter()
|
|
41
|
+
writer.SetInputData(grid)
|
|
42
|
+
writer.SetFileName(path)
|
|
43
|
+
writer.SetFileTypeToASCII()
|
|
44
|
+
writer.Write()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: icplot
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.7
|
|
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
|
|
@@ -18,6 +18,7 @@ Requires-Dist: wand
|
|
|
18
18
|
Requires-Dist: matplotlib
|
|
19
19
|
Requires-Dist: vtk
|
|
20
20
|
Requires-Dist: moviepy
|
|
21
|
+
Requires-Dist: scipy
|
|
21
22
|
Requires-Dist: iccore==0.1.13
|
|
22
23
|
Provides-Extra: test
|
|
23
24
|
Requires-Dist: pytest; extra == "test"
|
|
@@ -36,10 +36,12 @@ src/icplot/graph/vtk.py
|
|
|
36
36
|
src/icplot/mesh/__init__.py
|
|
37
37
|
src/icplot/mesh/foam.py
|
|
38
38
|
src/icplot/mesh/mesh.py
|
|
39
|
+
src/icplot/mesh/vtk.py
|
|
39
40
|
src/icplot/trace/__init__.py
|
|
40
41
|
src/icplot/trace/trace.py
|
|
41
42
|
test/test_cairo_interface.py
|
|
42
43
|
test/test_gantt_chart.py
|
|
44
|
+
test/test_geometry.py
|
|
43
45
|
test/test_image_utils.py
|
|
44
46
|
test/test_line_plots.py
|
|
45
47
|
test/test_mermaid.py
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from icplot.geometry import Point, Vector, Quad, Cuboid
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_quad():
|
|
7
|
+
|
|
8
|
+
quad = Quad(Point(0.0, 0.0, 0.0))
|
|
9
|
+
points = quad.points
|
|
10
|
+
|
|
11
|
+
assert points[0].x == -0.5
|
|
12
|
+
assert points[0].y == -0.5
|
|
13
|
+
|
|
14
|
+
assert points[1].x == 0.5
|
|
15
|
+
assert points[1].y == -0.5
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_quad_with_rotation():
|
|
19
|
+
|
|
20
|
+
normal = Vector(0.0, 1.0, 0.0)
|
|
21
|
+
|
|
22
|
+
quad = Quad(Point(0.0, 0.0, 0.0), normal)
|
|
23
|
+
points = quad.points
|
|
24
|
+
|
|
25
|
+
assert points[0].x == pytest.approx(-0.5)
|
|
26
|
+
assert points[0].y == pytest.approx(0.0)
|
|
27
|
+
assert points[0].z == pytest.approx(0.5)
|
|
28
|
+
|
|
29
|
+
assert points[1].x == pytest.approx(0.5)
|
|
30
|
+
assert points[1].y == pytest.approx(0.0)
|
|
31
|
+
assert points[1].z == pytest.approx(0.5)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_cuboid():
|
|
35
|
+
|
|
36
|
+
shape = Cuboid(Point(0.0, 0.0, 0.0))
|
|
37
|
+
points = shape.points
|
|
38
|
+
|
|
39
|
+
assert points[0].x == -0.5
|
|
40
|
+
assert points[0].y == -0.5
|
|
41
|
+
assert points[0].z == -0.5
|
|
42
|
+
|
|
43
|
+
assert points[1].x == 0.5
|
|
44
|
+
assert points[1].y == -0.5
|
|
45
|
+
|
|
46
|
+
assert points[4].x == -0.5
|
|
47
|
+
assert points[4].y == -0.5
|
|
48
|
+
assert points[4].z == 0.5
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_cuboid_rotation():
|
|
52
|
+
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
normal = Vector(0.0, 1.0, 0.0)
|
|
56
|
+
|
|
57
|
+
shape = Cuboid(Point(0.0, 0.0, 0.0), normal)
|
|
58
|
+
points = shape.points
|
|
59
|
+
|
|
60
|
+
assert points[0].x == -0.5
|
|
61
|
+
assert points[0].y == -0.5
|
|
62
|
+
assert points[0].z == -0.5
|
|
63
|
+
|
|
64
|
+
assert points[1].x == 0.5
|
|
65
|
+
assert points[1].y == -0.5
|
|
66
|
+
|
|
67
|
+
assert points[4].x == -0.5
|
|
68
|
+
assert points[4].y == -0.5
|
|
69
|
+
assert points[4].z == 0.5
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from iccore.test_utils import get_test_output_dir
|
|
2
|
+
|
|
3
|
+
from icplot.geometry import Point, Cylinder, Cuboid
|
|
4
|
+
from icplot.mesh import foam, vtk, from_cylinder, from_cuboid
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_closest_edge():
|
|
8
|
+
|
|
9
|
+
cuboid = Cuboid()
|
|
10
|
+
|
|
11
|
+
mesh = from_cuboid(cuboid)
|
|
12
|
+
|
|
13
|
+
closest_edge = mesh.get_closest_edge(Point(0.0, -0.5, -0.5))
|
|
14
|
+
|
|
15
|
+
assert closest_edge[0] == 0
|
|
16
|
+
assert closest_edge[1] == 1
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_mesh():
|
|
20
|
+
|
|
21
|
+
output_dir = get_test_output_dir()
|
|
22
|
+
|
|
23
|
+
cylinder = Cylinder(loc=Point(0.0, 0.0, 0.0), length=2.0, diameter=1.0)
|
|
24
|
+
|
|
25
|
+
mesh = from_cylinder(cylinder)
|
|
26
|
+
vtk.write_mesh(mesh, output_dir / "cylinder.vtk")
|
|
27
|
+
# return
|
|
28
|
+
|
|
29
|
+
foam_str = foam.mesh_to_foam(mesh)
|
|
30
|
+
|
|
31
|
+
assert foam_str
|
|
32
|
+
|
|
33
|
+
if not foam.has_openfoam():
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
foam.generate_mesh(mesh, output_dir)
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Meshing utilities for use in openfoam
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from .mesh import Mesh
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def mesh_to_foam(mesh: Mesh) -> str:
|
|
9
|
-
"""
|
|
10
|
-
Given a mesh, write it in openfoam blockMesh format
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
output = f"scale\t{mesh.scale}\n"
|
|
14
|
-
|
|
15
|
-
output += "vertices\n(\n"
|
|
16
|
-
for idx, v in enumerate(mesh.vertices):
|
|
17
|
-
output += f"\t( {v.x} {v.y} {v.z} ) // # {idx} \n"
|
|
18
|
-
output += ");\n\n"
|
|
19
|
-
|
|
20
|
-
output += "edges\n(\n"
|
|
21
|
-
for e in mesh.edges:
|
|
22
|
-
if e.type == "line":
|
|
23
|
-
output += f"\tline {e.vert0} {e.vert1}\n"
|
|
24
|
-
else:
|
|
25
|
-
interps = ""
|
|
26
|
-
for p in e.interp_points:
|
|
27
|
-
interps += f"({p.x} {p.y} {p.z}) "
|
|
28
|
-
output += f"\t {e.type} {e.vert0} {e.vert1} {interps}\n"
|
|
29
|
-
output += ");\n\n"
|
|
30
|
-
|
|
31
|
-
output += "blocks\n(\n"
|
|
32
|
-
for b in mesh.blocks:
|
|
33
|
-
verts = " ".join(str(v) for v in b.vertices)
|
|
34
|
-
output += f"\thex({verts})\n"
|
|
35
|
-
|
|
36
|
-
counts = " ".join(str(c) for c in b.cell_counts)
|
|
37
|
-
output += f"\t({counts})\n"
|
|
38
|
-
|
|
39
|
-
grades = " ".join(str(g) for g in b.grading_ratios)
|
|
40
|
-
output += f"\t{b.grading} ({grades})\n"
|
|
41
|
-
output += ");\n\n"
|
|
42
|
-
|
|
43
|
-
output += "boundary\n(\n"
|
|
44
|
-
for patch in mesh.patches:
|
|
45
|
-
output += f"\t{patch.name}\n"
|
|
46
|
-
output += "\t{\n"
|
|
47
|
-
output += f"\t\ttype {patch.type};\n"
|
|
48
|
-
output += "\t\tfaces\n\t\t(\n"
|
|
49
|
-
for f in patch.faces:
|
|
50
|
-
faces = " ".join(str(idx) for idx in f)
|
|
51
|
-
output += f"\t\t\t({faces})\n"
|
|
52
|
-
output += "\t\t);\n"
|
|
53
|
-
output += "\t}\n"
|
|
54
|
-
output += ");\n\n"
|
|
55
|
-
|
|
56
|
-
return output
|
|
@@ -1,79 +0,0 @@
|
|
|
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, Cuboid
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
@dataclass(frozen=True)
|
|
13
|
-
class Vertex:
|
|
14
|
-
"""
|
|
15
|
-
A mesh vertext
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
x: float
|
|
19
|
-
y: float
|
|
20
|
-
z: float = 0.0
|
|
21
|
-
|
|
22
|
-
@staticmethod
|
|
23
|
-
def from_point(p: Point) -> Vertex:
|
|
24
|
-
return Vertex(p.x, p.y, p.z)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
@dataclass(frozen=True)
|
|
28
|
-
class Edge:
|
|
29
|
-
"""
|
|
30
|
-
A mesh edge - consists of two verts
|
|
31
|
-
"""
|
|
32
|
-
|
|
33
|
-
vert0: int
|
|
34
|
-
vert1: int
|
|
35
|
-
type: str = "line"
|
|
36
|
-
interp_points: tuple[Point, ...] = ()
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
@dataclass(frozen=True)
|
|
40
|
-
class Block:
|
|
41
|
-
"""
|
|
42
|
-
A hex mesh element - used in openfoam meshing
|
|
43
|
-
"""
|
|
44
|
-
|
|
45
|
-
vertices: tuple[int, ...]
|
|
46
|
-
cell_counts: tuple[int, ...] = (1, 1, 1)
|
|
47
|
-
grading: str = "simple"
|
|
48
|
-
grading_ratios: tuple[int, ...] = (1, 1, 1)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
@dataclass(frozen=True)
|
|
52
|
-
class Patch:
|
|
53
|
-
"""
|
|
54
|
-
A collection of mesh faces - used for openfoam boundaries
|
|
55
|
-
"""
|
|
56
|
-
|
|
57
|
-
type: str
|
|
58
|
-
name: str
|
|
59
|
-
faces: tuple[tuple[int, ...], ...]
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
@dataclass(frozen=True)
|
|
63
|
-
class Mesh:
|
|
64
|
-
"""
|
|
65
|
-
A mesh tailored for use in openfoam
|
|
66
|
-
"""
|
|
67
|
-
|
|
68
|
-
vertices: tuple[Vertex, ...]
|
|
69
|
-
blocks: tuple[Block, ...]
|
|
70
|
-
edges: tuple[Edge, ...] = ()
|
|
71
|
-
patches: tuple[Patch, ...] = ()
|
|
72
|
-
scale: float = 1.0
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def from_cuboid(cuboid: Cuboid) -> Mesh:
|
|
76
|
-
|
|
77
|
-
verts = tuple(Vertex.from_point(p) for p in cuboid.points)
|
|
78
|
-
blocks = (Block(tuple(range(len(verts)))),)
|
|
79
|
-
return Mesh(verts, blocks)
|
icplot-0.1.6/test/test_mesh.py
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|