Skip to content

Commit 38be140

Browse files
authored
Merge pull request #25 from OpenSEMBA/dev
Dev
2 parents d6d8d5b + a9463f3 commit 38be140

File tree

47 files changed

+9443
-1050
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+9443
-1050
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
*.FCStd1
22
*.pyc
3-
*.msh
43
.vscode/
54
.pytest_cache/
65
.idea/
76
tmpFolder/
87
venv/
98
gmshDoc/
9+
10+
*.msh
1011
*.vtk
12+
*.areas.json

README.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,26 @@ Install requirements with
1616

1717
## Usage
1818

19+
Step2gmsh requires two diferent files:
20+
- A json file where material properties are described for each geometry
21+
- A step file with all the geometry info
22+
23+
Both files must have the same label and share folder path.
24+
An example of those files can be found in [Five wires case](testData/five_wires/)
25+
1926
Launch from command line as
2027

2128
```shell
2229
python src/step2gmsh.py <-i path_to_step_file>
2330
```
2431

25-
The tested input step files have been generated with [FreeCAD](https://www.freecad.org/). The geometrical entities within the step file must be separated in layers. The operations which are performed of the different layers depend on their name.
32+
The tested input step files have been generated with [FreeCAD](https://www.freecad.org/). The geometrical entities within the step file must be separated in layers. The operations performed on the different layers depend on their material asignment registered in the json file.
2633

27-
- 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.
28-
- Layers named as `Dielectric_N` are used to identify regions which will have a material assigned.
34+
- A layer with a `PEC material`, represent a perfect conductor. In case one of the layers surrounds the rest of elements, it will be asigned as ground and defines the global domain for the rest of conductors. Internally, this will be represented as Conductor_0. The areas of the rest of conductors different to zero will be substracted from the computational domain and removed. In open cases, Conductor_0 is just another conductor and the domain is defined using the bounding box of the layers.
35+
- Layers registered as `Dielectric` are used to identify regions which will have a material assigned.
2936
- Open and semi-open problems can be defined using a single layer called `OpenBoundary`.
3037

31-
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.
38+
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](testData/five_wires/) folder together with the exported as a step file. The resulting mesh after applying `step2gmsh` is shown below.
3239

3340
![Five wires example as modeled with FreeCAD](doc/fig/five_wires_freecad.png)
3441
![Five wires example meshed with gmsh](doc/fig/five_wires_gmsh.png)

partially_filled_coax.areas.json

Lines changed: 0 additions & 20 deletions
This file was deleted.

src/AreaExporterService.py

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,42 @@ def __init__(self):
1111
"geometries": []
1212
}
1313

14-
def addComputedArea(self, geometry:str, area:float):
14+
def addComputedArea(self, geometry:str, label:str, area:float):
1515
geometry:Dict ={
1616
"geometry": geometry,
17-
"area": area,
17+
"label": label,
18+
"area": round(area,6),
1819
}
1920
self.computedAreas['geometries'].append(geometry)
2021

21-
def addPhysicalModelOfDimension(self, dimension=2):
22-
physicalGroups = gmsh.model.getPhysicalGroups(dimension)
22+
def addPhysicalModelForConductors(self, mappedElements:Dict[str,str]):
23+
physicalGroups = gmsh.model.getPhysicalGroups(1)
2324
for physicalGroup in physicalGroups:
2425
entityTags = gmsh.model.getEntitiesForPhysicalGroup(*physicalGroup)
2526
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)
27+
if not geometryName.startswith("Conductor_"):
28+
continue
3429

30+
label = ''
31+
for key, geometry in mappedElements.items():
32+
if geometry == geometryName:
33+
label = key
34+
break
35+
36+
# Find surface that has these curves as boundaries
37+
allSurfaces = gmsh.model.getEntities(2)
38+
foundSurface = None
39+
for surface in allSurfaces:
40+
boundary = gmsh.model.getBoundary([surface], oriented=False, recursive=False)
41+
boundaryTags = set(tag for dim, tag in boundary)
42+
if set(entityTags) == boundaryTags:
43+
foundSurface = surface
44+
break
45+
46+
if foundSurface:
47+
area = gmsh.model.occ.getMass(2, foundSurface[1])
48+
self.addComputedArea(geometryName, label, area)
49+
3550
def exportToJson(self, exportFileName:str):
3651
with open(exportFileName + ".areas.json", 'w') as f:
3752
json.dump(self.computedAreas, f, indent=3)

src/Graph.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
from collections import defaultdict, deque
2+
from typing import Dict, List, Tuple
3+
4+
class Graph:
5+
def __init__(self):
6+
self._nodes:List = []
7+
self._edges:List[Tuple] = []
8+
9+
@property
10+
def roots(self) -> List:
11+
roots:List = []
12+
for node in self._nodes:
13+
isChild = False
14+
for edge in self._edges:
15+
if edge[-1] == node:
16+
isChild = True
17+
continue
18+
if isChild == False:
19+
roots.append(node)
20+
return roots.copy()
21+
22+
@property
23+
def nodes(self) -> List:
24+
return self._nodes.copy()
25+
26+
@property
27+
def edges(self) -> List:
28+
return self._edges.copy()
29+
30+
@nodes.setter
31+
def nodes(self, nodes):
32+
self._nodes = list(nodes)
33+
34+
@edges.setter
35+
def edges(self, edges):
36+
self._edges = [tuple(e) for e in edges]
37+
38+
def add_node(self, node):
39+
if node not in self._nodes:
40+
self._nodes.append(node)
41+
42+
def add_edge(self, source, destination):
43+
if source not in self._nodes:
44+
self.add_node(source)
45+
if destination not in self._nodes:
46+
self.add_node(destination)
47+
if (source, destination) not in self._edges:
48+
self._edges.append((source, destination))
49+
50+
def get_connections(self):
51+
connections = {node: [] for node in self._nodes}
52+
for source, destination in self._edges:
53+
connections[source].append(destination)
54+
return connections
55+
56+
def getParentNodes(self) -> List:
57+
return [edge[0] for edge in self._edges]
58+
59+
def getChildNodes(self) -> List:
60+
return [edge[-1] for edge in self._edges]
61+
62+
def prune_to_longest_paths(self):
63+
connections = self.get_connections()
64+
roots = [n for n in self._nodes if n not in self.getChildNodes()]
65+
66+
longest_paths = []
67+
68+
def dfs(node, path):
69+
path = path + [node]
70+
if node not in connections or not connections[node]:
71+
longest_paths.append(path)
72+
return
73+
for child in connections[node]:
74+
dfs(child, path)
75+
76+
for root in roots:
77+
dfs(root, [])
78+
79+
leaf_to_path = {}
80+
for path in longest_paths:
81+
leaf = path[-1]
82+
if leaf not in leaf_to_path or len(path) > len(leaf_to_path[leaf]):
83+
leaf_to_path[leaf] = path
84+
85+
new_nodes = set()
86+
new_edges = set()
87+
for path in leaf_to_path.values():
88+
new_nodes.update(path)
89+
new_edges.update([(path[i], path[i+1]) for i in range(len(path)-1)])
90+
91+
self._nodes = list(new_nodes)
92+
self._edges = list(new_edges)
93+
94+
def getAdyacencyTree(self) -> Dict:
95+
tree = defaultdict(list)
96+
for root in self.roots:
97+
tree[''].append(root)
98+
for parent, child in self._edges:
99+
tree[parent].append(child)
100+
return tree
101+
102+
def getNodesByLevels(self) -> List:
103+
adyacencyTree = self.getAdyacencyTree()
104+
qeue = deque([('',0)])
105+
nodeList = []
106+
while qeue:
107+
node,level = qeue.popleft()
108+
nodeList.append(node)
109+
for child in adyacencyTree[node]:
110+
qeue.append((child, level+1))
111+
return nodeList[1:] #Removes case 0 that is not part of nodes
112+
113+
def _reorderData(self) -> None:
114+
self._edges = sorted(self._edges)
115+
self._nodes = sorted(self._nodes)
116+
117+
def __str__(self):
118+
return f"Graph(Nodes: {self._nodes},\n Edges: {self._edges})"

0 commit comments

Comments
 (0)