femagtools 1.7.9__py3-none-any.whl → 1.8.1__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.
Files changed (43) hide show
  1. femagtools/__init__.py +1 -1
  2. femagtools/amela.py +2 -2
  3. femagtools/dxfsl/area.py +130 -26
  4. femagtools/dxfsl/conv.py +2 -14
  5. femagtools/dxfsl/converter.py +69 -12
  6. femagtools/dxfsl/fslrenderer.py +15 -13
  7. femagtools/dxfsl/geom.py +153 -82
  8. femagtools/dxfsl/journal.py +2 -2
  9. femagtools/dxfsl/machine.py +19 -15
  10. femagtools/dxfsl/shape.py +3 -0
  11. femagtools/ecloss.py +386 -2
  12. femagtools/femag.py +82 -9
  13. femagtools/fsl.py +52 -56
  14. femagtools/machine/pm.py +1 -1
  15. femagtools/machine/sm.py +16 -8
  16. femagtools/mcv.py +128 -124
  17. femagtools/me.py +13 -13
  18. femagtools/model.py +8 -2
  19. femagtools/plot/fieldlines.py +1 -1
  20. femagtools/plot/mcv.py +18 -0
  21. femagtools/plot/wdg.py +2 -2
  22. femagtools/svgfsl/converter.py +1 -1
  23. femagtools/templates/afm_rotor.mako +4 -0
  24. femagtools/templates/gen_hairpin_winding.mako +36 -45
  25. femagtools/templates/magnetIron.mako +1 -1
  26. femagtools/templates/magnetIron2.mako +1 -1
  27. femagtools/templates/magnetIron3.mako +1 -1
  28. femagtools/templates/magnetIron4.mako +1 -1
  29. femagtools/templates/magnetIron5.mako +1 -1
  30. femagtools/templates/magnetIronV.mako +1 -1
  31. femagtools/templates/magnetSector.mako +1 -1
  32. femagtools/templates/mesh-airgap.mako +12 -6
  33. femagtools/templates/prepare_thermal.mako +199 -61
  34. femagtools/windings.py +25 -20
  35. {femagtools-1.7.9.dist-info → femagtools-1.8.1.dist-info}/METADATA +20 -20
  36. {femagtools-1.7.9.dist-info → femagtools-1.8.1.dist-info}/RECORD +42 -43
  37. {femagtools-1.7.9.dist-info → femagtools-1.8.1.dist-info}/WHEEL +1 -1
  38. tests/test_mcv.py +106 -1
  39. tests/test_windings.py +5 -0
  40. tests/test_mcvwriter.py +0 -96
  41. {femagtools-1.7.9.dist-info → femagtools-1.8.1.dist-info}/LICENSE +0 -0
  42. {femagtools-1.7.9.dist-info → femagtools-1.8.1.dist-info}/entry_points.txt +0 -0
  43. {femagtools-1.7.9.dist-info → femagtools-1.8.1.dist-info}/top_level.txt +0 -0
femagtools/__init__.py CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  """
4
4
  __title__ = 'femagtools'
5
- __version__ = '1.7.9'
5
+ __version__ = '1.8.1'
6
6
  __author__ = 'Ronald Tanner'
7
7
  __license__ = 'BSD'
8
8
  __copyright__ = 'Copyright 2023-2024 Gamma Technology'
femagtools/amela.py CHANGED
@@ -200,10 +200,10 @@ class Amela():
200
200
  for k, i in enumerate(mag_spels):
201
201
 
202
202
  cond = i.conduc
203
- if cond == 0:
203
+ if cond == 0:
204
204
  cond = 625000
205
205
  logger.info('Magnet conductivity equals 0, using 625000 S/m')
206
-
206
+
207
207
  mur = np.abs(1/i.elements[0].reluc[0])
208
208
  logger.debug('Magnet: mur=%s, conductivity=%s', mur, cond)
209
209
 
femagtools/dxfsl/area.py CHANGED
@@ -171,37 +171,141 @@ class Area(object):
171
171
  if e1.n1 == e2.n1 and e1.n2 == e2.n2:
172
172
  yield e1
173
173
 
174
- def reduce_nodes(self, mindist=0.01):
174
+ def reduce_line_nodes(self, geom, mindist=0.01):
175
175
  """reduces number of nodes (lines only)
176
176
  https://rdp.readthedocs.io/en/latest
177
+ Note: this feature is deactivated silently
178
+ if the rdp package is not installed.
177
179
  """
178
- import rdp
179
- def reduce_nodes_(lines, mindist):
180
- nodes = [le.get_nodes()[0] for le in lines]
181
- r = rdp.rdp(nodes,
182
- epsilon=mindist)
183
- if len(r) < len(nodes):
184
- return [Line(Element(start=n1, end=n2))
185
- for n1, n2 in zip(r, r[1:])]
186
- return lines
180
+ try:
181
+ import rdp
182
+ except ModuleNotFoundError:
183
+ return 0
184
+
185
+ def is_valid_line_(n1, e):
186
+ if not isinstance(e, Line):
187
+ return False
188
+ if e.has_attribute('del'):
189
+ return False
190
+ return True
187
191
 
192
+ def reduce_nodes_(lines, mindist):
193
+ if not len(lines) > 1:
194
+ return 0
195
+
196
+ nodes = [n1 for n1, n2, e in lines]
197
+ n1, n2, e = lines[-1]
198
+ nodes.append(n2)
199
+
200
+ remaining_nodes = rdp.rdp(nodes,
201
+ epsilon=mindist)
202
+ nodes_deleted = len(nodes) - len(remaining_nodes)
203
+ if not nodes_deleted:
204
+ return 0
205
+
206
+ for n1, n2, e in lines:
207
+ e.set_attribute('del')
208
+ e.set_my_color('yellow')
209
+ geom.remove_edge(e)
210
+
211
+ n1 = remaining_nodes[0]
212
+ for n2 in remaining_nodes[1:]:
213
+ geom.add_line(n1, n2)
214
+ n1 = n2
215
+
216
+ self.area = []
217
+ return nodes_deleted
218
+
219
+ # -----
220
+ nodes_deleted = 0
188
221
  lines = []
189
- reduced = []
190
- for e in self.area:
191
- if isinstance(e, Line):
192
- lines.append(e)
193
- else:
194
- reduced.append(e)
195
- reduced += reduce_nodes_(lines, mindist)
222
+ for n1, n2, e in self.list_of_elements():
223
+ if not is_valid_line_(n1, e):
224
+ nodes_deleted += reduce_nodes_(lines, mindist)
196
225
  lines = []
197
- if lines:
198
- reduced += reduce_nodes_(lines, mindist)
226
+ elif not geom.num_of_neighbors(n1) == 2:
227
+ nodes_deleted += reduce_nodes_(lines, mindist)
228
+ lines = [(n1, n2, e)]
229
+ else:
230
+ lines.append((n1, n2, e))
231
+
232
+ nodes_deleted += reduce_nodes_(lines, mindist)
233
+ return nodes_deleted
234
+
235
+ def reduce_element_nodes(self, geom, mindist=0.01):
236
+ """reduces number of nodes (lines only)
237
+ https://rdp.readthedocs.io/en/latest
238
+ Note: this feature is deactivated silently
239
+ if the rdp package is not installed.
240
+ """
241
+ corners = geom.start_corners + geom.end_corners
199
242
 
200
- if len(self.area) > len(reduced):
201
- logger.info("reduced areas %d -> %d",
202
- len(self.area), len(reduced))
203
- return reduced
204
- return []
243
+ try:
244
+ import rdp
245
+ except ModuleNotFoundError:
246
+ return 0
247
+
248
+ def is_valid_element_(n1, e):
249
+ return not e.has_attribute('del')
250
+
251
+ def reduce_nodes_(elements, mindist):
252
+ if not len(elements) > 1:
253
+ return 0
254
+
255
+ old_nodes = []
256
+ for n1, n2, e in elements:
257
+ old_nodes.append(n1)
258
+ nodes = [n for n in e.get_nodes(parts=24)]
259
+ if len(nodes) > 2:
260
+ if points_are_close(n1, nodes[0]):
261
+ old_nodes += nodes[1:-1]
262
+ elif points_are_close(n2, nodes[0]):
263
+ nodes.reverse()
264
+ old_nodes += nodes[1:-1]
265
+ n1, n2, e = elmts[-1]
266
+ old_nodes.append(n2)
267
+
268
+ new_nodes = rdp.rdp(old_nodes,
269
+ epsilon=mindist)
270
+ nodes_deleted = len(old_nodes) - len(new_nodes)
271
+ if not nodes_deleted:
272
+ return 0
273
+
274
+ for n1, n2, e in elements:
275
+ e.set_attribute('del')
276
+ e.set_my_color('yellow')
277
+ geom.remove_edge(e)
278
+
279
+ n1 = new_nodes[0]
280
+ for n2 in new_nodes[1:]:
281
+ geom.add_line(n1, n2)
282
+ n1 = n2
283
+
284
+ self.area = []
285
+ return nodes_deleted
286
+
287
+ # -----
288
+ nodes_deleted = 0
289
+ tiny_mindist = 0.05
290
+ elmts = []
291
+ has_tiny = False
292
+ for n1, n2, e in self.list_of_elements():
293
+ if not is_valid_element_(n1, e):
294
+ if has_tiny:
295
+ nodes_deleted += reduce_nodes_(elmts, mindist)
296
+ elmts = []
297
+ elif not geom.num_of_neighbors(n1) == 2 or n1 in corners:
298
+ if has_tiny:
299
+ nodes_deleted += reduce_nodes_(elmts, mindist)
300
+ has_tiny = e.is_tiny(tiny_mindist)
301
+ elmts = [(n1, n2, e)]
302
+ else:
303
+ if e.is_tiny(tiny_mindist):
304
+ has_tiny = True
305
+ elmts.append((n1, n2, e))
306
+ if has_tiny:
307
+ nodes_deleted += reduce_nodes_(elmts, mindist)
308
+ return nodes_deleted
205
309
 
206
310
  def virtual_nodes(self, render=False, parts=64):
207
311
  if len(self.area) < 2:
@@ -718,9 +822,9 @@ class Area(object):
718
822
  mm[3] = max(mm[3], n[3])
719
823
  return mm
720
824
 
721
- def intersect_line(self, line):
825
+ def intersect_area(self, line):
722
826
  for e in self.area:
723
- if e.intersect_line(line):
827
+ if e.intersect_shape(line, include_end=True):
724
828
  return True
725
829
  return False
726
830
 
femagtools/dxfsl/conv.py CHANGED
@@ -9,18 +9,13 @@ import os
9
9
  import io
10
10
  import femagtools
11
11
  from femagtools.dxfsl.converter import convert
12
- from femagtools.dxfsl.journal import Journal, getJournal
13
12
  import argparse
14
13
  import logging
15
14
  import logging.config
16
15
 
17
16
  logger = logging.getLogger(__name__)
18
- journal = None
19
-
20
17
 
21
18
  def main():
22
- global journal
23
-
24
19
  argparser = argparse.ArgumentParser(
25
20
  description='Process DXF file and create a plot or FSL file.')
26
21
  argparser.add_argument('dxfile',
@@ -187,8 +182,6 @@ def main():
187
182
  logger.info("Python: %s", sys.version)
188
183
  sys.exit(0)
189
184
 
190
- journal = getJournal(name='converter_journal', aktiv=args.journal)
191
-
192
185
  if args.airgap > 0.0:
193
186
  if args.airgap2 > 0.0:
194
187
  logger.info("Airgap is set from {} to {}"
@@ -252,16 +245,11 @@ def main():
252
245
  write_png=args.write_png,
253
246
  write_id=args.write_id,
254
247
  debug_mode=args.debugger,
255
- full_model=args.full_model)
248
+ full_model=args.full_model,
249
+ write_journal=args.journal)
256
250
  keys = ('tot_num_slot', 'num_sl_gen', 'num_poles', 'nodedist',
257
251
  'dy1', 'da1', 'da2', 'dy2', 'agndst', 'name')
258
252
  logger.info("%s", {k: res[k] for k in keys if k in res})
259
- if args.write_fsl:
260
- if res is not None:
261
- basename = os.path.basename(args.dxfile).split('.')[0]
262
- with io.open(basename + '.fsl', 'w', encoding='utf-8') as f:
263
- f.write('\n'.join(res['fsl']))
264
- journal.write_journal()
265
253
 
266
254
  if __name__ == "__main__":
267
255
  loglevel = logging.INFO
@@ -2,7 +2,9 @@
2
2
 
3
3
  """
