topologicpy 0.4.8__py3-none-any.whl → 0.4.9__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 (79) hide show
  1. topologicpy/Aperture.py +46 -0
  2. topologicpy/Cell.py +1780 -0
  3. topologicpy/CellComplex.py +791 -0
  4. topologicpy/Cluster.py +591 -0
  5. topologicpy/Color.py +157 -0
  6. topologicpy/Context.py +56 -0
  7. topologicpy/DGL.py +2661 -0
  8. topologicpy/Dictionary.py +470 -0
  9. topologicpy/Edge.py +855 -0
  10. topologicpy/EnergyModel.py +1052 -0
  11. topologicpy/Face.py +1810 -0
  12. topologicpy/Graph.py +3526 -0
  13. topologicpy/Graph_Export.py +858 -0
  14. topologicpy/Grid.py +338 -0
  15. topologicpy/Helper.py +182 -0
  16. topologicpy/Honeybee.py +424 -0
  17. topologicpy/Matrix.py +255 -0
  18. topologicpy/Neo4jGraph.py +311 -0
  19. topologicpy/Plotly.py +1396 -0
  20. topologicpy/Polyskel.py +524 -0
  21. topologicpy/Process.py +1368 -0
  22. topologicpy/SQL.py +48 -0
  23. topologicpy/Shell.py +1418 -0
  24. topologicpy/Speckle.py +433 -0
  25. topologicpy/Topology.py +5854 -0
  26. topologicpy/UnitTest.py +29 -0
  27. topologicpy/Vector.py +555 -0
  28. topologicpy/Vertex.py +714 -0
  29. topologicpy/Wire.py +2346 -0
  30. topologicpy/__init__.py +20 -0
  31. topologicpy/bin/linux/topologic/__init__.py +2 -0
  32. topologicpy/bin/linux/topologic/topologic.cpython-310-x86_64-linux-gnu.so +0 -0
  33. topologicpy/bin/linux/topologic/topologic.cpython-311-x86_64-linux-gnu.so +0 -0
  34. topologicpy/bin/linux/topologic/topologic.cpython-38-x86_64-linux-gnu.so +0 -0
  35. topologicpy/bin/linux/topologic/topologic.cpython-39-x86_64-linux-gnu.so +0 -0
  36. topologicpy/bin/linux/topologic.libs/libTKBO-6bdf205d.so.7.7.0 +0 -0
  37. topologicpy/bin/linux/topologic.libs/libTKBRep-2960a069.so.7.7.0 +0 -0
  38. topologicpy/bin/linux/topologic.libs/libTKBool-c44b74bd.so.7.7.0 +0 -0
  39. topologicpy/bin/linux/topologic.libs/libTKFillet-9a670ba0.so.7.7.0 +0 -0
  40. topologicpy/bin/linux/topologic.libs/libTKG2d-8f31849e.so.7.7.0 +0 -0
  41. topologicpy/bin/linux/topologic.libs/libTKG3d-4c6bce57.so.7.7.0 +0 -0
  42. topologicpy/bin/linux/topologic.libs/libTKGeomAlgo-26066fd9.so.7.7.0 +0 -0
  43. topologicpy/bin/linux/topologic.libs/libTKGeomBase-2116cabe.so.7.7.0 +0 -0
  44. topologicpy/bin/linux/topologic.libs/libTKMath-72572fa8.so.7.7.0 +0 -0
  45. topologicpy/bin/linux/topologic.libs/libTKMesh-2a060427.so.7.7.0 +0 -0
  46. topologicpy/bin/linux/topologic.libs/libTKOffset-6cab68ff.so.7.7.0 +0 -0
  47. topologicpy/bin/linux/topologic.libs/libTKPrim-eb1262b3.so.7.7.0 +0 -0
  48. topologicpy/bin/linux/topologic.libs/libTKShHealing-e67e5cc7.so.7.7.0 +0 -0
  49. topologicpy/bin/linux/topologic.libs/libTKTopAlgo-e4c96c33.so.7.7.0 +0 -0
  50. topologicpy/bin/linux/topologic.libs/libTKernel-fb7fe3b7.so.7.7.0 +0 -0
  51. topologicpy/bin/linux/topologic.libs/libgcc_s-32c1665e.so.1 +0 -0
  52. topologicpy/bin/linux/topologic.libs/libstdc++-672d7b41.so.6.0.30 +0 -0
  53. topologicpy/bin/windows/topologic/TKBO-f6b191de.dll +0 -0
  54. topologicpy/bin/windows/topologic/TKBRep-e56a600e.dll +0 -0
  55. topologicpy/bin/windows/topologic/TKBool-7b8d47ae.dll +0 -0
  56. topologicpy/bin/windows/topologic/TKFillet-0ddbf0a8.dll +0 -0
  57. topologicpy/bin/windows/topologic/TKG2d-2e2dee3d.dll +0 -0
  58. topologicpy/bin/windows/topologic/TKG3d-6674513d.dll +0 -0
  59. topologicpy/bin/windows/topologic/TKGeomAlgo-d240e370.dll +0 -0
  60. topologicpy/bin/windows/topologic/TKGeomBase-df87aba5.dll +0 -0
  61. topologicpy/bin/windows/topologic/TKMath-45bd625a.dll +0 -0
  62. topologicpy/bin/windows/topologic/TKMesh-d6e826b1.dll +0 -0
  63. topologicpy/bin/windows/topologic/TKOffset-79b9cc94.dll +0 -0
  64. topologicpy/bin/windows/topologic/TKPrim-aa430a86.dll +0 -0
  65. topologicpy/bin/windows/topologic/TKShHealing-bb48be89.dll +0 -0
  66. topologicpy/bin/windows/topologic/TKTopAlgo-7d0d1e22.dll +0 -0
  67. topologicpy/bin/windows/topologic/TKernel-08c8cfbb.dll +0 -0
  68. topologicpy/bin/windows/topologic/__init__.py +2 -0
  69. topologicpy/bin/windows/topologic/topologic.cp310-win_amd64.pyd +0 -0
  70. topologicpy/bin/windows/topologic/topologic.cp311-win_amd64.pyd +0 -0
  71. topologicpy/bin/windows/topologic/topologic.cp38-win_amd64.pyd +0 -0
  72. topologicpy/bin/windows/topologic/topologic.cp39-win_amd64.pyd +0 -0
  73. {topologicpy-0.4.8.dist-info → topologicpy-0.4.9.dist-info}/METADATA +1 -1
  74. topologicpy-0.4.9.dist-info/RECORD +77 -0
  75. topologicpy-0.4.9.dist-info/top_level.txt +1 -0
  76. topologicpy-0.4.8.dist-info/RECORD +0 -5
  77. topologicpy-0.4.8.dist-info/top_level.txt +0 -1
  78. {topologicpy-0.4.8.dist-info → topologicpy-0.4.9.dist-info}/LICENSE +0 -0
  79. {topologicpy-0.4.8.dist-info → topologicpy-0.4.9.dist-info}/WHEEL +0 -0
