femagtools 1.6.4__py3-none-any.whl → 1.6.6__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/dxfsl/geom.py CHANGED
@@ -19,6 +19,7 @@ from .area import Area
19
19
  from .shape import Element, Shape, Circle, Arc, Line, Point
20
20
  from .shape import is_Circle, is_Arc, is_Line
21
21
  from .machine import Machine
22
+ from femagtools.dxfsl.areabuilder import AreaBuilder
22
23
  from .functions import less_equal, less, greater, greater_equal
23
24
  from .functions import distance, alpha_line, alpha_points, alpha_angle
24
25
  from .functions import point, points_are_close, is_point_inside_region
@@ -251,432 +252,6 @@ def intersect(a, b):
251
252
  ccw(b.p1, b.p2, a.p1) != ccw(b.p1, b.p2, a.p2)
252
253
 
253
254
 
254
- def polylines(entity, lf, rf, xoff=0.0, yoff=0.0, rotation=0.0):
255
- """returns a collection of bulged vertices
256
- http://www.afralisp.net/archive/lisp/Bulges1.htm
257
- """
258
- if isinstance(entity.points, list):
259
- points = [(p[0], p[1]) for p in entity.points]
260
- else:
261
- points = [(p[0], p[1]) for p in entity.points()]
262
- i = 0
263
- if isinstance(entity.vertices, list):
264
- vertices = entity.vertices
265
- else:
266
- vertices = entity.vertices()
267
-
268
- for v in vertices:
269
- if hasattr(v, 'bulge'):
270
- b = v.bulge
271
- else:
272
- b = v.get_dxf_attrib('bulge', 0.0)
273
- p1 = points[i]
274
- try:
275
- p2 = points[i+1]
276
- except Exception:
277
- if not entity.is_closed:
278
- break
279
- p2 = points[0]
280
- if b != 0.0:
281
- dx, dy = p2[0] - p1[0], p2[1] - p1[1]
282
- c = np.sqrt(dx**2 + dy**2)
283
- s = b * c/2
284
- r = ((c/2)**2 + s**2)/2/s
285
- g = np.arctan2(dx, dy)
286
- a = 2*np.arctan(b)
287
- pc = (p1[0] + r*np.sin(g - np.pi/2 + a),
288
- p1[1] + r*np.cos(g - np.pi/2 + a))
289
- if b < 0:
290
- sa = np.arctan2(p2[1]-pc[1], p2[0]-pc[0])
291
- ea = np.arctan2(p1[1]-pc[1], p1[0]-pc[0])
292
- else:
293
- sa = np.arctan2(p1[1]-pc[1], p1[0]-pc[0])
294
- ea = np.arctan2(p2[1]-pc[1], p2[0]-pc[0])
295
- logger.debug("Poly p1 %s p2 %s r %s", p1, p2, r)
296
- yield Arc(Element(center=(pc[0], pc[1]),
297
- radius=np.abs(r),
298
- start_angle=sa/rf,
299
- end_angle=ea/rf),
300
- lf, rf,
301
- xoff=xoff, yoff=yoff,
302
- rotation=rotation)
303
- else:
304
- yield Line(Element(start=p1, end=p2), lf,
305
- xoff=xoff, yoff=yoff,
306
- rotation=rotation)
307
- i += 1
308
-
309
-
310
- def lw_polyline(entity, lf, xoff=0.0, yoff=0.0, rotation=0.0):
311
- """returns a collection of bulged vertices
312
- http://www.afralisp.net/archive/lisp/Bulges1.htm
313
- """
314
- if isinstance(entity.points, list):
315
- points = [(lf*p[0], lf*p[1]) for p in entity.points]
316
- else:
317
- points = [(lf*p[0], lf*p[1]) for p in entity.points()]
318
-
319
- if points:
320
- p1 = points[0]
321
- for p2 in points[1:]:
322
- yield Line(Element(start=p1, end=p2), lf,
323
- xoff=xoff, yoff=yoff,
324
- rotation=rotation)
325
- p1 = p2
326
- if entity.is_closed:
327
- yield Line(Element(start=p1, end=points[0]), lf,
328
- xoff=xoff, yoff=yoff,
329
- rotation=rotation)
330
-
331
-
332
- def ellipse(entity, lf, xoff=0.0, yoff=0.0, rotation=0.0):
333
- w = np.linalg.norm(entity.major_axis) * 2
334
- h = entity.ratio * w
335
- theta = np.arctan2(entity.major_axis[1], entity.major_axis[0])
336
- start_angle = entity.start_param
337
- end_angle = entity.end_param
338
- if end_angle < start_angle:
339
- end_angle += 2*np.pi
340
- alfa = np.linspace(start_angle, end_angle, 20)
341
- x = 0.5 * w * np.cos(alfa)
342
- y = 0.5 * h * np.sin(alfa)
343
- R = np.array([
344
- [np.cos(theta), -np.sin(theta)],
345
- [np.sin(theta), np.cos(theta)]
346
- ])
347
- x, y = np.dot(R, [x, y])
348
- x += entity.center[0]
349
- y += entity.center[1]
350
- points = np.array((x, y)).T
351
- p1 = points[0]
352
- for p2 in points[1:]:
353
- yield Line(Element(start=p1, end=p2), lf,
354
- xoff=xoff, yoff=yoff,
355
- rotation=rotation)
356
- p1 = p2
357
-
358
-
359
- def spline(entity, lf, min_dist=0.001, xoff=0.0, yoff=0.0, rotation=0.0):
360
- if False:
361
- yield Line(Element(start=entity.control_points[0],
362
- end=entity.control_points[-1]), lf,
363
- xoff=xoff, yoff=yoff,
364
- rotation=rotation)
365
- return
366
-
367
- if False:
368
- p_prev = None
369
- for p in entity.control_points:
370
- if p_prev:
371
- yield Line(Element(start=p_prev, end=p), lf,
372
- xoff=xoff, yoff=yoff,
373
- rotation=rotation)
374
- p_prev = p
375
- return
376
-
377
- points_between = entity.control_points[1:-1]
378
- p1 = entity.control_points[0]
379
- pe = entity.control_points[-1]
380
- for p2 in points_between:
381
- dist_12 = distance(p1, p2)
382
- dist_2e = distance(p2, pe)
383
- if dist_2e < min_dist:
384
- logger.debug("SPLINE: ignor small end-distance %s", dist_2e)
385
- yield Line(Element(start=p1, end=pe), lf,
386
- xoff=xoff, yoff=yoff,
387
- rotation=rotation)
388
- return
389
-
390
- if dist_12 > min_dist:
391
- yield Line(Element(start=p1, end=p2), lf,
392
- xoff=xoff, yoff=yoff,
393
- rotation=rotation)
394
- p1 = p2
395
- else:
396
- logger.debug("SPLINE: ignor small distance %s", dist_12)
397
-
398
- yield Line(Element(start=p1, end=pe), lf,
399
- xoff=xoff, yoff=yoff,
400
- rotation=rotation)
401
-
402
-
403
- def face3d(entity, lf):
404
- logger.info("FACE3D: Points=%s", entity.points)
405
- for i in range(len(entity.points)-1):
406
- if not entity.is_edge_invisible(i):
407
- ip = i+1 if i < 4 else 0
408
- yield Line(Element(start=(entity.points[i][1],
409
- entity.points[i][2]),
410
- end=(entity.points[ip][1],
411
- entity.points[ip][2])))
412
-
413
-
414
- def insert_block(dwg, insert_entity, lf, rf, block, min_dist=0.001):
415
- logger.debug('Insert %s entities from block %s',
416
- len(block),
417
- insert_entity.name)
418
- logger.debug('Insert = %s', insert_entity.insert)
419
- logger.debug('Rotation = %s', insert_entity.rotation)
420
- logger.debug('Scale = %s', insert_entity.scale)
421
- logger.debug('Rows = %s', insert_entity.row_count)
422
- logger.debug('Cols = %s', insert_entity.col_count)
423
- logger.debug('Row spacing = %s', insert_entity.row_spacing)
424
- logger.debug('Col spacing = %s', insert_entity.col_spacing)
425
-
426
- if insert_entity.insert != (0.0, 0.0, 0.0):
427
- logger.debug('Different Location in Insert')
428
-
429
- xoff = insert_entity.insert[0]
430
- yoff = insert_entity.insert[1]
431
-
432
- scale = (round(insert_entity.scale[0], 8),
433
- round(insert_entity.scale[1], 8),
434
- round(insert_entity.scale[2], 8))
435
- if not (scale == (1.0, 1.0, 1.0) or
436
- scale == (1.0, 1.0, 0.0)):
437
- logger.error('Block scaling in Insert not supported')
438
- logger.error(' scale = {}'.format(scale))
439
- return
440
-
441
- if(insert_entity.row_count > 1 or
442
- insert_entity.col_count > 1 or
443
- insert_entity.row_spacing > 0 or
444
- insert_entity.col_spacing > 0):
445
- logger.error('Multi Block references in Insert not supported')
446
- return
447
-
448
- for e in block:
449
- if e.dxftype == 'ARC':
450
- logger.debug("Block Arc")
451
- yield Arc(e, lf, rf,
452
- xoff=xoff, yoff=yoff,
453
- rotation=insert_entity.rotation)
454
- elif e.dxftype == 'CIRCLE':
455
- logger.debug("Block Circle %s, Radius %f", e.center[:2], e.radius)
456
- yield Circle(e, lf,
457
- xoff=xoff, yoff=yoff,
458
- rotation=insert_entity.rotation)
459
- elif e.dxftype == 'LINE':
460
- logger.debug("Block Line")
461
- yield Line(e, lf,
462
- xoff=xoff, yoff=yoff,
463
- rotation=insert_entity.rotation)
464
- elif e.dxftype == 'POLYLINE':
465
- logger.debug("Block Polyline")
466
- for p in polylines(e, lf, rf,
467
- xoff=xoff, yoff=yoff,
468
- rotation=insert_entity.rotation):
469
- yield p
470
- elif e.dxftype == 'LWPOLYLINE':
471
- logger.debug("Block lwPolyline")
472
- for p in lw_polyline(e, lf,
473
- xoff=xoff, yoff=yoff,
474
- rotation=insert_entity.rotation):
475
- yield p
476
- elif e.dxftype == 'SPLINE':
477
- logger.debug("Block spline")
478
- for l in spline(e, lf,
479
- min_dist=min_dist,
480
- xoff=xoff, yoff=yoff,
481
- rotation=insert_entity.rotation):
482
- yield l
483
- elif e.dxftype == 'INSERT':
484
- logger.debug("Nested Insert of Block %s", e.name)
485
- block = dwg.blocks[e.name]
486
- for l in insert_block(dwg, e, lf, rf, block, min_dist=min_dist):
487
- yield l
488
-
489
- else:
490
- logger.warn("unknown type %s in block %s",
491
- e.dxftype, insert_entity.name)
492
-
493
-
494
- def dxfshapes0(dxffile, mindist=0.01, layers=[]):
495
- """returns a collection of dxf entities (ezdxf)"""
496
- import ezdxf
497
- dwg = ezdxf.readfile(dxffile)
498
- id = 0
499
- # $ACADVER: AC1006 = R10, AC1009 = R11 and R12, AC1012 = R13,
500
- # AC1014 = R14 AC1015 = Release 2000/0i/2
501
- # check units:
502
- # dwg.header['$ANGDIR'] 1 = Clockwise angles, 0 = Counterclockwise
503
- # dwg.header['$AUNITS'] 0 Decimal Degrees, 1 Deg/Min/Sec, 2 Grads, 3 Radians
504
- # dwg.header['$INSUNIT'] 1 = Inches; 2 = Feet; 3 = Miles;
505
- # 4 = Millimeters; 5 = Centimeters; 6 = Meters
506
- # dwg.header['$LUNITS']
507
- for e in dwg.modelspace():
508
- if e.dxftype() == 'ARC':
509
- yield Arc(e.dxf)
510
- elif e.dxftype() == 'CIRCLE':
511
- logger.debug("Circle %s, Radius %f", e.center[:2], e.radius)
512
- yield Circle(e.dxf)
513
- elif e.dxftype() == 'LINE':
514
- yield Line(e.dxf)
515
- elif e.dxftype() == 'POLYLINE':
516
- for p in polylines(e):
517
- yield p
518
- elif e.dxftype() == 'SPLINE':
519
- for l in spline(e, 1.0, in_dist=mindist):
520
- yield l
521
- elif e.dxftype() == 'POINT':
522
- logger.debug("Id %d4: type %s ignored", id, e.dxftype)
523
- else:
524
- logger.warning("Id %d4: unknown type %s", id, e.dxftype)
525
- id += 1
526
-
527
-
528
- def dxfshapes(dxffile, mindist=0.01, layers=[]):
529
- """returns a collection of dxf entities (dxfgrabber)"""
530
- import dxfgrabber
531
- dwg = dxfgrabber.readfile(dxffile)
532
- # print("Layers = {}".format(dwg.layers.names()))
533
- id = 0
534
- # $ACADVER: AC1006 = R10, AC1009 = R11 and R12, AC1012 = R13,
535
- # AC1014 = R14 AC1015 = Release 2000/0i/2
536
- # check units:
537
- # dwg.header['$ANGDIR'] 1 = Clockwise angles, 0 = Counterclockwise
538
- # dwg.header['$AUNITS'] Decimal Degrees, Deg/Min/Sec, Grads, Radians
539
- # dwg.header['$INSUNIT'] 1 = Inches; 2 = Feet; 3 = Miles;
540
- # 4 = Millimeters; 5 = Centimeters; 6 = Meters
541
- # dwg.header['$LUNITS']
542
- lf = 1
543
- if dwg.header.get('$LUNITS', 0) == 1:
544
- # conv = [1, 2.54e-2, 10.12, 633.0, 1e-3, 1e-2, 1]
545
- lf = 2.54e3
546
-
547
- rf = np.pi/180
548
- if dwg.header.get('$AUNITS', 0) == 4:
549
- rf = 1
550
-
551
- for e in dwg.modelspace():
552
- if not layers or e.layer in layers:
553
- if e.dxftype == 'ARC':
554
- yield Arc(e, lf, rf)
555
- elif e.dxftype == 'CIRCLE':
556
- logger.debug("Circle %s, Radius %f", e.center[:2], e.radius)
557
- yield Circle(e, lf)
558
- elif e.dxftype == 'LINE':
559
- yield Line(e, lf)
560
- elif e.dxftype == 'POLYLINE':
561
- for p in polylines(e, lf, rf):
562
- yield p
563
- elif e.dxftype == 'LWPOLYLINE':
564
- for p in lw_polyline(e, lf):
565
- yield p
566
- elif e.dxftype == 'SPLINE':
567
- for l in spline(e, lf, min_dist=mindist):
568
- yield l
569
- elif e.dxftype == 'INSERT':
570
- logger.debug("Insert of Block %s", e.name)
571
- block = dwg.blocks[e.name]
572
- for l in insert_block(dwg, e, lf, rf, block, min_dist=mindist):
573
- yield l
574
- elif e.dxftype == 'ELLIPSE':
575
- for l in ellipse(e, lf):
576
- yield l
577
- #w = np.linalg.norm(e.major_axis) * 2
578
- #h = e.ratio * w
579
- #rtheta = np.arctan2(e.major_axis[1], e.major_axis[0])
580
- #angle = rtheta*180/np.pi
581
- #start_angle = e.start_param*180/np.pi + angle
582
- #end_angle = e.end_param*180/np.pi + angle
583
- #if end_angle < start_angle:
584
- # end_angle += 360
585
- #arc = Arc(Element(center=e.center,
586
- # radius=w/2,
587
- # start_angle=start_angle,
588
- # end_angle=end_angle,
589
- # width=w,
590
- # height=h,
591
- # rtheta=rtheta,
592
- # start_param=e.start_param,
593
- # end_param=e.end_param))
594
- #yield arc
595
-
596
- elif e.dxftype == 'POINT':
597
- logger.debug("Id %d4: type %s ignored", id, e.dxftype)
598
- elif e.dxftype == '3DFACE':
599
- logger.warning(
600
- "Id %d4: type %s not implemented", id, e.dxftype)
601
- # for l in face3d(e, lf):
602
- # yield l
603
- else:
604
- logger.warning("Id %d4: unknown type %s", id, e.dxftype)
605
- id += 1
606
-
607
-
608
- fem_points = []
609
-
610
-
611
- def read_fem_points(f, num):
612
- for x in range(num):
613
- p = f.readline().split()
614
- fem_points.append([float(p[0]), float(p[1])])
615
-
616
-
617
- def read_fem_lines(f, num):
618
- for x in range(num):
619
- p = f.readline().split()
620
- i1 = int(p[0])
621
- i2 = int(p[1])
622
- p1 = fem_points[i1]
623
- p2 = fem_points[i2]
624
- if points_are_close(p1, p2):
625
- logger.warning("FEMM: Line with points close together")
626
- logger.warning(" p1 = %s, p2 =%s", p1, p2)
627
- yield Line(Element(start=p1, end=p2))
628
-
629
-
630
- def read_fem_arcs(f, num):
631
- for x in range(num):
632
- p = f.readline().split()
633
- i1 = int(p[0])
634
- i2 = int(p[1])
635
- alpha = float(p[2])
636
- p1 = fem_points[i1]
637
- p2 = fem_points[i2]
638
- if points_are_close(p1, p2):
639
- logger.warning("FEMM: Arc with points close together")
640
- logger.warning(" p1 = %s, p2 = %s", p1, p2)
641
- for e in get_fem_arc(p1, p2, alpha):
642
- yield e
643
-
644
-
645
- def get_fem_arc(pA, pB, alfa):
646
- alpha = alfa/180.0*np.pi/2.0
647
- y = distance(pA, pB) / 2.0
648
- x = y / np.tan(alpha)
649
- r = np.sqrt(x**2 + y**2)
650
-
651
- delta = alpha_line(pA, pB)
652
-
653
- c = [pA[0] + y, pA[1] + x]
654
- phi = alpha_line(pA, c) + delta
655
- pC = point_on_arc(pA, r, phi)
656
-
657
- startangle = alpha_line(pC, pA)
658
- endangle = alpha_line(pC, pB)
659
- yield Arc(Element(center=pC,
660
- radius=r,
661
- start_angle=startangle*180/np.pi,
662
- end_angle=endangle*180/np.pi))
663
-
664
-
665
- def femshapes(femfile):
666
- f = open(femfile, 'r')
667
-
668
- for data in f:
669
- text = data.split()
670
- if text[0] == '[NumPoints]':
671
- read_fem_points(f, int(text[2]))
672
- elif text[0] == '[NumSegments]':
673
- for e in read_fem_lines(f, int(text[2])):
674
- yield e
675
- elif text[0] == '[NumArcSegments]':
676
- for e in read_fem_arcs(f, int(text[2])):
677
- yield e
678
-
679
-
680
255
  def is_connected(a, b):
