Skip to content

Commit 5f7d670

Browse files
authored
Merge pull request #14 from OpenSEMBA/dev
Dev
2 parents 4a84ac3 + 2882872 commit 5f7d670

30 files changed

Lines changed: 1678 additions & 618 deletions

.github/workflows/tests.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@ on:
77
push:
88
branches:
99
- master
10+
- dev
11+
1012
pull_request:
1113
branches:
1214
- master
15+
- dev
1316

1417
env:
1518
STEP2GMSH_TOP_DIR: step2gmsh

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,7 @@
22
*.pyc
33
*.msh
44
.vscode/
5+
tmpFolder/
6+
venv/
7+
gmshDoc/
8+
*.vtk

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ The tested input step files have been generated with [FreeCAD](https://www.freec
2626

2727
- A layer named `Conductor_N` with `N` being an integer represents a perfect conductor. `Conductor_0` is a special case of which represents the ground and defines the global domain. For layers named `Conductor_N` with `N` different to zero their areas will be substracted from the computational domain and removed.
2828
- Layers named as `Dielectric_N` are used to identify regions which will have a material assigned.
29-
- Open and semi-open problems can be defined using a single layer called `OpenRegion`.
29+
- Open and semi-open problems can be defined using a single layer called `OpenBoundary`.
3030

3131
Below is shown an example of a closed case with 6 conductors and 5 dielectrics, the external boundary corresponds to `Conductor_0`. The case is modeled with FreeCAD and can be found in the `testData/five_wires` folder together with the exported as a step file. The resulting mesh after applying `step2gmsh` is shown below.
3232

__init__.py

Whitespace-only changes.

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
gmsh >= 4.11
2-
pytest >= 7.3
2+
pytest >= 7.3
3+
numpy

src/AreaExporterService.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import json
2+
import gmsh
3+
from typing import Dict, List
4+
import numpy as np
5+
class AreaExporterService:
6+
_EMPTY_NAME_CASE = ""
7+
computedAreas:Dict[str,List]
8+
geometry: Dict
9+
def __init__(self):
10+
self.computedAreas = {
11+
"geometries": []
12+
}
13+
14+
def addComputedArea(self, geometry:str, area:float):
15+
geometry:Dict ={
16+
"geometry": geometry,
17+
"area": area,
18+
}
19+
self.computedAreas['geometries'].append(geometry)
20+
21+
def addPhysicalModelOfDimension(self, dimension=2):
22+
physicalGroups = gmsh.model.getPhysicalGroups(dimension)
23+
for physicalGroup in physicalGroups:
24+
entityTags = gmsh.model.getEntitiesForPhysicalGroup(*physicalGroup)
25+
geometryName = gmsh.model.getPhysicalName(*physicalGroup)
26+
for tag in entityTags:
27+
if dimension == 1:
28+
rad = gmsh.model.occ.getMass(dimension, tag) / (2*np.pi)
29+
area = rad*rad*np.pi
30+
if dimension == 2:
31+
area = gmsh.model.occ.getMass(dimension, tag)
32+
if geometryName != AreaExporterService._EMPTY_NAME_CASE:
33+
self.addComputedArea(geometryName, area)
34+
35+
def exportToJson(self, exportFileName:str):
36+
with open(exportFileName + ".areas.json", 'w') as f:
37+
json.dump(self.computedAreas, f, indent=3)

src/BoundingBox.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
from typing import Tuple, List, Dict
2+
import gmsh
3+
from . import utils
4+
5+
class BoundingBox():
6+
edges: Dict[str, float]
7+
def __init__(self, listOfCoordinates:Tuple[float,float,float,float,float,float]):
8+
self.edges = {
9+
'XMin': listOfCoordinates[0],
10+
'YMin': listOfCoordinates[1],
11+
'ZMin': listOfCoordinates[2],
12+
'XMax': listOfCoordinates[3],
13+
'YMax': listOfCoordinates[4],
14+
'ZMax': listOfCoordinates[5],
15+
}
16+
17+
def getOrigin(self) -> Tuple[float,float,float]:
18+
return (
19+
self.edges['XMin'],
20+
self.edges['YMin'],
21+
self.edges['ZMin']
22+
)
23+
24+
def getCenter(self) -> Tuple[float,float,float]:
25+
return (
26+
(self.edges['XMax'] + self.edges['XMin']) / 2,
27+
(self.edges['YMax'] + self.edges['YMin']) / 2,
28+
(self.edges['ZMax'] + self.edges['ZMin']) / 2
29+
)
30+
def getDiagonal(self) -> float:
31+
dx = self.edges['XMax'] - self.edges['XMin']
32+
dy = self.edges['YMax'] - self.edges['YMin']
33+
dz = self.edges['ZMax'] - self.edges['ZMin']
34+
return (dx**2 + dy**2 + dz**2) ** 0.5
35+
36+
def getLengths(self) -> Tuple[float, float, float]:
37+
return (
38+
self.edges['XMax'] - self.edges['XMin'],
39+
self.edges['YMax'] - self.edges['YMin'],
40+
self.edges['ZMax'] - self.edges['ZMin']
41+
)
42+
43+
@staticmethod
44+
def _getBoundingBox(element:Tuple[int,int]) -> 'BoundingBox':
45+
boundingBox:BoundingBox = BoundingBox(gmsh.model.occ.get_bounding_box(*element))
46+
return boundingBox
47+
48+
@staticmethod
49+
def getBoundingBoxFromGroup(elements:List[Tuple[int,int]]) -> 'BoundingBox':
50+
boundingBoxs:List[BoundingBox] = []
51+
for element in elements:
52+
boundingBoxs.append(BoundingBox._getBoundingBox(element))
53+
54+
if len(boundingBoxs) != 0:
55+
edges: Dict[str, List[float]] = {
56+
'XMin': [],
57+
'YMin': [],
58+
'ZMin': [],
59+
'XMax': [],
60+
'YMax': [],
61+
'ZMax': [],
62+
}
63+
for boundingBox in boundingBoxs:
64+
for key in edges.keys():
65+
edges[key].append(boundingBox.edges[key])
66+
67+
return BoundingBox(
68+
(
69+
utils.getMinFromList(edges['XMin']),
70+
utils.getMinFromList(edges['YMin']),
71+
utils.getMinFromList(edges['ZMin']),
72+
utils.getMaxFromList(edges['XMax']),
73+
utils.getMaxFromList(edges['YMax']),
74+
utils.getMaxFromList(edges['ZMax'])
75+
)
76+
)
77+
else:
78+
return BoundingBox((0,0,0,0,0,0))

src/ShapesClassification.py

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
from typing import Any, Tuple, List, Dict
2+
3+
import gmsh
4+
from .BoundingBox import BoundingBox
5+
from itertools import chain
6+
import numpy as np
7+
8+
class ShapesClassification:
9+
isOpenCase:bool
10+
11+
12+
def __init__(self, shapes):
13+
gmsh.model.occ.synchronize()
14+
15+
self.allShapes = shapes
16+
self.pecs = self.get_surfaces_with_label(shapes, "Conductor_")
17+
self.dielectrics = self.get_surfaces_with_label(shapes, "Dielectric_")
18+
self.open = self.get_surfaces_with_label(shapes, "OpenBoundary_")
19+
self.vacuum = dict()
20+
21+
self.isOpenCase = self.isOpenProblem()
22+
23+
if len(self.open) > 1:
24+
raise ValueError("Only one open region is allowed.")
25+
26+
@staticmethod
27+
def getNumberFromName(entity_name: str, label: str):
28+
ini = entity_name.rindex(label) + len(label)
29+
num = int(entity_name[ini:])
30+
return num
31+
32+
@staticmethod
33+
def get_surfaces_with_label(entity_tags, label: str):
34+
surfaces = dict()
35+
for s in entity_tags:
36+
name = gmsh.model.get_entity_name(*s)
37+
if s[0] != 2 or label not in name:
38+
continue
39+
num = ShapesClassification.getNumberFromName(name, label)
40+
surfaces[num] = [s]
41+
42+
return surfaces
43+
44+
def isOpenProblem(self):
45+
elements = list(chain(self.pecs.values()))
46+
for idx, element in enumerate(elements):
47+
for otheridx, otherElement in enumerate(elements[idx+1:]):
48+
if element != otherElement:
49+
intersect = gmsh.model.occ.intersect(
50+
element,
51+
otherElement,
52+
removeObject=False,
53+
tag=300+otheridx,
54+
removeTool=False
55+
)[0]
56+
if intersect:
57+
return False
58+
return True
59+
60+
def removeConductorsFromDielectrics(self):
61+
for num, diel in self.dielectrics.items():
62+
pec_surfs = []
63+
for num2, pec_surf in self.pecs.items():
64+
if num2 == 0 and not self.isOpenCase:
65+
continue
66+
pec_surfs.extend(pec_surf)
67+
self.dielectrics[num] = gmsh.model.occ.cut(diel, pec_surfs, removeTool=False)[0]
68+
69+
gmsh.model.occ.synchronize()
70+
71+
def ensureDielectricsDoNotOverlap(self):
72+
for n1, diel1 in self.dielectrics.items():
73+
others = list(
74+
chain(
75+
*[x[1] for x in self.dielectrics.items() if x[0] != n1]
76+
)
77+
)
78+
79+
if len(others) == 0:
80+
continue
81+
82+
self.dielectrics[n1] = gmsh.model.occ.cut(
83+
self.dielectrics[n1], others, removeObject=True, removeTool=False)[0]
84+
85+
gmsh.model.occ.synchronize()
86+
87+
def buildVacuumDomain(self):
88+
if self.isOpenCase and len(self.open) == 0:
89+
self.vacuum = self._buildDefaultVacuumDomain()
90+
elif self.isOpenCase and len(self.open) > 0:
91+
self.vacuum = self._buildVacuumDomainFromOpenBoundary()
92+
else:
93+
self.vacuum = self._buildClosedVacuumDomain()
94+
return self.vacuum
95+
96+
def _buildVacuumDomainFromOpenBoundary(self) -> Dict[int, List[int]]:
97+
dom = self.open[0]
98+
99+
surfsToRemove = []
100+
for num, surf in self.pecs.items():
101+
surfsToRemove.extend(surf)
102+
103+
for _, surf in self.dielectrics.items():
104+
surfsToRemove.extend(surf)
105+
106+
dom = gmsh.model.occ.cut(
107+
dom, surfsToRemove, removeObject=False, removeTool=False)[0]
108+
gmsh.model.occ.synchronize()
109+
110+
return dict([[0, dom]])
111+
112+
def _buildClosedVacuumDomain(self) -> Tuple[int, int]:
113+
dom = self.pecs[0]
114+
surfsToRemove = []
115+
for num, surf in self.pecs.items():
116+
if num == 0:
117+
continue
118+
surfsToRemove.extend(surf)
119+
120+
for _, surf in self.dielectrics.items():
121+
surfsToRemove.extend(surf)
122+
dom = gmsh.model.occ.cut(
123+
dom, surfsToRemove, removeObject=False, removeTool=False)[0]
124+
gmsh.model.occ.synchronize()
125+
return dict([[0, dom]])
126+
127+
def _buildDefaultVacuumDomain(self):
128+
nonVacuumSurfaces = []
129+
for _, surf in self.pecs.items():
130+
nonVacuumSurfaces.extend(surf)
131+
for _, surf in self.dielectrics.items():
132+
nonVacuumSurfaces.extend(surf)
133+
134+
boundingBox = BoundingBox.getBoundingBoxFromGroup(nonVacuumSurfaces)
135+
136+
137+
bbMaxLength = np.max(boundingBox.getLengths())
138+
nVOrigin = tuple(
139+
np.subtract(boundingBox.getCenter(),
140+
(bbMaxLength, bbMaxLength, 0.0)))
141+
nearVacuum = [(2, gmsh.model.occ.addRectangle(*nVOrigin, *(bbMaxLength*2.0,)*2))]
142+
143+
farVacuumDiameter = 4.0 * boundingBox.getDiagonal()
144+
farVacuum = [(2, gmsh.model.occ.addDisk(
145+
*boundingBox.getCenter(),
146+
farVacuumDiameter, farVacuumDiameter))]
147+
148+
gmsh.model.occ.synchronize()
149+
150+
farVacuum = gmsh.model.occ.cut(
151+
farVacuum, nearVacuum, removeObject=True, removeTool=False)[0]
152+
153+
nearVacuum = gmsh.model.occ.cut(
154+
nearVacuum, nonVacuumSurfaces, removeObject=True, removeTool=False)[0]
155+
156+
# -- Set mesh size for near vacuum region
157+
bb = BoundingBox(
158+
gmsh.model.getBoundingBox(2, nearVacuum[0][1]))
159+
minSide = np.min(np.array([bb.getLengths()[0], bb.getLengths()[1]]))
160+
161+
innerRegion = gmsh.model.getBoundary(nearVacuum, recursive=True)
162+
gmsh.model.mesh.setSize(innerRegion, minSide / 20)
163+
164+
165+
self.open = dict([[0, gmsh.model.getBoundary(farVacuum)]])
166+
gmsh.model.occ.synchronize()
167+
168+
return dict([[0, nearVacuum], [1, farVacuum]])
169+
170+

0 commit comments

Comments
 (0)