@@ -0,0 +1,524 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+
4
+ import logging
5
+ import heapq
6
+ from itertools import *
7
+ from collections import namedtuple
8
+ import sys
9
+ import subprocess
10
+
11
+ try:
12
+ from euclid3 import *
13
+ except:
14
+ call = [sys.executable, '-m', 'pip', 'install', 'euclid3', '-t', sys.path[0]]
15
+ subprocess.run(call)
16
+ try:
17
+ from euclid3 import *
18
+ except:
19
+ print("Polyskel - ERROR: Could not import euclid3.")
20
+
21
+ log = logging.getLogger("__name__")
22
+
23
+ EPSILON = 0.00001
24
+
25
+ class Debug:
26
+ def __init__(self, image):
27
+ if image is not None:
28
+ self.im = image[0]
29
+ self.draw = image[1]
30
+ self.do = True
31
+ else:
32
+ self.do = False
33
+
34
+ def line(self, *args, **kwargs):
35
+ if self.do:
36
+ self.draw.line(*args, **kwargs)
37
+
38
+ def rectangle(self, *args, **kwargs):
39
+ if self.do:
40
+ self.draw.rectangle(*args, **kwargs)
41
+
42
+ def show(self):
43
+ if self.do:
44
+ self.im.show()
45
+
46
+
47
+ _debug = Debug(None)
48
+
49
+
50
+ def set_debug(image):
51
+ global _debug
52
+ _debug = Debug(image)
53
+
54
+
55
+ def _window(lst):
56
+ prevs, items, nexts = tee(lst, 3)
57
+ prevs = islice(cycle(prevs), len(lst) - 1, None)
58
+ nexts = islice(cycle(nexts), 1, None)
59
+ return zip(prevs, items, nexts)
60
+
61
+
62
+ def _cross(a, b):
63
+ res = a.x * b.y - b.x * a.y
64
+ return res
65
+
66
+
67
+ def _approximately_equals(a, b):
68
+ return a == b or (abs(a - b) <= max(abs(a), abs(b)) * 0.001)
69
+
70
+
71
+ def _approximately_same(point_a, point_b):
72
+ return _approximately_equals(point_a.x, point_b.x) and _approximately_equals(point_a.y, point_b.y)
73
+
74
+
75
+ def _normalize_contour(contour):
76
+ contour = [Point2(float(x), float(y)) for (x, y) in contour]
77
+ return [point for prev, point, next in _window(contour) if not (point == next or (point-prev).normalized() == (next - point).normalized())]
78
+
79
+
80
+ class _SplitEvent(namedtuple("_SplitEvent", "distance, intersection_point, vertex, opposite_edge")):
81
+ __slots__ = ()
82
+
83
+ def __lt__(self, other):
84
+ return self.distance < other.distance
85
+
86
+ def __str__(self):
87
+ return "{} Split event @ {} from {} to {}".format(self.distance, self.intersection_point, self.vertex, self.opposite_edge)
88
+
89
+
90
+ class _EdgeEvent(namedtuple("_EdgeEvent", "distance intersection_point vertex_a vertex_b")):
91
+ __slots__ = ()
92
+
93
+ def __lt__(self, other):
94
+ return self.distance < other.distance
95
+
96
+ def __str__(self):
97
+ return "{} Edge event @ {} between {} and {}".format(self.distance, self.intersection_point, self.vertex_a, self.vertex_b)
98
+
99
+
100
+ _OriginalEdge = namedtuple("_OriginalEdge", "edge bisector_left, bisector_right")
101
+
102
+ Subtree = namedtuple("Subtree", "source, height, sinks")
103
+
104
+
105
+ class _LAVertex:
106
+ def __init__(self, point, edge_left, edge_right, direction_vectors=None):
107
+ self.point = point
108
+ self.edge_left = edge_left
109
+ self.edge_right = edge_right
110
+ self.prev = None
111
+ self.next = None
112
+ self.lav = None
113
+ self._valid = True # TODO this might be handled better. Maybe membership in lav implies validity?
114
+
115
+ creator_vectors = (edge_left.v.normalized() * -1, edge_right.v.normalized())
116
+ if direction_vectors is None:
117
+ direction_vectors = creator_vectors
118
+
119
+ self._is_reflex = (_cross(*direction_vectors)) < 0
120
+ self._bisector = Ray2(self.point, operator.add(*creator_vectors) * (-1 if self.is_reflex else 1))
121
+ log.info("Created vertex %s", self.__repr__())
122
+ _debug.line((self.bisector.p.x, self.bisector.p.y, self.bisector.p.x + self.bisector.v.x * 100, self.bisector.p.y + self.bisector.v.y * 100), fill="blue")
123
+
124
+ @property
125
+ def bisector(self):
126
+ return self._bisector
127
+
128
+ @property
129
+ def is_reflex(self):
130
+ return self._is_reflex
131
+
132
+ @property
133
+ def original_edges(self):
134
+ return self.lav._slav._original_edges
135
+
136
+ def next_event(self):
137
+ events = []
138
+ if self.is_reflex:
139
+ # a reflex vertex may generate a split event
140
+ # split events happen when a vertex hits an opposite edge, splitting the polygon in two.
141
+ log.debug("looking for split candidates for vertex %s", self)
142
+ for edge in self.original_edges:
143
+ if edge.edge == self.edge_left or edge.edge == self.edge_right:
144
+ continue
145
+
146
+ log.debug("\tconsidering EDGE %s", edge)
147
+
148
+ # a potential b is at the intersection of between our own bisector and the bisector of the
149
+ # angle between the tested edge and any one of our own edges.
150
+
151
+ # we choose the "less parallel" edge (in order to exclude a potentially parallel edge)
152
+ leftdot = abs(self.edge_left.v.normalized().dot(edge.edge.v.normalized()))
153
+ rightdot = abs(self.edge_right.v.normalized().dot(edge.edge.v.normalized()))
154
+ selfedge = self.edge_left if leftdot < rightdot else self.edge_right
155
+ otheredge = self.edge_left if leftdot > rightdot else self.edge_right
156
+
157
+ i = Line2(selfedge).intersect(Line2(edge.edge))
158
+ if i is not None and not _approximately_equals(i, self.point):
159
+ # locate candidate b
160
+ linvec = (self.point - i).normalized()
161
+ edvec = edge.edge.v.normalized()
162
+ if linvec.dot(edvec) < 0:
163
+ edvec = -edvec
164
+
165
+ bisecvec = edvec + linvec
166
+ if abs(bisecvec) == 0:
167
+ continue
168
+ bisector = Line2(i, bisecvec)
169
+ b = bisector.intersect(self.bisector)
170
+
171
+ if b is None:
172
+ continue
173
+
174
+ # check eligibility of b
175
+ # a valid b should lie within the area limited by the edge and the bisectors of its two vertices:
176
+ xleft = _cross(edge.bisector_left.v.normalized(), (b - edge.bisector_left.p).normalized()) > -EPSILON
177
+ xright = _cross(edge.bisector_right.v.normalized(), (b - edge.bisector_right.p).normalized()) < EPSILON
178
+ xedge = _cross(edge.edge.v.normalized(), (b - edge.edge.p).normalized()) < EPSILON
179
+
180
+ if not (xleft and xright and xedge):
181
+ log.debug("\t\tDiscarded candidate %s (%s-%s-%s)", b, xleft, xright, xedge)
182
+ continue
183
+
184
+ log.debug("\t\tFound valid candidate %s", b)
185
+ events.append(_SplitEvent(Line2(edge.edge).distance(b), b, self, edge.edge))
186
+
187
+ i_prev = self.bisector.intersect(self.prev.bisector)
188
+ i_next = self.bisector.intersect(self.next.bisector)
189
+
190
+ if i_prev is not None:
191
+ events.append(_EdgeEvent(Line2(self.edge_left).distance(i_prev), i_prev, self.prev, self))
192
+ if i_next is not None:
193
+ events.append(_EdgeEvent(Line2(self.edge_right).distance(i_next), i_next, self, self.next))
194
+
195
+ if not events:
196
+ return None
197
+
198
+ ev = min(events, key=lambda event: self.point.distance(event.intersection_point))
199
+
200
+ log.info("Generated new event for %s: %s", self, ev)
201
+ return ev
202
+
203
+ def invalidate(self):
204
+ if self.lav is not None:
205
+ self.lav.invalidate(self)
206
+ else:
207
+ self._valid = False
208
+
209
+ @property
210
+ def is_valid(self):
211
+ return self._valid
212
+
213
+ def __str__(self):
214
+ return "Vertex ({:.2f};{:.2f})".format(self.point.x, self.point.y)
215
+
216
+ def __repr__(self):
217
+ return "Vertex ({}) ({:.2f};{:.2f}), bisector {}, edges {} {}".format("reflex" if self.is_reflex else "convex",
218
+ self.point.x, self.point.y, self.bisector,
219
+ self.edge_left, self.edge_right)
220
+
221
+
222
+ class _SLAV:
223
+ def __init__(self, polygon, holes):
224
+ contours = [_normalize_contour(polygon)]
225
+ contours.extend([_normalize_contour(hole) for hole in holes])
226
+
227
+ self._lavs = [_LAV.from_polygon(contour, self) for contour in contours]
228
+
229
+ # store original polygon edges for calculating split events
230
+ self._original_edges = [
231
+ _OriginalEdge(LineSegment2(vertex.prev.point, vertex.point), vertex.prev.bisector, vertex.bisector)
232
+ for vertex in chain.from_iterable(self._lavs)
233
+ ]
234
+
235
+ def __iter__(self):
236
+ for lav in self._lavs:
237
+ yield lav
238
+
239
+ def __len__(self):
240
+ return len(self._lavs)
241
+
242
+ def empty(self):
243
+ return len(self._lavs) == 0
244
+
245
+ def handle_edge_event(self, event):
246
+ sinks = []
247
+ events = []
248
+
249
+ lav = event.vertex_a.lav
250
+ if event.vertex_a.prev == event.vertex_b.next:
251
+ log.info("%.2f Peak event at intersection %s from <%s,%s,%s> in %s", event.distance,
252
+ event.intersection_point, event.vertex_a, event.vertex_b, event.vertex_a.prev, lav)
253
+ self._lavs.remove(lav)
254
+ for vertex in list(lav):
255
+ sinks.append(vertex.point)
256
+ vertex.invalidate()
257
+ else:
258
+ log.info("%.2f Edge event at intersection %s from <%s,%s> in %s", event.distance, event.intersection_point,
259
+ event.vertex_a, event.vertex_b, lav)
260
+ new_vertex = lav.unify(event.vertex_a, event.vertex_b, event.intersection_point)
261
+ if lav.head in (event.vertex_a, event.vertex_b):
262
+ lav.head = new_vertex
263
+ sinks.extend((event.vertex_a.point, event.vertex_b.point))
264
+ next_event = new_vertex.next_event()
265
+ if next_event is not None:
266
+ events.append(next_event)
267
+
268
+ return (Subtree(event.intersection_point, event.distance, sinks), events)
269
+
270
+ def handle_split_event(self, event):
271
+ lav = event.vertex.lav
272
+ log.info("%.2f Split event at intersection %s from vertex %s, for edge %s in %s", event.distance,
273
+ event.intersection_point, event.vertex, event.opposite_edge, lav)
274
+
275
+ sinks = [event.vertex.point]
276
+ vertices = []
277
+ x = None # right vertex
278
+ y = None # left vertex
279
+ norm = event.opposite_edge.v.normalized()
280
+ for v in chain.from_iterable(self._lavs):
281
+ log.debug("%s in %s", v, v.lav)
282
+ if norm == v.edge_left.v.normalized() and event.opposite_edge.p == v.edge_left.p:
283
+ x = v
284
+ y = x.prev
285
+ elif norm == v.edge_right.v.normalized() and event.opposite_edge.p == v.edge_right.p:
286
+ y = v
287
+ x = y.next
288
+
289
+ if x:
290
+ xleft = _cross(y.bisector.v.normalized(), (event.intersection_point - y.point).normalized()) >= -EPSILON
291
+ xright = _cross(x.bisector.v.normalized(), (event.intersection_point - x.point).normalized()) <= EPSILON
292
+ log.debug("Vertex %s holds edge as %s edge (%s, %s)", v, ("left" if x == v else "right"), xleft, xright)
293
+
294
+ if xleft and xright:
295
+ break
296
+ else:
297
+ x = None
298
+ y = None
299
+
300
+ if x is None:
301
+ log.info("Failed split event %s (equivalent edge event is expected to follow)", event)
302
+ return (None, [])
303
+
304
+ v1 = _LAVertex(event.intersection_point, event.vertex.edge_left, event.opposite_edge)
305
+ v2 = _LAVertex(event.intersection_point, event.opposite_edge, event.vertex.edge_right)
306
+
307
+ v1.prev = event.vertex.prev
308
+ v1.next = x
309
+ event.vertex.prev.next = v1
310
+ x.prev = v1
311
+
312
+ v2.prev = y
313
+ v2.next = event.vertex.next
314
+ event.vertex.next.prev = v2
315
+ y.next = v2
316
+
317
+ new_lavs = None
318
+ self._lavs.remove(lav)
319
+ if lav != x.lav:
320
+ # the split event actually merges two lavs
321
+ self._lavs.remove(x.lav)
322
+ new_lavs = [_LAV.from_chain(v1, self)]
323
+ else:
324
+ new_lavs = [_LAV.from_chain(v1, self), _LAV.from_chain(v2, self)]
325
+
326
+ for l in new_lavs:
327
+ log.debug(l)
328
+ if len(l) > 2:
329
+ self._lavs.append(l)
330
+ vertices.append(l.head)
331
+ else:
332
+ log.info("LAV %s has collapsed into the line %s--%s", l, l.head.point, l.head.next.point)
333
+ sinks.append(l.head.next.point)
334
+ for v in list(l):
335
+ v.invalidate()
336
+
337
+ events = []
338
+ for vertex in vertices:
339
+ next_event = vertex.next_event()
340
+ if next_event is not None:
341
+ events.append(next_event)
342
+
343
+ event.vertex.invalidate()
344
+ return (Subtree(event.intersection_point, event.distance, sinks), events)
345
+
346
+
347
+ class _LAV:
348
+ def __init__(self, slav):
349
+ self.head = None
350
+ self._slav = slav
351
+ self._len = 0
352
+ log.debug("Created LAV %s", self)
353
+
354
+ @classmethod
355
+ def from_polygon(cls, polygon, slav):
356
+ lav = cls(slav)
357
+ for prev, point, next in _window(polygon):
358
+ lav._len += 1
359
+ vertex = _LAVertex(point, LineSegment2(prev, point), LineSegment2(point, next))
360
+ vertex.lav = lav
361
+ if lav.head is None:
362
+ lav.head = vertex
363
+ vertex.prev = vertex.next = vertex
364
+ else:
365
+ vertex.next = lav.head
366
+ vertex.prev = lav.head.prev
367
+ vertex.prev.next = vertex
368
+ lav.head.prev = vertex
369
+ return lav
370
+
371
+ @classmethod
372
+ def from_chain(cls, head, slav):
373
+ lav = cls(slav)
374
+ lav.head = head
375
+ for vertex in lav:
376
+ lav._len += 1
377
+ vertex.lav = lav
378
+ return lav
379
+
380
+ def invalidate(self, vertex):
381
+ assert vertex.lav is self, "Tried to invalidate a vertex that's not mine"
382
+ log.debug("Invalidating %s", vertex)
383
+ vertex._valid = False
384
+ if self.head == vertex:
385
+ self.head = self.head.next
386
+ vertex.lav = None
387
+
388
+ def unify(self, vertex_a, vertex_b, point):
389
+ replacement = _LAVertex(point, vertex_a.edge_left, vertex_b.edge_right,
390
+ (vertex_b.bisector.v.normalized(), vertex_a.bisector.v.normalized()))
391
+ replacement.lav = self
392
+
393
+ if self.head in [vertex_a, vertex_b]:
394
+ self.head = replacement
395
+
396
+ vertex_a.prev.next = replacement
397
+ vertex_b.next.prev = replacement
398
+ replacement.prev = vertex_a.prev
399
+ replacement.next = vertex_b.next
400
+
401
+ vertex_a.invalidate()
402
+ vertex_b.invalidate()
403
+
404
+ self._len -= 1
405
+ return replacement
406
+
407
+ def __str__(self):
408
+ return "LAV {}".format(id(self))
409
+
410
+ def __repr__(self):
411
+ return "{} = {}".format(str(self), [vertex for vertex in self])
412
+
413
+ def __len__(self):
414
+ return self._len
415
+
416
+ def __iter__(self):
417
+ cur = self.head
418
+ while True:
419
+ yield cur
420
+ cur = cur.next
421
+ if cur == self.head:
422
+ return
423
+
424
+ def _show(self):
425
+ cur = self.head
426
+ while True:
427
+ print(cur.__repr__())
428
+ cur = cur.next
429
+ if cur == self.head:
430
+ break
431
+
432
+
433
+ class _EventQueue:
434
+ def __init__(self):
435
+ self.__data = []
436
+
437
+ def put(self, item):
438
+ if item is not None:
439
+ heapq.heappush(self.__data, item)
440
+
441
+ def put_all(self, iterable):
442
+ for item in iterable:
443
+ heapq.heappush(self.__data, item)
444
+
445
+ def get(self):
446
+ return heapq.heappop(self.__data)
447
+
448
+ def empty(self):
449
+ return len(self.__data) == 0
450
+
451
+ def peek(self):
452
+ return self.__data[0]
453
+
454
+ def show(self):
455
+ for item in self.__data:
456
+ print(item)
457
+
458
+ def _merge_sources(skeleton):
459
+ """
460
+ In highly symmetrical shapes with reflex vertices multiple sources may share the same
461
+ location. This function merges those sources.
462
+ """
463
+ sources = {}
464
+ to_remove = []
465
+ for i, p in enumerate(skeleton):
466
+ source = tuple(i for i in p.source)
467
+ if source in sources:
468
+ source_index = sources[source]
469
+ # source exists, merge sinks
470
+ for sink in p.sinks:
471
+ if sink not in skeleton[source_index].sinks:
472
+ skeleton[source_index].sinks.append(sink)
473
+ to_remove.append(i)
474
+ else:
475
+ sources[source] = i
476
+ for i in reversed(to_remove):
477
+ skeleton.pop(i)
478
+
479
+
480
+ def skeletonize(polygon, holes=None):
481
+ """
482
+ Compute the straight skeleton of a polygon.
483
+
484
+ The polygon should be given as a list of vertices in counter-clockwise order.
485
+ Holes is a list of the contours of the holes, the vertices of which should be in clockwise order.
486
+
487
+ Please note that the y-axis goes downwards as far as polyskel is concerned, so specify your ordering accordingly.
488
+
489
+ Returns the straight skeleton as a list of "subtrees", which are in the form of (source, height, sinks),
490
+ where source is the highest points, height is its height, and sinks are the point connected to the source.
491
+ """
492
+ slav = _SLAV(polygon, holes)
493
+ output = []
494
+ prioque = _EventQueue()
495
+
496
+ for lav in slav:
497
+ for vertex in lav:
498
+ prioque.put(vertex.next_event())
499
+
500
+ while not (prioque.empty() or slav.empty()):
501
+ log.debug("SLAV is %s", [repr(lav) for lav in slav])
502
+ i = prioque.get()
503
+ if isinstance(i, _EdgeEvent):
504
+ if not i.vertex_a.is_valid or not i.vertex_b.is_valid:
505
+ log.info("%.2f Discarded outdated edge event %s", i.distance, i)
506
+ continue
507
+
508
+ (arc, events) = slav.handle_edge_event(i)
509
+ elif isinstance(i, _SplitEvent):
510
+ if not i.vertex.is_valid:
511
+ log.info("%.2f Discarded outdated split event %s", i.distance, i)
512
+ continue
513
+ (arc, events) = slav.handle_split_event(i)
514
+
515
+ prioque.put_all(events)
516
+
517
+ if arc is not None:
518
+ output.append(arc)
519
+ for sink in arc.sinks:
520
+ _debug.line((arc.source.x, arc.source.y, sink.x, sink.y), fill="red")
521
+
522
+ _debug.show()
523
+ _merge_sources(output)
524
+ return output