681
256
  if a == b:
682
257
  return False
@@ -1079,6 +654,14 @@ class Geometry(object):
1079
654
  return [n[1]['object'] for n in self.g.nodes(data=True)
1080
655
  if n[1] and isinstance(n[1]['object'], Circle)]
1081
656
 
657
+ def nodes(self):
658
+ for n in self.g.nodes():
659
+ yield n
660
+
661
+ def get_nodes(self):
662
+ nodes = [n for n in self.g.nodes()]
663
+ return nodes
664
+
1082
665
  def virtual_nodes(self):
1083
666
  nodes = []
1084
667
  for e in self.elements(Shape):
@@ -1696,6 +1279,11 @@ class Geometry(object):
1696
1279
  # list already available
1697
1280
  return
1698
1281
 
1282
+ areabuilder = AreaBuilder(geom=self)
1283
+ areabuilder.create_list_of_areas(main=False)
1284
+ self.area_list = areabuilder.area_list
1285
+ return
1286
+
1699
1287
  def append(area_list, a):
1700
1288
  for area in area_list:
1701
1289
  if area.is_identical(a):
@@ -55,7 +55,6 @@ class Journal(object):
55
55
  if not self.journal:
56
56
  return
57
57
  self.set_benchmark()
58
-
59
58
  with open(self.filename, 'w') as f:
