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
topologicpy/Vertex.py ADDED
@@ -0,0 +1,714 @@
1
+ import topologicpy
2
+ import topologic
3
+ from topologicpy.Face import Face
4
+ from topologicpy.Topology import Topology
5
+ import collections
6
+
7
+ class Vertex(Topology):
8
+ @staticmethod
9
+ def AreIpsilateral(vertices: list, face: topologic.Face) -> bool:
10
+ """
11
+ Returns True if the two input vertices are on the same side of the input face. Returns False otherwise. If at least one of the vertices is on the face, this method return True.
12
+
13
+ Parameters
14
+ ----------
15
+ vertices : list
16
+ The input list of vertices.
17
+ face : topologic.Face
18
+ The input face
19
+
20
+ Returns
21
+ -------
22
+ bool
23
+ True if the input vertices are on the same side of the face. False otherwise. If at least one of the vertices is on the face, this method return True.
24
+
25
+ """
26
+ def check(dot_productA, pointB, pointC, normal):
27
+ # Calculate the dot products of the vectors from the surface point to each of the input points.
28
+ dot_productB = (pointB[0] - pointC[0]) * normal[0] + \
29
+ (pointB[1] - pointC[1]) * normal[1] + \
30
+ (pointB[2] - pointC[2]) * normal[2]
31
+
32
+ # Check if both points are on the same side of the surface.
33
+ if dot_productA * dot_productB > 0:
34
+ return True
35
+
36
+ # Check if both points are on opposite sides of the surface.
37
+ elif dot_productA * dot_productB < 0:
38
+ return False
39
+
40
+ # Otherwise, at least one point is on the surface.
41
+ else:
42
+ return True
43
+
44
+ from topologicpy.Vertex import Vertex
45
+ from topologicpy.Face import Face
46
+
47
+ if not isinstance(face, topologic.Face):
48
+ return None
49
+ vertexList = [x for x in vertices if isinstance(x, topologic.Vertex)]
50
+ if len(vertexList) < 2:
51
+ return None
52
+ pointA = Vertex.Coordinates(vertexList[0])
53
+ pointC = Vertex.Coordinates(Face.VertexByParameters(face, 0.5, 0.5))
54
+ normal = Face.Normal(face)
55
+ dot_productA = (pointA[0] - pointC[0]) * normal[0] + \
56
+ (pointA[1] - pointC[1]) * normal[1] + \
57
+ (pointA[2] - pointC[2]) * normal[2]
58
+ for i in range(1, len(vertexList)):
59
+ pointB = Vertex.Coordinates(vertexList[i])
60
+ if not check(dot_productA, pointB, pointC, normal):
61
+ return False
62
+ return True
63
+
64
+ @staticmethod
65
+ def AreIpsilateralCluster(cluster: topologic.Cluster, face: topologic.Face) -> bool:
66
+ """
67
+ Returns True if the two input vertices are on the same side of the input face. Returns False otherwise. If at least one of the vertices is on the face, this method return True.
68
+
69
+ Parameters
70
+ ----------
71
+ cluster : topologic.Cluster
72
+ The input list of vertices.
73
+ face : topologic.Face
74
+ The input face
75
+
76
+ Returns
77
+ -------
78
+ bool
79
+ True if the input vertices are on the same side of the face. False otherwise. If at least one of the vertices is on the face, this method return True.
80
+
81
+ """
82
+ from topologicpy.Topology import Topology
83
+ if not isinstance(cluster, topologic.Topology):
84
+ return None
85
+ vertices = Topology.SubTopologies(cluster, subTopologyType="vertex")
86
+ return Vertex.AreIpsilateral(vertices, face)
87
+
88
+ @staticmethod
89
+ def AreOnSameSide(vertices: list, face: topologicpy.Face.Face) -> bool:
90
+ """
91
+ Returns True if the two input vertices are on the same side of the input face. Returns False otherwise. If at least one of the vertices is on the face, this method return True.
92
+
93
+ Parameters
94
+ ----------
95
+ vertices : list
96
+ The input list of vertices.
97
+ face : topologic.Face
98
+ The input face
99
+
100
+ Returns
101
+ -------
102
+ bool
103
+ True if the input vertices are on the same side of the face. False otherwise. If at least one of the vertices is on the face, this method return True.
104
+
105
+ """
106
+ return Vertex.AreIpsilateral(vertices, face)
107
+
108
+ @staticmethod
109
+ def AreOnSameSideCluster(cluster: topologic.Cluster, face: topologic.Face) -> bool:
110
+ """
111
+ Returns True if the two input vertices are on the same side of the input face. Returns False otherwise. If at least one of the vertices is on the face, this method return True.
112
+
113
+ Parameters
114
+ ----------
115
+ cluster : topologic.Cluster
116
+ The input list of vertices.
117
+ face : topologic.Face
118
+ The input face
119
+
120
+ Returns
121
+ -------
122
+ bool
123
+ True if the input vertices are on the same side of the face. False otherwise. If at least one of the vertices is on the face, this method return True.
124
+
125
+ """
126
+ from topologicpy.Topology import Topology
127
+ if not isinstance(cluster, topologic.Topology):
128
+ return None
129
+ vertices = Topology.SubTopologies(cluster, subTopologyType="vertex")
130
+ return Vertex.AreIpsilateral(vertices, face)
131
+
132
+ @staticmethod
133
+ def ByCoordinates(x: float = 0, y: float = 0, z: float = 0) -> topologic.Vertex:
134
+ """
135
+ Creates a vertex at the coordinates specified by the x, y, z inputs.
136
+
137
+ Parameters
138
+ ----------
139
+ x : float , optional
140
+ The X coordinate. The default is 0.
141
+ y : float , optional
142
+ The Y coordinate. The default is 0.
143
+ z : float , optional
144
+ The Z coordinate. The defaults is 0.
145
+
146
+ Returns
147
+ -------
148
+ topologic.Vertex
149
+ The created vertex.
150
+
151
+ """
152
+ vertex = None
153
+ try:
154
+ vertex = topologic.Vertex.ByCoordinates(x, y, z)
155
+ except:
156
+ vertex = None
157
+ return vertex
158
+
159
+ @staticmethod
160
+ def Clockwise2D(vertices):
161
+ """
162
+ Sorts the input list of vertices in a clockwise fashion. This method assumes that the vertices are on the XY plane. The Z coordinate is ignored.
163
+
164
+ Parameters
165
+ -----------
166
+ vertices : list
167
+ The input list of vertices
168
+
169
+ Return
170
+ -----------
171
+ list
172
+ The input list of vertices sorted in a counter clockwise fashion
173
+
174
+ """
175
+ return list(reversed(Vertex.CounterClockwise2D(vertices)))
176
+
177
+ @staticmethod
178
+ def Coordinates(vertex: topologic.Vertex, outputType: str = "xyz", mantissa: int = 4) -> list:
179
+ """
180
+ Returns the coordinates of the input vertex.
181
+
182
+ Parameters
183
+ ----------
184
+ vertex : topologic.Vertex
185
+ The input vertex.
186
+ outputType : string, optional
187
+ The desired output type. Could be any permutation or substring of "xyz" or the string "matrix". The default is "xyz". The input is case insensitive and the coordinates will be returned in the specified order.
188
+ mantissa : int , optional
189
+ The desired length of the mantissa. The default is 4.
190
+
191
+ Returns
192
+ -------
193
+ list
194
+ The coordinates of the input vertex.
195
+
196
+ """
197
+ if not isinstance(vertex, topologic.Vertex):
198
+ return None
199
+ x = round(vertex.X(), mantissa)
200
+ y = round(vertex.Y(), mantissa)
201
+ z = round(vertex.Z(), mantissa)
202
+ matrix = [[1,0,0,x],
203
+ [0,1,0,y],
204
+ [0,0,1,z],
205
+ [0,0,0,1]]
206
+ output = []
207
+ outputType = outputType.lower()
208
+ if outputType == "matrix":
209
+ return matrix
210
+ else:
211
+ outputType = list(outputType)
212
+ for axis in outputType:
213
+ if axis == "x":
214
+ output.append(x)
215
+ elif axis == "y":
216
+ output.append(y)
217
+ elif axis == "z":
218
+ output.append(z)
219
+ return output
220
+
221
+ @staticmethod
222
+ def CounterClockwise2D(vertices):
223
+ """
224
+ Sorts the input list of vertices in a counterclockwise fashion. This method assumes that the vertices are on the XY plane. The Z coordinate is ignored.
225
+
226
+ Parameters
227
+ -----------
228
+ vertices : list
229
+ The input list of vertices
230
+
231
+ Return
232
+ -----------
233
+ list
234
+ The input list of vertices sorted in a counter clockwise fashion
235
+
236
+ """
237
+ import math
238
+ # find the centroid of the points
239
+ cx = sum(Vertex.X(v) for v in vertices) / len(vertices)
240
+ cy = sum(Vertex.Y(v) for v in vertices) / len(vertices)
241
+
242
+ # sort the points based on their angle with respect to the centroid
243
+ vertices.sort(key=lambda v: (math.atan2(Vertex.Y(v) - cy, Vertex.X(v) - cx) + 2 * math.pi) % (2 * math.pi))
244
+ return vertices
245
+
246
+ @staticmethod
247
+ def Distance(vertex: topologic.Vertex, topology: topologic.Topology, mantissa: int = 4) -> float:
248
+ """
249
+ Returns the distance between the input vertex and the input topology.
250
+
251
+ Parameters
252
+ ----------
253
+ vertex : topologic.Vertex
254
+ The input vertex.
255
+ topology : topologic.Topology
256
+ The input topology.
257
+ mantissa: int , optional
258
+ The desired length of the mantissa. The default is 4.
259
+
260
+ Returns
261
+ -------
262
+ float
263
+ The distance between the input vertex and the input topology.
264
+
265
+ """
266
+ if not isinstance(vertex, topologic.Vertex) or not isinstance(topology, topologic.Topology):
267
+ return None
268
+ return round(topologic.VertexUtility.Distance(vertex, topology), mantissa)
269
+
270
+ @staticmethod
271
+ def EnclosingCell(vertex: topologic.Vertex, topology: topologic.Topology, exclusive: bool = True, tolerance: float = 0.0001) -> list:
272
+ """
273
+ Returns the list of Cells found in the input topology that enclose the input vertex.
274
+
275
+ Parameters
276
+ ----------
277
+ vertex : topologic.Vertex
278
+ The input vertex.
279
+ topology : topologic.Topology
280
+ The input topology.
281
+ exclusive : bool , optional
282
+ If set to True, return only the first found enclosing cell. The default is True.
283
+ tolerance : float , optional
284
+ The tolerance for computing if the input vertex is enclosed in a cell. The default is 0.0001.
285
+
286
+ Returns
287
+ -------
288
+ list
289
+ The list of enclosing cells.
290
+
291
+ """
292
+
293
+ def boundingBox(cell):
294
+ vertices = []
295
+ _ = cell.Vertices(None, vertices)
296
+ x = []
297
+ y = []
298
+ z = []
299
+ for aVertex in vertices:
300
+ x.append(aVertex.X())
301
+ y.append(aVertex.Y())
302
+ z.append(aVertex.Z())
303
+ return ([min(x), min(y), min(z), max(x), max(y), max(z)])
304
+
305
+ if isinstance(topology, topologic.Cell):
306
+ cells = [topology]
307
+ elif isinstance(topology, topologic.Cluster) or isinstance(topology, topologic.CellComplex):
308
+ cells = []
309
+ _ = topology.Cells(None, cells)
310
+ else:
311
+ return None
312
+ if len(cells) < 1:
313
+ return None
314
+ enclosingCells = []
315
+ for i in range(len(cells)):
316
+ bbox = boundingBox(cells[i])
317
+ if ((vertex.X() < bbox[0]) or (vertex.Y() < bbox[1]) or (vertex.Z() < bbox[2]) or (vertex.X() > bbox[3]) or (vertex.Y() > bbox[4]) or (vertex.Z() > bbox[5])) == False:
318
+ if topologic.CellUtility.Contains(cells[i], vertex, tolerance) == 0:
319
+ if exclusive:
320
+ return([cells[i]])
321
+ else:
322
+ enclosingCells.append(cells[i])
323
+ return enclosingCells
324
+
325
+ @staticmethod
326
+ def Index(vertex: topologic.Vertex, vertices: list, strict: bool = False, tolerance: float = 0.0001) -> int:
327
+ """
328
+ Returns index of the input vertex in the input list of vertices
329
+
330
+ Parameters
331
+ ----------
332
+ vertex : topologic.Vertex
333
+ The input vertex.
334
+ vertices : list
335
+ The input list of vertices.
336
+ strict : bool , optional
337
+ If set to True, the vertex must be strictly identical to the one found in the list. Otherwise, a distance comparison is used. The default is False.
338
+ tolerance : float , optional
339
+ The tolerance for computing if the input vertex is enclosed in a cell. The default is 0.0001.
340
+
341
+ Returns
342
+ -------
343
+ int
344
+ The index of the input vertex in the input list of vertices.
345
+
346
+ """
347
+ from topologicpy.Topology import Topology
348
+ if not isinstance(vertex, topologic.Vertex):
349
+ return None
350
+ if not isinstance(vertices, list):
351
+ return None
352
+ vertices = [v for v in vertices if isinstance(v, topologic.Vertex)]
353
+ if len(vertices) == 0:
354
+ return None
355
+ for i in range(len(vertices)):
356
+ if strict:
357
+ if Topology.IsSame(vertex, vertices[i]):
358
+ return i
359
+ else:
360
+ d = Vertex.Distance(vertex, vertices[i])
361
+ if d < tolerance:
362
+ return i
363
+ return None
364
+
365
+ @staticmethod
366
+ def IsInside(vertex: topologic.Vertex, topology: topologic.Topology, tolerance: float = 0.0001) -> bool:
367
+ """
368
+ Returns True if the input vertex is inside the input topology. Returns False otherwise.
369
+
370
+ Parameters
371
+ ----------
372
+ vertex : topologic.Vertex
373
+ The input vertex.
374
+ topology : topologic.Topology
375
+ The input topology.
376
+ tolerance : float , optional
377
+ The tolerance for computing if the input vertex is enclosed in a cell. The default is 0.0001.
378
+
379
+ Returns
380
+ -------
381
+ bool
382
+ True if the input vertex is inside the input topology. False otherwise.
383
+
384
+ """
385
+ from topologicpy.Wire import Wire
386
+ from topologicpy.Shell import Shell
387
+ from topologicpy.CellComplex import CellComplex
388
+ from topologicpy.Cluster import Cluster
389
+ from topologicpy.Topology import Topology
390
+ if not isinstance(vertex, topologic.Vertex):
391
+ return None
392
+ if not isinstance(topology, topologic.Topology):
393
+ return None
394
+
395
+ if isinstance(topology, topologic.Vertex):
396
+ return topologic.VertexUtility.Distance(vertex, topology) < tolerance
397
+ elif isinstance(topology, topologic.Edge):
398
+ try:
399
+ parameter = topologic.EdgeUtility.ParameterAtPoint(topology, vertex)
400
+ except:
401
+ parameter = 400 #aribtrary large number greater than 1
402
+ return 0 <= parameter <= 1
403
+ elif isinstance(topology, topologic.Wire):
404
+ edges = Wire.Edges(topology)
405
+ for edge in edges:
406
+ if Vertex.IsInside(vertex, edge, tolerance):
407
+ return True
408
+ return False
409
+ elif isinstance(topology, topologic.Face):
410
+ return topologic.FaceUtility.IsInside(topology, vertex, tolerance)
411
+ elif isinstance(topology, topologic.Shell):
412
+ faces = Shell.Faces(topology)
413
+ for face in faces:
414
+ if Vertex.IsInside(vertex, face, tolerance):
415
+ return True
416
+ return False
417
+ elif isinstance(topology, topologic.Cell):
418
+ return topologic.CellUtility.Contains(topology, vertex, tolerance) == 0
419
+ elif isinstance(topology, topologic.CellComplex):
420
+ cells = CellComplex.Cells(topology)
421
+ faces = CellComplex.Faces(topology)
422
+ edges = CellComplex.Edges(topology)
423
+ vertices = CellComplex.Vertices(topology)
424
+ subtopologies = cells + faces + edges + vertices
425
+ for subtopology in subtopologies:
426
+ if Vertex.IsInside(vertex, subtopology, tolerance):
427
+ return True
428
+ return False
429
+ elif isinstance(topology, topologic.Cluster):
430
+ cells = Cluster.Cells(topology)
431
+ faces = Cluster.Faces(topology)
432
+ edges = Cluster.Edges(topology)
433
+ vertices = Cluster.Vertices(topology)
434
+ subtopologies = cells + faces + edges + vertices
435
+ for subtopology in subtopologies:
436
+ if Vertex.IsInside(vertex, subtopology, tolerance):
437
+ return True
438
+ return False
439
+ return False
440
+
441
+ @staticmethod
442
+ def NearestVertex(vertex: topologic.Vertex, topology: topologic.Topology, useKDTree: bool = True) -> topologic.Vertex:
443
+ """
444
+ Returns the vertex found in the input topology that is the nearest to the input vertex.
445
+
446
+ Parameters
447
+ ----------
448
+ vertex : topologic.Vertex
449
+ The input vertex.
450
+ topology : topologic.Topology
451
+ The input topology to be searched for the nearest vertex.
452
+ useKDTree : bool , optional
453
+ if set to True, the algorithm will use a KDTree method to search for the nearest vertex. The default is True.
454
+
455
+ Returns
456
+ -------
457
+ topologic.Vertex
458
+ The nearest vertex.
459
+
460
+ """
461
+ def SED(a, b):
462
+ """Compute the squared Euclidean distance between X and Y."""
463
+ p1 = (a.X(), a.Y(), a.Z())
464
+ p2 = (b.X(), b.Y(), b.Z())
465
+ return sum((i-j)**2 for i, j in zip(p1, p2))
466
+
467
+ BT = collections.namedtuple("BT", ["value", "left", "right"])
468
+ BT.__doc__ = """
469
+ A Binary Tree (BT) with a node value, and left- and
470
+ right-subtrees.
471
+ """
472
+ def firstItem(v):
473
+ return v.X()
474
+ def secondItem(v):
475
+ return v.Y()
476
+ def thirdItem(v):
477
+ return v.Z()
478
+
479
+ def itemAtIndex(v, index):
480
+ if index == 0:
481
+ return v.X()
482
+ elif index == 1:
483
+ return v.Y()
484
+ elif index == 2:
485
+ return v.Z()
486
+
487
+ def sortList(vertices, index):
488
+ if index == 0:
489
+ vertices.sort(key=firstItem)
490
+ elif index == 1:
491
+ vertices.sort(key=secondItem)
492
+ elif index == 2:
493
+ vertices.sort(key=thirdItem)
494
+ return vertices
495
+
496
+ def kdtree(topology):
497
+ assert isinstance(topology, topologic.Topology), "Vertex.NearestVertex: The input is not a Topology."
498
+ vertices = []
499
+ _ = topology.Vertices(None, vertices)
500
+ assert (len(vertices) > 0), "Vertex.NearestVertex: Could not find any vertices in the input Topology"
501
+
502
+ """Construct a k-d tree from an iterable of vertices.
503
+
504
+ This algorithm is taken from Wikipedia. For more details,
505
+
506
+ > https://en.wikipedia.org/wiki/K-d_tree#Construction
507
+
508
+ """
509
+ # k = len(points[0])
510
+ k = 3
511
+
512
+ def build(*, vertices, depth):
513
+ if len(vertices) == 0:
514
+ return None
515
+ #points.sort(key=operator.itemgetter(depth % k))
516
+ vertices = sortList(vertices, (depth % k))
517
+
518
+ middle = len(vertices) // 2
519
+
520
+ return BT(
521
+ value = vertices[middle],
522
+ left = build(
523
+ vertices=vertices[:middle],
524
+ depth=depth+1,
525
+ ),
526
+ right = build(
527
+ vertices=vertices[middle+1:],
528
+ depth=depth+1,
529
+ ),
530
+ )
531
+
532
+ return build(vertices=list(vertices), depth=0)
533
+
534
+ NNRecord = collections.namedtuple("NNRecord", ["vertex", "distance"])
535
+ NNRecord.__doc__ = """
536
+ Used to keep track of the current best guess during a nearest
537
+ neighbor search.
538
+ """
539
+
540
+ def find_nearest_neighbor(*, tree, vertex):
541
+ """Find the nearest neighbor in a k-d tree for a given vertex.
542
+ """
543
+ k = 3 # Forcing k to be 3 dimensional
544
+ best = None
545
+ def search(*, tree, depth):
546
+ """Recursively search through the k-d tree to find the nearest neighbor.
547
+ """
548
+ nonlocal best
549
+
550
+ if tree is None:
551
+ return
552
+ distance = SED(tree.value, vertex)
553
+ if best is None or distance < best.distance:
554
+ best = NNRecord(vertex=tree.value, distance=distance)
555
+
556
+ axis = depth % k
557
+ diff = itemAtIndex(vertex,axis) - itemAtIndex(tree.value,axis)
558
+ if diff <= 0:
559
+ close, away = tree.left, tree.right
560
+ else:
561
+ close, away = tree.right, tree.left
562
+
563
+ search(tree=close, depth=depth+1)
564
+ if diff**2 < best.distance:
565
+ search(tree=away, depth=depth+1)
566
+
567
+ search(tree=tree, depth=0)
568
+ return best.vertex
569
+
570
+ if useKDTree:
571
+ tree = kdtree(topology)
572
+ return find_nearest_neighbor(tree=tree, vertex=vertex)
573
+ else:
574
+ vertices = []
575
+ _ = topology.Vertices(None, vertices)
576
+ distances = []
577
+ indices = []
578
+ for i in range(len(vertices)):
579
+ distances.append(SED(vertex, vertices[i]))
580
+ indices.append(i)
581
+ sorted_indices = [x for _, x in sorted(zip(distances, indices))]
582
+ return vertices[sorted_indices[0]]
583
+
584
+ @staticmethod
585
+ def Origin() -> topologic.Vertex:
586
+ """
587
+ Returns a vertex with coordinates (0,0,0)
588
+
589
+ Parameters
590
+ -----------
591
+
592
+ Return
593
+ -----------
594
+ topologic.Vertex
595
+ """
596
+ return Vertex.ByCoordinates(0,0,0)
597
+
598
+ @staticmethod
599
+ def Project(vertex: topologic.Vertex, face: topologic.Face, direction: bool = None, mantissa: int = 4, tolerance: float = 0.0001) -> topologic.Vertex:
600
+ """
601
+ Returns a vertex that is the projection of the input vertex unto the input face.
602
+
603
+ Parameters
604
+ ----------
605
+ vertex : topologic.Vertex
606
+ The input vertex to project unto the input face.
607
+ face : topologic.Face
608
+ The input face that receives the projection of the input vertex.
609
+ direction : vector, optional
610
+ The direction in which to project the input vertex unto the input face. If not specified, the direction of the projection is the normal of the input face. The default is None.
611
+ mantissa : int , optional
612
+ The length of the desired mantissa. The default is 4.
613
+ tolerance : float , optional
614
+ The desired tolerance. The default is 0.0001.
615
+
616
+ Returns
617
+ -------
618
+ topologic.Vertex
619
+ The projected vertex.
620
+
621
+ """
622
+ from topologicpy.Edge import Edge
623
+ from topologicpy.Face import Face
624
+ from topologicpy.Topology import Topology
625
+ from topologicpy.Vector import Vector
626
+
627
+ if not isinstance(vertex, topologic.Vertex):
628
+ return None
629
+ if not isinstance(face, topologic.Face):
630
+ return None
631
+ if not direction:
632
+ direction = Vector.Reverse(Face.NormalAtParameters(face, 0.5, 0.5, "XYZ", mantissa))
633
+ if topologic.FaceUtility.IsInside(face, vertex, tolerance):
634
+ return vertex
635
+ d = topologic.VertexUtility.Distance(vertex, face)*10
636
+ far_vertex = topologic.TopologyUtility.Translate(vertex, direction[0]*d, direction[1]*d, direction[2]*d)
637
+ if topologic.VertexUtility.Distance(vertex, far_vertex) > tolerance:
638
+ e = topologic.Edge.ByStartVertexEndVertex(vertex, far_vertex)
639
+ pv = face.Intersect(e, False)
640
+ if not pv:
641
+ far_vertex = topologic.TopologyUtility.Translate(vertex, -direction[0]*d, -direction[1]*d, -direction[2]*d)
642
+ if topologic.VertexUtility.Distance(vertex, far_vertex) > tolerance:
643
+ e = topologic.Edge.ByStartVertexEndVertex(vertex, far_vertex)
644
+ pv = face.Intersect(e, False)
645
+ return pv
646
+ else:
647
+ return None
648
+
649
+ @staticmethod
650
+ def X(vertex: topologic.Vertex, mantissa: int = 4) -> float:
651
+ """
652
+ Returns the X coordinate of the input vertex.
653
+
654
+ Parameters
655
+ ----------
656
+ vertex : topologic.Vertex
657
+ The input vertex.
658
+ mantissa : int , optional
659
+ The desired length of the mantissa. The default is 4.
660
+
661
+ Returns
662
+ -------
663
+ float
664
+ The X coordinate of the input vertex.
665
+
666
+ """
667
+ if not isinstance(vertex, topologic.Vertex):
668
+ return None
669
+ return round(vertex.X(), mantissa)
670
+
671
+ @staticmethod
672
+ def Y(vertex: topologic.Vertex, mantissa: int = 4) -> float:
673
+ """
674
+ Returns the Y coordinate of the input vertex.
675
+
676
+ Parameters
677
+ ----------
678
+ vertex : topologic.Vertex
679
+ The input vertex.
680
+ mantissa : int , optional
681
+ The desired length of the mantissa. The default is 4.
682
+
683
+ Returns
684
+ -------
685
+ float
686
+ The Y coordinate of the input vertex.
687
+
688
+ """
689
+ if not isinstance(vertex, topologic.Vertex):
690
+ return None
691
+ return round(vertex.Y(), mantissa)
692
+
693
+ @staticmethod
694
+ def Z(vertex: topologic.Vertex, mantissa: int = 4) -> float:
695
+ """
696
+ Returns the Z coordinate of the input vertex.
697
+
698
+ Parameters
699
+ ----------
700
+ vertex : topologic.Vertex
701
+ The input vertex.
702
+ mantissa : int , optional
703
+ The desired length of the mantissa. The default is 4.
704
+
705
+ Returns
706
+ -------
707
+ float
708
+ The Z coordinate of the input vertex.
709
+
710
+ """
711
+ if not isinstance(vertex, topologic.Vertex):
712
+ return None
713
+ return round(vertex.Z(), mantissa)
714
+