icplot 0.2.3__py3-none-any.whl → 0.3.0__py3-none-any.whl
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/{gantt → charts}/gantt.py +2 -3
- icplot/{tree.py → charts/tree.py} +2 -3
- icplot/converter/__init__.py +3 -0
- icplot/converter/cli.py +27 -0
- icplot/converter/convert.py +32 -0
- icplot/{image_utils.py → converter/image.py} +5 -9
- icplot/geometry/base.py +6 -0
- icplot/{mesh → geometry/mesh}/cell.py +21 -0
- icplot/{mesh → geometry/mesh}/edge.py +1 -1
- icplot/{mesh → geometry/mesh}/mesh.py +50 -4
- icplot/{mesh → geometry/mesh}/openfoam/blockmesh.py +2 -3
- icplot/{mesh → geometry/mesh}/openfoam/foamfile.py +10 -2
- icplot/geometry/mesh/openfoam/polymesh.py +313 -0
- icplot/geometry/mesh/shapes.py +403 -0
- icplot/{mesh → geometry/mesh}/vertex.py +39 -1
- icplot/geometry/shapes.py +29 -0
- icplot/geometry/transform.py +2 -2
- icplot/graph/__init__.py +3 -4
- icplot/graph/plot.py +2 -53
- icplot/graph/plot_group.py +1 -1
- icplot/graph/series.py +3 -4
- icplot/main_cli.py +13 -62
- icplot/renderer/__init__.py +1 -0
- icplot/renderer/charts/pgfgantt.py +71 -0
- icplot/{gantt/gantt_renderer.py → renderer/charts/svggantt.py} +8 -6
- icplot/renderer/cli.py +58 -0
- icplot/renderer/config.py +41 -0
- icplot/renderer/formats/__init__.py +1 -0
- icplot/renderer/formats/mermaid.py +41 -0
- icplot/renderer/formats/plantuml.py +37 -0
- icplot/renderer/formats/tex.py +117 -0
- icplot/renderer/graph/__init__.py +3 -0
- icplot/renderer/graph/color.py +17 -0
- icplot/{graph → renderer/graph}/matplotlib.py +15 -7
- icplot/{graph → renderer/graph}/mpl.py +8 -8
- icplot/{graph/generator.py → renderer/graph/renderer.py} +43 -8
- icplot/{graph → renderer/graph}/vtk.py +2 -3
- icplot/renderer/render.py +140 -0
- icplot/{cairo_interface.py → renderer/scene/cairo.py} +5 -5
- icplot/{graph → renderer}/video.py +2 -3
- icplot/scene.py +1 -1
- icplot/theme/__init__.py +0 -0
- icplot/theme/cli.py +50 -0
- icplot/theme/color.py +97 -0
- icplot/theme/templates/puml.j2 +91 -0
- icplot/theme/templates/tikz.j2 +100 -0
- icplot/theme/theme.json +81 -0
- icplot/theme/theme.py +117 -0
- icplot/theme/theme_generator.py +119 -0
- {icplot-0.2.3.dist-info → icplot-0.3.0.dist-info}/METADATA +66 -18
- icplot-0.3.0.dist-info/RECORD +65 -0
- {icplot-0.2.3.dist-info → icplot-0.3.0.dist-info}/WHEEL +1 -1
- icplot/color.py +0 -50
- icplot/convert.py +0 -51
- icplot/gantt/__init__.py +0 -1
- icplot/gantt/pgfgantt.py +0 -15
- icplot/mermaid.py +0 -39
- icplot/mesh/openfoam/polymesh.py +0 -46
- icplot/mesh/shapes.py +0 -205
- icplot/tex.py +0 -78
- icplot/trace/__init__.py +0 -1
- icplot-0.2.3.dist-info/RECORD +0 -50
- icplot/{trace/trace.py → charts/__init__.py} +0 -0
- icplot/{mesh → geometry/mesh}/__init__.py +1 -1
- icplot/{mesh → geometry/mesh}/openfoam/__init__.py +1 -1
- icplot/{mesh → geometry/mesh}/operations.py +1 -1
- icplot/{mesh → geometry/mesh}/vtk.py +2 -2
- {icplot-0.2.3.dist-info → icplot-0.3.0.dist-info}/entry_points.txt +0 -0
- {icplot-0.2.3.dist-info → icplot-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {icplot-0.2.3.dist-info → icplot-0.3.0.dist-info}/top_level.txt +0 -0
icplot/{gantt → charts}/gantt.py
RENAMED
|
@@ -4,11 +4,10 @@ This module represents a gantt chard
|
|
|
4
4
|
|
|
5
5
|
from datetime import datetime
|
|
6
6
|
|
|
7
|
-
from pydantic import BaseModel
|
|
8
|
-
|
|
9
7
|
from iccore.project import Milestone
|
|
8
|
+
from pydantic import BaseModel
|
|
10
9
|
|
|
11
|
-
from icplot.color import Color
|
|
10
|
+
from icplot.theme.color import Color
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
class GanttChart(BaseModel, frozen=True):
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
import os
|
|
2
3
|
import shutil
|
|
3
|
-
import logging
|
|
4
4
|
from dataclasses import dataclass
|
|
5
|
-
from typing import Callable
|
|
6
5
|
from pathlib import Path
|
|
6
|
+
from typing import Callable
|
|
7
7
|
|
|
8
8
|
import graphviz
|
|
9
9
|
|
|
10
|
-
|
|
11
10
|
logger = logging.getLogger(__name__)
|
|
12
11
|
|
|
13
12
|
|
icplot/converter/cli.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from .convert import convert
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def convert_cli(args):
|
|
7
|
+
|
|
8
|
+
source = args.source.resolve()
|
|
9
|
+
target = Path(args.target).resolve() if args.target else None
|
|
10
|
+
convert(source, target)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def register_parser(subparsers):
|
|
14
|
+
|
|
15
|
+
convert_parser = subparsers.add_parser("convert")
|
|
16
|
+
convert_parser.add_argument(
|
|
17
|
+
"source",
|
|
18
|
+
type=Path,
|
|
19
|
+
help="Path to file to be converted from",
|
|
20
|
+
)
|
|
21
|
+
convert_parser.add_argument(
|
|
22
|
+
"--target",
|
|
23
|
+
type=str,
|
|
24
|
+
default="",
|
|
25
|
+
help="Path to file to be converted to",
|
|
26
|
+
)
|
|
27
|
+
convert_parser.set_defaults(func=convert_cli)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module supporting conversion between image formats
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from .image import pdf_to_png, svg_to_pdf, svg_to_png
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def convert(source: Path, target: Path | None = None):
|
|
14
|
+
"""
|
|
15
|
+
Convert between image formats using the source and target
|
|
16
|
+
file extensions as format hints.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
logger.info("Converting %s", source)
|
|
20
|
+
|
|
21
|
+
if source.suffix == ".pdf":
|
|
22
|
+
pdf_to_png(source, target)
|
|
23
|
+
elif source.suffix == ".svg":
|
|
24
|
+
if target:
|
|
25
|
+
if target.suffix == ".png":
|
|
26
|
+
svg_to_png(source, target)
|
|
27
|
+
elif target.suffix == ".pdf":
|
|
28
|
+
svg_to_pdf(source, target)
|
|
29
|
+
else:
|
|
30
|
+
svg_to_png(source)
|
|
31
|
+
|
|
32
|
+
logger.info("Finished conversion")
|
|
@@ -7,27 +7,23 @@ import logging
|
|
|
7
7
|
import os
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
10
12
|
_HAS_WAND = True
|
|
11
13
|
try:
|
|
12
|
-
from wand.image import Image
|
|
13
14
|
from wand.color import Color
|
|
15
|
+
from wand.image import Image
|
|
14
16
|
except ImportError as e:
|
|
15
|
-
|
|
16
|
-
"Disabling Wand stupport. Failed to load with: %s", e
|
|
17
|
-
)
|
|
17
|
+
logger.debug("wand not available: %s", e)
|
|
18
18
|
_HAS_WAND = False
|
|
19
19
|
|
|
20
20
|
_HAS_CAIRO_SVG = True
|
|
21
21
|
try:
|
|
22
22
|
import cairosvg
|
|
23
23
|
except ImportError as e:
|
|
24
|
-
|
|
25
|
-
"Disabling CairoSVG stupport. Failed to load with: %s", e
|
|
26
|
-
)
|
|
24
|
+
logger.debug("cairosvg not available: %s", e)
|
|
27
25
|
_HAS_CAIRO_SVG = False
|
|
28
26
|
|
|
29
|
-
logger = logging.getLogger(__name__)
|
|
30
|
-
|
|
31
27
|
|
|
32
28
|
def has_wand() -> bool:
|
|
33
29
|
return _HAS_WAND
|
icplot/geometry/base.py
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
+
|
|
2
3
|
from dataclasses import dataclass
|
|
3
4
|
|
|
4
5
|
import numpy as np
|
|
5
6
|
|
|
7
|
+
from .operations import get_point_distance
|
|
8
|
+
|
|
6
9
|
|
|
7
10
|
@dataclass(frozen=True)
|
|
8
11
|
class Vector:
|
|
@@ -47,6 +50,9 @@ class Point:
|
|
|
47
50
|
def subtract(self, v: Vector) -> Vector:
|
|
48
51
|
return Vector(self.x - v.x, self.y - v.y, self.z - v.z)
|
|
49
52
|
|
|
53
|
+
def get_distance(self, p: Point) -> float:
|
|
54
|
+
return get_point_distance(p.as_array(), self.as_array())
|
|
55
|
+
|
|
50
56
|
|
|
51
57
|
@dataclass(frozen=True)
|
|
52
58
|
class Bounds:
|
|
@@ -14,6 +14,27 @@ class Cell:
|
|
|
14
14
|
raise NotImplementedError()
|
|
15
15
|
|
|
16
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
|
+
|
|
17
38
|
@dataclass(frozen=True)
|
|
18
39
|
class HexCell(Cell):
|
|
19
40
|
"""
|
|
@@ -4,13 +4,14 @@ over performance. Consider something else for high performance meshing.
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
7
|
+
|
|
7
8
|
from dataclasses import dataclass
|
|
8
9
|
|
|
9
|
-
from icplot.geometry import Point,
|
|
10
|
+
from icplot.geometry import Bounds, Point, Shape, Transform, Vector
|
|
10
11
|
|
|
11
|
-
from .
|
|
12
|
-
from .cell import Cell
|
|
12
|
+
from .cell import Cell, flip_normals
|
|
13
13
|
from .edge import Edge, contains_point, get_distance
|
|
14
|
+
from .vertex import Vertex, find_closest_vertex
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
@dataclass(frozen=True)
|
|
@@ -49,9 +50,14 @@ class Mesh:
|
|
|
49
50
|
ymin = min(ymin, v.y)
|
|
50
51
|
ymax = max(ymax, v.y)
|
|
51
52
|
zmin = min(zmin, v.z)
|
|
52
|
-
zmax =
|
|
53
|
+
zmax = max(zmax, v.z)
|
|
53
54
|
return Bounds(xmin=xmin, xmax=xmax, ymin=ymin, ymax=ymax, zmin=zmin, zmax=zmax)
|
|
54
55
|
|
|
56
|
+
def flip_normals(self) -> Mesh:
|
|
57
|
+
return Mesh(
|
|
58
|
+
vertices=self.vertices, cells=tuple(flip_normals(c) for c in self.cells)
|
|
59
|
+
)
|
|
60
|
+
|
|
55
61
|
def get_location(self) -> Vector:
|
|
56
62
|
bounds = self.get_bounds()
|
|
57
63
|
return Vector(bounds.xmin, bounds.ymin, bounds.zmin)
|
|
@@ -62,6 +68,32 @@ class Mesh:
|
|
|
62
68
|
vertices=tuple(v.translate(delta) for v in self.vertices), cells=self.cells
|
|
63
69
|
)
|
|
64
70
|
|
|
71
|
+
def scale_by(self, factor: float) -> Mesh:
|
|
72
|
+
return Mesh(
|
|
73
|
+
vertices=tuple(v.scale(factor) for v in self.vertices), cells=self.cells
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
def rotate_to(self, vec: Vector) -> Mesh:
|
|
77
|
+
return Mesh(
|
|
78
|
+
vertices=tuple(v.rotate_to(vec) for v in self.vertices), cells=self.cells
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
def move_by(
|
|
82
|
+
self, x: float, y: float, z: float, selection: list[int] | None = None
|
|
83
|
+
) -> Mesh:
|
|
84
|
+
delta = Vector(x, y, z)
|
|
85
|
+
if selection:
|
|
86
|
+
return Mesh(
|
|
87
|
+
vertices=tuple(
|
|
88
|
+
v.translate(delta) if v.id in selection else v.copy()
|
|
89
|
+
for v in self.vertices
|
|
90
|
+
),
|
|
91
|
+
cells=self.cells,
|
|
92
|
+
)
|
|
93
|
+
return Mesh(
|
|
94
|
+
vertices=tuple(v.translate(delta) for v in self.vertices), cells=self.cells
|
|
95
|
+
)
|
|
96
|
+
|
|
65
97
|
def select_edge(self, p: Point) -> tuple[int, int]:
|
|
66
98
|
|
|
67
99
|
for c in self.cells:
|
|
@@ -70,6 +102,9 @@ class Mesh:
|
|
|
70
102
|
return (e[0], e[1])
|
|
71
103
|
raise RuntimeError("No edge found at selection point")
|
|
72
104
|
|
|
105
|
+
def get_closest_vertex(self, v: Vertex) -> Vertex:
|
|
106
|
+
return find_closest_vertex(self.vertices, v)
|
|
107
|
+
|
|
73
108
|
def get_closest_edge(self, p: Point) -> tuple[int, int]:
|
|
74
109
|
|
|
75
110
|
min_dist = -1.0
|
|
@@ -85,3 +120,14 @@ class Mesh:
|
|
|
85
120
|
min_dist = dist
|
|
86
121
|
closest_edges = (e[0], e[1])
|
|
87
122
|
return closest_edges
|
|
123
|
+
|
|
124
|
+
def copy(self) -> Mesh:
|
|
125
|
+
return Mesh(
|
|
126
|
+
vertices=tuple(Vertex(v.x, v.y, v.z, v.id) for v in self.vertices),
|
|
127
|
+
cells=tuple(Cell(c.vertices, c.type) for c in self.cells),
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
def select_region(self, region: Shape) -> list[int]:
|
|
131
|
+
return [
|
|
132
|
+
v.id for v in self.vertices if region.contains_point(Point(v.x, v.y, v.z))
|
|
133
|
+
]
|
|
@@ -2,14 +2,13 @@
|
|
|
2
2
|
Meshing utilities for use in openfoam
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
import os
|
|
5
6
|
import shutil
|
|
6
7
|
import subprocess
|
|
7
|
-
import os
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
from typing import cast
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
from icplot.mesh import Edge, HexCell, Mesh, Patch
|
|
11
|
+
from icplot.geometry.mesh import Edge, HexCell, Mesh, Patch
|
|
13
12
|
|
|
14
13
|
from .foamfile import FoamFile, write_header
|
|
15
14
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from dataclasses import dataclass
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
|
|
4
4
|
|
|
@@ -14,8 +14,9 @@ class FoamFile:
|
|
|
14
14
|
body: tuple[str, ...] = ()
|
|
15
15
|
version: float = 2.0
|
|
16
16
|
format: str = "ascii"
|
|
17
|
-
arch: str =
|
|
17
|
+
arch: str = "LSB;label=32;scalar=64"
|
|
18
18
|
foam_class: str = "vectorField"
|
|
19
|
+
meta: dict[str, tuple] = field(default_factory=dict)
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
def write_header(spec: FoamFile) -> str:
|
|
@@ -27,6 +28,13 @@ def write_header(spec: FoamFile) -> str:
|
|
|
27
28
|
output += f"\tclass\t{spec.foam_class};\n"
|
|
28
29
|
output += f'\tlocation\t"{spec.location}";\n'
|
|
29
30
|
output += f"\tobject\t{spec.foam_object};\n"
|
|
31
|
+
if spec.meta:
|
|
32
|
+
output += "\tmeta\n{\n"
|
|
33
|
+
for key, value in spec.meta.items():
|
|
34
|
+
values = list(value)
|
|
35
|
+
value_items = " ".join(str(item) for item in values)
|
|
36
|
+
output += f"\t{key} {len(values)}({value_items});\n"
|
|
37
|
+
output += "}\n"
|
|
30
38
|
output += "}\n\n"
|
|
31
39
|
return output
|
|
32
40
|
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from functools import cmp_to_key
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from icplot.geometry import Point
|
|
6
|
+
from icplot.geometry.mesh import Mesh, Vertex, get_hex_faces
|
|
7
|
+
|
|
8
|
+
from .foamfile import FoamFile, read_foamfile, write_header
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class Face:
|
|
13
|
+
"""
|
|
14
|
+
A mesh face, which is a sequence of vertices
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
vertices: tuple[int, ...]
|
|
18
|
+
id: int = -1
|
|
19
|
+
owner: int = -1
|
|
20
|
+
neighbour: int = -1
|
|
21
|
+
|
|
22
|
+
def get_centre(self, mesh: Mesh) -> Point:
|
|
23
|
+
x = sum(mesh.vertices[idx].x for idx in self.vertices)
|
|
24
|
+
y = sum(mesh.vertices[idx].y for idx in self.vertices)
|
|
25
|
+
z = sum(mesh.vertices[idx].z for idx in self.vertices)
|
|
26
|
+
return Point(
|
|
27
|
+
x / len(self.vertices), y / len(self.vertices), z / len(self.vertices)
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True)
|
|
32
|
+
class Cell:
|
|
33
|
+
"""
|
|
34
|
+
A mesh cell, which is a squence of faces
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
faces: tuple[int, ...]
|
|
38
|
+
id: int = -1
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True)
|
|
42
|
+
class Polymesh:
|
|
43
|
+
"""
|
|
44
|
+
The polymesh
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
vertices: tuple[Vertex, ...]
|
|
48
|
+
faces: tuple[Face, ...]
|
|
49
|
+
cells: tuple[Cell, ...]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(frozen=True)
|
|
53
|
+
class PolymeshPatch:
|
|
54
|
+
name: str
|
|
55
|
+
faces: tuple[Face, ...]
|
|
56
|
+
startFace: int
|
|
57
|
+
type: str = "patch"
|
|
58
|
+
|
|
59
|
+
def has_face(self, face: Face) -> bool:
|
|
60
|
+
for f in self.faces:
|
|
61
|
+
if f.id == face.id:
|
|
62
|
+
return True
|
|
63
|
+
return False
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def read_polymesh(path: Path) -> Polymesh:
|
|
67
|
+
|
|
68
|
+
_ = read_foamfile(path / "points")
|
|
69
|
+
# faces_file = read_foamfile(path / "faces")
|
|
70
|
+
return Polymesh((), (), ())
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _write_points(vertices: tuple[Vertex, ...]) -> str:
|
|
74
|
+
|
|
75
|
+
# Write file header
|
|
76
|
+
header = FoamFile(
|
|
77
|
+
location="constant/polyMesh", foam_object="points", foam_class="vectorField"
|
|
78
|
+
)
|
|
79
|
+
ret = write_header(header)
|
|
80
|
+
|
|
81
|
+
ret += f"{len(vertices)}\n(\n"
|
|
82
|
+
for v in vertices:
|
|
83
|
+
ret += f"({v.x} {v.y} {v.z})\n"
|
|
84
|
+
ret += ")\n"
|
|
85
|
+
return ret
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _write_faces(internal_faces: tuple[Face, ...], patches: list[PolymeshPatch]) -> str:
|
|
89
|
+
header = FoamFile(
|
|
90
|
+
location="constant/polyMesh", foam_object="faces", foam_class="faceList"
|
|
91
|
+
)
|
|
92
|
+
ret = write_header(header)
|
|
93
|
+
|
|
94
|
+
num_patch_faces = sum(len(p.faces) for p in patches)
|
|
95
|
+
num_faces = len(internal_faces) + num_patch_faces
|
|
96
|
+
|
|
97
|
+
ret += f"{num_faces}\n(\n"
|
|
98
|
+
for f in internal_faces:
|
|
99
|
+
verts = f.vertices
|
|
100
|
+
ret += f"{len(verts)}({verts[0]} {verts[1]} {verts[2]} {verts[3]})\n"
|
|
101
|
+
|
|
102
|
+
for p in patches:
|
|
103
|
+
for f in p.faces:
|
|
104
|
+
verts = f.vertices
|
|
105
|
+
ret += f"{len(verts)}({verts[0]} {verts[1]} {verts[2]} {verts[3]})\n"
|
|
106
|
+
ret += ")\n"
|
|
107
|
+
return ret
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _write_owners(
|
|
111
|
+
internal_faces: tuple[Face, ...], patches: list[PolymeshPatch]
|
|
112
|
+
) -> str:
|
|
113
|
+
header = FoamFile(
|
|
114
|
+
location="constant/polyMesh", foam_object="owner", foam_class="labelList"
|
|
115
|
+
)
|
|
116
|
+
ret = write_header(header)
|
|
117
|
+
|
|
118
|
+
num_patch_faces = sum(len(p.faces) for p in patches)
|
|
119
|
+
num_faces = len(internal_faces) + num_patch_faces
|
|
120
|
+
|
|
121
|
+
ret += f"{num_faces}\n(\n"
|
|
122
|
+
for f in internal_faces:
|
|
123
|
+
ret += f"{f.owner}\n"
|
|
124
|
+
for p in patches:
|
|
125
|
+
for f in p.faces:
|
|
126
|
+
ret += f"{f.owner}\n"
|
|
127
|
+
ret += ")\n"
|
|
128
|
+
return ret
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _write_neighbours(
|
|
132
|
+
internal_faces: tuple[Face, ...], patches: list[PolymeshPatch]
|
|
133
|
+
) -> str:
|
|
134
|
+
header = FoamFile(
|
|
135
|
+
location="constant/polyMesh", foam_object="neighbour", foam_class="labelList"
|
|
136
|
+
)
|
|
137
|
+
ret = write_header(header)
|
|
138
|
+
|
|
139
|
+
num_patch_faces = sum(len(p.faces) for p in patches)
|
|
140
|
+
num_faces = len(internal_faces) + num_patch_faces
|
|
141
|
+
|
|
142
|
+
ret += f"{num_faces}\n(\n"
|
|
143
|
+
for f in internal_faces:
|
|
144
|
+
ret += f"{f.neighbour}\n"
|
|
145
|
+
for p in patches:
|
|
146
|
+
for f in p.faces:
|
|
147
|
+
ret += f"{f.neighbour}\n"
|
|
148
|
+
ret += ")\n"
|
|
149
|
+
return ret
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
"""
|
|
153
|
+
def _write_face_zones(zones: list[FaceZone]) -> str:
|
|
154
|
+
|
|
155
|
+
header = FoamFile(
|
|
156
|
+
location="constant/polyMesh",
|
|
157
|
+
foam_object="faceZones",
|
|
158
|
+
foam_class="regIOobject",
|
|
159
|
+
meta={"names": tuple(zone.name for zone in zones)},
|
|
160
|
+
)
|
|
161
|
+
ret = write_header(header)
|
|
162
|
+
|
|
163
|
+
ret += f"{len(zones)}\n( "
|
|
164
|
+
for zone in zones:
|
|
165
|
+
ret += f"{zone.name}\n{{"
|
|
166
|
+
ret += "\ttype\tfaceZone;\n\tfaceLabels\tList<label>\n"
|
|
167
|
+
ret += f"{len(zone.faces)}\n("
|
|
168
|
+
for face in zone.faces:
|
|
169
|
+
ret += f"{face}\n"
|
|
170
|
+
ret += ");\n"
|
|
171
|
+
ret += "\tflipMap\tList<bool>\n"
|
|
172
|
+
ret += f"{len(zone.faces)}\n("
|
|
173
|
+
for face in zone.faces:
|
|
174
|
+
ret += "0\n"
|
|
175
|
+
ret += ");\n"
|
|
176
|
+
ret += "}\n\n"
|
|
177
|
+
ret += ")"
|
|
178
|
+
return ret
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _write_boundaries(patches: list[PolymeshPatch]):
|
|
183
|
+
|
|
184
|
+
header = FoamFile(
|
|
185
|
+
location="constant/polyMesh",
|
|
186
|
+
foam_object="boundary",
|
|
187
|
+
foam_class="polyBoundaryMesh",
|
|
188
|
+
)
|
|
189
|
+
ret = write_header(header)
|
|
190
|
+
|
|
191
|
+
ret += f"{len(patches)}(\n"
|
|
192
|
+
for p in patches:
|
|
193
|
+
ret += f"{p.name}\n{{"
|
|
194
|
+
ret += f"\ttype\t{p.type};\n"
|
|
195
|
+
ret += f"\tnFaces\t{len(p.faces)};\n"
|
|
196
|
+
ret += f"\tstartFace\t{p.startFace};\n"
|
|
197
|
+
ret += "}\n\n"
|
|
198
|
+
ret += ")"
|
|
199
|
+
return ret
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _faces_equal(f0, f1):
|
|
203
|
+
return set(f0) == set(f1)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _compare_faces(f0: Face, f1: Face):
|
|
207
|
+
if f0.owner == f1.owner:
|
|
208
|
+
if f0.neighbour == f1.neighbour:
|
|
209
|
+
return 0
|
|
210
|
+
elif f0.neighbour > f1.neighbour:
|
|
211
|
+
return 1
|
|
212
|
+
else:
|
|
213
|
+
return -1
|
|
214
|
+
else:
|
|
215
|
+
if f0.owner > f1.owner:
|
|
216
|
+
return 1
|
|
217
|
+
else:
|
|
218
|
+
return -1
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _build_faces(mesh: Mesh) -> list[Face]:
|
|
222
|
+
faces: list[Face] = []
|
|
223
|
+
|
|
224
|
+
count = 0
|
|
225
|
+
for idx, cell in enumerate(mesh.cells):
|
|
226
|
+
hex_faces = get_hex_faces(cell)
|
|
227
|
+
for hex_face in hex_faces:
|
|
228
|
+
found = False
|
|
229
|
+
for face in faces:
|
|
230
|
+
if _faces_equal(hex_face, face.vertices):
|
|
231
|
+
if idx < face.owner:
|
|
232
|
+
faces[face.id] = Face(
|
|
233
|
+
vertices=tuple(list(reversed(face.vertices))),
|
|
234
|
+
owner=idx,
|
|
235
|
+
id=face.id,
|
|
236
|
+
neighbour=face.owner,
|
|
237
|
+
)
|
|
238
|
+
else:
|
|
239
|
+
faces[face.id] = Face(
|
|
240
|
+
vertices=face.vertices,
|
|
241
|
+
owner=face.owner,
|
|
242
|
+
id=face.id,
|
|
243
|
+
neighbour=idx,
|
|
244
|
+
)
|
|
245
|
+
found = True
|
|
246
|
+
break
|
|
247
|
+
if not found:
|
|
248
|
+
faces.append(Face(id=count, vertices=hex_face, owner=idx, neighbour=-1))
|
|
249
|
+
count += 1
|
|
250
|
+
|
|
251
|
+
faces.sort(key=cmp_to_key(_compare_faces))
|
|
252
|
+
return faces
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _get_external_faces(faces: tuple[Face, ...]) -> tuple[Face, ...]:
|
|
256
|
+
return tuple(f for f in faces if f.neighbour == -1)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _get_internal_faces(faces: tuple[Face, ...]) -> tuple[Face, ...]:
|
|
260
|
+
return tuple(f for f in faces if f.neighbour != -1)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def write_polymesh(path: Path, mesh: Mesh, patch_funcs: dict):
|
|
264
|
+
|
|
265
|
+
# Write points
|
|
266
|
+
with open(path / "points", "w", encoding="utf-8") as f:
|
|
267
|
+
f.write(_write_points(mesh.vertices))
|
|
268
|
+
|
|
269
|
+
# Prepare faces, owners and neighbours
|
|
270
|
+
faces = _build_faces(mesh)
|
|
271
|
+
|
|
272
|
+
internal_faces = _get_internal_faces(tuple(faces))
|
|
273
|
+
external_faces = _get_external_faces(tuple(faces))
|
|
274
|
+
|
|
275
|
+
# Get patches
|
|
276
|
+
count = len(internal_faces)
|
|
277
|
+
patches = []
|
|
278
|
+
for name, func in patch_funcs.items():
|
|
279
|
+
patch_faces = tuple(f for f in external_faces if func(f.get_centre(mesh)))
|
|
280
|
+
patches.append(PolymeshPatch(name=name, faces=patch_faces, startFace=count))
|
|
281
|
+
count += len(patch_faces)
|
|
282
|
+
|
|
283
|
+
# Add a default wall patch
|
|
284
|
+
wall_faces = []
|
|
285
|
+
for face in external_faces:
|
|
286
|
+
found = False
|
|
287
|
+
for patch in patches:
|
|
288
|
+
if patch.has_face(face):
|
|
289
|
+
found = True
|
|
290
|
+
break
|
|
291
|
+
if not found:
|
|
292
|
+
wall_faces.append(face)
|
|
293
|
+
patches.append(
|
|
294
|
+
PolymeshPatch(
|
|
295
|
+
name="wall",
|
|
296
|
+
type="wall",
|
|
297
|
+
faces=tuple(f for f in wall_faces),
|
|
298
|
+
startFace=count,
|
|
299
|
+
)
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
with open(path / "faces", "w", encoding="utf-8") as f:
|
|
303
|
+
f.write(_write_faces(internal_faces, patches))
|
|
304
|
+
|
|
305
|
+
with open(path / "owner", "w", encoding="utf-8") as f:
|
|
306
|
+
f.write(_write_owners(internal_faces, patches))
|
|
307
|
+
|
|
308
|
+
with open(path / "neighbour", "w", encoding="utf-8") as f:
|
|
309
|
+
f.write(_write_neighbours(internal_faces, patches))
|
|
310
|
+
|
|
311
|
+
# Write boundaries
|
|
312
|
+
with open(path / "boundary", "w", encoding="utf-8") as f:
|
|
313
|
+
f.write(_write_boundaries(patches))
|