60
59
  f.write(json.dumps(self.journal, indent=4))
61
60
  f.close()
@@ -69,18 +68,8 @@ class Journal(object):
69
68
 
70
69
  self.open_journal()
71
70
  self.data = self.journal.get(name, None)
72
- if not self.data:
73
- self.data = {'filename': "",
74
- 'elements': 0,
75
- 'nodes': 0,
76
- 'concat_lines': 0,
77
- 'concat_arcs': 0,
78
- 'appendices': 0,
79
- 'appendices_connected': 0,
80
- 'appendices_remaining': 0,
81
- 'areas': 0,
82
- 'area_errors': 0}
83
- self.journal[name] = self.data
71
+ self.data = {'filename': ""} # initialise
72
+ self.journal[name] = self.data
84
73
  return self.data
85
74
 
86
75
  def set_benchmark(self):
@@ -88,19 +77,23 @@ class Journal(object):
88
77
  self.put_warning("Problem with areas")
89
78
  if self.data.get('appendices_deleted', 0) > 0:
90
79
  self.put_warning("Problem with appendices")
91
-
80
+
92
81
  def put(self, name, val):
93
82
  if self.data:
94
83
  self.data[name] = val
95
84
 
85
+ def add(self, name, val):
86
+ if self.data:
87
+ self.data[name] = val + self.data.get(name, 0)
88
+
96
89
  def put_filename(self, val):
