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.
Files changed (53) hide show
  1. {icplot-0.1.6/src/icplot.egg-info → icplot-0.1.7}/PKG-INFO +2 -1
  2. {icplot-0.1.6 → icplot-0.1.7}/pyproject.toml +4 -3
  3. {icplot-0.1.6 → icplot-0.1.7}/src/icplot/geometry.py +69 -11
  4. {icplot-0.1.6 → icplot-0.1.7}/src/icplot/graph/vtk.py +21 -2
  5. {icplot-0.1.6 → icplot-0.1.7}/src/icplot/image_utils.py +35 -4
  6. icplot-0.1.7/src/icplot/mesh/foam.py +140 -0
  7. icplot-0.1.7/src/icplot/mesh/mesh.py +274 -0
  8. icplot-0.1.7/src/icplot/mesh/vtk.py +44 -0
  9. {icplot-0.1.6 → icplot-0.1.7/src/icplot.egg-info}/PKG-INFO +2 -1
  10. {icplot-0.1.6 → icplot-0.1.7}/src/icplot.egg-info/SOURCES.txt +2 -0
  11. {icplot-0.1.6 → icplot-0.1.7}/src/icplot.egg-info/requires.txt +1 -0
  12. icplot-0.1.7/test/test_geometry.py +69 -0
  13. icplot-0.1.7/test/test_mesh.py +36 -0
  14. icplot-0.1.6/src/icplot/mesh/foam.py +0 -56
  15. icplot-0.1.6/src/icplot/mesh/mesh.py +0 -79
  16. icplot-0.1.6/test/test_mesh.py +0 -13
  17. {icplot-0.1.6 → icplot-0.1.7}/LICENSE +0 -0
  18. {icplot-0.1.6 → icplot-0.1.7}/README.md +0 -0
  19. {icplot-0.1.6 → icplot-0.1.7}/setup.cfg +0 -0
  20. {icplot-0.1.6 → icplot-0.1.7}/src/icplot/__init__.py +0 -0
  21. {icplot-0.1.6 → icplot-0.1.7}/src/icplot/cairo_interface.py +0 -0
  22. {icplot-0.1.6 → icplot-0.1.7}/src/icplot/color.py +0 -0
  23. {icplot-0.1.6 → icplot-0.1.7}/src/icplot/convert.py +0 -0
  24. {icplot-0.1.6 → icplot-0.1.7}/src/icplot/gantt/__init__.py +0 -0
  25. {icplot-0.1.6 → icplot-0.1.7}/src/icplot/gantt/gantt.py +0 -0
  26. {icplot-0.1.6 → icplot-0.1.7}/src/icplot/gantt/gantt_renderer.py +0 -0
  27. {icplot-0.1.6 → icplot-0.1.7}/src/icplot/gantt/pgfgantt.py +0 -0
  28. {icplot-0.1.6 → icplot-0.1.7}/src/icplot/graph/__init__.py +0 -0
  29. {icplot-0.1.6 → icplot-0.1.7}/src/icplot/graph/axis.py +0 -0
  30. {icplot-0.1.6 → icplot-0.1.7}/src/icplot/graph/generator.py +0 -0
  31. {icplot-0.1.6 → icplot-0.1.7}/src/icplot/graph/matplotlib.py +0 -0
  32. {icplot-0.1.6 → icplot-0.1.7}/src/icplot/graph/mpl.py +0 -0
  33. {icplot-0.1.6 → icplot-0.1.7}/src/icplot/graph/plot.py +0 -0
  34. {icplot-0.1.6 → icplot-0.1.7}/src/icplot/graph/plot_group.py +0 -0
  35. {icplot-0.1.6 → icplot-0.1.7}/src/icplot/graph/series.py +0 -0
  36. {icplot-0.1.6 → icplot-0.1.7}/src/icplot/graph/video.py +0 -0
  37. {icplot-0.1.6 → icplot-0.1.7}/src/icplot/main_cli.py +0 -0
  38. {icplot-0.1.6 → icplot-0.1.7}/src/icplot/mermaid.py +0 -0
  39. {icplot-0.1.6 → icplot-0.1.7}/src/icplot/mesh/__init__.py +0 -0
  40. {icplot-0.1.6 → icplot-0.1.7}/src/icplot/py.typed +0 -0
  41. {icplot-0.1.6 → icplot-0.1.7}/src/icplot/scene.py +0 -0
  42. {icplot-0.1.6 → icplot-0.1.7}/src/icplot/tex.py +0 -0
  43. {icplot-0.1.6 → icplot-0.1.7}/src/icplot/trace/__init__.py +0 -0
  44. {icplot-0.1.6 → icplot-0.1.7}/src/icplot/trace/trace.py +0 -0
  45. {icplot-0.1.6 → icplot-0.1.7}/src/icplot.egg-info/dependency_links.txt +0 -0
  46. {icplot-0.1.6 → icplot-0.1.7}/src/icplot.egg-info/entry_points.txt +0 -0
  47. {icplot-0.1.6 → icplot-0.1.7}/src/icplot.egg-info/top_level.txt +0 -0
  48. {icplot-0.1.6 → icplot-0.1.7}/test/test_cairo_interface.py +0 -0
  49. {icplot-0.1.6 → icplot-0.1.7}/test/test_gantt_chart.py +0 -0
  50. {icplot-0.1.6 → icplot-0.1.7}/test/test_image_utils.py +0 -0
  51. {icplot-0.1.6 → icplot-0.1.7}/test/test_line_plots.py +0 -0
  52. {icplot-0.1.6 → icplot-0.1.7}/test/test_mermaid.py +0 -0
  53. {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.6
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.6"
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.33
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
- p = copy.deepcopy(self.loc)
62
- return [
63
- p,
64
- p.translate(Vector(self.width)),
65
- p.translate(Vector(self.width, self.height)),
66
- p.translate(Vector(0.0, self.height)),
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(self.loc, self.normal, self.width, self.height)
93
- top = base.translate(self.normal.scale(self.depth))
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
- from wand.image import Image
11
- from wand.color import Color
12
- import cairosvg
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.6
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
@@ -2,6 +2,7 @@ wand
2
2
  matplotlib
3
3
  vtk
4
4
  moviepy
5
+ scipy
5
6
  iccore==0.1.13
6
7
 
7
8
  [cairo]
@@ -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)
@@ -1,13 +0,0 @@
1
- from icplot.geometry import Point, Cuboid
2
- from icplot.mesh import foam, from_cuboid
3
-
4
-
5
- def test_mesh():
6
-
7
- cuboid = Cuboid(Point(0.0, 0.0, 0.0))
8
-
9
- mesh = from_cuboid(cuboid)
10
-
11
- foam_str = foam.mesh_to_foam(mesh)
12
-
13
- assert foam_str
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