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/Wire.py ADDED
@@ -0,0 +1,2346 @@
1
+ from binascii import a2b_base64
2
+ from re import A
3
+ import topologicpy
4
+ import topologic
5
+ from topologicpy.Cluster import Cluster
6
+ from topologicpy.Topology import Topology
7
+ import math
8
+ import itertools
9
+ import numpy as np
10
+
11
+ class Wire(topologic.Wire):
12
+ @staticmethod
13
+ def BoundingRectangle(topology: topologic.Topology, optimize: int = 0) -> topologic.Wire:
14
+ """
15
+ Returns a wire representing a bounding rectangle of the input topology. The returned wire contains a dictionary with key "zrot" that represents rotations around the Z axis. If applied the resulting wire will become axis-aligned.
16
+
17
+ Parameters
18
+ ----------
19
+ topology : topologic.Topology
20
+ The input topology.
21
+ optimize : int , optional
22
+ If set to an integer from 1 (low optimization) to 10 (high optimization), the method will attempt to optimize the bounding rectangle so that it reduces its surface area. The default is 0 which will result in an axis-aligned bounding rectangle. The default is 0.
23
+
24
+ Returns
25
+ -------
26
+ topologic.Wire
27
+ The bounding rectangle of the input topology.
28
+
29
+ """
30
+ from topologicpy.Vertex import Vertex
31
+ from topologicpy.Wire import Wire
32
+ from topologicpy.Face import Face
33
+ from topologicpy.Cluster import Cluster
34
+ from topologicpy.Topology import Topology
35
+ from topologicpy.Dictionary import Dictionary
36
+ from random import sample
37
+
38
+ def br(topology):
39
+ vertices = []
40
+ _ = topology.Vertices(None, vertices)
41
+ x = []
42
+ y = []
43
+ for aVertex in vertices:
44
+ x.append(aVertex.X())
45
+ y.append(aVertex.Y())
46
+ minX = min(x)
47
+ minY = min(y)
48
+ maxX = max(x)
49
+ maxY = max(y)
50
+ return [minX, minY, maxX, maxY]
51
+
52
+ if not isinstance(topology, topologic.Topology):
53
+ return None
54
+
55
+ world_origin = Vertex.ByCoordinates(0,0,0)
56
+
57
+ xTran = None
58
+ # Create a sample face
59
+ while not xTran:
60
+ vertices = Topology.SubTopologies(topology=topology, subTopologyType="vertex")
61
+ v = sample(vertices, 3)
62
+ w = Wire.ByVertices(v)
63
+ f = Face.ByWire(w)
64
+ f = Face.Flatten(f)
65
+ dictionary = Topology.Dictionary(f)
66
+ xTran = Dictionary.ValueAtKey(dictionary,"xTran")
67
+ yTran = Dictionary.ValueAtKey(dictionary,"yTran")
68
+ zTran = Dictionary.ValueAtKey(dictionary,"zTran")
69
+ phi = Dictionary.ValueAtKey(dictionary,"phi")
70
+ theta = Dictionary.ValueAtKey(dictionary,"theta")
71
+
72
+ topology = Topology.Translate(topology, xTran*-1, yTran*-1, zTran*-1)
73
+ topology = Topology.Rotate(topology, origin=world_origin, x=0, y=0, z=1, degree=-phi)
74
+ topology = Topology.Rotate(topology, origin=world_origin, x=0, y=1, z=0, degree=-theta)
75
+
76
+ boundingRectangle = br(topology)
77
+ minX = boundingRectangle[0]
78
+ minY = boundingRectangle[1]
79
+ maxX = boundingRectangle[2]
80
+ maxY = boundingRectangle[3]
81
+ w = abs(maxX - minX)
82
+ l = abs(maxY - minY)
83
+ best_area = l*w
84
+ orig_area = best_area
85
+ best_z = 0
86
+ best_br = boundingRectangle
87
+ origin = Topology.Centroid(topology)
88
+ optimize = min(max(optimize, 0), 10)
89
+ if optimize > 0:
90
+ factor = (round(((11 - optimize)/30 + 0.57), 2))
91
+ flag = False
92
+ for n in range(10,0,-1):
93
+ if flag:
94
+ break
95
+ za = n
96
+ zb = 90+n
97
+ zc = n
98
+ for z in range(za,zb,zc):
99
+ if flag:
100
+ break
101
+ t = Topology.Rotate(topology, origin=origin, x=0,y=0,z=1, degree=z)
102
+ minX, minY, maxX, maxY = br(t)
103
+ w = abs(maxX - minX)
104
+ l = abs(maxY - minY)
105
+ area = l*w
106
+ if area < orig_area*factor:
107
+ best_area = area
108
+ best_z = z
109
+ best_br = [minX, minY, maxX, maxY]
110
+ flag = True
111
+ break
112
+ if area < best_area:
113
+ best_area = area
114
+ best_z = z
115
+ best_br = [minX, minY, maxX, maxY]
116
+
117
+ else:
118
+ best_br = boundingRectangle
119
+
120
+ minX, minY, maxX, maxY = best_br
121
+ vb1 = topologic.Vertex.ByCoordinates(minX, minY, 0)
122
+ vb2 = topologic.Vertex.ByCoordinates(maxX, minY, 0)
123
+ vb3 = topologic.Vertex.ByCoordinates(maxX, maxY, 0)
124
+ vb4 = topologic.Vertex.ByCoordinates(minX, maxY, 0)
125
+
126
+ boundingRectangle = Wire.ByVertices([vb1, vb2, vb3, vb4], close=True)
127
+ boundingRectangle = Topology.Rotate(boundingRectangle, origin=origin, x=0,y=0,z=1, degree=-best_z)
128
+ boundingRectangle = Topology.Rotate(boundingRectangle, origin=world_origin, x=0, y=1, z=0, degree=theta)
129
+ boundingRectangle = Topology.Rotate(boundingRectangle, origin=world_origin, x=0, y=0, z=1, degree=phi)
130
+ boundingRectangle = Topology.Translate(boundingRectangle, xTran, yTran, zTran)
131
+
132
+ dictionary = Dictionary.ByKeysValues(["zrot"], [best_z])
133
+ boundingRectangle = Topology.SetDictionary(boundingRectangle, dictionary)
134
+ return boundingRectangle
135
+
136
+ @staticmethod
137
+ def ByEdges(edges: list) -> topologic.Wire:
138
+ """
139
+ Creates a wire from the input list of edges.
140
+
141
+ Parameters
142
+ ----------
143
+ edges : list
144
+ The input list of edges.
145
+
146
+ Returns
147
+ -------
148
+ topologic.Wire
149
+ The created wire.
150
+
151
+ """
152
+ if not isinstance(edges, list):
153
+ return None
154
+ edgeList = [x for x in edges if isinstance(x, topologic.Edge)]
155
+ if len(edgeList) < 1:
156
+ return None
157
+ wire = None
158
+ for anEdge in edgeList:
159
+ if anEdge.Type() == 2:
160
+ if wire == None:
161
+ wire = topologic.Wire.ByEdges([anEdge])
162
+ else:
163
+ try:
164
+ wire = wire.Merge(anEdge)
165
+ except:
166
+ continue
167
+ if wire.Type() != 4:
168
+ wire = None
169
+ return wire
170
+
171
+ @staticmethod
172
+ def ByEdgesCluster(cluster: topologic.Cluster) -> topologic.Wire:
173
+ """
174
+ Creates a wire from the input cluster of edges.
175
+
176
+ Parameters
177
+ ----------
178
+ cluster : topologic.Cluster
179
+ The input cluster of edges.
180
+
181
+ Returns
182
+ -------
183
+ topologic.Wire
184
+ The created wire.
185
+
186
+ """
187
+ if not isinstance(cluster, topologic.Cluster):
188
+ return None
189
+ edges = []
190
+ _ = cluster.Edges(None, edges)
191
+ return Wire.ByEdges(edges)
192
+
193
+ @staticmethod
194
+ def ByOffset(wire: topologic.Wire, offset: float = 1.0, miter: bool = False, miterThreshold: float = None, offsetKey: str = None, miterThresholdKey: str = None, step: bool = True) -> topologic.Wire:
195
+ """
196
+ Creates an offset wire from the input wire.
197
+
198
+ Parameters
199
+ ----------
200
+ wire : topologic.Wire
201
+ The input wire.
202
+ offset : float , optional
203
+ The desired offset distance. The default is 1.0.
204
+ miter : bool , optional
205
+ if set to True, the corners will be mitered. The default is False.
206
+ miterThreshold : float , optional
207
+ The distance beyond which a miter should be added. The default is None which means the miter threshold is set to the offset distance multiplied by the square root of 2.
208
+ offsetKey : str , optional
209
+ If specified, the dictionary of the edges will be queried for this key to sepcify the desired offset. The default is None.
210
+ miterThresholdKey : str , optional
211
+ If specified, the dictionary of the vertices will be queried for this key to sepcify the desired miter threshold distance. The default is None.
212
+ step : bool , optional
213
+ If set to True, The transition between collinear edges with different offsets will be a step. Otherwise, it will be a continous edge. The default is True.
214
+
215
+ Returns
216
+ -------
217
+ topologic.Wire
218
+ The created wire.
219
+
220
+ """
221
+ from topologicpy.Vertex import Vertex
222
+ from topologicpy.Edge import Edge
223
+ from topologicpy.Face import Face
224
+ from topologicpy.Shell import Shell
225
+ from topologicpy.Cluster import Cluster
226
+ from topologicpy.Dictionary import Dictionary
227
+ from topologicpy.Vector import Vector
228
+ from random import randrange, sample
229
+
230
+ if not isinstance(wire, topologic.Wire):
231
+ return None
232
+ if not miterThreshold:
233
+ miterThreshold = offset*math.sqrt(2)
234
+ flatFace = Face.ByWire(wire)
235
+ flatFace = Face.Flatten(flatFace)
236
+
237
+ world_origin = Vertex.ByCoordinates(0,0,0)
238
+ # Retrieve the needed transformations
239
+ dictionary = Topology.Dictionary(flatFace)
240
+ xTran = Dictionary.ValueAtKey(dictionary,"xTran")
241
+ yTran = Dictionary.ValueAtKey(dictionary,"yTran")
242
+ zTran = Dictionary.ValueAtKey(dictionary,"zTran")
243
+ phi = Dictionary.ValueAtKey(dictionary,"phi")
244
+ theta = Dictionary.ValueAtKey(dictionary,"theta")
245
+
246
+
247
+
248
+ edges = Wire.Edges(wire)
249
+ vertices = Wire.Vertices(wire)
250
+ flatEdges = []
251
+ flatVertices = []
252
+ newEdges = []
253
+ for i in range(len(vertices)):
254
+ flatVertex = Topology.Translate(vertices[i], -xTran, -yTran, -zTran)
255
+ flatVertex = Topology.Rotate(flatVertex, origin=world_origin, x=0, y=0, z=1, degree=-phi)
256
+ flatVertex = Topology.Rotate(flatVertex, origin=world_origin, x=0, y=1, z=0, degree=-theta)
257
+ flatVertices.append(flatVertex)
258
+ vertices = flatVertices
259
+ for i in range(len(edges)):
260
+ flatEdge = Topology.Translate(edges[i], -xTran, -yTran, -zTran)
261
+ flatEdge = Topology.Rotate(flatEdge, origin=world_origin, x=0, y=0, z=1, degree=-phi)
262
+ flatEdge = Topology.Rotate(flatEdge, origin=world_origin, x=0, y=1, z=0, degree=-theta)
263
+ flatEdges.append(flatEdge)
264
+ if offsetKey:
265
+ d = Topology.Dictionary(edges[i])
266
+ value = Dictionary.ValueAtKey(d, key=offsetKey)
267
+ c = Topology.Centroid(flatEdge)
268
+ if value:
269
+ finalOffset = value
270
+ else:
271
+ finalOffset = offset
272
+ else:
273
+ finalOffset = offset
274
+ e1 = Edge.ByOffset2D(flatEdge,finalOffset)
275
+ newEdges.append(e1)
276
+ edges = flatEdges
277
+ newVertices = []
278
+ dupVertices = []
279
+ if Wire.IsClosed(wire):
280
+ e1 = newEdges[-1]
281
+ e2 = newEdges[0]
282
+ intV = Edge.Intersect2D(e1,e2)
283
+ if intV:
284
+ newVertices.append(intV)
285
+ dupVertices.append(vertices[0])
286
+ elif step:
287
+ edgeVertices= Edge.Vertices(e1)
288
+ newVertices.append(Vertex.NearestVertex(vertices[-1], Cluster.ByTopologies(edgeVertices), useKDTree=False))
289
+ edgeVertices= Edge.Vertices(e2)
290
+ newVertices.append(Vertex.NearestVertex(vertices[0], Cluster.ByTopologies(edgeVertices), useKDTree=False))
291
+ dupVertices.append(vertices[0])
292
+ dupVertices.append(vertices[0])
293
+ else:
294
+ tempEdge1 = Edge.ByVertices([Edge.StartVertex(e1), Edge.EndVertex(e2)])
295
+ normal = Edge.Normal(e1)
296
+ normal = [normal[0]*finalOffset*10, normal[1]*finalOffset*10, normal[2]*finalOffset*10]
297
+ tempV = Vertex.ByCoordinates(vertices[0].X()+normal[0], vertices[0].Y()+normal[1], vertices[0].Z()+normal[2])
298
+ tempEdge2 = Edge.ByVertices([vertices[0], tempV])
299
+ intV = Edge.Intersect2D(tempEdge1,tempEdge2)
300
+ newVertices.append(intV)
301
+ dupVertices.append(vertices[0])
302
+ else:
303
+ newVertices.append(Edge.StartVertex(newEdges[0]))
304
+
305
+ for i in range(len(newEdges)-1):
306
+ e1 = newEdges[i]
307
+ e2 = newEdges[i+1]
308
+ intV = Edge.Intersect2D(e1,e2)
309
+ if intV:
310
+ newVertices.append(intV)
311
+ dupVertices.append(vertices[i+1])
312
+ elif step:
313
+ newVertices.append(Edge.EndVertex(e1))
314
+ newVertices.append(Edge.StartVertex(e2))
315
+ dupVertices.append(vertices[i+1])
316
+ dupVertices.append(vertices[i+1])
317
+ else:
318
+ tempEdge1 = Edge.ByVertices([Edge.StartVertex(e1), Edge.EndVertex(e2)])
319
+ normal = Edge.Normal(e1)
320
+ normal = [normal[0]*finalOffset*10, normal[1]*finalOffset*10, normal[2]*finalOffset*10]
321
+ tempV = Vertex.ByCoordinates(vertices[i+1].X()+normal[0], vertices[i+1].Y()+normal[1], vertices[i+1].Z()+normal[2])
322
+ tempEdge2 = Edge.ByVertices([vertices[i+1], tempV])
323
+ intV = Edge.Intersect2D(tempEdge1,tempEdge2)
324
+ newVertices.append(intV)
325
+ dupVertices.append(vertices[i+1])
326
+
327
+ vertices = dupVertices
328
+ if not Wire.IsClosed(wire):
329
+ newVertices.append(Edge.EndVertex(newEdges[-1]))
330
+ newWire = Wire.ByVertices(newVertices, close=Wire.IsClosed(wire))
331
+
332
+ newVertices = Wire.Vertices(newWire)
333
+ newEdges = Wire.Edges(newWire)
334
+ miterEdges = []
335
+ cleanMiterEdges = []
336
+ # Handle miter
337
+ if miter:
338
+ for i in range(len(newVertices)):
339
+ if miterThresholdKey:
340
+ d = Topology.Dictionary(vertices[i])
341
+ value = Dictionary.ValueAtKey(d, key=miterThresholdKey)
342
+ if value:
343
+ finalMiterThreshold = value
344
+ else:
345
+ finalMiterThreshold = miterThreshold
346
+ else:
347
+ finalMiterThreshold = miterThreshold
348
+ if Vertex.Distance(vertices[i], newVertices[i]) > abs(finalMiterThreshold):
349
+ st = Topology.SuperTopologies(newVertices[i], newWire, topologyType="edge")
350
+ if len(st) > 1:
351
+ e1 = st[0]
352
+ e2 = st[1]
353
+ if not Edge.IsCollinear(e1, e2):
354
+ e1 = Edge.Reverse(e1)
355
+ bisector = Edge.ByVertices([vertices[i], newVertices[i]])
356
+ nv = Edge.VertexByDistance(bisector, distance=finalMiterThreshold, origin=Edge.StartVertex(bisector), tolerance=0.0001)
357
+ vec = Edge.Normal2D(bisector)
358
+ nv2 = Topology.Translate(nv, vec[0], vec[1], 0)
359
+ nv3 = Topology.Translate(nv, -vec[0], -vec[1], 0)
360
+ miterEdge = Edge.ByVertices([nv2,nv3])
361
+ if miterEdge:
362
+ miterEdge = Edge.SetLength(miterEdge, abs(offset)*10)
363
+ msv = Edge.Intersect2D(miterEdge, e1)
364
+ mev = Edge.Intersect2D(miterEdge, e2)
365
+ if (Topology.IsInside(e1, msv,tolerance=0.01) and (Topology.IsInside(e2, mev, tolerance=0.01))):
366
+ miterEdge = Edge.ByVertices([msv, mev])
367
+ if miterEdge:
368
+ cleanMiterEdges.append(miterEdge)
369
+ miterEdge = Edge.SetLength(miterEdge, Edge.Length(miterEdge)*1.02)
370
+ miterEdges.append(miterEdge)
371
+
372
+ c = Cluster.SelfMerge(Cluster.ByTopologies(newEdges+miterEdges))
373
+ vertices = Wire.Vertices(c)
374
+ subtractEdges = []
375
+ for v in vertices:
376
+ edges = Topology.SuperTopologies(v, c, topologyType="edge")
377
+ if len(edges) == 2:
378
+ if not Edge.IsCollinear(edges[0], edges[1]):
379
+ adjacentVertices = Topology.AdjacentTopologies(v, c)
380
+ total = 0
381
+ for adjV in adjacentVertices:
382
+ tempEdges = Topology.SuperTopologies(adjV, c, topologyType="edge")
383
+ total += len(tempEdges)
384
+ if total == 8:
385
+ subtractEdges = subtractEdges+edges
386
+
387
+ if len(subtractEdges) > 0:
388
+ newWire = Topology.Boolean(newWire, Cluster.ByTopologies(subtractEdges), operation="difference")
389
+ if len(cleanMiterEdges) > 0:
390
+ newWire = Topology.Boolean(newWire, Cluster.ByTopologies(cleanMiterEdges), operation="merge")
391
+
392
+ newWire = Topology.Rotate(newWire, origin=world_origin, x=0, y=1, z=0, degree=theta)
393
+ newWire = Topology.Rotate(newWire, origin=world_origin, x=0, y=0, z=1, degree=phi)
394
+ newWire = Topology.Translate(newWire, xTran, yTran, zTran)
395
+ return newWire
396
+
397
+ @staticmethod
398
+ def ByVertices(vertices: list, close: bool = True) -> topologic.Wire:
399
+ """
400
+ Creates a wire from the input list of vertices.
401
+
402
+ Parameters
403
+ ----------
404
+ vertices : list
405
+ the input list of vertices.
406
+ close : bool , optional
407
+ If True the last vertex will be connected to the first vertex to close the wire. The default is True.
408
+
409
+ Returns
410
+ -------
411
+ topologic.Wire
412
+ The created wire.
413
+
414
+ """
415
+ from topologicpy.Cluster import Cluster
416
+ if not isinstance(vertices, list):
417
+ return None
418
+ vertexList = [x for x in vertices if isinstance(x, topologic.Vertex)]
419
+ if len(vertexList) < 2:
420
+ return None
421
+ edges = []
422
+ for i in range(len(vertexList)-1):
423
+ v1 = vertexList[i]
424
+ v2 = vertexList[i+1]
425
+ try:
426
+ e = topologic.Edge.ByStartVertexEndVertex(v1, v2)
427
+ if e:
428
+ edges.append(e)
429
+ except:
430
+ continue
431
+ if close:
432
+ v1 = vertexList[-1]
433
+ v2 = vertexList[0]
434
+ try:
435
+ e = topologic.Edge.ByStartVertexEndVertex(v1, v2)
436
+ if e:
437
+ edges.append(e)
438
+ except:
439
+ pass
440
+ if len(edges) < 1:
441
+ return None
442
+ #return Wire.ByEdges(edges)
443
+ c = Cluster.ByTopologies(edges)
444
+ return Cluster.SelfMerge(c)
445
+
446
+ @staticmethod
447
+ def ByVerticesCluster(cluster: topologic.Cluster, close: bool = True) -> topologic.Wire:
448
+ """
449
+ Creates a wire from the input cluster of vertices.
450
+
451
+ Parameters
452
+ ----------
453
+ cluster : topologic.cluster
454
+ the input cluster of vertices.
455
+ close : bool , optional
456
+ If True the last vertex will be connected to the first vertex to close the wire. The default is True.
457
+
458
+ Returns
459
+ -------
460
+ topologic.Wire
461
+ The created wire.
462
+
463
+ """
464
+ if not isinstance(cluster, topologic.Cluster):
465
+ return None
466
+ vertices = []
467
+ _ = cluster.Vertices(None, vertices)
468
+ return Wire.ByVertices(vertices, close)
469
+
470
+ @staticmethod
471
+ def Circle(origin: topologic.Vertex = None, radius: float = 0.5, sides: int = 16, fromAngle: float = 0.0, toAngle: float = 360.0, close: bool = True, direction: list = [0,0,1], placement: str = "center", tolerance: float = 0.0001) -> topologic.Wire:
472
+ """
473
+ Creates a circle.
474
+
475
+ Parameters
476
+ ----------
477
+ origin : topologic.Vertex , optional
478
+ The location of the origin of the circle. The default is None which results in the circle being placed at (0,0,0).
479
+ radius : float , optional
480
+ The radius of the circle. The default is 0.5.
481
+ sides : int , optional
482
+ The number of sides of the circle. The default is 16.
483
+ fromAngle : float , optional
484
+ The angle in degrees from which to start creating the arc of the circle. The default is 0.
485
+ toAngle : float , optional
486
+ The angle in degrees at which to end creating the arc of the circle. The default is 360.
487
+ close : bool , optional
488
+ If set to True, arcs will be closed by connecting the last vertex to the first vertex. Otherwise, they will be left open.
489
+ direction : list , optional
490
+ The vector representing the up direction of the circle. The default is [0,0,1].
491
+ placement : str , optional
492
+ The description of the placement of the origin of the circle. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center".
493
+ tolerance : float , optional
494
+ The desired tolerance. The default is 0.0001.
495
+
496
+ Returns
497
+ -------
498
+ topologic.Wire
499
+ The created circle.
500
+
501
+ """
502
+ if not origin:
503
+ origin = topologic.Vertex.ByCoordinates(0,0,0)
504
+ if not isinstance(origin, topologic.Vertex):
505
+ return None
506
+ if not placement.lower() in ["center", "lowerleft", "upperleft", "lowerright", "upperright"]:
507
+ return None
508
+ radius = abs(radius)
509
+ if radius < tolerance:
510
+ return None
511
+
512
+ if (abs(direction[0]) + abs(direction[1]) + abs(direction[2])) < tolerance:
513
+ return None
514
+ baseV = []
515
+ xList = []
516
+ yList = []
517
+
518
+ if toAngle < fromAngle:
519
+ toAngle += 360
520
+ if abs(toAngle-fromAngle) < tolerance:
521
+ return None
522
+ angleRange = toAngle - fromAngle
523
+ fromAngle = math.radians(fromAngle)
524
+ toAngle = math.radians(toAngle)
525
+ sides = int(math.floor(sides))
526
+ for i in range(sides+1):
527
+ angle = fromAngle + math.radians(angleRange/sides)*i
528
+ x = math.sin(angle)*radius + origin.X()
529
+ y = math.cos(angle)*radius + origin.Y()
530
+ z = origin.Z()
531
+ xList.append(x)
532
+ yList.append(y)
533
+ baseV.append(topologic.Vertex.ByCoordinates(x,y,z))
534
+
535
+ baseWire = Wire.ByVertices(baseV[::-1], close) #reversing the list so that the normal points up in Blender
536
+
537
+ if placement.lower() == "lowerleft":
538
+ baseWire = topologic.TopologyUtility.Translate(baseWire, radius, radius, 0)
539
+ elif placement.lower() == "upperleft":
540
+ baseWire = topologic.TopologyUtility.Translate(baseWire, radius, -radius, 0)
541
+ elif placement.lower() == "lowerright":
542
+ baseWire = topologic.TopologyUtility.Translate(baseWire, -radius, radius, 0)
543
+ elif placement.lower() == "upperright":
544
+ baseWire = topologic.TopologyUtility.Translate(baseWire, -radius, -radius, 0)
545
+ x1 = origin.X()
546
+ y1 = origin.Y()
547
+ z1 = origin.Z()
548
+ x2 = origin.X() + direction[0]
549
+ y2 = origin.Y() + direction[1]
550
+ z2 = origin.Z() + direction[2]
551
+ dx = x2 - x1
552
+ dy = y2 - y1
553
+ dz = z2 - z1
554
+ dist = math.sqrt(dx**2 + dy**2 + dz**2)
555
+ phi = math.degrees(math.atan2(dy, dx)) # Rotation around Y-Axis
556
+ if dist < 0.0001:
557
+ theta = 0
558
+ else:
559
+ theta = math.degrees(math.acos(dz/dist)) # Rotation around Z-Axis
560
+ baseWire = topologic.TopologyUtility.Rotate(baseWire, origin, 0, 1, 0, theta)
561
+ baseWire = topologic.TopologyUtility.Rotate(baseWire, origin, 0, 0, 1, phi)
562
+ return baseWire
563
+
564
+ @staticmethod
565
+ def ConvexHull(topology):
566
+ """
567
+ Returns a wire representing the 2D convex hull of the input topology. The vertices of the topology are assumed to be coplanar.
568
+
569
+ Parameters
570
+ ----------
571
+ topology : topologic.Topology
572
+ The input topology.
573
+
574
+ Returns
575
+ -------
576
+ topologic.Wire
577
+ The convex hull of the input topology.
578
+
579
+ """
580
+ from topologicpy.Vertex import Vertex
581
+ from topologicpy.Face import Face
582
+ from topologicpy.Topology import Topology
583
+ from topologicpy.Dictionary import Dictionary
584
+ from random import sample
585
+
586
+
587
+ def Left_index(points):
588
+
589
+ '''
590
+ Finding the left most point
591
+ '''
592
+ minn = 0
593
+ for i in range(1,len(points)):
594
+ if points[i][0] < points[minn][0]:
595
+ minn = i
596
+ elif points[i][0] == points[minn][0]:
597
+ if points[i][1] > points[minn][1]:
598
+ minn = i
599
+ return minn
600
+
601
+ def orientation(p, q, r):
602
+ '''
603
+ To find orientation of ordered triplet (p, q, r).
604
+ The function returns following values
605
+ 0 --> p, q and r are collinear
606
+ 1 --> Clockwise
607
+ 2 --> Counterclockwise
608
+ '''
609
+ val = (q[1] - p[1]) * (r[0] - q[0]) - \
610
+ (q[0] - p[0]) * (r[1] - q[1])
611
+
612
+ if val == 0:
613
+ return 0
614
+ elif val > 0:
615
+ return 1
616
+ else:
617
+ return 2
618
+
619
+ def convex_hull(points, n):
620
+
621
+ # There must be at least 3 points
622
+ if n < 3:
623
+ return
624
+
625
+ # Find the leftmost point
626
+ l = Left_index(points)
627
+
628
+ hull = []
629
+
630
+ '''
631
+ Start from leftmost point, keep moving counterclockwise
632
+ until reach the start point again. This loop runs O(h)
633
+ times where h is number of points in result or output.
634
+ '''
635
+ p = l
636
+ q = 0
637
+ while(True):
638
+
639
+ # Add current point to result
640
+ hull.append(p)
641
+
642
+ '''
643
+ Search for a point 'q' such that orientation(p, q,
644
+ x) is counterclockwise for all points 'x'. The idea
645
+ is to keep track of last visited most counterclock-
646
+ wise point in q. If any point 'i' is more counterclock-
647
+ wise than q, then update q.
648
+ '''
649
+ q = (p + 1) % n
650
+
651
+ for i in range(n):
652
+
653
+ # If i is more counterclockwise
654
+ # than current q, then update q
655
+ if(orientation(points[p],
656
+ points[i], points[q]) == 2):
657
+ q = i
658
+
659
+ '''
660
+ Now q is the most counterclockwise with respect to p
661
+ Set p as q for next iteration, so that q is added to
662
+ result 'hull'
663
+ '''
664
+ p = q
665
+
666
+ # While we don't come to first point
667
+ if(p == l):
668
+ break
669
+
670
+ # Print Result
671
+ return hull
672
+
673
+
674
+ xTran = None
675
+ # Create a sample face and flatten
676
+ while not xTran:
677
+ vertices = Topology.SubTopologies(topology=topology, subTopologyType="vertex")
678
+ v = sample(vertices, 3)
679
+ w = Wire.ByVertices(v)
680
+ f = Face.ByWire(w)
681
+ f = Face.Flatten(f)
682
+ dictionary = Topology.Dictionary(f)
683
+ xTran = Dictionary.ValueAtKey(dictionary,"xTran")
684
+ yTran = Dictionary.ValueAtKey(dictionary,"yTran")
685
+ zTran = Dictionary.ValueAtKey(dictionary,"zTran")
686
+ phi = Dictionary.ValueAtKey(dictionary,"phi")
687
+ theta = Dictionary.ValueAtKey(dictionary,"theta")
688
+
689
+ world_origin = Vertex.Origin()
690
+ topology = Topology.Translate(topology, xTran*-1, yTran*-1, zTran*-1)
691
+ topology = Topology.Rotate(topology, origin=world_origin, x=0, y=0, z=1, degree=-phi)
692
+ topology = Topology.Rotate(topology, origin=world_origin, x=0, y=1, z=0, degree=-theta)
693
+
694
+ vertices = Topology.Vertices(topology)
695
+
696
+ points = []
697
+ for v in vertices:
698
+ points.append((Vertex.X(v), Vertex.Y(v)))
699
+ hull = convex_hull(points, len(points))
700
+
701
+ hull_vertices = []
702
+ for p in hull:
703
+ hull_vertices.append(Vertex.ByCoordinates(points[p][0], points[p][1], 0))
704
+
705
+ ch = Wire.ByVertices(hull_vertices)
706
+ ch = Topology.Rotate(ch, origin=world_origin, x=0, y=1, z=0, degree=theta)
707
+ ch = Topology.Rotate(ch, origin=world_origin, x=0, y=0, z=1, degree=phi)
708
+ ch = Topology.Translate(ch, xTran, yTran, zTran)
709
+ return ch
710
+
711
+ @staticmethod
712
+ def Cycles(wire: topologic.Wire, maxVertices: int = 4, tolerance: float = 0.0001) -> list:
713
+ """
714
+ Returns the closed circuits of wires found within the input wire.
715
+
716
+ Parameters
717
+ ----------
718
+ wire : topologic.Wire
719
+ The input wire.
720
+ maxVertices : int , optional
721
+ The maximum number of vertices of the circuits to be searched. The default is 4.
722
+ tolerance : float , optional
723
+ The desired tolerance. The default is 0.0001.
724
+
725
+ Returns
726
+ -------
727
+ list
728
+ The list of circuits (closed wires) found within the input wire.
729
+
730
+ """
731
+
732
+ def vIndex(v, vList, tolerance):
733
+ for i in range(len(vList)):
734
+ if topologic.VertexUtility.Distance(v, vList[i]) < tolerance:
735
+ return i+1
736
+ return None
737
+
738
+ # rotate cycle path such that it begins with the smallest node
739
+ def rotate_to_smallest(path):
740
+ n = path.index(min(path))
741
+ return path[n:]+path[:n]
742
+
743
+ def invert(path):
744
+ return rotate_to_smallest(path[::-1])
745
+
746
+ def isNew(cycles, path):
747
+ return not path in cycles
748
+
749
+ def visited(node, path):
750
+ return node in path
751
+
752
+ def findNewCycles(graph, cycles, path, maxVertices):
753
+ if len(path) > maxVertices:
754
+ return
755
+ start_node = path[0]
756
+ next_node= None
757
+ sub = []
758
+
759
+ #visit each edge and each node of each edge
760
+ for edge in graph:
761
+ node1, node2 = edge
762
+ if start_node in edge:
763
+ if node1 == start_node:
764
+ next_node = node2
765
+ else:
766
+ next_node = node1
767
+ if not visited(next_node, path):
768
+ # neighbor node not on path yet
769
+ sub = [next_node]
770
+ sub.extend(path)
771
+ # explore extended path
772
+ findNewCycles(graph, cycles, sub, maxVertices);
773
+ elif len(path) > 2 and next_node == path[-1]:
774
+ # cycle found
775
+ p = rotate_to_smallest(path);
776
+ inv = invert(p)
777
+ if isNew(cycles, p) and isNew(cycles, inv):
778
+ cycles.append(p)
779
+
780
+ def main(graph, cycles, maxVertices):
781
+ returnValue = []
782
+ for edge in graph:
783
+ for node in edge:
784
+ findNewCycles(graph, cycles, [node], maxVertices)
785
+ for cy in cycles:
786
+ row = []
787
+ for node in cy:
788
+ row.append(node)
789
+ returnValue.append(row)
790
+ return returnValue
791
+
792
+ tEdges = []
793
+ _ = wire.Edges(None, tEdges)
794
+ tVertices = []
795
+ _ = wire.Vertices(None, tVertices)
796
+ tVertices = tVertices
797
+
798
+ graph = []
799
+ for anEdge in tEdges:
800
+ graph.append([vIndex(anEdge.StartVertex(), tVertices, tolerance), vIndex(anEdge.EndVertex(), tVertices, tolerance)])
801
+
802
+ cycles = []
803
+ resultingCycles = main(graph, cycles, maxVertices)
804
+
805
+ result = []
806
+ for aRow in resultingCycles:
807
+ row = []
808
+ for anIndex in aRow:
809
+ row.append(tVertices[anIndex-1])
810
+ result.append(row)
811
+
812
+ resultWires = []
813
+ for i in range(len(result)):
814
+ c = result[i]
815
+ resultEdges = []
816
+ for j in range(len(c)-1):
817
+ v1 = c[j]
818
+ v2 = c[j+1]
819
+ e = topologic.Edge.ByStartVertexEndVertex(v1, v2)
820
+ resultEdges.append(e)
821
+ e = topologic.Edge.ByStartVertexEndVertex(c[len(c)-1], c[0])
822
+ resultEdges.append(e)
823
+ resultWire = topologic.Wire.ByEdges(resultEdges)
824
+ resultWires.append(resultWire)
825
+ return resultWires
826
+
827
+ @staticmethod
828
+ def Edges(wire: topologic.Wire) -> list:
829
+ """
830
+ Returns the edges of the input wire.
831
+
832
+ Parameters
833
+ ----------
834
+ wire : topologic.Wire
835
+ The input wire.
836
+
837
+ Returns
838
+ -------
839
+ list
840
+ The list of edges.
841
+
842
+ """
843
+ if not isinstance(wire, topologic.Wire):
844
+ return None
845
+ edges = []
846
+ _ = wire.Edges(None, edges)
847
+ return edges
848
+
849
+ @staticmethod
850
+ def Einstein(origin: topologic.Vertex = None, radius: float = 0.5, direction: list = [0,0,1], placement: str = "center") -> topologic.Wire:
851
+ """
852
+ Creates an aperiodic monotile, also called an 'einstein' tile (meaning one tile in German, not the name of the famous physist). See https://arxiv.org/abs/2303.10798
853
+
854
+ Parameters
855
+ ----------
856
+ origin : topologic.Vertex , optional
857
+ The location of the origin of the tile. The default is None which results in the tiles first vertex being placed at (0,0,0).
858
+ radius : float , optional
859
+ The radius of the hexagon determining the size of the tile. The default is 0.5.
860
+ direction : list , optional
861
+ The vector representing the up direction of the ellipse. The default is [0,0,1].
862
+ placement : str , optional
863
+ The description of the placement of the origin of the hexagon determining the location of the tile. This can be "center", or "lowerleft". It is case insensitive. The default is "center".
864
+
865
+ """
866
+ from topologicpy.Vertex import Vertex
867
+ from topologicpy.Topology import Topology
868
+ import math
869
+ def cos(angle):
870
+ return math.cos(math.radians(angle))
871
+ def sin(angle):
872
+ return math.sin(math.radians(angle))
873
+ if not origin:
874
+ origin = Vertex.ByCoordinates(0,0,0)
875
+ d = cos(30)*radius
876
+ v1 = Vertex.ByCoordinates(0,0,0)
877
+ v2 = Vertex.ByCoordinates(cos(30)*d, sin(30)*d, 0)
878
+ v3 = Vertex.ByCoordinates(radius, 0)
879
+ v4 = Vertex.ByCoordinates(2*radius, 0)
880
+ v5 = Vertex.ByCoordinates(2*radius+cos(60)*radius*0.5, sin(30)*d, 0)
881
+ v6 = Vertex.ByCoordinates(1.5*radius, d)
882
+ v7 = Vertex.ByCoordinates(1.5*radius, 2*d)
883
+ v8 = Vertex.ByCoordinates(radius, 2*d)
884
+ v9 = Vertex.ByCoordinates(radius-cos(60)*0.5*radius, 2*d+sin(60)*0.5*radius)
885
+ v10 = Vertex.ByCoordinates(0, 2*d)
886
+ v11 = Vertex.ByCoordinates(0, d)
887
+ v12 = Vertex.ByCoordinates(-radius*0.5, d)
888
+ v13 = Vertex.ByCoordinates(-cos(30)*d, sin(30)*d, 0)
889
+ einstein = Wire.ByVertices([v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13], close=True)
890
+
891
+ if placement.lower() == "lowerleft":
892
+ einstein = Topology.Translate(einstein, radius, d, 0)
893
+ dx = Vertex.X(origin)
894
+ dy = Vertex.Y(origin)
895
+ dz = Vertex.Z(origin)
896
+ einstein = Topology.Translate(einstein, dx, dy, dz)
897
+ if direction != [0,0,1]:
898
+ einstein = Topology.Orient(einstein, origin=origin, dirA=[0,0,1], dirB=direction)
899
+ return einstein
900
+
901
+ @staticmethod
902
+ def Ellipse(origin: topologic.Vertex = None, inputMode: int = 1, width: float = 2.0, length: float = 1.0, focalLength: float = 0.866025, eccentricity: float = 0.866025, majorAxisLength: float = 1.0, minorAxisLength: float = 0.5, sides: float = 32, fromAngle: float = 0.0, toAngle: float = 360.0, close: bool = True, direction: list = [0,0,1], placement: str = "center", tolerance: float = 0.0001) -> topologic.Wire:
903
+ """
904
+ Creates an ellipse and returns all its geometry and parameters.
905
+
906
+ Parameters
907
+ ----------
908
+ origin : topologic.Vertex , optional
909
+ The location of the origin of the ellipse. The default is None which results in the ellipse being placed at (0,0,0).
910
+ inputMode : int , optional
911
+ The method by wich the ellipse is defined. The default is 1.
912
+ Based on the inputMode value, only the following inputs will be considered. The options are:
913
+ 1. Width and Length (considered inputs: width, length)
914
+ 2. Focal Length and Eccentricity (considered inputs: focalLength, eccentricity)
915
+ 3. Focal Length and Minor Axis Length (considered inputs: focalLength, minorAxisLength)
916
+ 4. Major Axis Length and Minor Axis Length (considered input: majorAxisLength, minorAxisLength)
917
+ width : float , optional
918
+ The width of the ellipse. The default is 2.0. This is considered if the inputMode is 1.
919
+ length : float , optional
920
+ The length of the ellipse. The default is 1.0. This is considered if the inputMode is 1.
921
+ focalLength : float , optional
922
+ The focal length of the ellipse. The default is 0.866025. This is considered if the inputMode is 2 or 3.
923
+ eccentricity : float , optional
924
+ The eccentricity of the ellipse. The default is 0.866025. This is considered if the inputMode is 2.
925
+ majorAxisLength : float , optional
926
+ The length of the major axis of the ellipse. The default is 1.0. This is considered if the inputMode is 4.
927
+ minorAxisLength : float , optional
928
+ The length of the minor axis of the ellipse. The default is 0.5. This is considered if the inputMode is 3 or 4.
929
+ sides : int , optional
930
+ The number of sides of the ellipse. The default is 32.
931
+ fromAngle : float , optional
932
+ The angle in degrees from which to start creating the arc of the ellipse. The default is 0.
933
+ toAngle : float , optional
934
+ The angle in degrees at which to end creating the arc of the ellipse. The default is 360.
935
+ close : bool , optional
936
+ If set to True, arcs will be closed by connecting the last vertex to the first vertex. Otherwise, they will be left open.
937
+ direction : list , optional
938
+ The vector representing the up direction of the ellipse. The default is [0,0,1].
939
+ placement : str , optional
940
+ The description of the placement of the origin of the ellipse. This can be "center", or "lowerleft". It is case insensitive. The default is "center".
941
+ tolerance : float , optional
942
+ The desired tolerance. The default is 0.0001.
943
+
944
+ Returns
945
+ -------
946
+ topologic.Wire
947
+ The created ellipse
948
+
949
+ """
950
+ ellipseAll = Wire.EllipseAll(origin=origin, inputMode=inputMode, width=width, length=length, focalLength=focalLength, eccentricity=eccentricity, majorAxisLength=majorAxisLength, minorAxisLength=minorAxisLength, sides=sides, fromAngle=fromAngle, toAngle=toAngle, close=close, direction=direction, placement=placement, tolerance=tolerance)
951
+ return ellipseAll["ellipse"]
952
+
953
+ @staticmethod
954
+ def EllipseAll(origin: topologic.Vertex = None, inputMode: int = 1, width: float = 2.0, length: float = 1.0, focalLength: float = 0.866025, eccentricity: float = 0.866025, majorAxisLength: float = 1.0, minorAxisLength: float = 0.5, sides: int = 32, fromAngle: float = 0.0, toAngle: float = 360.0, close: bool = True, direction: list = [0,0,1], placement: str ="center", tolerance: float = 0.0001) -> topologic.Wire:
955
+ """
956
+ Creates an ellipse and returns all its geometry and parameters.
957
+
958
+ Parameters
959
+ ----------
960
+ origin : topologic.Vertex , optional
961
+ The location of the origin of the ellipse. The default is None which results in the ellipse being placed at (0,0,0).
962
+ inputMode : int , optional
963
+ The method by wich the ellipse is defined. The default is 1.
964
+ Based on the inputMode value, only the following inputs will be considered. The options are:
965
+ 1. Width and Length (considered inputs: width, length)
966
+ 2. Focal Length and Eccentricity (considered inputs: focalLength, eccentricity)
967
+ 3. Focal Length and Minor Axis Length (considered inputs: focalLength, minorAxisLength)
968
+ 4. Major Axis Length and Minor Axis Length (considered input: majorAxisLength, minorAxisLength)
969
+ width : float , optional
970
+ The width of the ellipse. The default is 2.0. This is considered if the inputMode is 1.
971
+ length : float , optional
972
+ The length of the ellipse. The default is 1.0. This is considered if the inputMode is 1.
973
+ focalLength : float , optional
974
+ The focal length of the ellipse. The default is 0.866025. This is considered if the inputMode is 2 or 3.
975
+ eccentricity : float , optional
976
+ The eccentricity of the ellipse. The default is 0.866025. This is considered if the inputMode is 2.
977
+ majorAxisLength : float , optional
978
+ The length of the major axis of the ellipse. The default is 1.0. This is considered if the inputMode is 4.
979
+ minorAxisLength : float , optional
980
+ The length of the minor axis of the ellipse. The default is 0.5. This is considered if the inputMode is 3 or 4.
981
+ sides : int , optional
982
+ The number of sides of the ellipse. The default is 32.
983
+ fromAngle : float , optional
984
+ The angle in degrees from which to start creating the arc of the ellipse. The default is 0.
985
+ toAngle : float , optional
986
+ The angle in degrees at which to end creating the arc of the ellipse. The default is 360.
987
+ close : bool , optional
988
+ If set to True, arcs will be closed by connecting the last vertex to the first vertex. Otherwise, they will be left open.
989
+ direction : list , optional
990
+ The vector representing the up direction of the ellipse. The default is [0,0,1].
991
+ placement : str , optional
992
+ The description of the placement of the origin of the ellipse. This can be "center", or "lowerleft". It is case insensitive. The default is "center".
993
+ tolerance : float , optional
994
+ The desired tolerance. The default is 0.0001.
995
+
996
+ Returns
997
+ -------
998
+ dictionary
999
+ A dictionary with the following keys and values:
1000
+ 1. "ellipse" : The ellipse (topologic.Wire)
1001
+ 2. "foci" : The two focal points (topologic.Cluster containing two vertices)
1002
+ 3. "a" : The major axis length
1003
+ 4. "b" : The minor axis length
1004
+ 5. "c" : The focal length
1005
+ 6. "e" : The eccentricity
1006
+ 7. "width" : The width
1007
+ 8. "length" : The length
1008
+
1009
+ """
1010
+ if not origin:
1011
+ origin = topologic.Vertex.ByCoordinates(0,0,0)
1012
+ if not isinstance(origin, topologic.Vertex):
1013
+ return None
1014
+ if inputMode not in [1,2,3,4]:
1015
+ return None
1016
+ if placement.lower() not in ["center", "lowerleft"]:
1017
+ return None
1018
+ if (abs(direction[0]) + abs(direction[1]) + abs(direction[2])) < tolerance:
1019
+ return None
1020
+ width = abs(width)
1021
+ length = abs(length)
1022
+ focalLength= abs(focalLength)
1023
+ eccentricity=abs(eccentricity)
1024
+ majorAxisLength=abs(majorAxisLength)
1025
+ minorAxisLength=abs(minorAxisLength)
1026
+ sides = abs(sides)
1027
+ if width < tolerance or length < tolerance or focalLength < tolerance or eccentricity < tolerance or majorAxisLength < tolerance or minorAxisLength < tolerance or sides < 3:
1028
+ return None
1029
+ if inputMode == 1:
1030
+ w = width
1031
+ l = length
1032
+ a = width/2
1033
+ b = length/2
1034
+ c = math.sqrt(abs(b**2 - a**2))
1035
+ e = c/a
1036
+ elif inputMode == 2:
1037
+ c = focalLength
1038
+ e = eccentricity
1039
+ a = c/e
1040
+ b = math.sqrt(abs(a**2 - c**2))
1041
+ w = a*2
1042
+ l = b*2
1043
+ elif inputMode == 3:
1044
+ c = focalLength
1045
+ b = minorAxisLength
1046
+ a = math.sqrt(abs(b**2 + c**2))
1047
+ e = c/a
1048
+ w = a*2
1049
+ l = b*2
1050
+ elif inputMode == 4:
1051
+ a = majorAxisLength
1052
+ b = minorAxisLength
1053
+ c = math.sqrt(abs(b**2 - a**2))
1054
+ e = c/a
1055
+ w = a*2
1056
+ l = b*2
1057
+ else:
1058
+ return None
1059
+ baseV = []
1060
+ xList = []
1061
+ yList = []
1062
+
1063
+ if toAngle < fromAngle:
1064
+ toAngle += 360
1065
+ if abs(toAngle - fromAngle) < tolerance:
1066
+ return None
1067
+
1068
+ angleRange = toAngle - fromAngle
1069
+ fromAngle = math.radians(fromAngle)
1070
+ toAngle = math.radians(toAngle)
1071
+ sides = int(math.floor(sides))
1072
+ for i in range(sides+1):
1073
+ angle = fromAngle + math.radians(angleRange/sides)*i
1074
+ x = math.sin(angle)*a + origin.X()
1075
+ y = math.cos(angle)*b + origin.Y()
1076
+ z = origin.Z()
1077
+ xList.append(x)
1078
+ yList.append(y)
1079
+ baseV.append(topologic.Vertex.ByCoordinates(x,y,z))
1080
+
1081
+ ellipse = Wire.ByVertices(baseV[::-1], close) #reversing the list so that the normal points up in Blender
1082
+
1083
+ if placement.lower() == "lowerleft":
1084
+ ellipse = topologic.TopologyUtility.Translate(ellipse, a, b, 0)
1085
+ x1 = origin.X()
1086
+ y1 = origin.Y()
1087
+ z1 = origin.Z()
1088
+ x2 = origin.X() + direction[0]
1089
+ y2 = origin.Y() + direction[1]
1090
+ z2 = origin.Z() + direction[2]
1091
+ dx = x2 - x1
1092
+ dy = y2 - y1
1093
+ dz = z2 - z1
1094
+ dist = math.sqrt(dx**2 + dy**2 + dz**2)
1095
+ phi = math.degrees(math.atan2(dy, dx)) # Rotation around Y-Axis
1096
+ if dist < 0.0001:
1097
+ theta = 0
1098
+ else:
1099
+ theta = math.degrees(math.acos(dz/dist)) # Rotation around Z-Axis
1100
+ ellipse = topologic.TopologyUtility.Rotate(ellipse, origin, 0, 1, 0, theta)
1101
+ ellipse = topologic.TopologyUtility.Rotate(ellipse, origin, 0, 0, 1, phi)
1102
+
1103
+ # Create a Cluster of the two foci
1104
+ v1 = topologic.Vertex.ByCoordinates(c+origin.X(), 0+origin.Y(),0)
1105
+ v2 = topologic.Vertex.ByCoordinates(-c+origin.X(), 0+origin.Y(),0)
1106
+ foci = topologic.Cluster.ByTopologies([v1, v2])
1107
+ if placement.lower() == "lowerleft":
1108
+ foci = topologic.TopologyUtility.Translate(foci, a, b, 0)
1109
+ foci = topologic.TopologyUtility.Rotate(foci, origin, 0, 1, 0, theta)
1110
+ foci = topologic.TopologyUtility.Rotate(foci, origin, 0, 0, 1, phi)
1111
+ d = {}
1112
+ d['ellipse'] = ellipse
1113
+ d['foci'] = foci
1114
+ d['a'] = a
1115
+ d['b'] = b
1116
+ d['c'] = c
1117
+ d['e'] = e
1118
+ d['w'] = w
1119
+ d['l'] = l
1120
+ return d
1121
+
1122
+ @staticmethod
1123
+ def Flatten(wire: topologic.Wire, oldLocation: topologic.Vertex =None, newLocation: topologic.Vertex = None, direction: list = None):
1124
+ """
1125
+ Flattens the input wire such that its center of mass is located at the origin and the specified direction is pointed in the positive Z axis.
1126
+
1127
+ Parameters
1128
+ ----------
1129
+ wire : topologic.Wire
1130
+ The input wire.
1131
+ oldLocation : topologic.Vertex , optional
1132
+ The old location to use as the origin of the movement. If set to None, the center of mass of the input topology is used. The default is None.
1133
+ newLocation : topologic.Vertex , optional
1134
+ The new location at which to place the topology. If set to None, the world origin (0,0,0) is used. The default is None.
1135
+ direction : list , optional
1136
+ The direction, expressed as a list of [X,Y,Z] that signifies the direction of the wire. If set to None, the positive ZAxis direction is considered the direction of the wire. The deafult is None.
1137
+
1138
+ Returns
1139
+ -------
1140
+ topologic.Wire
1141
+ The flattened wire.
1142
+
1143
+ """
1144
+ from topologicpy.Vertex import Vertex
1145
+ from topologicpy.Edge import Edge
1146
+ from topologicpy.Cluster import Cluster
1147
+ from topologicpy.Topology import Topology
1148
+ from topologicpy.Dictionary import Dictionary
1149
+ from topologicpy.Vector import Vector
1150
+ if not isinstance(wire, topologic.Wire):
1151
+ return None
1152
+ if direction == None:
1153
+ direction = Vector.ZAxis()
1154
+ if not isinstance(oldLocation, topologic.Vertex):
1155
+ oldLocation = Topology.CenterOfMass(wire)
1156
+ if not isinstance(newLocation, topologic.Vertex):
1157
+ newLocation = Vertex.ByCoordinates(0,0,0)
1158
+ cm = oldLocation
1159
+ world_origin = newLocation
1160
+
1161
+ x1 = Vertex.X(cm)
1162
+ y1 = Vertex.Y(cm)
1163
+ z1 = Vertex.Z(cm)
1164
+ x2 = Vertex.X(cm) + direction[0]
1165
+ y2 = Vertex.Y(cm) + direction[1]
1166
+ z2 = Vertex.Z(cm) + direction[2]
1167
+ dx = x2 - x1
1168
+ dy = y2 - y1
1169
+ dz = z2 - z1
1170
+ dist = math.sqrt(dx**2 + dy**2 + dz**2)
1171
+ phi = math.degrees(math.atan2(dy, dx)) # Rotation around Y-Axis
1172
+ if dist < 0.0001:
1173
+ theta = 0
1174
+ else:
1175
+ theta = math.degrees(math.acos(dz/dist)) # Rotation around Z-Axis
1176
+ flatWire = Topology.Translate(wire, -cm.X(), -cm.Y(), -cm.Z())
1177
+ flatWire = Topology.Rotate(flatWire, world_origin, 0, 0, 1, -phi)
1178
+ flatWire = Topology.Rotate(flatWire, world_origin, 0, 1, 0, -theta)
1179
+ # Ensure flatness. Force Z to be zero
1180
+ edges = Wire.Edges(flatWire)
1181
+ flatEdges = []
1182
+ for edge in edges:
1183
+ sv = Edge.StartVertex(edge)
1184
+ ev = Edge.EndVertex(edge)
1185
+ sv1 = Vertex.ByCoordinates(Vertex.X(sv), Vertex.Y(sv), 0)
1186
+ ev1 = Vertex.ByCoordinates(Vertex.X(ev), Vertex.Y(ev), 0)
1187
+ e1 = Edge.ByVertices([sv1, ev1])
1188
+ flatEdges.append(e1)
1189
+ flatWire = Topology.SelfMerge(Cluster.ByTopologies(flatEdges))
1190
+ dictionary = Dictionary.ByKeysValues(["xTran", "yTran", "zTran", "phi", "theta"], [cm.X(), cm.Y(), cm.Z(), phi, theta])
1191
+ flatWire = Topology.SetDictionary(flatWire, dictionary)
1192
+ return flatWire
1193
+
1194
+ @staticmethod
1195
+ def Interpolate(wires: list, n: int = 5, outputType: str = "default", replication: str = "default") -> topologic.Topology:
1196
+ """
1197
+ Creates *n* number of wires that interpolate between wireA and wireB.
1198
+
1199
+ Parameters
1200
+ ----------
1201
+ wireA : topologic.Wire
1202
+ The first input wire.
1203
+ wireB : topologic.Wire
1204
+ The second input wire.
1205
+ n : int , optional
1206
+ The number of intermediate wires to create. The default is 5.
1207
+ outputType : str , optional
1208
+ The desired type of output. The options are case insensitive. The default is "contour". The options are:
1209
+ - "Default" or "Contours" (wires are not connected)
1210
+ - "Raster or "Zigzag" or "Toolpath" (the wire ends are connected to create a continous path)
1211
+ - "Grid" (the wire ends are connected to create a grid).
1212
+ replication : str , optiona;
1213
+ The desired type of replication for wires with different number of vertices. It is case insensitive. The default is "default". The options are:
1214
+ - "Default" or "Repeat" which repeats the last vertex of the wire with the least number of vertices
1215
+ - "Nearest" which maps the vertices of one wire to the nearest vertex of the next wire creating a list of equal number of vertices.
1216
+ Returns
1217
+ -------
1218
+ toplogic.Topology
1219
+ The created interpolated wires as well as the input wires. The return type can be a topologic.Cluster or a topologic.Wire based on options.
1220
+
1221
+ """
1222
+
1223
+ from topologicpy.Vertex import Vertex
1224
+ from topologicpy.Edge import Edge
1225
+ from topologicpy.Face import Face
1226
+ from topologicpy.Cluster import Cluster
1227
+ from topologicpy.Helper import Helper
1228
+
1229
+ outputType = outputType.lower()
1230
+ if outputType not in ["default", "contours", "raster", "zigzag", "toolpath", "grid"]:
1231
+ return None
1232
+ if outputType == "default" or outputType == "contours":
1233
+ outputType = "contours"
1234
+ if outputType == "raster" or outputType == "zigzag" or outputType == "toolpath":
1235
+ outputType = "zigzag"
1236
+
1237
+ replication = replication.lower()
1238
+ if replication not in ["default", "nearest", "repeat"]:
1239
+ return None
1240
+
1241
+ def nearestVertex(v, vertices):
1242
+ distances = [Vertex.Distance(v, vertex) for vertex in vertices]
1243
+ return vertices[distances.index(sorted(distances)[0])]
1244
+
1245
+ def replicate(vertices, replication="default"):
1246
+ vertices = Helper.Repeat(vertices)
1247
+ finalList = vertices
1248
+ if replication == "nearest":
1249
+ finalList = [vertices[0]]
1250
+ for i in range(len(vertices)-1):
1251
+ loopA = vertices[i]
1252
+ loopB = vertices[i+1]
1253
+ nearestVertices = []
1254
+ for j in range(len(loopA)):
1255
+ #clusB = Cluster.ByTopologies(loopB)
1256
+ #nv = Vertex.NearestVertex(loopA[j], clusB, useKDTree=False)
1257
+ nv = nearestVertex(loopA[j], loopB)
1258
+ nearestVertices.append(nv)
1259
+ finalList.append(nearestVertices)
1260
+ return finalList
1261
+
1262
+ def process(verticesA, verticesB, n=5, outputType="contours", replication="repeat"):
1263
+ #if outputType == "zigzag" and Wire.IsClosed(wireA):
1264
+ #verticesA.append(verticesA[0])
1265
+ #verticesA, verticesB = replicate(verticesA=verticesA, verticesB=verticesB, replication=replication)
1266
+
1267
+ contours = [verticesA]
1268
+ for i in range(1, n+1):
1269
+ u = float(i)/float(n+1)
1270
+ temp_vertices = []
1271
+ for j in range(len(verticesA)):
1272
+ temp_v = Edge.VertexByParameter(Edge.ByVertices([verticesA[j], verticesB[j]]), u)
1273
+ temp_vertices.append(temp_v)
1274
+ contours.append(temp_vertices)
1275
+ return contours
1276
+
1277
+ if len(wires) < 2:
1278
+ return None
1279
+
1280
+ vertices = []
1281
+ for wire in wires:
1282
+ vertices.append(Topology.SubTopologies(wire, subTopologyType="vertex"))
1283
+ vertices = replicate(vertices, replication=replication)
1284
+ contours = []
1285
+
1286
+ finalWires = []
1287
+ for i in range(len(vertices)-1):
1288
+ verticesA = vertices[i]
1289
+ verticesB = vertices[i+1]
1290
+ contour = process(verticesA=verticesA, verticesB=verticesB, n=n, outputType=outputType, replication=replication)
1291
+ contours += contour
1292
+ for c in contour:
1293
+ finalWires.append(Wire.ByVertices(c, Wire.IsClosed(wires[i])))
1294
+
1295
+ contours.append(vertices[-1])
1296
+ finalWires.append(wires[-1])
1297
+ ridges = []
1298
+ if outputType == "grid" or outputType == "zigzag":
1299
+ for i in range(len(contours)-1):
1300
+ verticesA = contours[i]
1301
+ verticesB = contours[i+1]
1302
+ if outputType == "grid":
1303
+ for j in range(len(verticesA)):
1304
+ ridges.append(Edge.ByVertices([verticesA[j], verticesB[j]]))
1305
+ elif outputType == "zigzag":
1306
+ if i%2 == 0:
1307
+ sv = verticesA[-1]
1308
+ ev = verticesB[-1]
1309
+ ridges.append(Edge.ByVertices([sv, ev]))
1310
+ else:
1311
+ sv = verticesA[0]
1312
+ ev = verticesB[0]
1313
+ ridges.append(Edge.ByVertices([sv, ev]))
1314
+
1315
+ return Topology.SelfMerge(Cluster.ByTopologies(finalWires+ridges))
1316
+
1317
+ @staticmethod
1318
+ def Invert(wire: topologic.Wire) -> topologic.Wire:
1319
+ """
1320
+ Creates a wire that is an inverse (mirror) of the input wire.
1321
+
1322
+ Parameters
1323
+ ----------
1324
+ wire : topologic.Wire
1325
+ The input wire.
1326
+
1327
+ Returns
1328
+ -------
1329
+ topologic.Wire
1330
+ The inverted wire.
1331
+
1332
+ """
1333
+ if not isinstance(wire, topologic.Wire):
1334
+ return None
1335
+ vertices = Wire.Vertices(wire)
1336
+ reversed_vertices = vertices[::-1]
1337
+ return Wire.ByVertices(reversed_vertices)
1338
+
1339
+ @staticmethod
1340
+ def IsClosed(wire: topologic.Wire) -> bool:
1341
+ """
1342
+ Returns True if the input wire is closed. Returns False otherwise.
1343
+
1344
+ Parameters
1345
+ ----------
1346
+ wire : topologic.Wire
1347
+ The input wire.
1348
+
1349
+ Returns
1350
+ -------
1351
+ bool
1352
+ True if the input wire is closed. False otherwise.
1353
+
1354
+ """
1355
+ status = None
1356
+ if wire:
1357
+ if isinstance(wire, topologic.Wire):
1358
+ status = wire.IsClosed()
1359
+ return status
1360
+
1361
+ @staticmethod
1362
+ def IsInside(wire: topologic.Wire, vertex: topologic.Vertex, tolerance: float = 0.0001) -> bool:
1363
+ """
1364
+ Returns True if the input vertex is inside the input wire. Returns False otherwise.
1365
+
1366
+ Parameters
1367
+ ----------
1368
+ wire : topologic.Wire
1369
+ The input wire.
1370
+ vertex : topologic.Vertex
1371
+ The input Vertex.
1372
+ tolerance : float , optional
1373
+ The desired tolerance. The default is 0.0001.
1374
+
1375
+ Returns
1376
+ -------
1377
+ bool
1378
+ True if the input vertex is inside the input wire. False otherwise.
1379
+
1380
+ """
1381
+ from topologicpy.Vertex import Vertex
1382
+ from topologicpy.Edge import Edge
1383
+
1384
+ if not isinstance(wire, topologic.Wire):
1385
+ return None
1386
+ if not isinstance(vertex, topologic.Vertex):
1387
+ return None
1388
+ is_inside = False
1389
+ edges = Wire.Edges(wire)
1390
+ for edge in edges:
1391
+ if (Vertex.Distance(vertex, edge) <= tolerance):
1392
+ return True
1393
+ return False
1394
+
1395
+ @staticmethod
1396
+ def Isovist(wire: topologic.Wire, viewPoint: topologic.Vertex, obstaclesCluster: topologic.Cluster, tolerance: float = 0.0001) -> list:
1397
+ """
1398
+ Returns a list of faces representing the isovist projection from the input viewpoint.
1399
+
1400
+ Parameters
1401
+ ----------
1402
+ wire : topologic.Wire
1403
+ The wire representing the external boundary (border) of the isovist.
1404
+ viewPoint : topologic.Vertex
1405
+ The vertex representing the location of the viewpoint of the isovist.
1406
+ obstaclesCluster : topologic.Cluster
1407
+ A cluster of wires representing the obstacles within the externalBoundary.
1408
+
1409
+ Returns
1410
+ -------
1411
+ list
1412
+ A list of faces representing the isovist projection from the input viewpoint.
1413
+
1414
+ """
1415
+
1416
+ def vertexPartofFace(vertex, face, tolerance):
1417
+ vertices = []
1418
+ _ = face.Vertices(None, vertices)
1419
+ for v in vertices:
1420
+ if topologic.VertexUtility.Distance(vertex, v) < tolerance:
1421
+ return True
1422
+ return False
1423
+
1424
+ internalBoundaries = []
1425
+ _ = obstaclesCluster.Wires(None, internalBoundaries)
1426
+ internalVertices = []
1427
+ _ = obstaclesCluster.Vertices(None, internalVertices)
1428
+ # 1. Create a Face with external and internal boundaries
1429
+ face = topologic.Face.ByExternalInternalBoundaries(wire, internalBoundaries, False)
1430
+ # 2. Draw Rays from viewpoint through each Vertex of the obstacles extending to the External Boundary
1431
+ # 2.1 Get the Edges and Vertices of the External Boundary
1432
+ exBoundaryEdges = []
1433
+ _ = wire.Edges(None, exBoundaryEdges)
1434
+ exBoundaryVertices = []
1435
+ _ = wire.Vertices(None, exBoundaryVertices)
1436
+ testTopologies = exBoundaryEdges+exBoundaryVertices
1437
+ # 1.2 Find the maximum distance from the viewpoint to the edges and vertices of the external boundary
1438
+ distances = []
1439
+ for x in testTopologies:
1440
+ distances.append(topologic.VertexUtility.Distance(viewPoint, x))
1441
+ maxDistance = max(distances)*1.5
1442
+ # 1.3 Shoot rays and intersect with the external boundary
1443
+ rays = []
1444
+ for aVertex in (internalVertices+exBoundaryVertices):
1445
+ d = topologic.VertexUtility.Distance(viewPoint, aVertex)
1446
+ if d > tolerance:
1447
+ scaleFactor = maxDistance/d
1448
+ newV = topologic.TopologyUtility.Scale(aVertex, viewPoint, scaleFactor, scaleFactor, scaleFactor)
1449
+ try:
1450
+ ray = topologic.Edge.ByStartVertexEndVertex(viewPoint, newV)
1451
+ topologyC = ray.Intersect(wire, False)
1452
+ vertices = []
1453
+ _ = topologyC.Vertices(None, vertices)
1454
+ if topologyC:
1455
+ try:
1456
+ rays.append(topologic.Edge.ByStartVertexEndVertex(viewPoint, vertices[0]))
1457
+ except:
1458
+ pass
1459
+ try:
1460
+ rays.append(topologic.Edge.ByStartVertexEndVertex(viewPoint, aVertex))
1461
+ except:
1462
+ pass
1463
+ except:
1464
+ pass
1465
+ rayEdges = []
1466
+ for r in rays:
1467
+ a = r.Difference(obstaclesCluster, False)
1468
+ if a:
1469
+ edges = []
1470
+ _ = a.Edges(None, edges)
1471
+ w = None
1472
+ try:
1473
+ w = topologic.Wire.ByEdges(edges)
1474
+ rayEdges = rayEdges + edges
1475
+ except:
1476
+ c = topologic.Cluster.ByTopologies(edges)
1477
+ c = c.SelfMerge()
1478
+ wires = []
1479
+ _ = c.Wires(None, wires)
1480
+ if len(wires) > 0:
1481
+ edges = []
1482
+ _ = wires[0].Edges(None, edges)
1483
+ rayEdges = rayEdges + edges
1484
+ else:
1485
+ for e in edges:
1486
+ vertices = []
1487
+ e.Vertices(None, vertices)
1488
+ for v in vertices:
1489
+ if topologic.VertexUtility.Distance(viewPoint, v) < tolerance:
1490
+ rayEdges.append(e)
1491
+ rayCluster = topologic.Cluster.ByTopologies(rayEdges)
1492
+ #return rayCluster
1493
+ shell = face.Slice(rayCluster, False)
1494
+ faces = []
1495
+ _ = shell.Faces(None, faces)
1496
+ finalFaces = []
1497
+ for aFace in faces:
1498
+ if vertexPartofFace(viewPoint, aFace, 0.001):
1499
+ finalFaces.append(aFace)
1500
+ return finalFaces
1501
+
1502
+ @staticmethod
1503
+ def IsSimilar(wireA: topologic.Wire, wireB: topologic.Wire, angTolerance: float = 0.1, tolerance: float = 0.0001) -> bool:
1504
+ """
1505
+ Returns True if the input wires are similar. Returns False otherwise. The wires must be closed.
1506
+
1507
+ Parameters
1508
+ ----------
1509
+ wireA : topologic.Wire
1510
+ The first input wire.
1511
+ wireB : topologic.Wire
1512
+ The second input wire.
1513
+ angTolerance : float , optional
1514
+ The desired angular tolerance. The default is 0.1.
1515
+ tolerance : float , optional
1516
+ The desired tolerance. The default is 0.0001.
1517
+
1518
+ Returns
1519
+ -------
1520
+ bool
1521
+ True if the two input wires are similar. False otherwise.
1522
+
1523
+ """
1524
+
1525
+ def isCyclicallyEquivalent(u, v, lengthTolerance, angleTolerance):
1526
+ n, i, j = len(u), 0, 0
1527
+ if n != len(v):
1528
+ return False
1529
+ while i < n and j < n:
1530
+ if (i % 2) == 0:
1531
+ tol = lengthTolerance
1532
+ else:
1533
+ tol = angleTolerance
1534
+ k = 1
1535
+ while k <= n and math.fabs(u[(i + k) % n]- v[(j + k) % n]) <= tol:
1536
+ k += 1
1537
+ if k > n:
1538
+ return True
1539
+ if math.fabs(u[(i + k) % n]- v[(j + k) % n]) > tol:
1540
+ i += k
1541
+ else:
1542
+ j += k
1543
+ return False
1544
+
1545
+ def angleBetweenEdges(e1, e2, tolerance):
1546
+ a = e1.EndVertex().X() - e1.StartVertex().X()
1547
+ b = e1.EndVertex().Y() - e1.StartVertex().Y()
1548
+ c = e1.EndVertex().Z() - e1.StartVertex().Z()
1549
+ d = topologic.VertexUtility.Distance(e1.EndVertex(), e2.StartVertex())
1550
+ if d <= tolerance:
1551
+ d = e2.StartVertex().X() - e2.EndVertex().X()
1552
+ e = e2.StartVertex().Y() - e2.EndVertex().Y()
1553
+ f = e2.StartVertex().Z() - e2.EndVertex().Z()
1554
+ else:
1555
+ d = e2.EndVertex().X() - e2.StartVertex().X()
1556
+ e = e2.EndVertex().Y() - e2.StartVertex().Y()
1557
+ f = e2.EndVertex().Z() - e2.StartVertex().Z()
1558
+ dotProduct = a*d + b*e + c*f
1559
+ modOfVector1 = math.sqrt( a*a + b*b + c*c)*math.sqrt(d*d + e*e + f*f)
1560
+ angle = dotProduct/modOfVector1
1561
+ angleInDegrees = math.degrees(math.acos(angle))
1562
+ return angleInDegrees
1563
+
1564
+ def getInteriorAngles(edges, tolerance):
1565
+ angles = []
1566
+ for i in range(len(edges)-1):
1567
+ e1 = edges[i]
1568
+ e2 = edges[i+1]
1569
+ angles.append(angleBetweenEdges(e1, e2, tolerance))
1570
+ return angles
1571
+
1572
+ def getRep(edges, tolerance):
1573
+ angles = getInteriorAngles(edges, tolerance)
1574
+ lengths = []
1575
+ for anEdge in edges:
1576
+ lengths.append(topologic.EdgeUtility.Length(anEdge))
1577
+ minLength = min(lengths)
1578
+ normalisedLengths = []
1579
+ for aLength in lengths:
1580
+ normalisedLengths.append(aLength/minLength)
1581
+ return [x for x in itertools.chain(*itertools.zip_longest(normalisedLengths, angles)) if x is not None]
1582
+
1583
+ if (wireA.IsClosed() == False):
1584
+ return None
1585
+ if (wireB.IsClosed() == False):
1586
+ return None
1587
+ edgesA = []
1588
+ _ = wireA.Edges(None, edgesA)
1589
+ edgesB = []
1590
+ _ = wireB.Edges(None, edgesB)
1591
+ if len(edgesA) != len(edgesB):
1592
+ return False
1593
+ repA = getRep(list(edgesA), tolerance)
1594
+ repB = getRep(list(edgesB), tolerance)
1595
+ if isCyclicallyEquivalent(repA, repB, tolerance, angTolerance):
1596
+ return True
1597
+ if isCyclicallyEquivalent(repA, repB[::-1], tolerance, angTolerance):
1598
+ return True
1599
+ return False
1600
+
1601
+ @staticmethod
1602
+ def Length(wire: topologic.Wire, mantissa: int = 4) -> float:
1603
+ """
1604
+ Returns the length of the input wire.
1605
+
1606
+ Parameters
1607
+ ----------
1608
+ wire : topologic.Wire
1609
+ The input wire.
1610
+ mantissa : int , optional
1611
+ The desired length of the mantissa. The default is 4.
1612
+
1613
+ Returns
1614
+ -------
1615
+ float
1616
+ The length of the input wire.
1617
+
1618
+ """
1619
+ if not wire:
1620
+ return None
1621
+ if not isinstance(wire, topologic.Wire):
1622
+ return None
1623
+ totalLength = None
1624
+ try:
1625
+ edges = []
1626
+ _ = wire.Edges(None, edges)
1627
+ totalLength = 0
1628
+ for anEdge in edges:
1629
+ totalLength = totalLength + topologic.EdgeUtility.Length(anEdge)
1630
+ totalLength = round(totalLength, mantissa)
1631
+ except:
1632
+ totalLength = None
1633
+ return totalLength
1634
+
1635
+ @staticmethod
1636
+ def Planarize(wire: topologic.Wire) -> topologic.Wire:
1637
+ """
1638
+ Returns a planarized version of the input wire.
1639
+
1640
+ Parameters
1641
+ ----------
1642
+ wire : topologic.Wire
1643
+ The input wire.
1644
+
1645
+ Returns
1646
+ -------
1647
+ topologic.Wire
1648
+ The planarized wire.
1649
+
1650
+ """
1651
+ from topologicpy.Vertex import Vertex
1652
+ from topologicpy.Face import Face
1653
+ from topologicpy.Topology import Topology
1654
+ if not isinstance(wire, topologic.Wire):
1655
+ return None
1656
+ verts = []
1657
+ _ = wire.Vertices(None, verts)
1658
+ w = Wire.ByVertices([verts[0], verts[1], verts[2]], close=True)
1659
+ f = topologic.Face.ByExternalBoundary(w)
1660
+ f = Topology.Scale(f, f.Centroid(), 500,500,500)
1661
+ proj_verts = []
1662
+ direction = Face.NormalAtParameters(f)
1663
+ for v in verts:
1664
+ v = Vertex.ByCoordinates(v.X()+direction[0]*5, v.Y()+direction[1]*5, v.Z()+direction[2]*5)
1665
+ proj_verts.append(Vertex.Project(v, f))
1666
+ return Wire.ByVertices(proj_verts, close=True)
1667
+
1668
+ @staticmethod
1669
+ def Project(wire: topologic.Wire, face: topologic.Face, direction: list = None, mantissa: int = 4) -> topologic.Wire:
1670
+ """
1671
+ Creates a projection of the input wire unto the input face.
1672
+
1673
+ Parameters
1674
+ ----------
1675
+ wire : topologic.Wire
1676
+ The input wire.
1677
+ face : topologic.Face
1678
+ The face unto which to project the input wire.
1679
+ direction : list, optional
1680
+ The vector direction of the projection. If None, the reverse vector of the receiving face normal will be used. The default is None.
1681
+ mantissa : int , optional
1682
+ The desired length of the mantissa. The default is 4.
1683
+ tolerance : float , optional
1684
+ The desired tolerance. The default is 0.0001.
1685
+
1686
+ Returns
1687
+ -------
1688
+ topologic.Wire
1689
+ The projected wire.
1690
+
1691
+ """
1692
+ from topologicpy.Vertex import Vertex
1693
+ from topologicpy.Edge import Edge
1694
+ from topologicpy.Face import Face
1695
+ if not wire:
1696
+ return None
1697
+ if not isinstance(wire, topologic.Wire):
1698
+ return None
1699
+ if not face:
1700
+ return None
1701
+ if not isinstance(face, topologic.Face):
1702
+ return None
1703
+ if not direction:
1704
+ direction = -1*Face.NormalAtParameters(face, 0.5, 0.5, "XYZ", mantissa)
1705
+ large_face = Topology.Scale(face, face.CenterOfMass(), 500, 500, 500)
1706
+ edges = []
1707
+ _ = wire.Edges(None, edges)
1708
+ projected_edges = []
1709
+
1710
+ if large_face:
1711
+ if (large_face.Type() == Face.Type()):
1712
+ for edge in edges:
1713
+ if edge:
1714
+ if (edge.Type() == topologic.Edge.Type()):
1715
+ sv = edge.StartVertex()
1716
+ ev = edge.EndVertex()
1717
+
1718
+ psv = Vertex.Project(vertex=sv, face=large_face, direction=direction)
1719
+ pev = Vertex.Project(vertex=ev, face=large_face, direction=direction)
1720
+ if psv and pev:
1721
+ try:
1722
+ pe = Edge.ByVertices([psv, pev])
1723
+ projected_edges.append(pe)
1724
+ except:
1725
+ continue
1726
+ w = Wire.ByEdges(projected_edges)
1727
+ return w
1728
+
1729
+ @staticmethod
1730
+ def Rectangle(origin: topologic.Vertex = None, width: float = 1.0, length: float = 1.0, direction: list = [0,0,1], placement: str = "center", tolerance: float = 0.0001) -> topologic.Wire:
1731
+ """
1732
+ Creates a rectangle.
1733
+
1734
+ Parameters
1735
+ ----------
1736
+ origin : topologic.Vertex , optional
1737
+ The location of the origin of the rectangle. The default is None which results in the rectangle being placed at (0,0,0).
1738
+ width : float , optional
1739
+ The width of the rectangle. The default is 1.0.
1740
+ length : float , optional
1741
+ The length of the rectangle. The default is 1.0.
1742
+ direction : list , optional
1743
+ The vector representing the up direction of the rectangle. The default is [0,0,1].
1744
+ placement : str , optional
1745
+ The description of the placement of the origin of the rectangle. This can be "center", "lowerleft", "upperleft", "lowerright", "upperright". It is case insensitive. The default is "center".
1746
+ tolerance : float , optional
1747
+ The desired tolerance. The default is 0.0001.
1748
+
1749
+ Returns
1750
+ -------
1751
+ topologic.Wire
1752
+ The created rectangle.
1753
+
1754
+ """
1755
+ from topologicpy.Vertex import Vertex
1756
+ from topologicpy.Topology import Topology
1757
+ if not origin:
1758
+ origin = Vertex.ByCoordinates(0,0,0)
1759
+ if not isinstance(origin, topologic.Vertex):
1760
+ print("Wire.Rectangle - Error: specified origin is not a topologic vertex. Retruning None.")
1761
+ return None
1762
+ if not placement.lower() in ["center", "lowerleft", "upperleft", "lowerright", "upperright"]:
1763
+ print("Wire.Rectangle - Error: Could not find placement in the list of placements. Retruning None.")
1764
+ return None
1765
+ width = abs(width)
1766
+ length = abs(length)
1767
+ if width < tolerance or length < tolerance:
1768
+ print("Wire.Rectangle - Error: One or more of the specified dimensions is below the tolerance value. Retruning None.")
1769
+ return None
1770
+ if (abs(direction[0]) + abs(direction[1]) + abs(direction[2])) < tolerance:
1771
+ print("Wire.Rectangle - Error: The direction vector magnitude is below the tolerance value. Retruning None.")
1772
+ return None
1773
+ xOffset = 0
1774
+ yOffset = 0
1775
+ if placement.lower() == "lowerleft":
1776
+ xOffset = width*0.5
1777
+ yOffset = length*0.5
1778
+ elif placement.lower() == "upperleft":
1779
+ xOffset = width*0.5
1780
+ yOffset = -length*0.5
1781
+ elif placement.lower() == "lowerright":
1782
+ xOffset = -width*0.5
1783
+ yOffset = length*0.5
1784
+ elif placement.lower() == "upperright":
1785
+ xOffset = -width*0.5
1786
+ yOffset = -length*0.5
1787
+
1788
+ vb1 = Vertex.ByCoordinates(origin.X()-width*0.5+xOffset,origin.Y()-length*0.5+yOffset,origin.Z())
1789
+ vb2 = Vertex.ByCoordinates(origin.X()+width*0.5+xOffset,origin.Y()-length*0.5+yOffset,origin.Z())
1790
+ vb3 = Vertex.ByCoordinates(origin.X()+width*0.5+xOffset,origin.Y()+length*0.5+yOffset,origin.Z())
1791
+ vb4 = Vertex.ByCoordinates(origin.X()-width*0.5+xOffset,origin.Y()+length*0.5+yOffset,origin.Z())
1792
+
1793
+ baseWire = Wire.ByVertices([vb1, vb2, vb3, vb4], True)
1794
+ x1 = origin.X()
1795
+ y1 = origin.Y()
1796
+ z1 = origin.Z()
1797
+ x2 = origin.X() + direction[0]
1798
+ y2 = origin.Y() + direction[1]
1799
+ z2 = origin.Z() + direction[2]
1800
+ dx = x2 - x1
1801
+ dy = y2 - y1
1802
+ dz = z2 - z1
1803
+ dist = math.sqrt(dx**2 + dy**2 + dz**2)
1804
+ phi = math.degrees(math.atan2(dy, dx)) # Rotation around Y-Axis
1805
+ if dist < 0.0001:
1806
+ theta = 0
1807
+ else:
1808
+ theta = math.degrees(math.acos(dz/dist)) # Rotation around Z-Axis
1809
+ baseWire = Topology.Rotate(baseWire, origin, 0, 1, 0, theta)
1810
+ baseWire = Topology.Rotate(baseWire, origin, 0, 0, 1, phi)
1811
+ return baseWire
1812
+
1813
+ @staticmethod
1814
+ def RemoveCollinearEdges(wire: topologic.Wire, angTolerance: float = 0.1) -> topologic.Wire:
1815
+ """
1816
+ Removes any collinear edges in the input wire.
1817
+
1818
+ Parameters
1819
+ ----------
1820
+ wire : topologic.Wire
1821
+ The input wire.
1822
+ angTolerance : float , optional
1823
+ The desired angular tolerance. The default is 0.1.
1824
+
1825
+ Returns
1826
+ -------
1827
+ topologic.Wire
1828
+ The created wire without any collinear edges.
1829
+
1830
+ """
1831
+ from topologicpy.Edge import Edge
1832
+ from topologicpy.Wire import Wire
1833
+ from topologicpy.Topology import Topology
1834
+ def rce(wire, angTolerance=0.1):
1835
+ if not isinstance(wire, topologic.Wire):
1836
+ return None
1837
+ final_wire = None
1838
+ vertices = []
1839
+ wire_verts = []
1840
+ try:
1841
+ _ = wire.Vertices(None, vertices)
1842
+ except:
1843
+ return None
1844
+ for aVertex in vertices:
1845
+ edges = []
1846
+ _ = aVertex.Edges(wire, edges)
1847
+ if len(edges) > 1:
1848
+ if not Edge.IsCollinear(edges[0], edges[1], angTolerance=angTolerance):
1849
+ wire_verts.append(aVertex)
1850
+ else:
1851
+ wire_verts.append(aVertex)
1852
+ if len(wire_verts) > 2:
1853
+ if wire.IsClosed():
1854
+ final_wire = Wire.ByVertices(wire_verts, True)
1855
+ else:
1856
+ final_wire = Wire.ByVertices(wire_verts, False)
1857
+ elif len(wire_verts) == 2:
1858
+ final_wire = topologic.Edge.ByStartVertexEndVertex(wire_verts[0], wire_verts[1])
1859
+ return final_wire
1860
+
1861
+ if not topologic.Topology.IsManifold(wire, wire):
1862
+ wires = Wire.Split(wire)
1863
+ else:
1864
+ wires = [wire]
1865
+ returnWires = []
1866
+ for aWire in wires:
1867
+ if not isinstance(aWire, topologic.Wire):
1868
+ returnWires.append(aWire)
1869
+ else:
1870
+ returnWires.append(rce(aWire, angTolerance=angTolerance))
1871
+ if len(returnWires) == 1:
1872
+ returnWire = returnWires[0]
1873
+ if isinstance(returnWire, topologic.Edge):
1874
+ return Wire.ByEdges([returnWire])
1875
+ elif isinstance(returnWire, topologic.Wire):
1876
+ return returnWire
1877
+ else:
1878
+ return None
1879
+ elif len(returnWires) > 1:
1880
+ returnWire = topologic.Cluster.ByTopologies(returnWires).SelfMerge()
1881
+ if isinstance(returnWire, topologic.Edge):
1882
+ return Wire.ByEdges([returnWire])
1883
+ elif isinstance(returnWire, topologic.Wire):
1884
+ return returnWire
1885
+ else:
1886
+ return None
1887
+ else:
1888
+ return None
1889
+
1890
+ def Roof(face, degree=45, tolerance=0.001):
1891
+ """
1892
+ Creates a hipped roof through a straight skeleton. This method is contributed by 高熙鹏 xipeng gao <gaoxipeng1998@gmail.com>
1893
+ This algorithm depends on the polyskel code which is included in the library. Polyskel code is found at: https://github.com/Botffy/polyskel
1894
+
1895
+ Parameters
1896
+ ----------
1897
+ face : topologic.Face
1898
+ The input face.
1899
+ degree : float , optioal
1900
+ The desired angle in degrees of the roof. The default is 45.
1901
+ tolerance : float , optional
1902
+ The desired tolerance. The default is 0.001. (This is set to a larger number as it was found to work better)
1903
+
1904
+ Returns
1905
+ -------
1906
+ topologic.Wire
1907
+ The created roof. This method returns the roof as a set of edges. No faces are created.
1908
+
1909
+ """
1910
+ from topologicpy import Polyskel
1911
+ from topologicpy.Vertex import Vertex
1912
+ from topologicpy.Edge import Edge
1913
+ from topologicpy.Face import Face
1914
+ from topologicpy.Cluster import Cluster
1915
+ from topologicpy.Topology import Topology
1916
+ from topologicpy.Dictionary import Dictionary
1917
+ from topologicpy.Helper import Helper
1918
+ import topologic
1919
+ import math
1920
+
1921
+ def subtrees_to_edges(subtrees, polygon, slope):
1922
+ polygon_z = {}
1923
+ for x, y, z in polygon:
1924
+ polygon_z[(x, y)] = z
1925
+
1926
+ edges = []
1927
+ for subtree in subtrees:
1928
+ source = subtree.source
1929
+ height = subtree.height
1930
+ z = slope * height
1931
+ source_vertex = Vertex.ByCoordinates(source.x, source.y, z)
1932
+
1933
+ for sink in subtree.sinks:
1934
+ if (sink.x, sink.y) in polygon_z:
1935
+ z = 0
1936
+ else:
1937
+ z = None
1938
+ for st in subtrees:
1939
+ if st.source.x == sink.x and st.source.y == sink.y:
1940
+ z = slope * st.height
1941
+ break
1942
+ for sk in st.sinks:
1943
+ if sk.x == sink.x and sk.y == sink.y:
1944
+ z = slope * st.height
1945
+ break
1946
+ if z is None:
1947
+ height = subtree.height
1948
+ z = slope * height
1949
+ sink_vertex = Vertex.ByCoordinates(sink.x, sink.y, z)
1950
+ if (source.x, source.y) == (sink.x, sink.y):
1951
+ continue
1952
+ if Edge.ByStartVertexEndVertex(source_vertex, sink_vertex) not in edges:
1953
+ edges.append(Edge.ByStartVertexEndVertex(source_vertex, sink_vertex))
1954
+ return edges
1955
+
1956
+ def face_to_skeleton(face, degree=0):
1957
+ normal = Face.Normal(face)
1958
+ eb_wire = Face.ExternalBoundary(face)
1959
+ ib_wires = Face.InternalBoundaries(face)
1960
+ eb_vertices = Topology.Vertices(eb_wire)
1961
+ if normal[2] > 0:
1962
+ eb_vertices = list(reversed(eb_vertices))
1963
+ eb_polygon_coordinates = [(v.X(), v.Y(), v.Z()) for v in eb_vertices]
1964
+ eb_polygonxy = [(x[0], x[1]) for x in eb_polygon_coordinates]
1965
+
1966
+ ib_polygonsxy = []
1967
+ zero_coordinates = eb_polygon_coordinates
1968
+ for ib_wire in ib_wires:
1969
+ ib_vertices = Topology.Vertices(ib_wire)
1970
+ if normal[2] > 0:
1971
+ ib_vertices = list(reversed(ib_vertices))
1972
+ ib_polygon_coordinates = [(v.X(), v.Y(), v.Z()) for v in ib_vertices]
1973
+ ib_polygonxy = [(x[0], x[1]) for x in ib_polygon_coordinates]
1974
+ ib_polygonsxy.append(ib_polygonxy)
1975
+ zero_coordinates += ib_polygon_coordinates
1976
+ skeleton = Polyskel.skeletonize(eb_polygonxy, ib_polygonsxy)
1977
+ slope = math.tan(math.radians(degree))
1978
+ roofEdges = subtrees_to_edges(skeleton, zero_coordinates, slope)
1979
+ roofEdges = Helper.Flatten(roofEdges)+Topology.Edges(face)
1980
+ roofTopology = Topology.SelfMerge(Cluster.ByTopologies(roofEdges))
1981
+ return roofTopology
1982
+
1983
+ if not isinstance(face, topologic.Face):
1984
+ return None
1985
+ degree = abs(degree)
1986
+ if degree >= 90-tolerance:
1987
+ return None
1988
+ flat_face = Face.Flatten(face)
1989
+ d = Topology.Dictionary(flat_face)
1990
+ roof = face_to_skeleton(flat_face, degree)
1991
+ if not roof:
1992
+ return None
1993
+ xTran = Dictionary.ValueAtKey(d,"xTran")
1994
+ yTran = Dictionary.ValueAtKey(d,"yTran")
1995
+ zTran = Dictionary.ValueAtKey(d,"zTran")
1996
+ phi = Dictionary.ValueAtKey(d,"phi")
1997
+ theta = Dictionary.ValueAtKey(d,"theta")
1998
+ roof = Topology.Rotate(roof, origin=Vertex.Origin(), x=0, y=1, z=0, degree=theta)
1999
+ roof = Topology.Rotate(roof, origin=Vertex.Origin(), x=0, y=0, z=1, degree=phi)
2000
+ roof = Topology.Translate(roof, xTran, yTran, zTran)
2001
+ return roof
2002
+
2003
+ def Skeleton(face, tolerance=0.001):
2004
+ """
2005
+ Creates a straight skeleton. This method is contributed by 高熙鹏 xipeng gao <gaoxipeng1998@gmail.com>
2006
+ This algorithm depends on the polyskel code which is included in the library. Polyskel code is found at: https://github.com/Botffy/polyskel
2007
+
2008
+
2009
+ Parameters
2010
+ ----------
2011
+ face : topologic.Face
2012
+ The input face.
2013
+
2014
+ tolerance : float , optional
2015
+ The desired tolerance. The default is 0.001. (This is set to a larger number as it was found to work better)
2016
+
2017
+ Returns
2018
+ -------
2019
+ topologic.Wire
2020
+ The created straight skeleton.
2021
+
2022
+ """
2023
+ if not isinstance(face, topologic.Face):
2024
+ return None
2025
+ return Wire.Roof(face, degree=0, tolerance=tolerance)
2026
+
2027
+ @staticmethod
2028
+ def Split(wire: topologic.Wire) -> list:
2029
+ """
2030
+ Splits the input wire into segments at its intersections (i.e. at any vertex where more than two edges meet).
2031
+
2032
+ Parameters
2033
+ ----------
2034
+ wire : topologic.Wire
2035
+ The input wire.
2036
+
2037
+ Returns
2038
+ -------
2039
+ list
2040
+ The list of split wire segments.
2041
+
2042
+ """
2043
+
2044
+ def vertexDegree(v, wire):
2045
+ edges = []
2046
+ _ = v.Edges(wire, edges)
2047
+ return len(edges)
2048
+
2049
+ def vertexOtherEdge(vertex, edge, wire):
2050
+ edges = []
2051
+ _ = vertex.Edges(wire, edges)
2052
+ if topologic.Topology.IsSame(edges[0], edge):
2053
+ return edges[-1]
2054
+ else:
2055
+ return edges[0]
2056
+
2057
+ def edgeOtherVertex(edge, vertex):
2058
+ vertices = []
2059
+ _ = edge.Vertices(None, vertices)
2060
+ if topologic.Topology.IsSame(vertex, vertices[0]):
2061
+ return vertices[-1]
2062
+ else:
2063
+ return vertices[0]
2064
+
2065
+ def edgeInList(edge, edgeList):
2066
+ for anEdge in edgeList:
2067
+ if topologic.Topology.IsSame(anEdge, edge):
2068
+ return True
2069
+ return False
2070
+
2071
+ vertices = []
2072
+ _ = wire.Vertices(None, vertices)
2073
+ hubs = []
2074
+ for aVertex in vertices:
2075
+ if vertexDegree(aVertex, wire) > 2:
2076
+ hubs.append(aVertex)
2077
+ wires = []
2078
+ global_edges = []
2079
+ for aVertex in hubs:
2080
+ hub_edges = []
2081
+ _ = aVertex.Edges(wire, hub_edges)
2082
+ wire_edges = []
2083
+ for hub_edge in hub_edges:
2084
+ if not edgeInList(hub_edge, global_edges):
2085
+ current_edge = hub_edge
2086
+ oe = edgeOtherVertex(current_edge, aVertex)
2087
+ while vertexDegree(oe, wire) == 2:
2088
+ if not edgeInList(current_edge, global_edges):
2089
+ global_edges.append(current_edge)
2090
+ wire_edges.append(current_edge)
2091
+ current_edge = vertexOtherEdge(oe, current_edge, wire)
2092
+ oe = edgeOtherVertex(current_edge, oe)
2093
+ if not edgeInList(current_edge, global_edges):
2094
+ global_edges.append(current_edge)
2095
+ wire_edges.append(current_edge)
2096
+ if len(wire_edges) > 1:
2097
+ wires.append(topologic.Cluster.ByTopologies(wire_edges).SelfMerge())
2098
+ else:
2099
+ wires.append(wire_edges[0])
2100
+ wire_edges = []
2101
+ if len(wires) < 1:
2102
+ return [wire]
2103
+ return wires
2104
+
2105
+ @staticmethod
2106
+ def Square(origin: topologic.Vertex = None, size: float = 1.0, direction: list = [0,0,1], placement: str = "center", tolerance: float = 0.0001) -> topologic.Wire:
2107
+ """
2108
+ Creates a square.
2109
+
2110
+ Parameters
2111
+ ----------
2112
+ origin : topologic.Vertex , optional
2113
+ The location of the origin of the square. The default is None which results in the square being placed at (0,0,0).
2114
+ size : float , optional
2115
+ The size of the square. The default is 1.0.
2116
+ direction : list , optional
2117
+ The vector representing the up direction of the square. The default is [0,0,1].
2118
+ placement : str , optional
2119
+ The description of the placement of the origin of the square. This can be "center", "lowerleft", "upperleft", "lowerright", "upperright". It is case insensitive. The default is "center".
2120
+ tolerance : float , optional
2121
+ The desired tolerance. The default is 0.0001.
2122
+
2123
+ Returns
2124
+ -------
2125
+ topologic.Wire
2126
+ The created square.
2127
+
2128
+ """
2129
+ return Wire.Rectangle(origin = origin, width = size, length = size, direction = direction, placement = placement, tolerance = tolerance)
2130
+
2131
+ @staticmethod
2132
+ def Star(origin: topologic.Wire = None, radiusA: float = 0.5, radiusB: float = 0.2, rays: int = 8, direction: list = [0,0,1], placement: str = "center", tolerance: float = 0.0001) -> topologic.Wire:
2133
+ """
2134
+ Creates a star.
2135
+
2136
+ Parameters
2137
+ ----------
2138
+ origin : topologic.Vertex , optional
2139
+ The location of the origin of the star. The default is None which results in the star being placed at (0,0,0).
2140
+ radiusA : float , optional
2141
+ The outer radius of the star. The default is 1.0.
2142
+ radiusB : float , optional
2143
+ The outer radius of the star. The default is 0.4.
2144
+ rays : int , optional
2145
+ The number of star rays. The default is 8.
2146
+ direction : list , optional
2147
+ The vector representing the up direction of the star. The default is [0,0,1].
2148
+ placement : str , optional
2149
+ The description of the placement of the origin of the star. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center".
2150
+ tolerance : float , optional
2151
+ The desired tolerance. The default is 0.0001.
2152
+
2153
+ Returns
2154
+ -------
2155
+ topologic.Wire
2156
+ The created star.
2157
+
2158
+ """
2159
+
2160
+ if not origin:
2161
+ origin = topologic.Vertex.ByCoordinates(0,0,0)
2162
+ if not isinstance(origin, topologic.Vertex):
2163
+ return None
2164
+ radiusA = abs(radiusA)
2165
+ radiusB = abs(radiusB)
2166
+ if radiusA < tolerance or radiusB < tolerance:
2167
+ return None
2168
+ rays = abs(rays)
2169
+ if rays < 3:
2170
+ return None
2171
+ if not placement.lower() in ["center", "lowerleft", "upperleft", "lowerright", "upperright"]:
2172
+ return None
2173
+ sides = rays*2 # Sides is double the number of rays
2174
+ baseV = []
2175
+
2176
+ xList = []
2177
+ yList = []
2178
+ for i in range(sides):
2179
+ if i%2 == 0:
2180
+ radius = radiusA
2181
+ else:
2182
+ radius = radiusB
2183
+ angle = math.radians(360/sides)*i
2184
+ x = math.sin(angle)*radius + origin.X()
2185
+ y = math.cos(angle)*radius + origin.Y()
2186
+ z = origin.Z()
2187
+ xList.append(x)
2188
+ yList.append(y)
2189
+ baseV.append([x,y])
2190
+
2191
+ if placement.lower() == "lowerleft":
2192
+ xmin = min(xList)
2193
+ ymin = min(yList)
2194
+ xOffset = origin.X() - xmin
2195
+ yOffset = origin.Y() - ymin
2196
+ elif placement.lower() == "upperleft":
2197
+ xmin = min(xList)
2198
+ ymax = max(yList)
2199
+ xOffset = origin.X() - xmin
2200
+ yOffset = origin.Y() - ymax
2201
+ elif placement.lower() == "lowerright":
2202
+ xmax = max(xList)
2203
+ ymin = min(yList)
2204
+ xOffset = origin.X() - xmax
2205
+ yOffset = origin.Y() - ymin
2206
+ elif placement.lower() == "upperright":
2207
+ xmax = max(xList)
2208
+ ymax = max(yList)
2209
+ xOffset = origin.X() - xmax
2210
+ yOffset = origin.Y() - ymax
2211
+ else:
2212
+ xOffset = 0
2213
+ yOffset = 0
2214
+ tranBase = []
2215
+ for coord in baseV:
2216
+ tranBase.append(topologic.Vertex.ByCoordinates(coord[0]+xOffset, coord[1]+yOffset, origin.Z()))
2217
+
2218
+ baseWire = Wire.ByVertices(tranBase[::-1], True) #reversing the list so that the normal points up in Blender
2219
+
2220
+ x1 = origin.X()
2221
+ y1 = origin.Y()
2222
+ z1 = origin.Z()
2223
+ x2 = origin.X() + direction[0]
2224
+ y2 = origin.Y() + direction[1]
2225
+ z2 = origin.Z() + direction[2]
2226
+ dx = x2 - x1
2227
+ dy = y2 - y1
2228
+ dz = z2 - z1
2229
+ dist = math.sqrt(dx**2 + dy**2 + dz**2)
2230
+ phi = math.degrees(math.atan2(dy, dx)) # Rotation around Z-Axis
2231
+ if dist < 0.0001:
2232
+ theta = 0
2233
+ else:
2234
+ theta = math.degrees(math.acos(dz/dist)) # Rotation around Y-Axis
2235
+ baseWire = topologic.TopologyUtility.Rotate(baseWire, origin, 0, 1, 0, theta)
2236
+ baseWire = topologic.TopologyUtility.Rotate(baseWire, origin, 0, 0, 1, phi)
2237
+ return baseWire
2238
+
2239
+ @staticmethod
2240
+ def Trapezoid(origin: topologic.Vertex = None, widthA: float = 1.0, widthB: float = 0.75, offsetA: float = 0.0, offsetB: float = 0.0, length: float = 1.0, direction: list = [0,0,1], placement: str = "center", tolerance: float = 0.0001) -> topologic.Wire:
2241
+ """
2242
+ Creates a trapezoid.
2243
+
2244
+ Parameters
2245
+ ----------
2246
+ origin : topologic.Vertex , optional
2247
+ The location of the origin of the trapezoid. The default is None which results in the trapezoid being placed at (0,0,0).
2248
+ widthA : float , optional
2249
+ The width of the bottom edge of the trapezoid. The default is 1.0.
2250
+ widthB : float , optional
2251
+ The width of the top edge of the trapezoid. The default is 0.75.
2252
+ offsetA : float , optional
2253
+ The offset of the bottom edge of the trapezoid. The default is 0.0.
2254
+ offsetB : float , optional
2255
+ The offset of the top edge of the trapezoid. The default is 0.0.
2256
+ length : float , optional
2257
+ The length of the trapezoid. The default is 1.0.
2258
+ direction : list , optional
2259
+ The vector representing the up direction of the trapezoid. The default is [0,0,1].
2260
+ placement : str , optional
2261
+ The description of the placement of the origin of the trapezoid. This can be "center", or "lowerleft". It is case insensitive. The default is "center".
2262
+ tolerance : float , optional
2263
+ The desired tolerance. The default is 0.0001.
2264
+
2265
+ Returns
2266
+ -------
2267
+ topologic.Wire
2268
+ The created trapezoid.
2269
+
2270
+ """
2271
+ if not origin:
2272
+ origin = topologic.Vertex.ByCoordinates(0,0,0)
2273
+ if not isinstance(origin, topologic.Vertex):
2274
+ return None
2275
+ widthA = abs(widthA)
2276
+ widthB = abs(widthB)
2277
+ length = abs(length)
2278
+ if widthA < tolerance or widthB < tolerance or length < tolerance:
2279
+ return None
2280
+ if not placement.lower() in ["center", "lowerleft", "upperleft", "lowerright", "upperright"]:
2281
+ return None
2282
+ xOffset = 0
2283
+ yOffset = 0
2284
+ if placement.lower() == "center":
2285
+ xOffset = -((-widthA*0.5 + offsetA) + (-widthB*0.5 + offsetB) + (widthA*0.5 + offsetA) + (widthB*0.5 + offsetB))/4.0
2286
+ yOffset = 0
2287
+ elif placement.lower() == "lowerleft":
2288
+ xOffset = -(min((-widthA*0.5 + offsetA), (-widthB*0.5 + offsetB)))
2289
+ yOffset = length*0.5
2290
+ elif placement.lower() == "upperleft":
2291
+ xOffset = -(min((-widthA*0.5 + offsetA), (-widthB*0.5 + offsetB)))
2292
+ yOffset = -length*0.5
2293
+ elif placement.lower() == "lowerright":
2294
+ xOffset = -(max((widthA*0.5 + offsetA), (widthB*0.5 + offsetB)))
2295
+ yOffset = length*0.5
2296
+ elif placement.lower() == "upperright":
2297
+ xOffset = -(max((widthA*0.5 + offsetA), (widthB*0.5 + offsetB)))
2298
+ yOffset = -length*0.5
2299
+
2300
+ vb1 = topologic.Vertex.ByCoordinates(origin.X()-widthA*0.5+offsetA+xOffset,origin.Y()-length*0.5+yOffset,origin.Z())
2301
+ vb2 = topologic.Vertex.ByCoordinates(origin.X()+widthA*0.5+offsetA+xOffset,origin.Y()-length*0.5+yOffset,origin.Z())
2302
+ vb3 = topologic.Vertex.ByCoordinates(origin.X()+widthB*0.5+offsetB+xOffset,origin.Y()+length*0.5+yOffset,origin.Z())
2303
+ vb4 = topologic.Vertex.ByCoordinates(origin.X()-widthB*0.5++offsetB+xOffset,origin.Y()+length*0.5+yOffset,origin.Z())
2304
+
2305
+ baseWire = Wire.ByVertices([vb1, vb2, vb3, vb4], True)
2306
+ x1 = origin.X()
2307
+ y1 = origin.Y()
2308
+ z1 = origin.Z()
2309
+ x2 = origin.X() + direction[0]
2310
+ y2 = origin.Y() + direction[1]
2311
+ z2 = origin.Z() + direction[2]
2312
+ dx = x2 - x1
2313
+ dy = y2 - y1
2314
+ dz = z2 - z1
2315
+ dist = math.sqrt(dx**2 + dy**2 + dz**2)
2316
+ phi = math.degrees(math.atan2(dy, dx)) # Rotation around Y-Axis
2317
+ if dist < 0.0001:
2318
+ theta = 0
2319
+ else:
2320
+ theta = math.degrees(math.acos(dz/dist)) # Rotation around Z-Axis
2321
+ baseWire = topologic.TopologyUtility.Rotate(baseWire, origin, 0, 1, 0, theta)
2322
+ baseWire = topologic.TopologyUtility.Rotate(baseWire, origin, 0, 0, 1, phi)
2323
+ return baseWire
2324
+
2325
+ @staticmethod
2326
+ def Vertices(wire: topologic.Wire) -> list:
2327
+ """
2328
+ Returns the list of vertices of the input wire.
2329
+
2330
+ Parameters
2331
+ ----------
2332
+ wire : topologic.Wire
2333
+ The input wire.
2334
+
2335
+ Returns
2336
+ -------
2337
+ list
2338
+ The list of vertices.
2339
+
2340
+ """
2341
+ if not isinstance(wire, topologic.Wire):
2342
+ return None
2343
+ vertices = []
2344
+ _ = wire.Vertices(None, vertices)
2345
+ return vertices
2346
+