97
90
  self.put('filename', val)
98
91
 
99
92
  def put_areas(self, val):
100
- self.put('areas', val)
93
+ self.add('areas', val)
101
94
 
102
95
  def put_area_errors(self, val):
103
- self.put('area_errors', val)
96
+ self.add('area_errors', val)
104
97
 
105
98
  def put_elements(self, val):
106
99
  self.put('elements', val)
@@ -109,28 +102,27 @@ class Journal(object):
109
102
  self.put('nodes', val)
110
103
 
111
104
  def put_concat_lines(self, val):
112
- self.put('concat_lines', val)
105
+ self.add('concat_lines', val)
113
106
 
114
107
  def put_concat_arcs(self, val):
115
- self.put('concat_arcs', val)
108
+ self.add('concat_arcs', val)
116
109
 
117
110
  def put_appendices(self, val):
118
- self.put('appendices', val)
111
+ self.add('appendices', val)
119
112
 
120
113
  def put_appendices_connected(self, val):
121
- self.put('appendices_connected', val)
114
+ self.add('appendices_connected', val)
122
115
 
123
116
  def put_appendices_remaining(self, val):
124
- self.put('appendices_remaining', val)
117
+ self.add('appendices_remaining', val)
125
118
 
126
119
  def put_appendices_deleted(self, val):
