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.
- topologicpy/Aperture.py +46 -0
- topologicpy/Cell.py +1780 -0
- topologicpy/CellComplex.py +791 -0
- topologicpy/Cluster.py +591 -0
- topologicpy/Color.py +157 -0
- topologicpy/Context.py +56 -0
- topologicpy/DGL.py +2661 -0
- topologicpy/Dictionary.py +470 -0
- topologicpy/Edge.py +855 -0
- topologicpy/EnergyModel.py +1052 -0
- topologicpy/Face.py +1810 -0
- topologicpy/Graph.py +3526 -0
- topologicpy/Graph_Export.py +858 -0
- topologicpy/Grid.py +338 -0
- topologicpy/Helper.py +182 -0
- topologicpy/Honeybee.py +424 -0
- topologicpy/Matrix.py +255 -0
- topologicpy/Neo4jGraph.py +311 -0
- topologicpy/Plotly.py +1396 -0
- topologicpy/Polyskel.py +524 -0
- topologicpy/Process.py +1368 -0
- topologicpy/SQL.py +48 -0
- topologicpy/Shell.py +1418 -0
- topologicpy/Speckle.py +433 -0
- topologicpy/Topology.py +5854 -0
- topologicpy/UnitTest.py +29 -0
- topologicpy/Vector.py +555 -0
- topologicpy/Vertex.py +714 -0
- topologicpy/Wire.py +2346 -0
- topologicpy/__init__.py +20 -0
- topologicpy/bin/linux/topologic/__init__.py +2 -0
- topologicpy/bin/linux/topologic/topologic.cpython-310-x86_64-linux-gnu.so +0 -0
- topologicpy/bin/linux/topologic/topologic.cpython-311-x86_64-linux-gnu.so +0 -0
- topologicpy/bin/linux/topologic/topologic.cpython-38-x86_64-linux-gnu.so +0 -0
- topologicpy/bin/linux/topologic/topologic.cpython-39-x86_64-linux-gnu.so +0 -0
- topologicpy/bin/linux/topologic.libs/libTKBO-6bdf205d.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKBRep-2960a069.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKBool-c44b74bd.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKFillet-9a670ba0.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKG2d-8f31849e.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKG3d-4c6bce57.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKGeomAlgo-26066fd9.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKGeomBase-2116cabe.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKMath-72572fa8.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKMesh-2a060427.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKOffset-6cab68ff.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKPrim-eb1262b3.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKShHealing-e67e5cc7.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKTopAlgo-e4c96c33.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libTKernel-fb7fe3b7.so.7.7.0 +0 -0
- topologicpy/bin/linux/topologic.libs/libgcc_s-32c1665e.so.1 +0 -0
- topologicpy/bin/linux/topologic.libs/libstdc++-672d7b41.so.6.0.30 +0 -0
- topologicpy/bin/windows/topologic/TKBO-f6b191de.dll +0 -0
- topologicpy/bin/windows/topologic/TKBRep-e56a600e.dll +0 -0
- topologicpy/bin/windows/topologic/TKBool-7b8d47ae.dll +0 -0
- topologicpy/bin/windows/topologic/TKFillet-0ddbf0a8.dll +0 -0
- topologicpy/bin/windows/topologic/TKG2d-2e2dee3d.dll +0 -0
- topologicpy/bin/windows/topologic/TKG3d-6674513d.dll +0 -0
- topologicpy/bin/windows/topologic/TKGeomAlgo-d240e370.dll +0 -0
- topologicpy/bin/windows/topologic/TKGeomBase-df87aba5.dll +0 -0
- topologicpy/bin/windows/topologic/TKMath-45bd625a.dll +0 -0
- topologicpy/bin/windows/topologic/TKMesh-d6e826b1.dll +0 -0
- topologicpy/bin/windows/topologic/TKOffset-79b9cc94.dll +0 -0
- topologicpy/bin/windows/topologic/TKPrim-aa430a86.dll +0 -0
- topologicpy/bin/windows/topologic/TKShHealing-bb48be89.dll +0 -0
- topologicpy/bin/windows/topologic/TKTopAlgo-7d0d1e22.dll +0 -0
- topologicpy/bin/windows/topologic/TKernel-08c8cfbb.dll +0 -0
- topologicpy/bin/windows/topologic/__init__.py +2 -0
- topologicpy/bin/windows/topologic/topologic.cp310-win_amd64.pyd +0 -0
- topologicpy/bin/windows/topologic/topologic.cp311-win_amd64.pyd +0 -0
- topologicpy/bin/windows/topologic/topologic.cp38-win_amd64.pyd +0 -0
- topologicpy/bin/windows/topologic/topologic.cp39-win_amd64.pyd +0 -0
- {topologicpy-0.4.8.dist-info → topologicpy-0.4.9.dist-info}/METADATA +1 -1
- topologicpy-0.4.9.dist-info/RECORD +77 -0
- topologicpy-0.4.9.dist-info/top_level.txt +1 -0
- topologicpy-0.4.8.dist-info/RECORD +0 -5
- topologicpy-0.4.8.dist-info/top_level.txt +0 -1
- {topologicpy-0.4.8.dist-info → topologicpy-0.4.9.dist-info}/LICENSE +0 -0
- {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
|
+
|