4
4
  import os
5
+ import io
5
6
  from pathlib import Path
7
+ from femagtools import __version__
6
8
  from femagtools.dxfsl.geom import Geometry
7
9
  from femagtools.dxfsl.shape import Shape
8
10
  from femagtools.dxfsl.fslrenderer import FslRenderer, agndst
@@ -159,12 +161,16 @@ def symmetry_search(machine,
159
161
 
160
162
 
161
163
  def build_machine_rotor(machine, inner, mindist, plt, EESM=False, single=False):
164
+ global journal
162
165
  logger.debug("Begin of build_machine_rotor")
166
+
163
167
  if machine.has_windings():
164
168
  logger.debug("do nothing here with windings in rotor")
165
169
  logger.debug("End of build_machine_rotor")
166
170
  return machine
167
171
 
172
+ timer = Timer(start_it=True)
173
+
168
174
  if machine.is_mirrored():
169
175
  logger.debug("Rotor is mirrored")
170
176
  machine_temp = machine.undo_mirror()
@@ -206,41 +212,77 @@ def build_machine_rotor(machine, inner, mindist, plt, EESM=False, single=False):
206
212
  machine_temp.create_inner_corner_areas()
207
213
 
208
214
  if not machine_temp.is_mirrored():
209
- machine_temp.create_boundery_nodes()
215
+ plot_geom(False, # for developer
216
+ plt, machine_temp.geom,
217
+ title="Rotor before Boundery Corr")
218
+ machine_temp.create_boundary_nodes()
210
219
 
211
220
  plot_geom(False, # for developer
212
221
  plt, machine_temp.geom,
213
222
  title="Final Rotor")
223
+
224
+ t = timer.stop("-- rotor created in %0.4f seconds --")
225
+ journal.put('time_rotor_created', t)
226
+
214
227
  logger.debug("End of build_machine_rotor")
215
228
  return machine_temp
216
229
 
217
230
 
218
231
  def build_machine_stator(machine, inner, mindist, plt, EESM=False, single=False):
232
+ global journal
219
233
  logger.debug("Begin of build_machine_stator")
234
+ timer = Timer(start_it=True)
235
+
220
236
  if not machine.geom.is_stator():
221
237
  logger.debug("Rotor with windings")
222
238
 
223
- if machine.has_mirrored_windings():
239
+ if machine.is_mirrored():
240
+ plot_geom(False, # for developer
241
+ plt, machine.previous_machine.geom,
242
+ title="Mirrored Stator",
243
+ areas=True)
244
+
224
245
  logger.debug("undo mirrored windings")
225
246
  machine_temp = machine.undo_mirror()
226
247
  machine_temp.delete_tiny_elements(mindist)
227
248
  machine_temp.geom.set_stator()
228
249
  machine_temp.search_stator_subregions(single=single)
229
- machine_temp.create_mirror_lines_outside_windings()
250
+ if not machine_temp.has_windings_in_the_middle():
251
+ logger.debug("Back to the mirrored machine")
252
+ machine_temp = machine # undo
253
+ else:
254
+ machine_temp.create_mirror_lines_outside_windings()
230
255
  else:
231
256
  machine_temp = machine
257
+
258
+ if machine_temp.geom.reduce_element_nodes(mindist):
259
+ machine_temp.rebuild_subregions(EESM, single=single)
260
+ plot_geom(False, # for developer
261
+ plt, machine_temp.geom,
262
+ title="Nodes reduced")
263
+
232
264
  if machine_temp.create_auxiliary_lines():
233
265
  machine_temp.rebuild_subregions(EESM, single=single)
266
+ plot_geom(False, # for developer
267
+ plt, machine_temp.geom,
268
+ title="Stator with Auxiliary Lines")
234
269
 
235
270
  if inner:
236
271
  machine_temp.create_inner_corner_areas()
237
272
 
238
273
  if not machine_temp.is_mirrored():
239
- machine_temp.create_boundery_nodes()
274
+ plot_geom(False, # for developer
275
+ plt, machine_temp.geom,
276
+ title="Stator before Boundery Corr")
277
+ machine_temp.create_boundary_nodes()
240
278
 
241
279
  plot_geom(False, # for developer
242
280
  plt, machine_temp.geom,
243
281
  title="Final Stator")
282
+
283
+ t = timer.stop("-- stator created in %0.4f seconds --")
284
+ journal.put('time_stator_created', t)
285
+
244
286
  logger.debug("End of build_machine_stator")
245
287
  return machine_temp
246
288
 
@@ -270,7 +312,9 @@ def convert(dxfile,
270
312
  write_png=False,
271
313
  write_id=False,
272
314
  full_model=False,
273
- debug_mode=False):
315
+ debug_mode=False,
316
+ write_journal=False):
317
+ global journal
274
318
  layers = ()