127
- self.put('appendices_deleted', val)
120
+ self.add('appendices_deleted', val)
128
121
 
129
122
  def put_exception(self, msg):
130
123
  self.put('exception', msg)
131
124
 
132
125
  def put_warning(self, msg):
133
- print(msg)
134
126
  lst = self.data.get('warning', [])
135
127
  lst.append(msg)
136
128
  self.put('warning', lst)
@@ -7,6 +7,7 @@ import numpy as np
7
7
  import logging
8
8
  from .shape import Element, Circle, Arc, Line
9
9
  from .corner import Corner
10
+ from femagtools.dxfsl.symmetry import Symmetry
10
11
  from .functions import point, points_are_close, distance
11
12
  from .functions import alpha_angle, normalise_angle, middle_angle, third_angle
12
13
  from .functions import alpha_line, line_m, line_n, mirror_point
@@ -592,9 +593,18 @@ class Machine(object):
592
593
  if self.radius <= 0.0:
593
594
  return False
594
595
 
595
- found = self.geom.find_symmetry(self.radius,
596
- self.startangle, self.endangle,
597
- sym_tolerance)
596
+ symmetry = Symmetry(geom=self.geom,
597
+ startangle=self.startangle,
598
+ endangle=self.endangle)
599
+ parts = symmetry.find_symmetry() # temp solution
600
+ logger.debug(">>> Symmetry parts = %s <<<", parts)
601
+
602
+ if parts > 1:
603
+ found = True
604
+ else:
605
+ found = self.geom.find_symmetry(self.radius,
606
+ self.startangle, self.endangle,
607
+ sym_tolerance)
598
608
  if not found and len(self.geom.area_list) < 5:
599
609
  if is_inner:
600
610
  found = self.find_stator_symmetry(sym_tolerance, True)
femagtools/dxfsl/shape.py CHANGED
@@ -1008,7 +1008,7 @@ class Arc(Circle):
1008
1008
  for p in points:
1009
1009
  alpha_p = alpha_line(center, p)
1010
1010
  alpha_min = min_angle(alpha_min, alpha_p)
1011
- alpha_max = min_angle(alpha_max, alpha_p)
1011
+ alpha_max = max_angle(alpha_max, alpha_p)
1012
1012
 
1013
1013
  return (alpha_min, alpha_max)
1014
1014
 
@@ -1305,8 +1305,18 @@ class Line(Shape):
1305
1305
  return (dist_min, dist_max)
1306
1306
 
1307
1307
  def minmax_angle_from_center(self, center):
1308
- alpha_p1 = alpha_line(center, self.p1)
1309
- alpha_p2 = alpha_line(center, self.p2)
1308
+ if points_are_close(center, self.p1):
1309
+ alpha_p1 = None
1310
+ else:
1311
+ alpha_p1 = alpha_line(center, self.p1)
1312
+ if points_are_close(center, self.p2):
1313
+ alpha_p2 = None
1314
+ else:
1315
+ alpha_p2 = alpha_line(center, self.p2)
1316
+ if alpha_p1 is None:
1317
+ alpha_p1 = alpha_p2
1318
+ if alpha_p2 is None:
1319
+ alpha_p2 = alpha_p1
1310
1320
  if alpha_angle(alpha_p1, alpha_p2) < np.pi:
1311
1321
  return (alpha_p1, alpha_p2)
1312
1322
  else: