femagtools 1.7.9__py3-none-any.whl → 1.8.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.
Files changed (42) hide show
  1. femagtools/__init__.py +1 -1
  2. femagtools/amela.py +2 -2
  3. femagtools/dxfsl/area.py +129 -25
  4. femagtools/dxfsl/conv.py +2 -9
  5. femagtools/dxfsl/converter.py +33 -10
  6. femagtools/dxfsl/fslrenderer.py +6 -12
  7. femagtools/dxfsl/geom.py +31 -9
  8. femagtools/dxfsl/journal.py +2 -2
  9. femagtools/dxfsl/machine.py +14 -13
  10. femagtools/dxfsl/shape.py +3 -0
  11. femagtools/ecloss.py +381 -2
  12. femagtools/femag.py +41 -0
  13. femagtools/fsl.py +31 -50
  14. femagtools/machine/pm.py +1 -1
  15. femagtools/machine/sm.py +14 -0
  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/gen_hairpin_winding.mako +36 -45
  24. femagtools/templates/magnetIron.mako +1 -1
  25. femagtools/templates/magnetIron2.mako +1 -1
  26. femagtools/templates/magnetIron3.mako +1 -1
  27. femagtools/templates/magnetIron4.mako +1 -1
  28. femagtools/templates/magnetIron5.mako +1 -1
  29. femagtools/templates/magnetIronV.mako +1 -1
  30. femagtools/templates/magnetSector.mako +1 -1
  31. femagtools/templates/mesh-airgap.mako +12 -6
  32. femagtools/templates/prepare_thermal.mako +148 -13
  33. femagtools/windings.py +25 -20
  34. {femagtools-1.7.9.dist-info → femagtools-1.8.0.dist-info}/METADATA +20 -20
  35. {femagtools-1.7.9.dist-info → femagtools-1.8.0.dist-info}/RECORD +41 -42
  36. {femagtools-1.7.9.dist-info → femagtools-1.8.0.dist-info}/WHEEL +1 -1
  37. tests/test_mcv.py +106 -1
  38. tests/test_windings.py +5 -0
  39. tests/test_mcvwriter.py +0 -96
  40. {femagtools-1.7.9.dist-info → femagtools-1.8.0.dist-info}/LICENSE +0 -0
  41. {femagtools-1.7.9.dist-info → femagtools-1.8.0.dist-info}/entry_points.txt +0 -0
  42. {femagtools-1.7.9.dist-info → femagtools-1.8.0.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.0'
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)
199
-
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 []
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
242
+
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:
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,7 +245,8 @@ 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})
@@ -261,7 +255,6 @@ def main():
261
255
  basename = os.path.basename(args.dxfile).split('.')[0]
262
256
  with io.open(basename + '.fsl', 'w', encoding='utf-8') as f:
263
257
  f.write('\n'.join(res['fsl']))
264
- journal.write_journal()
265
258
 
266
259
  if __name__ == "__main__":
267
260
  loglevel = logging.INFO
@@ -3,6 +3,7 @@
3
3
  """
4
4
  import os
5
5
  from pathlib import Path
6
+ from femagtools import __version__
6
7
  from femagtools.dxfsl.geom import Geometry
7
8
  from femagtools.dxfsl.shape import Shape
8
9
  from femagtools.dxfsl.fslrenderer import FslRenderer, agndst
@@ -220,16 +221,33 @@ def build_machine_stator(machine, inner, mindist, plt, EESM=False, single=False)
220
221
  if not machine.geom.is_stator():
221
222
  logger.debug("Rotor with windings")
222
223
 
223
- if machine.has_mirrored_windings():
224
+ if machine.is_mirrored():
225
+ plot_geom(False, # for developer
226
+ plt, machine.previous_machine.geom,
227
+ title="Mirrored Stator",
228
+ areas=True)
229
+
224
230
  logger.debug("undo mirrored windings")
225
231
  machine_temp = machine.undo_mirror()
226
232
  machine_temp.delete_tiny_elements(mindist)
227
233
  machine_temp.geom.set_stator()
228
234
  machine_temp.search_stator_subregions(single=single)
229
- machine_temp.create_mirror_lines_outside_windings()
235
+ if not machine_temp.has_windings_in_the_middle():
236
+ logger.debug("Back to the mirrored machine")
237
+ machine_temp = machine # undo
238
+ else:
239
+ machine_temp.create_mirror_lines_outside_windings()
230
240
  else:
231
241
  machine_temp = machine
232
- if machine_temp.create_auxiliary_lines():
242
+
243
+ rebuild = machine_temp.create_auxiliary_lines()
244
+ if machine_temp.geom.reduce_element_nodes(mindist):
245
+ plot_geom(False, # for developer
246
+ plt, machine_temp.geom,
247
+ title="Nodes reduced",
248
+ areas=False)
249
+ rebuild = True
250
+ if rebuild:
233
251
  machine_temp.rebuild_subregions(EESM, single=single)
234
252
 
235
253
  if inner:
@@ -270,7 +288,8 @@ def convert(dxfile,
270
288
  write_png=False,
271
289
  write_id=False,
272
290
  full_model=False,
273
- debug_mode=False):
291
+ debug_mode=False,
292
+ write_journal=False):
274
293
  layers = ()
275
294
  conv = {}
276
295
 
@@ -281,14 +300,19 @@ def convert(dxfile,
281
300
 
282
301
  basename = input_file.stem
283
302
  if part:
284
- logger.info("***** start processing %s (%s) *****", basename, part)
303
+ logger.info("***** start processing %s (%s) [%s] *****",
304
+ basename,
305
+ part,
306
+ __version__)
285
307
  else:
286
- logger.info("***** start processing %s *****", basename)
308
+ logger.info("***** start processing %s [%s] *****",
309
+ basename,
310
+ __version__)
287
311
  timer = Timer(start_it=True)
288
312
 
289
- journal = getJournal(name='converter', aktiv=debug_mode)
313
+ journal = getJournal(name='converter_journal', aktiv=write_journal)
290
314
  journal.get_journal(input_file.name)
291
- journal.put_filename(str(input_file.resolve()))
315
+ journal.set_filename(str(input_file.resolve()))
292
316
  journal.set('success', False)
293
317
  journal.write_journal()
294
318
 
@@ -533,7 +557,6 @@ def convert(dxfile,
533
557
  EESM=EESM)
534
558
 
535
559
  if machine_outer.geom.is_stator() or machine_outer.has_windings():
536
- machine_outer.geom.reduce_winding_nodes()
537
560
  machine_outer = build_machine_stator(machine_outer,
538
561
  False,
539
562
  mindist,
@@ -719,7 +742,6 @@ def convert(dxfile,
719
742
  p,
720
743
  EESM=EESM,
721
744
  single=True)
722
- machine.geom.reduce_winding_nodes()
723
745
  params = create_femag_parameters_stator(machine,
724
746
  part[1])
725
747
  else:
@@ -802,6 +824,7 @@ def convert(dxfile,
802
824
  t = timer.stop("-- all done in %0.4f seconds --", info=True)
803
825
  journal.put('time_total', t)
804
826
  journal.set('success', True)
827
+ journal.write_journal()
805
828
  return conv
806
829
 
807
830
 
@@ -164,18 +164,12 @@ class FslRenderer(object):
164
164
 
165
165
  MAXDST=4.0
166
166
  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)
167
+ NDT0=1.1
168
+ # ndt list format [ (rdistx, ndtx) ...]
169
+ # where
170
+ # - rdistx is rel dist from airgap (range 0 .. NUMLEVELS-1/NUMLEVELS)
171
+ # - ndtx nodedist (range NDT0 .. (NUMLEVELS-1/NUMLEVELS))*(MAXDST-1.1) + NDT0)
172
+ ndt_list = [(1.1*nl/NUMLEVELS, nl/NUMLEVELS*(MAXDST-1.1)+NDT0)
179
173
  for nl in range(NUMLEVELS+1)]
180
174
 
181
175
  dist = geom.max_radius - geom.min_radius
femagtools/dxfsl/geom.py CHANGED
@@ -811,6 +811,10 @@ class Geometry(object):
811
811
  def get_neighbors(self, n):
812
812
  return [nbr for nbr in self.g.neighbors(n)]
813
813
 
814
+ def num_of_neighbors(self, n):
815
+ nbrs = [nbr for nbr in self.g.neighbors(n)]
816
+ return len(nbrs)
817
+
814
818
  def angle_nodes(self, center, angle, rtol, atol):
815
819
  if np.isclose(abs(angle), np.pi, rtol, atol):
816
820
  angle_func = positive_angle
@@ -868,8 +872,8 @@ class Geometry(object):
868
872
  logger.debug("begin of dist_end_max_corner")
869
873
  logger.debug("end corners: %s", self.end_corners)
870
874
 
871
- #if self.is_mirrored():
872
- # return self.dist_start_max_corner()
875
+ if self.is_mirrored():
876
+ return self.dist_start_max_corner()
873
877
  d = distance(self.center, self.end_corners[-1])
874
878
  logger.debug("end of dist_end_max_corner: %s", d)
875
879
  return d
@@ -1845,14 +1849,22 @@ class Geometry(object):
1845
1849
  return []
1846
1850
 
1847
1851
  def reduce_winding_nodes(self, mindist=0.01):
1852
+ return self.reduce_element_nodes(mindist=mindist,
1853
+ area_types=(AREA.TYPE_WINDINGS,))
1854
+
1855
+ def reduce_element_nodes(self, mindist=0.01, area_types=()):
1856
+ timer = Timer(start_it=True)
1857
+ nodes_deleted = 0
1848
1858
  for area in self.list_of_areas():
1849
- if area.is_winding():
1850
- reduced_shapes = area.reduce_nodes(mindist)
1851
- if reduced_shapes:
1852
- area.remove_edges(self.g, ndec)
1853
- area.area = reduced_shapes
1854
- for r in reduced_shapes:
1855
- self.add_element(r, rtol=self.rtol, atol=self.atol)
1859
+ if not area_types or area.type in area_types:
1860
+ nodes_deleted += area.reduce_element_nodes(self, mindist)
1861
+
1862
+ t = timer.stop("-- {} nodes deleted in %0.4f seconds --".format(nodes_deleted))
1863
+ self.journal.put('time_deleting_nodes', t)
1864
+ if nodes_deleted:
1865
+ self.journal.put('nodes_deleted', nodes_deleted)
1866
+ self.area_list = []
1867
+ return nodes_deleted > 0
1856
1868
 
1857
1869
  def render_areagroups(self, renderer):
1858
1870
  if not self.areagroup_list:
@@ -3598,6 +3610,16 @@ class Geometry(object):
3598
3610
  return True
3599
3611
  return False
3600
3612
 
3613
+ def windings_in_the_middle(self, midangle):
3614
+ wdg_areas = [a for a in self.list_of_areas()
3615
+ if a.is_winding()]
3616
+ logger.info("%s windings in geom", len(wdg_areas))
3617
+ for a in wdg_areas:
3618
+ if greater(a.max_angle, midangle) and \
3619
+ less(a.min_angle, midangle):
3620
+ return True
3621
+ return False
3622
+
3601
3623
  def looking_for_corners(self):
3602
3624
  if self.is_inner:
3603
3625
  logger.debug("looking_for_corners: inner")
@@ -121,8 +121,8 @@ class Journal(object):
121
121
  data_list.append(val)
122
122
  self.data[name] = data_list
123
123
 
124
- def put_filename(self, val):
125
- self.put('filename', val)
124
+ def set_filename(self, val):
125
+ self.set('filename', val)
126
126
 
127
127
  def put_areas(self, val):
128
128
  self.put('areas', val)
@@ -5,14 +5,15 @@
5
5
  from __future__ import print_function
6
6
  import numpy as np
7
7
  import logging
8
- from .shape import Element, Circle, Arc, Line, Shape
9
- from .corner import Corner
8
+ from femagtools.dxfsl.shape import Element, Circle, Arc, Line, Shape
9
+ from femagtools.dxfsl.corner import Corner
10
10
  from femagtools.dxfsl.symmetry import Symmetry
11
- from .functions import point, points_are_close, distance
12
- from .functions import alpha_angle, normalise_angle, middle_angle, third_angle
13
- from .functions import alpha_line, line_m, line_n, mirror_point
14
- from .functions import within_interval, part_of_circle
15
- from .functions import less, less_equal, greater, greater_equal
11
+ import femagtools.dxfsl.area as AREA
12
+ from femagtools.dxfsl.functions import point, points_are_close, distance
13
+ from femagtools.dxfsl.functions import alpha_angle, normalise_angle, middle_angle, third_angle
14
+ from femagtools.dxfsl.functions import alpha_line, line_m, line_n, mirror_point
15
+ from femagtools.dxfsl.functions import within_interval, part_of_circle
16
+ from femagtools.dxfsl.functions import less, less_equal, greater, greater_equal
16
17
  logger = logging.getLogger('femagtools.geom')
17
18
 
18
19
 
@@ -283,11 +284,6 @@ class Machine(object):
283
284
  logger.debug("end of copy_mirror")
284
285
  return machine
285
286
 
286
- def has_mirrored_windings(self):
287
- if not self.is_mirrored():
288
- return False
289
- return self.geom.area_close_to_endangle(2) > 0
290
-
291
287
  def undo_mirror(self):
292
288
  assert(self.is_mirrored())
293
289
  assert(self.previous_machine)
@@ -533,7 +529,7 @@ class Machine(object):
533
529
  def repair_hull_geom(self, geom, startangle, endangle):
534
530
  logger.debug('begin repair_hull_geom (%s, %s)', startangle, endangle)
535
531
 
536
- rtol = 1e-4
532
+ rtol = 1e-3
537
533
  atol = 1e-4
538
534
  c_corner = Corner(self.center, self.center)
539
535
  start_c_added, start_corners = geom.get_corner_list(self.center, startangle,
@@ -1138,6 +1134,11 @@ class Machine(object):
1138
1134
 
1139
1135
  return 0.0
1140
1136
 
1137
+ def has_windings_in_the_middle(self):
1138
+ midangle = middle_angle(self.startangle,
1139
+ self.endangle)
1140
+ return self.geom.windings_in_the_middle(midangle)
1141
+
1141
1142
  def create_mirror_lines_outside_windings(self):
1142
1143
  logger.debug("create_mirror_lines_outside_windings")
1143
1144
 
femagtools/dxfsl/shape.py CHANGED
@@ -54,6 +54,9 @@ class Shape(object):
54
54
  def classname(self):
55
55
  return "Shape"
56
56
 
57
+ def set_my_color(self, color):
58
+ self.my_color = color
59
+
57
60
  def get_my_color(self):
58
61
  if hasattr(self, 'my_color'):
59
62
  return self.my_color