275
319
  conv = {}
276
320
 
@@ -281,14 +325,19 @@ def convert(dxfile,
281
325
 
282
326
  basename = input_file.stem
283
327
  if part:
284
- logger.info("***** start processing %s (%s) *****", basename, part)
328
+ logger.info("***** start processing %s (%s) [%s] *****",
329
+ basename,
330
+ part,
331
+ __version__)
285
332
  else:
286
- logger.info("***** start processing %s *****", basename)
333
+ logger.info("***** start processing %s [%s] *****",
334
+ basename,
335
+ __version__)
287
336
  timer = Timer(start_it=True)
288
337
 
289
- journal = getJournal(name='converter', aktiv=debug_mode)
338
+ journal = getJournal(name='converter_journal', aktiv=write_journal)
290
339
  journal.get_journal(input_file.name)
291
- journal.put_filename(str(input_file.resolve()))
340
+ journal.set_filename(str(input_file.resolve()))
292
341
  journal.set('success', False)
293
342
  journal.write_journal()
294
343
 
@@ -533,7 +582,6 @@ def convert(dxfile,
533
582
  EESM=EESM)
534
583
 
535
584
  if machine_outer.geom.is_stator() or machine_outer.has_windings():
536
- machine_outer.geom.reduce_winding_nodes()
537
585
  machine_outer = build_machine_stator(machine_outer,
538
586
  False,
539
587
  mindist,
@@ -719,7 +767,6 @@ def convert(dxfile,
719
767
  p,
720
768
  EESM=EESM,
721
769
  single=True)
722
- machine.geom.reduce_winding_nodes()
723
770
  params = create_femag_parameters_stator(machine,
724
771
  part[1])
725
772
  else:
@@ -794,14 +841,24 @@ def convert(dxfile,
794
841
 
795
842
  mtype = 'EESM' if EESM else 'PMSM'
796
843
  fslrenderer = FslRenderer(basename, mtype)
797
- conv['fsl'] = fslrenderer.render(machine, inner, outer)
844
+ conv['fsl'] = fslrenderer.render(machine, inner, outer, standalone=True)
798
845
 
799
846
  if params is not None:
800
847
  conv.update(params)
848
+
849
+ if write_fsl:
850
+ logger.debug("Write fsl")
851
+ if conv and conv['fsl']:
852
+ with io.open(basename + '.fsl', 'w', encoding='utf-8') as f:
853
+ f.write('\n'.join(conv['fsl']))
854
+ else:
855
+ logger.warning("No fsl data available")
856
+
801
857
  conv['name'] = basename
802
858
  t = timer.stop("-- all done in %0.4f seconds --", info=True)
803
859
  journal.put('time_total', t)
804
860
  journal.set('success', True)
861
+ journal.write_journal()
805
862
  return conv
806
863
 
807
864
 
@@ -154,7 +154,7 @@ class FslRenderer(object):
154
154
  return sorted([(abs(r - np.linalg.norm(e.center_of_connection())), e)
155
155
  for e in geom.elements(Shape)])
156
156
 
157
- def render(self, machine, inner=False, outer=False):
157
+ def render(self, machine, inner=False, outer=False, standalone=False):
158
158
  '''create fsl statements with nodechains'''
159
159
  machine.set_alfa_and_corners()
160
160
  geom = machine.geom
@@ -162,20 +162,22 @@ class FslRenderer(object):
162
162
  geom.split_all_lines_longer_than(split_len)
163
163
  self.content = []
164
164
 
165
+ if standalone:
166
+ self.content += ['if (agndst == nil) then',
167
+ ' agndst = 0.5',
168
+ ' m.npols_gen = 2',
169
+ ' m.num_sl_gen = 2',
170
+ ' new_model_force("{}","Test")'.format(self.model),
171
+ 'end']
172
+
165
173
  MAXDST=4.0
166
174
  NUMLEVELS=10
167
- ndt_list = [(0.00, 1.1),
168
- (0.05, 1.5),
169
- (0.10, 1.7),
170
- (0.15, 2.0),
171
- (0.20, 2.3),
172
- (0.30, 2.7),
173
- (0.40, 3.1),
174
- (0.50, 3.5),
175
- (0.70, 4.5),
176
- (0.85, 5.5),
177
- (1.10, 5.5)]
178
- ndt_list = [(1.1*nl/NUMLEVELS, nl/NUMLEVELS*(MAXDST-1.1)+1.1)
175
+ NDT0=1.1
176
+ # ndt list format [ (rdistx, ndtx) ...]
177
+ # where
178
+ # - rdistx is rel dist from airgap (range 0 .. NUMLEVELS-1/NUMLEVELS)
179
+ # - ndtx nodedist (range NDT0 .. (NUMLEVELS-1/NUMLEVELS))*(MAXDST-1.1) + NDT0)
180
+ ndt_list = [(1.1*nl/NUMLEVELS, nl/NUMLEVELS*(MAXDST-1.1)+NDT0)
179
181
  for nl in range(NUMLEVELS+1)]
180
182
 
181
183
  dist = geom.max_radius - geom.min_radius