femagtools 1.7.3__py3-none-any.whl → 1.7.4__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.
femagtools/__init__.py CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  """
4
4
  __title__ = 'femagtools'
5
- __version__ = '1.7.3'
5
+ __version__ = '1.7.4'
6
6
  __author__ = 'Ronald Tanner'
7
7
  __license__ = 'BSD'
8
8
  __copyright__ = 'Copyright 2023-2024 Gamma Technology'
femagtools/airgap.py CHANGED
@@ -52,3 +52,14 @@ def read(filename, pmod=0):
52
52
  return(dict())
53
53
 
54
54
  return fft(bag[0], bag[1], pmod)
55
+
56
+ if __name__ == '__main__':
57
+ import sys
58
+ import matplotlib.pyplot as plt
59
+ import femagtools.plot.fluxdens
60
+ ag = read(sys.argv[1])
61
+ fig, axs = plt.subplots(nrows=2)
62
+ femagtools.plot.fluxdens.airgap(ag, ax=axs[0])
63
+ femagtools.plot.fluxdens.airgap_fft(ag, ax=axs[1])
64
+ fig.tight_layout()
65
+ plt.show()
femagtools/bch.py CHANGED
@@ -70,7 +70,7 @@ def sttylosses(losses):
70
70
  except KeyError:
71
71
  pass
72
72
  return d
73
-
73
+
74
74
  if sregs == {'StYoke', 'StTeeth'}:
75
75
  return hysteddy('StYoke', 'StTeeth', losses)
76
76
  if sregs == {'StJo', 'StZa'}:
@@ -95,9 +95,9 @@ def sttylosses(losses):
95
95
  return l
96
96
  return {}
97
97
 
98
- def losses_mapping_external_rotor(losses):
98
+ def losses_mapping_external_rotor(losses):
99
99
  styoke = 'rotor'
100
- rotor = 'Iron'
100
+ rotor = 'Iron'
101
101
  d = {}
102
102
  try:
103
103
  d['styoke'] = losses[styoke]
@@ -1046,7 +1046,7 @@ class Reader:
1046
1046
  '''
1047
1047
  diff = np.floor(np.abs(np.diff(idList)))
1048
1048
  if idList[-1] == 0 and len(idList) > 2 and \
1049
- not np.all(diff == diff[0]):
1049
+ diff[-1] == 0:
1050
1050
  idList = idList[:-1]
1051
1051
  return idList
1052
1052
 
@@ -1062,7 +1062,8 @@ class Reader:
1062
1062
  m.append([floatnan(x) for x in rec])
1063
1063
 
1064
1064
  m = np.array(m).T
1065
- ncols = np.argmax(np.abs(m[1][1:]-m[1][:-1]))+1
1065
+ d = np.diff(m[1])
1066
+ ncols = (len(d)+1)//(len(d[d < 0])+1)
1066
1067
  if ncols == 1 and len(m[1]) > 1 and m[1][0] != m[1][1]: # simple correction
1067
1068
  ncols = 2
1068
1069
  iq = np.reshape(m[1], (-1, ncols))[0]
@@ -1071,7 +1072,8 @@ class Reader:
1071
1072
  ncols = ncols-1
1072
1073
 
1073
1074
  id = np.reshape(m[0], (-1, ncols)).T[0]
1074
- id = self.__removeTrailingZero(id)
1075
+ if id[0] >= 0:
1076
+ id = self.__removeTrailingZero(id)
1075
1077
  nrows = len(id)
1076
1078
  if nrows > 1 and id[nrows-1] < id[nrows-2]:
1077
1079
  nrows = nrows-1
@@ -1096,7 +1098,8 @@ class Reader:
1096
1098
  m.append([floatnan(x) for x in rec])
1097
1099
 
1098
1100
  m = np.array(m).T
1099
- ncols = np.argmax(np.abs(m[1][1:]-m[1][:-1]))+1
1101
+ d = np.diff(m[1])
1102
+ ncols = (len(d)+1)//(len(d[d < 0])+1)
1100
1103
  if ncols == 1 and len(m[1]) > 1 and m[1][0] != m[1][1]: # simple correction
1101
1104
  ncols = 2
1102
1105
  iq = np.linspace(np.min(m[1]), np.max(m[1]), ncols)
@@ -1105,7 +1108,8 @@ class Reader:
1105
1108
  ncols = ncols-1
1106
1109
 
1107
1110
  id = np.reshape(m[0], (-1, ncols)).T[0]
1108
- id = self.__removeTrailingZero(id)
1111
+ if id[0] >= 0:
1112
+ id = self.__removeTrailingZero(id)
1109
1113
  nrows = len(id)
1110
1114
  if nrows > 1 and id[nrows-1] < id[nrows-2]:
1111
1115
  nrows = nrows-1
@@ -1168,7 +1172,7 @@ class Reader:
1168
1172
  for name in content[2].split('\t') if name][2:]
1169
1173
  # outer rotor motor
1170
1174
  if 'Iron' in subregs and \
1171
- 'Rotor' in subregs:
1175
+ 'Rotor' in subregs:
1172
1176
  self.external_rotor = True
1173
1177
  logger.info("Stator Subregions: %s", subregs)
1174
1178
  speed = float(content[0].split()[-1])/60.
@@ -1184,7 +1188,11 @@ class Reader:
1184
1188
  if not m:
1185
1189
  return
1186
1190
  m = np.array(m).T
1187
- ncols = np.argmax(np.abs(m[1][1:]-m[1][:-1]))+1
1191
+ d = np.diff(m[1])
1192
+ if self.ldq:
1193
+ ncols = (len(d)+1)//(len(d[d > 0])+1)
1194
+ else:
1195
+ ncols = (len(d)+1)//(len(d[d < 0])+1)
1188
1196
  if ncols == 1 and len(m[1]) > 1 and m[1][0] != m[1][1]:
1189
1197
  ncols = 2
1190
1198
  id = np.reshape(m[0], (-1, ncols)).T[0]
@@ -1229,9 +1237,9 @@ class Reader:
1229
1237
  (nrows, ncols)).T.tolist()
1230
1238
  for k, v in zip(cols, m[2:])})
1231
1239
  ls['speed'] = speed
1232
- if self.external_rotor:
1240
+ if self.external_rotor:
1233
1241
  ls.update(losses_mapping_external_rotor(ls))
1234
- else:
1242
+ else:
1235
1243
  ls.update(sttylosses(ls))
1236
1244
  if self.ldq:
1237
1245
  self.ldq['losses'] = ls
@@ -1487,9 +1495,9 @@ class Reader:
1487
1495
  def __read_losses(self, content):
1488
1496
  losses = {}
1489
1497
  i = 0
1490
- # check if external rotor
1498
+ # check if external rotor
1491
1499
  if self.weights[0][1] == 0.0 and \
1492
- self.weights[0][-1] > 0:
1500
+ self.weights[0][-1] > 0:
1493
1501
  self.external_rotor = True
1494
1502
  # find results for angle:
1495
1503
  while True:
@@ -1548,8 +1556,8 @@ class Reader:
1548
1556
  losses['total'] += losses['staza']+losses['stajo']
1549
1557
  elif len(rec) == 1:
1550
1558
  t = l.split(':')[-1].strip()
1551
- if t == 'Iron':
1552
- if self.external_rotor:
1559
+ if t == 'Iron':
1560
+ if self.external_rotor:
1553
1561
  losses['rotfe'] = floatnan(rec[0])
1554
1562
  losses['total'] += losses['rotfe']
1555
1563
  else:
@@ -1561,14 +1569,14 @@ class Reader:
1561
1569
  continue
1562
1570
 
1563
1571
  if _rotloss.search(l):
1564
- if l.find('StZa') > -1:
1572
+ if l.find('StZa') > -1:
1565
1573
  self.external_rotor = True
1566
1574
  rec = self.__findNums(content[i+2])
1567
1575
  if len(rec) == 1:
1568
- if self.external_rotor:
1576
+ if self.external_rotor:
1569
1577
  losses['staza'] = floatnan(rec[0])
1570
1578
  losses['total'] += losses['staza']
1571
- else:
1579
+ else:
1572
1580
  rotfe = floatnan(rec[0])
1573
1581
  losses['rotfe'] += rotfe
1574
1582
  losses['total'] += rotfe
@@ -1602,7 +1610,7 @@ class Reader:
1602
1610
  if content[i+1].split() == ['rotf', '----']:
1603
1611
  losses['rotfe'] = sum([floatnan(x) for x in rec])
1604
1612
  losses['total'] += losses['rotfe']
1605
-
1613
+
1606
1614
  if content[i+1].split() == ['Iron', '----']: # external rotor
1607
1615
  losses['rotfe'] = sum([floatnan(x) for x in rec])
1608
1616
  losses['total'] += losses['rotfe']
@@ -189,7 +189,7 @@ class FslRenderer(object):
189
189
  '-- min_corner = {}, {}'.format(
190
190
  geom.start_min_corner(0),
191
191
  geom.start_min_corner(1)),
192
- '-- max_corner = {}, {}'.format(
192
+ '-- max_corner start = {}, {}'.format(
193
193
  geom.start_max_corner(0),
194
194
  geom.start_max_corner(1)),
195
195
  '\n']
@@ -211,11 +211,12 @@ class FslRenderer(object):
211
211
  'r, phi = c2pr(x0, y0)',
212
212
  'x1, y1 = pr2c(r1, phi)',
213
213
  'x2, y2 = pr2c(r1, {}*math.pi/parts)'.format(slice),
214
+ f'r = {geom.dist_end_max_corner()}',
214
215
  'x3, y3 = pr2c(r, {}*math.pi/parts)'.format(slice),
215
216
  'nc_line(x0, y0, x1, y1, 0)',
216
217
  'nc_circle_m(x1, y1, x2, y2, 0.0, 0.0, 0)',
217
218
  'nc_line(x2, y2, x3, y3, 0)',
218
- 'x0, y0 = pr2c(r1 - hair/2, math.pi/parts)',
219
+ 'x0, y0 = pr2c(r1 - hair/2, math.pi/parts/2)',
219
220
  'create_mesh_se(x0, y0)',
220
221
  '\n',
221
222
  'outer_da_start = {}'.format(
femagtools/fsl.py CHANGED
@@ -106,6 +106,7 @@ class Builder:
106
106
  params['show_plots'] = model.stator[templ].get('plot', False)
107
107
  params['write_fsl'] = True
108
108
  params['airgap'] = -1.0
109
+ params['nodedist'] = model.stator.get('nodedist', 1)
109
110
  pos = 'in' if model.external_rotor else 'out'
110
111
  params['part'] = ('stator', pos)
111
112
  conv = convert(model.stator['dxffile']['name'], **params)
@@ -491,6 +492,11 @@ class Builder:
491
492
  if not hasattr(model, 'stator'):
492
493
  setattr(model, 'stator', {})
493
494
  model.stator['num_slots'] = conv.get('tot_num_slot')
495
+ if model.get('num_agnodes', 0) == 0:
496
+ model.set_value('agndst', conv['agndst']*1e-3)
497
+ logger.info("num poles %d num slots %d outer diameter %.4f m agndst %.4f mm",
498
+ model.poles, model.stator['num_slots'],
499
+ model.outer_diam, model.agndst*1e3)
494
500
  if params['full_model']:
495
501
  model.stator['num_slots_gen'] = model.stator['num_slots']
496
502
  else:
@@ -531,21 +537,6 @@ class Builder:
531
537
  logger.info("create new model '%s'", model.name)
532
538
  if model.is_dxffile():
533
539
  self.prepare_model_with_dxf(model)
534
- if model.get('num_agnodes', 0) == 0:
535
- from femagtools.dxfsl.fslrenderer import agndst
536
- ag = model.get('airgap')
537
- model.set_value(
538
- 'agndst',
539
- agndst(model.get('bore_diam'),
540
- model.get('bore_diam') - 2*ag,
541
- model.stator.get('num_slots'),
542
- model.get('poles'),
543
- model.dxffile.get('nodedist')))
544
-
545
- logger.info(" num poles %d num slots %d outer diameter %.4f m",
546
- model.poles, model.stator['num_slots'],
547
- model.outer_diam)
548
-
549
540
  else:
550
541
  self.prepare_stator(model)
551
542
  if hasattr(model, 'magnet'):
@@ -553,9 +544,9 @@ class Builder:
553
544
  self.prepare_diameter(model)
554
545
  if self.fsl_stator and model.get('num_agnodes', 0) == 0:
555
546
  from femagtools.dxfsl.fslrenderer import agndst
556
- if model.get('agndst'):
547
+ if model.get('agndst',0):
557
548
  pass
558
- else:
549
+ else:
559
550
  ag = model.get('airgap')
560
551
  model.set_value(
561
552
  'agndst',
@@ -0,0 +1,403 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ heat source network analysis
4
+
5
+ @author: werner b. vetter
6
+ """
7
+
8
+ import xml.etree.ElementTree as ET
9
+ import numpy as np
10
+ import json
11
+
12
+
13
+ class HeatSourceNetwork:
14
+ #Read the json-file with the heat source network definition
15
+
16
+ #If you modify the netlist, you must call the command "process()" again
17
+
18
+ #Parameter:
19
+ # filename (str): Filename of heat source network with extension (.hsn)
20
+
21
+ def __init__(self, netlist):
22
+ self.netlist = netlist
23
+ self.process()
24
+
25
+ def get_node_names(self):
26
+ return [n['Name'] for n in self.netlist['Nodes']]
27
+
28
+ def process(self):
29
+ #Set up independent admittance matrix and the source vector
30
+
31
+ self.nodes = []
32
+ for branch in self.netlist['Branches']:
33
+ branch_nodes = branch['Nodes']
34
+ for nd in range(len(branch_nodes)):
35
+ node = branch_nodes[nd]
36
+ if not(node in self.nodes):
37
+ self.nodes.append(node)
38
+ self.nodes.sort()
39
+
40
+ self.G = np.zeros((len(self.nodes)-1,len(self.nodes)-1))
41
+ self.P = np.zeros(len(self.nodes)-1)
42
+
43
+ for branch in self.netlist['Branches']:
44
+ ind1 = self.nodes.index(branch['Nodes'][0])-1
45
+ ind2 = self.nodes.index(branch['Nodes'][1])-1
46
+ if branch['Type'] == 'R_th':
47
+ if ind1 != -1:
48
+ self.G[ind1,ind1] = self.G[ind1,ind1] + 1/branch['val']
49
+ if ind2 != -1:
50
+ self.G[ind2,ind2] = self.G[ind2,ind2] + 1/branch['val']
51
+ if ind1 != -1 and ind2 != -1:
52
+ self.G[ind1,ind2] = self.G[ind1,ind2] - 1/branch['val']
53
+ self.G[ind2,ind1] = self.G[ind2,ind1] - 1/branch['val']
54
+
55
+ if branch['Type'] == 'Power_Source':
56
+ if ind1 == -1:
57
+ self.P[ind2] = self.P[ind2] + branch['val']
58
+ if ind2 == -1:
59
+ self.P[ind1] = self.P[ind1] + branch['val']
60
+
61
+ if branch['Type'] == 'Temperature_Source':
62
+ if ind1 == -1:
63
+ self.P[ind2] = self.P[ind2] + branch['T_val']/branch['R_val']
64
+ self.G[ind2,ind2] = self.G[ind2,ind2] + 1/branch['R_val']
65
+ if ind2 == -1:
66
+ self.P[ind1] = self.P[ind1] + branch['T_val']/branch['R_val']
67
+ self.G[ind1,ind1] = self.G[ind1,ind1] + 1/branch['R_val']
68
+
69
+
70
+ def solve(self):
71
+ #Solve the system of equations
72
+
73
+ self.T = np.linalg.solve(self.G, self.P)
74
+ return(self.T)
75
+
76
+ def draw(self,filename):
77
+ #Creates an xml file of the network that can be displayed with draw.io
78
+
79
+ #Parameter:
80
+ # filename (str): Filename of diagram with extension (.xml)
81
+
82
+ HeatSourceDiagram.draw(self.netlist,filename)
83
+ return()
84
+
85
+
86
+ def read(filename):
87
+ with open(filename) as fp:
88
+ netlist = json.load(fp)
89
+ return HeatSourceNetwork(netlist)
90
+
91
+
92
+ class HeatSourceDiagram:
93
+ #Init a diagram to create a diagram of a heat source netlist
94
+ def __init__(self):
95
+ self.mxfile = self.createFile()
96
+ self.diagram = self.addDiagram(self.mxfile)
97
+ self.graph = self.addGraphModel(self.diagram)
98
+ self.root = self.addRoot(self.graph)
99
+ mxCell_dict = {"id":"0"}
100
+ self.addCell(self.root,mxCell_dict)
101
+ mxCell_dict = {"id":"1", "parent":"0"}
102
+ self.addCell(self.root,mxCell_dict)
103
+
104
+
105
+ def writeFile(self,filename):
106
+ tree = ET.ElementTree(self.mxfile)
107
+ tree.write(filename, encoding="utf-8")
108
+
109
+
110
+ def createFile(self):
111
+ mxfile = ET.Element('mxfile')
112
+ mxfile.attrib = {"host":"Electron",
113
+ "modified":"2023-03-08T08:47:19.445Z",
114
+ "agent":"5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/20.8.16 Chrome/106.0.5249.199 Electron/21.4.0 Safari/537.36",
115
+ "etag":"5VmoL6vL6aGyHSSOdqAj",
116
+ "version":"20.8.16",
117
+ "type":"device"
118
+ }
119
+ return mxfile
120
+
121
+ def addDiagram(self,parent):
122
+ diagram = ET.SubElement(parent, 'diagram')
123
+ diagram.attrib = {"name":"Seite-1",
124
+ "id":"5XRmyqCaqz2rHzUy15r-"
125
+ }
126
+ return diagram
127
+
128
+ def addGraphModel(self,parent):
129
+ mxGraphModel = ET.SubElement(parent, 'mxGraphModel')
130
+ mxGraphModel.attrib = {"dx":"770",
131
+ "dy":"569",
132
+ "grid":"1",
133
+ "gridSize":"10",
134
+ "guides":"1",
135
+ "tooltips":"1",
136
+ "connect":"1",
137
+ "arrows":"1",
138
+ "fold":"1",
139
+ "page":"1",
140
+ "pageScale":"1",
141
+ "pageWidth":"827",
142
+ "pageHeight":"1169",
143
+ "math":"0",
144
+ "shadow":"0"
145
+ }
146
+ return mxGraphModel
147
+
148
+ def addRoot(self,parent):
149
+ root = ET.SubElement(parent, 'root')
150
+ root.attrib = {}
151
+ return root
152
+
153
+ def addCell(self,parent,cell_dict):
154
+ mxCell = ET.SubElement(parent, 'mxCell')
155
+ mxCell.attrib = cell_dict
156
+ return mxCell
157
+
158
+ def addGeometry(self,parent,geometry_dict):
159
+ mxGeometry = ET.SubElement(parent, 'mxGeometry')
160
+ mxGeometry.attrib = geometry_dict
161
+ return mxGeometry
162
+
163
+ def addPntArray(self,parent,pntArray):
164
+ array = ET.SubElement(parent, 'Array')
165
+ array.attrib = {"as":"points"}
166
+ mxPoint = ET.SubElement(array, 'mxPoint')
167
+ if isinstance(pntArray[0], list):
168
+ for i in range(len(pntArray)):
169
+ pnt = pntArray[i]
170
+ mxPoint.attrib = {"x":str(pnt[0]), "y":str(pnt[1])}
171
+ else:
172
+ pnt = pntArray
173
+ mxPoint.attrib = {"x":str(pnt[0]), "y":str(pnt[1])}
174
+ return array
175
+
176
+ def addNode(self,nodeKey,x,y):
177
+ mxCell_dict = {"id":"node_"+str(nodeKey),
178
+ "value":str(nodeKey),
179
+ "style":"shape=waypoint;sketch=0;fillStyle=solid;size=6;pointerEvents=1;points=[];fillColor=none;resizable=0;rotatable=0;perimeter=centerPerimeter;snapToPoint=1;verticalAlign=top;spacingBottom=0;spacingTop=-5;fontFamily=Verdana;fontSize=12;",
180
+ "vertex":"1",
181
+ "parent":"1"
182
+ }
183
+ cell = self.addCell(self.root,mxCell_dict)
184
+ mxGeometry_dict = {"x":str(x-20),
185
+ "y":str(y-20),
186
+ "width":"40",
187
+ "height":"40",
188
+ "as":"geometry"
189
+ }
190
+ geometry = self.addGeometry(cell,mxGeometry_dict)
191
+
192
+ return geometry
193
+
194
+ def addPowerSource(self,name,value,x,y,rotation=0):
195
+ mxCell_dict = {"id":name,
196
+ "value":"Name = "+name+"\nLosses = "+str(value),
197
+ "style":"shape=stencil(vVXhboMgEH4a/i4I8QEWur7AHqCh9DZJFcyJ7fb2Q8CtteJWs42YmPsOvrvv44yEi66SLRBGjWyA8A1hbCN2okcE43bPtkfls8xvqGK2oDTG5xizMZZdC8pF8CRRy32dTnYO7RHO+uAShzYVoHZDlj8R+uj3DA8XyhrjSbQ13VXmIu/JpDb+LH2LZKn8e4oeyhi3vkIDDjC1HdEvlQkgbPvjSsW6Smy+EhceyQnmYi/V8RVtbw6z/UVPla0teiC+QzXC+DasZX043FXOw/kLng5Ajjq2llOc0cXFi0VYKZiGtdxVK4fpu0mM6caeIOdHuUg9MtTaXDCU91AEYzINLvi5SlpxV2fz4j5H4j/UQV3rtstbez2txa9N6zp3J858U37e3inJX7gbjt18cgGNf4QAfAA=);whiteSpace=wrap;html=1;labelPosition=center;verticalLabelPosition=bottom;align=center;verticalAlign=top;rotation="+str(rotation)+";",
198
+ "vertex":"1",
199
+ "parent":"1"
200
+ }
201
+ cell = self.addCell(self.root,mxCell_dict)
202
+ mxGeometry_dict = {"x":str(x-60),
203
+ "y":str(y-30),
204
+ "width":"120",
205
+ "height":"60",
206
+ "as":"geometry"
207
+ }
208
+ geometry = self.addGeometry(cell,mxGeometry_dict)
209
+
210
+ return geometry
211
+
212
+
213
+ def addTemperatureSource(self,name,T,Rth,x,y,rotation=0):
214
+ mxCell_dict = {"id":name,
215
+ "value":"Name = "+name+"\nT = "+str(T)+"\nRth = "+str(Rth),
216
+ "style":"shape=stencil(vVXbboMwDP2avE65jO15YusPdO9TSt0RNSQohHb7+4UEtpaSbGUVERKynRz7HJuAWN6UvAZEseIVIPaMKH2FqgbDbWvgba1bU7gwdTvKECYYB/sYbDrYvKmhsMF54EbwjexPNtboPRzF1vYYQpVghO2i7AXhJ7ene1heaKUciNCqOYucxB0YF8qdxR8BrE//2Vt3WbAdB1GBBdOXHbw/NHsHoqs/ZyLzMtHpTCx3nhhhlm94sX83ulXbyfqCpoWW2jhHePtsiLKVX2l+putVTMPpBo8HIAYdSosxjvBi+U4bmEkY+5Wuqubd9F0EhnClDxDTI0tCDwhSqBOE7BoIL0ykwISes6iRqyqbJvc9EkuwAylF3cSlPZ9WcrNpnaXu4//FJWxBcXdCyoW+KTIamvsZ2txC3lEZDzfASFNJt6jrwK9tSl+pFzen94Y/u3d8AQ==);whiteSpace=wrap;html=1;verticalAlign=top;spacing=0;labelPosition=center;verticalLabelPosition=bottom;align=center;verticalAlign=top;rotation="+str(rotation)+";",
217
+ "vertex":"1",
218
+ "parent":"1"
219
+ }
220
+ cell = self.addCell(self.root,mxCell_dict)
221
+ mxGeometry_dict = {"x":str(x-60),
222
+ "y":str(y-30),
223
+ "width":"120",
224
+ "height":"60",
225
+ "as":"geometry"
226
+ }
227
+ geometry = self.addGeometry(cell,mxGeometry_dict)
228
+
229
+ return geometry
230
+
231
+ def addResistor(self,name,value,x,y,rotation=0):
232
+ mxCell_dict = {"id":name,
233
+ "value":"Name = "+name+"\nR = "+str(value),
234
+ "style":"shape=stencil(vVRtbsMgDD0NfycC6wEq1h5gN6Cpt6AmEAFtt9sXMOlniNpoGoqU+Dl5fs+2QrhwjeyBMKplB4R/EMY+wSnnjQ2PAW8QfKcYHjFkNMfS9VB7BA/SKrlpATPOW7ODo9r6TKF0A1b5mOUrQpfhnXhxURutA4ky2t1krvKBTCodvqU/SJbL/+bobYFxHyp04MEiXiF68ZYBwtZPV6rmVWLjlbgISMkwFxtZ776t2evtqD7saW3aOB2K91SNML5OZ9qfjbMq9XB8wHfzLzGjspLhgi0uvoyFmX5pOtOqehmX7yExpDtzgFI72CT1wNAqfcWweIUiNaYgcKKfs6xVLykbN3feiP9wd7uod+oHIXlVqz9d1YeNTCj+JRNwAg==);whiteSpace=wrap;html=1;labelPosition=center;verticalLabelPosition=bottom;align=center;verticalAlign=top;rotation="+str(rotation)+";",
235
+ "vertex":"1",
236
+ "parent":"1"
237
+ }
238
+ cell = self.addCell(self.root,mxCell_dict)
239
+ mxGeometry_dict = {"x":str(x-60),
240
+ "y":str(y-10),
241
+ "width":"120",
242
+ "height":"20",
243
+ "as":"geometry"
244
+ }
245
+ geometry = self.addGeometry(cell,mxGeometry_dict)
246
+
247
+ return geometry
248
+
249
+ def addCapacitor(self,name,value,x,y,rotation=0):
250
+ mxCell_dict = {"id":name,
251
+ "value":"Name = "+name+"\nC = "+str(value),
252
+ "style":"shape=stencil(zVXRUsMgEPwaXh0C+tBHB+1/UHoapgkwBFv9+wJHtaYhajqOMplJbjfs3d6RCeFiaKUDwqiRPRD+QBgT0kmlg/XxORItorcUwwOGjJZYDg5UQHAvvZabDpAZgrc7OOhtKBLatOB1SCx/JPQ+vpMuLpQ1Jopoa4ZPzBkfxaQ2cS99RbGS/q1EN3cYu5ihhwAe8QbRD3MFIGz97UzNskxsOhMXEakZ5mIj1e7Z2xeznawPe6psl6ZD8Z6zEcbXec3782lWtR5OD3g0/5oyVlYzXLHFxZP1sNAvzWu+KifT4bsgTnRv91BrB5uVPil02pwprH4ikRtTKXCmn4usNc315t5PxL9zN2r79YP76rD/3eAWeBtL/Iq5vO3iY84o/mEycAQ=);whiteSpace=wrap;html=1;labelPosition=center;verticalLabelPosition=bottom;align=center;verticalAlign=top;rotation="+str(rotation)+";",
253
+ "vertex":"1",
254
+ "parent":"1"
255
+ }
256
+ cell = self.addCell(self.root,mxCell_dict)
257
+ mxGeometry_dict = {"x":str(x-60),
258
+ "y":str(y-18.75),
259
+ "width":"120",
260
+ "height":"37.5",
261
+ "as":"geometry"
262
+ }
263
+ geometry = self.addGeometry(cell,mxGeometry_dict)
264
+
265
+ return geometry
266
+
267
+ def addConnection(self,name,startID,startPnt,endID,endPnt,pntArray=0):
268
+ # Pnt = [X,Y] gibt die relative Position des Anschlusspunktes an
269
+ # X=0 => links
270
+ # X=1 => rechts
271
+ # Y=0 => oben
272
+ # Y=1 => unten
273
+ mxCell_dict = {"id":name,
274
+ "value":"",
275
+ "style":"endArrow=none;entryX="+str(endPnt[0])+";entryY="+str(endPnt[1])+";exitX="+str(startPnt[0])+";exitY="+str(startPnt[1])+";html=1;rounded=0;entryDx=0;entryDy=0;entryPerimeter=0;exitDx=0;exitDy=0;",
276
+ "edge":"1",
277
+ "parent":"1",
278
+ "source":startID,
279
+ "target":endID
280
+ }
281
+ cell = self.addCell(self.root,mxCell_dict)
282
+ mxGeometry_dict = {"width":"50",
283
+ "height":"50",
284
+ "relative":"1",
285
+ "as":"geometry"
286
+ }
287
+ geometry = self.addGeometry(cell,mxGeometry_dict)
288
+
289
+ if isinstance(pntArray, list):
290
+ self.addPntArray(geometry,pntArray)
291
+
292
+ return
293
+
294
+ def draw(netlist,file):
295
+ #Creates an xml file of the netlist that can be displayed with draw.io
296
+
297
+ #Parameter:
298
+ # netlist (list): List of branches
299
+ # file (str): Filename of diagram with extension (.xml)
300
+
301
+ # list of nodes
302
+ nodes = []
303
+ for branch in netlist['Branches']:
304
+ branch_nodes = branch['Nodes']
305
+ for nd in range(len(branch_nodes)):
306
+ node = branch_nodes[nd]
307
+ if not(node in nodes):
308
+ nodes.append(node)
309
+ nodes.sort()
310
+
311
+ #list of connections
312
+ connections = [None] * len(nodes)
313
+ for node in nodes:
314
+ connection = []
315
+ for branch in netlist['Branches']:
316
+ branch_nodes = branch['Nodes']
317
+ if node in branch_nodes:
318
+ for nd in branch_nodes:
319
+ if nd!=node and not(nd in connection):
320
+ connection.append(nd)
321
+ i = nodes.index(node)
322
+ connections[i]=connection
323
+
324
+ diagram = HeatSourceDiagram()
325
+
326
+ #determine nodes position
327
+ nd_pos = []
328
+ xstep = 250
329
+ ystep = 250
330
+ xpos = 0
331
+ ypos = ystep*len(connections)
332
+ nd_pos.append([xpos,ypos])
333
+ def_nd = [0]
334
+ ypos = ypos-ystep
335
+ for i in range(len(connections)):
336
+ #new nodes in connection
337
+ new_nd = []
338
+ for nd in connections[i]:
339
+ if nd>i and not(nd in new_nd) and not(nd in def_nd) :
340
+ new_nd.append(nd)
341
+ xpos = -xstep*(1+(len(new_nd)-1)/2)
342
+ draw = False
343
+ for nd in new_nd:
344
+ xpos = xpos+xstep
345
+ nd_pos.append([xpos,ypos])
346
+ def_nd.append(nd)
347
+ draw = True
348
+ #next level
349
+ if draw:
350
+ ypos = ypos-ystep
351
+
352
+
353
+ #draw nodes
354
+ for nd in def_nd:
355
+ i = def_nd.index(nd)
356
+ diagram.addNode(nd,nd_pos[i][0],nd_pos[i][1])
357
+
358
+ # draw branches
359
+ for branch in netlist['Branches']:
360
+ #print(branch)
361
+ xb = 0
362
+ yb = 0
363
+ for nd in branch['Nodes']:
364
+ i = def_nd.index(nd)
365
+ xb = xb+nd_pos[i][0]/len(branch['Nodes'])
366
+ yb = yb+nd_pos[i][1]/len(branch['Nodes'])
367
+ if len(branch['Nodes'])==2:
368
+ i1 = def_nd.index(branch['Nodes'][0])
369
+ i2 = def_nd.index(branch['Nodes'][1])
370
+ dx = nd_pos[i1][0]-nd_pos[i2][0]
371
+ dy = nd_pos[i1][1]-nd_pos[i2][1]
372
+ angle = np.arctan2(dy,dx)*180/np.pi
373
+ else:
374
+ angle = 0
375
+
376
+ if branch['Type']=='R_th':
377
+ diagram.addResistor(branch['Name'],branch['val'],xb,yb,angle)
378
+
379
+ if branch['Type']=='C_th':
380
+ diagram.addCapacitor(branch['Name'],branch['val'],xb,yb,angle)
381
+
382
+ if branch['Type']=='Power_Source':
383
+ diagram.addPowerSource(branch['Name'],branch['val'],xb,yb,angle)
384
+
385
+ if branch['Type']=='Temperature_Source':
386
+ diagram.addTemperatureSource(branch['Name'],branch['T_val'],branch['R_val'],xb,yb,angle)
387
+
388
+ # draw connections
389
+ num_con = 0
390
+ for branch in netlist['Branches']:
391
+ num_con = num_con+1
392
+ branch_id = branch['Name']
393
+ y_offset = 0.5
394
+ diagram.addConnection("connect"+str(num_con),\
395
+ "node_"+str(branch['Nodes'][0]),[0,0],branch_id,[1,y_offset])
396
+ num_con = num_con+1
397
+ diagram.addConnection("connect"+str(num_con),\
398
+ "node_"+str(branch['Nodes'][1]),[0,0],branch_id,[0,y_offset])
399
+
400
+
401
+ diagram.writeFile(file)
402
+
403
+ return